[gtk/fix-directory-list] directorylist: Fix several issues




commit dcdcc659ef0b4640bd53e407f8bf880235c116b5
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Jan 11 21:35:19 2022 -0500

    directorylist: Fix several issues
    
    We were handling events in the wrong order,
    by doing async calls for some of them, but not
    for all of them.
    
    And we were not taking into account that GFileMonitors
    RENAMED events may or may not move a file on top
    of an existing file.
    
    Fixes: #3784

 gtk/gtkdirectorylist.c | 238 +++++++++++++++++++++++++++++++++++++------------
 1 file changed, 180 insertions(+), 58 deletions(-)
---
diff --git a/gtk/gtkdirectorylist.c b/gtk/gtkdirectorylist.c
index a164eac8ef..3b8908aefd 100644
--- a/gtk/gtkdirectorylist.c
+++ b/gtk/gtkdirectorylist.c
@@ -65,6 +65,25 @@ enum {
   NUM_PROPERTIES
 };
 
+typedef struct _QueuedEvent QueuedEvent;
+struct _QueuedEvent
+{
+  GtkDirectoryList *list;
+  GFile *file;
+  GFileInfo *info;
+  GFileMonitorEvent event;
+};
+
+static void
+free_queued_event (gpointer data)
+{
+  QueuedEvent *event = data;
+
+  g_clear_object (&event->file);
+  g_clear_object (&event->info);
+  g_free (event);
+}
+
 struct _GtkDirectoryList
 {
   GObject parent_instance;
@@ -78,6 +97,7 @@ struct _GtkDirectoryList
   GCancellable *cancellable;
   GError *error; /* Error while loading */
   GSequence *items; /* Use GPtrArray or GListStore here? */
+  GQueue events;
 };
 
 struct _GtkDirectoryListClass
@@ -140,7 +160,6 @@ gtk_directory_list_set_property (GObject      *object,
     case PROP_ATTRIBUTES:
       gtk_directory_list_set_attributes (self, g_value_get_string (value));
       break;
-
     case PROP_FILE:
       gtk_directory_list_set_file (self, g_value_get_object (value));
       break;
@@ -238,6 +257,9 @@ gtk_directory_list_dispose (GObject *object)
   g_clear_error (&self->error);
   g_clear_pointer (&self->items, g_sequence_free);
 
+  g_queue_foreach (&self->events, (GFunc) free_queued_event, NULL);
+  g_queue_clear (&self->events);
+
   G_OBJECT_CLASS (gtk_directory_list_parent_class)->dispose (object);
 }
 
@@ -331,6 +353,7 @@ gtk_directory_list_init (GtkDirectoryList *self)
   self->items = g_sequence_new (g_object_unref);
   self->io_priority = G_PRIORITY_DEFAULT;
   self->monitored = TRUE;
