/* * nautilus-file.c: Nautilus file model. * * Copyright (C) 1999, 2000, 2001 Eazel, Inc. * * 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, see . * * Author: Darin Adler */ #define G_LOG_DOMAIN "nautilus-file" #include "nautilus-file.h" #ifndef NAUTILUS_COMPILATION #define NAUTILUS_COMPILATION #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nautilus-date-utilities.h" #include "nautilus-directory-notify.h" #include "nautilus-directory-private.h" #include "nautilus-enums.h" #include "nautilus-file-operations.h" #include "nautilus-file-private.h" #include "nautilus-file-undo-manager.h" #include "nautilus-file-undo-operations.h" #include "nautilus-file-utilities.h" #include "nautilus-filename-utilities.h" #include "nautilus-global-preferences.h" #include "nautilus-icon-info.h" #include "nautilus-metadata.h" #include "nautilus-mime-actions.h" #include "nautilus-module.h" #include "nautilus-scheme.h" #include "nautilus-signaller.h" #include "nautilus-tag-manager.h" #include "nautilus-thumbnails.h" #include "nautilus-ui-utilities.h" #include "nautilus-vfs-file.h" #ifdef HAVE_SELINUX #include #endif /* Time in seconds to cache getpwuid results */ #define GETPWUID_CACHE_TIME (5 * 60) #define ICON_NAME_THUMBNAIL_LOADING "image-loading" /* Emblems sometimes displayed for NautilusFiles. */ #define NAUTILUS_FILE_EMBLEM_NAME_SYMBOLIC_LINK "symbolic-link-symbolic" #define NAUTILUS_FILE_EMBLEM_NAME_CANT_READ "not-accessible-symbolic" #define NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE "readonly-symbolic" #undef NAUTILUS_FILE_DEBUG_REF #undef NAUTILUS_FILE_DEBUG_REF_VALGRIND #ifdef NAUTILUS_FILE_DEBUG_REF_VALGRIND #include #define DEBUG_REF_PRINTF VALGRIND_PRINTF_BACKTRACE #else #define DEBUG_REF_PRINTF printf #endif #define MEGA_TO_BASE_RATE 1000000 /* Files that start with these characters sort after files that don't. */ #define SORT_LAST_CHAR1 '.' #define SORT_LAST_CHAR2 '#' /* Name of Nautilus trash directories */ #define TRASH_DIRECTORY_NAME ".Trash" #define METADATA_ID_IS_LIST_MASK (1U << 31) typedef enum { SHOW_HIDDEN = 1 << 0, } FilterOptions; typedef enum { NAUTILUS_DATE_FORMAT_REGULAR = 0, NAUTILUS_DATE_FORMAT_FULL = 1, } NautilusDateFormat; typedef void (*ModifyListFunction) (GList **list, NautilusFile *file); enum { CHANGED, UPDATED_DEEP_COUNT_IN_PROGRESS, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static GHashTable *symbolic_links; static guint64 cached_thumbnail_limit; static NautilusSpeedTradeoffValue show_file_thumbs; static NautilusSpeedTradeoffValue show_directory_item_count; static GQuark attribute_name_q, attribute_size_q, attribute_type_q, attribute_detailed_type_q, attribute_modification_date_q, attribute_date_modified_q, attribute_date_modified_full_q, attribute_accessed_date_q, attribute_date_accessed_q, attribute_date_accessed_full_q, attribute_date_created_q, attribute_date_created_full_q, attribute_mime_type_q, attribute_size_detail_q, attribute_deep_size_q, attribute_deep_file_count_q, attribute_deep_directory_count_q, attribute_deep_total_count_q, attribute_search_relevance_q, attribute_trashed_on_q, attribute_trashed_on_full_q, attribute_trash_orig_path_q, attribute_recency_q, attribute_permissions_q, attribute_selinux_context_q, attribute_octal_permissions_q, attribute_owner_q, attribute_group_q, attribute_uri_q, attribute_where_q, attribute_link_target_q, attribute_volume_q, attribute_free_space_q, attribute_starred_q; static void nautilus_file_info_iface_init (NautilusFileInfoInterface *iface); static char *nautilus_file_get_owner_as_string (NautilusFile *file, gboolean include_real_name); static char *nautilus_file_get_type_as_string (NautilusFile *file); static const char *nautilus_file_get_type_as_string_no_extra_text (NautilusFile *file); static char *nautilus_file_get_detailed_type_as_string (NautilusFile *file); static gboolean update_info_and_name (NautilusFile *file, GFileInfo *info); static const char *nautilus_file_peek_display_name (NautilusFile *file); static const char *nautilus_file_peek_display_name_collation_key (NautilusFile *file); static void file_mount_unmounted (GMount *mount, gpointer data); static void metadata_hash_free (GHashTable *hash); G_DEFINE_TYPE_WITH_CODE (NautilusFile, nautilus_file, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_FILE_INFO, nautilus_file_info_iface_init) G_ADD_PRIVATE (NautilusFile)); enum { PROP_0, PROP_DIRECTORY, PROP_DISPLAY_NAME, PROP_A11Y_NAME, N_PROPS }; static GParamSpec *properties[N_PROPS] = { NULL, }; static void nautilus_file_init (NautilusFile *file) { file->details = nautilus_file_get_instance_private (file); nautilus_file_clear_info (file); nautilus_file_invalidate_extension_info_internal (file); file->details->free_space = -1; } static GObject * nautilus_file_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_params) { GObject *object; NautilusFile *file; object = (*G_OBJECT_CLASS (nautilus_file_parent_class)->constructor)(type, n_construct_properties, construct_params); file = NAUTILUS_FILE (object); /* Set to default type after full construction */ if (NAUTILUS_FILE_GET_CLASS (file)->default_file_type != G_FILE_TYPE_UNKNOWN) { file->details->type = NAUTILUS_FILE_GET_CLASS (file)->default_file_type; } return object; } gboolean nautilus_file_set_display_name (NautilusFile *file, const char *display_name, const char *edit_name, gboolean custom) { gboolean changed; if (custom && display_name == NULL) { /* We're re-setting a custom display name, invalidate it if * we already set it so that the old one is re-read */ if (file->details->got_custom_display_name) { file->details->got_custom_display_name = FALSE; nautilus_file_invalidate_attributes (file, NAUTILUS_ATTRIBUTE_INFO); } return FALSE; } if (display_name == NULL || *display_name == 0) { return FALSE; } if (!custom && file->details->got_custom_display_name) { return FALSE; } if (edit_name == NULL) { edit_name = display_name; } changed = FALSE; if (g_strcmp0 (file->details->display_name, display_name) != 0) { changed = TRUE; g_clear_pointer (&file->details->display_name, g_ref_string_release); if (g_strcmp0 (file->details->name, display_name) == 0) { file->details->display_name = g_ref_string_acquire (file->details->name); } else { file->details->display_name = g_ref_string_new (display_name); } g_free (file->details->display_name_collation_key); file->details->display_name_collation_key = g_utf8_collate_key_for_filename (display_name, -1); g_object_notify_by_pspec (G_OBJECT (file), properties[PROP_DISPLAY_NAME]); g_object_notify_by_pspec (G_OBJECT (file), properties[PROP_A11Y_NAME]); } if (g_strcmp0 (file->details->edit_name, edit_name) != 0) { changed = TRUE; g_clear_pointer (&file->details->edit_name, g_ref_string_release); if (g_strcmp0 (file->details->display_name, edit_name) == 0) { file->details->edit_name = g_ref_string_acquire (file->details->display_name); } else { file->details->edit_name = g_ref_string_new (edit_name); } } file->details->got_custom_display_name = custom; return changed; } static void nautilus_file_clear_display_name (NautilusFile *file) { g_clear_pointer (&file->details->display_name, g_ref_string_release); g_free (file->details->display_name_collation_key); file->details->display_name_collation_key = NULL; g_clear_pointer (&file->details->edit_name, g_ref_string_release); } static gboolean foreach_metadata_free (gpointer key, gpointer value, gpointer user_data) { guint id; id = GPOINTER_TO_UINT (key); if (id & METADATA_ID_IS_LIST_MASK) { g_strfreev ((char **) value); } else { g_free ((char *) value); } return TRUE; } static void metadata_hash_free (GHashTable *hash) { g_hash_table_foreach_remove (hash, foreach_metadata_free, NULL); g_hash_table_destroy (hash); } static gboolean _g_strv_equal (GStrv a, GStrv b) { if (g_strv_length (a) != g_strv_length (b)) { return FALSE; } for (int i = 0; a[i] != NULL; i++) { if (strcmp (a[i], b[i]) != 0) { return FALSE; } } return TRUE; } static gboolean metadata_hash_equal (GHashTable *hash1, GHashTable *hash2) { GHashTableIter iter; gpointer key1, value1, value2; guint id; if (hash1 == NULL && hash2 == NULL) { return TRUE; } if (hash1 == NULL || hash2 == NULL) { return FALSE; } if (g_hash_table_size (hash1) != g_hash_table_size (hash2)) { return FALSE; } g_hash_table_iter_init (&iter, hash1); while (g_hash_table_iter_next (&iter, &key1, &value1)) { value2 = g_hash_table_lookup (hash2, key1); if (value2 == NULL) { return FALSE; } id = GPOINTER_TO_UINT (key1); if (id & METADATA_ID_IS_LIST_MASK) { if (!_g_strv_equal ((char **) value1, (char **) value2)) { return FALSE; } } else { if (strcmp ((char *) value1, (char *) value2) != 0) { return FALSE; } } } return TRUE; } static void clear_metadata (NautilusFile *file) { if (file->details->metadata) { metadata_hash_free (file->details->metadata); file->details->metadata = NULL; } } static GHashTable * get_metadata_from_info (GFileInfo *info) { GHashTable *metadata; char **attrs; guint id; int i; GFileAttributeType type; gpointer value; attrs = g_file_info_list_attributes (info, "metadata"); metadata = g_hash_table_new (NULL, NULL); for (i = 0; attrs[i] != NULL; i++) { id = nautilus_metadata_get_id (attrs[i] + strlen ("metadata::")); if (id == 0) { continue; } if (!g_file_info_get_attribute_data (info, attrs[i], &type, &value, NULL)) { continue; } if (type == G_FILE_ATTRIBUTE_TYPE_STRING) { g_hash_table_insert (metadata, GUINT_TO_POINTER (id), g_strdup ((char *) value)); } else if (type == G_FILE_ATTRIBUTE_TYPE_STRINGV) { id |= METADATA_ID_IS_LIST_MASK; g_hash_table_insert (metadata, GUINT_TO_POINTER (id), g_strdupv ((char **) value)); } } g_strfreev (attrs); return metadata; } gboolean nautilus_file_update_metadata_from_info (NautilusFile *file, GFileInfo *info) { gboolean changed = FALSE; if (g_file_info_has_namespace (info, "metadata")) { GHashTable *metadata; metadata = get_metadata_from_info (info); if (!metadata_hash_equal (metadata, file->details->metadata)) { changed = TRUE; clear_metadata (file); file->details->metadata = metadata; } else { metadata_hash_free (metadata); } } else if (file->details->metadata) { changed = TRUE; clear_metadata (file); } return changed; } void nautilus_file_clear_info (NautilusFile *file) { file->details->got_file_info = FALSE; if (file->details->get_info_error) { g_error_free (file->details->get_info_error); file->details->get_info_error = NULL; } /* Reset to default type, which might be other than unknown for * special kinds of files like the desktop or a search directory */ file->details->type = NAUTILUS_FILE_GET_CLASS (file)->default_file_type; if (!file->details->got_custom_display_name) { nautilus_file_clear_display_name (file); } if (file->details->activation_uri != NULL) { g_free (file->details->activation_uri); file->details->activation_uri = NULL; } if (file->details->icon != NULL) { g_object_unref (file->details->icon); file->details->icon = NULL; } g_free (file->details->thumbnail_path); file->details->thumbnail_path = NULL; file->details->thumbnailing_failed = FALSE; file->details->is_symlink = FALSE; file->details->is_hidden = FALSE; file->details->is_mountpoint = FALSE; file->details->has_uid = FALSE; file->details->has_gid = FALSE; file->details->can_read = TRUE; file->details->can_write = TRUE; file->details->can_execute = TRUE; file->details->can_delete = TRUE; file->details->can_trash = TRUE; file->details->can_rename = TRUE; file->details->can_mount = FALSE; file->details->can_unmount = FALSE; file->details->can_eject = FALSE; file->details->can_start = FALSE; file->details->can_start_degraded = FALSE; file->details->can_stop = FALSE; file->details->start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; file->details->can_poll_for_media = FALSE; file->details->is_media_check_automatic = FALSE; file->details->has_permissions = FALSE; file->details->permissions = 0; file->details->size = -1; file->details->sort_order = 0; file->details->mtime = 0; file->details->atime = 0; file->details->btime = 0; file->details->trash_time = 0; file->details->recency = 0; g_free (file->details->symlink_name); file->details->symlink_name = NULL; g_clear_pointer (&file->details->mime_type, g_ref_string_release); g_free (file->details->selinux_context); file->details->selinux_context = NULL; g_clear_pointer (&file->details->owner, g_ref_string_release); g_clear_pointer (&file->details->owner_real, g_ref_string_release); g_clear_pointer (&file->details->group, g_ref_string_release); g_clear_pointer (&file->details->filesystem_id, g_ref_string_release); clear_metadata (file); } NautilusDirectory * nautilus_file_get_directory (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); return file->details->directory; } void nautilus_file_set_directory (NautilusFile *file, NautilusDirectory *directory) { if (!g_set_object (&file->details->directory, directory)) { return; } g_clear_pointer (&file->details->directory_name_collation_key, g_free); g_object_notify_by_pspec (G_OBJECT (file), properties[PROP_DIRECTORY]); } static inline const gchar * nautilus_file_get_directory_name (NautilusFile *file) { if (G_UNLIKELY (file->details->directory_name_collation_key == NULL)) { g_autofree char *parent_uri = nautilus_file_get_parent_uri (file); file->details->directory_name_collation_key = g_utf8_collate_key_for_filename (parent_uri, -1); } return file->details->directory_name_collation_key; } static NautilusFile * nautilus_file_new_self_owned (NautilusDirectory *directory) { g_autofree char *filename = nautilus_directory_get_name_for_self_as_new_file (directory); NautilusFile *file = nautilus_directory_new_as_file (directory); file->details->name = g_ref_string_new (filename); g_warn_if_fail (directory->details->as_file == NULL); directory->details->as_file = file; #ifdef NAUTILUS_FILE_DEBUG_REF DEBUG_REF_PRINTF ("%10p ref'd", file); #endif return file; } static NautilusFile * nautilus_file_new_from_filename (NautilusDirectory *directory, const char *filename) { g_assert (filename != NULL); g_assert (filename[0] != '\0'); /* Any not self-owned file must be a NautilusVfsFile */ NautilusFile *file = nautilus_directory_new_as_vfs_file (directory); file->details->name = g_ref_string_new (filename); #ifdef NAUTILUS_FILE_DEBUG_REF DEBUG_REF_PRINTF ("%10p ref'd", file); #endif nautilus_directory_add_file (directory, file); return file; } static void modify_link_hash_table (NautilusFile *file, ModifyListFunction modify_function) { char *target_uri; gboolean found; gpointer original_key; GList **list_ptr; /* Check if there is a symlink name. If none, we are OK. */ if (file->details->symlink_name == NULL || !nautilus_file_is_symbolic_link (file)) { return; } /* Create the hash table first time through. */ if (symbolic_links == NULL) { symbolic_links = g_hash_table_new (g_str_hash, g_str_equal); } target_uri = nautilus_file_get_symbolic_link_target_uri (file); /* Find the old contents of the hash table. */ found = g_hash_table_lookup_extended (symbolic_links, target_uri, &original_key, (gpointer *) &list_ptr); if (!found) { list_ptr = g_new0 (GList *, 1); original_key = g_strdup (target_uri); g_hash_table_insert (symbolic_links, original_key, list_ptr); } (*modify_function)(list_ptr, file); if (*list_ptr == NULL) { g_hash_table_remove (symbolic_links, target_uri); g_free (list_ptr); g_free (original_key); } g_free (target_uri); } static void symbolic_link_weak_notify (gpointer data, GObject *where_the_object_was) { GList **list = data; /* This really shouldn't happen, but we're seeing some strange things in * bug #358172 where the symlink hashtable isn't correctly updated. */ *list = g_list_remove (*list, where_the_object_was); } static void add_to_link_hash_table_list (GList **list, NautilusFile *file) { if (g_list_find (*list, file) != NULL) { g_warning ("Adding file to symlink_table multiple times. " "Please add feedback of what you were doing at http://bugzilla.gnome.org/show_bug.cgi?id=358172\n"); return; } g_object_weak_ref (G_OBJECT (file), symbolic_link_weak_notify, list); *list = g_list_prepend (*list, file); } static void add_to_link_hash_table (NautilusFile *file) { modify_link_hash_table (file, add_to_link_hash_table_list); } static void remove_from_link_hash_table_list (GList **list, NautilusFile *file) { if (g_list_find (*list, file) != NULL) { g_object_weak_unref (G_OBJECT (file), symbolic_link_weak_notify, list); *list = g_list_remove (*list, file); } } static void remove_from_link_hash_table (NautilusFile *file) { modify_link_hash_table (file, remove_from_link_hash_table_list); } NautilusFile * nautilus_file_new_from_info (NautilusDirectory *directory, GFileInfo *info) { g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (directory), NULL); g_return_val_if_fail (info != NULL, NULL); NautilusFile *file = nautilus_directory_new_as_vfs_file (directory); update_info_and_name (file, info); #ifdef NAUTILUS_FILE_DEBUG_REF DEBUG_REF_PRINTF ("%10p ref'd", file); #endif return file; } static NautilusFile * nautilus_file_get_internal (GFile *location, gboolean create) { g_autoptr (GFile) parent = g_file_get_parent (location); gboolean self_owned = (parent == NULL); GFile *dir_location = self_owned ? location : parent; /* Get object that represents the directory. */ g_autoptr (NautilusDirectory) directory = (create) ? nautilus_directory_get (dir_location) : nautilus_directory_get_existing (dir_location); if (self_owned) { if (directory != NULL && directory->details->as_file != NULL) { return nautilus_file_ref (directory->details->as_file); } else if (create) { return nautilus_file_new_self_owned (directory); } } else { g_autofree char *basename = g_file_get_basename (location); if (directory != NULL) { /* Check if file is already known */ NautilusFile *file = nautilus_directory_find_file_by_name (directory, basename); if (file != NULL) { return nautilus_file_ref (file); } } if (create) { return nautilus_file_new_from_filename (directory, basename); } } return NULL; } NautilusFile * nautilus_file_get (GFile *location) { g_return_val_if_fail (G_IS_FILE (location), NULL); return nautilus_file_get_internal (location, TRUE); } NautilusFile * nautilus_file_get_existing (GFile *location) { g_return_val_if_fail (G_IS_FILE (location), NULL); return nautilus_file_get_internal (location, FALSE); } NautilusFile * nautilus_file_get_existing_by_uri (const char *uri) { g_autoptr (GFile) location = NULL; location = g_file_new_for_uri (uri); return nautilus_file_get_existing (location); } NautilusFile * nautilus_file_get_by_uri (const char *uri) { g_autoptr (GFile) location = NULL; location = g_file_new_for_uri (uri); return nautilus_file_get (location); } gboolean nautilus_file_is_self_owned (NautilusFile *file) { return file->details->directory->details->as_file == file; } static void nautilus_file_dispose (GObject *object) { G_OBJECT_CLASS (nautilus_file_parent_class)->dispose (object); } static void finalize (GObject *object) { NautilusDirectory *directory; NautilusFile *file; file = NAUTILUS_FILE (object); g_assert (file->details->operations_in_progress == NULL); nautilus_async_destroying_file (file); remove_from_link_hash_table (file); directory = file->details->directory; if (nautilus_file_is_self_owned (file)) { directory->details->as_file = NULL; } else { if (!file->details->is_gone) { nautilus_directory_remove_file (directory, file); } } if (file->details->get_info_error) { g_error_free (file->details->get_info_error); } nautilus_directory_unref (directory); g_clear_pointer (&file->details->name, g_ref_string_release); g_clear_pointer (&file->details->display_name, g_ref_string_release); g_free (file->details->display_name_collation_key); g_free (file->details->directory_name_collation_key); g_clear_pointer (&file->details->edit_name, g_ref_string_release); if (file->details->icon) { g_object_unref (file->details->icon); } g_free (file->details->thumbnail_path); g_free (file->details->symlink_name); g_clear_pointer (&file->details->mime_type, g_ref_string_release); g_clear_pointer (&file->details->owner, g_ref_string_release); g_clear_pointer (&file->details->owner_real, g_ref_string_release); g_clear_pointer (&file->details->group, g_ref_string_release); g_free (file->details->selinux_context); g_free (file->details->activation_uri); if (file->details->thumbnail) { g_object_unref (file->details->thumbnail); } g_clear_object (&file->details->mount); g_clear_pointer (&file->details->filesystem_id, g_ref_string_release); g_free (file->details->trash_orig_path); g_list_free_full (file->details->extension_emblems, g_free); g_list_free_full (file->details->pending_info_providers, g_object_unref); if (file->details->extension_attributes) { g_hash_table_destroy (file->details->extension_attributes); } if (file->details->metadata) { metadata_hash_free (file->details->metadata); } g_free (file->details->fts_snippet); G_OBJECT_CLASS (nautilus_file_parent_class)->finalize (object); } NautilusFile * nautilus_file_ref (NautilusFile *file) { if (file == NULL) { return NULL; } g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); #ifdef NAUTILUS_FILE_DEBUG_REF DEBUG_REF_PRINTF ("%10p ref'd", file); #endif return g_object_ref (file); } void nautilus_file_unref (NautilusFile *file) { if (file == NULL) { return; } g_return_if_fail (NAUTILUS_IS_FILE (file)); #ifdef NAUTILUS_FILE_DEBUG_REF DEBUG_REF_PRINTF ("%10p unref'd", file); #endif g_object_unref (file); } /** * nautilus_file_get_parent_uri_for_display: * * Get the uri for the parent directory. * * @file: The file in question. * * Return value: A string representing the parent's location, * formatted for user display (including stripping "file://" * and adding trailing slash). * If the parent is NULL, returns the empty string. */ static char * nautilus_file_get_parent_uri_for_display (NautilusFile *file) { g_autoptr (GFile) parent = NULL; char *result; g_assert (NAUTILUS_IS_FILE (file)); parent = nautilus_file_get_parent_location (file); if (parent) { g_autofree gchar *parse_name = g_file_get_parse_name (parent); /* URI decode the string if it is a network connection*/ if (g_uri_is_valid (parse_name, G_URI_FLAGS_NONE, NULL)) { g_autofree gchar *temp = parse_name; parse_name = g_uri_unescape_string (temp, NULL); } /* Ensure a trailing slash to emphasize it is a directory */ if (g_str_has_suffix (parse_name, G_DIR_SEPARATOR_S)) { result = g_steal_pointer (&parse_name); } else { result = g_strconcat (parse_name, G_DIR_SEPARATOR_S, NULL); } } else { result = g_strdup (""); } return result; } /** * nautilus_file_can_read: * * Check whether the user is allowed to read the contents of this file. * * @file: The file to check. * * Return value: FALSE if the user is definitely not allowed to read * the contents of the file. If the user has read permission, or * the code can't tell whether the user has read permission, * returns TRUE (so failures must always be handled). */ gboolean nautilus_file_can_read (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_read; } /** * nautilus_file_can_execute: * * Check whether the user is allowed to execute this file. * * @file: The file to check. * * Return value: FALSE if the user is definitely not allowed to execute * the file. If the user has execute permission, or * the code can't tell whether the user has execute permission, * returns TRUE (so failures must always be handled). */ gboolean nautilus_file_can_execute (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_execute; } gboolean nautilus_file_can_mount (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_mount; } gboolean nautilus_file_can_unmount (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_unmount || (file->details->mount != NULL && g_mount_can_unmount (file->details->mount)); } gboolean nautilus_file_can_eject (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_eject || (file->details->mount != NULL && g_mount_can_eject (file->details->mount)); } typedef gboolean (*DriveCheckFunc) (GDrive *); static gboolean can_mount_do (NautilusFile *file, DriveCheckFunc drive_check_func) { if (file->details->mount != NULL) { g_autoptr (GDrive) drive = g_mount_get_drive (file->details->mount); if (drive != NULL) { return drive_check_func (drive); } } return FALSE; } gboolean nautilus_file_can_start (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_start || can_mount_do (file, g_drive_can_start); } gboolean nautilus_file_can_start_degraded (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_start_degraded || can_mount_do (file, g_drive_can_start_degraded); } gboolean nautilus_file_can_poll_for_media (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_poll_for_media || can_mount_do (file, g_drive_can_poll_for_media); } gboolean nautilus_file_is_media_check_automatic (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->is_media_check_automatic || can_mount_do (file, g_drive_is_media_check_automatic); } gboolean nautilus_file_can_stop (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_stop || can_mount_do (file, g_drive_can_stop); } GDriveStartStopType nautilus_file_get_start_stop_type (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), G_DRIVE_START_STOP_TYPE_UNKNOWN); if (file->details->start_stop_type != G_DRIVE_START_STOP_TYPE_UNKNOWN) { return file->details->start_stop_type; } if (file->details->mount != NULL) { g_autoptr (GDrive) drive = g_mount_get_drive (file->details->mount); if (drive != NULL) { return g_drive_get_start_stop_type (drive); } } return G_DRIVE_START_STOP_TYPE_UNKNOWN; } void nautilus_file_mount (NautilusFile *file, GMountOperation *mount_op, GCancellable *cancellable, NautilusFileOperationCallback callback, gpointer callback_data) { GError *error; if (NAUTILUS_FILE_GET_CLASS (file)->mount == NULL) { if (callback) { error = NULL; g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("This file cannot be mounted")); callback (file, NULL, error, callback_data); g_error_free (error); } } else { NAUTILUS_FILE_GET_CLASS (file)->mount (file, mount_op, cancellable, callback, callback_data); } } typedef struct { NautilusFile *file; NautilusFileOperationCallback callback; gpointer callback_data; } UnmountData; static void unmount_done (void *callback_data) { UnmountData *data; data = (UnmountData *) callback_data; if (data->callback) { data->callback (data->file, NULL, NULL, data->callback_data); } nautilus_file_unref (data->file); g_free (data); } void nautilus_file_unmount (NautilusFile *file, GMountOperation *mount_op, GCancellable *cancellable, NautilusFileOperationCallback callback, gpointer callback_data) { GError *error; UnmountData *data; if (file->details->can_unmount) { if (NAUTILUS_FILE_GET_CLASS (file)->unmount != NULL) { NAUTILUS_FILE_GET_CLASS (file)->unmount (file, mount_op, cancellable, callback, callback_data); } else { if (callback) { error = NULL; g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("This file cannot be unmounted")); callback (file, NULL, error, callback_data); g_error_free (error); } } } else if (file->details->mount != NULL && g_mount_can_unmount (file->details->mount)) { GtkWindow *parent; parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op)); data = g_new0 (UnmountData, 1); data->file = nautilus_file_ref (file); data->callback = callback; data->callback_data = callback_data; nautilus_file_operations_unmount_mount_full (parent, file->details->mount, mount_op, FALSE, unmount_done, data); } else if (callback) { callback (file, NULL, NULL, callback_data); } } void nautilus_file_eject (NautilusFile *file, GMountOperation *mount_op, GCancellable *cancellable, NautilusFileOperationCallback callback, gpointer callback_data) { GError *error; UnmountData *data; if (file->details->can_eject) { if (NAUTILUS_FILE_GET_CLASS (file)->eject != NULL) { NAUTILUS_FILE_GET_CLASS (file)->eject (file, mount_op, cancellable, callback, callback_data); } else { if (callback) { error = NULL; g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("This file cannot be ejected")); callback (file, NULL, error, callback_data); g_error_free (error); } } } else if (file->details->mount != NULL && g_mount_can_eject (file->details->mount)) { GtkWindow *parent; parent = gtk_mount_operation_get_parent (GTK_MOUNT_OPERATION (mount_op)); data = g_new0 (UnmountData, 1); data->file = nautilus_file_ref (file); data->callback = callback; data->callback_data = callback_data; nautilus_file_operations_unmount_mount_full (parent, file->details->mount, mount_op, TRUE, unmount_done, data); } else if (callback) { callback (file, NULL, NULL, callback_data); } } void nautilus_file_start (NautilusFile *file, GMountOperation *start_op, GCancellable *cancellable, NautilusFileOperationCallback callback, gpointer callback_data) { GError *error; if ((file->details->can_start || file->details->can_start_degraded) && NAUTILUS_FILE_GET_CLASS (file)->start != NULL) { NAUTILUS_FILE_GET_CLASS (file)->start (file, start_op, cancellable, callback, callback_data); } else { if (callback) { error = NULL; g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("This file cannot be started")); callback (file, NULL, error, callback_data); g_error_free (error); } } } static void file_stop_callback (GObject *source_object, GAsyncResult *res, gpointer callback_data) { NautilusFileOperation *op; gboolean stopped; GError *error; op = callback_data; error = NULL; stopped = g_drive_stop_finish (G_DRIVE (source_object), res, &error); if (!stopped && error->domain == G_IO_ERROR && (error->code == G_IO_ERROR_FAILED_HANDLED || error->code == G_IO_ERROR_CANCELLED)) { g_error_free (error); error = NULL; } nautilus_file_operation_complete (op, NULL, error); if (error) { g_error_free (error); } } void nautilus_file_stop (NautilusFile *file, GMountOperation *mount_op, GCancellable *cancellable, NautilusFileOperationCallback callback, gpointer callback_data) { GError *error; if (NAUTILUS_FILE_GET_CLASS (file)->stop != NULL) { if (file->details->can_stop) { NAUTILUS_FILE_GET_CLASS (file)->stop (file, mount_op, cancellable, callback, callback_data); } else { if (callback) { error = NULL; g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("This file cannot be stopped")); callback (file, NULL, error, callback_data); g_error_free (error); } } } else { g_autoptr (GDrive) drive = NULL; if (file->details->mount != NULL) { drive = g_mount_get_drive (file->details->mount); } if (drive != NULL && g_drive_can_stop (drive)) { NautilusFileOperation *op; op = nautilus_file_operation_new (file, callback, callback_data); if (cancellable) { g_set_object (&op->cancellable, cancellable); } g_drive_stop (drive, G_MOUNT_UNMOUNT_NONE, mount_op, op->cancellable, file_stop_callback, op); } else { if (callback) { error = NULL; g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("This file cannot be stopped")); callback (file, NULL, error, callback_data); g_error_free (error); } } } } void nautilus_file_poll_for_media (NautilusFile *file) { if (file->details->can_poll_for_media) { if (NAUTILUS_FILE_GET_CLASS (file)->stop != NULL) { NAUTILUS_FILE_GET_CLASS (file)->poll_for_media (file); } } else if (file->details->mount != NULL) { g_autoptr (GDrive) drive = g_mount_get_drive (file->details->mount); if (drive != NULL) { g_drive_poll_for_media (drive, NULL, /* cancellable */ NULL, /* GAsyncReadyCallback */ NULL); /* user_data */ } } } /** * nautilus_file_can_rename: * * Check whether the user is allowed to change the name of the file. * * @file: The file to check. * * Return value: FALSE if the user is definitely not allowed to change * the name of the file. If the user is allowed to change the name, or * the code can't tell whether the user is allowed to change the name, * returns TRUE (so rename failures must always be handled). */ gboolean nautilus_file_can_rename (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); /* Nonexistent files can't be renamed. */ if (nautilus_file_is_gone (file)) { return FALSE; } /* Self-owned files can't be renamed */ if (nautilus_file_is_self_owned (file)) { return FALSE; } if (nautilus_file_is_home (file)) { return FALSE; } return file->details->can_rename; } gboolean nautilus_file_can_delete (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); /* Nonexistent files can't be deleted. */ if (nautilus_file_is_gone (file)) { return FALSE; } /* Self-owned files can't be deleted */ if (nautilus_file_is_self_owned (file)) { return FALSE; } return file->details->can_delete; } gboolean nautilus_file_can_trash (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); /* Nonexistent files can't be deleted. */ if (nautilus_file_is_gone (file)) { return FALSE; } /* Self-owned files can't be deleted */ if (nautilus_file_is_self_owned (file)) { return FALSE; } return file->details->can_trash; } gboolean nautilus_file_opens_in_view (NautilusFile *file) { return (nautilus_file_is_directory (file) || nautilus_file_get_file_type (file) == G_FILE_TYPE_MOUNTABLE || (nautilus_file_get_file_type (file) == G_FILE_TYPE_SHORTCUT && g_strcmp0 (nautilus_file_get_mime_type (file), "inode/directory") == 0)); } NautilusFileOperation * nautilus_file_operation_new (NautilusFile *file, NautilusFileOperationCallback callback, gpointer callback_data) { NautilusFileOperation *op; op = g_new0 (NautilusFileOperation, 1); op->file = nautilus_file_ref (file); op->callback = callback; op->callback_data = callback_data; op->cancellable = g_cancellable_new (); op->file->details->operations_in_progress = g_list_prepend (op->file->details->operations_in_progress, op); return op; } static void nautilus_file_operation_remove (NautilusFileOperation *op) { GList *l; NautilusFile *file; op->file->details->operations_in_progress = g_list_remove (op->file->details->operations_in_progress, op); for (l = op->files; l != NULL; l = l->next) { file = NAUTILUS_FILE (l->data); file->details->operations_in_progress = g_list_remove (file->details->operations_in_progress, op); } } void nautilus_file_operation_free (NautilusFileOperation *op) { nautilus_file_operation_remove (op); if (op->files == NULL) { nautilus_file_unref (op->file); } else { nautilus_file_list_free (op->files); } g_object_unref (op->cancellable); if (op->free_data) { op->free_data (op->data); } if (op->undo_info != NULL) { nautilus_file_undo_manager_set_action (op->undo_info); g_object_unref (op->undo_info); } g_free (op); } void nautilus_file_operation_complete (NautilusFileOperation *op, GFile *result_file, GError *error) { /* Claim that something changed even if the operation failed. * This makes it easier for some clients who see the "reverting" * as "changing back". */ nautilus_file_operation_remove (op); if (op->files == NULL) { nautilus_file_changed (op->file); } if (op->callback) { (*op->callback)(op->file, result_file, error, op->callback_data); } if (error != NULL) { g_clear_object (&op->undo_info); } nautilus_file_operation_free (op); } void nautilus_file_operation_cancel (NautilusFileOperation *op) { /* Cancel the operation if it's still in progress. */ g_cancellable_cancel (op->cancellable); } static void rename_get_info_callback (GObject *source_object, GAsyncResult *res, gpointer callback_data) { NautilusFileOperation *op; NautilusDirectory *directory; g_autoptr (NautilusFile) existing_file = NULL; char *old_uri; char *new_uri; const char *new_name; GFileInfo *new_info; GError *error; op = callback_data; error = NULL; new_info = g_file_query_info_finish (G_FILE (source_object), res, &error); if (new_info != NULL) { g_autoptr (GFile) old_location = NULL; g_autoptr (GFile) new_location = NULL; directory = op->file->details->directory; new_name = g_file_info_get_name (new_info); /* If there was another file by the same name in this * directory and it is not the same file that we are * renaming, mark it gone. */ existing_file = nautilus_directory_find_file_by_name (directory, new_name); if (existing_file != NULL) { g_object_ref (existing_file); if (existing_file != op->file) { nautilus_file_mark_gone (existing_file); nautilus_file_changed (existing_file); } } old_location = nautilus_file_get_location (op->file); old_uri = g_file_get_uri (old_location); update_info_and_name (op->file, new_info); new_location = nautilus_file_get_location (op->file); new_uri = g_file_get_uri (new_location); nautilus_directory_moved (old_uri, new_uri); nautilus_tag_manager_update_moved_uris (nautilus_tag_manager_get (), old_location, new_location); g_free (new_uri); g_free (old_uri); g_object_unref (new_info); } nautilus_file_operation_complete (op, NULL, error); if (error) { g_error_free (error); } } static void rename_callback (GObject *source_object, GAsyncResult *res, gpointer callback_data) { NautilusFileOperation *op = callback_data; g_autoptr (GFile) new_file = NULL; g_autoptr (GError) error = NULL; new_file = g_file_set_display_name_finish (G_FILE (source_object), res, &error); if (new_file != NULL) { if (op->undo_info != NULL) { nautilus_file_undo_info_rename_set_data_post (NAUTILUS_FILE_UNDO_INFO_RENAME (op->undo_info), new_file); } g_file_query_info_async (new_file, NAUTILUS_FILE_DEFAULT_ATTRIBUTES, G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, op->cancellable, rename_get_info_callback, op); } else { nautilus_file_operation_complete (op, NULL, error); } } static gboolean name_is (NautilusFile *file, const char *new_name) { const char *old_name; old_name = file->details->name; return strcmp (new_name, old_name) == 0; } static const char * nautilus_file_can_rename_file (NautilusFile *file, const char *new_name, NautilusFileOperationCallback callback, gpointer callback_data) { GError *error; /* Return an error for incoming names containing path separators. */ if (strstr (new_name, G_DIR_SEPARATOR_S) != NULL) { error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Slashes are not allowed in filenames")); if (callback != NULL) { (*callback)(file, NULL, error, callback_data); } g_error_free (error); return NULL; } /* Can't rename a file that's already gone. * We need to check this here because there may be a new * file with the same name. */ if (nautilus_file_rename_handle_file_gone (file, callback, callback_data)) { return NULL; } /* Test the name-hasn't-changed case explicitly, for two reasons. * (1) rename returns an error if new & old are same. * (2) We don't want to send file-changed signal if nothing changed. */ if (name_is (file, new_name)) { if (callback != NULL) { (*callback)(file, NULL, NULL, callback_data); } return NULL; } /* Self-owned files can't be renamed. Test the name-not-actually-changing * case before this case. */ if (nautilus_file_is_self_owned (file)) { /* Claim that something changed even if the rename * failed. This makes it easier for some clients who * see the "reverting" to the old name as "changing * back". */ nautilus_file_changed (file); error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Toplevel files cannot be renamed")); if (callback != NULL) { (*callback)(file, NULL, error, callback_data); } g_error_free (error); return NULL; } return new_name; } void nautilus_file_rename (NautilusFile *file, const char *new_name, NautilusFileOperationCallback callback, gpointer callback_data) { NautilusFileOperation *op; const char *new_file_name; GFile *location; g_return_if_fail (NAUTILUS_IS_FILE (file)); g_return_if_fail (new_name != NULL); g_return_if_fail (callback != NULL); new_file_name = nautilus_file_can_rename_file (file, new_name, callback, callback_data); if (new_file_name == NULL) { return; } /* Set up a renaming operation. */ op = nautilus_file_operation_new (file, callback, callback_data); op->is_rename = TRUE; location = nautilus_file_get_location (file); /* Tell the undo manager a rename is taking place */ if (!nautilus_file_undo_manager_is_operating ()) { const char *old_name; op->undo_info = nautilus_file_undo_info_rename_new (); old_name = nautilus_file_get_display_name (file); nautilus_file_undo_info_rename_set_data_pre (NAUTILUS_FILE_UNDO_INFO_RENAME (op->undo_info), location, old_name, new_file_name); } /* Do the renaming. */ g_file_set_display_name_async (location, new_file_name, G_PRIORITY_DEFAULT, op->cancellable, rename_callback, op); g_object_unref (location); } gboolean nautilus_file_rename_handle_file_gone (NautilusFile *file, NautilusFileOperationCallback callback, gpointer callback_data) { GError *error; if (nautilus_file_is_gone (file)) { /* Claim that something changed even if the rename * failed. This makes it easier for some clients who * see the "reverting" to the old name as "changing * back". */ nautilus_file_changed (file); error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("File not found")); if (callback) { (*callback)(file, NULL, error, callback_data); } g_error_free (error); return TRUE; } return FALSE; } typedef struct { GPtrArray *chain_files; gboolean is_cycle; NautilusFileOperation *op; GAsyncQueue *rollback_queue; } RenameChainData; typedef struct { GList *old_files; GList *new_files; } TwoLists; static void rename_file_chain (gpointer data, gpointer user_data) { gboolean skip_files = FALSE; g_autofree RenameChainData *rename_chain = data; g_autoptr (GPtrArray) chain_files = rename_chain->chain_files; gsize len = chain_files->len; g_autoptr (GFile) snapped_file = NULL; NautilusFileOperation *op = rename_chain->op; GList *old_files = NULL, *new_files = NULL; GAsyncQueue *changed_files = user_data; if (rename_chain->is_cycle) { /* We have a cycle, break it with a temporary name. */ g_autofree gchar *random_string = g_uuid_string_random (); snapped_file = g_ptr_array_index (chain_files, len - 1); GFile *renamed_file = g_file_set_display_name (snapped_file, random_string, op->cancellable, NULL); if (renamed_file != NULL) { chain_files->pdata[len - 1] = renamed_file; } else { /* Unlikely to be a name conflict, skip the entire chain. */ g_autoptr (GFile) parent = g_file_get_parent (snapped_file); GFile *new_file = g_file_get_child (parent, random_string); TwoLists *rollback_files = g_new0 (TwoLists, 1); rollback_files->old_files = g_list_prepend (NULL, g_object_ref (snapped_file)); rollback_files->new_files = g_list_prepend (NULL, new_file); g_async_queue_push (rename_chain->rollback_queue, rollback_files); skip_files = TRUE; } } /* Process the chain leading to this file */ for (uint i = 0; i < len - 1; i++) { GFile *src = g_ptr_array_index (chain_files, i + 1); GFile *target = g_ptr_array_index (chain_files, i); if (skip_files) { guint remaining = len - 1 - i; g_atomic_int_add (&op->skipped_files, remaining); break; } g_autofree gchar *new_name = g_file_get_basename (target); g_autoptr (GError) error = NULL; /* Do the renaming. */ g_autoptr (GFile) renamed_file = g_file_set_display_name (src, new_name, op->cancellable, &error); if (error != NULL) { g_autofree gchar *old_name = g_file_get_basename (src); g_warning ("Batch rename for file \"%s\" failed: %s", old_name, error->message); /* We need to skip the rest of the files in the chain. */ skip_files = TRUE; g_atomic_int_inc (&op->skipped_files); } else { g_atomic_int_inc (&op->renamed_files); old_files = g_list_prepend (old_files, g_object_ref (src)); new_files = g_list_prepend (new_files, g_steal_pointer (&renamed_file)); } } if (snapped_file != NULL) { /* Use the original snapped file to undo the operation correctly. */ g_set_object (&old_files->data, snapped_file); } TwoLists *changed_files_lists = g_new0 (TwoLists, 1); changed_files_lists->old_files = old_files; changed_files_lists->new_files = new_files; g_async_queue_push (changed_files, changed_files_lists); } static void real_batch_rename (GList *files, GList *new_names, NautilusFileOperationCallback callback, gpointer callback_data) { GList *l1, *l2; g_autolist (GFile) old_files = NULL, new_files = NULL; NautilusFileOperation *op; gsize len = 0; /* `staged_targets` and `staged_files` are for the file => renamed file map * and a reverse map respectively. */ g_autoptr (GHashTable) staged_targets = g_hash_table_new (g_file_hash, (GEqualFunc) g_file_equal); g_autoptr (GHashTable) staged_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, g_object_unref); /* `stub_files` are for files that will not or cannot be renamed without * trying. */ g_autoptr (GHashTable) stub_files = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, g_object_unref, NULL); GHashTableIter hash_iter; GFile *key, *value; g_autoptr (GAsyncQueue) changed_files = g_async_queue_new (); GThreadPool *thread_pool = NULL; g_autoptr (GAsyncQueue) rollback_files = g_async_queue_new (); g_autoptr (GError) skip_error = NULL; /* Set up a batch renaming operation. */ op = nautilus_file_operation_new (files->data, callback, callback_data); op->files = nautilus_file_list_copy (files); op->renamed_files = 0; op->skipped_files = 0; /* Collect valid files in hashtables */ for (l1 = files, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) { NautilusFile *file = NAUTILUS_FILE (l1->data); GString *new_name = l2->data; GFile *location = nautilus_file_get_location (file); const char *new_file_name = nautilus_file_can_rename_file (file, new_name->str, NULL, NULL); len++; if (new_file_name == NULL) { g_hash_table_add (stub_files, location); if (name_is (file, new_name->str)) { /* We are ignoring this rename, and not even adding to the undo list. */ op->renamed_files++; } else { op->skipped_files++; } continue; } g_autoptr (GFile) parent = nautilus_file_get_parent_location (file); g_autoptr (GFile) target = g_file_get_child (parent, new_file_name); if (g_hash_table_lookup (stub_files, target) != NULL) { /* file depends on a stub, so it becomes a stub itself */ op->skipped_files++; g_hash_table_add (stub_files, location); continue; } file->details->operations_in_progress = g_list_prepend (file->details->operations_in_progress, op); g_assert (g_hash_table_insert (staged_targets, location, target)); g_assert (g_hash_table_insert (staged_files, g_steal_pointer (&target), location)); } thread_pool = g_thread_pool_new ((GFunc) rename_file_chain, changed_files, MIN (32, len), FALSE, NULL); /* Discover and assemble individual dependency chains by walking the hash * table and push them into thread pools. */ g_hash_table_iter_init (&hash_iter, staged_targets); while (g_hash_table_iter_next (&hash_iter, (gpointer *) &key, (gpointer *) &value)) { GFile *chain_file = key, *iter_start_file = key; RenameChainData *rename_chain = g_new0 (RenameChainData, 1); rename_chain->chain_files = g_ptr_array_new_full (3, g_object_unref); rename_chain->op = op; rename_chain->rollback_queue = rollback_files; /* Iterate to find out if it's a chain or a cycle. */ while (TRUE) { GFile *prev_chain_file = g_hash_table_lookup (staged_targets, chain_file); if (prev_chain_file == NULL) { /* Reached chain start */ break; } if (g_file_equal (iter_start_file, prev_chain_file)) { /* We found a cycle */ rename_chain->is_cycle = TRUE; g_ptr_array_add (rename_chain->chain_files, g_object_ref (iter_start_file)); break; } chain_file = prev_chain_file; } GFile *target = chain_file; iter_start_file = chain_file; /* Walk the reverse hash table to collect a list of the files to be * renamed in an ordered list, but terminate appropriately with a * cycle. */ do { g_autoptr (GFile) src = NULL; GFile *new_target = NULL; g_ptr_array_add (rename_chain->chain_files, target); if (g_hash_table_steal_extended (staged_files, target, (gpointer *) &src, (gpointer *) &new_target)) { g_hash_table_remove (staged_targets, new_target); if (src == target) { src = NULL; } if (rename_chain->is_cycle && g_file_equal (new_target, iter_start_file)) { g_object_unref (new_target); break; } } target = new_target; } while (target != NULL); g_thread_pool_push (thread_pool, rename_chain, NULL); g_hash_table_iter_init (&hash_iter, staged_targets); } /* Wait for threads to finish and collect the changes. */ g_thread_pool_free (thread_pool, FALSE, TRUE); while (g_async_queue_length (changed_files) > 0) { g_autofree TwoLists *changed_files_lists = g_async_queue_pop (changed_files); old_files = g_list_concat (changed_files_lists->old_files, old_files); new_files = g_list_concat (changed_files_lists->new_files, new_files); } /* Attempt to rename back the files that were renamed to temporary names. * Don't handle any errors though, it's likely futile. */ while (g_async_queue_length (rollback_files) > 0) { g_autofree TwoLists *rollback_file_lists = g_async_queue_pop (changed_files); g_autoptr (GList) old_file_list = rollback_file_lists->old_files; g_autoptr (GList) new_file_list = rollback_file_lists->new_files; g_file_move (new_file_list->data, old_file_list->data, G_FILE_COPY_NONE, NULL, NULL, NULL, NULL); NautilusFile *old_file = nautilus_file_get_existing (old_file_list->data); NautilusFile *new_file = nautilus_file_get_existing (new_file_list->data); if (old_file != NULL) { nautilus_file_invalidate_attributes (old_file, NAUTILUS_ATTRIBUTE_INFO); } if (new_file != NULL) { nautilus_file_invalidate_attributes (new_file, NAUTILUS_ATTRIBUTE_INFO); } } /* Invalidate attributes of modified files in idle. */ for (l1 = old_files, l2 = new_files; l1 != NULL; l1 = l1->next, l2 = l2->next) { NautilusFile *old_file = nautilus_file_get_existing (l1->data); NautilusFile *new_file = nautilus_file_get_existing (l2->data); if (old_file != NULL) { nautilus_file_invalidate_attributes (old_file, NAUTILUS_ATTRIBUTE_INFO); } if (new_file != NULL) { nautilus_file_invalidate_attributes (new_file, NAUTILUS_ATTRIBUTE_INFO); } } /* Tell the undo manager a batch rename is taking place if at least * a file has been renamed*/ if (!nautilus_file_undo_manager_is_operating () && op->skipped_files != len) { op->undo_info = nautilus_file_undo_info_batch_rename_new (g_list_length (new_files)); nautilus_file_undo_info_batch_rename_set_data_pre (NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (op->undo_info), g_steal_pointer (&old_files)); nautilus_file_undo_info_batch_rename_set_data_post (NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (op->undo_info), g_steal_pointer (&new_files)); nautilus_file_undo_manager_set_action (op->undo_info); } if (op->skipped_files > 1) { skip_error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "Skipped %d file(s)", op->skipped_files); } nautilus_file_operation_complete (op, NULL, skip_error); } void nautilus_file_batch_rename (GList *files, GList *new_names, NautilusFileOperationCallback callback, gpointer callback_data) { real_batch_rename (files, new_names, callback, callback_data); } gboolean nautilus_file_rename_in_progress (NautilusFile *file) { GList *node; NautilusFileOperation *op; for (node = file->details->operations_in_progress; node != NULL; node = node->next) { op = node->data; if (op->is_rename) { return TRUE; } } return FALSE; } void nautilus_file_cancel (NautilusFile *file, NautilusFileOperationCallback callback, gpointer callback_data) { GList *node, *next; NautilusFileOperation *op; for (node = file->details->operations_in_progress; node != NULL; node = next) { next = node->next; op = node->data; g_assert (op->file == file); if (op->callback == callback && op->callback_data == callback_data) { nautilus_file_operation_cancel (op); } } } gboolean nautilus_file_matches_uri (NautilusFile *file, const char *match_uri) { GFile *match_file, *location; gboolean result; g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); g_return_val_if_fail (match_uri != NULL, FALSE); location = nautilus_file_get_location (file); match_file = g_file_new_for_uri (match_uri); result = g_file_equal (location, match_file); g_object_unref (location); g_object_unref (match_file); return result; } int nautilus_file_compare_location (NautilusFile *file_1, NautilusFile *file_2) { GFile *loc_a, *loc_b; gboolean res; loc_a = nautilus_file_get_location (file_1); loc_b = nautilus_file_get_location (file_2); res = !g_file_equal (loc_a, loc_b); g_object_unref (loc_a); g_object_unref (loc_b); return (gint) res; } /** * nautilus_file_has_local_path: * * @file: a #NautilusFile * * Checks whether this file has an obtainable local paths. Usually, this means * the local path can be obtained by calling g_file_get_path(); this includes * native and FUSE files. As an exception, the local URI for files in recent:// * can only be obtained from the G_FILE_ATTRIBUTE_STANDARD_TARGET_URI attribute. * * Returns: %TRUE if a local path is known to be obtainable for this file. */ gboolean nautilus_file_has_local_path (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return nautilus_directory_is_local_or_fuse (file->details->directory); } static void update_link (NautilusFile *link_file, NautilusFile *target_file) { g_assert (NAUTILUS_IS_FILE (link_file)); g_assert (NAUTILUS_IS_FILE (target_file)); /* FIXME bugzilla.gnome.org 42044: If we don't put any code * here then the hash table is a waste of time. */ } static GList * get_link_files (NautilusFile *target_file) { char *uri; GList **link_files; if (symbolic_links == NULL) { link_files = NULL; } else { uri = nautilus_file_get_uri (target_file); link_files = g_hash_table_lookup (symbolic_links, uri); g_free (uri); } if (link_files) { return nautilus_file_list_copy (*link_files); } return NULL; } static void update_links_if_target (NautilusFile *target_file) { GList *link_files, *p; link_files = get_link_files (target_file); for (p = link_files; p != NULL; p = p->next) { update_link (NAUTILUS_FILE (p->data), target_file); } nautilus_file_list_free (link_files); } static gboolean update_info_internal (NautilusFile *file, GFileInfo *info, gboolean update_name) { GList *node; gboolean changed; gboolean is_symlink, is_hidden, is_mountpoint; gboolean has_permissions; guint32 permissions; gboolean can_read, can_write, can_execute, can_delete, can_trash, can_rename, can_mount, can_unmount, can_eject; gboolean can_start, can_start_degraded, can_stop, can_poll_for_media, is_media_check_automatic; GDriveStartStopType start_stop_type; gboolean has_uid = FALSE; uid_t uid = 0; gboolean has_gid = FALSE; gid_t gid = 0; goffset size; int sort_order; time_t atime, mtime, btime; time_t trash_time; time_t recency; const char *time_string; const char *symlink_name, *mime_type, *selinux_context, *name; GFileType file_type; GIcon *icon; const char *filesystem_id; const char *trash_orig_path; const char *group, *owner, *owner_real; gboolean free_owner, free_group; const char *edit_name; if (file->details->is_gone) { return FALSE; } if (info == NULL) { nautilus_file_mark_gone (file); return TRUE; } file->details->file_info_is_up_to_date = TRUE; /* FIXME bugzilla.gnome.org 42044: Need to let links that * point to the old name know that the file has been renamed. */ remove_from_link_hash_table (file); changed = FALSE; if (!file->details->got_file_info) { changed = TRUE; } file->details->got_file_info = TRUE; edit_name = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME); changed |= nautilus_file_set_display_name (file, g_file_info_get_display_name (info), edit_name, FALSE); file_type = g_file_info_get_file_type (info); if (file->details->type != file_type) { changed = TRUE; } file->details->type = file_type; if (!nautilus_file_is_directory (file)) { file->details->directory_count_is_up_to_date = TRUE; file->details->directory_count_failed = FALSE; file->details->got_directory_count = FALSE; } if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL) || file_type == G_FILE_TYPE_SHORTCUT || file_type == G_FILE_TYPE_MOUNTABLE || nautilus_file_is_in_recent (file)) { if (g_set_str (&file->details->activation_uri, g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI))) { changed = TRUE; } } is_symlink = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK); if (file->details->is_symlink != is_symlink) { changed = TRUE; } file->details->is_symlink = is_symlink; is_hidden = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) || g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP); if (file->details->is_hidden != is_hidden) { changed = TRUE; } file->details->is_hidden = is_hidden; is_mountpoint = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_UNIX_IS_MOUNTPOINT); if (file->details->is_mountpoint != is_mountpoint) { changed = TRUE; } file->details->is_mountpoint = is_mountpoint; has_permissions = g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE); permissions = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE); if (file->details->has_permissions != has_permissions || file->details->permissions != permissions) { changed = TRUE; } file->details->has_permissions = has_permissions; file->details->permissions = permissions; /* We default to TRUE for this if we can't know */ can_read = TRUE; can_write = TRUE; can_execute = TRUE; can_delete = TRUE; can_rename = TRUE; can_trash = FALSE; can_mount = FALSE; can_unmount = FALSE; can_eject = FALSE; can_start = FALSE; can_start_degraded = FALSE; can_stop = FALSE; can_poll_for_media = FALSE; is_media_check_automatic = FALSE; start_stop_type = G_DRIVE_START_STOP_TYPE_UNKNOWN; if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) { can_read = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE)) { can_write = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)) { can_execute = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE)) { can_delete = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH)) { can_trash = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME)) { can_rename = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT)) { can_mount = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_MOUNT); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT)) { can_unmount = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_UNMOUNT); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT)) { can_eject = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_EJECT); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START)) { can_start = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START_DEGRADED)) { can_start_degraded = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_START_DEGRADED); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP)) { can_stop = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_STOP); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE)) { start_stop_type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_MOUNTABLE_START_STOP_TYPE); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_POLL)) { can_poll_for_media = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_CAN_POLL); } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_MOUNTABLE_IS_MEDIA_CHECK_AUTOMATIC)) { is_media_check_automatic = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_MOUNTABLE_IS_MEDIA_CHECK_AUTOMATIC); } if (file->details->can_read != can_read || file->details->can_write != can_write || file->details->can_execute != can_execute || file->details->can_delete != can_delete || file->details->can_trash != can_trash || file->details->can_rename != can_rename || file->details->can_mount != can_mount || file->details->can_unmount != can_unmount || file->details->can_eject != can_eject || file->details->can_start != can_start || file->details->can_start_degraded != can_start_degraded || file->details->can_stop != can_stop || file->details->start_stop_type != start_stop_type || file->details->can_poll_for_media != can_poll_for_media || file->details->is_media_check_automatic != is_media_check_automatic) { changed = TRUE; } file->details->can_read = can_read; file->details->can_write = can_write; file->details->can_execute = can_execute; file->details->can_delete = can_delete; file->details->can_trash = can_trash; file->details->can_rename = can_rename; file->details->can_mount = can_mount; file->details->can_unmount = can_unmount; file->details->can_eject = can_eject; file->details->can_start = can_start; file->details->can_start_degraded = can_start_degraded; file->details->can_stop = can_stop; file->details->start_stop_type = start_stop_type; file->details->can_poll_for_media = can_poll_for_media; file->details->is_media_check_automatic = is_media_check_automatic; free_owner = FALSE; owner = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER); owner_real = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER_REAL); free_group = FALSE; group = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP); if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_UID)) { uid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID); has_uid = TRUE; if (owner == NULL) { free_owner = TRUE; owner = g_strdup_printf ("%d", uid); } } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_GID)) { gid = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID); has_gid = TRUE; if (group == NULL) { free_group = TRUE; group = g_strdup_printf ("%d", gid); } } if (file->details->has_uid != has_uid || (file->details->has_uid && file->details->uid != uid) || file->details->has_gid != has_gid || (file->details->has_gid && file->details->gid != gid)) { changed = TRUE; } file->details->has_uid = has_uid; file->details->uid = uid; file->details->has_gid = has_gid; file->details->gid = gid; if (g_strcmp0 (file->details->owner, owner) != 0) { changed = TRUE; g_clear_pointer (&file->details->owner, g_ref_string_release); file->details->owner = g_ref_string_new_intern (owner); } if (g_strcmp0 (file->details->owner_real, owner_real) != 0) { changed = TRUE; g_clear_pointer (&file->details->owner_real, g_ref_string_release); file->details->owner_real = g_ref_string_new_intern (owner_real); } if (g_strcmp0 (file->details->group, group) != 0) { changed = TRUE; g_clear_pointer (&file->details->group, g_ref_string_release); file->details->group = g_ref_string_new_intern (group); } if (free_owner) { g_free ((char *) owner); } if (free_group) { g_free ((char *) group); } size = -1; if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) { size = g_file_info_get_size (info); } if (file->details->size != size) { changed = TRUE; } file->details->size = size; sort_order = g_file_info_get_attribute_int32 (info, G_FILE_ATTRIBUTE_STANDARD_SORT_ORDER); if (file->details->sort_order != sort_order) { changed = TRUE; } file->details->sort_order = sort_order; atime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS); mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); btime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CREATED); if (file->details->atime != atime) { changed = TRUE; } if (file->details->mtime != mtime) { file->details->thumbnail_info_is_up_to_date = FALSE; file->details->thumbnail_is_up_to_date = FALSE; changed = TRUE; } file->details->atime = atime; file->details->mtime = mtime; file->details->btime = btime; if (nautilus_file_has_thumbnail (file) && file->details->thumbnail_mtime != 0 && file->details->thumbnail_mtime != mtime) { file->details->thumbnail_info_is_up_to_date = FALSE; file->details->thumbnail_is_up_to_date = FALSE; changed = TRUE; } icon = g_file_info_get_icon (info); if (!g_icon_equal (icon, file->details->icon)) { changed = TRUE; if (file->details->icon) { g_object_unref (file->details->icon); } file->details->icon = g_object_ref (icon); } symlink_name = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET); if (g_set_str (&file->details->symlink_name, symlink_name)) { changed = TRUE; } mime_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE); if (mime_type == NULL) { mime_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE); } if (g_strcmp0 (file->details->mime_type, mime_type) != 0) { changed = TRUE; g_clear_pointer (&file->details->mime_type, g_ref_string_release); file->details->mime_type = g_ref_string_new_intern (mime_type); } selinux_context = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_SELINUX_CONTEXT); if (g_set_str (&file->details->selinux_context, selinux_context)) { changed = TRUE; } filesystem_id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM); if (g_strcmp0 (file->details->filesystem_id, filesystem_id) != 0) { changed = TRUE; g_clear_pointer (&file->details->filesystem_id, g_ref_string_release); file->details->filesystem_id = g_ref_string_new_intern (filesystem_id); } trash_time = 0; time_string = g_file_info_get_attribute_string (info, "trash::deletion-date"); if (time_string != NULL) { g_autoptr (GTimeZone) tz = g_time_zone_new_local (); g_autoptr (GDateTime) date_time = g_date_time_new_from_iso8601 (time_string, tz); trash_time = date_time != NULL ? g_date_time_to_unix (date_time) : 0; } if (file->details->trash_time != trash_time) { changed = TRUE; file->details->trash_time = trash_time; } recency = g_file_info_get_attribute_int64 (info, G_FILE_ATTRIBUTE_RECENT_MODIFIED); if (file->details->recency != recency) { changed = TRUE; file->details->recency = recency; } trash_orig_path = g_file_info_get_attribute_byte_string (info, "trash::orig-path"); if (g_set_str (&file->details->trash_orig_path, trash_orig_path)) { changed = TRUE; } if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_PREVIEW_ICON)) { file->details->has_preview_icon = TRUE; } changed |= nautilus_file_update_metadata_from_info (file, info); if (update_name) { name = g_file_info_get_name (info); if (file->details->name == NULL || strcmp (file->details->name, name) != 0) { changed = TRUE; node = nautilus_directory_begin_file_name_change (file->details->directory, file); g_clear_pointer (&file->details->name, g_ref_string_release); if (g_strcmp0 (file->details->display_name, name) == 0) { file->details->name = g_ref_string_acquire (file->details->display_name); } else { file->details->name = g_ref_string_new (name); } if (!file->details->got_custom_display_name && g_file_info_get_display_name (info) == NULL) { /* If the file info's display name is NULL, * nautilus_file_set_display_name() did * not unset the display name. */ nautilus_file_clear_display_name (file); } nautilus_directory_end_file_name_change (file->details->directory, file, node); } } if (changed) { add_to_link_hash_table (file); update_links_if_target (file); } return changed; } static gboolean update_info_and_name (NautilusFile *file, GFileInfo *info) { return update_info_internal (file, info, TRUE); } gboolean nautilus_file_update_info (NautilusFile *file, GFileInfo *info) { return update_info_internal (file, info, FALSE); } gboolean nautilus_file_update_thumbnail_info (NautilusFile *file, GFileInfo *info) { gboolean changed = FALSE; if (!file->details->thumbnail_info_is_up_to_date) { file->details->thumbnail_info_is_up_to_date = TRUE; changed = TRUE; } const gchar *thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); if (g_set_str (&file->details->thumbnail_path, thumbnail_path)) { changed = TRUE; } gboolean cache_is_valid = g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAIL_IS_VALID); gboolean thumbnailing_failed = cache_is_valid && g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); if (file->details->thumbnailing_failed != thumbnailing_failed) { changed = TRUE; file->details->thumbnailing_failed = thumbnailing_failed; } return changed; } static gboolean update_name_internal (NautilusFile *file, const char *name, gboolean in_directory) { GList *node; g_assert (name != NULL); if (file->details->is_gone) { return FALSE; } if (name_is (file, name)) { return FALSE; } node = NULL; if (in_directory) { node = nautilus_directory_begin_file_name_change (file->details->directory, file); } g_clear_pointer (&file->details->name, g_ref_string_release); file->details->name = g_ref_string_new (name); if (!file->details->got_custom_display_name) { nautilus_file_clear_display_name (file); } if (in_directory) { nautilus_directory_end_file_name_change (file->details->directory, file, node); } return TRUE; } gboolean nautilus_file_update_name (NautilusFile *file, const char *name) { gboolean ret; ret = update_name_internal (file, name, TRUE); if (ret) { update_links_if_target (file); } return ret; } gboolean nautilus_file_update_name_and_directory (NautilusFile *file, const char *name, NautilusDirectory *new_directory) { NautilusDirectory *old_directory; FileMonitors *monitors; g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (file->details->directory), FALSE); g_return_val_if_fail (!file->details->is_gone, FALSE); g_return_val_if_fail (!nautilus_file_is_self_owned (file), FALSE); g_return_val_if_fail (NAUTILUS_IS_DIRECTORY (new_directory), FALSE); old_directory = file->details->directory; if (old_directory == new_directory) { if (name) { return update_name_internal (file, name, TRUE); } else { return FALSE; } } nautilus_file_ref (file); /* FIXME bugzilla.gnome.org 42044: Need to let links that * point to the old name know that the file has been moved. */ remove_from_link_hash_table (file); monitors = nautilus_directory_remove_file_monitors (old_directory, file); nautilus_directory_remove_file (old_directory, file); nautilus_file_set_directory (file, new_directory); if (name) { update_name_internal (file, name, FALSE); } nautilus_directory_add_file (new_directory, file); nautilus_directory_add_file_monitors (new_directory, file, monitors); add_to_link_hash_table (file); update_links_if_target (file); nautilus_file_unref (file); return TRUE; } static Knowledge get_item_count (NautilusFile *file, guint *count) { gboolean known, unreadable; known = nautilus_file_get_directory_item_count (file, count, &unreadable); if (!known) { return UNKNOWN; } if (unreadable) { return UNKNOWABLE; } return KNOWN; } static Knowledge get_size (NautilusFile *file, goffset *size) { /* If we tried and failed, then treat it like there is no size * to know. */ if (file->details->get_info_failed) { return UNKNOWABLE; } /* If the info is NULL that means we haven't even tried yet, * so it's just unknown, not unknowable. */ if (!file->details->got_file_info) { return UNKNOWN; } /* If we got info with no size in it, it means there is no * such thing as a size as far as gnome-vfs is concerned, * so "unknowable". */ if (file->details->size == -1) { return UNKNOWABLE; } /* We have a size! */ *size = file->details->size; return KNOWN; } static Knowledge get_time (NautilusFile *file, time_t *time_out, NautilusDateType type) { time_t time; /* If we tried and failed, then treat it like there is no size * to know. */ if (file->details->get_info_failed) { return UNKNOWABLE; } /* If the info is NULL that means we haven't even tried yet, * so it's just unknown, not unknowable. */ if (!file->details->got_file_info) { return UNKNOWN; } switch (type) { case NAUTILUS_DATE_TYPE_MODIFIED: { time = file->details->mtime; } break; case NAUTILUS_DATE_TYPE_ACCESSED: { time = file->details->atime; } break; case NAUTILUS_DATE_TYPE_CREATED: { time = file->details->btime; } break; case NAUTILUS_DATE_TYPE_TRASHED: { time = file->details->trash_time; } break; case NAUTILUS_DATE_TYPE_RECENCY: { time = file->details->recency; } break; default: { g_assert_not_reached (); } break; } *time_out = time; /* If we got info with no modification time in it, it means * there is no such thing as a modification time as far as * gnome-vfs is concerned, so "unknowable". */ if (time == 0) { return UNKNOWABLE; } return KNOWN; } static int compare_directories_by_count (NautilusFile *file_1, NautilusFile *file_2) { /* Sort order: * Directories with unknown # of items * Directories with "unknowable" # of items * Directories with 0 items * Directories with n items */ Knowledge count_known_1, count_known_2; guint count_1, count_2; count_known_1 = get_item_count (file_1, &count_1); count_known_2 = get_item_count (file_2, &count_2); if (count_known_1 > count_known_2) { return -1; } if (count_known_1 < count_known_2) { return +1; } /* count_known_1 and count_known_2 are equal now. Check if count * details are UNKNOWABLE or UNKNOWN. */ if (count_known_1 == UNKNOWABLE || count_known_1 == UNKNOWN) { return 0; } if (count_1 < count_2) { return -1; } if (count_1 > count_2) { return +1; } return 0; } static int compare_files_by_size (NautilusFile *file_1, NautilusFile *file_2) { /* Sort order: * Files with unknown size. * Files with "unknowable" size. * Files with smaller sizes. * Files with large sizes. */ Knowledge size_known_1, size_known_2; goffset size_1 = 0, size_2 = 0; size_known_1 = get_size (file_1, &size_1); size_known_2 = get_size (file_2, &size_2); if (size_known_1 > size_known_2) { return -1; } if (size_known_1 < size_known_2) { return +1; } /* size_known_1 and size_known_2 are equal now. Check if size * details are UNKNOWABLE or UNKNOWN */ if (size_known_1 == UNKNOWABLE || size_known_1 == UNKNOWN) { return 0; } if (size_1 < size_2) { return -1; } if (size_1 > size_2) { return +1; } return 0; } static int compare_by_size (NautilusFile *file_1, NautilusFile *file_2) { /* Sort order: * Directories with n items * Directories with 0 items * Directories with "unknowable" # of items * Directories with unknown # of items * Files with large sizes. * Files with smaller sizes. * Files with "unknowable" size. * Files with unknown size. */ gboolean is_directory_1, is_directory_2; is_directory_1 = nautilus_file_is_directory (file_1); is_directory_2 = nautilus_file_is_directory (file_2); if (is_directory_1 && !is_directory_2) { return -1; } if (is_directory_2 && !is_directory_1) { return +1; } if (is_directory_1) { return compare_directories_by_count (file_1, file_2); } else { return compare_files_by_size (file_1, file_2); } } static int compare_by_display_name (NautilusFile *file_1, NautilusFile *file_2) { const char *name_1, *name_2; const char *key_1, *key_2; gboolean sort_last_1, sort_last_2; int compare; name_1 = nautilus_file_peek_display_name (file_1); name_2 = nautilus_file_peek_display_name (file_2); sort_last_1 = name_1[0] == SORT_LAST_CHAR1 || name_1[0] == SORT_LAST_CHAR2; sort_last_2 = name_2[0] == SORT_LAST_CHAR1 || name_2[0] == SORT_LAST_CHAR2; if (sort_last_1 && !sort_last_2) { compare = +1; } else if (!sort_last_1 && sort_last_2) { compare = -1; } else { key_1 = nautilus_file_peek_display_name_collation_key (file_1); key_2 = nautilus_file_peek_display_name_collation_key (file_2); compare = strcmp (key_1, key_2); } return compare; } static inline int compare_by_name (NautilusFile *file_1, NautilusFile *file_2) { return g_strcmp0 (file_1->details->name, file_2->details->name); } static int compare_by_directory_name (NautilusFile *file_1, NautilusFile *file_2) { const gchar *directory_key1 = nautilus_file_get_directory_name (file_1); const gchar *directory_key2 = nautilus_file_get_directory_name (file_2); return strcmp (directory_key1, directory_key2); } static int compare_by_type (NautilusFile *file_1, NautilusFile *file_2) { gboolean is_directory_1; gboolean is_directory_2; const char *type_string_1; const char *type_string_2; int result; /* Directories go first. Then, if mime types are identical, * don't bother getting strings (for speed). This assumes * that the string is dependent entirely on the mime type, * which is true now but might not be later. */ is_directory_1 = nautilus_file_is_directory (file_1); is_directory_2 = nautilus_file_is_directory (file_2); if (is_directory_1 && is_directory_2) { return 0; } if (is_directory_1) { return -1; } if (is_directory_2) { return +1; } if (file_1->details->mime_type != NULL && file_2->details->mime_type != NULL && strcmp (file_1->details->mime_type, file_2->details->mime_type) == 0) { return 0; } type_string_1 = nautilus_file_get_type_as_string_no_extra_text (file_1); type_string_2 = nautilus_file_get_type_as_string_no_extra_text (file_2); if (type_string_1 == NULL || type_string_2 == NULL) { if (type_string_1 != NULL) { return -1; } if (type_string_2 != NULL) { return 1; } return 0; } result = g_utf8_collate (type_string_1, type_string_2); if (result == 0) { /* Among files of the same (generic) type, sort them by mime type. */ result = g_utf8_collate (file_1->details->mime_type, file_2->details->mime_type); } return result; } static int compare_by_starred (NautilusFile *file_1, NautilusFile *file_2) { NautilusTagManager *tag_manager = nautilus_tag_manager_get (); g_autofree gchar *uri_1 = NULL; g_autofree gchar *uri_2 = NULL; gboolean file_1_is_starred; gboolean file_2_is_starred; uri_1 = nautilus_file_get_activation_uri (file_1); uri_2 = nautilus_file_get_activation_uri (file_2); file_1_is_starred = nautilus_tag_manager_file_is_starred (tag_manager, uri_1); file_2_is_starred = nautilus_tag_manager_file_is_starred (tag_manager, uri_2); if (!!file_1_is_starred == !!file_2_is_starred) { return 0; } else if (file_1_is_starred && !file_2_is_starred) { return -1; } else { return 1; } } static Knowledge get_search_relevance (NautilusFile *file, gdouble *relevance_out) { /* we're only called in search directories, and in that * case, the relevance is always known (or zero). */ *relevance_out = file->details->search_relevance; return KNOWN; } static int compare_by_search_relevance (NautilusFile *file_1, NautilusFile *file_2) { gdouble r_1, r_2; get_search_relevance (file_1, &r_1); get_search_relevance (file_2, &r_2); if (r_1 < r_2) { return -1; } if (r_1 > r_2) { return +1; } return 0; } static int compare_by_time (NautilusFile *file_1, NautilusFile *file_2, NautilusDateType type) { /* Sort order: * Files with unknown times. * Files with "unknowable" times. * Files with older times. * Files with newer times. */ Knowledge time_known_1, time_known_2; time_t time_1, time_2; time_1 = 0; time_2 = 0; time_known_1 = get_time (file_1, &time_1, type); time_known_2 = get_time (file_2, &time_2, type); if (time_known_1 > time_known_2) { return -1; } if (time_known_1 < time_known_2) { return +1; } /* Now time_known_1 is equal to time_known_2. Check whether * we failed to get modification times for files */ if (time_known_1 == UNKNOWABLE || time_known_1 == UNKNOWN) { return 0; } if (time_1 < time_2) { return -1; } if (time_1 > time_2) { return +1; } return 0; } static int compare_by_full_path (NautilusFile *file_1, NautilusFile *file_2) { int compare; compare = compare_by_directory_name (file_1, file_2); if (compare != 0) { return compare; } compare = compare_by_display_name (file_1, file_2); if (compare != 0) { return compare; } return compare_by_name (file_1, file_2); } static int nautilus_file_compare_for_sort_internal (NautilusFile *file_1, NautilusFile *file_2, gboolean directories_first, gboolean reversed) { gboolean is_directory_1, is_directory_2; if (directories_first) { is_directory_1 = nautilus_file_is_directory (file_1); is_directory_2 = nautilus_file_is_directory (file_2); if (is_directory_1 && !is_directory_2) { return -1; } if (is_directory_2 && !is_directory_1) { return +1; } } if (file_1->details->sort_order < file_2->details->sort_order) { return reversed ? 1 : -1; } else if (file_1->details->sort_order > file_2->details->sort_order) { return reversed ? -1 : 1; } return 0; } /** * nautilus_file_compare_for_sort: * @file_1: A file object * @file_2: Another file object * @sort_type: Sort criterion * @directories_first: Put all directories before any non-directories * @reversed: Reverse the order of the items, except that * the directories_first flag is still respected. * * Return value: int < 0 if @file_1 should come before file_2 in a * sorted list; int > 0 if @file_2 should come before file_1 in a * sorted list; 0 if @file_1 and @file_2 are equal for this sort criterion. Note * that each named sort type may actually break ties several ways, with the name * of the sort criterion being the primary but not only differentiator. **/ int nautilus_file_compare_for_sort (NautilusFile *file_1, NautilusFile *file_2, NautilusFileSortType sort_type, gboolean directories_first, gboolean reversed) { int result; if (file_1 == file_2) { return 0; } result = nautilus_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed); if (result == 0) { switch (sort_type) { case NAUTILUS_FILE_SORT_BY_DISPLAY_NAME: { result = compare_by_display_name (file_1, file_2); if (result == 0) { result = compare_by_directory_name (file_1, file_2); } /* Some GVfs backends like google-drive allow multiple files * with the same display name. */ if (result == 0) { result = compare_by_name (file_1, file_2); } } break; case NAUTILUS_FILE_SORT_BY_SIZE: { /* Compare directory sizes ourselves, then if necessary * use GnomeVFS to compare file sizes. */ result = compare_by_size (file_1, file_2); if (result == 0) { result = compare_by_full_path (file_1, file_2); } } break; case NAUTILUS_FILE_SORT_BY_TYPE: { /* GnomeVFS doesn't know about our special text for certain * mime types, so we handle the mime-type sorting ourselves. */ result = compare_by_type (file_1, file_2); if (result == 0) { result = compare_by_full_path (file_1, file_2); } } break; case NAUTILUS_FILE_SORT_BY_STARRED: { result = compare_by_starred (file_1, file_2); if (result == 0) { result = compare_by_full_path (file_1, file_2); } } break; case NAUTILUS_FILE_SORT_BY_MTIME: { result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_MODIFIED); if (result == 0) { result = compare_by_full_path (file_1, file_2); } } break; case NAUTILUS_FILE_SORT_BY_ATIME: { result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_ACCESSED); if (result == 0) { result = compare_by_full_path (file_1, file_2); } } break; case NAUTILUS_FILE_SORT_BY_BTIME: { result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_CREATED); if (result == 0) { result = compare_by_full_path (file_1, file_2); } } break; case NAUTILUS_FILE_SORT_BY_TRASHED_TIME: { result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_TRASHED); if (result == 0) { result = compare_by_full_path (file_1, file_2); } } break; case NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE: { result = compare_by_search_relevance (file_1, file_2); if (result == 0) { result = compare_by_full_path (file_1, file_2); /* ensure alphabetical order for files of the same relevance * grows in reverse (higher character = lower relevance) */ result = -result; } } break; case NAUTILUS_FILE_SORT_BY_RECENCY: { result = compare_by_time (file_1, file_2, NAUTILUS_DATE_TYPE_RECENCY); if (result == 0) { result = compare_by_full_path (file_1, file_2); } } break; default: { g_return_val_if_reached (0); } } if (reversed) { result = -result; } } return result; } int nautilus_file_compare_for_sort_by_attribute_q (NautilusFile *file_1, NautilusFile *file_2, GQuark attribute, gboolean directories_first, gboolean reversed) { int result; if (file_1 == file_2) { return 0; } /* Convert certain attributes into NautilusFileSortTypes and use * nautilus_file_compare_for_sort() */ if (attribute == 0 || attribute == attribute_name_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, directories_first, reversed); } else if (attribute == attribute_size_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_SIZE, directories_first, reversed); } else if (attribute == attribute_type_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_TYPE, directories_first, reversed); } else if (attribute == attribute_starred_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_STARRED, directories_first, reversed); } else if (attribute == attribute_modification_date_q || attribute == attribute_date_modified_q || attribute == attribute_date_modified_full_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_MTIME, directories_first, reversed); } else if (attribute == attribute_accessed_date_q || attribute == attribute_date_accessed_q || attribute == attribute_date_accessed_full_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_ATIME, directories_first, reversed); } else if (attribute == attribute_date_created_q || attribute == attribute_date_created_full_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_BTIME, directories_first, reversed); } else if (attribute == attribute_trashed_on_q || attribute == attribute_trashed_on_full_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_TRASHED_TIME, directories_first, reversed); } else if (attribute == attribute_search_relevance_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE, directories_first, reversed); } else if (attribute == attribute_recency_q) { return nautilus_file_compare_for_sort (file_1, file_2, NAUTILUS_FILE_SORT_BY_RECENCY, directories_first, reversed); } /* it is a normal attribute, compare by strings */ result = nautilus_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed); if (result == 0) { char *value_1; char *value_2; value_1 = nautilus_file_get_string_attribute_q (file_1, attribute); value_2 = nautilus_file_get_string_attribute_q (file_2, attribute); if (value_1 != NULL && value_2 != NULL) { result = strcmp (value_1, value_2); } g_free (value_1); g_free (value_2); if (reversed) { result = -result; } } return result; } int nautilus_file_compare_for_sort_by_attribute (NautilusFile *file_1, NautilusFile *file_2, const char *attribute, gboolean directories_first, gboolean reversed) { return nautilus_file_compare_for_sort_by_attribute_q (file_1, file_2, g_quark_from_string (attribute), directories_first, reversed); } /** * nautilus_file_compare_name: * @file: A file object * @string: A string we are comparing it with * * Return value: result of a comparison of the file name and the given string. **/ int nautilus_file_compare_display_name (NautilusFile *file, const char *string) { const char *name; int result; g_return_val_if_fail (string != NULL, -1); name = nautilus_file_peek_display_name (file); result = g_strcmp0 (name, string); return result; } gboolean nautilus_file_is_hidden_file (NautilusFile *file) { return file->details->is_hidden; } /** * nautilus_file_should_show: * @file: the file to check * @show_hidden: whether we want to show hidden files or not * * Determines if a #NautilusFile should be shown. Note that when browsing * a trash directory, this function will always return %TRUE. * * Returns: %TRUE if the file should be shown, %FALSE if it shouldn't. */ gboolean nautilus_file_should_show (NautilusFile *file, gboolean show_hidden) { /* Never hide any files in trash. */ if (nautilus_file_is_in_trash (file)) { return TRUE; } if (!show_hidden && file->details->file_info_is_up_to_date && nautilus_file_is_hidden_file (file)) { return FALSE; } return TRUE; } gboolean nautilus_file_is_home (NautilusFile *file) { g_autoptr (GFile) location = NULL; g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); location = nautilus_directory_get_location (file->details->directory); if (location == NULL) { return FALSE; } return nautilus_is_home_directory_file (location, file->details->name); } gboolean nautilus_file_is_in_search (NautilusFile *file) { g_autoptr (GFile) location = nautilus_file_get_location (file); return g_file_has_uri_scheme (location, SCHEME_SEARCH); } /** * nautilus_file_list_filter * @file_list: (transfer full): A list of files to filter * @filter_function: Function that decides which items to keep in list * @user_data: Passed as is to @filter_function * * Removes and frees all files from the given list that don't pass the given * filter function. * * Returns: (transfer full): the filtered file list */ NautilusFileList * nautilus_file_list_filter (NautilusFileList *file_list, NautilusFileFilterFunc filter_function, gpointer user_data) { GList *next = NULL; for (NautilusFileList *node = file_list; node != NULL; node = next) { NautilusFile *file = node->data; next = node->next; if (!filter_function (file, user_data)) { nautilus_file_unref (file); file_list = g_list_delete_link (file_list, node); } } return file_list; } gboolean nautilus_file_list_are_all_folders (const GList *files) { const GList *l; for (l = files; l != NULL; l = l->next) { if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data))) { return FALSE; } } return TRUE; } NautilusFileList * nautilus_file_list_from_uris (const GStrv uris) { if (uris == NULL) { return NULL; } NautilusFileList *list = NULL; for (guint i = 0; uris[i] != NULL; i++) { list = g_list_prepend (list, nautilus_file_get_by_uri (uris[i])); } return g_list_reverse (list); } static const char * nautilus_file_get_metadata_impl (NautilusFile *file, const char *key, const char *default_metadata) { if (file->details->metadata == NULL) { return default_metadata; } guint id = nautilus_metadata_get_id (key); const char *value = g_hash_table_lookup (file->details->metadata, GUINT_TO_POINTER (id)); return (value != NULL) ? value : default_metadata; } const char * nautilus_file_get_metadata (NautilusFile *file, const char *key, const char *default_metadata) { g_return_val_if_fail (key != NULL, default_metadata); g_return_val_if_fail (key[0] != '\0', default_metadata); g_return_val_if_fail (NAUTILUS_IS_FILE (file), default_metadata); return nautilus_file_get_metadata_impl (file, key, default_metadata); } /** * nautilus_file_get_metadata_list: * @file: A #NautilusFile to get metadata from. * @key: A string representation of the metadata key (use macros when possible). * * Get the value of a metadata attribute which holds a list of strings. * * Returns: (transfer none): A zero-terminated array of strings. */ GStrv nautilus_file_get_metadata_list (NautilusFile *file, const char *key) { guint id; g_return_val_if_fail (key != NULL, NULL); g_return_val_if_fail (key[0] != '\0', NULL); if (file == NULL || file->details->metadata == NULL) { return NULL; } g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); id = nautilus_metadata_get_id (key); id |= METADATA_ID_IS_LIST_MASK; return g_hash_table_lookup (file->details->metadata, GUINT_TO_POINTER (id)); } void nautilus_file_set_metadata (NautilusFile *file, const char *key, const char *default_metadata, const char *metadata) { const char *val; g_return_if_fail (NAUTILUS_IS_FILE (file)); g_return_if_fail (key != NULL); g_return_if_fail (key[0] != '\0'); val = metadata; if (val == NULL) { val = default_metadata; } NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->set_metadata (file, key, val); } /** * nautilus_file_set_metadata_list: * @file: A #NautilusFile to set metadata into. * @key: A string representation of the metadata key (use macros when possible). * @list: (transfer none): A zero-terminated array of newly allocated strings. * * Set the value of a metadata attribute which takes a list of strings. */ void nautilus_file_set_metadata_list (NautilusFile *file, const char *key, gchar **list) { g_return_if_fail (NAUTILUS_IS_FILE (file)); g_return_if_fail (key != NULL); g_return_if_fail (key[0] != '\0'); NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->set_metadata_as_list (file, key, list); } gboolean nautilus_file_get_boolean_metadata (NautilusFile *file, const char *key, gboolean default_metadata) { g_return_val_if_fail (key != NULL, default_metadata); g_return_val_if_fail (key[0] != '\0', default_metadata); g_return_val_if_fail (NAUTILUS_IS_FILE (file), default_metadata); if (file == NULL) { return default_metadata; } const char *value = nautilus_file_get_metadata_impl (file, key, default_metadata ? "true" : "false"); g_assert (value != NULL); if (g_ascii_strcasecmp (value, "true") == 0) { return TRUE; } else if (g_ascii_strcasecmp (value, "false") == 0) { return FALSE; } else { g_error ("boolean metadata with value other than true or false"); return default_metadata; } } void nautilus_file_set_boolean_metadata (NautilusFile *file, const char *key, gboolean metadata) { g_return_if_fail (NAUTILUS_IS_FILE (file)); g_return_if_fail (key != NULL); g_return_if_fail (key[0] != '\0'); nautilus_file_set_metadata (file, key, NULL, /* No default needed. Boolean string below is never NULL. */ metadata ? "true" : "false"); } static const char * nautilus_file_peek_display_name_collation_key (NautilusFile *file) { const char *res; res = file->details->display_name_collation_key; if (res == NULL) { res = ""; } return res; } static const char * nautilus_file_peek_display_name (NautilusFile *file) { const char *name; char *escaped_name; /* FIXME: for some reason we can get a NautilusFile instance which is * no longer valid or could be freed somewhere else in the same time. * There's race condition somewhere. See bug 602500. */ if (G_UNLIKELY (file == NULL || nautilus_file_is_gone (file))) { return ""; } /* Default to display name based on filename if its not set yet */ if (G_UNLIKELY (file->details->display_name == NULL)) { name = file->details->name; if (g_utf8_validate (name, -1, NULL)) { nautilus_file_set_display_name (file, name, NULL, FALSE); } else { escaped_name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); nautilus_file_set_display_name (file, escaped_name, NULL, FALSE); g_free (escaped_name); } } return file->details->display_name ? file->details->display_name : ""; } const char * nautilus_file_get_display_name (NautilusFile *file) { return nautilus_file_peek_display_name (file); } const char * nautilus_file_get_edit_name (NautilusFile *file) { return file->details->edit_name != NULL ? file->details->edit_name : ""; } void nautilus_file_monitor_add (NautilusFile *file, gconstpointer client, NautilusAttributes attributes) { g_return_if_fail (NAUTILUS_IS_FILE (file)); g_return_if_fail (client != NULL); NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->monitor_add (file, client, attributes); } void nautilus_file_monitor_remove (NautilusFile *file, gconstpointer client) { g_return_if_fail (NAUTILUS_IS_FILE (file)); g_return_if_fail (client != NULL); NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->monitor_remove (file, client); } gboolean nautilus_file_has_activation_uri (NautilusFile *file) { return file->details->activation_uri != NULL; } GFile * nautilus_file_get_activation_location (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); if (file->details->activation_uri != NULL) { return g_file_new_for_uri (file->details->activation_uri); } return nautilus_file_get_location (file); } static gboolean is_uri_relative (const char *uri) { char *scheme; gboolean ret; scheme = g_uri_parse_scheme (uri); ret = (scheme == NULL); g_free (scheme); return ret; } static char * get_custom_icon_metadata_uri (NautilusFile *file) { const char *uri = nautilus_file_get_metadata_impl (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON, NULL); if (uri != NULL && nautilus_file_is_directory (file) && is_uri_relative (uri)) { g_autofree char *dir_uri = nautilus_file_get_uri (file); return g_build_filename (dir_uri, uri, NULL); } else { return g_strdup (uri); } } static const char * get_custom_icon_metadata_name (NautilusFile *file) { return nautilus_file_get_metadata_impl (file, NAUTILUS_METADATA_KEY_CUSTOM_ICON_NAME, NULL); } static GIcon * get_mount_icon (NautilusFile *file, gboolean symbolic) { g_autoptr (GMount) mount = nautilus_file_get_mount (file); if (mount != NULL) { return symbolic ? g_mount_get_symbolic_icon (mount) : g_mount_get_icon (mount); } else { g_autoptr (GFile) location = nautilus_file_get_location (file); /* Root directory doesn't have a GMount, but for UI purposes we want * it to be treated the same way. */ if (nautilus_is_root_directory (location)) { return symbolic ? g_themed_icon_new_with_default_fallbacks ("drive-harddisk-symbolic") : g_themed_icon_new_with_default_fallbacks ("drive-harddisk"); } } return NULL; } static gboolean has_custom_icon (NautilusFile *file) { g_autofree gchar *custom_icon_uri = get_custom_icon_metadata_uri (file); return custom_icon_uri != NULL || get_custom_icon_metadata_name (file) != NULL; } static GIcon * get_custom_icon (NautilusFile *file) { char *custom_icon_uri; GFile *icon_file; GIcon *icon; icon = NULL; /* Metadata takes precedence; first we look at the custom * icon URI, then at the custom icon name. */ custom_icon_uri = get_custom_icon_metadata_uri (file); if (custom_icon_uri) { icon_file = g_file_new_for_uri (custom_icon_uri); icon = g_file_icon_new (icon_file); g_object_unref (icon_file); g_free (custom_icon_uri); } if (icon == NULL) { const char *custom_icon_name = get_custom_icon_metadata_name (file); if (custom_icon_name != NULL) { icon = g_themed_icon_new_with_default_fallbacks (custom_icon_name); } } return icon; } static GFilesystemPreviewType get_filesystem_use_preview (NautilusFile *file, NautilusFile *parent) { if (file->details->filesystem_info_is_up_to_date) { return file->details->filesystem_use_preview; } else if (parent != NULL && parent->details->filesystem_info_is_up_to_date) { return parent->details->filesystem_use_preview; } else { return G_FILESYSTEM_PREVIEW_TYPE_IF_ALWAYS; } } static gboolean get_filesystem_remote (NautilusFile *file, NautilusFile *parent) { if (file->details->filesystem_info_is_up_to_date) { return file->details->filesystem_remote; } g_autoptr (NautilusFile) tmp_parent = NULL; if (parent == NULL) { tmp_parent = nautilus_file_get_parent (file); parent = tmp_parent; } if (parent != NULL && parent->details->filesystem_info_is_up_to_date) { if (nautilus_file_is_regular_file (file)) { file->details->filesystem_remote = parent->details->filesystem_remote; file->details->filesystem_readonly = parent->details->filesystem_readonly; file->details->filesystem_use_preview = parent->details->filesystem_use_preview; file->details->filesystem_info_is_up_to_date = TRUE; } return parent->details->filesystem_remote; } /* Pretend it's local */ return FALSE; } static gboolean get_speed_tradeoff_preference_for_file (NautilusFile *file, NautilusSpeedTradeoffValue value) { if (value == NAUTILUS_SPEED_TRADEOFF_NEVER) { return FALSE; } g_autoptr (NautilusFile) parent = file->details->filesystem_info_is_up_to_date ? NULL : nautilus_file_get_parent (file); GFilesystemPreviewType use_preview = get_filesystem_use_preview (file, parent); if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) { /* file system says to never preview anything */ return FALSE; } else if (value == NAUTILUS_SPEED_TRADEOFF_ALWAYS) { /* we don't care whether it's local or not */ return TRUE; } /* value == NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY */ else if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL) { /* file system says we should treat file as if it's local */ return TRUE; } else { /* check whether file is not remote */ return !get_filesystem_remote (file, parent); } } gboolean nautilus_file_should_show_thumbnail (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); if (has_custom_icon (file)) { return FALSE; } const char *mime_type = file->details->mime_type; if (mime_type == NULL) { mime_type = "application/octet-stream"; } /* If the thumbnail has already been created, don't care about the size * of the original file. */ if (!nautilus_file_has_thumbnail (file) && nautilus_file_get_size (file) > cached_thumbnail_limit && nautilus_thumbnail_is_mimetype_limited_by_size (mime_type)) { return FALSE; } if (show_file_thumbs != NAUTILUS_SPEED_TRADEOFF_NEVER && file->details->has_preview_icon) { /* The thumbnail should be generated if the preview icon is available * regardless of the filesystem type (i.e. for MTP/GPhoto2 backends). */ return TRUE; } return get_speed_tradeoff_preference_for_file (file, show_file_thumbs); } static GList * strv_to_glist (GStrv strv) { GList *list = NULL; for (guint i = 0; strv != NULL && strv[i] != NULL; i++) { list = g_list_prepend (list, strv[i]); } return list; } static GList * get_automatic_emblem_keywords (NautilusFile *file) { GList *keywords = NULL; if (nautilus_file_is_symbolic_link (file)) { keywords = g_list_prepend (keywords, NAUTILUS_FILE_EMBLEM_NAME_SYMBOLIC_LINK); } if (!nautilus_file_can_read (file)) { keywords = g_list_prepend (keywords, NAUTILUS_FILE_EMBLEM_NAME_CANT_READ); } /* Trash files are assumed to be read-only, so we want to ignore them here. */ else if (!nautilus_file_can_write (file) && !nautilus_file_is_in_trash (file)) { g_autoptr (NautilusFile) parent = nautilus_file_get_parent (file); if (parent == NULL || nautilus_file_can_write (parent)) { keywords = g_list_prepend (keywords, NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE); } } return keywords; } static GList * get_extension_emblem_keywords (NautilusFile *file) { const GStrv key_emblems = nautilus_file_get_metadata_list (file, NAUTILUS_METADATA_KEY_EMBLEMS); GList *keywords = g_list_concat (g_list_copy (file->details->extension_emblems), strv_to_glist (key_emblems)); if (keywords != NULL) { /* Sort and deduplicate keywords */ keywords = g_list_sort (keywords, (GCompareFunc) g_utf8_collate); for (GList *l = keywords; l->next != NULL;) { if (strcmp ((const char *) l->data, (const char *) l->next->data) == 0) { keywords = g_list_delete_link (keywords, l->next); } else { l = l->next; } } } return keywords; } /** * nautilus_file_get_emblem_icons * * Return the list of names of emblems that this file should display, * in canonical order. * @file: (not nullable): NautilusFile representing the file in question. * * Returns: (transfer full) (element-type GIcon): A list of emblem names. * **/ GList * nautilus_file_get_emblem_icons (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); GList *icons = NULL; GIcon *mount_icon = get_mount_icon (file, TRUE); g_autoptr (GList) keywords = g_list_concat (get_automatic_emblem_keywords (file), get_extension_emblem_keywords (file)); for (GList *l = keywords; l != NULL; l = l->next) { char *keyword = l->data; g_autofree char *prefixed = g_strconcat ("emblem-", keyword, NULL); char *icon_names[2] = { prefixed, keyword }; icons = g_list_prepend (icons, g_themed_icon_new_from_names (icon_names, 2)); } if (mount_icon != NULL) { icons = g_list_prepend (icons, mount_icon); } return icons; } GIcon * nautilus_file_get_gicon (NautilusFile *file, NautilusFileIconFlags flags) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); GIcon *icon = get_custom_icon (file); if (icon != NULL) { return icon; } else if (flags & NAUTILUS_FILE_ICON_FLAGS_USE_MOUNT_ICON && (icon = get_mount_icon (file, FALSE)) != NULL) { return icon; } else if (file->details->icon != NULL) { return g_object_ref (file->details->icon); } return g_object_ref (nautilus_icon_info_get_default_file_icon ()); } const char * nautilus_file_get_thumbnail_path (NautilusFile *file) { return file->details->thumbnail_path; } gboolean nautilus_file_has_thumbnail (NautilusFile *file) { return file->details->thumbnail_is_up_to_date && file->details->thumbnail != NULL; } static GdkPaintable * nautilus_file_get_thumbnail_icon (NautilusFile *file, int size, int scale, NautilusFileIconFlags flags) { GdkPaintable *paintable = NULL; if (nautilus_file_has_thumbnail (file)) { GdkTexture *texture = file->details->thumbnail; double width = gdk_texture_get_width (texture) / scale; double height = gdk_texture_get_height (texture) / scale; g_autoptr (GtkSnapshot) snapshot = gtk_snapshot_new (); GskRoundedRect rounded_rect; if (MAX (width, height) != size) { double scale_factor = size / MAX (width, height); width = round (width * scale_factor); height = round (height * scale_factor); } gsk_rounded_rect_init_from_rect (&rounded_rect, &GRAPHENE_RECT_INIT (0, 0, width, height), 2 /* radius*/); gtk_snapshot_push_rounded_clip (snapshot, &rounded_rect); gdk_paintable_snapshot (GDK_PAINTABLE (texture), GDK_SNAPSHOT (snapshot), width, height); if (size >= NAUTILUS_GRID_ICON_SIZE_SMALL && nautilus_mime_is_video (file->details->mime_type)) { nautilus_ui_frame_video (snapshot, width, height); } gtk_snapshot_pop (snapshot); /* End rounded clip */ if (g_getenv ("G_MESSAGES_DEBUG") != NULL) { g_debug ("Returning thumbnailed image, at size %d %d", (int) (width), (int) (height)); } paintable = gtk_snapshot_to_paintable (snapshot, NULL); } if (paintable != NULL) { return paintable; } if (nautilus_file_check_if_ready (file, NAUTILUS_ATTRIBUTE_THUMBNAIL_INFO) && !nautilus_file_check_if_ready (file, NAUTILUS_ATTRIBUTE_THUMBNAIL_BUFFER)) { g_autoptr (GIcon) gicon = g_themed_icon_new (ICON_NAME_THUMBNAIL_LOADING); paintable = nautilus_icon_info_lookup (gicon, size, scale); } return paintable; } GdkPaintable * nautilus_file_get_icon_paintable (NautilusFile *file, int size, int scale, NautilusFileIconFlags flags) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); g_autoptr (GIcon) custom_gicon = get_custom_icon (file); if (custom_gicon != NULL) { return nautilus_icon_info_lookup (custom_gicon, size, scale); } GdkPaintable *paintable = NULL; if (g_getenv ("G_MESSAGES_DEBUG") != NULL) { g_debug ("Called file_get_icon(), at size %d", size); } if (flags & NAUTILUS_FILE_ICON_FLAGS_USE_THUMBNAILS && size >= NAUTILUS_THUMBNAIL_MINIMUM_ICON_SIZE && nautilus_file_should_show_thumbnail (file)) { paintable = nautilus_file_get_thumbnail_icon (file, size, scale, flags); } if (paintable == NULL) { g_autoptr (GIcon) gicon = nautilus_file_get_gicon (file, flags); paintable = nautilus_icon_info_lookup (gicon, size, scale); } return paintable; } GDateTime * nautilus_file_get_date (NautilusFile *file, NautilusDateType date_type) { if (file == NULL) { return NULL; } g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); time_t file_time_raw = 0; switch (date_type) { case NAUTILUS_DATE_TYPE_ACCESSED: { file_time_raw = nautilus_file_get_atime (file); } break; case NAUTILUS_DATE_TYPE_MODIFIED: { file_time_raw = nautilus_file_get_mtime (file); } break; case NAUTILUS_DATE_TYPE_CREATED: { file_time_raw = nautilus_file_get_btime (file); } break; case NAUTILUS_DATE_TYPE_TRASHED: { file_time_raw = nautilus_file_get_trash_time (file); } break; case NAUTILUS_DATE_TYPE_RECENCY: { file_time_raw = nautilus_file_get_recency (file); } break; default: { g_return_val_if_reached (NULL); } } /* Before we have info on a file, the date is unknown. */ if (file_time_raw == 0) { return NULL; } return g_date_time_new_from_unix_local (file_time_raw); } static char * nautilus_file_get_where_string (NautilusFile *file) { if (file == NULL) { return NULL; } g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); g_autoptr (NautilusFile) real_file = NULL; if (nautilus_file_is_in_recent (file)) { g_autoptr (GFile) activation_location = nautilus_file_get_activation_location (file); real_file = nautilus_file_get (activation_location); } else { real_file = g_object_ref (file); } return nautilus_file_get_parent_uri_for_display (real_file); } static char * nautilus_file_get_trash_original_file_parent_as_string (NautilusFile *file) { NautilusFile *orig_file; char *filename; filename = NULL; orig_file = nautilus_file_get_trash_original_file (file); if (orig_file != NULL) { filename = nautilus_file_get_parent_uri_for_display (orig_file); nautilus_file_unref (orig_file); } return filename; } /** * nautilus_file_get_date_as_string: * * Get a user-displayable string representing a file modification date. * The caller is responsible for g_free-ing this string. * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_date_as_string (NautilusFile *file, NautilusDateType date_type, NautilusDateFormat date_format) { g_autoptr (GDateTime) file_date_time = nautilus_file_get_date (file, date_type); if (file_date_time == NULL) { return NULL; } return nautilus_date_to_str (file_date_time, date_format == NAUTILUS_DATE_FORMAT_REGULAR); } gboolean nautilus_file_should_show_directory_item_count (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); /* Don't count items in autofs directories to avoid triggering automount. */ if (file->details->is_mountpoint) { g_autoptr (GFile) location = nautilus_file_get_location (file); if (nautilus_location_is_autofs_mountpoint (location)) { return FALSE; } } return get_speed_tradeoff_preference_for_file (file, show_directory_item_count); } /** * nautilus_file_get_directory_item_count * * Get the number of items in a directory. * @file: NautilusFile representing a directory. * @count: Place to put count. * @count_unreadable: Set to TRUE (if non-NULL) if permissions prevent * the item count from being read on this directory. Otherwise set to FALSE. * * Returns: TRUE if count is available. * **/ gboolean nautilus_file_get_directory_item_count (NautilusFile *file, guint *count, gboolean *count_unreadable) { if (count != NULL) { *count = 0; } if (count_unreadable != NULL) { *count_unreadable = FALSE; } g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); if (!nautilus_file_is_directory (file)) { return FALSE; } if (!nautilus_file_should_show_directory_item_count (file)) { return FALSE; } return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_item_count (file, count, count_unreadable); } /** * nautilus_file_get_deep_counts * * Get the statistics about items inside a directory. * @file: NautilusFile representing a directory or file. * @directory_count: Place to put count of directories inside. * @files_count: Place to put count of files inside. * @unreadable_directory_count: Number of directories encountered * that were unreadable. * @total_size: Total size of all files and directories visited. * @force: Whether the deep counts should even be collected if * nautilus_file_should_show_directory_item_count returns FALSE * for this file. * * Returns: Status to indicate whether sizes are available. * **/ NautilusRequestStatus nautilus_file_get_deep_counts (NautilusFile *file, guint *directory_count, guint *file_count, guint *unreadable_directory_count, goffset *total_size, gboolean force) { if (directory_count != NULL) { *directory_count = 0; } if (file_count != NULL) { *file_count = 0; } if (unreadable_directory_count != NULL) { *unreadable_directory_count = 0; } if (total_size != NULL) { *total_size = 0; } g_return_val_if_fail (NAUTILUS_IS_FILE (file), NAUTILUS_REQUEST_DONE); if (!force && !nautilus_file_should_show_directory_item_count (file)) { /* Set field so an existing value isn't treated as up-to-date * when preference changes later. */ file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED; return file->details->deep_counts_status; } return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->get_deep_counts (file, directory_count, file_count, unreadable_directory_count, total_size); } void nautilus_file_recompute_deep_counts (NautilusFile *file) { if (file->details->deep_counts_status != NAUTILUS_REQUEST_IN_PROGRESS) { file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED; if (file->details->directory != NULL) { nautilus_directory_add_file_to_work_queue (file->details->directory, file); nautilus_directory_async_state_changed (file->details->directory); } } } gboolean nautilus_file_can_get_size (NautilusFile *file) { return file->details->size == -1; } /** * nautilus_file_get_size * * Get the file size. * @file: NautilusFile representing the file in question. * * Returns: Size in bytes. * **/ guint64 nautilus_file_get_size (NautilusFile *file) { /* Before we have info on the file, we don't know the size. */ if (file->details->size == -1) { return 0; } return file->details->size; } time_t nautilus_file_get_mtime (NautilusFile *file) { return file->details->mtime; } time_t nautilus_file_get_atime (NautilusFile *file) { return file->details->atime; } time_t nautilus_file_get_btime (NautilusFile *file) { return file->details->btime; } time_t nautilus_file_get_recency (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), 0); return file->details->recency; } time_t nautilus_file_get_trash_time (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), 0); return file->details->trash_time; } static void set_attributes_get_info_callback (GObject *source_object, GAsyncResult *res, gpointer callback_data) { NautilusFileOperation *op; GFileInfo *new_info; GError *error; op = callback_data; error = NULL; new_info = g_file_query_info_finish (G_FILE (source_object), res, &error); if (new_info != NULL) { if (nautilus_file_update_info (op->file, new_info)) { nautilus_file_changed (op->file); } g_object_unref (new_info); } nautilus_file_operation_complete (op, NULL, error); if (error) { g_error_free (error); } } static void set_attributes_callback (GObject *source_object, GAsyncResult *result, gpointer callback_data) { NautilusFileOperation *op; GError *error; gboolean res; op = callback_data; error = NULL; res = g_file_set_attributes_finish (G_FILE (source_object), result, NULL, &error); if (res) { g_file_query_info_async (G_FILE (source_object), NAUTILUS_FILE_DEFAULT_ATTRIBUTES, G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, op->cancellable, set_attributes_get_info_callback, op); } else { nautilus_file_operation_complete (op, NULL, error); g_error_free (error); } } void nautilus_file_set_attributes (NautilusFile *file, GFileInfo *attributes, NautilusFileOperationCallback callback, gpointer callback_data) { NautilusFileOperation *op; GFile *location; op = nautilus_file_operation_new (file, callback, callback_data); location = nautilus_file_get_location (file); g_file_set_attributes_async (location, attributes, 0, G_PRIORITY_DEFAULT, op->cancellable, set_attributes_callback, op); g_object_unref (location); } void nautilus_file_set_search_relevance (NautilusFile *file, gdouble relevance) { file->details->search_relevance = relevance; } void nautilus_file_set_search_fts_snippet (NautilusFile *file, const gchar *fts_snippet) { g_set_str (&file->details->fts_snippet, fts_snippet); } const gchar * nautilus_file_get_search_fts_snippet (NautilusFile *file) { return file->details->fts_snippet; } /** * nautilus_file_can_get_permissions: * * Check whether the permissions for a file are determinable. * This might not be the case for files on non-UNIX file systems. * * @file: The file in question. * * Return value: TRUE if the permissions are valid. */ gboolean nautilus_file_can_get_permissions (NautilusFile *file) { return file->details->has_permissions; } /** * nautilus_file_can_set_permissions: * * Check whether the current user is allowed to change * the permissions of a file. * * @file: The file in question. * * Return value: TRUE if the current user can change the * permissions of @file, FALSE otherwise. It's always possible * that when you actually try to do it, you will fail. */ gboolean nautilus_file_can_set_permissions (NautilusFile *file) { g_autoptr (GFile) location = nautilus_file_get_location (file); if (file->details->has_uid && g_file_is_native (location)) { /* Owner is allowed to set permissions. */ if (geteuid () == file->details->uid) { return TRUE; } /* Administrators are also allowed to set permissions. */ if (nautilus_file_is_in_admin (file)) { return TRUE; } /* Nobody else is allowed. */ return FALSE; } /* pretend to have full chmod rights when no info is available, relevant when * the FS can't provide ownership info, for instance for FTP */ return TRUE; } guint nautilus_file_get_permissions (NautilusFile *file) { g_return_val_if_fail (nautilus_file_can_get_permissions (file), 0); return file->details->permissions; } /** * nautilus_file_set_permissions: * * Change a file's permissions. This should only be called if * nautilus_file_can_set_permissions returned TRUE. * * @file: NautilusFile representing the file in question. * @new_permissions: New permissions value. This is the whole * set of permissions, not a delta. **/ void nautilus_file_set_permissions (NautilusFile *file, guint32 new_permissions, NautilusFileOperationCallback callback, gpointer callback_data) { GFileInfo *info; GError *error; if (!nautilus_file_can_set_permissions (file)) { /* Claim that something changed even if the permission change failed. * This makes it easier for some clients who see the "reverting" * to the old permissions as "changing back". */ nautilus_file_changed (file); error = g_error_new (G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Not allowed to set permissions")); (*callback)(file, NULL, error, callback_data); g_error_free (error); return; } /* Test the permissions-haven't-changed case explicitly * because we don't want to send the file-changed signal if * nothing changed. */ if (new_permissions == file->details->permissions) { (*callback)(file, NULL, NULL, callback_data); return; } if (!nautilus_file_undo_manager_is_operating ()) { NautilusFileUndoInfo *undo_info; g_autoptr (GFile) location = nautilus_file_get_location (file); undo_info = nautilus_file_undo_info_permissions_new (location, file->details->permissions, new_permissions); nautilus_file_undo_manager_set_action (undo_info); g_object_unref (undo_info); } info = g_file_info_new (); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, new_permissions); nautilus_file_set_attributes (file, info, callback, callback_data); g_object_unref (info); } /** * nautilus_file_can_get_selinux_context: * * Check whether the selinux context for a file are determinable. * This might not be the case for files on non-UNIX file systems, * files without a context or systems that don't support selinux. * * @file: The file in question. * * Return value: TRUE if the permissions are valid. */ gboolean nautilus_file_can_get_selinux_context (NautilusFile *file) { return file->details->selinux_context != NULL; } /** * nautilus_file_get_selinux_context: * * Get a user-displayable string representing a file's selinux * context * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ char * nautilus_file_get_selinux_context (NautilusFile *file) { char *translated; char *raw; g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); if (!nautilus_file_can_get_selinux_context (file)) { return NULL; } raw = file->details->selinux_context; #ifdef HAVE_SELINUX if (selinux_raw_to_trans_context (raw, &translated) == 0) { char *tmp; tmp = g_strdup (translated); freecon (translated); translated = tmp; } else #endif { translated = g_strdup (raw); } return translated; } static char * get_real_name (const char *name, const char *gecos) { char *part_before_comma, *capitalized_login_name, *real_name; const char *locale_string; g_auto (GStrv) geco_parts = NULL; if (gecos == NULL || gecos[0] == '\0') { return NULL; } geco_parts = g_strsplit (gecos, ",", 2); if (geco_parts == NULL) { return NULL; } locale_string = geco_parts[0]; if (!g_utf8_validate (locale_string, -1, NULL)) { part_before_comma = g_locale_to_utf8 (locale_string, -1, NULL, NULL, NULL); } else { part_before_comma = g_strdup (locale_string); } if (!g_utf8_validate (name, -1, NULL)) { g_autofree gchar *login_name = g_locale_to_utf8 (name, -1, NULL, NULL, NULL); capitalized_login_name = nautilus_capitalize_str (login_name); } else { capitalized_login_name = nautilus_capitalize_str (name); } if (capitalized_login_name == NULL) { real_name = part_before_comma; } else { GString *real_name_str = g_string_new_take (g_steal_pointer (&part_before_comma)); g_string_replace (real_name_str, "&", capitalized_login_name, 0); real_name = g_string_free_and_steal (real_name_str); } if (g_strcmp0 (real_name, NULL) == 0 || g_strcmp0 (name, real_name) == 0 || g_strcmp0 (capitalized_login_name, real_name) == 0) { g_free (real_name); real_name = NULL; } g_free (capitalized_login_name); return real_name; } static gboolean get_group_id_from_group_name (const char *group_name, uid_t *gid) { struct group *group; g_assert (gid != NULL); group = getgrnam (group_name); if (group == NULL) { return FALSE; } *gid = group->gr_gid; return TRUE; } static gboolean get_ids_from_user_name (const char *user_name, uid_t *uid, uid_t *gid) { struct passwd *password_info; g_assert (uid != NULL || gid != NULL); password_info = getpwnam (user_name); if (password_info == NULL) { return FALSE; } if (uid != NULL) { *uid = password_info->pw_uid; } if (gid != NULL) { *gid = password_info->pw_gid; } return TRUE; } static gboolean get_user_id_from_user_name (const char *user_name, uid_t *id) { return get_ids_from_user_name (user_name, id, NULL); } static gboolean get_id_from_digit_string (const char *digit_string, uid_t *id) { long scanned_id; char c; g_assert (id != NULL); /* Only accept string if it has one integer with nothing * afterwards. */ if (sscanf (digit_string, "%ld%c", &scanned_id, &c) != 1) { return FALSE; } *id = scanned_id; return TRUE; } /** * nautilus_file_can_get_owner: * * Check whether the owner a file is determinable. * This might not be the case for files on non-UNIX file systems. * * @file: The file in question. * * Return value: TRUE if the owner is valid. */ gboolean nautilus_file_can_get_owner (NautilusFile *file) { /* Before we have info on a file, the owner is unknown. */ return file->details->has_uid; } /** * nautilus_file_get_uid: * * Get the user id of the file's owner. * * @file: The file in question. * * Return value: (transfer none): the user id. */ uid_t nautilus_file_get_uid (NautilusFile *file) { return file->details->uid; } /** * nautilus_file_get_owner_name: * * Get the user name of the file's owner. If the owner has no * name, returns the userid as a string. The caller is responsible * for g_free-ing this string. * * @file: The file in question. * * Return value: A newly-allocated string. */ char * nautilus_file_get_owner_name (NautilusFile *file) { return nautilus_file_get_owner_as_string (file, FALSE); } /** * nautilus_file_can_set_owner: * * Check whether the current user is allowed to change * the owner of a file. * * @file: The file in question. * * Return value: TRUE if the current user can change the * owner of @file, FALSE otherwise. It's always possible * that when you actually try to do it, you will fail. */ gboolean nautilus_file_can_set_owner (NautilusFile *file) { /* Not allowed to set the owner if we can't * even read it. This can happen on non-UNIX file * systems. */ if (!nautilus_file_can_get_owner (file)) { return FALSE; } /* Owner can be changed by administrators, thanks to PolKit integration. */ return nautilus_file_is_in_admin (file); } /** * nautilus_file_set_owner: * * Set the owner of a file. This will only have any effect if * nautilus_file_can_set_owner returns TRUE. * * @file: The file in question. * @user_name_or_id: The user name to set the owner to. * If the string does not match any user name, and the * string is an integer, the owner will be set to the * userid represented by that integer. * @callback: Function called when asynch owner change succeeds or fails. * @callback_data: Parameter passed back with callback function. */ void nautilus_file_set_owner (NautilusFile *file, const char *user_name_or_id, NautilusFileOperationCallback callback, gpointer callback_data) { GError *error; GFileInfo *info; uid_t new_id; if (!nautilus_file_can_set_owner (file)) { /* Claim that something changed even if the permission * change failed. This makes it easier for some * clients who see the "reverting" to the old owner as * "changing back". */ nautilus_file_changed (file); error = g_error_new (G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Not allowed to set owner")); (*callback)(file, NULL, error, callback_data); g_error_free (error); return; } /* If no match treating user_name_or_id as name, try treating * it as id. */ if (!get_user_id_from_user_name (user_name_or_id, &new_id) && !get_id_from_digit_string (user_name_or_id, &new_id)) { /* Claim that something changed even if the permission * change failed. This makes it easier for some * clients who see the "reverting" to the old owner as * "changing back". */ nautilus_file_changed (file); error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Specified owner “%s” doesn’t exist"), user_name_or_id); (*callback)(file, NULL, error, callback_data); g_error_free (error); return; } /* Test the owner-hasn't-changed case explicitly because we * don't want to send the file-changed signal if nothing * changed. */ if (file->details->has_uid && new_id == file->details->uid) { (*callback)(file, NULL, NULL, callback_data); return; } if (!nautilus_file_undo_manager_is_operating ()) { NautilusFileUndoInfo *undo_info; g_autoptr (GFile) location = nautilus_file_get_location (file); char *current_owner; current_owner = nautilus_file_get_owner_as_string (file, FALSE); undo_info = nautilus_file_undo_info_ownership_new (NAUTILUS_FILE_UNDO_OP_CHANGE_OWNER, location, current_owner, user_name_or_id); nautilus_file_undo_manager_set_action (undo_info); g_free (current_owner); } info = g_file_info_new (); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, new_id); nautilus_file_set_attributes (file, info, callback, callback_data); g_object_unref (info); } /** * nautilus_get_user_names: * * Get a list of user names. For users with a different associated * "real name", the real name follows the standard user name, separated * by a dash surrounded by spaces. The caller is responsible for freeing * this list and its contents. */ GList * nautilus_get_user_names (void) { GList *list; char *real_name, *name; struct passwd *user; list = NULL; setpwent (); while ((user = getpwent ()) != NULL) { real_name = get_real_name (user->pw_name, user->pw_gecos); if (real_name != NULL && !g_str_equal (real_name, "")) { name = g_strconcat (user->pw_name, " – ", real_name, NULL); } else { name = g_strdup (user->pw_name); } g_free (real_name); list = g_list_prepend (list, name); } endpwent (); return g_list_sort (list, (GCompareFunc) g_utf8_collate); } /** * nautilus_file_can_get_group: * * Check whether the group a file is determinable. * This might not be the case for files on non-UNIX file systems. * * @file: The file in question. * * Return value: TRUE if the group is valid. */ gboolean nautilus_file_can_get_group (NautilusFile *file) { /* Before we have info on a file, the group is unknown. */ return file->details->has_gid; } /** * nautilus_file_get_gid: * * Get the group id of the file's group. * * @file: The file in question. * * Return value: (transfer none): the group id. */ gid_t nautilus_file_get_gid (NautilusFile *file) { return file->details->gid; } /** * nautilus_file_get_group_name: * * Get the name of the file's group. If the group has no * name, returns the groupid as a string. * * @file: The file in question. * * Return value: A newly-allocated string. **/ const char * nautilus_file_get_group_name (NautilusFile *file) { return file->details->group; } /** * nautilus_file_can_set_group: * * Check whether the current user is allowed to change * the group of a file. * * @file: The file in question. * * Return value: TRUE if the current user can change the * group of @file, FALSE otherwise. It's always possible * that when you actually try to do it, you will fail. */ gboolean nautilus_file_can_set_group (NautilusFile *file) { /* Not allowed to set the permissions if we can't * even read them. This can happen on non-UNIX file * systems. */ if (!nautilus_file_can_get_group (file)) { return FALSE; } /* Owner is allowed to set group (with restrictions). */ if (file->details->has_uid && geteuid () == file->details->uid) { return TRUE; } /* Groups can be changed by administrators, thanks to PolKit integration. */ if (nautilus_file_is_in_admin (file)) { return TRUE; } /* Nobody else is allowed. */ return FALSE; } /* Get a list of group names, filtered to only the ones * that contain the given username. If the username is * NULL, returns a list of all group names. */ static GList * nautilus_get_group_names_for_user (void) { GList *list; struct group *group; int count, i; gid_t gid_list[NGROUPS_MAX + 1]; list = NULL; count = getgroups (NGROUPS_MAX + 1, gid_list); for (i = 0; i < count; i++) { group = getgrgid (gid_list[i]); if (group == NULL) { break; } list = g_list_prepend (list, g_strdup (group->gr_name)); } return g_list_sort (list, (GCompareFunc) g_utf8_collate); } /** * nautilus_get_group_names: * * Get a list of all group names. */ GList * nautilus_get_all_group_names (void) { GList *list; struct group *group; list = NULL; setgrent (); while ((group = getgrent ()) != NULL) { list = g_list_prepend (list, g_strdup (group->gr_name)); } endgrent (); return g_list_sort (list, (GCompareFunc) g_utf8_collate); } /** * nautilus_file_get_settable_group_names: * * Get a list of all group names that the current user * can set the group of a specific file to. * * @file: The NautilusFile in question. */ GList * nautilus_file_get_settable_group_names (NautilusFile *file) { GList *result = NULL; if (!nautilus_file_can_set_group (file)) { return NULL; } if (nautilus_file_is_in_admin (file)) { /* Administrators are allowed to set group to anything. */ result = nautilus_get_all_group_names (); } else if (file->details->has_uid && geteuid () == file->details->uid) { /* Owner is allowed to set group to any that owner is member of. */ result = nautilus_get_group_names_for_user (); } else { g_warning ("unhandled case in nautilus_get_settable_group_names"); } return result; } /** * nautilus_file_set_group: * * Set the group of a file. This will only have any effect if * nautilus_file_can_set_group returns TRUE. * * @file: The file in question. * @group_name_or_id: The group name to set the owner to. * If the string does not match any group name, and the * string is an integer, the group will be set to the * group id represented by that integer. * @callback: Function called when asynch group change succeeds or fails. * @callback_data: Parameter passed back with callback function. */ void nautilus_file_set_group (NautilusFile *file, const char *group_name_or_id, NautilusFileOperationCallback callback, gpointer callback_data) { GError *error; GFileInfo *info; uid_t new_id; if (!nautilus_file_can_set_group (file)) { /* Claim that something changed even if the group * change failed. This makes it easier for some * clients who see the "reverting" to the old group as * "changing back". */ nautilus_file_changed (file); error = g_error_new (G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Not allowed to set group")); (*callback)(file, NULL, error, callback_data); g_error_free (error); return; } /* If no match treating group_name_or_id as name, try treating * it as id. */ if (!get_group_id_from_group_name (group_name_or_id, &new_id) && !get_id_from_digit_string (group_name_or_id, &new_id)) { /* Claim that something changed even if the group * change failed. This makes it easier for some * clients who see the "reverting" to the old group as * "changing back". */ nautilus_file_changed (file); error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Specified group “%s” doesn’t exist"), group_name_or_id); (*callback)(file, NULL, error, callback_data); g_error_free (error); return; } if (file->details->has_gid && new_id == file->details->gid) { (*callback)(file, NULL, NULL, callback_data); return; } if (!nautilus_file_undo_manager_is_operating ()) { NautilusFileUndoInfo *undo_info; g_autoptr (GFile) location = nautilus_file_get_location (file); const char *current_group = nautilus_file_get_group_name (file); undo_info = nautilus_file_undo_info_ownership_new (NAUTILUS_FILE_UNDO_OP_CHANGE_GROUP, location, current_group, group_name_or_id); nautilus_file_undo_manager_set_action (undo_info); } info = g_file_info_new (); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, new_id); nautilus_file_set_attributes (file, info, callback, callback_data); g_object_unref (info); } /** * nautilus_file_get_octal_permissions_as_string: * * Get a user-displayable string representing a file's permissions * as an octal number. The caller * is responsible for g_free-ing this string. * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_octal_permissions_as_string (NautilusFile *file) { guint32 permissions; g_assert (NAUTILUS_IS_FILE (file)); if (!nautilus_file_can_get_permissions (file)) { return NULL; } permissions = file->details->permissions; return g_strdup_printf ("%03o", permissions); } /** * nautilus_file_get_permissions_as_string: * * Get a user-displayable string representing a file's permissions. The caller * is responsible for g_free-ing this string. * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_permissions_as_string (NautilusFile *file) { guint32 permissions; gboolean is_directory; gboolean is_link; gboolean suid, sgid, sticky; if (!nautilus_file_can_get_permissions (file)) { return NULL; } g_assert (NAUTILUS_IS_FILE (file)); permissions = file->details->permissions; is_directory = file->details->type == G_FILE_TYPE_DIRECTORY; is_link = nautilus_file_is_symbolic_link (file); /* We use ls conventions for displaying these three obscure flags */ suid = permissions & S_ISUID; sgid = permissions & S_ISGID; sticky = permissions & S_ISVTX; return g_strdup_printf ("%c%c%c%c%c%c%c%c%c%c", is_link ? 'l' : is_directory ? 'd' : '-', permissions & S_IRUSR ? 'r' : '-', permissions & S_IWUSR ? 'w' : '-', permissions & S_IXUSR ? (suid ? 's' : 'x') : (suid ? 'S' : '-'), permissions & S_IRGRP ? 'r' : '-', permissions & S_IWGRP ? 'w' : '-', permissions & S_IXGRP ? (sgid ? 's' : 'x') : (sgid ? 'S' : '-'), permissions & S_IROTH ? 'r' : '-', permissions & S_IWOTH ? 'w' : '-', permissions & S_IXOTH ? (sticky ? 't' : 'x') : (sticky ? 'T' : '-')); } /** * nautilus_file_get_owner_as_string: * * Get a user-displayable string representing a file's owner. The caller * is responsible for g_free-ing this string. * @file: NautilusFile representing the file in question. * @include_real_name: Whether or not to append the real name (if any) * for this user after the user name. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_owner_as_string (NautilusFile *file, gboolean include_real_name) { char *user_name; /* Before we have info on a file, the owner is unknown. */ if (file->details->owner == NULL && file->details->owner_real == NULL) { return NULL; } if (include_real_name && file->details->has_uid && file->details->uid == getuid ()) { /* Translators: This is a username followed by "(You)" to indicate the file is owned by the current user */ user_name = g_strdup_printf (_("%s (You)"), file->details->owner); } else if (file->details->owner_real == NULL || *file->details->owner_real == '\0') { user_name = g_strdup (file->details->owner); } else if (file->details->owner == NULL || *file->details->owner == '\0') { user_name = g_strdup (file->details->owner_real); } else if (include_real_name && strcmp (file->details->owner, file->details->owner_real) != 0) { user_name = g_strdup (file->details->owner_real); } else { user_name = g_strdup (file->details->owner); } return user_name; } static char * format_item_count_for_display (guint item_count, gboolean includes_directories, gboolean includes_files) { g_assert (includes_directories || includes_files); return g_strdup_printf (includes_directories ? (includes_files ? ngettext ("%'u item", "%'u items", item_count) : ngettext ("%'u folder", "%'u folders", item_count)) : ngettext ("%'u file", "%'u files", item_count), item_count); } /** * nautilus_file_get_size_as_string: * * Get a user-displayable string representing a file size. The caller * is responsible for g_free-ing this string. The string is an item * count for directories. * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_size_as_string (NautilusFile *file) { guint item_count; gboolean count_unreadable; if (file == NULL) { return NULL; } g_assert (NAUTILUS_IS_FILE (file)); if (nautilus_file_is_directory (file)) { if (!nautilus_file_get_directory_item_count (file, &item_count, &count_unreadable)) { return NULL; } return format_item_count_for_display (item_count, TRUE, TRUE); } if (file->details->size == -1) { return NULL; } return g_format_size (file->details->size); } /** * nautilus_file_get_size_as_string_with_real_size: * * Get a user-displayable string representing a file size. The caller * is responsible for g_free-ing this string. The string is an item * count for directories. * This function adds the real size in the string. * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_size_as_string_with_real_size (NautilusFile *file) { guint item_count; gboolean count_unreadable; g_autofree char *size_str = NULL; if (file == NULL) { return NULL; } g_assert (NAUTILUS_IS_FILE (file)); if (nautilus_file_is_directory (file)) { if (!nautilus_file_get_directory_item_count (file, &item_count, &count_unreadable)) { return NULL; } return format_item_count_for_display (item_count, TRUE, TRUE); } if (file->details->size == -1) { return NULL; } size_str = g_strdup_printf ("%'" G_GOFFSET_FORMAT, file->details->size); return g_strdup_printf (ngettext ("%s byte", "%s bytes", file->details->size), size_str); } static char * nautilus_file_get_deep_count_as_string_internal (NautilusFile *file, gboolean report_size, gboolean report_directory_count, gboolean report_file_count) { NautilusRequestStatus status; guint directory_count; guint file_count; guint unreadable_count; guint total_count; goffset total_size; /* Must ask for size or some kind of count, but not both. */ g_assert (!report_size || (!report_directory_count && !report_file_count)); g_assert (report_size || report_directory_count || report_file_count); if (file == NULL) { return NULL; } g_assert (NAUTILUS_IS_FILE (file)); g_assert (nautilus_file_is_directory (file)); status = nautilus_file_get_deep_counts (file, &directory_count, &file_count, &unreadable_count, &total_size, FALSE); /* Check whether any info is available. */ if (status == NAUTILUS_REQUEST_NOT_STARTED) { return NULL; } total_count = file_count + directory_count; if (total_count == 0) { switch (status) { case NAUTILUS_REQUEST_IN_PROGRESS: { /* Don't return confident "zero" until we're finished looking, * because of next case. */ return NULL; } case NAUTILUS_REQUEST_DONE: { /* Don't return "zero" if we there were contents but we couldn't read them. */ if (unreadable_count != 0) { return NULL; } } default: {} break; } } /* Note that we don't distinguish the "everything was readable" case * from the "some things but not everything was readable" case here. * Callers can distinguish them using nautilus_file_get_deep_counts * directly if desired. */ if (report_size) { return g_format_size (total_size); } return format_item_count_for_display (report_directory_count ? (report_file_count ? total_count : directory_count) : file_count, report_directory_count, report_file_count); } /** * nautilus_file_get_deep_size_as_string: * * Get a user-displayable string representing the size of all contained * items (only makes sense for directories). The caller * is responsible for g_free-ing this string. * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_deep_size_as_string (NautilusFile *file) { return nautilus_file_get_deep_count_as_string_internal (file, TRUE, FALSE, FALSE); } /** * nautilus_file_get_deep_total_count_as_string: * * Get a user-displayable string representing the count of all contained * items (only makes sense for directories). The caller * is responsible for g_free-ing this string. * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_deep_total_count_as_string (NautilusFile *file) { return nautilus_file_get_deep_count_as_string_internal (file, FALSE, TRUE, TRUE); } /** * nautilus_file_get_deep_file_count_as_string: * * Get a user-displayable string representing the count of all contained * items, not including directories. It only makes sense to call this * function on a directory. The caller * is responsible for g_free-ing this string. * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_deep_file_count_as_string (NautilusFile *file) { return nautilus_file_get_deep_count_as_string_internal (file, FALSE, FALSE, TRUE); } /** * nautilus_file_get_deep_directory_count_as_string: * * Get a user-displayable string representing the count of all contained * directories. It only makes sense to call this * function on a directory. The caller * is responsible for g_free-ing this string. * @file: NautilusFile representing the file in question. * * Returns: Newly allocated string ready to display to the user. * **/ static char * nautilus_file_get_deep_directory_count_as_string (NautilusFile *file) { return nautilus_file_get_deep_count_as_string_internal (file, FALSE, TRUE, FALSE); } /** * nautilus_file_get_string_attribute: * * Get a user-displayable string from a named attribute. Use g_free to * free this string. If the value is unknown, returns NULL. You can call * nautilus_file_get_string_attribute_with_default if you want a non-NULL * default. * * @file: NautilusFile representing the file in question. * @attribute_name: The name of the desired attribute. The currently supported * set includes "name", "type", "detailed_type", "mime_type", "size", "deep_size", "deep_directory_count", * "deep_file_count", "deep_total_count", "date_modified", "date_accessed", "date_created", * "date_modified_full", "date_accessed_full", "date_created_full", * "owner", "group", "permissions", "octal_permissions", "uri", "where", * "link_target", "volume", "free_space", "selinux_context", "trashed_on", "trashed_on_full", "trashed_orig_path", * "recency" * * Returns: Newly allocated string ready to display to the user, or NULL * if the value is unknown or @attribute_name is not supported. * **/ char * nautilus_file_get_string_attribute_q (NautilusFile *file, GQuark attribute_q) { if (attribute_q == attribute_name_q) { return g_strdup (nautilus_file_get_display_name (file)); } if (attribute_q == attribute_type_q) { return nautilus_file_get_type_as_string (file); } if (attribute_q == attribute_detailed_type_q) { return nautilus_file_get_detailed_type_as_string (file); } if (attribute_q == attribute_mime_type_q) { return g_strdup (nautilus_file_get_mime_type (file)); } if (attribute_q == attribute_size_q) { return nautilus_file_get_size_as_string (file); } if (attribute_q == attribute_size_detail_q) { return nautilus_file_get_size_as_string_with_real_size (file); } if (attribute_q == attribute_deep_size_q) { return nautilus_file_get_deep_size_as_string (file); } if (attribute_q == attribute_deep_file_count_q) { return nautilus_file_get_deep_file_count_as_string (file); } if (attribute_q == attribute_deep_directory_count_q) { return nautilus_file_get_deep_directory_count_as_string (file); } if (attribute_q == attribute_deep_total_count_q) { return nautilus_file_get_deep_total_count_as_string (file); } if (attribute_q == attribute_trash_orig_path_q) { return nautilus_file_get_trash_original_file_parent_as_string (file); } if (attribute_q == attribute_date_modified_q) { return nautilus_file_get_date_as_string (file, NAUTILUS_DATE_TYPE_MODIFIED, NAUTILUS_DATE_FORMAT_REGULAR); } if (attribute_q == attribute_date_modified_full_q) { return nautilus_file_get_date_as_string (file, NAUTILUS_DATE_TYPE_MODIFIED, NAUTILUS_DATE_FORMAT_FULL); } if (attribute_q == attribute_date_accessed_q) { return nautilus_file_get_date_as_string (file, NAUTILUS_DATE_TYPE_ACCESSED, NAUTILUS_DATE_FORMAT_REGULAR); } if (attribute_q == attribute_date_accessed_full_q) { return nautilus_file_get_date_as_string (file, NAUTILUS_DATE_TYPE_ACCESSED, NAUTILUS_DATE_FORMAT_FULL); } if (attribute_q == attribute_date_created_q) { return nautilus_file_get_date_as_string (file, NAUTILUS_DATE_TYPE_CREATED, NAUTILUS_DATE_FORMAT_REGULAR); } if (attribute_q == attribute_date_created_full_q) { return nautilus_file_get_date_as_string (file, NAUTILUS_DATE_TYPE_CREATED, NAUTILUS_DATE_FORMAT_FULL); } if (attribute_q == attribute_trashed_on_q) { return nautilus_file_get_date_as_string (file, NAUTILUS_DATE_TYPE_TRASHED, NAUTILUS_DATE_FORMAT_REGULAR); } if (attribute_q == attribute_trashed_on_full_q) { return nautilus_file_get_date_as_string (file, NAUTILUS_DATE_TYPE_TRASHED, NAUTILUS_DATE_FORMAT_FULL); } if (attribute_q == attribute_recency_q) { return nautilus_file_get_date_as_string (file, NAUTILUS_DATE_TYPE_RECENCY, NAUTILUS_DATE_FORMAT_REGULAR); } if (attribute_q == attribute_permissions_q) { return nautilus_file_get_permissions_as_string (file); } if (attribute_q == attribute_selinux_context_q) { return nautilus_file_get_selinux_context (file); } if (attribute_q == attribute_octal_permissions_q) { return nautilus_file_get_octal_permissions_as_string (file); } if (attribute_q == attribute_owner_q) { return nautilus_file_get_owner_as_string (file, TRUE); } if (attribute_q == attribute_group_q) { return g_strdup (nautilus_file_get_group_name (file)); } if (attribute_q == attribute_uri_q) { return nautilus_file_get_uri (file); } if (attribute_q == attribute_where_q) { return nautilus_file_get_where_string (file); } if (attribute_q == attribute_link_target_q) { return g_strdup (nautilus_file_get_symbolic_link_target_path (file)); } if (attribute_q == attribute_volume_q) { return nautilus_file_get_volume_name (file); } if (attribute_q == attribute_free_space_q) { return nautilus_file_get_volume_free_space (file); } if (file->details->extension_attributes != NULL) { return g_strdup (g_hash_table_lookup (file->details->extension_attributes, GINT_TO_POINTER (attribute_q))); } return NULL; } char * nautilus_file_get_string_attribute (NautilusFile *file, const char *attribute_name) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); return nautilus_file_get_string_attribute_q (file, g_quark_from_string (attribute_name)); } /** * nautilus_file_get_string_attribute_with_default: * * Get a user-displayable string from a named attribute. Use g_free to * free this string. If the value is unknown, returns a string representing * the unknown value, which varies with attribute. You can call * nautilus_file_get_string_attribute if you want NULL instead of a default * result. * * @file: NautilusFile representing the file in question. * @attribute_name: The name of the desired attribute. See the description of * nautilus_file_get_string for the set of available attributes. * * Returns: Newly allocated string ready to display to the user, or a string * such as "unknown" if the value is unknown or @attribute_name is not supported. * **/ char * nautilus_file_get_string_attribute_with_default_q (NautilusFile *file, GQuark attribute_q) { char *result; guint item_count; gboolean count_unreadable; NautilusRequestStatus status; result = nautilus_file_get_string_attribute_q (file, attribute_q); if (result != NULL) { return result; } /* Supply default values for the ones we know about. */ /* FIXME bugzilla.gnome.org 40646: * Use hash table and switch statement or function pointers for speed? */ if (attribute_q == attribute_size_q) { if (!nautilus_file_should_show_directory_item_count (file)) { return g_strdup ("—"); } count_unreadable = FALSE; if (nautilus_file_is_directory (file)) { nautilus_file_get_directory_item_count (file, &item_count, &count_unreadable); } return g_strdup (count_unreadable ? "—" : "…"); } if (attribute_q == attribute_deep_size_q) { status = nautilus_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE); if (status == NAUTILUS_REQUEST_DONE) { /* This means no contents at all were readable */ return g_strdup (_("? bytes")); } return g_strdup ("…"); } if (attribute_q == attribute_deep_file_count_q || attribute_q == attribute_deep_directory_count_q || attribute_q == attribute_deep_total_count_q) { status = nautilus_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE); if (status == NAUTILUS_REQUEST_DONE) { /* This means no contents at all were readable */ return g_strdup (_("? items")); } return g_strdup ("…"); } if (attribute_q == attribute_type_q || attribute_q == attribute_detailed_type_q || attribute_q == attribute_mime_type_q) { /* Translators: This about a file type. */ return g_strdup (_("Unknown type")); } if (attribute_q == attribute_trashed_on_q) { /* If n/a */ return g_strdup (""); } if (attribute_q == attribute_trash_orig_path_q) { /* If n/a */ return g_strdup ("—"); } if (attribute_q == attribute_recency_q) { /* If n/a */ return g_strdup (""); } if (attribute_q == attribute_starred_q) { /* If n/a */ return g_strdup (""); } if (attribute_q == attribute_date_created_full_q) { /* If n/a */ return g_strdup ("—"); } /* Fallback, use for both unknown attributes and attributes * for which we have no more appropriate default. */ return g_strdup (_("Unknown")); } char * nautilus_file_get_string_attribute_with_default (NautilusFile *file, const char *attribute_name) { return nautilus_file_get_string_attribute_with_default_q (file, g_quark_from_string (attribute_name)); } gboolean nautilus_file_is_date_sort_attribute_q (GQuark attribute_q) { if (attribute_q == attribute_modification_date_q || attribute_q == attribute_date_modified_q || attribute_q == attribute_date_modified_full_q || attribute_q == attribute_accessed_date_q || attribute_q == attribute_date_accessed_q || attribute_q == attribute_date_accessed_full_q || attribute_q == attribute_date_created_q || attribute_q == attribute_date_created_full_q || attribute_q == attribute_trashed_on_q || attribute_q == attribute_trashed_on_full_q || attribute_q == attribute_recency_q) { return TRUE; } return FALSE; } gboolean nautilus_file_attribute_slow_sort (const gchar *sort_attribute) { GQuark attribute_q = g_quark_from_string (sort_attribute); return attribute_q == attribute_size_q || attribute_q == attribute_size_detail_q || attribute_q == attribute_deep_size_q || attribute_q == attribute_deep_file_count_q || attribute_q == attribute_deep_directory_count_q || attribute_q == attribute_deep_total_count_q; } struct { const char *icon_name; const char *display_name; } mime_type_map[] = { { "application-x-executable", N_("Program") }, { "audio-x-generic", N_("Audio") }, { "font-x-generic", N_("Font") }, { "image-x-generic", N_("Image") }, { "package-x-generic", N_("Archive") }, { "text-html", N_("Markup") }, { "text-x-generic", N_("Text") }, { "text-x-generic-template", N_("Text") }, { "text-x-script", N_("Program") }, { "video-x-generic", N_("Video") }, { "x-office-address-book", N_("Contacts") }, { "x-office-calendar", N_("Calendar") }, { "x-office-document", N_("Document") }, { "x-office-presentation", N_("Presentation") }, { "x-office-spreadsheet", N_("Spreadsheet") }, }; /** Only returns NULL, if mime_type was NULL. */ static const char * get_basic_type_for_mime_type (const char *mime_type) { g_autofree char *icon_name = NULL; if (mime_type == NULL) { return NULL; } icon_name = g_content_type_get_generic_icon_name (mime_type); if (icon_name != NULL) { for (guint i = 0; i < G_N_ELEMENTS (mime_type_map); i++) { if (strcmp (mime_type_map[i].icon_name, icon_name) == 0) { const char *basic_type = gettext (mime_type_map[i].display_name); if (basic_type != NULL) { return basic_type; } break; } } } /* Refers to a file type which is known but not one of the basic types */ return _("Other"); } static const char * get_common_description (NautilusFile *file) { const char *mime_type; g_assert (NAUTILUS_IS_FILE (file)); mime_type = file->details->mime_type; if (mime_type == NULL) { return NULL; } if (g_content_type_is_unknown (mime_type)) { if (nautilus_file_is_executable (file)) { return _("Program"); } return _("Binary"); } if (strcmp (mime_type, "inode/directory") == 0) { return _("Folder"); } /* No common type */ return NULL; } static const char * get_description (NautilusFile *file) { const char *common_description = get_common_description (file); if (common_description != NULL) { return common_description; } else { const char *mime_type = file->details->mime_type; return get_basic_type_for_mime_type (mime_type); } } static char * get_detailed_description (NautilusFile *file) { char *description; const char *mime_type = file->details->mime_type; const char *common_description = get_common_description (file); if (common_description != NULL) { return g_strdup (common_description); } if (mime_type == NULL) { return NULL; } description = g_content_type_get_description (mime_type); if (description != NULL) { return description; } return g_strdup (mime_type); } static char * update_description_for_link (NautilusFile *file, const char *string) { if (nautilus_file_is_symbolic_link (file)) { g_assert (!nautilus_file_is_broken_symbolic_link (file)); if (string == NULL) { return g_strdup (_("Link")); } /* Note to localizers: convert file type string for file * (e.g. "folder", "plain text") to file type for symbolic link * to that kind of file (e.g. "link to folder"). */ return g_strdup_printf (_("Link to %s"), string); } return g_strdup (string); } static char * nautilus_file_get_type_as_string (NautilusFile *file) { if (file == NULL) { return NULL; } if (nautilus_file_is_broken_symbolic_link (file)) { return g_strdup (_("Link (broken)")); } return update_description_for_link (file, get_description (file)); } static const char * nautilus_file_get_type_as_string_no_extra_text (NautilusFile *file) { if (file == NULL) { return NULL; } if (nautilus_file_is_broken_symbolic_link (file)) { return _("Link (broken)"); } return get_description (file); } static char * nautilus_file_get_detailed_type_as_string (NautilusFile *file) { if (file == NULL) { return NULL; } if (nautilus_file_is_broken_symbolic_link (file)) { return g_strdup (_("Link (broken)")); } g_autofree char *detailed_description = get_detailed_description (file); return update_description_for_link (file, detailed_description); } gboolean nautilus_file_is_launchable (NautilusFile *file) { gboolean type_can_be_executable; type_can_be_executable = FALSE; if (file->details->mime_type != NULL) { type_can_be_executable = g_content_type_can_be_executable (file->details->mime_type); } return type_can_be_executable && nautilus_file_can_get_permissions (file) && nautilus_file_can_execute (file) && nautilus_file_is_executable (file) && nautilus_file_is_regular_file (file); } /** * nautilus_file_is_symbolic_link * * Check if this file is a symbolic link. * @file: NautilusFile representing the file in question. * * Returns: True if the file is a symbolic link. * **/ gboolean nautilus_file_is_symbolic_link (NautilusFile *file) { return file->details->is_symlink; } static void file_mount_unmounted (GMount *mount, gpointer data) { NautilusFile *file; file = NAUTILUS_FILE (data); nautilus_file_invalidate_attributes (file, NAUTILUS_ATTRIBUTE_MOUNT); } void nautilus_file_set_mount (NautilusFile *file, GMount *mount) { if (file->details->mount) { g_signal_handlers_disconnect_by_func (file->details->mount, file_mount_unmounted, file); g_object_unref (file->details->mount); file->details->mount = NULL; } if (mount) { file->details->mount = g_object_ref (mount); g_signal_connect_object (mount, "unmounted", G_CALLBACK (file_mount_unmounted), file, G_CONNECT_DEFAULT); } } /** * nautilus_file_is_broken_symbolic_link * * Check if this file is a symbolic link with a missing target. * @file: NautilusFile representing the file in question. * * Returns: True if the file is a symbolic link with a missing target. * **/ gboolean nautilus_file_is_broken_symbolic_link (NautilusFile *file) { if (file == NULL) { return FALSE; } g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); /* Non-broken symbolic links return the target's type for get_file_type. */ return nautilus_file_get_file_type (file) == G_FILE_TYPE_SYMBOLIC_LINK; } static void get_fs_free_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { NautilusFile *file; guint64 free_space; GFileInfo *info; file = NAUTILUS_FILE (user_data); free_space = (guint64) - 1; info = g_file_query_filesystem_info_finish (G_FILE (source_object), res, NULL); if (info) { if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE)) { free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); } g_object_unref (info); } if (file->details->free_space != free_space) { file->details->free_space = free_space; nautilus_file_emit_changed (file); } nautilus_file_unref (file); } /** * nautilus_file_get_volume_free_space * Get a nicely formatted char with free space on the file's volume * @file: NautilusFile representing the file in question. * * Returns: newly-allocated copy of file size in a formatted string */ char * nautilus_file_get_volume_free_space (NautilusFile *file) { GFile *location; char *res; time_t now; now = time (NULL); /* Update first time and then every 2 seconds */ if (file->details->free_space_read == 0 || (now - file->details->free_space_read) > 2) { file->details->free_space_read = now; location = nautilus_file_get_location (file); g_file_query_filesystem_info_async (location, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 0, NULL, get_fs_free_cb, nautilus_file_ref (file)); g_object_unref (location); } res = NULL; if (file->details->free_space != (guint64) - 1) { g_autofree gchar *size_string = g_format_size (file->details->free_space); /* Translators: This refers to available space in a folder; e.g.: 100 MB Free */ res = g_strdup_printf (_("%s Free"), size_string); } return res; } /** * nautilus_file_get_volume_name * Get the name of the volume the file resides on * @file: NautilusFile representing the file in question. * * Returns: newly-allocated copy of the volume name of the target file. */ char * nautilus_file_get_volume_name (NautilusFile *file) { g_autoptr (GFile) location = nautilus_file_get_location (file); g_autoptr (GMount) mount = g_file_find_enclosing_mount (location, NULL, NULL); return mount != NULL ? g_mount_get_name (mount) : NULL; } /** * nautilus_file_get_symbolic_link_target_path * * Get the file path of the target of a symbolic link. It is an error * to call this function on a file that isn't a symbolic link. * @file: NautilusFile representing the symbolic link in question. * * Returns: the file path of the target of the symbolic link. */ const char * nautilus_file_get_symbolic_link_target_path (NautilusFile *file) { if (!nautilus_file_is_symbolic_link (file)) { g_warning ("File has symlink target, but is not marked as symlink"); } return file->details->symlink_name; } /** * nautilus_file_get_symbolic_link_target_uri * * Get the uri of the target of a symbolic link. It is an error * to call this function on a file that isn't a symbolic link. * @file: NautilusFile representing the symbolic link in question. * * Returns: newly-allocated copy of the uri of the target of the symbolic link. */ char * nautilus_file_get_symbolic_link_target_uri (NautilusFile *file) { GFile *location, *parent, *target; char *target_uri; if (!nautilus_file_is_symbolic_link (file)) { g_warning ("File has symlink target, but is not marked as symlink"); } if (file->details->symlink_name == NULL) { return NULL; } else { target = NULL; location = nautilus_file_get_location (file); parent = g_file_get_parent (location); g_object_unref (location); if (parent) { target = g_file_resolve_relative_path (parent, file->details->symlink_name); g_object_unref (parent); } target_uri = NULL; if (target) { target_uri = g_file_get_uri (target); g_object_unref (target); } return target_uri; } } /** * nautilus_file_is_regular_file * * Check if this file is a regular file. * @file: NautilusFile representing the file in question. * * Returns: TRUE if @file is a regular file. * **/ gboolean nautilus_file_is_regular_file (NautilusFile *file) { return nautilus_file_get_file_type (file) == G_FILE_TYPE_REGULAR; } gboolean nautilus_file_is_public_share_folder (NautilusFile *file) { if (nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_PUBLIC_SHARE)) { return TRUE; } if (g_strcmp0 (g_get_home_dir (), g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE)) != 0) { /* In order to match the behavior of gnome-user-share the ~/Public folder * is considered to be the public sharing folder when XDG_PUBLICSHARE_DIR * is set to the home folder. */ g_autoptr (GFile) public_folder = g_file_new_build_filename (g_get_home_dir (), "Public", NULL); g_autoptr (GFile) location = nautilus_file_get_location (file); return g_file_equal (public_folder, location); } return FALSE; } /** * nautilus_file_is_user_special_directory * * Check if this file is a special platform directory. * @file: NautilusFile representing the file in question. * @special_directory: GUserDirectory representing the type to test for * * Returns: TRUE if @file is a special directory of the given kind. */ gboolean nautilus_file_is_user_special_directory (NautilusFile *file, GUserDirectory special_directory) { gboolean is_special_dir; const gchar *special_dir; if (nautilus_file_is_home (file)) { /* A xdg-user-dir is disabled by setting it to the home directory */ return FALSE; } special_dir = g_get_user_special_dir (special_directory); is_special_dir = FALSE; if (special_dir) { GFile *loc; GFile *special_gfile; loc = nautilus_file_get_location (file); special_gfile = g_file_new_for_path (special_dir); is_special_dir = g_file_equal (loc, special_gfile); g_object_unref (special_gfile); g_object_unref (loc); } return is_special_dir; } gboolean nautilus_file_is_archive (NautilusFile *file) { const char *mime_type = nautilus_file_get_mime_type (file); return autoar_check_mime_type_supported (mime_type); } /** * nautilus_file_is_in_trash * * Check if this file is a file in trash. * @file: NautilusFile representing the file in question. * * Returns: TRUE if @file is in a trash. * **/ gboolean nautilus_file_is_in_trash (NautilusFile *file) { g_assert (NAUTILUS_IS_FILE (file)); return nautilus_directory_is_in_trash (file->details->directory); } /** * nautilus_file_is_in_recent * * Check if this file is a file in Recent. * @file: NautilusFile representing the file in question. * * Returns: TRUE if @file is in Recent. * **/ gboolean nautilus_file_is_in_recent (NautilusFile *file) { g_assert (NAUTILUS_IS_FILE (file)); return nautilus_directory_is_in_recent (file->details->directory); } /** * nautilus_file_is_in_starred * * Check if this file is a file in Starred. * @file: NautilusFile representing the file in question. * * Returns: TRUE if @file is in Starred. * **/ gboolean nautilus_file_is_in_starred (NautilusFile *file) { g_assert (NAUTILUS_IS_FILE (file)); return nautilus_directory_is_in_starred (file->details->directory); } /** * nautilus_file_is_remote * * Check if this file is a file in a remote filesystem. * @file: NautilusFile representing the file in question. * * Returns: TRUE if @file is in a remote filesystem. * **/ gboolean nautilus_file_is_remote (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return get_filesystem_remote (file, NULL); } /** * nautilus_file_is_starred_location * * Check if this file is the Favorite location. * @file: NautilusFile representing the file in question. * * Returns: TRUE if @file is the Favorite location. * **/ gboolean nautilus_file_is_starred_location (NautilusFile *file) { g_autoptr (GFile) location = NULL; g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); location = nautilus_file_get_location (file); return g_file_has_uri_scheme (location, SCHEME_STARRED); } gboolean nautilus_file_is_network_view (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); g_autoptr (GFile) location = nautilus_file_get_location (file); return nautilus_is_root_for_scheme (location, SCHEME_NETWORK_VIEW); } /** * nautilus_file_is_in_admin * * Check if this file is using admin backend. * @file: NautilusFile representing the file in question. * * Returns: TRUE if @file is using admin backend. * **/ gboolean nautilus_file_is_in_admin (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return nautilus_directory_is_in_admin (file->details->directory); } GError * nautilus_file_get_file_info_error (NautilusFile *file) { if (!file->details->get_info_failed) { return NULL; } return file->details->get_info_error; } /** * nautilus_file_contains_text * * Check if this file contains text. * This is private and is used to decide whether or not to read the top left text. * @file: NautilusFile representing the file in question. * * Returns: TRUE if @file has a text MIME type. * **/ gboolean nautilus_file_contains_text (NautilusFile *file) { if (file == NULL) { return FALSE; } /* All text files inherit from text/plain */ return nautilus_file_is_mime_type (file, "text/plain"); } /** * nautilus_file_is_executable * * Check if this file is executable at all. * @file: NautilusFile representing the file in question. * * Returns: TRUE if any of the execute bits are set. FALSE if * not, or if the permissions are unknown. * **/ gboolean nautilus_file_is_executable (NautilusFile *file) { if (!file->details->has_permissions) { /* File's permissions field is not valid. * Can't access specific permissions, so return FALSE. */ return FALSE; } return file->details->can_execute; } const char * nautilus_file_get_filesystem_id (NautilusFile *file) { return file->details->filesystem_id; } NautilusFile * nautilus_file_get_trash_original_file (NautilusFile *file) { GFile *location; NautilusFile *original_file; original_file = NULL; if (file->details->trash_orig_path != NULL) { location = g_file_new_for_path (file->details->trash_orig_path); original_file = nautilus_file_get (location); g_object_unref (location); } return original_file; } void nautilus_file_mark_unmounted (NautilusFile *file) { file->details->has_been_unmounted = TRUE; } void nautilus_file_mark_gone (NautilusFile *file) { NautilusDirectory *directory; if (file->details->is_gone) { return; } file->details->is_gone = TRUE; update_links_if_target (file); /* Drop it from the symlink hash ! */ remove_from_link_hash_table (file); /* Removing the file from the directory can result in dropping the last * reference, and so clearing the info then will result in a crash. */ nautilus_file_clear_info (file); /* Let the directory know it's gone. */ directory = file->details->directory; if (!nautilus_file_is_self_owned (file)) { nautilus_directory_remove_file (directory, file); } /* FIXME bugzilla.gnome.org 42429: * Maybe we can get rid of the name too eventually, but * for now that would probably require too many if statements * everywhere anyone deals with the name. Maybe we can give it * a hard-coded "" name or something. */ } /** * nautilus_file_changed * * Notify the user that this file has changed. * @file: NautilusFile representing the file in question. **/ void nautilus_file_changed (NautilusFile *file) { g_return_if_fail (NAUTILUS_IS_FILE (file)); if (nautilus_file_is_self_owned (file)) { nautilus_file_emit_changed (file); } else { nautilus_directory_emit_change_signals (file->details->directory, &(NautilusFileList){ .data = file }); } } /** * nautilus_file_updated_deep_count_in_progress * * Notify clients that a newer deep count is available for * the directory in question. */ void nautilus_file_updated_deep_count_in_progress (NautilusFile *file) { GList *link_files, *node; g_assert (NAUTILUS_IS_FILE (file)); g_assert (nautilus_file_is_directory (file)); /* Send out a signal. */ g_signal_emit (file, signals[UPDATED_DEEP_COUNT_IN_PROGRESS], 0, file); /* Tell link files pointing to this object about the change. */ link_files = get_link_files (file); for (node = link_files; node != NULL; node = node->next) { nautilus_file_updated_deep_count_in_progress (NAUTILUS_FILE (node->data)); } nautilus_file_list_free (link_files); } /** * nautilus_file_emit_changed * * Emit a file changed signal. * This can only be called by the directory, since the directory * also has to emit a files_changed signal. * * @file: NautilusFile representing the file in question. **/ void nautilus_file_emit_changed (NautilusFile *file) { GList *link_files, *p; g_assert (NAUTILUS_IS_FILE (file)); /* Send out a signal. */ g_signal_emit (file, signals[CHANGED], 0, file); /* Tell link files pointing to this object about the change. */ link_files = get_link_files (file); for (p = link_files; p != NULL; p = p->next) { /* Looking for directly recursive links. */ g_autolist (NautilusFile) link_targets = NULL; NautilusDirectory *directory; /* Files can be links to themselves. */ if (p->data == file) { continue; } link_targets = get_link_files (p->data); directory = nautilus_file_get_directory (p->data); /* Reiterating (heh) that this will break with more complex cycles. * Users, stop trying to break things on purpose. */ if (g_list_find (link_targets, file) != NULL && directory == nautilus_file_get_directory (file)) { g_signal_emit (p->data, signals[CHANGED], 0, p->data); continue; } nautilus_file_changed (NAUTILUS_FILE (p->data)); } nautilus_file_list_free (link_files); } gboolean nautilus_file_has_been_unmounted (NautilusFile *file) { return file->details->has_been_unmounted; } /** * nautilus_file_check_if_ready * * Check whether the values for a set of file attributes are * currently available, without doing any additional work. This * is useful for callers that want to reflect updated information * when it is ready but don't want to force the work required to * obtain the information, which might be slow network calls, e.g. * * @file: The file being queried. * @attributes: A bit-mask with the desired information. * * Return value: TRUE if all of the specified attributes are currently readable. */ gboolean nautilus_file_check_if_ready (NautilusFile *file, NautilusAttributes attributes) { /* To be parallel with call_when_ready, return * TRUE for NULL file. */ if (file == NULL) { return TRUE; } g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->check_if_ready (file, attributes); } void nautilus_file_call_when_ready (NautilusFile *file, NautilusAttributes attributes, NautilusFileCallback callback, gpointer callback_data) { if (file == NULL) { (*callback)(file, callback_data); return; } g_return_if_fail (NAUTILUS_IS_FILE (file)); NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->call_when_ready (file, attributes, callback, callback_data); } void nautilus_file_cancel_call_when_ready (NautilusFile *file, NautilusFileCallback callback, gpointer callback_data) { g_return_if_fail (callback != NULL); if (file == NULL) { return; } g_return_if_fail (NAUTILUS_IS_FILE (file)); NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file))->cancel_call_when_ready (file, callback, callback_data); } static void invalidate_directory_count (NautilusFile *file) { file->details->directory_count_is_up_to_date = FALSE; } static void invalidate_deep_counts (NautilusFile *file) { file->details->deep_counts_status = NAUTILUS_REQUEST_NOT_STARTED; } static void invalidate_file_info (NautilusFile *file) { file->details->file_info_is_up_to_date = FALSE; } static void invalidate_thumbnail_info (NautilusFile *file) { file->details->thumbnail_info_is_up_to_date = FALSE; } static void invalidate_thumbnail (NautilusFile *file) { file->details->thumbnail_is_up_to_date = FALSE; } static void invalidate_mount (NautilusFile *file) { file->details->mount_is_up_to_date = FALSE; } void nautilus_file_invalidate_extension_info_internal (NautilusFile *file) { g_list_free_full (file->details->extension_emblems, g_free); file->details->extension_emblems = NULL; g_clear_pointer (&file->details->extension_attributes, g_hash_table_destroy); g_list_free_full (file->details->pending_info_providers, g_object_unref); file->details->pending_info_providers = nautilus_module_get_extensions_for_type (NAUTILUS_TYPE_INFO_PROVIDER); } void nautilus_file_invalidate_attributes_internal (NautilusFile *file, NautilusAttributes attributes) { if (file == NULL) { return; } if (IS_ATTRIBUTE_SET (attributes, NAUTILUS_ATTRIBUTE_DIRECTORY_ITEM_COUNT)) { invalidate_directory_count (file); } if (IS_ATTRIBUTE_SET (attributes, NAUTILUS_ATTRIBUTE_DEEP_COUNT)) { invalidate_deep_counts (file); } if (IS_ATTRIBUTE_SET (attributes, NAUTILUS_ATTRIBUTE_INFO)) { invalidate_file_info (file); } if (IS_ATTRIBUTE_SET (attributes, NAUTILUS_ATTRIBUTE_EXTENSION_INFO)) { nautilus_file_invalidate_extension_info_internal (file); } if (IS_ATTRIBUTE_SET (attributes, NAUTILUS_ATTRIBUTE_THUMBNAIL_INFO)) { invalidate_thumbnail_info (file); } if (IS_ATTRIBUTE_SET (attributes, NAUTILUS_ATTRIBUTE_THUMBNAIL_BUFFER)) { invalidate_thumbnail (file); } if (IS_ATTRIBUTE_SET (attributes, NAUTILUS_ATTRIBUTE_MOUNT)) { invalidate_mount (file); } /* FIXME bugzilla.gnome.org 45075: implement invalidating metadata */ } gboolean nautilus_file_set_thumbnail (NautilusFile *file, GdkPixbuf *pixbuf) { const char *thumb_mtime_str; time_t thumb_mtime = 0; g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); file->details->thumbnail_is_up_to_date = TRUE; g_clear_object (&file->details->thumbnail); if (pixbuf != NULL) { thumb_mtime_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::MTime"); if (thumb_mtime_str) { thumb_mtime = atol (thumb_mtime_str); } if (thumb_mtime == 0 || thumb_mtime == file->details->mtime) { file->details->thumbnail = gdk_texture_new_for_pixbuf (pixbuf); file->details->thumbnail_mtime = thumb_mtime; if (file->details->thumbnail_path == NULL) { g_autofree gchar *uri = nautilus_file_get_uri (file); file->details->thumbnail_path = nautilus_thumbnail_get_path_for_uri (uri); } } else { return FALSE; } } return TRUE; } /** * nautilus_file_invalidate_attributes * * Invalidate the specified attributes and force a reload. * @file: NautilusFile representing the file in question. * @attributes: attributes to froget. **/ void nautilus_file_invalidate_attributes (NautilusFile *file, NautilusAttributes attributes) { /* Cancel possible in-progress loads of any of these attributes */ nautilus_directory_cancel_loading_attributes (file->details->directory, file, attributes); /* Actually invalidate the values */ nautilus_file_invalidate_attributes_internal (file, attributes); nautilus_directory_add_file_to_work_queue (file->details->directory, file); /* Kick off I/O if necessary */ nautilus_directory_async_state_changed (file->details->directory); } NautilusAttributes nautilus_file_get_all_attributes (void) { return NAUTILUS_ATTRIBUTE_INFO | NAUTILUS_ATTRIBUTE_DEEP_COUNT | NAUTILUS_ATTRIBUTE_DIRECTORY_ITEM_COUNT | NAUTILUS_ATTRIBUTE_EXTENSION_INFO | NAUTILUS_ATTRIBUTE_THUMBNAIL_INFO | NAUTILUS_ATTRIBUTE_THUMBNAIL_BUFFER | NAUTILUS_ATTRIBUTE_MOUNT; } void nautilus_file_invalidate_all_attributes (NautilusFile *file) { NautilusAttributes all_attributes; all_attributes = nautilus_file_get_all_attributes (); nautilus_file_invalidate_attributes (file, all_attributes); } /** * nautilus_file_dump * * Debugging call, prints out the contents of the file * fields. * * @file: file to dump. **/ void nautilus_file_dump (NautilusFile *file) { long size = file->details->deep_size; char *uri; const char *file_kind; uri = nautilus_file_get_uri (file); g_print ("uri: %s \n", uri); if (!file->details->got_file_info) { g_print ("no file info \n"); } else if (file->details->get_info_failed) { g_print ("failed to get file info \n"); } else { g_print ("size: %ld \n", size); switch (file->details->type) { case G_FILE_TYPE_REGULAR: { file_kind = "regular file"; } break; case G_FILE_TYPE_DIRECTORY: { file_kind = "folder"; } break; case G_FILE_TYPE_SPECIAL: { file_kind = "special"; } break; case G_FILE_TYPE_SYMBOLIC_LINK: { file_kind = "symbolic link"; } break; case G_FILE_TYPE_UNKNOWN: default: { file_kind = "unknown"; } break; } g_print ("kind: %s \n", file_kind); if (file->details->type == G_FILE_TYPE_SYMBOLIC_LINK) { g_print ("link to %s \n", file->details->symlink_name); /* FIXME bugzilla.gnome.org 42430: add following of symlinks here */ } /* FIXME bugzilla.gnome.org 42431: add permissions and other useful stuff here */ } g_free (uri); } void nautilus_file_list_debug (GList *files) { if (g_getenv ("G_MESSAGES_DEBUG") == NULL) { return; } for (GList *l = files; l != NULL; l = l->next) { NautilusFile *file = l->data; g_autofree gchar *uri = nautilus_file_get_uri (file); g_debug ("%s%s", uri, nautilus_file_is_gone (file) ? " (gone)" : ""); } } /** * nautilus_file_list_free * * Free a list of files after unrefing them. * @list: GList of files. **/ void nautilus_file_list_free (GList *list) { g_list_free_full (list, (GDestroyNotify) nautilus_file_unref); } /** * nautilus_file_list_copy * * Copy the list of files, making a new ref of each, * @list: GList of files. **/ GList * nautilus_file_list_copy (GList *list) { return g_list_copy_deep (list, (GCopyFunc) nautilus_file_ref, NULL); } /** * nautilus_file_get_default_sort_type: * @file: A #NautilusFile representing a location * @reversed: (out): Location to store whether the order is reversed by default. * * Gets which sort order applies by default for the provided locations. * * If @file is a location with special needs (e.g. Trash or Recent), the return * value and @reversed flag are dictated by design. Otherwise, they stem from * the "default-sort-order" and "default-sort-in-reverse-order" preference keys. * * Returns: The default #NautilusFileSortType for this @file. */ NautilusFileSortType nautilus_file_get_default_sort_type (NautilusFile *file, gboolean *reversed) { g_assert (reversed != NULL); /* Special handling for certain directories */ if (nautilus_file_is_user_special_directory (file, G_USER_DIRECTORY_DOWNLOAD)) { *reversed = TRUE; return NAUTILUS_FILE_SORT_BY_MTIME; } else if (nautilus_file_is_in_trash (file)) { *reversed = TRUE; return NAUTILUS_FILE_SORT_BY_TRASHED_TIME; } else if (nautilus_file_is_in_recent (file)) { *reversed = TRUE; return NAUTILUS_FILE_SORT_BY_RECENCY; } else if (nautilus_file_is_in_search (file)) { *reversed = TRUE; return NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE; } /* Use defaults */ *reversed = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_DEFAULT_SORT_IN_REVERSE_ORDER); return g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_DEFAULT_SORT_ORDER); } const char * nautilus_file_sort_type_get_attribute (NautilusFileSortType sort_type) { GQuark sort_q = 0; switch (sort_type) { case NAUTILUS_FILE_SORT_BY_DISPLAY_NAME: { sort_q = attribute_name_q; } break; case NAUTILUS_FILE_SORT_BY_SIZE: { sort_q = attribute_size_q; } break; case NAUTILUS_FILE_SORT_BY_TYPE: { sort_q = attribute_type_q; } break; case NAUTILUS_FILE_SORT_BY_MTIME: { sort_q = attribute_date_modified_q; } break; case NAUTILUS_FILE_SORT_BY_ATIME: { sort_q = attribute_date_accessed_q; } break; case NAUTILUS_FILE_SORT_BY_BTIME: { sort_q = attribute_date_created_q; } break; case NAUTILUS_FILE_SORT_BY_TRASHED_TIME: { sort_q = attribute_trashed_on_q; } break; case NAUTILUS_FILE_SORT_BY_SEARCH_RELEVANCE: { sort_q = attribute_search_relevance_q; } break; case NAUTILUS_FILE_SORT_BY_RECENCY: { sort_q = attribute_recency_q; } break; case NAUTILUS_FILE_SORT_BY_STARRED: { sort_q = attribute_starred_q; } break; default: { g_assert_not_reached (); } break; } g_assert (sort_q != 0); return g_quark_to_string (sort_q); } static int compare_by_display_name_cover (gconstpointer a, gconstpointer b) { return compare_by_display_name (NAUTILUS_FILE (a), NAUTILUS_FILE (b)); } /** * nautilus_file_list_sort_by_display_name * * Sort the list of files by file name. * @list: GList of files. **/ GList * nautilus_file_list_sort_by_display_name (GList *list) { return g_list_sort (list, compare_by_display_name_cover); } static GList *ready_data_list = NULL; typedef struct { GList *file_list; GHashTable *remaining_files; gboolean all_checked; NautilusFileListCallback callback; gpointer callback_data; } FileListReadyData; static void file_list_ready_data_free (FileListReadyData *data) { GList *node = g_list_find (ready_data_list, data); g_return_if_fail (node != NULL); ready_data_list = g_list_delete_link (ready_data_list, node); nautilus_file_list_free (data->file_list); g_hash_table_unref (data->remaining_files); g_free (data); } static FileListReadyData * file_list_ready_data_new (GList *file_list, NautilusFileListCallback callback, gpointer callback_data) { FileListReadyData *data; data = g_new0 (FileListReadyData, 1); data->file_list = nautilus_file_list_copy (file_list); data->remaining_files = g_hash_table_new (NULL, NULL); data->callback = callback; data->callback_data = callback_data; ready_data_list = g_list_prepend (ready_data_list, data); return data; } static void file_list_file_ready_finish (FileListReadyData *data) { if (data->callback) { (*data->callback)(data->file_list, data->callback_data); } file_list_ready_data_free (data); } static void file_list_file_ready_callback (NautilusFile *file, gpointer user_data) { FileListReadyData *data = user_data; g_hash_table_remove (data->remaining_files, file); if (data->all_checked && g_hash_table_size (data->remaining_files) == 0) { file_list_file_ready_finish (data); } } void nautilus_file_list_call_when_ready (GList *file_list, NautilusAttributes attributes, NautilusFileListHandle **handle, NautilusFileListCallback callback, gpointer callback_data) { g_return_if_fail (file_list != NULL); FileListReadyData *data = file_list_ready_data_new (file_list, callback, callback_data); if (handle) { *handle = (NautilusFileListHandle *) data; } for (GList *l = file_list; l != NULL;) { NautilusFile *file = NAUTILUS_FILE (l->data); /* Need to do this here, as the callback can modify the list */ l = l->next; if (!nautilus_file_check_if_ready (file, attributes)) { g_hash_table_add (data->remaining_files, file); nautilus_file_call_when_ready (file, attributes, file_list_file_ready_callback, data); } } /* By now all files are checked, if all are ready callback can be called */ data->all_checked = TRUE; /* In case all files are already ready call callback now */ if (g_hash_table_size (data->remaining_files) == 0) { file_list_file_ready_finish (data); } } void nautilus_file_list_cancel_call_when_ready (NautilusFileListHandle *handle) { g_return_if_fail (handle != NULL); FileListReadyData *data = (FileListReadyData *) handle; if (g_list_find (ready_data_list, data) == NULL) { g_return_if_reached (); } g_autoptr (GPtrArray) remaining_files = g_hash_table_steal_all_keys (data->remaining_files); for (guint i = 0; i < remaining_files->len; i++) { NautilusFile *file = NAUTILUS_FILE (remaining_files->pdata[i]); NautilusFileClass *file_class = NAUTILUS_FILE_CLASS (G_OBJECT_GET_CLASS (file)); file_class->cancel_call_when_ready (file, file_list_file_ready_callback, data); } file_list_ready_data_free (data); } static void update_thumbnail_limit (void) { cached_thumbnail_limit = g_settings_get_uint64 (nautilus_preferences, NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT); /*Converts the obtained limit in MB to bytes */ cached_thumbnail_limit *= MEGA_TO_BASE_RATE; } static void thumbnail_limit_changed_callback (gpointer user_data) { update_thumbnail_limit (); /* Tell the world that icons might have changed. We could invent a narrower-scope * signal to mean only "thumbnails might have changed" if this ends up being slow * for some reason. */ emit_change_signals_for_all_files_in_all_directories (); } static void update_show_thumbnails (void) { show_file_thumbs = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS); } static void show_thumbnails_changed_callback (gpointer user_data) { update_show_thumbnails (); /* Tell the world that icons might have changed. We could invent a narrower-scope * signal to mean only "thumbnails might have changed" if this ends up being slow * for some reason. */ emit_change_signals_for_all_files_in_all_directories (); } static void update_show_directory_item_count (void) { show_directory_item_count = g_settings_get_enum (nautilus_preferences, NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS); } static void mime_type_data_changed_callback (GObject *signaller, gpointer user_data) { /* Tell the world that icons might have changed. We could invent a narrower-scope * signal to mean only "thumbnails might have changed" if this ends up being slow * for some reason. */ emit_change_signals_for_all_files_in_all_directories (); } static gboolean real_get_item_count (NautilusFile *file, guint *count, gboolean *count_unreadable) { if (count_unreadable != NULL) { *count_unreadable = file->details->directory_count_failed; } if (!file->details->got_directory_count) { if (count != NULL) { *count = 0; } return FALSE; } if (count != NULL) { *count = file->details->directory_count; } return TRUE; } static NautilusRequestStatus real_get_deep_counts (NautilusFile *file, guint *directory_count, guint *file_count, guint *unreadable_directory_count, goffset *total_size) { GFileType type; type = nautilus_file_get_file_type (file); if (directory_count != NULL) { *directory_count = 0; } if (file_count != NULL) { *file_count = 0; } if (unreadable_directory_count != NULL) { *unreadable_directory_count = 0; } if (total_size != NULL) { *total_size = 0; } if (type != G_FILE_TYPE_DIRECTORY) { return NAUTILUS_REQUEST_DONE; } if (file->details->deep_counts_status != NAUTILUS_REQUEST_NOT_STARTED) { if (directory_count != NULL) { *directory_count = file->details->deep_directory_count; } if (file_count != NULL) { *file_count = file->details->deep_file_count; } if (unreadable_directory_count != NULL) { *unreadable_directory_count = file->details->deep_unreadable_count; } if (total_size != NULL) { *total_size = file->details->deep_size; } return file->details->deep_counts_status; } /* For directories, or before we know the type, we haven't started. */ if (type == G_FILE_TYPE_UNKNOWN || type == G_FILE_TYPE_DIRECTORY) { return NAUTILUS_REQUEST_NOT_STARTED; } /* For other types, we are done, and the zeros are permanent. */ return NAUTILUS_REQUEST_DONE; } static void nautilus_file_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NautilusFile *file = NAUTILUS_FILE (object); switch (prop_id) { case PROP_DISPLAY_NAME: { g_value_set_string (value, nautilus_file_get_display_name (file)); } break; case PROP_A11Y_NAME: { NautilusTagManager *tag_manager = nautilus_tag_manager_get (); g_autofree gchar *uri = nautilus_file_get_uri (file); gboolean is_starred = nautilus_tag_manager_file_is_starred (tag_manager, uri); gboolean is_directory = nautilus_file_is_directory (file); const gchar *role = is_directory ? /* Translators: This is the role for a folder icon when a screen reader is * used. If a folder is selected, it will say "XXXXXX. Folder" */ C_("Role", "Folder") : /* Translators: This is the role for a file icon when a screen reader is * used. If a file is selected, it will say "XXXXXX. File" */ C_("Role", "File"); g_autofree gchar *accessible_name = NULL; if (is_starred) { accessible_name = g_strdup_printf ( /* A "." is added in between file name and the role, * and another one between the role and the starring * action description as a useful pause in the screen reader * announcement. */ "%s. %s. %s", file->details->display_name, role, /* Translators: when a file is marked with a star, this text * is added at the end of the name when prononunced by a * screen reader, after the role * (e.g. "My documents. Folder. Starred") */ C_("Role", "Starred")); } else { accessible_name = g_strdup_printf ( /* A "." is added in between file name and the role * as a useful pause in the screen reader * announcement. */ "%s. %s", file->details->display_name, role); } g_value_set_string (value, accessible_name); } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } } static void nautilus_file_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NautilusFile *file = NAUTILUS_FILE (object); switch (prop_id) { case PROP_DIRECTORY: { nautilus_file_set_directory (file, g_value_get_object (value)); } break; default: { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } } static void default_no_op (NautilusFile *file) { /* Dummy default impl */ } static NautilusFileInfo * nautilus_file_get_info (GFile *location, gboolean create) { return NAUTILUS_FILE_INFO ((create) ? nautilus_file_get (location) : nautilus_file_get_existing (location)); } static void nautilus_file_class_init (NautilusFileClass *class) { nautilus_file_info_getter = nautilus_file_get_info; attribute_name_q = g_quark_from_static_string ("name"); attribute_size_q = g_quark_from_static_string ("size"); attribute_type_q = g_quark_from_static_string ("type"); attribute_detailed_type_q = g_quark_from_static_string ("detailed_type"); attribute_modification_date_q = g_quark_from_static_string ("modification_date"); attribute_date_modified_q = g_quark_from_static_string ("date_modified"); attribute_date_modified_full_q = g_quark_from_static_string ("date_modified_full"); attribute_recency_q = g_quark_from_static_string ("recency"); attribute_accessed_date_q = g_quark_from_static_string ("accessed_date"); attribute_date_accessed_q = g_quark_from_static_string ("date_accessed"); attribute_date_accessed_full_q = g_quark_from_static_string ("date_accessed_full"); attribute_date_created_q = g_quark_from_static_string ("date_created"); attribute_date_created_full_q = g_quark_from_static_string ("date_created_full"); attribute_mime_type_q = g_quark_from_static_string ("mime_type"); attribute_size_detail_q = g_quark_from_static_string ("size_detail"); attribute_deep_size_q = g_quark_from_static_string ("deep_size"); attribute_deep_file_count_q = g_quark_from_static_string ("deep_file_count"); attribute_deep_directory_count_q = g_quark_from_static_string ("deep_directory_count"); attribute_deep_total_count_q = g_quark_from_static_string ("deep_total_count"); attribute_search_relevance_q = g_quark_from_static_string ("search_relevance"); attribute_trashed_on_q = g_quark_from_static_string ("trashed_on"); attribute_trashed_on_full_q = g_quark_from_static_string ("trashed_on_full"); attribute_trash_orig_path_q = g_quark_from_static_string ("trash_orig_path"); attribute_permissions_q = g_quark_from_static_string ("permissions"); attribute_selinux_context_q = g_quark_from_static_string ("selinux_context"); attribute_octal_permissions_q = g_quark_from_static_string ("octal_permissions"); attribute_owner_q = g_quark_from_static_string ("owner"); attribute_group_q = g_quark_from_static_string ("group"); attribute_uri_q = g_quark_from_static_string ("uri"); attribute_where_q = g_quark_from_static_string ("where"); attribute_link_target_q = g_quark_from_static_string ("link_target"); attribute_volume_q = g_quark_from_static_string ("volume"); attribute_free_space_q = g_quark_from_static_string ("free_space"); attribute_starred_q = g_quark_from_static_string ("starred"); G_OBJECT_CLASS (class)->dispose = nautilus_file_dispose; G_OBJECT_CLASS (class)->finalize = finalize; G_OBJECT_CLASS (class)->constructor = nautilus_file_constructor; G_OBJECT_CLASS (class)->get_property = nautilus_file_get_property; G_OBJECT_CLASS (class)->set_property = nautilus_file_set_property; class->get_item_count = real_get_item_count; class->get_deep_counts = real_get_deep_counts; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wincompatible-pointer-types" class->set_metadata = default_no_op; class->set_metadata_as_list = default_no_op; class->mount = default_no_op; class->unmount = default_no_op; class->eject = default_no_op; class->start = default_no_op; class->stop = default_no_op; class->poll_for_media = default_no_op; #pragma GCC diagnostic pop signals[CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFileClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[UPDATED_DEEP_COUNT_IN_PROGRESS] = g_signal_new ("updated-deep-count-in-progress", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NautilusFileClass, updated_deep_count_in_progress), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); update_thumbnail_limit (); g_signal_connect_swapped (nautilus_preferences, "changed::" NAUTILUS_PREFERENCES_FILE_THUMBNAIL_LIMIT, G_CALLBACK (thumbnail_limit_changed_callback), NULL); update_show_thumbnails (); g_signal_connect_swapped (nautilus_preferences, "changed::" NAUTILUS_PREFERENCES_SHOW_FILE_THUMBNAILS, G_CALLBACK (show_thumbnails_changed_callback), NULL); update_show_directory_item_count (); g_signal_connect_swapped (nautilus_preferences, "changed::" NAUTILUS_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS, G_CALLBACK (update_show_directory_item_count), NULL); g_signal_connect (nautilus_signaller_get_current (), "mime-data-changed", G_CALLBACK (mime_type_data_changed_callback), NULL); properties[PROP_DIRECTORY] = g_param_spec_object ("directory", NULL, NULL, NAUTILUS_TYPE_DIRECTORY, (G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); properties[PROP_DISPLAY_NAME] = g_param_spec_string ("display-name", NULL, NULL, "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); properties[PROP_A11Y_NAME] = g_param_spec_string ("a11y-name", NULL, NULL, "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (G_OBJECT_CLASS (class), N_PROPS, properties); } static gboolean is_gone (NautilusFileInfo *file_info) { return nautilus_file_is_gone (NAUTILUS_FILE (file_info)); } /** * nautilus_file_is_gone * * Check if a file has already been deleted. * @file: NautilusFile representing the file in question. * * Returns: TRUE if the file is already gone. **/ gboolean nautilus_file_is_gone (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->is_gone; } static char * get_name (NautilusFileInfo *file_info) { return g_strdup (nautilus_file_get_name (NAUTILUS_FILE (file_info))); } const char * nautilus_file_get_name (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); return file->details->name; } static char * get_uri (NautilusFileInfo *file_info) { return nautilus_file_get_uri (NAUTILUS_FILE (file_info)); } /* Return the actual uri associated with the passed-in file. */ char * nautilus_file_get_uri (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); g_autoptr (GFile) location = nautilus_file_get_location (file); return g_file_get_uri (location); } static char * get_parent_uri (NautilusFileInfo *file_info) { return nautilus_file_get_parent_uri (NAUTILUS_FILE (file_info)); } /** * nautilus_file_get_parent_uri: * * Get the uri for the parent directory. * * @file: The file in question. * * Return value: A string for the parent's location, in "raw URI" form. * Use nautilus_file_get_parent_uri_for_display instead if the * result is to be displayed on-screen. * If the parent is NULL, returns the empty string. */ char * nautilus_file_get_parent_uri (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); if (nautilus_file_is_self_owned (file)) { /* Callers expect an empty string, not a NULL. */ return g_strdup (""); } return nautilus_directory_get_uri (file->details->directory); } static char * get_uri_scheme (NautilusFileInfo *file_info) { return nautilus_file_get_uri_scheme (NAUTILUS_FILE (file_info)); } char * nautilus_file_get_uri_scheme (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); if (file->details->directory == NULL) { return NULL; } g_autoptr (GFile) location = nautilus_directory_get_location (file->details->directory); if (location == NULL) { return NULL; } return g_file_get_uri_scheme (location); } static char * get_mime_type (NautilusFileInfo *file_info) { return g_strdup (nautilus_file_get_mime_type (NAUTILUS_FILE (file_info))); } /** * nautilus_file_get_mime_type * * Return this file's default mime type. * @file: NautilusFile representing the file in question. * * Returns: (transfer none): The mime type. * **/ const char * nautilus_file_get_mime_type (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); return file->details->mime_type != NULL ? file->details->mime_type : "application/octet-stream"; } static gboolean is_mime_type (NautilusFileInfo *file_info, const char *mime_type) { return nautilus_file_is_mime_type (NAUTILUS_FILE (file_info), mime_type); } /** * nautilus_file_is_mime_type * * Check whether a file is of a particular MIME type, or inherited * from it. * @file: NautilusFile representing the file in question. * @mime_type: The MIME-type string to test (e.g. "text/plain") * * Return value: TRUE if @mime_type exactly matches the * file's MIME type. * **/ gboolean nautilus_file_is_mime_type (NautilusFile *file, const char *mime_type) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); g_return_val_if_fail (mime_type != NULL, FALSE); if (file->details->mime_type == NULL) { return FALSE; } return g_content_type_is_a (file->details->mime_type, mime_type); } static gboolean is_directory (NautilusFileInfo *file_info) { return nautilus_file_is_directory (NAUTILUS_FILE (file_info)); } /** * nautilus_file_is_directory * * Check if this file is a directory. * @file: NautilusFile representing the file in question. * * Returns: TRUE if @file is a directory. * **/ gboolean nautilus_file_is_directory (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return nautilus_file_get_file_type (file) == G_FILE_TYPE_DIRECTORY; } static void add_emblem (NautilusFileInfo *file_info, const char *emblem_name) { NautilusFile *file = NAUTILUS_FILE (file_info); file->details->extension_emblems = g_list_prepend (file->details->extension_emblems, g_strdup (emblem_name)); nautilus_file_changed (file); } static char * get_string_attribute (NautilusFileInfo *file_info, const char *attribute_name) { return nautilus_file_get_string_attribute (NAUTILUS_FILE (file_info), attribute_name); } static void add_string_attribute (NautilusFileInfo *file_info, const char *attribute_name, const char *value) { NautilusFile *file = NAUTILUS_FILE (file_info); /* Lazily create hashtable */ if (file->details->extension_attributes == NULL) { file->details->extension_attributes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_free); } g_hash_table_insert (file->details->extension_attributes, GINT_TO_POINTER (g_quark_from_string (attribute_name)), g_strdup (value)); nautilus_file_changed (file); } static void invalidate_extension_info (NautilusFileInfo *file_info) { NautilusFile *file = NAUTILUS_FILE (file_info); nautilus_file_invalidate_attributes (file, NAUTILUS_ATTRIBUTE_EXTENSION_INFO); } static char * get_activation_uri (NautilusFileInfo *file_info) { return nautilus_file_get_activation_uri (NAUTILUS_FILE (file_info)); } /* Return the uri associated with the passed-in file, which may not be * the actual uri if the file is an desktop file or a nautilus * xml link file. */ char * nautilus_file_get_activation_uri (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); if (file->details->activation_uri != NULL) { return g_strdup (file->details->activation_uri); } return nautilus_file_get_uri (file); } static GFileType get_file_type (NautilusFileInfo *file_info) { return nautilus_file_get_file_type (NAUTILUS_FILE (file_info)); } /** * nautilus_file_get_file_type * * Return this file's type. * @file: NautilusFile representing the file in question. * * Returns: The type. * **/ GFileType nautilus_file_get_file_type (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), G_FILE_TYPE_UNKNOWN); return file->details->type; } static GFile * get_location (NautilusFileInfo *file_info) { return nautilus_file_get_location (NAUTILUS_FILE (file_info)); } GFile * nautilus_file_get_location (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); g_autoptr (GFile) location = nautilus_directory_get_location (file->details->directory); if (nautilus_file_is_self_owned (file)) { return g_steal_pointer (&location); } return g_file_get_child (location, file->details->name); } static GFile * get_parent_location (NautilusFileInfo *file_info) { return nautilus_file_get_parent_location (NAUTILUS_FILE (file_info)); } GFile * nautilus_file_get_parent_location (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); if (nautilus_file_is_self_owned (file)) { return NULL; } return nautilus_directory_get_location (file->details->directory); } static NautilusFileInfo * get_parent_info (NautilusFileInfo *file_info) { return NAUTILUS_FILE_INFO (nautilus_file_get_parent (NAUTILUS_FILE (file_info))); } NautilusFile * nautilus_file_get_parent (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); if (nautilus_file_is_self_owned (file)) { return NULL; } return nautilus_directory_get_corresponding_file (file->details->directory); } static GMount * get_mount (NautilusFileInfo *file_info) { return nautilus_file_get_mount (NAUTILUS_FILE (file_info)); } GMount * nautilus_file_get_mount (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), NULL); return (file->details->mount != NULL) ? g_object_ref (file->details->mount) : NULL; } static gboolean can_write (NautilusFileInfo *file_info) { return nautilus_file_can_write (NAUTILUS_FILE (file_info)); } /** * nautilus_file_can_write: * * Check whether the user is allowed to write to this file. * * @file: The file to check. * * Return value: FALSE if the user is definitely not allowed to write * to the file. If the user has write permission, or * the code can't tell whether the user has write permission, * returns TRUE (so failures must always be handled). */ gboolean nautilus_file_can_write (NautilusFile *file) { g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE); return file->details->can_write; } static void nautilus_file_info_iface_init (NautilusFileInfoInterface *iface) { iface->is_gone = is_gone; iface->get_name = get_name; iface->get_uri = get_uri; iface->get_parent_uri = get_parent_uri; iface->get_uri_scheme = get_uri_scheme; iface->get_mime_type = get_mime_type; iface->is_mime_type = is_mime_type; iface->is_directory = is_directory; iface->add_emblem = add_emblem; iface->get_string_attribute = get_string_attribute; iface->add_string_attribute = add_string_attribute; iface->invalidate_extension_info = invalidate_extension_info; iface->get_activation_uri = get_activation_uri; iface->get_file_type = get_file_type; iface->get_location = get_location; iface->get_parent_location = get_parent_location; iface->get_parent_info = get_parent_info; iface->get_mount = get_mount; iface->can_write = can_write; }