[nautilus/wip/csoriano/zoomsv3: 9/9] file-operations: check that trashed files are actually in the trash



commit 27418e487ec4740df32443bde245ab0cffa7c238
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     |  140 ++++++++++++++++++--
 .../nautilus-file-undo-operations.c                |   19 +++-
 .../nautilus-file-undo-operations.h                |    1 +
 libnautilus-private/nautilus-trash-monitor.c       |   14 ++
 4 files changed, 160 insertions(+), 14 deletions(-)
---
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c
index 74ae194..a59b2d6 100644
--- a/libnautilus-private/nautilus-file-operations.c
+++ b/libnautilus-private/nautilus-file-operations.c
@@ -105,6 +105,7 @@ typedef struct {
        gboolean user_cancel;
        NautilusDeleteCallback done_callback;
        gpointer done_callback_data;
+       GTask *current_check;
 } DeleteJob;
 
 typedef struct {
@@ -2129,10 +2130,8 @@ 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;
@@ -2140,6 +2139,7 @@ delete_task_done (GObject *source_object,
        job = user_data;
 
        g_list_free_full (job->files, g_object_unref);
+       g_object_unref (job->current_check);
 
        if (job->done_callback) {
                debuting_uris = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, 
NULL);
@@ -2150,6 +2150,106 @@ delete_task_done (GObject *source_object,
        finalize_common ((CommonJob *)job);
 
        nautilus_file_changes_consume_changes (TRUE);
+
+       return FALSE;
+}
+
+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);
+       NautilusFileUndoInfoTrash *undo_info = NAUTILUS_FILE_UNDO_INFO_TRASH (job->common.undo_info);
+       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) &&
+           nautilus_file_undo_info_trash_files_in_trash (undo_info, infos) &&
+           !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 the enumeration was cancelled, enumerator will be NULL */
+       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 +2279,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 +2312,29 @@ 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);
+
+               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,6 +2355,7 @@ 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;
 
        if (try_trash) {
                inhibit_power_manager ((CommonJob *)job, _("Trashing Files"));
@@ -2254,11 +2363,16 @@ trash_or_delete_internal (GList                  *files,
                inhibit_power_manager ((CommonJob *)job, _("Deleting Files"));
        }
        
-       if (!nautilus_file_undo_manager_is_operating () && try_trash) {
+       /* The undo info for a trash operation should always be renewed, so the
+        * timestamp of the trashed files is set properly. If an undo info is
+        * recycled, this timestamp will be set after the job is finalized, which
+        * can occur many seconds after the files have actually been trashed
+        */
+       if (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);
+       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-undo-operations.c 
b/libnautilus-private/nautilus-file-undo-operations.c
index 98e7910..003af77 100644
--- a/libnautilus-private/nautilus-file-undo-operations.c
+++ b/libnautilus-private/nautilus-file-undo-operations.c
@@ -1129,7 +1129,7 @@ trash_enumerate_children (GError **error)
        return g_list_reverse (children);
 }
 
-static void
+static guint
 trash_match_files (GList *trash_children,
                    GHashTable *trashed,
                    GHashTable *matched_files)
@@ -1143,6 +1143,7 @@ trash_match_files (GList *trash_children,
        glong trash_time;
        glong original_trash_time;
        GFile *item;
+       guint matched_files_count = 0;
 
        trash = g_file_new_for_uri ("trash:///");
 
@@ -1174,6 +1175,7 @@ trash_match_files (GList *trash_children,
                                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));
@@ -1185,6 +1187,8 @@ trash_match_files (GList *trash_children,
        }
 
        g_object_unref (trash);
+
+       return matched_files_count;
 }
 
 static void
@@ -1336,6 +1340,19 @@ nautilus_file_undo_info_trash_add_file (NautilusFileUndoInfoTrash *self,
        g_hash_table_insert (self->priv->trashed, g_object_ref (file), GSIZE_TO_POINTER (orig_trash_time));
 }
 
+/* Checks that the trashed files are actually in the trash */
+gboolean
+nautilus_file_undo_info_trash_files_in_trash (NautilusFileUndoInfoTrash *self, GList *trash_children)
+{
+       guint files_in_trash;
+
+       files_in_trash = trash_match_files (trash_children,
+                                           self->priv->trashed,
+                                           NULL);
+
+       return files_in_trash == g_hash_table_size (self->priv->trashed);
+}
+
 GList *
 nautilus_file_undo_info_trash_get_files (NautilusFileUndoInfoTrash *self)
 {
diff --git a/libnautilus-private/nautilus-file-undo-operations.h 
b/libnautilus-private/nautilus-file-undo-operations.h
index ffc1fa2..0aa9a8a 100644
--- a/libnautilus-private/nautilus-file-undo-operations.h
+++ b/libnautilus-private/nautilus-file-undo-operations.h
@@ -211,6 +211,7 @@ 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);
+gboolean nautilus_file_undo_info_trash_files_in_trash (NautilusFileUndoInfoTrash *self, GList 
*trash_children);
 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..0b5de23 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,15 @@ 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 +117,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]