[nautilus] file-operations: check that trashed files are actually in the trash



commit 9bf4390e67e374f7f4f3ee6e757d12feb0d165e6
Author: Razvan Chitu <razvan ch95 gmail com>
Date:   Fri Feb 12 12:51:29 2016 +0200

    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

 libnautilus-private/nautilus-file-operations.c     |  244 ++++++++++++++++++--
 libnautilus-private/nautilus-file-operations.h     |    4 +-
 .../nautilus-file-undo-operations.c                |   86 +-------
 .../nautilus-file-undo-operations.h                |    4 +-
 libnautilus-private/nautilus-trash-monitor.c       |   13 +
 5 files changed, 250 insertions(+), 101 deletions(-)
---
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c
index 74ae194..f60e7b2 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 38b714f..0034c20 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 98e7910..773b8ad 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 ffc1fa2..0646332 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 41745b9..e802c9c 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 {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]