[gnome-builder] project-tree: update project tree when file monitor changes



commit 586150304f70813aa4cf7f6fe66b98654936557d
Author: Christian Hergert <chergert redhat com>
Date:   Thu Nov 30 23:00:47 2017 -0800

    project-tree: update project tree when file monitor changes
    
    This tries to react to changes in the project tree that are
    discovered via inotify (or other backend on non-Linux).
    
    Since the recursive file monitor is particularly suited to
    Linux (inotify) in modern GLib versions, we might need to
    ratchet things back when we other platforms such as BSD. I'm not
    sure how scalable the GFileMonitor backends are there.
    
    To keep things more lightweight, this will not respond to
    ignored files by the VCS. I'm not sure its worth the effort to
    try to deal with the "show ignored files" option in this case.
    It will require waking up lots of monitors that I don't want
    to churn handlers upon.
    
    Getting this right could take a lot of tweaks, so if you find
    issues (especially with larger projects) do let me know. I
    can't imagine adding something of this magnitude will handle
    everything on a first try...

 src/plugins/project-tree/gb-project-tree-builder.c |  259 ++++++++++++++++++--
 1 files changed, 243 insertions(+), 16 deletions(-)
---
diff --git a/src/plugins/project-tree/gb-project-tree-builder.c 
b/src/plugins/project-tree/gb-project-tree-builder.c
index 32893a1..24ae4e9 100644
--- a/src/plugins/project-tree/gb-project-tree-builder.c
+++ b/src/plugins/project-tree/gb-project-tree-builder.c
@@ -28,12 +28,29 @@ struct _GbProjectTreeBuilder
   DzlTreeBuilder  parent_instance;
 
   GSettings      *settings;
+  GHashTable     *expanded;
 
   guint           sort_directories_first : 1;
+  guint           has_monitor : 1;
 };
 
 G_DEFINE_TYPE (GbProjectTreeBuilder, gb_project_tree_builder, DZL_TYPE_TREE_BUILDER)
 
+static gint
+compare_nodes_func (DzlTreeNode *a,
+                    DzlTreeNode *b,
+                    gpointer     user_data)
+{
+  GbProjectFile *file_a = GB_PROJECT_FILE (dzl_tree_node_get_item (a));
+  GbProjectFile *file_b = GB_PROJECT_FILE (dzl_tree_node_get_item (b));
+  GbProjectTreeBuilder *self = user_data;
+
+  if (self->sort_directories_first)
+    return gb_project_file_compare_directories_first (file_a, file_b);
+  else
+    return gb_project_file_compare (file_a, file_b);
+}
+
 DzlTreeBuilder *
 gb_project_tree_builder_new (void)
 {
@@ -41,6 +58,136 @@ gb_project_tree_builder_new (void)
 }
 
 static void
