diff options
author | Perberos <[email protected]> | 2011-12-01 22:24:23 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-12-01 22:24:23 -0300 |
commit | 0e004c696b0e68b2cff37a4c3315b022a35eaf43 (patch) | |
tree | 43261e815529cb9518ed7be37af13b846af8b26b /libcaja-private/caja-file.c | |
download | caja-0e004c696b0e68b2cff37a4c3315b022a35eaf43.tar.bz2 caja-0e004c696b0e68b2cff37a4c3315b022a35eaf43.tar.xz |
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'libcaja-private/caja-file.c')
-rw-r--r-- | libcaja-private/caja-file.c | 8411 |
1 files changed, 8411 insertions, 0 deletions
diff --git a/libcaja-private/caja-file.c b/libcaja-private/caja-file.c new file mode 100644 index 00000000..4b229ca6 --- /dev/null +++ b/libcaja-private/caja-file.c @@ -0,0 +1,8411 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + + caja-file.c: Caja 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, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Author: Darin Adler <[email protected]> +*/ + +#include <config.h> +#include "caja-file.h" + +#include "caja-directory-notify.h" +#include "caja-directory-private.h" +#include "caja-signaller.h" +#include "caja-desktop-directory.h" +#include "caja-desktop-directory-file.h" +#include "caja-desktop-icon-file.h" +#include "caja-file-attributes.h" +#include "caja-file-private.h" +#include "caja-file-operations.h" +#include "caja-file-utilities.h" +#include "caja-global-preferences.h" +#include "caja-lib-self-check-functions.h" +#include "caja-link.h" +#include "caja-metadata.h" +#include "caja-module.h" +#include "caja-search-directory.h" +#include "caja-search-directory-file.h" +#include "caja-thumbnails.h" +#include "caja-users-groups-cache.h" +#include "caja-vfs-file.h" +#include "caja-saved-search-file.h" +#include <eel/eel-debug.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-vfs-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-string.h> +#include <grp.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <gio/gio.h> +#include <glib.h> + +#include <libcaja-extension/caja-file-info.h> +#include <libcaja-extension/caja-extension-private.h> +#include <libxml/parser.h> +#include <pwd.h> +#include <stdlib.h> +#include <sys/time.h> +#include <time.h> +#include <unistd.h> +#include <sys/stat.h> + +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#endif + +/* Time in seconds to cache getpwuid results */ +#define GETPWUID_CACHE_TIME (5*60) + +#define ICON_NAME_THUMBNAIL_LOADING "image-loading" + +#undef CAJA_FILE_DEBUG_REF +#undef CAJA_FILE_DEBUG_REF_VALGRIND + +#ifdef CAJA_FILE_DEBUG_REF_VALGRIND +#include <valgrind/valgrind.h> +#define DEBUG_REF_PRINTF VALGRIND_PRINTF_BACKTRACE +#else +#define DEBUG_REF_PRINTF printf +#endif + +/* Files that start with these characters sort after files that don't. */ +#define SORT_LAST_CHAR1 '.' +#define SORT_LAST_CHAR2 '#' + +/* Name of Caja trash directories */ +#define TRASH_DIRECTORY_NAME ".Trash" + +#define METADATA_ID_IS_LIST_MASK (1<<31) + +typedef enum { + SHOW_HIDDEN = 1 << 0, + SHOW_BACKUP = 1 << 1 +} FilterOptions; + +typedef void (* ModifyListFunction) (GList **list, CajaFile *file); + +enum { + CHANGED, + UPDATED_DEEP_COUNT_IN_PROGRESS, + LAST_SIGNAL +}; + +static int date_format_pref; + +static guint signals[LAST_SIGNAL]; + +static GHashTable *symbolic_links; + +static GQuark attribute_name_q, + attribute_size_q, + attribute_type_q, + attribute_modification_date_q, + attribute_date_modified_q, + attribute_accessed_date_q, + attribute_date_accessed_q, + attribute_emblems_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_date_changed_q, + attribute_trashed_on_q, + attribute_trash_orig_path_q, + attribute_date_permissions_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; + +static void caja_file_info_iface_init (CajaFileInfoIface *iface); +static char * caja_file_get_owner_as_string (CajaFile *file, + gboolean include_real_name); +static char * caja_file_get_type_as_string (CajaFile *file); +static gboolean update_info_and_name (CajaFile *file, + GFileInfo *info); +static const char * caja_file_peek_display_name (CajaFile *file); +static const char * caja_file_peek_display_name_collation_key (CajaFile *file); +static void file_mount_unmounted (GMount *mount, gpointer data); +static void metadata_hash_free (GHashTable *hash); + +G_DEFINE_TYPE_WITH_CODE (CajaFile, caja_file, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CAJA_TYPE_FILE_INFO, + caja_file_info_iface_init)); + +static void +caja_file_init (CajaFile *file) +{ + file->details = G_TYPE_INSTANCE_GET_PRIVATE ((file), CAJA_TYPE_FILE, CajaFileDetails); + + caja_file_clear_info (file); + caja_file_invalidate_extension_info_internal (file); +} + +static GObject* +caja_file_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + CajaFile *file; + + object = (* G_OBJECT_CLASS (caja_file_parent_class)->constructor) (type, + n_construct_properties, + construct_params); + + file = CAJA_FILE (object); + + /* Set to default type after full construction */ + if (CAJA_FILE_GET_CLASS (file)->default_file_type != G_FILE_TYPE_UNKNOWN) { + file->details->type = CAJA_FILE_GET_CLASS (file)->default_file_type; + } + + return object; +} + +gboolean +caja_file_set_display_name (CajaFile *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; + caja_file_invalidate_attributes (file, + CAJA_FILE_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 (eel_strcmp (eel_ref_str_peek (file->details->display_name), display_name) != 0) { + changed = TRUE; + + eel_ref_str_unref (file->details->display_name); + + if (eel_strcmp (eel_ref_str_peek (file->details->name), display_name) == 0) { + file->details->display_name = eel_ref_str_ref (file->details->name); + } else { + file->details->display_name = eel_ref_str_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); + } + + if (eel_strcmp (eel_ref_str_peek (file->details->edit_name), edit_name) != 0) { + changed = TRUE; + + eel_ref_str_unref (file->details->edit_name); + if (eel_strcmp (eel_ref_str_peek (file->details->display_name), edit_name) == 0) { + file->details->edit_name = eel_ref_str_ref (file->details->display_name); + } else { + file->details->edit_name = eel_ref_str_new (edit_name); + } + } + + file->details->got_custom_display_name = custom; + return changed; +} + +static void +caja_file_clear_display_name (CajaFile *file) +{ + eel_ref_str_unref (file->details->display_name); + file->details->display_name = NULL; + g_free (file->details->display_name_collation_key); + file->details->display_name_collation_key = NULL; + eel_ref_str_unref (file->details->edit_name); + file->details->edit_name = NULL; +} + +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 +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 (!eel_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 (CajaFile *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 = caja_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 +caja_file_update_metadata_from_info (CajaFile *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 +caja_file_clear_info (CajaFile *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 = CAJA_FILE_GET_CLASS (file)->default_file_type; + + if (!file->details->got_custom_display_name) { + caja_file_clear_display_name (file); + } + + if (!file->details->got_custom_activation_uri && + 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_launcher = FALSE; + file->details->is_foreign_link = FALSE; + file->details->is_trusted_link = FALSE; + file->details->is_symlink = FALSE; + file->details->is_hidden = FALSE; + file->details->is_backup = FALSE; + file->details->is_mountpoint = FALSE; + file->details->uid = -1; + file->details->gid = -1; + 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->ctime = 0; + file->details->trash_time = 0; + g_free (file->details->symlink_name); + file->details->symlink_name = NULL; + eel_ref_str_unref (file->details->mime_type); + file->details->mime_type = NULL; + g_free (file->details->selinux_context); + file->details->selinux_context = NULL; + g_free (file->details->description); + file->details->description = NULL; + eel_ref_str_unref (file->details->owner); + file->details->owner = NULL; + eel_ref_str_unref (file->details->owner_real); + file->details->owner_real = NULL; + eel_ref_str_unref (file->details->group); + file->details->group = NULL; + + eel_ref_str_unref (file->details->filesystem_id); + file->details->filesystem_id = NULL; + + clear_metadata (file); +} + +static CajaFile * +caja_file_new_from_filename (CajaDirectory *directory, + const char *filename, + gboolean self_owned) +{ + CajaFile *file; + + g_assert (CAJA_IS_DIRECTORY (directory)); + g_assert (filename != NULL); + g_assert (filename[0] != '\0'); + + if (CAJA_IS_DESKTOP_DIRECTORY (directory)) { + if (self_owned) { + file = CAJA_FILE (g_object_new (CAJA_TYPE_DESKTOP_DIRECTORY_FILE, NULL)); + } else { + /* This doesn't normally happen, unless the user somehow types in a uri + * that references a file like this. (See #349840) */ + file = CAJA_FILE (g_object_new (CAJA_TYPE_VFS_FILE, NULL)); + } + } else if (CAJA_IS_SEARCH_DIRECTORY (directory)) { + if (self_owned) { + file = CAJA_FILE (g_object_new (CAJA_TYPE_SEARCH_DIRECTORY_FILE, NULL)); + } else { + /* This doesn't normally happen, unless the user somehow types in a uri + * that references a file like this. (See #349840) */ + file = CAJA_FILE (g_object_new (CAJA_TYPE_VFS_FILE, NULL)); + } + } else if (g_str_has_suffix (filename, CAJA_SAVED_SEARCH_EXTENSION)) { + file = CAJA_FILE (g_object_new (CAJA_TYPE_SAVED_SEARCH_FILE, NULL)); + } else { + file = CAJA_FILE (g_object_new (CAJA_TYPE_VFS_FILE, NULL)); + } + + file->details->directory = caja_directory_ref (directory); + + file->details->name = eel_ref_str_new (filename); + +#ifdef CAJA_FILE_DEBUG_REF + DEBUG_REF_PRINTF("%10p ref'd", file); +#endif + + return file; +} + +static void +modify_link_hash_table (CajaFile *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) { + return; + } + + /* Create the hash table first time through. */ + if (symbolic_links == NULL) { + symbolic_links = eel_g_hash_table_new_free_at_exit + (g_str_hash, g_str_equal, "caja-file.c: symbolic_links"); + } + + target_uri = caja_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, CajaFile *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 (CajaFile *file) +{ + modify_link_hash_table (file, add_to_link_hash_table_list); +} + +static void +remove_from_link_hash_table_list (GList **list, CajaFile *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 (CajaFile *file) +{ + modify_link_hash_table (file, remove_from_link_hash_table_list); +} + +CajaFile * +caja_file_new_from_info (CajaDirectory *directory, + GFileInfo *info) +{ + CajaFile *file; + const char *mime_type; + + g_return_val_if_fail (CAJA_IS_DIRECTORY (directory), NULL); + g_return_val_if_fail (info != NULL, NULL); + + mime_type = g_file_info_get_content_type (info); + if (mime_type && + strcmp (mime_type, CAJA_SAVED_SEARCH_MIMETYPE) == 0) { + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); + file = CAJA_FILE (g_object_new (CAJA_TYPE_SAVED_SEARCH_FILE, NULL)); + } else { + file = CAJA_FILE (g_object_new (CAJA_TYPE_VFS_FILE, NULL)); + } + + file->details->directory = caja_directory_ref (directory); + + update_info_and_name (file, info); + +#ifdef CAJA_FILE_DEBUG_REF + DEBUG_REF_PRINTF("%10p ref'd", file); +#endif + + return file; +} + +static CajaFile * +caja_file_get_internal (GFile *location, gboolean create) +{ + gboolean self_owned; + CajaDirectory *directory; + CajaFile *file; + GFile *parent; + char *basename; + + g_assert (location != NULL); + + parent = g_file_get_parent (location); + + self_owned = FALSE; + if (parent == NULL) { + self_owned = TRUE; + parent = g_object_ref (location); + } + + /* Get object that represents the directory. */ + directory = caja_directory_get_internal (parent, create); + + g_object_unref (parent); + + /* Get the name for the file. */ + if (self_owned && directory != NULL) { + basename = caja_directory_get_name_for_self_as_new_file (directory); + } else { + basename = g_file_get_basename (location); + } + /* Check to see if it's a file that's already known. */ + if (directory == NULL) { + file = NULL; + } else if (self_owned) { + file = directory->details->as_file; + } else { + file = caja_directory_find_file_by_name (directory, basename); + } + + /* Ref or create the file. */ + if (file != NULL) { + caja_file_ref (file); + } else if (create) { + file = caja_file_new_from_filename (directory, basename, self_owned); + if (self_owned) { + g_assert (directory->details->as_file == NULL); + directory->details->as_file = file; + } else { + caja_directory_add_file (directory, file); + } + } + + g_free (basename); + caja_directory_unref (directory); + + return file; +} + +CajaFile * +caja_file_get (GFile *location) +{ + return caja_file_get_internal (location, TRUE); +} + +CajaFile * +caja_file_get_existing (GFile *location) +{ + return caja_file_get_internal (location, FALSE); +} + +CajaFile * +caja_file_get_existing_by_uri (const char *uri) +{ + GFile *location; + CajaFile *file; + + location = g_file_new_for_uri (uri); + file = caja_file_get_internal (location, FALSE); + g_object_unref (location); + + return file; +} + +CajaFile * +caja_file_get_by_uri (const char *uri) +{ + GFile *location; + CajaFile *file; + + location = g_file_new_for_uri (uri); + file = caja_file_get_internal (location, TRUE); + g_object_unref (location); + + return file; +} + +gboolean +caja_file_is_self_owned (CajaFile *file) +{ + return file->details->directory->details->as_file == file; +} + +static void +finalize (GObject *object) +{ + CajaDirectory *directory; + CajaFile *file; + char *uri; + + file = CAJA_FILE (object); + + g_assert (file->details->operations_in_progress == NULL); + + if (file->details->is_thumbnailing) { + uri = caja_file_get_uri (file); + caja_thumbnail_remove_from_queue (uri); + g_free (uri); + } + + caja_async_destroying_file (file); + + remove_from_link_hash_table (file); + + directory = file->details->directory; + + if (caja_file_is_self_owned (file)) { + directory->details->as_file = NULL; + } else { + if (!file->details->is_gone) { + caja_directory_remove_file (directory, file); + } + } + + if (file->details->get_info_error) { + g_error_free (file->details->get_info_error); + } + + caja_directory_unref (directory); + eel_ref_str_unref (file->details->name); + eel_ref_str_unref (file->details->display_name); + g_free (file->details->display_name_collation_key); + eel_ref_str_unref (file->details->edit_name); + if (file->details->icon) { + g_object_unref (file->details->icon); + } + g_free (file->details->thumbnail_path); + g_free (file->details->symlink_name); + eel_ref_str_unref (file->details->mime_type); + eel_ref_str_unref (file->details->owner); + eel_ref_str_unref (file->details->owner_real); + eel_ref_str_unref (file->details->group); + g_free (file->details->selinux_context); + g_free (file->details->description); + g_free (file->details->top_left_text); + g_free (file->details->custom_icon); + g_free (file->details->activation_uri); + g_free (file->details->compare_by_emblem_cache); + + if (file->details->thumbnail) { + g_object_unref (file->details->thumbnail); + } + if (file->details->mount) { + g_signal_handlers_disconnect_by_func (file->details->mount, file_mount_unmounted, file); + g_object_unref (file->details->mount); + } + + eel_ref_str_unref (file->details->filesystem_id); + + eel_g_list_free_deep (file->details->mime_list); + + eel_g_list_free_deep (file->details->pending_extension_emblems); + eel_g_list_free_deep (file->details->extension_emblems); + eel_g_object_list_free (file->details->pending_info_providers); + + if (file->details->pending_extension_attributes) { + g_hash_table_destroy (file->details->pending_extension_attributes); + } + + if (file->details->extension_attributes) { + g_hash_table_destroy (file->details->extension_attributes); + } + + if (file->details->metadata) { + metadata_hash_free (file->details->metadata); + } + + G_OBJECT_CLASS (caja_file_parent_class)->finalize (object); +} + +CajaFile * +caja_file_ref (CajaFile *file) +{ + if (file == NULL) { + return NULL; + } + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + +#ifdef CAJA_FILE_DEBUG_REF + DEBUG_REF_PRINTF("%10p ref'd", file); +#endif + + return g_object_ref (file); +} + +void +caja_file_unref (CajaFile *file) +{ + if (file == NULL) { + return; + } + + g_return_if_fail (CAJA_IS_FILE (file)); + +#ifdef CAJA_FILE_DEBUG_REF + DEBUG_REF_PRINTF("%10p unref'd", file); +#endif + + g_object_unref (file); +} + +/** + * caja_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://"). + * If the parent is NULL, returns the empty string. + */ +char * +caja_file_get_parent_uri_for_display (CajaFile *file) +{ + GFile *parent; + char *result; + + g_assert (CAJA_IS_FILE (file)); + + parent = caja_file_get_parent_location (file); + if (parent) { + result = g_file_get_parse_name (parent); + g_object_unref (parent); + } else { + result = g_strdup (""); + } + + return result; +} + +/** + * caja_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 caja_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 * +caja_file_get_parent_uri (CajaFile *file) +{ + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_self_owned (file)) { + /* Callers expect an empty string, not a NULL. */ + return g_strdup (""); + } + + return caja_directory_get_uri (file->details->directory); +} + +GFile * +caja_file_get_parent_location (CajaFile *file) +{ + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_self_owned (file)) { + /* Callers expect an empty string, not a NULL. */ + return NULL; + } + + return caja_directory_get_location (file->details->directory); +} + +CajaFile * +caja_file_get_parent (CajaFile *file) +{ + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_self_owned (file)) { + return NULL; + } + + return caja_directory_get_corresponding_file (file->details->directory); +} + +/** + * caja_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 +caja_file_can_read (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return file->details->can_read; +} + +/** + * caja_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 +caja_file_can_write (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return file->details->can_write; +} + +/** + * caja_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 +caja_file_can_execute (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return file->details->can_execute; +} + +gboolean +caja_file_can_mount (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return file->details->can_mount; +} + +gboolean +caja_file_can_unmount (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return file->details->can_unmount || + (file->details->mount != NULL && + g_mount_can_unmount (file->details->mount)); +} + +gboolean +caja_file_can_eject (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return file->details->can_eject || + (file->details->mount != NULL && + g_mount_can_eject (file->details->mount)); +} + +gboolean +caja_file_can_start (CajaFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->can_start) { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) { + ret = g_drive_can_start (drive); + g_object_unref (drive); + } + } + + out: + return ret; +} + +gboolean +caja_file_can_start_degraded (CajaFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->can_start_degraded) { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) { + ret = g_drive_can_start_degraded (drive); + g_object_unref (drive); + } + } + + out: + return ret; +} + +gboolean +caja_file_can_poll_for_media (CajaFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->can_poll_for_media) { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) { + ret = g_drive_can_poll_for_media (drive); + g_object_unref (drive); + } + } + + out: + return ret; +} + +gboolean +caja_file_is_media_check_automatic (CajaFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->is_media_check_automatic) { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) { + ret = g_drive_is_media_check_automatic (drive); + g_object_unref (drive); + } + } + + out: + return ret; +} + + +gboolean +caja_file_can_stop (CajaFile *file) +{ + gboolean ret; + GDrive *drive; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + ret = FALSE; + + if (file->details->can_stop) { + ret = TRUE; + goto out; + } + + if (file->details->mount != NULL) { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) { + ret = g_drive_can_stop (drive); + g_object_unref (drive); + } + } + + out: + return ret; +} + +GDriveStartStopType +caja_file_get_start_stop_type (CajaFile *file) +{ + GDriveStartStopType ret; + GDrive *drive; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + ret = G_DRIVE_START_STOP_TYPE_UNKNOWN; + + ret = file->details->start_stop_type; + if (ret != G_DRIVE_START_STOP_TYPE_UNKNOWN) + goto out; + + if (file->details->mount != NULL) { + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) { + ret = g_drive_get_start_stop_type (drive); + g_object_unref (drive); + } + } + + out: + return ret; +} + +void +caja_file_mount (CajaFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + + if (CAJA_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 { + CAJA_FILE_GET_CLASS (file)->mount (file, mount_op, cancellable, callback, callback_data); + } +} + +typedef struct { + CajaFile *file; + CajaFileOperationCallback 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); + } + caja_file_unref (data->file); + g_free (data); +} + +void +caja_file_unmount (CajaFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + UnmountData *data; + + if (file->details->can_unmount) { + if (CAJA_FILE_GET_CLASS (file)->unmount != NULL) { + CAJA_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)) { + data = g_new0 (UnmountData, 1); + data->file = caja_file_ref (file); + data->callback = callback; + data->callback_data = callback_data; + caja_file_operations_unmount_mount_full (NULL, file->details->mount, FALSE, TRUE, unmount_done, data); + } else if (callback) { + callback (file, NULL, NULL, callback_data); + } +} + +void +caja_file_eject (CajaFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + UnmountData *data; + + if (file->details->can_eject) { + if (CAJA_FILE_GET_CLASS (file)->eject != NULL) { + CAJA_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)) { + data = g_new0 (UnmountData, 1); + data->file = caja_file_ref (file); + data->callback = callback; + data->callback_data = callback_data; + caja_file_operations_unmount_mount_full (NULL, file->details->mount, TRUE, TRUE, unmount_done, data); + } else if (callback) { + callback (file, NULL, NULL, callback_data); + } +} + +void +caja_file_start (CajaFile *file, + GMountOperation *start_op, + GCancellable *cancellable, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + + if ((file->details->can_start || file->details->can_start_degraded) && + CAJA_FILE_GET_CLASS (file)->start != NULL) { + CAJA_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) +{ + CajaFileOperation *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; + } + + caja_file_operation_complete (op, NULL, error); + if (error) { + g_error_free (error); + } +} + +void +caja_file_stop (CajaFile *file, + GMountOperation *mount_op, + GCancellable *cancellable, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + + if (CAJA_FILE_GET_CLASS (file)->stop != NULL) { + if (file->details->can_stop) { + CAJA_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 { + GDrive *drive; + + drive = NULL; + if (file->details->mount != NULL) + drive = g_mount_get_drive (file->details->mount); + + if (drive != NULL && g_drive_can_stop (drive)) { + CajaFileOperation *op; + + op = caja_file_operation_new (file, callback, callback_data); + if (cancellable) { + g_object_unref (op->cancellable); + op->cancellable = g_object_ref (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); + } + } + + if (drive != NULL) { + g_object_unref (drive); + } + } +} + +void +caja_file_poll_for_media (CajaFile *file) +{ + if (file->details->can_poll_for_media) { + if (CAJA_FILE_GET_CLASS (file)->stop != NULL) { + CAJA_FILE_GET_CLASS (file)->poll_for_media (file); + } + } else if (file->details->mount != NULL) { + GDrive *drive; + drive = g_mount_get_drive (file->details->mount); + if (drive != NULL) { + g_drive_poll_for_media (drive, + NULL, /* cancellable */ + NULL, /* GAsyncReadyCallback */ + NULL); /* user_data */ + g_object_unref (drive); + } + } +} + +/** + * caja_file_is_desktop_directory: + * + * Check whether this file is the desktop directory. + * + * @file: The file to check. + * + * Return value: TRUE if this is the physical desktop directory. + */ +gboolean +caja_file_is_desktop_directory (CajaFile *file) +{ + GFile *dir; + + dir = file->details->directory->details->location; + + if (dir == NULL) { + return FALSE; + } + + return caja_is_desktop_directory_file (dir, eel_ref_str_peek (file->details->name)); +} + +static gboolean +is_desktop_file (CajaFile *file) +{ + return caja_file_is_mime_type (file, "application/x-desktop"); +} + +static gboolean +can_rename_desktop_file (CajaFile *file) +{ + GFile *location; + gboolean res; + + location = caja_file_get_location (file); + res = g_file_is_native (location); + g_object_unref (location); + return res; +} + +/** + * caja_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 +caja_file_can_rename (CajaFile *file) +{ + gboolean can_rename; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + /* Nonexistent files can't be renamed. */ + if (caja_file_is_gone (file)) { + return FALSE; + } + + /* Self-owned files can't be renamed */ + if (caja_file_is_self_owned (file)) { + return FALSE; + } + + if ((is_desktop_file (file) && !can_rename_desktop_file (file)) || + caja_file_is_home (file)) { + return FALSE; + } + + can_rename = TRUE; + + /* Certain types of links can't be renamed */ + if (CAJA_IS_DESKTOP_ICON_FILE (file)) { + CajaDesktopLink *link; + + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); + + if (link != NULL) { + can_rename = caja_desktop_link_can_rename (link); + g_object_unref (link); + } + } + + if (!can_rename) { + return FALSE; + } + + return file->details->can_rename; +} + +gboolean +caja_file_can_delete (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + /* Nonexistent files can't be deleted. */ + if (caja_file_is_gone (file)) { + return FALSE; + } + + /* Self-owned files can't be deleted */ + if (caja_file_is_self_owned (file)) { + return FALSE; + } + + return file->details->can_delete; +} + +gboolean +caja_file_can_trash (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + /* Nonexistent files can't be deleted. */ + if (caja_file_is_gone (file)) { + return FALSE; + } + + /* Self-owned files can't be deleted */ + if (caja_file_is_self_owned (file)) { + return FALSE; + } + + return file->details->can_trash; +} + +GFile * +caja_file_get_location (CajaFile *file) +{ + GFile *dir; + + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + dir = file->details->directory->details->location; + + if (caja_file_is_self_owned (file)) { + return g_object_ref (dir); + } + + return g_file_get_child (dir, eel_ref_str_peek (file->details->name)); +} + +/* Return the actual uri associated with the passed-in file. */ +char * +caja_file_get_uri (CajaFile *file) +{ + char *uri; + GFile *loc; + + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + loc = caja_file_get_location (file); + uri = g_file_get_uri (loc); + g_object_unref (loc); + + return uri; +} + +char * +caja_file_get_uri_scheme (CajaFile *file) +{ + GFile *loc; + char *scheme; + + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + if (file->details->directory == NULL || + file->details->directory->details->location == NULL) { + return NULL; + } + + loc = caja_directory_get_location (file->details->directory); + scheme = g_file_get_uri_scheme (loc); + g_object_unref (loc); + + return scheme; +} + +CajaFileOperation * +caja_file_operation_new (CajaFile *file, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + CajaFileOperation *op; + + op = g_new0 (CajaFileOperation, 1); + op->file = caja_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 +caja_file_operation_remove (CajaFileOperation *op) +{ + op->file->details->operations_in_progress = g_list_remove + (op->file->details->operations_in_progress, op); +} + +void +caja_file_operation_free (CajaFileOperation *op) +{ + caja_file_operation_remove (op); + caja_file_unref (op->file); + g_object_unref (op->cancellable); + if (op->free_data) { + op->free_data (op->data); + } + g_free (op); +} + +void +caja_file_operation_complete (CajaFileOperation *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". + */ + caja_file_operation_remove (op); + caja_file_changed (op->file); + if (op->callback) { + (* op->callback) (op->file, result_file, error, op->callback_data); + } + caja_file_operation_free (op); +} + +void +caja_file_operation_cancel (CajaFileOperation *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) +{ + CajaFileOperation *op; + CajaDirectory *directory; + CajaFile *existing_file; + char *old_name; + 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) { + 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, mark it gone. + */ + existing_file = caja_directory_find_file_by_name (directory, new_name); + if (existing_file != NULL) { + caja_file_mark_gone (existing_file); + caja_file_changed (existing_file); + } + + old_uri = caja_file_get_uri (op->file); + old_name = g_strdup (eel_ref_str_peek (op->file->details->name)); + + update_info_and_name (op->file, new_info); + + g_free (old_name); + + new_uri = caja_file_get_uri (op->file); + caja_directory_moved (old_uri, new_uri); + g_free (new_uri); + g_free (old_uri); + + /* the rename could have affected the display name if e.g. + * we're in a vfolder where the name comes from a desktop file + * and a rename affects the contents of the desktop file. + */ + if (op->file->details->got_custom_display_name) { + caja_file_invalidate_attributes (op->file, + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO); + } + + g_object_unref (new_info); + } + caja_file_operation_complete (op, NULL, error); + if (error) { + g_error_free (error); + } +} + +static void +rename_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + CajaFileOperation *op; + GFile *new_file; + GError *error; + + op = callback_data; + + error = NULL; + new_file = g_file_set_display_name_finish (G_FILE (source_object), + res, &error); + + if (new_file != NULL) { + g_file_query_info_async (new_file, + CAJA_FILE_DEFAULT_ATTRIBUTES, + 0, + G_PRIORITY_DEFAULT, + op->cancellable, + rename_get_info_callback, op); + } else { + caja_file_operation_complete (op, NULL, error); + g_error_free (error); + } +} + +static gboolean +name_is (CajaFile *file, const char *new_name) +{ + const char *old_name; + old_name = eel_ref_str_peek (file->details->name); + return strcmp (new_name, old_name) == 0; +} + +void +caja_file_rename (CajaFile *file, + const char *new_name, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + CajaFileOperation *op; + char *uri; + char *old_name; + char *new_file_name; + gboolean success, name_changed; + gboolean is_renameable_desktop_file; + GFile *location; + GError *error; + + g_return_if_fail (CAJA_IS_FILE (file)); + g_return_if_fail (new_name != NULL); + g_return_if_fail (callback != NULL); + + is_renameable_desktop_file = + is_desktop_file (file) && can_rename_desktop_file (file); + + /* Return an error for incoming names containing path separators. + * But not for .desktop files as '/' are allowed for them */ + if (strstr (new_name, "/") != NULL && !is_renameable_desktop_file) { + error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Slashes are not allowed in filenames")); + (* callback) (file, NULL, error, callback_data); + g_error_free (error); + return; + } + + /* 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 (caja_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". + */ + caja_file_changed (file); + error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + _("File not found")); + (* callback) (file, NULL, error, callback_data); + g_error_free (error); + return; + } + + /* 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 (!CAJA_IS_DESKTOP_ICON_FILE (file) && + !is_renameable_desktop_file && + name_is (file, new_name)) { + (* callback) (file, NULL, NULL, callback_data); + return; + } + + /* Self-owned files can't be renamed. Test the name-not-actually-changing + * case before this case. + */ + if (caja_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". + */ + caja_file_changed (file); + error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + _("Toplevel files cannot be renamed")); + + (* callback) (file, NULL, error, callback_data); + g_error_free (error); + return; + } + + if (CAJA_IS_DESKTOP_ICON_FILE (file)) { + CajaDesktopLink *link; + + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); + old_name = caja_file_get_display_name (file); + + if ((old_name != NULL && strcmp (new_name, old_name) == 0)) { + success = TRUE; + } else { + success = (link != NULL && caja_desktop_link_rename (link, new_name)); + } + + if (success) { + (* callback) (file, NULL, NULL, callback_data); + } else { + error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, + _("Unable to rename desktop icon")); + (* callback) (file, NULL, error, callback_data); + g_error_free (error); + } + + g_free (old_name); + g_object_unref (link); + return; + } + + if (is_renameable_desktop_file) { + /* Don't actually change the name if the new name is the same. + * This helps for the vfolder method where this can happen and + * we want to minimize actual changes + */ + uri = caja_file_get_uri (file); + old_name = caja_link_local_get_text (uri); + if (old_name != NULL && strcmp (new_name, old_name) == 0) { + success = TRUE; + name_changed = FALSE; + } else { + success = caja_link_local_set_text (uri, new_name); + name_changed = TRUE; + } + g_free (old_name); + g_free (uri); + + if (!success) { + error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, + _("Unable to rename desktop file")); + (* callback) (file, NULL, error, callback_data); + g_error_free (error); + return; + } + new_file_name = g_strdup_printf ("%s.desktop", new_name); + new_file_name = g_strdelimit (new_file_name, "/", '-'); + + if (name_is (file, new_file_name)) { + if (name_changed) { + caja_file_invalidate_attributes (file, + CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO); + } + + (* callback) (file, NULL, NULL, callback_data); + g_free (new_file_name); + return; + } + } else { + new_file_name = g_strdup (new_name); + } + + /* Set up a renaming operation. */ + op = caja_file_operation_new (file, callback, callback_data); + op->is_rename = TRUE; + + /* Do the renaming. */ + + location = caja_file_get_location (file); + g_file_set_display_name_async (location, + new_file_name, + G_PRIORITY_DEFAULT, + op->cancellable, + rename_callback, + op); + g_free (new_file_name); + g_object_unref (location); +} + +gboolean +caja_file_rename_in_progress (CajaFile *file) +{ + GList *node; + CajaFileOperation *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 +caja_file_cancel (CajaFile *file, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + GList *node, *next; + CajaFileOperation *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) { + caja_file_operation_cancel (op); + } + } +} + +gboolean +caja_file_matches_uri (CajaFile *file, const char *match_uri) +{ + GFile *match_file, *location; + gboolean result; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + g_return_val_if_fail (match_uri != NULL, FALSE); + + location = caja_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 +caja_file_compare_location (CajaFile *file_1, + CajaFile *file_2) +{ + GFile *loc_a, *loc_b; + gboolean res; + + loc_a = caja_file_get_location (file_1); + loc_b = caja_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; +} + +gboolean +caja_file_is_local (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return caja_directory_is_local (file->details->directory); +} + +static void +update_link (CajaFile *link_file, CajaFile *target_file) +{ + g_assert (CAJA_IS_FILE (link_file)); + g_assert (CAJA_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 (CajaFile *target_file) +{ + char *uri; + GList **link_files; + + if (symbolic_links == NULL) { + link_files = NULL; + } else { + uri = caja_file_get_uri (target_file); + link_files = g_hash_table_lookup (symbolic_links, uri); + g_free (uri); + } + if (link_files) { + return caja_file_list_copy (*link_files); + } + return NULL; +} + +static void +update_links_if_target (CajaFile *target_file) +{ + GList *link_files, *p; + + link_files = get_link_files (target_file); + for (p = link_files; p != NULL; p = p->next) { + update_link (CAJA_FILE (p->data), target_file); + } + caja_file_list_free (link_files); +} + +static gboolean +update_info_internal (CajaFile *file, + GFileInfo *info, + gboolean update_name) +{ + GList *node; + gboolean changed; + gboolean is_symlink, is_hidden, is_backup, 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 thumbnailing_failed; + int uid, gid; + goffset size; + int sort_order; + time_t atime, mtime, ctime; + time_t trash_time; + GTimeVal g_trash_time; + const char * time_string; + const char *symlink_name, *mime_type, *selinux_context, *name, *thumbnail_path; + GFileType file_type; + GIcon *icon; + char *old_activation_uri; + const char *activation_uri; + const char *description; + const char *filesystem_id; + const char *trash_orig_path; + const char *group, *owner, *owner_real; + gboolean free_owner, free_group; + + if (file->details->is_gone) { + return FALSE; + } + + if (info == NULL) { + caja_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; + + changed |= caja_file_set_display_name (file, + g_file_info_get_display_name (info), + g_file_info_get_edit_name (info), + FALSE); + + file_type = g_file_info_get_file_type (info); + if (file->details->type != file_type) { + changed = TRUE; + } + file->details->type = file_type; + + if (!file->details->got_custom_activation_uri) { + activation_uri = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI); + if (activation_uri == NULL) { + if (file->details->activation_uri) { + g_free (file->details->activation_uri); + file->details->activation_uri = NULL; + changed = TRUE; + } + } else { + old_activation_uri = file->details->activation_uri; + file->details->activation_uri = g_strdup (activation_uri); + + if (old_activation_uri) { + if (strcmp (old_activation_uri, + file->details->activation_uri) != 0) { + changed = TRUE; + } + g_free (old_activation_uri); + } else { + changed = TRUE; + } + } + } + + is_symlink = g_file_info_get_is_symlink (info); + if (file->details->is_symlink != is_symlink) { + changed = TRUE; + } + file->details->is_symlink = is_symlink; + + is_hidden = g_file_info_get_is_hidden (info); + if (file->details->is_hidden != is_hidden) { + changed = TRUE; + } + file->details->is_hidden = is_hidden; + + is_backup = g_file_info_get_is_backup (info); + if (file->details->is_backup != is_backup) { + changed = TRUE; + } + file->details->is_backup = is_backup; + + 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_trash = TRUE; + can_rename = TRUE; + 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); + + uid = -1; + gid = -1; + 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); + 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); + if (group == NULL) { + free_group = TRUE; + group = g_strdup_printf ("%d", gid); + } + } + if (file->details->uid != uid || + file->details->gid != gid) { + changed = TRUE; + } + file->details->uid = uid; + file->details->gid = gid; + + if (eel_strcmp (eel_ref_str_peek (file->details->owner), owner) != 0) { + changed = TRUE; + eel_ref_str_unref (file->details->owner); + file->details->owner = eel_ref_str_get_unique (owner); + } + + if (eel_strcmp (eel_ref_str_peek (file->details->owner_real), owner_real) != 0) { + changed = TRUE; + eel_ref_str_unref (file->details->owner_real); + file->details->owner_real = eel_ref_str_get_unique (owner_real); + } + + if (eel_strcmp (eel_ref_str_peek (file->details->group), group) != 0) { + changed = TRUE; + eel_ref_str_unref (file->details->group); + file->details->group = eel_ref_str_get_unique (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_sort_order (info); + 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); + ctime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CHANGED); + mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + if (file->details->atime != atime || + file->details->mtime != mtime || + file->details->ctime != ctime) { + if (file->details->thumbnail == NULL) { + file->details->thumbnail_is_up_to_date = FALSE; + } + + changed = TRUE; + } + file->details->atime = atime; + file->details->ctime = ctime; + file->details->mtime = mtime; + + if (file->details->thumbnail != NULL && + file->details->thumbnail_mtime != 0 && + file->details->thumbnail_mtime != mtime) { + 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); + } + + thumbnail_path = g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH); + if (eel_strcmp (file->details->thumbnail_path, thumbnail_path) != 0) { + changed = TRUE; + g_free (file->details->thumbnail_path); + file->details->thumbnail_path = g_strdup (thumbnail_path); + } + + thumbnailing_failed = 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; + } + + symlink_name = g_file_info_get_symlink_target (info); + if (eel_strcmp (file->details->symlink_name, symlink_name) != 0) { + changed = TRUE; + g_free (file->details->symlink_name); + file->details->symlink_name = g_strdup (symlink_name); + } + + mime_type = g_file_info_get_content_type (info); + if (eel_strcmp (eel_ref_str_peek (file->details->mime_type), mime_type) != 0) { + changed = TRUE; + eel_ref_str_unref (file->details->mime_type); + file->details->mime_type = eel_ref_str_get_unique (mime_type); + } + + selinux_context = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_SELINUX_CONTEXT); + if (eel_strcmp (file->details->selinux_context, selinux_context) != 0) { + changed = TRUE; + g_free (file->details->selinux_context); + file->details->selinux_context = g_strdup (selinux_context); + } + + description = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION); + if (eel_strcmp (file->details->description, description) != 0) { + changed = TRUE; + g_free (file->details->description); + file->details->description = g_strdup (description); + } + + filesystem_id = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILESYSTEM); + if (eel_strcmp (eel_ref_str_peek (file->details->filesystem_id), filesystem_id) != 0) { + changed = TRUE; + eel_ref_str_unref (file->details->filesystem_id); + file->details->filesystem_id = eel_ref_str_get_unique (filesystem_id); + } + + trash_time = 0; + time_string = g_file_info_get_attribute_string (info, "trash::deletion-date"); + if (time_string != NULL) { + g_time_val_from_iso8601 (time_string, &g_trash_time); + trash_time = g_trash_time.tv_sec; + } + if (file->details->trash_time != trash_time) { + changed = TRUE; + file->details->trash_time = trash_time; + } + + trash_orig_path = g_file_info_get_attribute_byte_string (info, "trash::orig-path"); + if (eel_strcmp (file->details->trash_orig_path, trash_orig_path) != 0) { + changed = TRUE; + g_free (file->details->trash_orig_path); + file->details->trash_orig_path = g_strdup (trash_orig_path); + } + + changed |= + caja_file_update_metadata_from_info (file, info); + + if (update_name) { + name = g_file_info_get_name (info); + if (file->details->name == NULL || + strcmp (eel_ref_str_peek (file->details->name), name) != 0) { + changed = TRUE; + + node = caja_directory_begin_file_name_change + (file->details->directory, file); + + eel_ref_str_unref (file->details->name); + if (eel_strcmp (eel_ref_str_peek (file->details->display_name), + name) == 0) { + file->details->name = eel_ref_str_ref (file->details->display_name); + } else { + file->details->name = eel_ref_str_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, + * caja_file_set_display_name() did + * not unset the display name. + */ + caja_file_clear_display_name (file); + } + + caja_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 (CajaFile *file, + GFileInfo *info) +{ + return update_info_internal (file, info, TRUE); +} + +gboolean +caja_file_update_info (CajaFile *file, + GFileInfo *info) +{ + return update_info_internal (file, info, FALSE); +} + +static gboolean +update_name_internal (CajaFile *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 = caja_directory_begin_file_name_change + (file->details->directory, file); + } + + eel_ref_str_unref (file->details->name); + file->details->name = eel_ref_str_new (name); + + if (!file->details->got_custom_display_name) { + caja_file_clear_display_name (file); + } + + if (in_directory) { + caja_directory_end_file_name_change + (file->details->directory, file, node); + } + + return TRUE; +} + +gboolean +caja_file_update_name (CajaFile *file, const char *name) +{ + gboolean ret; + + ret = update_name_internal (file, name, TRUE); + + if (ret) { + update_links_if_target (file); + } + + return ret; +} + +gboolean +caja_file_update_name_and_directory (CajaFile *file, + const char *name, + CajaDirectory *new_directory) +{ + CajaDirectory *old_directory; + FileMonitors *monitors; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + g_return_val_if_fail (CAJA_IS_DIRECTORY (file->details->directory), FALSE); + g_return_val_if_fail (!file->details->is_gone, FALSE); + g_return_val_if_fail (!caja_file_is_self_owned (file), FALSE); + g_return_val_if_fail (CAJA_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; + } + } + + caja_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 = caja_directory_remove_file_monitors (old_directory, file); + caja_directory_remove_file (old_directory, file); + + file->details->directory = caja_directory_ref (new_directory); + caja_directory_unref (old_directory); + + if (name) { + update_name_internal (file, name, FALSE); + } + + caja_directory_add_file (new_directory, file); + caja_directory_add_file_monitors (new_directory, file, monitors); + + add_to_link_hash_table (file); + + update_links_if_target (file); + + caja_file_unref (file); + + return TRUE; +} + +void +caja_file_set_directory (CajaFile *file, + CajaDirectory *new_directory) +{ + caja_file_update_name_and_directory (file, NULL, new_directory); +} + +static Knowledge +get_item_count (CajaFile *file, + guint *count) +{ + gboolean known, unreadable; + + known = caja_file_get_directory_item_count + (file, count, &unreadable); + if (!known) { + return UNKNOWN; + } + if (unreadable) { + return UNKNOWABLE; + } + return KNOWN; +} + +static Knowledge +get_size (CajaFile *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 mate-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 (CajaFile *file, + time_t *time_out, + CajaDateType 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; + } + + time = 0; + switch (type) { + case CAJA_DATE_TYPE_MODIFIED: + time = file->details->mtime; + break; + case CAJA_DATE_TYPE_ACCESSED: + time = file->details->atime; + break; + case CAJA_DATE_TYPE_TRASHED: + time = file->details->trash_time; + 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 + * mate-vfs is concerned, so "unknowable". + */ + if (time == 0) { + return UNKNOWABLE; + } + return KNOWN; +} + +static int +compare_directories_by_count (CajaFile *file_1, CajaFile *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 (CajaFile *file_1, CajaFile *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, size_2; + + 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 (CajaFile *file_1, CajaFile *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 = caja_file_is_directory (file_1); + is_directory_2 = caja_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 (CajaFile *file_1, CajaFile *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 = caja_file_peek_display_name (file_1); + name_2 = caja_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 = caja_file_peek_display_name_collation_key (file_1); + key_2 = caja_file_peek_display_name_collation_key (file_2); + compare = strcmp (key_1, key_2); + } + + return compare; +} + +static int +compare_by_directory_name (CajaFile *file_1, CajaFile *file_2) +{ + char *directory_1, *directory_2; + int compare; + + if (file_1->details->directory == file_2->details->directory) { + return 0; + } + + directory_1 = caja_file_get_parent_uri_for_display (file_1); + directory_2 = caja_file_get_parent_uri_for_display (file_2); + + compare = g_utf8_collate (directory_1, directory_2); + + g_free (directory_1); + g_free (directory_2); + + return compare; +} + +static gboolean +file_has_note (CajaFile *file) +{ + char *note; + gboolean res; + + note = caja_file_get_metadata (file, CAJA_METADATA_KEY_ANNOTATION, NULL); + res = note != NULL && note[0] != 0; + g_free (note); + + return res; +} + +static GList * +prepend_automatic_keywords (CajaFile *file, + GList *names) +{ + /* Prepend in reverse order. */ + CajaFile *parent; + + parent = caja_file_get_parent (file); + +#ifdef TRASH_IS_FAST_ENOUGH + if (caja_file_is_in_trash (file)) { + names = g_list_prepend + (names, g_strdup (CAJA_FILE_EMBLEM_NAME_TRASH)); + } +#endif + if (file_has_note (file)) { + names = g_list_prepend + (names, g_strdup (CAJA_FILE_EMBLEM_NAME_NOTE)); + } + + /* Trash files are assumed to be read-only, + * so we want to ignore them here. */ + if (!caja_file_can_write (file) && + !caja_file_is_in_trash (file) && + (parent == NULL || caja_file_can_write (parent))) { + names = g_list_prepend + (names, g_strdup (CAJA_FILE_EMBLEM_NAME_CANT_WRITE)); + } + if (!caja_file_can_read (file)) { + names = g_list_prepend + (names, g_strdup (CAJA_FILE_EMBLEM_NAME_CANT_READ)); + } + if (caja_file_is_symbolic_link (file)) { + names = g_list_prepend + (names, g_strdup (CAJA_FILE_EMBLEM_NAME_SYMBOLIC_LINK)); + } + + if (parent) { + caja_file_unref (parent); + } + + + return names; +} + +static void +fill_emblem_cache_if_needed (CajaFile *file) +{ + GList *node, *keywords; + char *scanner; + size_t length; + + if (file->details->compare_by_emblem_cache != NULL) { + /* Got a cache already. */ + return; + } + + keywords = caja_file_get_keywords (file); + + /* Add up the keyword string lengths */ + length = 1; + for (node = keywords; node != NULL; node = node->next) { + length += strlen ((const char *) node->data) + 1; + } + + /* Now that we know how large the cache struct needs to be, allocate it. */ + file->details->compare_by_emblem_cache = g_malloc (sizeof(CajaFileSortByEmblemCache) + length); + + /* Copy them into the cache. */ + scanner = file->details->compare_by_emblem_cache->emblem_keywords; + for (node = keywords; node != NULL; node = node->next) { + length = strlen ((const char *) node->data) + 1; + memcpy (scanner, (const char *) node->data, length); + scanner += length; + } + + /* Zero-terminate so we can tell where the list ends. */ + *scanner = 0; + + eel_g_list_free_deep (keywords); +} + +static int +compare_by_emblems (CajaFile *file_1, CajaFile *file_2) +{ + const char *keyword_cache_1, *keyword_cache_2; + size_t length; + int compare_result; + + fill_emblem_cache_if_needed (file_1); + fill_emblem_cache_if_needed (file_2); + + /* We ignore automatic emblems, and only sort by user-added keywords. */ + compare_result = 0; + keyword_cache_1 = file_1->details->compare_by_emblem_cache->emblem_keywords; + keyword_cache_2 = file_2->details->compare_by_emblem_cache->emblem_keywords; + for (; *keyword_cache_1 != '\0' && *keyword_cache_2 != '\0';) { + compare_result = g_utf8_collate (keyword_cache_1, keyword_cache_2); + if (compare_result != 0) { + return compare_result; + } + + /* Advance to the next keyword */ + length = strlen (keyword_cache_1); + keyword_cache_1 += length + 1; + keyword_cache_2 += length + 1; + } + + + /* One or both is now NULL. */ + if (*keyword_cache_1 != '\0') { + g_assert (*keyword_cache_2 == '\0'); + return -1; + } else if (*keyword_cache_2 != '\0') { + return +1; + } + + return 0; +} + +static int +compare_by_type (CajaFile *file_1, CajaFile *file_2) +{ + gboolean is_directory_1; + gboolean is_directory_2; + char *type_string_1; + 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 = caja_file_is_directory (file_1); + is_directory_2 = caja_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 (eel_ref_str_peek (file_1->details->mime_type), + eel_ref_str_peek (file_2->details->mime_type)) == 0) { + return 0; + } + + type_string_1 = caja_file_get_type_as_string (file_1); + type_string_2 = caja_file_get_type_as_string (file_2); + + result = g_utf8_collate (type_string_1, type_string_2); + + g_free (type_string_1); + g_free (type_string_2); + + return result; +} + +static int +compare_by_time (CajaFile *file_1, CajaFile *file_2, CajaDateType 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 (CajaFile *file_1, CajaFile *file_2) +{ + int compare; + + compare = compare_by_directory_name (file_1, file_2); + if (compare != 0) { + return compare; + } + return compare_by_display_name (file_1, file_2); +} + +static int +caja_file_compare_for_sort_internal (CajaFile *file_1, + CajaFile *file_2, + gboolean directories_first, + gboolean reversed) +{ + gboolean is_directory_1, is_directory_2; + + if (directories_first) { + is_directory_1 = caja_file_is_directory (file_1); + is_directory_2 = caja_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; +} + +/** + * caja_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 +caja_file_compare_for_sort (CajaFile *file_1, + CajaFile *file_2, + CajaFileSortType sort_type, + gboolean directories_first, + gboolean reversed) +{ + int result; + + if (file_1 == file_2) { + return 0; + } + + result = caja_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed); + + if (result == 0) { + switch (sort_type) { + case CAJA_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); + } + break; + case CAJA_FILE_SORT_BY_DIRECTORY: + result = compare_by_full_path (file_1, file_2); + break; + case CAJA_FILE_SORT_BY_SIZE: + /* Compare directory sizes ourselves, then if necessary + * use MateVFS 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 CAJA_FILE_SORT_BY_TYPE: + /* MateVFS 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 CAJA_FILE_SORT_BY_MTIME: + result = compare_by_time (file_1, file_2, CAJA_DATE_TYPE_MODIFIED); + if (result == 0) { + result = compare_by_full_path (file_1, file_2); + } + break; + case CAJA_FILE_SORT_BY_ATIME: + result = compare_by_time (file_1, file_2, CAJA_DATE_TYPE_ACCESSED); + if (result == 0) { + result = compare_by_full_path (file_1, file_2); + } + break; + case CAJA_FILE_SORT_BY_TRASHED_TIME: + result = compare_by_time (file_1, file_2, CAJA_DATE_TYPE_TRASHED); + if (result == 0) { + result = compare_by_full_path (file_1, file_2); + } + break; + case CAJA_FILE_SORT_BY_EMBLEMS: + /* MateVFS doesn't know squat about our emblems, so + * we handle comparing them here, before falling back + * to tie-breakers. + */ + result = compare_by_emblems (file_1, file_2); + 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 +caja_file_compare_for_sort_by_attribute_q (CajaFile *file_1, + CajaFile *file_2, + GQuark attribute, + gboolean directories_first, + gboolean reversed) +{ + int result; + + if (file_1 == file_2) { + return 0; + } + + /* Convert certain attributes into CajaFileSortTypes and use + * caja_file_compare_for_sort() + */ + if (attribute == 0 || attribute == attribute_name_q) { + return caja_file_compare_for_sort (file_1, file_2, + CAJA_FILE_SORT_BY_DISPLAY_NAME, + directories_first, + reversed); + } else if (attribute == attribute_size_q) { + return caja_file_compare_for_sort (file_1, file_2, + CAJA_FILE_SORT_BY_SIZE, + directories_first, + reversed); + } else if (attribute == attribute_type_q) { + return caja_file_compare_for_sort (file_1, file_2, + CAJA_FILE_SORT_BY_TYPE, + directories_first, + reversed); + } else if (attribute == attribute_modification_date_q || attribute == attribute_date_modified_q) { + return caja_file_compare_for_sort (file_1, file_2, + CAJA_FILE_SORT_BY_MTIME, + directories_first, + reversed); + } else if (attribute == attribute_accessed_date_q || attribute == attribute_date_accessed_q) { + return caja_file_compare_for_sort (file_1, file_2, + CAJA_FILE_SORT_BY_ATIME, + directories_first, + reversed); + } else if (attribute == attribute_trashed_on_q) { + return caja_file_compare_for_sort (file_1, file_2, + CAJA_FILE_SORT_BY_TRASHED_TIME, + directories_first, + reversed); + } else if (attribute == attribute_emblems_q) { + return caja_file_compare_for_sort (file_1, file_2, + CAJA_FILE_SORT_BY_EMBLEMS, + directories_first, + reversed); + } + + /* it is a normal attribute, compare by strings */ + + result = caja_file_compare_for_sort_internal (file_1, file_2, directories_first, reversed); + + if (result == 0) { + char *value_1; + char *value_2; + + value_1 = caja_file_get_string_attribute_q (file_1, + attribute); + value_2 = caja_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 +caja_file_compare_for_sort_by_attribute (CajaFile *file_1, + CajaFile *file_2, + const char *attribute, + gboolean directories_first, + gboolean reversed) +{ + return caja_file_compare_for_sort_by_attribute_q (file_1, file_2, + g_quark_from_string (attribute), + directories_first, + reversed); +} + + +/** + * caja_file_compare_name: + * @file: A file object + * @pattern: A string we are comparing it with + * + * Return value: result of a comparison of the file name and the given pattern, + * using the same sorting order as sort by name. + **/ +int +caja_file_compare_display_name (CajaFile *file, + const char *pattern) +{ + const char *name; + int result; + + g_return_val_if_fail (pattern != NULL, -1); + + name = caja_file_peek_display_name (file); + result = g_utf8_collate (name, pattern); + return result; +} + + +gboolean +caja_file_is_hidden_file (CajaFile *file) +{ + return file->details->is_hidden; +} + +gboolean +caja_file_is_backup_file (CajaFile *file) +{ + return file->details->is_backup; +} + +static gboolean +is_file_hidden (CajaFile *file) +{ + return file->details->directory->details->hidden_file_hash != NULL && + g_hash_table_lookup (file->details->directory->details->hidden_file_hash, + eel_ref_str_peek (file->details->name)) != NULL; + +} + +/** + * caja_file_should_show: + * @file: the file to check. + * @show_hidden: whether we want to show hidden files or not. + * @show_backup: whether we want to show backup files or not. + * + * Determines if a #CajaFile 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 +caja_file_should_show (CajaFile *file, + gboolean show_hidden, + gboolean show_backup, + gboolean show_foreign) +{ + /* Never hide any files in trash. */ + if (caja_file_is_in_trash (file)) { + return TRUE; + } else { + return (show_hidden || (!caja_file_is_hidden_file (file) && !is_file_hidden (file))) && + (show_backup || !caja_file_is_backup_file (file)) && + (show_foreign || !(caja_file_is_in_desktop (file) && caja_file_is_foreign_link (file))); + } +} + +gboolean +caja_file_is_home (CajaFile *file) +{ + GFile *dir; + + dir = file->details->directory->details->location; + if (dir == NULL) { + return FALSE; + } + + return caja_is_home_directory_file (dir, + eel_ref_str_peek (file->details->name)); +} + +gboolean +caja_file_is_in_desktop (CajaFile *file) +{ + if (file->details->directory->details->location) { + return caja_is_desktop_directory (file->details->directory->details->location); + } + return FALSE; + +} + +static gboolean +filter_hidden_and_backup_partition_callback (gpointer data, + gpointer callback_data) +{ + CajaFile *file; + FilterOptions options; + + file = CAJA_FILE (data); + options = GPOINTER_TO_INT (callback_data); + + return caja_file_should_show (file, + options & SHOW_HIDDEN, + options & SHOW_BACKUP, + TRUE); +} + +GList * +caja_file_list_filter_hidden_and_backup (GList *files, + gboolean show_hidden, + gboolean show_backup) +{ + GList *filtered_files; + GList *removed_files; + + /* FIXME bugzilla.gnome.org 40653: + * Eventually this should become a generic filtering thingy. + */ + + filtered_files = caja_file_list_copy (files); + filtered_files = eel_g_list_partition (filtered_files, + filter_hidden_and_backup_partition_callback, + GINT_TO_POINTER ((show_hidden ? SHOW_HIDDEN : 0) | + (show_backup ? SHOW_BACKUP : 0)), + &removed_files); + caja_file_list_free (removed_files); + + return filtered_files; +} + +char * +caja_file_get_metadata (CajaFile *file, + const char *key, + const char *default_metadata) +{ + guint id; + char *value; + + g_return_val_if_fail (key != NULL, g_strdup (default_metadata)); + g_return_val_if_fail (key[0] != '\0', g_strdup (default_metadata)); + + if (file == NULL || + file->details->metadata == NULL) { + return g_strdup (default_metadata); + } + + g_return_val_if_fail (CAJA_IS_FILE (file), g_strdup (default_metadata)); + + id = caja_metadata_get_id (key); + value = g_hash_table_lookup (file->details->metadata, GUINT_TO_POINTER (id)); + + if (value) { + return g_strdup (value); + } + return g_strdup (default_metadata); +} + +GList * +caja_file_get_metadata_list (CajaFile *file, + const char *key) +{ + GList *res; + guint id; + char **value; + int i; + + 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 (CAJA_IS_FILE (file), NULL); + + id = caja_metadata_get_id (key); + id |= METADATA_ID_IS_LIST_MASK; + + value = g_hash_table_lookup (file->details->metadata, GUINT_TO_POINTER (id)); + + if (value) { + res = NULL; + for (i = 0; value[i] != NULL; i++) { + res = g_list_prepend (res, g_strdup (value[i])); + } + return g_list_reverse (res); + } + + return NULL; +} + +void +caja_file_set_metadata (CajaFile *file, + const char *key, + const char *default_metadata, + const char *metadata) +{ + const char *val; + + g_return_if_fail (CAJA_IS_FILE (file)); + g_return_if_fail (key != NULL); + g_return_if_fail (key[0] != '\0'); + + val = metadata; + if (val == NULL) { + val = default_metadata; + } + + EEL_CALL_METHOD + (CAJA_FILE_CLASS, file, + set_metadata, (file, key, val)); +} + +void +caja_file_set_metadata_list (CajaFile *file, + const char *key, + GList *list) +{ + char **val; + int len, i; + GList *l; + + g_return_if_fail (CAJA_IS_FILE (file)); + g_return_if_fail (key != NULL); + g_return_if_fail (key[0] != '\0'); + + len = g_list_length (list); + val = g_new (char *, len + 1); + for (l = list, i = 0; l != NULL; l = l->next, i++) { + val[i] = l->data; + } + val[i] = NULL; + + EEL_CALL_METHOD + (CAJA_FILE_CLASS, file, + set_metadata_as_list, (file, key, val)); + + g_free (val); +} + + +gboolean +caja_file_get_boolean_metadata (CajaFile *file, + const char *key, + gboolean default_metadata) +{ + char *result_as_string; + gboolean result; + + g_return_val_if_fail (key != NULL, default_metadata); + g_return_val_if_fail (key[0] != '\0', default_metadata); + + if (file == NULL) { + return default_metadata; + } + + g_return_val_if_fail (CAJA_IS_FILE (file), default_metadata); + + result_as_string = caja_file_get_metadata + (file, key, default_metadata ? "true" : "false"); + g_assert (result_as_string != NULL); + + if (g_ascii_strcasecmp (result_as_string, "true") == 0) { + result = TRUE; + } else if (g_ascii_strcasecmp (result_as_string, "false") == 0) { + result = FALSE; + } else { + g_error ("boolean metadata with value other than true or false"); + result = default_metadata; + } + + g_free (result_as_string); + return result; +} + +int +caja_file_get_integer_metadata (CajaFile *file, + const char *key, + int default_metadata) +{ + char *result_as_string; + char default_as_string[32]; + int result; + char c; + + g_return_val_if_fail (key != NULL, default_metadata); + g_return_val_if_fail (key[0] != '\0', default_metadata); + + if (file == NULL) { + return default_metadata; + } + g_return_val_if_fail (CAJA_IS_FILE (file), default_metadata); + + g_snprintf (default_as_string, sizeof (default_as_string), "%d", default_metadata); + result_as_string = caja_file_get_metadata + (file, key, default_as_string); + + /* Normally we can't get a a NULL, but we check for it here to + * handle the oddball case of a non-existent directory. + */ + if (result_as_string == NULL) { + result = default_metadata; + } else { + if (sscanf (result_as_string, " %d %c", &result, &c) != 1) { + result = default_metadata; + } + g_free (result_as_string); + } + + return result; +} + +static gboolean +get_time_from_time_string (const char *time_string, + time_t *time) +{ + long scanned_time; + char c; + + g_assert (time != NULL); + + /* Only accept string if it has one integer with nothing + * afterwards. + */ + if (time_string == NULL || + sscanf (time_string, "%ld%c", &scanned_time, &c) != 1) { + return FALSE; + } + *time = (time_t) scanned_time; + return TRUE; +} + +time_t +caja_file_get_time_metadata (CajaFile *file, + const char *key) +{ + time_t time; + char *time_string; + + time_string = caja_file_get_metadata (file, key, NULL); + if (!get_time_from_time_string (time_string, &time)) { + time = UNDEFINED_TIME; + } + g_free (time_string); + + return time; +} + +void +caja_file_set_time_metadata (CajaFile *file, + const char *key, + time_t time) +{ + char time_str[21]; + char *metadata; + + if (time != UNDEFINED_TIME) { + /* 2^64 turns out to be 20 characters */ + g_snprintf (time_str, 20, "%ld", (long int)time); + time_str[20] = '\0'; + metadata = time_str; + } else { + metadata = NULL; + } + + caja_file_set_metadata (file, key, NULL, metadata); +} + + +void +caja_file_set_boolean_metadata (CajaFile *file, + const char *key, + gboolean default_metadata, + gboolean metadata) +{ + g_return_if_fail (CAJA_IS_FILE (file)); + g_return_if_fail (key != NULL); + g_return_if_fail (key[0] != '\0'); + + caja_file_set_metadata (file, key, + default_metadata ? "true" : "false", + metadata ? "true" : "false"); +} + +void +caja_file_set_integer_metadata (CajaFile *file, + const char *key, + int default_metadata, + int metadata) +{ + char value_as_string[32]; + char default_as_string[32]; + + g_return_if_fail (CAJA_IS_FILE (file)); + g_return_if_fail (key != NULL); + g_return_if_fail (key[0] != '\0'); + + g_snprintf (value_as_string, sizeof (value_as_string), "%d", metadata); + g_snprintf (default_as_string, sizeof (default_as_string), "%d", default_metadata); + + caja_file_set_metadata (file, key, + default_as_string, value_as_string); +} + +static const char * +caja_file_peek_display_name_collation_key (CajaFile *file) +{ + const char *res; + + res = file->details->display_name_collation_key; + if (res == NULL) + res = ""; + + return res; +} + +static const char * +caja_file_peek_display_name (CajaFile *file) +{ + const char *name; + char *escaped_name; + + /* Default to display name based on filename if its not set yet */ + + if (file->details->display_name == NULL) { + name = eel_ref_str_peek (file->details->name); + if (g_utf8_validate (name, -1, NULL)) { + caja_file_set_display_name (file, + name, + NULL, + FALSE); + } else { + escaped_name = g_uri_escape_string (name, G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE); + caja_file_set_display_name (file, + escaped_name, + NULL, + FALSE); + g_free (escaped_name); + } + } + + return eel_ref_str_peek (file->details->display_name); +} + +char * +caja_file_get_display_name (CajaFile *file) +{ + return g_strdup (caja_file_peek_display_name (file)); +} + +char * +caja_file_get_edit_name (CajaFile *file) +{ + const char *res; + + res = eel_ref_str_peek (file->details->edit_name); + if (res == NULL) + res = ""; + + return g_strdup (res); +} + +char * +caja_file_get_name (CajaFile *file) +{ + return g_strdup (eel_ref_str_peek (file->details->name)); +} + +/** + * caja_file_get_description: + * @file: a #CajaFile. + * + * Gets the standard::description key from @file, if + * it has been cached. + * + * Returns: a string containing the value of the standard::description + * key, or %NULL. + */ +char * +caja_file_get_description (CajaFile *file) +{ + return g_strdup (file->details->description); +} + +void +caja_file_monitor_add (CajaFile *file, + gconstpointer client, + CajaFileAttributes attributes) +{ + g_return_if_fail (CAJA_IS_FILE (file)); + g_return_if_fail (client != NULL); + + EEL_CALL_METHOD + (CAJA_FILE_CLASS, file, + monitor_add, (file, client, attributes)); +} + +void +caja_file_monitor_remove (CajaFile *file, + gconstpointer client) +{ + g_return_if_fail (CAJA_IS_FILE (file)); + g_return_if_fail (client != NULL); + + EEL_CALL_METHOD + (CAJA_FILE_CLASS, file, + monitor_remove, (file, client)); +} + +gboolean +caja_file_is_launcher (CajaFile *file) +{ + return file->details->is_launcher; +} + +gboolean +caja_file_is_foreign_link (CajaFile *file) +{ + return file->details->is_foreign_link; +} + +gboolean +caja_file_is_trusted_link (CajaFile *file) +{ + return file->details->is_trusted_link; +} + +gboolean +caja_file_has_activation_uri (CajaFile *file) +{ + return file->details->activation_uri != NULL; +} + + +/* 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 caja + * xml link file. + */ +char * +caja_file_get_activation_uri (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + if (file->details->activation_uri != NULL) { + return g_strdup (file->details->activation_uri); + } + + return caja_file_get_uri (file); +} + +GFile * +caja_file_get_activation_location (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + if (file->details->activation_uri != NULL) { + return g_file_new_for_uri (file->details->activation_uri); + } + + return caja_file_get_location (file); +} + + +char * +caja_file_get_drop_target_uri (CajaFile *file) +{ + char *uri, *target_uri; + GFile *location; + CajaDesktopLink *link; + + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + if (CAJA_IS_DESKTOP_ICON_FILE (file)) { + link = caja_desktop_icon_file_get_link (CAJA_DESKTOP_ICON_FILE (file)); + + if (link != NULL) { + location = caja_desktop_link_get_activation_location (link); + g_object_unref (link); + if (location != NULL) { + uri = g_file_get_uri (location); + g_object_unref (location); + return uri; + } + } + } + + uri = caja_file_get_uri (file); + + /* Check for Caja link */ + if (caja_file_is_caja_link (file)) { + location = caja_file_get_location (file); + /* FIXME bugzilla.gnome.org 43020: This does sync. I/O and works only locally. */ + if (g_file_is_native (location)) { + target_uri = caja_link_local_get_link_uri (uri); + if (target_uri != NULL) { + g_free (uri); + uri = target_uri; + } + } + g_object_unref (location); + } + + return uri; +} + +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 (CajaFile *file) +{ + char *custom_icon_uri; + char *uri; + char *dir_uri; + + uri = caja_file_get_metadata (file, CAJA_METADATA_KEY_CUSTOM_ICON, NULL); + if (uri != NULL && + caja_file_is_directory (file) && + is_uri_relative (uri)) { + dir_uri = caja_file_get_uri (file); + custom_icon_uri = g_build_filename (dir_uri, uri, NULL); + g_free (dir_uri); + g_free (uri); + } else { + custom_icon_uri = uri; + } + return custom_icon_uri; +} + +static GIcon * +get_custom_icon (CajaFile *file) +{ + char *custom_icon_uri; + GFile *icon_file; + GIcon *icon; + + if (file == NULL) { + return NULL; + } + + icon = NULL; + + /* Metadata takes precedence */ + 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 && file->details->got_link_info && file->details->custom_icon != NULL) { + if (g_path_is_absolute (file->details->custom_icon)) { + icon_file = g_file_new_for_path (file->details->custom_icon); + icon = g_file_icon_new (icon_file); + g_object_unref (icon_file); + } else { + icon = g_themed_icon_new (file->details->custom_icon); + } + } + + return icon; +} + + +static guint cached_thumbnail_limit; +int cached_thumbnail_size; +static int show_image_thumbs; + +GFilesystemPreviewType +caja_file_get_filesystem_use_preview (CajaFile *file) +{ + GFilesystemPreviewType use_preview; + CajaFile *parent; + + parent = caja_file_get_parent (file); + if (parent != NULL) { + use_preview = parent->details->filesystem_use_preview; + g_object_unref (parent); + } else { + use_preview = 0; + } + + return use_preview; +} + +gboolean +caja_file_should_show_thumbnail (CajaFile *file) +{ + const char *mime_type; + GFilesystemPreviewType use_preview; + + use_preview = caja_file_get_filesystem_use_preview (file); + + mime_type = eel_ref_str_peek (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 (caja_thumbnail_is_mimetype_limited_by_size (mime_type) && + file->details->thumbnail_path == NULL && + caja_file_get_size (file) > cached_thumbnail_limit) { + return FALSE; + } + + if (show_image_thumbs == CAJA_SPEED_TRADEOFF_ALWAYS) { + if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) { + return FALSE; + } else { + return TRUE; + } + } else if (show_image_thumbs == CAJA_SPEED_TRADEOFF_NEVER) { + return FALSE; + } else { + if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) { + /* file system says to never thumbnail anything */ + return FALSE; + } 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 { + /* only local files */ + return caja_file_is_local (file); + } + } + + return FALSE; +} + +static void +prepend_icon_name (const char *name, + GThemedIcon *icon) +{ + g_themed_icon_prepend_name(icon, name); +} + +GIcon * +caja_file_get_gicon (CajaFile *file, + CajaFileIconFlags flags) +{ + const char * const * names; + const char *name; + GPtrArray *prepend_array; + GMount *mount; + GIcon *icon, *mount_icon = NULL, *emblemed_icon; + GEmblem *emblem; + int i; + gboolean is_folder = FALSE, is_preview = FALSE, is_inode_directory = FALSE; + + if (file == NULL) { + return NULL; + } + + if (file->details->icon) { + icon = NULL; + + /* fetch the mount icon here, we'll use it later */ + if (flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON || + flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM) { + mount = caja_file_get_mount (file); + + if (mount != NULL) { + mount_icon = g_mount_get_icon (mount); + g_object_unref (mount); + } + } + + if (((flags & CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT) || + (flags & CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT) || + (flags & CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER) || + (flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON) || + (flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM) || + ((flags & CAJA_FILE_ICON_FLAGS_IGNORE_VISITING) == 0 && + caja_file_has_open_window (file))) && + G_IS_THEMED_ICON (file->details->icon)) { + names = g_themed_icon_get_names (G_THEMED_ICON (file->details->icon)); + prepend_array = g_ptr_array_new (); + + for (i = 0; names[i] != NULL; i++) { + name = names[i]; + + if (strcmp (name, "folder") == 0) { + is_folder = TRUE; + } + if (strcmp (name, "inode-directory") == 0) { + is_inode_directory = TRUE; + } + if (strcmp (name, "text-x-generic") == 0 && + (flags & CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT)) { + is_preview = TRUE; + } + } + + /* Here, we add icons in reverse order of precedence, + * because they are later prepended */ + if (is_preview) { + g_ptr_array_add (prepend_array, "text-x-preview"); + } + + /* "folder" should override "inode-directory", not the other way around */ + if (is_inode_directory) { + g_ptr_array_add (prepend_array, "folder"); + } + if (is_folder && (flags & CAJA_FILE_ICON_FLAGS_FOR_OPEN_FOLDER)) { + g_ptr_array_add (prepend_array, "folder-open"); + } + if (is_folder && + (flags & CAJA_FILE_ICON_FLAGS_IGNORE_VISITING) == 0 && + caja_file_has_open_window (file)) { + g_ptr_array_add (prepend_array, "folder-visiting"); + } + if (is_folder && + (flags & CAJA_FILE_ICON_FLAGS_FOR_DRAG_ACCEPT)) { + g_ptr_array_add (prepend_array, "folder-drag-accept"); + } + + if (prepend_array->len) { + /* When constructing GThemed Icon, pointers from the array + * are reused, but not the array itself, so the cast is safe */ + icon = g_themed_icon_new_from_names ((char**) names, -1); + g_ptr_array_foreach (prepend_array, (GFunc) prepend_icon_name, icon); + } + + g_ptr_array_free (prepend_array, TRUE); + } + + if (icon == NULL) { + icon = g_object_ref (file->details->icon); + } + + if ((flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON) && + mount_icon != NULL) { + g_object_unref (icon); + icon = mount_icon; + } else if ((flags & CAJA_FILE_ICON_FLAGS_USE_MOUNT_ICON_AS_EMBLEM) && + mount_icon != NULL && !g_icon_equal (mount_icon, icon)) { + + emblem = g_emblem_new (mount_icon); + emblemed_icon = g_emblemed_icon_new (icon, emblem); + + g_object_unref (emblem); + g_object_unref (icon); + g_object_unref (mount_icon); + + icon = emblemed_icon; + } else if (mount_icon != NULL) { + g_object_unref (mount_icon); + } + + return icon; + } + + return g_themed_icon_new ("text-x-generic"); +} + +static GIcon * +get_default_file_icon (CajaFileIconFlags flags) +{ + static GIcon *fallback_icon = NULL; + static GIcon *fallback_icon_preview = NULL; + if (fallback_icon == NULL) { + fallback_icon = g_themed_icon_new ("text-x-generic"); + fallback_icon_preview = g_themed_icon_new ("text-x-preview"); + g_themed_icon_append_name (G_THEMED_ICON (fallback_icon_preview), "text-x-generic"); + } + if (flags & CAJA_FILE_ICON_FLAGS_EMBEDDING_TEXT) { + return fallback_icon_preview; + } else { + return fallback_icon; + } +} + +CajaIconInfo * +caja_file_get_icon (CajaFile *file, + int size, + CajaFileIconFlags flags) +{ + CajaIconInfo *icon; + GIcon *gicon; + GdkPixbuf *raw_pixbuf, *scaled_pixbuf; + int modified_size; + + if (file == NULL) { + return NULL; + } + + gicon = get_custom_icon (file); + if (gicon) { + icon = caja_icon_info_lookup (gicon, size); + g_object_unref (gicon); + return icon; + } + + if (flags & CAJA_FILE_ICON_FLAGS_FORCE_THUMBNAIL_SIZE) { + modified_size = size; + } else { + modified_size = size * cached_thumbnail_size / CAJA_ICON_SIZE_STANDARD; + } + if (flags & CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS && + caja_file_should_show_thumbnail (file)) { + if (file->details->thumbnail) { + int w, h, s; + double scale; + + raw_pixbuf = g_object_ref (file->details->thumbnail); + + w = gdk_pixbuf_get_width (raw_pixbuf); + h = gdk_pixbuf_get_height (raw_pixbuf); + + s = MAX (w, h); + /* Don't scale up small thumbnails in the standard view */ + if (s <= cached_thumbnail_size) { + scale = (double)size / CAJA_ICON_SIZE_STANDARD; + } + else { + scale = (double)modified_size / s; + } + /* Make sure that icons don't get smaller than CAJA_ICON_SIZE_SMALLEST */ + if (s*scale <= CAJA_ICON_SIZE_SMALLEST) { + scale = (double) CAJA_ICON_SIZE_SMALLEST / s; + } + + scaled_pixbuf = gdk_pixbuf_scale_simple (raw_pixbuf, + w * scale, h * scale, + GDK_INTERP_BILINEAR); + + /* We don't want frames around small icons */ + if (!gdk_pixbuf_get_has_alpha(raw_pixbuf) || s >= 128) { + caja_thumbnail_frame_image (&scaled_pixbuf); + } + g_object_unref (raw_pixbuf); + + /* Don't scale up if more than 25%, then read the original + image instead. We don't want to compare to exactly 100%, + since the zoom level 150% gives thumbnails at 144, which is + ok to scale up from 128. */ + if (modified_size > 128*1.25 && + !file->details->thumbnail_wants_original && + caja_can_thumbnail_internally (file)) { + /* Invalidate if we resize upward */ + file->details->thumbnail_wants_original = TRUE; + caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_THUMBNAIL); + } + + icon = caja_icon_info_new_for_pixbuf (scaled_pixbuf); + g_object_unref (scaled_pixbuf); + return icon; + } else if (file->details->thumbnail_path == NULL && + file->details->can_read && + !file->details->is_thumbnailing && + !file->details->thumbnailing_failed) { + if (caja_can_thumbnail (file)) { + caja_create_thumbnail (file); + } + } + } + + if (file->details->is_thumbnailing && + flags & CAJA_FILE_ICON_FLAGS_USE_THUMBNAILS) + gicon = g_themed_icon_new (ICON_NAME_THUMBNAIL_LOADING); + else + gicon = caja_file_get_gicon (file, flags); + + if (gicon) { + icon = caja_icon_info_lookup (gicon, size); + if (caja_icon_info_is_fallback (icon)) { + g_object_unref (icon); + icon = caja_icon_info_lookup (get_default_file_icon (flags), size); + } + g_object_unref (gicon); + return icon; + } else { + return caja_icon_info_lookup (get_default_file_icon (flags), size); + } +} + +GdkPixbuf * +caja_file_get_icon_pixbuf (CajaFile *file, + int size, + gboolean force_size, + CajaFileIconFlags flags) +{ + CajaIconInfo *info; + GdkPixbuf *pixbuf; + + info = caja_file_get_icon (file, size, flags); + if (force_size) { + pixbuf = caja_icon_info_get_pixbuf_at_size (info, size); + } else { + pixbuf = caja_icon_info_get_pixbuf (info); + } + g_object_unref (info); + + return pixbuf; +} + +char * +caja_file_get_custom_icon (CajaFile *file) +{ + char *custom_icon; + + if (file == NULL) { + return NULL; + } + + /* Metadata takes precedence */ + custom_icon = get_custom_icon_metadata_uri (file); + + if (custom_icon == NULL && file->details->got_link_info) { + custom_icon = g_strdup (file->details->custom_icon); + } + + return custom_icon; +} + + +gboolean +caja_file_get_date (CajaFile *file, + CajaDateType date_type, + time_t *date) +{ + if (date != NULL) { + *date = 0; + } + + g_return_val_if_fail (date_type == CAJA_DATE_TYPE_CHANGED + || date_type == CAJA_DATE_TYPE_ACCESSED + || date_type == CAJA_DATE_TYPE_MODIFIED + || date_type == CAJA_DATE_TYPE_TRASHED + || date_type == CAJA_DATE_TYPE_PERMISSIONS_CHANGED, FALSE); + + if (file == NULL) { + return FALSE; + } + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (CAJA_FILE_CLASS, file, + get_date, (file, date_type, date)); +} + +static char * +caja_file_get_where_string (CajaFile *file) +{ + if (file == NULL) { + return NULL; + } + + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (CAJA_FILE_CLASS, file, + get_where_string, (file)); +} + +static const char *TODAY_TIME_FORMATS [] = { + /* Today, use special word. + * strftime patterns preceeded with the widest + * possible resulting string for that pattern. + * + * Note to localizers: You can look at man strftime + * for details on the format, but you should only use + * the specifiers from the C standard, not extensions. + * These include "%" followed by one of + * "aAbBcdHIjmMpSUwWxXyYZ". There are two extensions + * in the Caja version of strftime that can be + * used (and match GNU extensions). Putting a "-" + * between the "%" and any numeric directive will turn + * off zero padding, and putting a "_" there will use + * space padding instead of zero padding. + */ + N_("today at 00:00:00 PM"), + N_("today at %-I:%M:%S %p"), + + N_("today at 00:00 PM"), + N_("today at %-I:%M %p"), + + N_("today, 00:00 PM"), + N_("today, %-I:%M %p"), + + N_("today"), + N_("today"), + + NULL +}; + +static const char *YESTERDAY_TIME_FORMATS [] = { + /* Yesterday, use special word. + * Note to localizers: Same issues as "today" string. + */ + N_("yesterday at 00:00:00 PM"), + N_("yesterday at %-I:%M:%S %p"), + + N_("yesterday at 00:00 PM"), + N_("yesterday at %-I:%M %p"), + + N_("yesterday, 00:00 PM"), + N_("yesterday, %-I:%M %p"), + + N_("yesterday"), + N_("yesterday"), + + NULL +}; + +static const char *CURRENT_WEEK_TIME_FORMATS [] = { + /* Current week, include day of week. + * Note to localizers: Same issues as "today" string. + * The width measurement templates correspond to + * the day/month name with the most letters. + */ + N_("Wednesday, September 00 0000 at 00:00:00 PM"), + N_("%A, %B %-d %Y at %-I:%M:%S %p"), + + N_("Mon, Oct 00 0000 at 00:00:00 PM"), + N_("%a, %b %-d %Y at %-I:%M:%S %p"), + + N_("Mon, Oct 00 0000 at 00:00 PM"), + N_("%a, %b %-d %Y at %-I:%M %p"), + + N_("Oct 00 0000 at 00:00 PM"), + N_("%b %-d %Y at %-I:%M %p"), + + N_("Oct 00 0000, 00:00 PM"), + N_("%b %-d %Y, %-I:%M %p"), + + N_("00/00/00, 00:00 PM"), + N_("%m/%-d/%y, %-I:%M %p"), + + N_("00/00/00"), + N_("%m/%d/%y"), + + NULL +}; + +static char * +caja_file_fit_date_as_string (CajaFile *file, + CajaDateType date_type, + int width, + CajaWidthMeasureCallback measure_callback, + CajaTruncateCallback truncate_callback, + void *measure_context) +{ + time_t file_time_raw; + struct tm *file_time; + const char **formats; + const char *width_template; + const char *format; + char *date_string; + char *result; + GDate *today; + GDate *file_date; + guint32 file_date_age; + int i; + + if (!caja_file_get_date (file, date_type, &file_time_raw)) { + return NULL; + } + + file_time = localtime (&file_time_raw); + + if (date_format_pref == CAJA_DATE_FORMAT_LOCALE) { + return eel_strdup_strftime ("%c", file_time); + } else if (date_format_pref == CAJA_DATE_FORMAT_ISO) { + return eel_strdup_strftime ("%Y-%m-%d %H:%M:%S", file_time); + } + + file_date = eel_g_date_new_tm (file_time); + + today = g_date_new (); + g_date_set_time_t (today, time (NULL)); + + /* Overflow results in a large number; fine for our purposes. */ + file_date_age = (g_date_get_julian (today) - + g_date_get_julian (file_date)); + + g_date_free (file_date); + g_date_free (today); + + /* Format varies depending on how old the date is. This minimizes + * the length (and thus clutter & complication) of typical dates + * while providing sufficient detail for recent dates to make + * them maximally understandable at a glance. Keep all format + * strings separate rather than combining bits & pieces for + * internationalization's sake. + */ + + if (file_date_age == 0) { + formats = TODAY_TIME_FORMATS; + } else if (file_date_age == 1) { + formats = YESTERDAY_TIME_FORMATS; + } else if (file_date_age < 7) { + formats = CURRENT_WEEK_TIME_FORMATS; + } else { + formats = CURRENT_WEEK_TIME_FORMATS; + } + + /* Find the date format that just fits the required width. Instead of measuring + * the resulting string width directly, measure the width of a template that represents + * the widest possible version of a date in a given format. This is done by using M, m + * and 0 for the variable letters/digits respectively. + */ + format = NULL; + + for (i = 0; ; i += 2) { + width_template = (formats [i] ? _(formats [i]) : NULL); + if (width_template == NULL) { + /* no more formats left */ + g_assert (format != NULL); + + /* Can't fit even the shortest format -- return an ellipsized form in the + * shortest format + */ + + date_string = eel_strdup_strftime (format, file_time); + + if (truncate_callback == NULL) { + return date_string; + } + + result = (* truncate_callback) (date_string, width, measure_context); + g_free (date_string); + return result; + } + + format = _(formats [i + 1]); + + if (measure_callback == NULL) { + /* don't care about fitting the width */ + break; + } + + if ((* measure_callback) (width_template, measure_context) <= width) { + /* The template fits, this is the format we can fit. */ + break; + } + } + + return eel_strdup_strftime (format, file_time); + +} + +/** + * caja_file_fit_modified_date_as_string: + * + * Get a user-displayable string representing a file modification date, + * truncated to @width using the measuring and truncating callbacks. + * @file: CajaFile representing the file in question. + * @width: The desired resulting string width. + * @measure_callback: The callback used to measure the string width. + * @truncate_callback: The callback used to truncate the string to a desired width. + * @measure_context: Data neede when measuring and truncating. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +char * +caja_file_fit_modified_date_as_string (CajaFile *file, + int width, + CajaWidthMeasureCallback measure_callback, + CajaTruncateCallback truncate_callback, + void *measure_context) +{ + return caja_file_fit_date_as_string (file, CAJA_DATE_TYPE_MODIFIED, + width, measure_callback, truncate_callback, measure_context); +} + +static char * +caja_file_get_trash_original_file_parent_as_string (CajaFile *file) +{ + CajaFile *orig_file, *parent; + GFile *location; + char *filename; + + if (file->details->trash_orig_path != NULL) { + orig_file = caja_file_get_trash_original_file (file); + parent = caja_file_get_parent (orig_file); + location = caja_file_get_location (parent); + + filename = g_file_get_parse_name (location); + + g_object_unref (location); + caja_file_unref (parent); + caja_file_unref (orig_file); + + return filename; + } + + return NULL; +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +caja_file_get_date_as_string (CajaFile *file, CajaDateType date_type) +{ + return caja_file_fit_date_as_string (file, date_type, + 0, NULL, NULL, NULL); +} + +static CajaSpeedTradeoffValue show_directory_item_count; +static CajaSpeedTradeoffValue show_text_in_icons; + +static void +show_text_in_icons_changed_callback (gpointer callback_data) +{ + show_text_in_icons = eel_preferences_get_enum (CAJA_PREFERENCES_SHOW_TEXT_IN_ICONS); +} + +static void +show_directory_item_count_changed_callback (gpointer callback_data) +{ + show_directory_item_count = eel_preferences_get_enum (CAJA_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS); +} + +static gboolean +get_speed_tradeoff_preference_for_file (CajaFile *file, CajaSpeedTradeoffValue value) +{ + GFilesystemPreviewType use_preview; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + use_preview = caja_file_get_filesystem_use_preview (file); + + if (value == CAJA_SPEED_TRADEOFF_ALWAYS) { + if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) { + return FALSE; + } else { + return TRUE; + } + } + + if (value == CAJA_SPEED_TRADEOFF_NEVER) { + return FALSE; + } + + g_assert (value == CAJA_SPEED_TRADEOFF_LOCAL_ONLY); + + if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) { + /* file system says to never preview anything */ + return FALSE; + } 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 { + /* only local files */ + return caja_file_is_local (file); + } +} + +gboolean +caja_file_should_show_directory_item_count (CajaFile *file) +{ + static gboolean show_directory_item_count_callback_added = FALSE; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + if (file->details->mime_type && + strcmp (eel_ref_str_peek (file->details->mime_type), "x-directory/smb-share") == 0) { + return FALSE; + } + + /* Add the callback once for the life of our process */ + if (!show_directory_item_count_callback_added) { + eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_DIRECTORY_ITEM_COUNTS, + show_directory_item_count_changed_callback, + NULL); + show_directory_item_count_callback_added = TRUE; + + /* Peek for the first time */ + show_directory_item_count_changed_callback (NULL); + } + + return get_speed_tradeoff_preference_for_file (file, show_directory_item_count); +} + +gboolean +caja_file_should_show_type (CajaFile *file) +{ + char *uri; + gboolean ret; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + uri = caja_file_get_uri (file); + ret = ((strcmp (uri, "computer:///") != 0) && + (strcmp (uri, "network:///") != 0) && + (strcmp (uri, "smb:///") != 0)); + g_free (uri); + + return ret; +} + +gboolean +caja_file_should_get_top_left_text (CajaFile *file) +{ + static gboolean show_text_in_icons_callback_added = FALSE; + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + /* Add the callback once for the life of our process */ + if (!show_text_in_icons_callback_added) { + eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_TEXT_IN_ICONS, + show_text_in_icons_changed_callback, + NULL); + show_text_in_icons_callback_added = TRUE; + + /* Peek for the first time */ + show_text_in_icons_changed_callback (NULL); + } + + if (show_text_in_icons == CAJA_SPEED_TRADEOFF_ALWAYS) { + return TRUE; + } + + if (show_text_in_icons == CAJA_SPEED_TRADEOFF_NEVER) { + return FALSE; + } + + return get_speed_tradeoff_preference_for_file (file, show_text_in_icons); +} + +/** + * caja_file_get_directory_item_count + * + * Get the number of items in a directory. + * @file: CajaFile 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 +caja_file_get_directory_item_count (CajaFile *file, + guint *count, + gboolean *count_unreadable) +{ + if (count != NULL) { + *count = 0; + } + if (count_unreadable != NULL) { + *count_unreadable = FALSE; + } + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + if (!caja_file_is_directory (file)) { + return FALSE; + } + + if (!caja_file_should_show_directory_item_count (file)) { + return FALSE; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (CAJA_FILE_CLASS, file, + get_item_count, (file, count, count_unreadable)); +} + +/** + * caja_file_get_deep_counts + * + * Get the statistics about items inside a directory. + * @file: CajaFile 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 + * caja_file_should_show_directory_item_count returns FALSE + * for this file. + * + * Returns: Status to indicate whether sizes are available. + * + **/ +CajaRequestStatus +caja_file_get_deep_counts (CajaFile *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 (CAJA_IS_FILE (file), CAJA_REQUEST_DONE); + + if (!force && !caja_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 = CAJA_REQUEST_NOT_STARTED; + return file->details->deep_counts_status; + } + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (CAJA_FILE_CLASS, file, + get_deep_counts, (file, + directory_count, + file_count, + unreadable_directory_count, + total_size)); +} + +void +caja_file_recompute_deep_counts (CajaFile *file) +{ + if (file->details->deep_counts_status != CAJA_REQUEST_IN_PROGRESS) { + file->details->deep_counts_status = CAJA_REQUEST_NOT_STARTED; + if (file->details->directory != NULL) { + caja_directory_add_file_to_work_queue (file->details->directory, file); + caja_directory_async_state_changed (file->details->directory); + } + } +} + + +/** + * caja_file_get_directory_item_mime_types + * + * Get the list of mime-types present in a directory. + * @file: CajaFile representing a directory. It is an error to + * call this function on a file that is not a directory. + * @mime_list: Place to put the list of mime-types. + * + * Returns: TRUE if mime-type list is available. + * + **/ +gboolean +caja_file_get_directory_item_mime_types (CajaFile *file, + GList **mime_list) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + g_return_val_if_fail (mime_list != NULL, FALSE); + + if (!caja_file_is_directory (file) + || !file->details->got_mime_list) { + *mime_list = NULL; + return FALSE; + } + + *mime_list = eel_g_str_list_copy (file->details->mime_list); + return TRUE; +} + +gboolean +caja_file_can_get_size (CajaFile *file) +{ + return file->details->size == -1; +} + + +/** + * caja_file_get_size + * + * Get the file size. + * @file: CajaFile representing the file in question. + * + * Returns: Size in bytes. + * + **/ +goffset +caja_file_get_size (CajaFile *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 +caja_file_get_mtime (CajaFile *file) +{ + return file->details->mtime; +} + + +static void +set_attributes_get_info_callback (GObject *source_object, + GAsyncResult *res, + gpointer callback_data) +{ + CajaFileOperation *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 (caja_file_update_info (op->file, new_info)) { + caja_file_changed (op->file); + } + g_object_unref (new_info); + } + caja_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) +{ + CajaFileOperation *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), + CAJA_FILE_DEFAULT_ATTRIBUTES, + 0, + G_PRIORITY_DEFAULT, + op->cancellable, + set_attributes_get_info_callback, op); + } else { + caja_file_operation_complete (op, NULL, error); + g_error_free (error); + } +} + +void +caja_file_set_attributes (CajaFile *file, + GFileInfo *attributes, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + CajaFileOperation *op; + GFile *location; + + op = caja_file_operation_new (file, callback, callback_data); + + location = caja_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); +} + + +/** + * caja_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 +caja_file_can_get_permissions (CajaFile *file) +{ + return file->details->has_permissions; +} + +/** + * caja_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 +caja_file_can_set_permissions (CajaFile *file) +{ + uid_t user_id; + + if (file->details->uid != -1 && + caja_file_is_local (file)) { + /* Check the user. */ + user_id = geteuid(); + + /* Owner is allowed to set permissions. */ + if (user_id == (uid_t) file->details->uid) { + return TRUE; + } + + /* Root is also allowed to set permissions. */ + if (user_id == 0) { + 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 +caja_file_get_permissions (CajaFile *file) +{ + g_return_val_if_fail (caja_file_can_get_permissions (file), 0); + + return file->details->permissions; +} + +/** + * caja_file_set_permissions: + * + * Change a file's permissions. This should only be called if + * caja_file_can_set_permissions returned TRUE. + * + * @file: CajaFile representing the file in question. + * @new_permissions: New permissions value. This is the whole + * set of permissions, not a delta. + **/ +void +caja_file_set_permissions (CajaFile *file, + guint32 new_permissions, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + GFileInfo *info; + GError *error; + + if (!caja_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". + */ + caja_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; + } + + info = g_file_info_new (); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, new_permissions); + caja_file_set_attributes (file, info, callback, callback_data); + g_object_unref (info); +} + +/** + * caja_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 +caja_file_can_get_selinux_context (CajaFile *file) +{ + return file->details->selinux_context != NULL; +} + + +/** + * caja_file_get_selinux_context: + * + * Get a user-displayable string representing a file's selinux + * context + * @file: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +char * +caja_file_get_selinux_context (CajaFile *file) +{ + char *translated; + char *raw; + + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + if (!caja_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 *locale_string, *part_before_comma, *capitalized_login_name, *real_name; + + if (gecos == NULL) { + return NULL; + } + + locale_string = eel_str_strip_substring_and_after (gecos, ","); + if (!g_utf8_validate (locale_string, -1, NULL)) { + part_before_comma = g_locale_to_utf8 (locale_string, -1, NULL, NULL, NULL); + g_free (locale_string); + } else { + part_before_comma = locale_string; + } + + if (!g_utf8_validate (name, -1, NULL)) { + locale_string = g_locale_to_utf8 (name, -1, NULL, NULL, NULL); + } else { + locale_string = g_strdup (name); + } + + capitalized_login_name = eel_str_capitalize (locale_string); + g_free (locale_string); + + if (capitalized_login_name == NULL) { + real_name = part_before_comma; + } else { + real_name = eel_str_replace_substring + (part_before_comma, "&", capitalized_login_name); + g_free (part_before_comma); + } + + + if (eel_str_is_empty (real_name) + || eel_strcmp (name, real_name) == 0 + || eel_strcmp (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; +} + +/** + * caja_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 +caja_file_can_get_owner (CajaFile *file) +{ + /* Before we have info on a file, the owner is unknown. */ + return file->details->uid != -1; +} + +/** + * caja_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 * +caja_file_get_owner_name (CajaFile *file) +{ + return caja_file_get_owner_as_string (file, FALSE); +} + +/** + * caja_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 +caja_file_can_set_owner (CajaFile *file) +{ + /* Not allowed to set the owner if we can't + * even read it. This can happen on non-UNIX file + * systems. + */ + if (!caja_file_can_get_owner (file)) { + return FALSE; + } + + /* Only root is also allowed to set the owner. */ + return geteuid() == 0; +} + +/** + * caja_file_set_owner: + * + * Set the owner of a file. This will only have any effect if + * caja_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 +caja_file_set_owner (CajaFile *file, + const char *user_name_or_id, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + GFileInfo *info; + uid_t new_id; + + if (!caja_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". + */ + caja_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". + */ + caja_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 (new_id == (uid_t) file->details->uid) { + (* callback) (file, NULL, NULL, callback_data); + return; + } + + info = g_file_info_new (); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, new_id); + caja_file_set_attributes (file, info, callback, callback_data); + g_object_unref (info); +} + +/** + * caja_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 carriage return. The caller is responsible for freeing this list + * and its contents. + */ +GList * +caja_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) { + name = g_strconcat (user->pw_name, "\n", real_name, NULL); + } else { + name = g_strdup (user->pw_name); + } + g_free (real_name); + list = g_list_prepend (list, name); + } + + endpwent (); + + return eel_g_str_list_alphabetize (list); +} + +/** + * caja_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 +caja_file_can_get_group (CajaFile *file) +{ + /* Before we have info on a file, the group is unknown. */ + return file->details->gid != -1; +} + +/** + * caja_file_get_group_name: + * + * Get the name of the file's group. If the group has no + * name, returns the groupid 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 * +caja_file_get_group_name (CajaFile *file) +{ + return g_strdup (eel_ref_str_peek (file->details->group)); +} + +/** + * caja_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 +caja_file_can_set_group (CajaFile *file) +{ + uid_t user_id; + + /* Not allowed to set the permissions if we can't + * even read them. This can happen on non-UNIX file + * systems. + */ + if (!caja_file_can_get_group (file)) { + return FALSE; + } + + /* Check the user. */ + user_id = geteuid(); + + /* Owner is allowed to set group (with restrictions). */ + if (user_id == (uid_t) file->details->uid) { + return TRUE; + } + + /* Root is also allowed to set group. */ + if (user_id == 0) { + 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 * +caja_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 eel_g_str_list_alphabetize (list); +} + +/** + * caja_get_group_names: + * + * Get a list of all group names. + */ +GList * +caja_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 eel_g_str_list_alphabetize (list); +} + +/** + * caja_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 CajaFile in question. + */ +GList * +caja_file_get_settable_group_names (CajaFile *file) +{ + uid_t user_id; + GList *result; + + if (!caja_file_can_set_group (file)) { + return NULL; + } + + /* Check the user. */ + user_id = geteuid(); + + if (user_id == 0) { + /* Root is allowed to set group to anything. */ + result = caja_get_all_group_names (); + } else if (user_id == (uid_t) file->details->uid) { + /* Owner is allowed to set group to any that owner is member of. */ + result = caja_get_group_names_for_user (); + } else { + g_warning ("unhandled case in caja_get_settable_group_names"); + result = NULL; + } + + return result; +} + +/** + * caja_file_set_group: + * + * Set the group of a file. This will only have any effect if + * caja_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 +caja_file_set_group (CajaFile *file, + const char *group_name_or_id, + CajaFileOperationCallback callback, + gpointer callback_data) +{ + GError *error; + GFileInfo *info; + uid_t new_id; + + if (!caja_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". + */ + caja_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". + */ + caja_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 (new_id == (gid_t) file->details->gid) { + (* callback) (file, NULL, NULL, callback_data); + return; + } + + + info = g_file_info_new (); + g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, new_id); + caja_file_set_attributes (file, info, callback, callback_data); + g_object_unref (info); +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +caja_file_get_octal_permissions_as_string (CajaFile *file) +{ + guint32 permissions; + + g_assert (CAJA_IS_FILE (file)); + + if (!caja_file_can_get_permissions (file)) { + return NULL; + } + + permissions = file->details->permissions; + return g_strdup_printf ("%03o", permissions); +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +caja_file_get_permissions_as_string (CajaFile *file) +{ + guint32 permissions; + gboolean is_directory; + gboolean is_link; + gboolean suid, sgid, sticky; + + if (!caja_file_can_get_permissions (file)) { + return NULL; + } + + g_assert (CAJA_IS_FILE (file)); + + permissions = file->details->permissions; + is_directory = caja_file_is_directory (file); + is_link = caja_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' : '-')); +} + +/** + * caja_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: CajaFile 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 * +caja_file_get_owner_as_string (CajaFile *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 (file->details->owner_real == NULL) { + user_name = g_strdup (eel_ref_str_peek (file->details->owner)); + } else if (file->details->owner == NULL) { + user_name = g_strdup (eel_ref_str_peek (file->details->owner_real)); + } else if (include_real_name && + strcmp (eel_ref_str_peek (file->details->owner), eel_ref_str_peek (file->details->owner_real)) != 0) { + user_name = g_strdup_printf ("%s - %s", + eel_ref_str_peek (file->details->owner), + eel_ref_str_peek (file->details->owner_real)); + } else { + user_name = g_strdup (eel_ref_str_peek (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); +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +caja_file_get_size_as_string (CajaFile *file) +{ + guint item_count; + gboolean count_unreadable; + + if (file == NULL) { + return NULL; + } + + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_directory (file)) { + if (!caja_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; + } + + #if GLIB_CHECK_VERSION(2, 30, 0) + return g_format_size(file->details->size); + #else // Since 2.16 + return g_format_size_for_display(file->details->size); + #endif +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +caja_file_get_size_as_string_with_real_size (CajaFile *file) +{ + guint item_count; + gboolean count_unreadable; + char * formated; + char * formated_plus_real; + char * real_size; + + if (file == NULL) { + return NULL; + } + + g_assert (CAJA_IS_FILE (file)); + + if (caja_file_is_directory (file)) { + if (!caja_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; + } + + #if GLIB_CHECK_VERSION(2, 30, 0) + formated = g_format_size(file->details->size); + #else + formated = g_format_size_for_display(file->details->size); + #endif + + /* Do this in a separate stage so that we don't have to put G_GUINT64_FORMAT in the translated string */ + real_size = g_strdup_printf (_("%"G_GUINT64_FORMAT), (guint64) file->details->size); + formated_plus_real = g_strdup_printf (_("%s (%s bytes)"), formated, real_size); + g_free (real_size); + g_free (formated); + return formated_plus_real; +} + + +static char * +caja_file_get_deep_count_as_string_internal (CajaFile *file, + gboolean report_size, + gboolean report_directory_count, + gboolean report_file_count) +{ + CajaRequestStatus 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 (CAJA_IS_FILE (file)); + g_assert (caja_file_is_directory (file)); + + status = caja_file_get_deep_counts + (file, &directory_count, &file_count, &unreadable_count, &total_size, FALSE); + + /* Check whether any info is available. */ + if (status == CAJA_REQUEST_NOT_STARTED) { + return NULL; + } + + total_count = file_count + directory_count; + + if (total_count == 0) { + switch (status) { + case CAJA_REQUEST_IN_PROGRESS: + /* Don't return confident "zero" until we're finished looking, + * because of next case. + */ + return NULL; + case CAJA_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 caja_file_get_deep_counts + * directly if desired. + */ + if (report_size) + { + #if GLIB_CHECK_VERSION(2, 30, 0) + return g_format_size(total_size); + #else + return g_format_size_for_display(total_size); + #endif + } + + return format_item_count_for_display (report_directory_count + ? (report_file_count ? total_count : directory_count) + : file_count, + report_directory_count, report_file_count); +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +caja_file_get_deep_size_as_string (CajaFile *file) +{ + return caja_file_get_deep_count_as_string_internal (file, TRUE, FALSE, FALSE); +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +caja_file_get_deep_total_count_as_string (CajaFile *file) +{ + return caja_file_get_deep_count_as_string_internal (file, FALSE, TRUE, TRUE); +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +caja_file_get_deep_file_count_as_string (CajaFile *file) +{ + return caja_file_get_deep_count_as_string_internal (file, FALSE, FALSE, TRUE); +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +caja_file_get_deep_directory_count_as_string (CajaFile *file) +{ + return caja_file_get_deep_count_as_string_internal (file, FALSE, TRUE, FALSE); +} + +/** + * caja_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 + * caja_file_get_string_attribute_with_default if you want a non-NULL + * default. + * + * @file: CajaFile representing the file in question. + * @attribute_name: The name of the desired attribute. The currently supported + * set includes "name", "type", "mime_type", "size", "deep_size", "deep_directory_count", + * "deep_file_count", "deep_total_count", "date_modified", "date_changed", "date_accessed", + * "date_permissions", "owner", "group", "permissions", "octal_permissions", "uri", "where", + * "link_target", "volume", "free_space", "selinux_context", "trashed_on", "trashed_orig_path" + * + * Returns: Newly allocated string ready to display to the user, or NULL + * if the value is unknown or @attribute_name is not supported. + * + **/ +char * +caja_file_get_string_attribute_q (CajaFile *file, GQuark attribute_q) +{ + char *extension_attribute; + + if (attribute_q == attribute_name_q) { + return caja_file_get_display_name (file); + } + if (attribute_q == attribute_type_q) { + return caja_file_get_type_as_string (file); + } + if (attribute_q == attribute_mime_type_q) { + return caja_file_get_mime_type (file); + } + if (attribute_q == attribute_size_q) { + return caja_file_get_size_as_string (file); + } + if (attribute_q == attribute_size_detail_q) { + return caja_file_get_size_as_string_with_real_size (file); + } + if (attribute_q == attribute_deep_size_q) { + return caja_file_get_deep_size_as_string (file); + } + if (attribute_q == attribute_deep_file_count_q) { + return caja_file_get_deep_file_count_as_string (file); + } + if (attribute_q == attribute_deep_directory_count_q) { + return caja_file_get_deep_directory_count_as_string (file); + } + if (attribute_q == attribute_deep_total_count_q) { + return caja_file_get_deep_total_count_as_string (file); + } + if (attribute_q == attribute_trash_orig_path_q) { + return caja_file_get_trash_original_file_parent_as_string (file); + } + if (attribute_q == attribute_date_modified_q) { + return caja_file_get_date_as_string (file, + CAJA_DATE_TYPE_MODIFIED); + } + if (attribute_q == attribute_date_changed_q) { + return caja_file_get_date_as_string (file, + CAJA_DATE_TYPE_CHANGED); + } + if (attribute_q == attribute_date_accessed_q) { + return caja_file_get_date_as_string (file, + CAJA_DATE_TYPE_ACCESSED); + } + if (attribute_q == attribute_trashed_on_q) { + return caja_file_get_date_as_string (file, + CAJA_DATE_TYPE_TRASHED); + } + if (attribute_q == attribute_date_permissions_q) { + return caja_file_get_date_as_string (file, + CAJA_DATE_TYPE_PERMISSIONS_CHANGED); + } + if (attribute_q == attribute_permissions_q) { + return caja_file_get_permissions_as_string (file); + } + if (attribute_q == attribute_selinux_context_q) { + return caja_file_get_selinux_context (file); + } + if (attribute_q == attribute_octal_permissions_q) { + return caja_file_get_octal_permissions_as_string (file); + } + if (attribute_q == attribute_owner_q) { + return caja_file_get_owner_as_string (file, TRUE); + } + if (attribute_q == attribute_group_q) { + return caja_file_get_group_name (file); + } + if (attribute_q == attribute_uri_q) { + return caja_file_get_uri (file); + } + if (attribute_q == attribute_where_q) { + return caja_file_get_where_string (file); + } + if (attribute_q == attribute_link_target_q) { + return caja_file_get_symbolic_link_target_path (file); + } + if (attribute_q == attribute_volume_q) { + return caja_file_get_volume_name (file); + } + if (attribute_q == attribute_free_space_q) { + return caja_file_get_volume_free_space (file); + } + + extension_attribute = NULL; + + if (file->details->pending_extension_attributes) { + extension_attribute = g_hash_table_lookup (file->details->pending_extension_attributes, + GINT_TO_POINTER (attribute_q)); + } + + if (extension_attribute == NULL && file->details->extension_attributes) { + extension_attribute = g_hash_table_lookup (file->details->extension_attributes, + GINT_TO_POINTER (attribute_q)); + } + + return g_strdup (extension_attribute); +} + +char * +caja_file_get_string_attribute (CajaFile *file, const char *attribute_name) +{ + return caja_file_get_string_attribute_q (file, g_quark_from_string (attribute_name)); +} + + +/** + * caja_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 + * caja_file_get_string_attribute if you want NULL instead of a default + * result. + * + * @file: CajaFile representing the file in question. + * @attribute_name: The name of the desired attribute. See the description of + * caja_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 * +caja_file_get_string_attribute_with_default_q (CajaFile *file, GQuark attribute_q) +{ + char *result; + guint item_count; + gboolean count_unreadable; + CajaRequestStatus status; + + result = caja_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 (!caja_file_should_show_directory_item_count (file)) { + return g_strdup ("--"); + } + count_unreadable = FALSE; + if (caja_file_is_directory (file)) { + caja_file_get_directory_item_count (file, &item_count, &count_unreadable); + } + return g_strdup (count_unreadable ? _("? items") : "..."); + } + if (attribute_q == attribute_deep_size_q) { + status = caja_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE); + if (status == CAJA_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 = caja_file_get_deep_counts (file, NULL, NULL, NULL, NULL, FALSE); + if (status == CAJA_REQUEST_DONE) { + /* This means no contents at all were readable */ + return g_strdup (_("? items")); + } + return g_strdup ("..."); + } + if (attribute_q == attribute_type_q) { + return g_strdup (_("unknown type")); + } + if (attribute_q == attribute_mime_type_q) { + return g_strdup (_("unknown MIME 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 (""); + } + + /* Fallback, use for both unknown attributes and attributes + * for which we have no more appropriate default. + */ + return g_strdup (_("unknown")); +} + +char * +caja_file_get_string_attribute_with_default (CajaFile *file, const char *attribute_name) +{ + return caja_file_get_string_attribute_with_default_q (file, g_quark_from_string (attribute_name)); +} + +gboolean +caja_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_accessed_date_q || + attribute_q == attribute_date_accessed_q || + attribute_q == attribute_date_changed_q || + attribute_q == attribute_trashed_on_q || + attribute_q == attribute_date_permissions_q) { + return TRUE; + } + + return FALSE; +} + +/** + * get_description: + * + * Get a user-displayable string representing a file type. The caller + * is responsible for g_free-ing this string. + * @file: CajaFile representing the file in question. + * + * Returns: Newly allocated string ready to display to the user. + * + **/ +static char * +get_description (CajaFile *file) +{ + const char *mime_type; + char *description; + + g_assert (CAJA_IS_FILE (file)); + + mime_type = eel_ref_str_peek (file->details->mime_type); + if (eel_str_is_empty (mime_type)) { + return NULL; + } + + if (g_content_type_is_unknown (mime_type) && + caja_file_is_executable (file)) { + return g_strdup (_("program")); + } + + description = g_content_type_get_description (mime_type); + if (!eel_str_is_empty (description)) { + return description; + } + + return g_strdup (mime_type); +} + +/* Takes ownership of string */ +static char * +update_description_for_link (CajaFile *file, char *string) +{ + char *res; + + if (caja_file_is_symbolic_link (file)) { + g_assert (!caja_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"). + */ + res = g_strdup_printf (_("Link to %s"), string); + g_free (string); + return res; + } + + return string; +} + +static char * +caja_file_get_type_as_string (CajaFile *file) +{ + if (file == NULL) { + return NULL; + } + + if (caja_file_is_broken_symbolic_link (file)) { + return g_strdup (_("link (broken)")); + } + + return update_description_for_link (file, get_description (file)); +} + +/** + * caja_file_get_file_type + * + * Return this file's type. + * @file: CajaFile representing the file in question. + * + * Returns: The type. + * + **/ +GFileType +caja_file_get_file_type (CajaFile *file) +{ + if (file == NULL) { + return G_FILE_TYPE_UNKNOWN; + } + + return file->details->type; +} + +/** + * caja_file_get_mime_type + * + * Return this file's default mime type. + * @file: CajaFile representing the file in question. + * + * Returns: The mime type. + * + **/ +char * +caja_file_get_mime_type (CajaFile *file) +{ + if (file != NULL) { + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + if (file->details->mime_type != NULL) { + return g_strdup (eel_ref_str_peek (file->details->mime_type)); + } + } + return g_strdup ("application/octet-stream"); +} + +/** + * caja_file_is_mime_type + * + * Check whether a file is of a particular MIME type, or inherited + * from it. + * @file: CajaFile 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 +caja_file_is_mime_type (CajaFile *file, const char *mime_type) +{ + g_return_val_if_fail (CAJA_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 (eel_ref_str_peek (file->details->mime_type), + mime_type); +} + +gboolean +caja_file_is_launchable (CajaFile *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 (eel_ref_str_peek (file->details->mime_type)); + } + + return type_can_be_executable && + caja_file_can_get_permissions (file) && + caja_file_can_execute (file) && + caja_file_is_executable (file) && + !caja_file_is_directory (file); +} + + +/** + * caja_file_get_emblem_icons + * + * Return the list of names of emblems that this file should display, + * in canonical order. + * @file: CajaFile representing the file in question. + * + * Returns: A list of emblem names. + * + **/ +GList * +caja_file_get_emblem_icons (CajaFile *file, + char **exclude) +{ + GList *keywords, *l; + GList *icons; + char *icon_names[2]; + char *keyword; + int i; + GIcon *icon; + + if (file == NULL) { + return NULL; + } + + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + keywords = caja_file_get_keywords (file); + keywords = prepend_automatic_keywords (file, keywords); + + icons = NULL; + for (l = keywords; l != NULL; l = l->next) { + keyword = l->data; + +#ifdef TRASH_IS_FAST_ENOUGH + if (strcmp (keyword, CAJA_FILE_EMBLEM_NAME_TRASH) == 0) { + char *uri; + gboolean file_is_trash; + /* Leave out the trash emblem for the trash itself, since + * putting a trash emblem on a trash icon is gilding the + * lily. + */ + uri = caja_file_get_uri (file); + file_is_trash = strcmp (uri, EEL_TRASH_URI) == 0; + g_free (uri); + if (file_is_trash) { + continue; + } + } +#endif + if (exclude) { + for (i = 0; exclude[i] != NULL; i++) { + if (strcmp (exclude[i], keyword) == 0) { + continue; + } + } + } + + + icon_names[0] = g_strconcat ("emblem-", keyword, NULL); + icon_names[1] = keyword; + icon = g_themed_icon_new_from_names (icon_names, 2); + g_free (icon_names[0]); + + icons = g_list_prepend (icons, icon); + } + + eel_g_list_free_deep (keywords); + + return icons; +} + +GList * +caja_file_get_emblem_pixbufs (CajaFile *file, + int size, + gboolean force_size, + char **exclude) +{ + GList *icons, *l; + GList *pixbufs; + GIcon *icon; + GdkPixbuf *pixbuf; + CajaIconInfo *icon_info; + + icons = caja_file_get_emblem_icons (file, exclude); + pixbufs = NULL; + + for (l = icons; l != NULL; l = l->next) { + icon = l->data; + + icon_info = caja_icon_info_lookup (icon, size); + if (force_size) { + pixbuf = caja_icon_info_get_pixbuf_nodefault_at_size (icon_info, size); + } else { + pixbuf = caja_icon_info_get_pixbuf_nodefault (icon_info); + } + + if (pixbuf) { + pixbufs = g_list_prepend (pixbufs, pixbuf); + } + + + g_object_unref (icon_info); + g_object_unref (icon); + } + g_list_free (icons); + + return g_list_reverse (pixbufs); + + +} + +static GList * +sort_keyword_list_and_remove_duplicates (GList *keywords) +{ + GList *p; + GList *duplicate_link; + + if (keywords != NULL) { + keywords = eel_g_str_list_alphabetize (keywords); + + p = keywords; + while (p->next != NULL) { + if (strcmp ((const char *) p->data, (const char *) p->next->data) == 0) { + duplicate_link = p->next; + keywords = g_list_remove_link (keywords, duplicate_link); + eel_g_list_free_deep (duplicate_link); + } else { + p = p->next; + } + } + } + + return keywords; +} + +/** + * caja_file_get_keywords + * + * Return this file's keywords. + * @file: CajaFile representing the file in question. + * + * Returns: A list of keywords. + * + **/ +GList * +caja_file_get_keywords (CajaFile *file) +{ + GList *keywords; + + if (file == NULL) { + return NULL; + } + + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + /* Put all the keywords into a list. */ + keywords = caja_file_get_metadata_list + (file, CAJA_METADATA_KEY_EMBLEMS); + + keywords = g_list_concat (keywords, eel_g_str_list_copy (file->details->extension_emblems)); + keywords = g_list_concat (keywords, eel_g_str_list_copy (file->details->pending_extension_emblems)); + + return sort_keyword_list_and_remove_duplicates (keywords); +} + +/** + * caja_file_set_keywords + * + * Change this file's keywords. + * @file: CajaFile representing the file in question. + * @keywords: New set of keywords (a GList of strings). + * + **/ +void +caja_file_set_keywords (CajaFile *file, GList *keywords) +{ + GList *canonical_keywords; + + /* Invalidate the emblem compare cache */ + g_free (file->details->compare_by_emblem_cache); + file->details->compare_by_emblem_cache = NULL; + + g_return_if_fail (CAJA_IS_FILE (file)); + + canonical_keywords = sort_keyword_list_and_remove_duplicates + (g_list_copy (keywords)); + caja_file_set_metadata_list + (file, CAJA_METADATA_KEY_EMBLEMS, canonical_keywords); + g_list_free (canonical_keywords); +} + +/** + * caja_file_is_symbolic_link + * + * Check if this file is a symbolic link. + * @file: CajaFile representing the file in question. + * + * Returns: True if the file is a symbolic link. + * + **/ +gboolean +caja_file_is_symbolic_link (CajaFile *file) +{ + return file->details->is_symlink; +} + +gboolean +caja_file_is_mountpoint (CajaFile *file) +{ + return file->details->is_mountpoint; +} + +GMount * +caja_file_get_mount (CajaFile *file) +{ + if (file->details->mount) { + return g_object_ref (file->details->mount); + } + return NULL; +} + +static void +file_mount_unmounted (GMount *mount, + gpointer data) +{ + CajaFile *file; + + file = CAJA_FILE (data); + + caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_MOUNT); +} + +void +caja_file_set_mount (CajaFile *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 (mount, "unmounted", + G_CALLBACK (file_mount_unmounted), file); + } +} + +/** + * caja_file_is_broken_symbolic_link + * + * Check if this file is a symbolic link with a missing target. + * @file: CajaFile representing the file in question. + * + * Returns: True if the file is a symbolic link with a missing target. + * + **/ +gboolean +caja_file_is_broken_symbolic_link (CajaFile *file) +{ + if (file == NULL) { + return FALSE; + } + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + /* Non-broken symbolic links return the target's type for get_file_type. */ + return caja_file_get_file_type (file) == G_FILE_TYPE_SYMBOLIC_LINK; +} + +static void +get_fs_free_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + CajaDirectory *directory; + CajaFile *file; + guint64 free_space; + GFileInfo *info; + + directory = CAJA_DIRECTORY (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 (directory->details->free_space != free_space) { + directory->details->free_space = free_space; + file = caja_directory_get_existing_corresponding_file (directory); + if (file) { + caja_file_emit_changed (file); + caja_file_unref (file); + } + } + caja_directory_unref (directory); +} + +/** + * caja_file_get_volume_free_space + * Get a nicely formatted char with free space on the file's volume + * @file: CajaFile representing the file in question. + * + * Returns: newly-allocated copy of file size in a formatted string + */ +char * +caja_file_get_volume_free_space (CajaFile *file) +{ + CajaDirectory *directory; + GFile *location; + char *res; + time_t now; + + directory = caja_directory_get_for_file (file); + + now = time (NULL); + /* Update first time and then every 2 seconds */ + if (directory->details->free_space_read == 0 || + (now - directory->details->free_space_read) > 2) { + directory->details->free_space_read = now; + location = caja_file_get_location (file); + g_file_query_filesystem_info_async (location, + G_FILE_ATTRIBUTE_FILESYSTEM_FREE, + 0, NULL, + get_fs_free_cb, + directory); /* Inherits ref */ + g_object_unref (location); + } else { + caja_directory_unref (directory); + } + + + res = NULL; + + if (directory->details->free_space != (guint64) -1) + { + #if GLIB_CHECK_VERSION(2, 30, 0) + res = g_format_size(directory->details->free_space); + #else + res = g_format_size_for_display(directory->details->free_space); + #endif + } + + return res; +} + +/** + * caja_file_get_volume_name + * Get the path of the volume the file resides on + * @file: CajaFile representing the file in question. + * + * Returns: newly-allocated copy of the volume name of the target file, + * if the volume name isn't set, it returns the mount path of the volume + */ +char * +caja_file_get_volume_name (CajaFile *file) +{ + GFile *location; + char *res; + GMount *mount; + + res = NULL; + + location = caja_file_get_location (file); + mount = g_file_find_enclosing_mount (location, NULL, NULL); + if (mount) { + res = g_strdup (g_mount_get_name (mount)); + g_object_unref (mount); + } + g_object_unref (location); + + return res; +} + +/** + * caja_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: CajaFile representing the symbolic link in question. + * + * Returns: newly-allocated copy of the file path of the target of the symbolic link. + */ +char * +caja_file_get_symbolic_link_target_path (CajaFile *file) +{ + if (!caja_file_is_symbolic_link (file)) { + g_warning ("File has symlink target, but is not marked as symlink"); + } + + return g_strdup (file->details->symlink_name); +} + +/** + * caja_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: CajaFile representing the symbolic link in question. + * + * Returns: newly-allocated copy of the uri of the target of the symbolic link. + */ +char * +caja_file_get_symbolic_link_target_uri (CajaFile *file) +{ + GFile *location, *parent, *target; + char *target_uri; + + if (!caja_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 = caja_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; + } +} + +/** + * caja_file_is_caja_link + * + * Check if this file is a "caja link", meaning a historical + * caja xml link file or a desktop file. + * @file: CajaFile representing the file in question. + * + * Returns: True if the file is a caja link. + * + **/ +gboolean +caja_file_is_caja_link (CajaFile *file) +{ + /* NOTE: I removed the historical link here, because i don't think we + even detect that mimetype anymore */ + return caja_file_is_mime_type (file, "application/x-desktop"); +} + +/** + * caja_file_is_directory + * + * Check if this file is a directory. + * @file: CajaFile representing the file in question. + * + * Returns: TRUE if @file is a directory. + * + **/ +gboolean +caja_file_is_directory (CajaFile *file) +{ + return caja_file_get_file_type (file) == G_FILE_TYPE_DIRECTORY; +} + +/** + * caja_file_is_user_special_directory + * + * Check if this file is a special platform directory. + * @file: CajaFile 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 +caja_file_is_user_special_directory (CajaFile *file, + GUserDirectory special_directory) +{ + gboolean is_special_dir; + const gchar *special_dir; + + special_dir = g_get_user_special_dir (special_directory); + is_special_dir = FALSE; + + if (special_dir) { + GFile *loc; + GFile *special_gfile; + + loc = caja_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 +caja_file_is_archive (CajaFile *file) +{ + char *mime_type; + int i; + static const char * archive_mime_types[] = { "application/x-gtar", + "application/x-zip", + "application/x-zip-compressed", + "application/zip", + "application/x-zip", + "application/x-tar", + "application/x-7z-compressed", + "application/x-rar", + "application/x-rar-compressed", + "application/x-jar", + "application/x-java-archive", + "application/x-war", + "application/x-ear", + "application/x-arj", + "application/x-gzip", + "application/x-bzip-compressed-tar", + "application/x-compressed-tar" }; + + g_return_val_if_fail (file != NULL, FALSE); + + mime_type = caja_file_get_mime_type (file); + for (i = 0; i < G_N_ELEMENTS (archive_mime_types); i++) { + if (!strcmp (mime_type, archive_mime_types[i])) { + g_free (mime_type); + return TRUE; + } + } + g_free (mime_type); + + return FALSE; +} + + +/** + * caja_file_is_in_trash + * + * Check if this file is a file in trash. + * @file: CajaFile representing the file in question. + * + * Returns: TRUE if @file is in a trash. + * + **/ +gboolean +caja_file_is_in_trash (CajaFile *file) +{ + g_assert (CAJA_IS_FILE (file)); + + return caja_directory_is_in_trash (file->details->directory); +} + +GError * +caja_file_get_file_info_error (CajaFile *file) +{ + if (!file->details->get_info_failed) { + return NULL; + } + + return file->details->get_info_error; +} + +/** + * caja_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: CajaFile representing the file in question. + * + * Returns: TRUE if @file has a text MIME type. + * + **/ +gboolean +caja_file_contains_text (CajaFile *file) +{ + if (file == NULL) { + return FALSE; + } + + /* All text files inherit from text/plain */ + return caja_file_is_mime_type (file, "text/plain"); +} + +/** + * caja_file_is_executable + * + * Check if this file is executable at all. + * @file: CajaFile 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 +caja_file_is_executable (CajaFile *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; +} + +/** + * caja_file_peek_top_left_text + * + * Peek at the text from the top left of the file. + * @file: CajaFile representing the file in question. + * + * Returns: NULL if there is no text readable, otherwise, the text. + * This string is owned by the file object and should not + * be kept around or freed. + * + **/ +char * +caja_file_peek_top_left_text (CajaFile *file, + gboolean need_large_text, + gboolean *needs_loading) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), NULL); + + if (!caja_file_should_get_top_left_text (file)) { + if (needs_loading) { + *needs_loading = FALSE; + } + return NULL; + } + + if (needs_loading) { + *needs_loading = !file->details->top_left_text_is_up_to_date; + if (need_large_text) { + *needs_loading |= file->details->got_top_left_text != file->details->got_large_top_left_text; + } + } + + /* Show " ..." in the file until we read the contents in. */ + if (!file->details->got_top_left_text) { + + if (caja_file_contains_text (file)) { + return " ..."; + } + return NULL; + } + + /* Show what we read in. */ + return file->details->top_left_text; +} + +/** + * caja_file_get_top_left_text + * + * Get the text from the top left of the file. + * @file: CajaFile representing the file in question. + * + * Returns: NULL if there is no text readable, otherwise, the text. + * + **/ +char * +caja_file_get_top_left_text (CajaFile *file) +{ + return g_strdup (caja_file_peek_top_left_text (file, FALSE, NULL)); +} + +char * +caja_file_get_filesystem_id (CajaFile *file) +{ + return g_strdup (eel_ref_str_peek (file->details->filesystem_id)); +} + +CajaFile * +caja_file_get_trash_original_file (CajaFile *file) +{ + GFile *location; + CajaFile *original_file; + char *filename; + + original_file = NULL; + + if (file->details->trash_orig_path != NULL) { + /* file name is stored in URL encoding */ + filename = g_uri_unescape_string (file->details->trash_orig_path, ""); + location = g_file_new_for_path (filename); + original_file = caja_file_get (location); + g_object_unref (G_OBJECT (location)); + g_free (filename); + } + + return original_file; + +} + +void +caja_file_mark_gone (CajaFile *file) +{ + CajaDirectory *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); + + /* Let the directory know it's gone. */ + directory = file->details->directory; + if (!caja_file_is_self_owned (file)) { + caja_directory_remove_file (directory, file); + } + + caja_file_clear_info (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 "<deleted>" name or something. + */ +} + +/** + * caja_file_changed + * + * Notify the user that this file has changed. + * @file: CajaFile representing the file in question. + **/ +void +caja_file_changed (CajaFile *file) +{ + GList fake_list; + + g_return_if_fail (CAJA_IS_FILE (file)); + + if (caja_file_is_self_owned (file)) { + caja_file_emit_changed (file); + } else { + fake_list.data = file; + fake_list.next = NULL; + fake_list.prev = NULL; + caja_directory_emit_change_signals + (file->details->directory, &fake_list); + } +} + +/** + * caja_file_updated_deep_count_in_progress + * + * Notify clients that a newer deep count is available for + * the directory in question. + */ +void +caja_file_updated_deep_count_in_progress (CajaFile *file) { + GList *link_files, *node; + + g_assert (CAJA_IS_FILE (file)); + g_assert (caja_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) { + caja_file_updated_deep_count_in_progress (CAJA_FILE (node->data)); + } + caja_file_list_free (link_files); +} + +/** + * caja_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: CajaFile representing the file in question. + **/ +void +caja_file_emit_changed (CajaFile *file) +{ + GList *link_files, *p; + + g_assert (CAJA_IS_FILE (file)); + + + /* Invalidate the emblem compare cache. -- This is not the cleanest + * place to do it but it is the one guaranteed bottleneck through + * which all change notifications pass. + */ + g_free (file->details->compare_by_emblem_cache); + file->details->compare_by_emblem_cache = NULL; + + /* 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) { + if (p->data != file) { + caja_file_changed (CAJA_FILE (p->data)); + } + } + caja_file_list_free (link_files); +} + +/** + * caja_file_is_gone + * + * Check if a file has already been deleted. + * @file: CajaFile representing the file in question. + * + * Returns: TRUE if the file is already gone. + **/ +gboolean +caja_file_is_gone (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return file->details->is_gone; +} + +/** + * caja_file_is_not_yet_confirmed + * + * Check if we're in a state where we don't know if a file really + * exists or not, before the initial I/O is complete. + * @file: CajaFile representing the file in question. + * + * Returns: TRUE if the file is already gone. + **/ +gboolean +caja_file_is_not_yet_confirmed (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return !file->details->got_file_info; +} + +/** + * caja_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. + * @file_attributes: A bit-mask with the desired information. + * + * Return value: TRUE if all of the specified attributes are currently readable. + */ +gboolean +caja_file_check_if_ready (CajaFile *file, + CajaFileAttributes file_attributes) +{ + /* To be parallel with call_when_ready, return + * TRUE for NULL file. + */ + if (file == NULL) { + return TRUE; + } + + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (CAJA_FILE_CLASS, file, + check_if_ready, (file, file_attributes)); +} + +void +caja_file_call_when_ready (CajaFile *file, + CajaFileAttributes file_attributes, + CajaFileCallback callback, + gpointer callback_data) + +{ + if (file == NULL) { + (* callback) (file, callback_data); + return; + } + + g_return_if_fail (CAJA_IS_FILE (file)); + + EEL_CALL_METHOD + (CAJA_FILE_CLASS, file, + call_when_ready, (file, file_attributes, + callback, callback_data)); +} + +void +caja_file_cancel_call_when_ready (CajaFile *file, + CajaFileCallback callback, + gpointer callback_data) +{ + g_return_if_fail (callback != NULL); + + if (file == NULL) { + return; + } + + g_return_if_fail (CAJA_IS_FILE (file)); + + EEL_CALL_METHOD + (CAJA_FILE_CLASS, file, + cancel_call_when_ready, (file, callback, callback_data)); +} + +static void +invalidate_directory_count (CajaFile *file) +{ + file->details->directory_count_is_up_to_date = FALSE; +} + +static void +invalidate_deep_counts (CajaFile *file) +{ + file->details->deep_counts_status = CAJA_REQUEST_NOT_STARTED; +} + +static void +invalidate_mime_list (CajaFile *file) +{ + file->details->mime_list_is_up_to_date = FALSE; +} + +static void +invalidate_top_left_text (CajaFile *file) +{ + file->details->top_left_text_is_up_to_date = FALSE; +} + +static void +invalidate_file_info (CajaFile *file) +{ + file->details->file_info_is_up_to_date = FALSE; +} + +static void +invalidate_link_info (CajaFile *file) +{ + file->details->link_info_is_up_to_date = FALSE; +} + +static void +invalidate_thumbnail (CajaFile *file) +{ + file->details->thumbnail_is_up_to_date = FALSE; +} + +static void +invalidate_mount (CajaFile *file) +{ + file->details->mount_is_up_to_date = FALSE; +} + +void +caja_file_invalidate_extension_info_internal (CajaFile *file) +{ + if (file->details->pending_info_providers) + eel_g_object_list_free (file->details->pending_info_providers); + + file->details->pending_info_providers = + caja_module_get_extensions_for_type (CAJA_TYPE_INFO_PROVIDER); +} + +void +caja_file_invalidate_attributes_internal (CajaFile *file, + CajaFileAttributes file_attributes) +{ + Request request; + + if (file == NULL) { + return; + } + + if (CAJA_IS_DESKTOP_ICON_FILE (file)) { + /* Desktop icon files are always up to date. + * If we invalidate their attributes they + * will lose data, so we just ignore them. + */ + return; + } + + request = caja_directory_set_up_request (file_attributes); + + if (REQUEST_WANTS_TYPE (request, REQUEST_DIRECTORY_COUNT)) { + invalidate_directory_count (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_DEEP_COUNT)) { + invalidate_deep_counts (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_MIME_LIST)) { + invalidate_mime_list (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_FILE_INFO)) { + invalidate_file_info (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_TOP_LEFT_TEXT)) { + invalidate_top_left_text (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_LINK_INFO)) { + invalidate_link_info (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_EXTENSION_INFO)) { + caja_file_invalidate_extension_info_internal (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_THUMBNAIL)) { + invalidate_thumbnail (file); + } + if (REQUEST_WANTS_TYPE (request, REQUEST_MOUNT)) { + invalidate_mount (file); + } + + /* FIXME bugzilla.gnome.org 45075: implement invalidating metadata */ +} + +gboolean +caja_file_has_open_window (CajaFile *file) +{ + return file->details->has_open_window; +} + +void +caja_file_set_has_open_window (CajaFile *file, + gboolean has_open_window) +{ + has_open_window = (has_open_window != FALSE); + + if (file->details->has_open_window != has_open_window) { + file->details->has_open_window = has_open_window; + caja_file_changed (file); + } +} + + +gboolean +caja_file_is_thumbnailing (CajaFile *file) +{ + g_return_val_if_fail (CAJA_IS_FILE (file), FALSE); + + return file->details->is_thumbnailing; +} + +void +caja_file_set_is_thumbnailing (CajaFile *file, + gboolean is_thumbnailing) +{ + g_return_if_fail (CAJA_IS_FILE (file)); + + file->details->is_thumbnailing = is_thumbnailing; +} + + +/** + * caja_file_invalidate_attributes + * + * Invalidate the specified attributes and force a reload. + * @file: CajaFile representing the file in question. + * @file_attributes: attributes to froget. + **/ + +void +caja_file_invalidate_attributes (CajaFile *file, + CajaFileAttributes file_attributes) +{ + /* Cancel possible in-progress loads of any of these attributes */ + caja_directory_cancel_loading_file_attributes (file->details->directory, + file, + file_attributes); + + /* Actually invalidate the values */ + caja_file_invalidate_attributes_internal (file, file_attributes); + + caja_directory_add_file_to_work_queue (file->details->directory, file); + + /* Kick off I/O if necessary */ + caja_directory_async_state_changed (file->details->directory); +} + +CajaFileAttributes +caja_file_get_all_attributes (void) +{ + return CAJA_FILE_ATTRIBUTE_INFO | + CAJA_FILE_ATTRIBUTE_LINK_INFO | + CAJA_FILE_ATTRIBUTE_DEEP_COUNTS | + CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT | + CAJA_FILE_ATTRIBUTE_DIRECTORY_ITEM_MIME_TYPES | + CAJA_FILE_ATTRIBUTE_TOP_LEFT_TEXT | + CAJA_FILE_ATTRIBUTE_LARGE_TOP_LEFT_TEXT | + CAJA_FILE_ATTRIBUTE_EXTENSION_INFO | + CAJA_FILE_ATTRIBUTE_THUMBNAIL | + CAJA_FILE_ATTRIBUTE_MOUNT; +} + +void +caja_file_invalidate_all_attributes (CajaFile *file) +{ + CajaFileAttributes all_attributes; + + all_attributes = caja_file_get_all_attributes (); + caja_file_invalidate_attributes (file, all_attributes); +} + + +/** + * caja_file_dump + * + * Debugging call, prints out the contents of the file + * fields. + * + * @file: file to dump. + **/ +void +caja_file_dump (CajaFile *file) +{ + long size = file->details->deep_size; + char *uri; + const char *file_kind; + + uri = caja_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); +} + +/** + * caja_file_list_ref + * + * Ref all the files in a list. + * @list: GList of files. + **/ +GList * +caja_file_list_ref (GList *list) +{ + g_list_foreach (list, (GFunc) caja_file_ref, NULL); + return list; +} + +/** + * caja_file_list_unref + * + * Unref all the files in a list. + * @list: GList of files. + **/ +void +caja_file_list_unref (GList *list) +{ + g_list_foreach (list, (GFunc) caja_file_unref, NULL); +} + +/** + * caja_file_list_free + * + * Free a list of files after unrefing them. + * @list: GList of files. + **/ +void +caja_file_list_free (GList *list) +{ + caja_file_list_unref (list); + g_list_free (list); +} + +/** + * caja_file_list_copy + * + * Copy the list of files, making a new ref of each, + * @list: GList of files. + **/ +GList * +caja_file_list_copy (GList *list) +{ + return g_list_copy (caja_file_list_ref (list)); +} + +GList * +caja_file_list_from_uris (GList *uri_list) +{ + GList *l, *file_list; + const char *uri; + GFile *file; + + file_list = NULL; + + for (l = uri_list; l != NULL; l = l->next) { + uri = l->data; + file = g_file_new_for_uri (uri); + file_list = g_list_prepend (file_list, file); + } + return g_list_reverse (file_list); +} + +static gboolean +get_attributes_for_default_sort_type (CajaFile *file, + gboolean *is_download, + gboolean *is_trash) +{ + gboolean is_download_dir, is_desktop_dir, is_trash_dir, retval; + + *is_download = FALSE; + *is_trash = FALSE; + retval = FALSE; + + /* special handling for certain directories */ + if (file && caja_file_is_directory (file)) { + is_download_dir = + caja_file_is_user_special_directory (file, G_USER_DIRECTORY_DOWNLOAD); + is_desktop_dir = + caja_file_is_user_special_directory (file, G_USER_DIRECTORY_DESKTOP); + is_trash_dir = + caja_file_is_in_trash (file); + + if (is_download_dir && !is_desktop_dir) { + *is_download = TRUE; + retval = TRUE; + } else if (is_trash_dir) { + *is_trash = TRUE; + retval = TRUE; + } + } + + return retval; +} + +CajaFileSortType +caja_file_get_default_sort_type (CajaFile *file, + gboolean *reversed) +{ + CajaFileSortType retval; + gboolean is_download, is_trash, res; + + retval = CAJA_FILE_SORT_NONE; + is_download = is_trash = FALSE; + res = get_attributes_for_default_sort_type (file, &is_download, &is_trash); + + if (res) { + if (is_download) { + retval = CAJA_FILE_SORT_BY_MTIME; + } else if (is_trash) { + retval = CAJA_FILE_SORT_BY_TRASHED_TIME; + } + + if (reversed != NULL) { + *reversed = res; + } + } + + return retval; +} + +const gchar * +caja_file_get_default_sort_attribute (CajaFile *file, + gboolean *reversed) +{ + const gchar *retval; + gboolean is_download, is_trash, res; + + retval = NULL; + is_download = is_trash = FALSE; + res = get_attributes_for_default_sort_type (file, &is_download, &is_trash); + + if (res) { + if (is_download) { + retval = g_quark_to_string (attribute_date_modified_q); + } else if (is_trash) { + retval = g_quark_to_string (attribute_trashed_on_q); + } + + if (reversed != NULL) { + *reversed = res; + } + } + + return retval; +} + +static int +compare_by_display_name_cover (gconstpointer a, gconstpointer b) +{ + return compare_by_display_name (CAJA_FILE (a), CAJA_FILE (b)); +} + +/** + * caja_file_list_sort_by_display_name + * + * Sort the list of files by file name. + * @list: GList of files. + **/ +GList * +caja_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; + GList *remaining_files; + CajaFileListCallback callback; + gpointer callback_data; +} FileListReadyData; + +static void +file_list_ready_data_free (FileListReadyData *data) +{ + GList *l; + + l = g_list_find (ready_data_list, data); + if (l != NULL) { + ready_data_list = g_list_delete_link (ready_data_list, l); + + caja_file_list_free (data->file_list); + g_list_free (data->remaining_files); + g_free (data); + } +} + +static FileListReadyData * +file_list_ready_data_new (GList *file_list, + CajaFileListCallback callback, + gpointer callback_data) +{ + FileListReadyData *data; + + data = g_new0 (FileListReadyData, 1); + data->file_list = caja_file_list_copy (file_list); + data->remaining_files = g_list_copy (file_list); + 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_callback (CajaFile *file, + gpointer user_data) +{ + FileListReadyData *data; + + data = user_data; + data->remaining_files = g_list_remove (data->remaining_files, file); + + if (data->remaining_files == NULL) { + if (data->callback) { + (*data->callback) (data->file_list, data->callback_data); + } + + file_list_ready_data_free (data); + } +} + +void +caja_file_list_call_when_ready (GList *file_list, + CajaFileAttributes attributes, + CajaFileListHandle **handle, + CajaFileListCallback callback, + gpointer callback_data) +{ + GList *l; + FileListReadyData *data; + CajaFile *file; + + g_return_if_fail (file_list != NULL); + + data = file_list_ready_data_new + (file_list, callback, callback_data); + + if (handle) { + *handle = (CajaFileListHandle *) data; + } + + + l = file_list; + while (l != NULL) { + file = CAJA_FILE (l->data); + /* Need to do this here, as the list can be modified by this call */ + l = l->next; + caja_file_call_when_ready (file, + attributes, + file_list_file_ready_callback, + data); + } +} + +void +caja_file_list_cancel_call_when_ready (CajaFileListHandle *handle) +{ + GList *l; + CajaFile *file; + FileListReadyData *data; + + g_return_if_fail (handle != NULL); + + data = (FileListReadyData *) handle; + + l = g_list_find (ready_data_list, data); + if (l != NULL) { + for (l = data->remaining_files; l != NULL; l = l->next) { + file = CAJA_FILE (l->data); + + EEL_CALL_METHOD + (CAJA_FILE_CLASS, file, + cancel_call_when_ready, (file, file_list_file_ready_callback, data)); + } + + file_list_ready_data_free (data); + } +} + +static char * +try_to_make_utf8 (const char *text, int *length) +{ + static const char *encodings_to_try[2]; + static int n_encodings_to_try = 0; + gsize converted_length; + GError *conversion_error; + char *utf8_text; + int i; + + if (n_encodings_to_try == 0) { + const char *charset; + gboolean charset_is_utf8; + + charset_is_utf8 = g_get_charset (&charset); + if (!charset_is_utf8) { + encodings_to_try[n_encodings_to_try++] = charset; + } + + if (g_ascii_strcasecmp (charset, "ISO-8859-1") != 0) { + encodings_to_try[n_encodings_to_try++] = "ISO-8859-1"; + } + } + + utf8_text = NULL; + for (i = 0; i < n_encodings_to_try; i++) { + conversion_error = NULL; + utf8_text = g_convert (text, *length, + "UTF-8", encodings_to_try[i], + NULL, &converted_length, &conversion_error); + if (utf8_text != NULL) { + *length = converted_length; + break; + } + g_error_free (conversion_error); + } + + return utf8_text; +} + + + +/* Extract the top left part of the read-in text. */ +char * +caja_extract_top_left_text (const char *text, + gboolean large, + int length) +{ + GString* buffer; + const gchar *in; + const gchar *end; + int line, i; + gunichar c; + char *text_copy; + const char *utf8_end; + gboolean validated; + int max_bytes, max_lines, max_cols; + + if (large) { + max_bytes = CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_BYTES; + max_lines = CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_LINES; + max_cols = CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_CHARACTERS_PER_LINE; + } else { + max_bytes = CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_BYTES; + max_lines = CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_LINES; + max_cols = CAJA_FILE_TOP_LEFT_TEXT_MAXIMUM_CHARACTERS_PER_LINE; + } + + + + text_copy = NULL; + if (text != NULL) { + /* Might be a partial utf8 character at the end if we didn't read whole file */ + validated = g_utf8_validate (text, length, &utf8_end); + if (!validated && + !(length >= max_bytes && + text + length - utf8_end < 6)) { + text_copy = try_to_make_utf8 (text, &length); + text = text_copy; + } else if (!validated) { + length = utf8_end - text; + } + } + + if (text == NULL || length == 0) { + return NULL; + } + + buffer = g_string_new (""); + end = text + length; in = text; + + for (line = 0; line < max_lines; line++) { + /* Extract one line. */ + for (i = 0; i < max_cols; ) { + if (*in == '\n') { + break; + } + + c = g_utf8_get_char (in); + + if (g_unichar_isprint (c)) { + g_string_append_unichar (buffer, c); + i++; + } + + in = g_utf8_next_char (in); + if (in == end) { + goto done; + } + } + + /* Skip the rest of the line. */ + while (*in != '\n') { + if (++in == end) { + goto done; + } + } + if (++in == end) { + goto done; + } + + /* Put a new-line separator in. */ + g_string_append_c(buffer, '\n'); + } + done: + g_free (text_copy); + + return g_string_free(buffer, FALSE); +} + +static void +thumbnail_limit_changed_callback (gpointer user_data) +{ + cached_thumbnail_limit = eel_preferences_get_uint (CAJA_PREFERENCES_IMAGE_FILE_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 +thumbnail_size_changed_callback (gpointer user_data) +{ + cached_thumbnail_size = eel_preferences_get_integer (CAJA_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE); + + /* 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 +show_thumbnails_changed_callback (gpointer user_data) +{ + show_image_thumbs = eel_preferences_get_enum (CAJA_PREFERENCES_SHOW_IMAGE_FILE_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 +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 void +icon_theme_changed_callback (GtkIconTheme *icon_theme, + gpointer user_data) +{ + /* Clear all pixmap caches as the icon => pixmap lookup changed */ + caja_icon_info_clear_caches (); + + /* 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 +caja_file_class_init (CajaFileClass *class) +{ + GtkIconTheme *icon_theme; + + caja_file_info_getter = caja_file_get_internal; + + 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_modification_date_q = g_quark_from_static_string ("modification_date"); + attribute_date_modified_q = g_quark_from_static_string ("date_modified"); + attribute_accessed_date_q = g_quark_from_static_string ("accessed_date"); + attribute_date_accessed_q = g_quark_from_static_string ("date_accessed"); + attribute_emblems_q = g_quark_from_static_string ("emblems"); + 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_date_changed_q = g_quark_from_static_string ("date_changed"); + attribute_trashed_on_q = g_quark_from_static_string ("trashed_on"); + attribute_trash_orig_path_q = g_quark_from_static_string ("trash_orig_path"); + attribute_date_permissions_q = g_quark_from_static_string ("date_permissions"); + 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"); + + G_OBJECT_CLASS (class)->finalize = finalize; + G_OBJECT_CLASS (class)->constructor = caja_file_constructor; + + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CajaFileClass, 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 (CajaFileClass, updated_deep_count_in_progress), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (class, sizeof (CajaFileDetails)); + + + eel_preferences_add_auto_enum (CAJA_PREFERENCES_DATE_FORMAT, + &date_format_pref); + + thumbnail_limit_changed_callback (NULL); + eel_preferences_add_callback (CAJA_PREFERENCES_IMAGE_FILE_THUMBNAIL_LIMIT, + thumbnail_limit_changed_callback, + NULL); + thumbnail_size_changed_callback (NULL); + eel_preferences_add_callback (CAJA_PREFERENCES_ICON_VIEW_THUMBNAIL_SIZE, + thumbnail_size_changed_callback, + NULL); + show_thumbnails_changed_callback (NULL); + eel_preferences_add_callback (CAJA_PREFERENCES_SHOW_IMAGE_FILE_THUMBNAILS, + show_thumbnails_changed_callback, + NULL); + + icon_theme = gtk_icon_theme_get_default (); + g_signal_connect_object (icon_theme, + "changed", + G_CALLBACK (icon_theme_changed_callback), + NULL, 0); + + g_signal_connect (caja_signaller_get_current (), + "mime_data_changed", + G_CALLBACK (mime_type_data_changed_callback), + NULL); +} + +static void +caja_file_add_emblem (CajaFile *file, + const char *emblem_name) +{ + if (file->details->pending_info_providers) { + file->details->pending_extension_emblems = g_list_prepend (file->details->pending_extension_emblems, + g_strdup (emblem_name)); + } else { + file->details->extension_emblems = g_list_prepend (file->details->extension_emblems, + g_strdup (emblem_name)); + } + + caja_file_changed (file); +} + +static void +caja_file_add_string_attribute (CajaFile *file, + const char *attribute_name, + const char *value) +{ + if (file->details->pending_info_providers) { + /* Lazily create hashtable */ + if (!file->details->pending_extension_attributes) { + file->details->pending_extension_attributes = + g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, + (GDestroyNotify)g_free); + } + g_hash_table_insert (file->details->pending_extension_attributes, + GINT_TO_POINTER (g_quark_from_string (attribute_name)), + g_strdup (value)); + } else { + if (!file->details->extension_attributes) { + 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)); + } + + caja_file_changed (file); +} + +static void +caja_file_invalidate_extension_info (CajaFile *file) +{ + caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_EXTENSION_INFO); +} + +void +caja_file_info_providers_done (CajaFile *file) +{ + eel_g_list_free_deep (file->details->extension_emblems); + file->details->extension_emblems = file->details->pending_extension_emblems; + file->details->pending_extension_emblems = NULL; + + if (file->details->extension_attributes) { + g_hash_table_destroy (file->details->extension_attributes); + } + + file->details->extension_attributes = file->details->pending_extension_attributes; + file->details->pending_extension_attributes = NULL; + + caja_file_changed (file); +} + +static void +caja_file_info_iface_init (CajaFileInfoIface *iface) +{ + iface->is_gone = caja_file_is_gone; + iface->get_name = caja_file_get_name; + iface->get_file_type = caja_file_get_file_type; + iface->get_location = caja_file_get_location; + iface->get_uri = caja_file_get_uri; + iface->get_parent_location = caja_file_get_parent_location; + iface->get_parent_uri = caja_file_get_parent_uri; + iface->get_parent_info = caja_file_get_parent; + iface->get_mount = caja_file_get_mount; + iface->get_uri_scheme = caja_file_get_uri_scheme; + iface->get_activation_uri = caja_file_get_activation_uri; + iface->get_mime_type = caja_file_get_mime_type; + iface->is_mime_type = caja_file_is_mime_type; + iface->is_directory = caja_file_is_directory; + iface->can_write = caja_file_can_write; + iface->add_emblem = caja_file_add_emblem; + iface->get_string_attribute = caja_file_get_string_attribute; + iface->add_string_attribute = caja_file_add_string_attribute; + iface->invalidate_extension_info = caja_file_invalidate_extension_info; +} + +#if !defined (CAJA_OMIT_SELF_CHECK) + +void +caja_self_check_file (void) +{ + CajaFile *file_1; + CajaFile *file_2; + GList *list; + + /* refcount checks */ + + EEL_CHECK_INTEGER_RESULT (caja_directory_number_outstanding (), 0); + + file_1 = caja_file_get_by_uri ("file:///home/"); + + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 1); + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1->details->directory)->ref_count, 1); + EEL_CHECK_INTEGER_RESULT (caja_directory_number_outstanding (), 1); + + caja_file_unref (file_1); + + EEL_CHECK_INTEGER_RESULT (caja_directory_number_outstanding (), 0); + + file_1 = caja_file_get_by_uri ("file:///etc"); + file_2 = caja_file_get_by_uri ("file:///usr"); + + list = NULL; + list = g_list_prepend (list, file_1); + list = g_list_prepend (list, file_2); + + caja_file_list_ref (list); + + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 2); + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_2)->ref_count, 2); + + caja_file_list_unref (list); + + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 1); + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_2)->ref_count, 1); + + caja_file_list_free (list); + + EEL_CHECK_INTEGER_RESULT (caja_directory_number_outstanding (), 0); + + + /* name checks */ + file_1 = caja_file_get_by_uri ("file:///home/"); + + EEL_CHECK_STRING_RESULT (caja_file_get_name (file_1), "home"); + + EEL_CHECK_BOOLEAN_RESULT (caja_file_get_by_uri ("file:///home/") == file_1, TRUE); + caja_file_unref (file_1); + + EEL_CHECK_BOOLEAN_RESULT (caja_file_get_by_uri ("file:///home") == file_1, TRUE); + caja_file_unref (file_1); + + caja_file_unref (file_1); + + file_1 = caja_file_get_by_uri ("file:///home"); + EEL_CHECK_STRING_RESULT (caja_file_get_name (file_1), "home"); + caja_file_unref (file_1); + +#if 0 + /* ALEX: I removed this, because it was breaking distchecks. + * It used to work, but when canonical uris changed from + * foo: to foo:/// it broke. I don't expect it to matter + * in real life */ + file_1 = caja_file_get_by_uri (":"); + EEL_CHECK_STRING_RESULT (caja_file_get_name (file_1), ":"); + caja_file_unref (file_1); +#endif + + file_1 = caja_file_get_by_uri ("eazel:"); + EEL_CHECK_STRING_RESULT (caja_file_get_name (file_1), "eazel"); + caja_file_unref (file_1); + + /* sorting */ + file_1 = caja_file_get_by_uri ("file:///etc"); + file_2 = caja_file_get_by_uri ("file:///usr"); + + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_1)->ref_count, 1); + EEL_CHECK_INTEGER_RESULT (G_OBJECT (file_2)->ref_count, 1); + + EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_2, CAJA_FILE_SORT_BY_DISPLAY_NAME, FALSE, FALSE) < 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_2, CAJA_FILE_SORT_BY_DISPLAY_NAME, FALSE, TRUE) > 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_1, CAJA_FILE_SORT_BY_DISPLAY_NAME, FALSE, FALSE) == 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_1, CAJA_FILE_SORT_BY_DISPLAY_NAME, TRUE, FALSE) == 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_1, CAJA_FILE_SORT_BY_DISPLAY_NAME, FALSE, TRUE) == 0, TRUE); + EEL_CHECK_BOOLEAN_RESULT (caja_file_compare_for_sort (file_1, file_1, CAJA_FILE_SORT_BY_DISPLAY_NAME, TRUE, TRUE) == 0, TRUE); + + caja_file_unref (file_1); + caja_file_unref (file_2); +} + +#endif /* !CAJA_OMIT_SELF_CHECK */ |