+  g_queue_init (&self->events);
 }
 
 /**
@@ -519,78 +542,136 @@ gtk_directory_list_start_loading (GtkDirectoryList *self)
     g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]);
 }
 
-static void
-got_new_file_info_cb (GObject      *source,
-                      GAsyncResult *res,
-                      gpointer      data)
+static GSequenceIter *
+find_file (GSequence *sequence,
+           GFile     *file)
 {
-  GFile *file = G_FILE (source);
-  GtkDirectoryList *self = GTK_DIRECTORY_LIST (data);
-  GFileInfo *info;
-  guint position;
+  GSequenceIter *iter;
 
-  info = g_file_query_info_finish (file, res, NULL);
-  if (!info)
-    return;
+  for (iter = g_sequence_get_begin_iter (sequence);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter))
+    {
+      GFileInfo *item = G_FILE_INFO (g_sequence_get (iter));
+      GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file"));
 
-  g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file));
-  position = g_sequence_get_length (self->items);
-  g_sequence_append (self->items, info);
-  g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+      if (g_file_equal (f, file))
+        return iter;
+    }
+
+  return NULL;
 }
 
-static void
-got_existing_file_info_cb (GObject      *source,
-                           GAsyncResult *res,
-                           gpointer      data)
+static gboolean
+handle_event (QueuedEvent *event)
 {
-  GFile *file = G_FILE (source);
-  GtkDirectoryList *self = GTK_DIRECTORY_LIST (data);
-  GFileInfo *info;
+  GtkDirectoryList *self = event->list;
+  GFile *file = event->file;
+  GFileInfo *info = event->info;
   GSequenceIter *iter;
+  unsigned int position;
 
-  info = g_file_query_info_finish (file, res, NULL);
-  if (!info)
-    return;
+  switch ((int)event->event)
+    {
+    case G_FILE_MONITOR_EVENT_MOVED_IN:
+    case G_FILE_MONITOR_EVENT_CREATED:
+      if (!info)
+        return FALSE;
 
-  g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file));
+      g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file));
 
-  for (iter = g_sequence_get_begin_iter (self->items);
-       !g_sequence_iter_is_end (iter);
-       iter = g_sequence_iter_next (iter))
-    {
-      GFileInfo *item = g_sequence_get (iter);
-      GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file"));
-      if (g_file_equal (f, file))
+      iter = find_file (self->items, file);
+      if (iter)
         {
-          guint position = g_sequence_iter_get_position (iter);
+          position = g_sequence_iter_get_position (iter);
           g_sequence_set (iter, g_object_ref (info));
           g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 1);
-          break;
         }
+      else
+        {
+          position = g_sequence_get_length (self->items);
+          g_sequence_append (self->items, g_object_ref (info));
+          g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+        }
+      break;
+
+    case G_FILE_MONITOR_EVENT_MOVED_OUT:
+    case G_FILE_MONITOR_EVENT_DELETED:
+      iter = find_file (self->items, file);
+      if (iter)
+        {
+          position = g_sequence_iter_get_position (iter);
+          g_sequence_remove (iter);
+          g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
+        }
+      break;
+
+    case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+      if (!info)
+        return FALSE;
+
+      g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file));
+
+      iter = find_file (self->items, file);
+      if (iter)
+        {
+          position = g_sequence_iter_get_position (iter);
+          g_sequence_set (iter, g_object_ref (info));
+          g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 1);
+        }
+      break;
+
+    default:
+      g_assert_not_reached ();
     }
+
+  return TRUE;
 }
 
 static void
-gtk_directory_list_remove_file (GtkDirectoryList *self,
-                                GFile            *file)
+handle_events (GtkDirectoryList *self)
 {
-  GSequenceIter *iter;
+  QueuedEvent *event;
 
-  for (iter = g_sequence_get_begin_iter (self->items);
-       !g_sequence_iter_is_end (iter);
-       iter = g_sequence_iter_next (iter))
+  do
     {
-      GFileInfo *item = g_sequence_get (iter);
-      GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file"));
-      if (g_file_equal (f, file))
-        {
-          guint position = g_sequence_iter_get_position (iter);
-          g_sequence_remove (iter);
-          g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
-          break;
-        }
+      event = g_queue_peek_tail (&self->events);
+      if (!event)
+        return;
+
+      if (!handle_event (event))
+        return;
+
+      event = g_queue_pop_tail (&self->events);
+      free_queued_event (event);
     }
+  while (TRUE);
+}
+
+static void
+got_new_file_info_cb (GObject      *source,
+                      GAsyncResult *res,
+                      gpointer      data)
+{
+  QueuedEvent *event = data;
+  GtkDirectoryList *self = event->list;
+  GFile *file = event->file;
+
+  event->info = g_file_query_info_finish (file, res, NULL);
+  handle_events (self);
+}
+
+static void
+got_existing_file_info_cb (GObject      *source,
+                           GAsyncResult *res,
+                           gpointer      data)
+{
+  QueuedEvent *event = data;
+  GtkDirectoryList *self = event->list;
+  GFile *file = event->file;
+
+  event->info = g_file_query_info_finish (file, res, NULL);
+  handle_events (self);
 }
 
 static void
@@ -601,30 +682,74 @@ directory_changed (GFileMonitor       *monitor,
                    gpointer            data)
 {
   GtkDirectoryList *self = GTK_DIRECTORY_LIST (data);
+  QueuedEvent *ev;
+
   switch (event)
     {
+    case G_FILE_MONITOR_EVENT_MOVED_IN:
     case G_FILE_MONITOR_EVENT_CREATED:
+      ev = g_new0 (QueuedEvent, 1);
+      ev->list = self;
+      ev->event = event;
+      ev->file = g_object_ref (file);
+      g_queue_push_head (&self->events, ev);
+
       g_file_query_info_async (file,
                                self->attributes,
                                G_FILE_QUERY_INFO_NONE,
                                self->io_priority,
                                self->cancellable,
                                got_new_file_info_cb,
-                               self);
+                               ev);
       break;
 
+    case G_FILE_MONITOR_EVENT_MOVED_OUT:
     case G_FILE_MONITOR_EVENT_DELETED:
-      gtk_directory_list_remove_file (self, file);
+      ev = g_new0 (QueuedEvent, 1);
+      ev->list = self;
+      ev->event = event;
+      ev->file = g_object_ref (file);
+      g_queue_push_head (&self->events, ev);
+
+      handle_events (self);
       break;
 
     case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
+      ev = g_new0 (QueuedEvent, 1);
+      ev->list = self;
+      ev->event = event;
+      ev->file = g_object_ref (file);
+      g_queue_push_head (&self->events, ev);
+
       g_file_query_info_async (file,
                                self->attributes,
                                G_FILE_QUERY_INFO_NONE,
                                self->io_priority,
                                self->cancellable,
                                got_existing_file_info_cb,
-                               self);
+                               ev);
+      break;
+
+    case G_FILE_MONITOR_EVENT_RENAMED:
+      ev = g_new0 (QueuedEvent, 1);
+      ev->list = self;
+      ev->event = G_FILE_MONITOR_EVENT_DELETED;
+      ev->file = g_object_ref (file);
+      g_queue_push_head (&self->events, ev);
+
+      ev = g_new0 (QueuedEvent, 1);
+      ev->list = self;
+      ev->event = G_FILE_MONITOR_EVENT_CREATED;
+      ev->file = g_object_ref (other_file);
+      g_queue_push_head (&self->events, ev);
+
+      g_file_query_info_async (other_file,
+                               self->attributes,
+                               G_FILE_QUERY_INFO_NONE,
+                               self->io_priority,
+                               self->cancellable,
+                               got_existing_file_info_cb,
+                               ev);
       break;
 
     case G_FILE_MONITOR_EVENT_CHANGED:
@@ -632,9 +757,6 @@ directory_changed (GFileMonitor       *monitor,
     case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
     case G_FILE_MONITOR_EVENT_UNMOUNTED:
     case G_FILE_MONITOR_EVENT_MOVED:
-    case G_FILE_MONITOR_EVENT_RENAMED:
-    case G_FILE_MONITOR_EVENT_MOVED_IN:
-    case G_FILE_MONITOR_EVENT_MOVED_OUT:
     default:
       break;
     }
@@ -644,7 +766,7 @@ static void
 gtk_directory_list_start_monitoring (GtkDirectoryList *self)
 {
   g_assert (self->monitor == NULL);
-  self->monitor = g_file_monitor_directory (self->file, G_FILE_MONITOR_NONE, NULL, NULL);
+  self->monitor = g_file_monitor_directory (self->file, G_FILE_MONITOR_WATCH_MOVES, NULL, NULL);
   g_signal_connect (self->monitor, "changed", G_CALLBACK (directory_changed), self);
 }
 


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