directorylist: Fix several issues
authorMatthias Clasen <mclasen@redhat.com>
Wed, 12 Jan 2022 02:35:19 +0000 (21:35 -0500)
committerMatthias Clasen <mclasen@redhat.com>
Wed, 12 Jan 2022 02:35:19 +0000 (21:35 -0500)
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

index a164eac8efb58e02f11096ccf68043c400246868..3b8908aefd0487d67978b1736eaf8b67829b9249 100644 (file)
@@ -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);
 }