summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRazvan Chitu <razvan.ch95@gmail.com>2016-02-12 12:51:29 +0200
committerCarlos Soriano <csoriano@gnome.org>2016-02-25 13:39:39 +0100
commit9bf4390e67e374f7f4f3ee6e757d12feb0d165e6 (patch)
tree2920b884c8fa44e6a8c5e9ed94d5d9eec05f3516
parent8b565e14fa47972560afe7124e32fc7ee1a8fde8 (diff)
downloadnautilus-9bf4390e67e374f7f4f3ee6e757d12feb0d165e6.tar.gz
file-operations: check that trashed files are actually in the trash
In Nautilus, trash operations are performed and finalized before the trash is updated. Because of this, the option to undo trashing becomes available even if the trash is not aware of new files. If an undo is then requested, the operation would do nothing, because the files would appear to not exist in the trash. A better solution would be to wait for the trash to notified, and then check that the trashed files are actually there. Only then should a trash operation be considered finalized. Add a signal to the trash-monitor that is emitted when the trash changes. Connect trash operations to this signal. On signal emission, asynchronously check that the files are in the trash. Finalize the job if the check was not cancelled and successful. https://bugzilla.gnome.org/show_bug.cgi?id=762126
-rw-r--r--libnautilus-private/nautilus-file-operations.c244
-rw-r--r--libnautilus-private/nautilus-file-operations.h4
-rw-r--r--libnautilus-private/nautilus-file-undo-operations.c86
-rw-r--r--libnautilus-private/nautilus-file-undo-operations.h4
-rw-r--r--libnautilus-private/nautilus-trash-monitor.c13
5 files changed, 250 insertions, 101 deletions
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c
index 74ae1941a..f60e7b29a 100644
--- a/libnautilus-private/nautilus-file-operations.c
+++ b/libnautilus-private/nautilus-file-operations.c
@@ -64,6 +64,15 @@
#include "nautilus-file-undo-operations.h"
#include "nautilus-file-undo-manager.h"
+
+/* Since we use g_get_current_time for setting the trashed file timestamp,
+ * there are situations where the difference between this value and the
+ * real deletion time can differ enough to make the rounding a difference of 1
+ * second, failing the equality check. To make sure we avoid this, and to be
+ * preventive, use 2 seconds epsilon.
+ */
+#define TRASH_TIME_EPSILON 2
+
/* TODO: TESTING!!! */
typedef struct {
@@ -102,9 +111,11 @@ typedef struct {
CommonJob common;
GList *files;
gboolean try_trash;
+ GHashTable *trashed;
gboolean user_cancel;
NautilusDeleteCallback done_callback;
gpointer done_callback_data;
+ GTask *current_check;
} DeleteJob;
typedef struct {
@@ -2006,9 +2017,13 @@ trash_file (CommonJob *job,
gboolean toplevel,
GList *to_delete)
{
+ DeleteJob *delete_job = (DeleteJob*) job;
GError *error;
char *primary, *secondary, *details;
int response;
+ GTimeVal current_time;
+ gsize orig_trash_time;
+
if (should_skip_file (job, file)) {
*skipped_file = TRUE;
@@ -2018,13 +2033,15 @@ trash_file (CommonJob *job,
error = NULL;
if (g_file_trash (file, job->cancellable, &error)) {
+ g_get_current_time (&current_time);
+ orig_trash_time = current_time.tv_sec;
+
+ g_hash_table_insert (delete_job->trashed, g_object_ref (file),
+ GSIZE_TO_POINTER (orig_trash_time));
+
transfer_info->num_files ++;
nautilus_file_changes_queue_file_removed (file);
- if (job->undo_info != NULL) {
- nautilus_file_undo_info_trash_add_file (NAUTILUS_FILE_UNDO_INFO_TRASH (job->undo_info), file);
- }
-
report_trash_progress (job, source_info, transfer_info);
return;
}
@@ -2129,18 +2146,30 @@ trash_files (CommonJob *job,
}
}
-static void
-delete_task_done (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
+static gboolean
+delete_task_done (gpointer user_data)
{
DeleteJob *job;
GHashTable *debuting_uris;
+ NautilusFileUndoInfoTrash *undo_info;
job = user_data;
+ if (job->common.undo_info) {
+ undo_info = NAUTILUS_FILE_UNDO_INFO_TRASH (job->common.undo_info);
+ nautilus_file_undo_info_trash_set_trashed (undo_info, job->trashed);
+ }
+
g_list_free_full (job->files, g_object_unref);
+ if (job->current_check) {
+ g_object_unref (job->current_check);
+ }
+
+ if (job->trashed) {
+ g_hash_table_unref (job->trashed);
+ }
+
if (job->done_callback) {
debuting_uris = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
job->done_callback (debuting_uris, job->user_cancel, job->done_callback_data);
@@ -2150,6 +2179,165 @@ delete_task_done (GObject *source_object,
finalize_common ((CommonJob *)job);
nautilus_file_changes_consume_changes (TRUE);
+
+ return FALSE;
+}
+
+gboolean
+trash_files_match (GList *trash_children,
+ GHashTable *trashed,
+ GHashTable *matched_files)
+{
+ GFile *trash;
+ GList *l;
+ GFileInfo *info;
+ const char *original_path;
+ GFile *original_file;
+ gpointer lookup_value;
+ glong trash_time;
+ glong original_trash_time;
+ GFile *item;
+ guint matched_files_count = 0;
+
+ trash = g_file_new_for_uri ("trash:///");
+
+ /* Iterate over the trash children and check if they match the trashed
+ * files. This is not done as a match between two hash-tables because
+ * the trash can contain multiple files with the same original path
+ */
+ for (l = trash_children; l != NULL; l = l->next) {
+ info = l->data;
+ /* Retrieve the original file uri */
+ original_path = g_file_info_get_attribute_byte_string (info,
+ G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);
+ original_file = g_file_new_for_path (original_path);
+
+ lookup_value = g_hash_table_lookup (trashed, original_file);
+
+ if (lookup_value) {
+ GDateTime *date;
+
+ original_trash_time = GPOINTER_TO_SIZE (lookup_value);
+ trash_time = 0;
+ date = g_file_info_get_deletion_date (info);
+ if (date) {
+ trash_time = g_date_time_to_unix (date);
+ g_date_time_unref (date);
+ }
+
+ if (abs (original_trash_time - trash_time) > TRASH_TIME_EPSILON) {
+ continue;
+ }
+ /* File in the trash */
+ matched_files_count += 1;
+ if (matched_files != NULL) {
+ item = g_file_get_child (trash, g_file_info_get_name (info));
+ g_hash_table_insert (matched_files, item, g_object_ref (original_file));
+ }
+ }
+
+ g_object_unref (original_file);
+
+ }
+
+ g_object_unref (trash);
+
+ return matched_files_count == g_hash_table_size (trashed);
+}
+
+static void
+trash_enumerate_next_files_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task = user_data;
+ DeleteJob *job = g_task_get_task_data (task);
+ GError *error = NULL;
+ GList *infos;
+
+ infos = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source), res, &error);
+
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ trash_files_match (infos, job->trashed, NULL) &&
+ !g_cancellable_is_cancelled (g_task_get_cancellable (task)))
+ {
+ g_signal_handlers_disconnect_by_data (nautilus_trash_monitor_get (), job);
+ g_main_context_invoke (NULL, delete_task_done, job);
+ }
+
+ g_clear_error (&error);
+ g_list_free_full (infos, g_object_unref);
+ g_object_unref (task);
+}
+
+static void
+trash_enumerate_children_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GTask *task = user_data;
+ GFileEnumerator *enumerator;
+ GFile *trash;
+ GError *error = NULL;
+
+ trash = G_FILE (source);
+ enumerator = g_file_enumerate_children_finish (trash, res, &error);
+
+ if (enumerator != NULL) {
+ GFileInfo *trash_info;
+ guint32 trash_item_count;
+
+ trash_info = g_file_query_info (trash,
+ G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT,
+ G_FILE_COPY_NOFOLLOW_SYMLINKS,
+ NULL,
+ NULL);
+ trash_item_count = g_file_info_get_attribute_uint32 (trash_info,
+ G_FILE_ATTRIBUTE_TRASH_ITEM_COUNT);
+ g_file_enumerator_next_files_async (enumerator,
+ trash_item_count,
+ G_PRIORITY_DEFAULT,
+ g_task_get_cancellable (task),
+ trash_enumerate_next_files_cb,
+ task);
+
+ g_object_unref (enumerator);
+ g_object_unref (trash_info);
+ } else {
+ g_object_unref (task);
+ }
+
+ g_clear_error (&error);
+}
+
+static void
+trash_changed_cb (NautilusTrashMonitor *trash_monitor,
+ gpointer user_data)
+{
+ DeleteJob *job = user_data;
+ GFile *trash;
+
+ if (job->current_check != NULL) {
+ g_cancellable_cancel (g_task_get_cancellable (job->current_check));
+ g_clear_object (&job->current_check);
+ }
+
+ job->current_check = g_task_new (NULL, g_cancellable_new (), NULL, NULL);
+ g_task_set_task_data (job->current_check, job, NULL);
+
+ trash = g_file_new_for_uri ("trash:///");
+
+ g_file_enumerate_children_async (trash,
+ G_FILE_ATTRIBUTE_STANDARD_NAME","
+ G_FILE_ATTRIBUTE_TRASH_DELETION_DATE","
+ G_FILE_ATTRIBUTE_TRASH_ORIG_PATH,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ G_PRIORITY_DEFAULT,
+ g_task_get_cancellable (job->current_check),
+ trash_enumerate_children_cb,
+ g_object_ref (job->current_check));
+
+ g_object_unref (trash);
}
static void
@@ -2179,7 +2367,7 @@ delete_task_thread_func (GTask *task,
must_confirm_delete_in_trash = FALSE;
must_confirm_delete = FALSE;
files_skipped = 0;
-
+
for (l = job->files; l != NULL; l = l->next) {
file = l->data;
@@ -2212,21 +2400,31 @@ delete_task_thread_func (GTask *task,
} else {
job->user_cancel = TRUE;
}
+ g_list_free (to_delete_files);
}
if (to_trash_files != NULL) {
to_trash_files = g_list_reverse (to_trash_files);
+ /* Wait until the trash acknowledges the files were trashed to
+ * finish the operation. It usually takes some time for the
+ * trash to acknowledge file movement */
+ g_signal_connect (nautilus_trash_monitor_get (), "trash-changed",
+ (GCallback)trash_changed_cb, job);
trash_files (common, to_trash_files, &files_skipped);
- }
- g_list_free (to_trash_files);
- g_list_free (to_delete_files);
-
- if (files_skipped == g_list_length (job->files)) {
/* User has skipped all files, report user cancel */
- job->user_cancel = TRUE;
+ job->user_cancel = files_skipped == g_list_length (job->files);
+ g_list_free (to_trash_files);
+
+ return;
}
+
+ job->user_cancel = files_skipped == g_list_length (job->files);
+
+ g_main_context_invoke (g_task_get_context (task),
+ delete_task_done,
+ job);
}
static void
@@ -2247,18 +2445,24 @@ trash_or_delete_internal (GList *files,
job->user_cancel = FALSE;
job->done_callback = done_callback;
job->done_callback_data = done_callback_data;
+ job->current_check = NULL;
+ job->trashed = NULL;
if (try_trash) {
inhibit_power_manager ((CommonJob *)job, _("Trashing Files"));
} else {
inhibit_power_manager ((CommonJob *)job, _("Deleting Files"));
}
-
- if (!nautilus_file_undo_manager_is_operating () && try_trash) {
- job->common.undo_info = nautilus_file_undo_info_trash_new (g_list_length (files));
- }
- task = g_task_new (NULL, NULL, delete_task_done, job);
+ if (try_trash) {
+ job->trashed = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
+ g_object_unref, NULL);
+ if (!nautilus_file_undo_manager_is_operating ()) {
+ job->common.undo_info = nautilus_file_undo_info_trash_new (g_list_length (files));
+ }
+ }
+
+ task = g_task_new (NULL, NULL, NULL, NULL);
g_task_set_task_data (task, job, NULL);
g_task_run_in_thread (task, delete_task_thread_func);
g_object_unref (task);
diff --git a/libnautilus-private/nautilus-file-operations.h b/libnautilus-private/nautilus-file-operations.h
index 38b714fd9..0034c20cd 100644
--- a/libnautilus-private/nautilus-file-operations.h
+++ b/libnautilus-private/nautilus-file-operations.h
@@ -84,7 +84,9 @@ void nautilus_file_operations_new_file_from_template (GtkWidget *p
const char *template_uri,
NautilusCreateCallback done_callback,
gpointer data);
-
+gboolean trash_files_match (GList *trash_children,
+ GHashTable *trashed,
+ GHashTable *matched_files);
void nautilus_file_operations_delete (GList *files,
GtkWindow *parent_window,
NautilusDeleteCallback done_callback,
diff --git a/libnautilus-private/nautilus-file-undo-operations.c b/libnautilus-private/nautilus-file-undo-operations.c
index 98e791048..773b8add7 100644
--- a/libnautilus-private/nautilus-file-undo-operations.c
+++ b/libnautilus-private/nautilus-file-undo-operations.c
@@ -33,13 +33,6 @@
#include "nautilus-file.h"
#include "nautilus-file-undo-manager.h"
-/* Since we use g_get_current_time for setting "orig_trash_time" in the undo
- * info, there are situations where the difference between this value and the
- * real deletion time can differ enough to make the rounding a difference of 1
- * second, failing the equality check. To make sure we avoid this, and to be
- * preventive, use 2 seconds epsilon.
- */
-#define TRASH_TIME_EPSILON 2
G_DEFINE_TYPE (NautilusFileUndoInfo, nautilus_file_undo_info, G_TYPE_OBJECT)
@@ -1130,64 +1123,6 @@ trash_enumerate_children (GError **error)
}
static void
-trash_match_files (GList *trash_children,
- GHashTable *trashed,
- GHashTable *matched_files)
-{
- GFile *trash;
- GList *l;
- GFileInfo *info;
- const char *original_path;
- GFile *original_file;
- gpointer lookup_value;
- glong trash_time;
- glong original_trash_time;
- GFile *item;
-
- trash = g_file_new_for_uri ("trash:///");
-
- /* Iterate over the trash children and check if they match the trashed
- * files. This is not done as a match between two hash-tables because
- * the trash can contain multiple files with the same original path
- */
- for (l = trash_children; l != NULL; l = l->next) {
- info = l->data;
- /* Retrieve the original file uri */
- original_path = g_file_info_get_attribute_byte_string (info,
- G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);
- original_file = g_file_new_for_path (original_path);
-
- lookup_value = g_hash_table_lookup (trashed, original_file);
-
- if (lookup_value) {
- GDateTime *date;
-
- original_trash_time = GPOINTER_TO_SIZE (lookup_value);
- trash_time = 0;
- date = g_file_info_get_deletion_date (info);
- if (date) {
- trash_time = g_date_time_to_unix (date);
- g_date_time_unref (date);
- }
-
- if (abs (original_trash_time - trash_time) > TRASH_TIME_EPSILON) {
- continue;
- }
- /* File in the trash */
- if (matched_files != NULL) {
- item = g_file_get_child (trash, g_file_info_get_name (info));
- g_hash_table_insert (matched_files, item, g_object_ref (original_file));
- }
- }
-
- g_object_unref (original_file);
-
- }
-
- g_object_unref (trash);
-}
-
-static void
trash_retrieve_files_to_restore_thread (GSimpleAsyncResult *res,
GObject *object,
GCancellable *cancellable)
@@ -1202,7 +1137,7 @@ trash_retrieve_files_to_restore_thread (GSimpleAsyncResult *res,
g_object_unref, g_object_unref);
trash_children = trash_enumerate_children (&error);
- trash_match_files (trash_children,
+ trash_files_match (trash_children,
self->priv->trashed,
to_restore);
@@ -1285,9 +1220,7 @@ nautilus_file_undo_info_trash_init (NautilusFileUndoInfoTrash *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_trash_get_type (),
NautilusFileUndoInfoTrashDetails);
- self->priv->trashed =
- g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
- g_object_unref, NULL);
+ self->priv->trashed = NULL;
}
static void
@@ -1324,16 +1257,13 @@ nautilus_file_undo_info_trash_new (gint item_count)
}
void
-nautilus_file_undo_info_trash_add_file (NautilusFileUndoInfoTrash *self,
- GFile *file)
+nautilus_file_undo_info_trash_set_trashed (NautilusFileUndoInfoTrash *self,
+ GHashTable *trashed)
{
- GTimeVal current_time;
- gsize orig_trash_time;
-
- g_get_current_time (&current_time);
- orig_trash_time = current_time.tv_sec;
-
- g_hash_table_insert (self->priv->trashed, g_object_ref (file), GSIZE_TO_POINTER (orig_trash_time));
+ if (self->priv->trashed) {
+ g_hash_table_destroy (self->priv->trashed);
+ }
+ self->priv->trashed = g_hash_table_ref (trashed);
}
GList *
diff --git a/libnautilus-private/nautilus-file-undo-operations.h b/libnautilus-private/nautilus-file-undo-operations.h
index ffc1fa2db..06463324d 100644
--- a/libnautilus-private/nautilus-file-undo-operations.h
+++ b/libnautilus-private/nautilus-file-undo-operations.h
@@ -209,8 +209,8 @@ struct _NautilusFileUndoInfoTrashClass {
GType nautilus_file_undo_info_trash_get_type (void) G_GNUC_CONST;
NautilusFileUndoInfo *nautilus_file_undo_info_trash_new (gint item_count);
-void nautilus_file_undo_info_trash_add_file (NautilusFileUndoInfoTrash *self,
- GFile *file);
+void nautilus_file_undo_info_trash_set_trashed (NautilusFileUndoInfoTrash *self,
+ GHashTable *trashed);
GList *nautilus_file_undo_info_trash_get_files (NautilusFileUndoInfoTrash *self);
/* recursive permissions */
diff --git a/libnautilus-private/nautilus-trash-monitor.c b/libnautilus-private/nautilus-trash-monitor.c
index 41745b96d..e802c9c9f 100644
--- a/libnautilus-private/nautilus-trash-monitor.c
+++ b/libnautilus-private/nautilus-trash-monitor.c
@@ -38,6 +38,7 @@ struct NautilusTrashMonitorDetails {
enum {
TRASH_STATE_CHANGED,
+ TRASH_CHANGED,
LAST_SIGNAL
};
@@ -79,6 +80,14 @@ nautilus_trash_monitor_class_init (NautilusTrashMonitorClass *klass)
G_TYPE_NONE, 1,
G_TYPE_BOOLEAN);
+ signals[TRASH_CHANGED] = g_signal_new ("trash-changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
g_type_class_add_private (object_class, sizeof(NautilusTrashMonitorDetails));
}
@@ -107,6 +116,10 @@ enumerate_next_files_cb (GObject *source,
GList *infos;
infos = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source), res, NULL);
+
+ g_signal_emit (trash_monitor,
+ signals[TRASH_CHANGED], 0);
+
if (!infos) {
update_empty_info (trash_monitor, TRUE);
} else {