+gb_project_tree_builder_add (GbProjectTreeBuilder *self,
+                             DzlTreeNode          *parent,
+                             GFile                *file)
+{
+  g_autofree gchar *name = NULL;
+  g_autoptr(GFileInfo) file_info = NULL;
+  g_autoptr(GbProjectFile) item = NULL;
+  DzlTreeNode *child;
+  const gchar *display_name;
+  const gchar *icon_name;
+  const gchar *expanded = NULL;
+
+  g_assert (GB_IS_PROJECT_TREE_BUILDER (self));
+  g_assert (DZL_IS_TREE_NODE (parent));
+  g_assert (G_IS_FILE (file));
+
+  file_info = g_file_query_info (file,
+                                 G_FILE_ATTRIBUTE_STANDARD_NAME","
+                                 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
+                                 G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                 NULL, NULL);
+  if (file_info == NULL)
+    return;
+
+  item = gb_project_file_new (file, file_info);
+  display_name = gb_project_file_get_display_name (item);
+  icon_name = gb_project_file_get_icon_name (item);
+
+  if (g_strcmp0 (icon_name, "folder-symbolic") == 0)
+    expanded = "folder-open-symbolic";
+
+  child = g_object_new (DZL_TYPE_TREE_NODE,
+                        "icon-name", icon_name,
+                        "expanded-icon-name", expanded,
+                        "text", display_name,
+                        "item", item,
+                        NULL);
+
+  dzl_tree_node_insert_sorted (parent, child, compare_nodes_func, self);
+
+  if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+    {
+      dzl_tree_node_set_children_possible (child, TRUE);
+      dzl_tree_node_set_reset_on_collapse (child, TRUE);
+    }
+}
+
+static void
+gb_project_tree_builder_remove (GbProjectTreeBuilder *self,
+                                DzlTreeNode          *parent,
+                                GFile                *file)
+{
+  DzlTree *tree;
+  GtkTreeModel *model;
+  GtkTreeIter piter;
+  GtkTreeIter iter;
+
+  g_assert (GB_IS_PROJECT_TREE_BUILDER (self));
+  g_assert (DZL_IS_TREE_NODE (parent));
+  g_assert (G_IS_FILE (file));
+
+  if (!dzl_tree_node_get_iter (parent, &piter))
+    return;
+
+  tree = dzl_tree_builder_get_tree (DZL_TREE_BUILDER (self));
+
+  /* TODO: deal with filter? We don't currently use one. */
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+
+  if (gtk_tree_model_iter_children (model, &iter, &piter))
+    {
+      do
+        {
+          g_autoptr(DzlTreeNode) cur = NULL;
+          GObject *item;
+          GFile *cur_file;
+
+          gtk_tree_model_get (model, &iter, 0, &cur, -1);
+
+          item = dzl_tree_node_get_item (cur);
+          if (!GB_IS_PROJECT_FILE (item))
+            continue;
+
+          cur_file = gb_project_file_get_file (GB_PROJECT_FILE (item));
+          if (!G_IS_FILE (cur_file))
+            continue;
+
+          if (g_file_equal (cur_file, file))
+            {
+              dzl_tree_node_remove (parent, cur);
+              return;
+            }
+        }
+      while (gtk_tree_model_iter_next (model, &iter));
+    }
+}
+
+static void
+gb_project_tree_builder_changed (GbProjectTreeBuilder    *self,
+                                 GFile                   *file,
+                                 GFile                   *other_file,
+                                 GFileMonitorEvent        event,
+                                 DzlRecursiveFileMonitor *monitor)
+{
+  g_assert (GB_PROJECT_TREE_BUILDER (self));
+  g_assert (G_IS_FILE (file));
+  g_assert (!other_file || G_IS_FILE (other_file));
+  g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (monitor));
+
+  if (FALSE) {}
+  else if (event == G_FILE_MONITOR_EVENT_CREATED)
+    {
+      g_autoptr(GFile) parent = g_file_get_parent (file);
+      DzlTreeNode *node = g_hash_table_lookup (self->expanded, parent);
+
+      if (node != NULL)
+        gb_project_tree_builder_add (self, node, file);
+    }
+  else if (event == G_FILE_MONITOR_EVENT_DELETED)
+    {
+      g_autoptr(GFile) parent = g_file_get_parent (file);
+      DzlTreeNode *node = g_hash_table_lookup (self->expanded, parent);
+
+      if (node != NULL)
+        gb_project_tree_builder_remove (self, node, file);
+    }
+}
+
+static void
 build_context (GbProjectTreeBuilder *self,
                DzlTreeNode          *node)
 {
@@ -61,6 +208,19 @@ build_context (GbProjectTreeBuilder *self,
   workdir = ide_vcs_get_working_directory (vcs);
   project = ide_context_get_project (context);
 
+  if (!self->has_monitor)
+    {
+      DzlRecursiveFileMonitor *monitor = ide_context_get_monitor (context);
+
+      self->has_monitor = TRUE;
+
+      g_signal_connect_object (monitor,
+                               "changed",
+                               G_CALLBACK (gb_project_tree_builder_changed),
+                               self,
+                               G_CONNECT_SWAPPED);
+    }
+
   file_info = g_file_info_new ();
 
   g_file_info_set_file_type (file_info, G_FILE_TYPE_DIRECTORY);
@@ -100,21 +260,6 @@ get_vcs (DzlTreeNode *node)
   return ide_context_get_vcs (context);
 }
 
-static gint
-compare_nodes_func (DzlTreeNode *a,
-                    DzlTreeNode *b,
-                    gpointer     user_data)
-{
-  GbProjectFile *file_a = GB_PROJECT_FILE (dzl_tree_node_get_item (a));
-  GbProjectFile *file_b = GB_PROJECT_FILE (dzl_tree_node_get_item (b));
-  GbProjectTreeBuilder *self = user_data;
-
-  if (self->sort_directories_first)
-    return gb_project_file_compare_directories_first (file_a, file_b);
-  else
-    return gb_project_file_compare (file_a, file_b);
-}
-
 static void
 build_file (GbProjectTreeBuilder *self,
             DzlTreeNode          *node)
@@ -151,7 +296,7 @@ build_file (GbProjectTreeBuilder *self,
                                           G_FILE_ATTRIBUTE_STANDARD_NAME","
                                           G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
                                           G_FILE_ATTRIBUTE_STANDARD_TYPE,
-                                          G_FILE_QUERY_INFO_NONE,
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                           NULL,
                                           NULL);
 
@@ -390,10 +535,86 @@ gb_project_tree_builder_rebuild (GSettings            *settings,
 }
 
 static void
+gb_project_tree_builder_node_expanded (DzlTreeBuilder *builder,
+                                       DzlTreeNode    *node)
+{
+  GbProjectTreeBuilder *self = (GbProjectTreeBuilder *)builder;
+  GObject *item;
+  GFile *file;
+
+  IDE_ENTRY;
+
+  g_assert (DZL_IS_TREE_BUILDER (self));
+  g_assert (DZL_IS_TREE_NODE (node));
+
+  item = dzl_tree_node_get_item (node);
+  if (!GB_IS_PROJECT_FILE (item))
+    IDE_EXIT;
+
+  file = gb_project_file_get_file (GB_PROJECT_FILE (item));
+  if (!G_IS_FILE (file))
+    IDE_EXIT;
+
+  g_hash_table_insert (self->expanded,
+                       g_object_ref (file),
+                       g_object_ref (node));
+
+  IDE_EXIT;
+}
+
+static void
+gb_project_tree_builder_node_collapsed (DzlTreeBuilder *builder,
+                                        DzlTreeNode    *node)
+{
+  GbProjectTreeBuilder *self = (GbProjectTreeBuilder *)builder;
+  GObject *item;
+  GFile *file;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  IDE_ENTRY;
+
+  g_assert (DZL_IS_TREE_BUILDER (self));
+  g_assert (DZL_IS_TREE_NODE (node));
+
+  item = dzl_tree_node_get_item (node);
+  if (!GB_IS_PROJECT_FILE (item))
+    return;
+
+  file = gb_project_file_get_file (GB_PROJECT_FILE (item));
+  if (!G_IS_FILE (file))
+    return;
+
+  /*
+   * Stop tracking all nodes from this directory on down. We have to
+   * iterate the hashtable so that we can keep our changed handler fast
+   * with a direct file lookup.
+   */
+
+  g_hash_table_iter_init (&iter, self->expanded);
+
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      GFile *dir = key;
+      DzlTreeNode *dir_node = value;
+
+      if (g_file_has_prefix (dir, file) || g_file_equal (dir, file))
+        {
+          g_hash_table_iter_steal (&iter);
+          g_object_unref (dir);
+          g_object_unref (dir_node);
+        }
+    }
+
+  IDE_EXIT;
+}
+
+static void
 gb_project_tree_builder_finalize (GObject *object)
 {
   GbProjectTreeBuilder *self = (GbProjectTreeBuilder *)object;
 
+  g_clear_pointer (&self->expanded, g_hash_table_unref);
   g_clear_object (&self->settings);
 
   G_OBJECT_CLASS (gb_project_tree_builder_parent_class)->finalize (object);
@@ -409,6 +630,8 @@ gb_project_tree_builder_class_init (GbProjectTreeBuilderClass *klass)
 
   tree_builder_class->build_node = gb_project_tree_builder_build_node;
   tree_builder_class->node_activated = gb_project_tree_builder_node_activated;
+  tree_builder_class->node_collapsed = gb_project_tree_builder_node_collapsed;
+  tree_builder_class->node_expanded = gb_project_tree_builder_node_expanded;
   tree_builder_class->node_popup = gb_project_tree_builder_node_popup;
 }
 
@@ -417,6 +640,10 @@ gb_project_tree_builder_init (GbProjectTreeBuilder *self)
 {
   self->settings = g_settings_new ("org.gnome.builder.project-tree");
   self->sort_directories_first = g_settings_get_boolean (self->settings, "sort-directories-first");
+  self->expanded = g_hash_table_new_full (g_file_hash,
+                                          (GEqualFunc) g_file_equal,
+                                          g_object_unref,
+                                          g_object_unref);
 
   g_signal_connect_object (self->settings,
                            "changed::sort-directories-first",


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