[gnome-builder] libide/gui: add IdeSession and IdeSessionItem



commit 6b02527c1e23de31eebfa8102767dd76bb54302f
Author: Christian Hergert <chergert redhat com>
Date:   Wed Sep 14 16:55:23 2022 -0700

    libide/gui: add IdeSession and IdeSessionItem
    
    These will be our container objects for state to be preserved across runs
    of Builder and specifically with a project.

 src/libide/gui/ide-session-item-private.h |   32 +
 src/libide/gui/ide-session-item.c         |  560 ++++++++++++++++
 src/libide/gui/ide-session-item.h         |   82 +++
 src/libide/gui/ide-session-private.h      |   51 --
 src/libide/gui/ide-session.c              | 1044 +++++------------------------
 src/libide/gui/ide-session.h              |   67 ++
 src/libide/gui/libide-gui.h               |    1 -
 src/libide/gui/meson.build                |    8 +-
 8 files changed, 908 insertions(+), 937 deletions(-)
---
diff --git a/src/libide/gui/ide-session-item-private.h b/src/libide/gui/ide-session-item-private.h
new file mode 100644
index 000000000..61683d502
--- /dev/null
+++ b/src/libide/gui/ide-session-item-private.h
@@ -0,0 +1,32 @@
+/* ide-session-item-private.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "ide-session-item.h"
+
+G_BEGIN_DECLS
+
+IdeSessionItem *_ide_session_item_new_from_variant (GVariant         *variant,
+                                                    GError          **error);
+void            _ide_session_item_to_variant       (IdeSessionItem  *self,
+                                                    GVariantBuilder  *builder);
+
+G_END_DECLS
diff --git a/src/libide/gui/ide-session-item.c b/src/libide/gui/ide-session-item.c
new file mode 100644
index 000000000..773a142c0
--- /dev/null
+++ b/src/libide/gui/ide-session-item.c
@@ -0,0 +1,560 @@
+/* ide-session-item.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-session-item"
+
+#include "config.h"
+
+#include "ide-session-item-private.h"
+#include "ide-macros.h"
+
+struct _IdeSessionItem
+{
+  GObject parent_instance;
+  PanelPosition *position;
+  char *id;
+  char *type_hint;
+  GHashTable *metadata;
+};
+
+enum {
+  PROP_0,
+  PROP_ID,
+  PROP_POSITION,
+  PROP_TYPE_HINT,
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (IdeSessionItem, ide_session_item, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_session_item_dispose (GObject *object)
+{
+  IdeSessionItem *self = (IdeSessionItem *)object;
+
+  g_clear_object (&self->position);
+
+  g_clear_pointer (&self->id, g_free);
+  g_clear_pointer (&self->type_hint, g_free);
+  g_clear_pointer (&self->metadata, g_hash_table_unref);
+
+  G_OBJECT_CLASS (ide_session_item_parent_class)->dispose (object);
+}
+
+static void
+ide_session_item_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  IdeSessionItem *self = IDE_SESSION_ITEM (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      g_value_set_string (value, ide_session_item_get_id (self));
+      break;
+
+    case PROP_POSITION:
+      g_value_set_object (value, ide_session_item_get_position (self));
+      break;
+
+    case PROP_TYPE_HINT:
+      g_value_set_string (value, ide_session_item_get_type_hint (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_session_item_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  IdeSessionItem *self = IDE_SESSION_ITEM (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      ide_session_item_set_id (self, g_value_get_string (value));
+      break;
+
+    case PROP_POSITION:
+      ide_session_item_set_position (self, g_value_get_object (value));
+      break;
+
+    case PROP_TYPE_HINT:
+      ide_session_item_set_type_hint (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_session_item_class_init (IdeSessionItemClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_session_item_dispose;
+  object_class->get_property = ide_session_item_get_property;
+  object_class->set_property = ide_session_item_set_property;
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id", NULL, NULL, NULL,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_EXPLICIT_NOTIFY |
+                          G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_POSITION] =
+    g_param_spec_object ("position", NULL, NULL,
+                         PANEL_TYPE_POSITION,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_EXPLICIT_NOTIFY |
+                          G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TYPE_HINT] =
+    g_param_spec_string ("type-hint", NULL, NULL, NULL,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_EXPLICIT_NOTIFY |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_session_item_init (IdeSessionItem *self)
+{
+}
+
+/**
+ * ide_session_item_get_id:
+ * @self: a #IdeSessionItem
+ *
+ * Gets the id for the session item, if any.
+ *
+ * Returns: (nullable): a string containing the id; otherwise %NULL
+ */
+const char *
+ide_session_item_get_id (IdeSessionItem *self)
+{
+  g_return_val_if_fail (IDE_IS_SESSION_ITEM (self), NULL);
+
+  return self->id;
+}
+
+/**
+ * ide_session_item_set_id:
+ * @self: a #IdeSessionItem
+ * @id: (nullable): an optional identifier for the item
+ *
+ * Sets the identifier for the item.
+ *
+ * The identifier should generally be global to the session as it would
+ * not be expected to come across multiple items with the same id.
+ */
+void
+ide_session_item_set_id (IdeSessionItem *self,
+                         const char     *id)
+{
+  g_return_if_fail (IDE_IS_SESSION_ITEM (self));
+
+  if (ide_set_string (&self->id, id))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ID]);
+}
+
+/**
+ * ide_session_item_get_type_hint:
+ * @self: a #IdeSessionItem
+ *
+ * Gets the type hint for an item.
+ *
+ * Returns: (nullable): a type-hint or %NULL
+ */
+const char *
+ide_session_item_get_type_hint (IdeSessionItem *self)
+{
+  g_return_val_if_fail (IDE_IS_SESSION_ITEM (self), NULL);
+
+  return self->type_hint;
+}
+
+/**
+ * ide_session_item_set_type_hint:
+ * @self: a #IdeSessionItem
+ * @type_hint: (nullable): a type hint string for the item
+ *
+ * Sets the type-hint value for the item.
+ *
+ * This is generally used to help inflate the right version of
+ * an object when loading session items.
+ */
+void
+ide_session_item_set_type_hint (IdeSessionItem *self,
+                                const char     *type_hint)
+{
+  g_return_if_fail (IDE_IS_SESSION_ITEM (self));
+
+  if (ide_set_string (&self->type_hint, type_hint))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TYPE_HINT]);
+}
+
+/**
+ * ide_session_item_get_position:
+ * @self: a #IdeSessionItem
+ *
+ * Gets the #PanelPosition for the item.
+ *
+ * Returns: (transfer none) (nullable): a #PanelPosition or %NULL
+ */
+PanelPosition *
+ide_session_item_get_position (IdeSessionItem *self)
+{
+  g_return_val_if_fail (IDE_IS_SESSION_ITEM (self), NULL);
+
+  return self->position;
+}
+
+/**
+ * ide_session_item_set_position:
+ * @self: a #IdeSessionItem
+ * @position: (nullable): a #PanelPosition or %NULL
+ *
+ * Sets the position for @self, if any.
+ */
+void
+ide_session_item_set_position (IdeSessionItem *self,
+                               PanelPosition  *position)
+{
+  g_return_if_fail (IDE_IS_SESSION_ITEM (self));
+  g_return_if_fail (!position || PANEL_IS_POSITION (position));
+
+  if (g_set_object (&self->position, position))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION]);
+}
+
+/**
+ * ide_session_item_set_metadata: (skip)
+ * @self: a #IdeSessionItem
+ *
+ * A variadic helper to set metadata.
+ *
+ * The format should be identical to g_variant_new().
+ */
+void
+ide_session_item_set_metadata (IdeSessionItem *self,
+                               const char      *key,
+                               const char      *format,
+                               ...)
+{
+  GVariant *value;
+  va_list args;
+
+  g_return_if_fail (IDE_IS_SESSION_ITEM (self));
+  g_return_if_fail (key != NULL);
+
+  va_start (args, format);
+  value = g_variant_new_va (format, NULL, &args);
+  va_end (args);
+
+  g_return_if_fail (value != NULL);
+
+  ide_session_item_set_metadata_value (self, key, value);
+}
+
+/**
+ * ide_session_item_has_metadata:
+ * @self: a #IdeSessionItem
+ * @key: the name of the metadata
+ * @value_type: (out) (nullable): a location for a #GVariantType or %NULL
+ *
+ * If the item contains a metadata value for @key.
+ *
+ * Checks if a value exists for a metadata key and retrieves the #GVariantType
+ * for that key.
+ *
+ * Returns: %TRUE if @self contains metadata named @key and @value_type is set
+ *   to the value's #GVariantType. Otherwise %FALSE and @value_type is unchanged.
+ */
+gboolean
+ide_session_item_has_metadata (IdeSessionItem     *self,
+                               const char          *key,
+                               const GVariantType **value_type)
+{
+  GVariant *value;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (IDE_IS_SESSION_ITEM (self), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+
+  if ((value = ide_session_item_get_metadata_value (self, key, NULL)))
+    {
+      g_assert (!g_variant_is_floating (value));
+
+      if (value_type != NULL)
+        *value_type = g_variant_get_type (value);
+
+      ret = TRUE;
+
+      g_variant_unref (value);
+    }
+
+  return ret;
+}
+
+/**
+ * ide_session_item_has_metadata_with_type:
+ * @self: a #IdeSessionItem
+ * @key: the metadata key
+ * @expected_type: the #GVariantType to check for @key
+ *
+ * Checks if the item contains metadata @key with @expected_type.
+ *
+ * Returns: %TRUE if a value was found for @key matching @expected_typed;
+ *   otherwise %FALSE is returned.
+ */
+gboolean
+ide_session_item_has_metadata_with_type (IdeSessionItem    *self,
+                                         const char         *key,
+                                         const GVariantType *expected_type)
+{
+  const GVariantType *value_type = NULL;
+
+  g_return_val_if_fail (IDE_IS_SESSION_ITEM (self), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+  g_return_val_if_fail (expected_type != NULL, FALSE);
+
+  if (ide_session_item_has_metadata (self, key, &value_type))
+    return g_variant_type_equal (value_type, expected_type);
+
+  return FALSE;
+}
+
+/**
+ * ide_session_item_get_metadata: (skip)
+ * @self: a #IdeSessionItem
+ * @key: the key for the metadata value
+ * @format: the format of the value
+ *
+ * Extract a metadata value matching @format.
+ *
+ * It is an error to use this function on untrusted data where you have not
+ * checked the type of the value of @key using ide_session_item_has_metadata()
+ * or ide_session_item_has_metadata_with_type().
+ */
+void
+ide_session_item_get_metadata (IdeSessionItem *self,
+                               const char      *key,
+                               const char      *format,
+                               ...)
+{
+  GVariant *value;
+  va_list args;
+
+  g_return_if_fail (IDE_IS_SESSION_ITEM (self));
+  g_return_if_fail (key != NULL);
+  g_return_if_fail (format != NULL);
+  g_return_if_fail (g_variant_type_string_is_valid (format));
+  g_return_if_fail (ide_session_item_has_metadata (self, key, NULL));
+
+  value = ide_session_item_get_metadata_value (self, key, NULL);
+
+  g_return_if_fail (value != NULL);
+
+  va_start (args, format);
+  g_variant_get_va (value, format, NULL, &args);
+  va_end (args);
+
+  g_variant_unref (value);
+}
+
+/**
+ * ide_session_item_get_metadata_value:
+ * @self: a #IdeSessionItem
+ * @key: the metadata key
+ * @expected_type: (nullable): a #GVariantType or %NULL
+ *
+ * Retrieves the metadata value for @key.
+ *
+ * If @expected_type is non-%NULL, any non-%NULL value returned from this
+ * function will match @expected_type.
+ *
+ * Returns: (transfer full): a non-floating #GVariant which should be
+ *   released with g_variant_unref(); otherwise %NULL.
+ */
+GVariant *
+ide_session_item_get_metadata_value (IdeSessionItem     *self,
+                                     const char         *key,
+                                     const GVariantType *expected_type)
+{
+  GVariant *ret;
+
+  g_return_val_if_fail (IDE_IS_SESSION_ITEM (self), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  if (self->metadata == NULL)
+    return NULL;
+
+  if ((ret = g_hash_table_lookup (self->metadata, key)))
+    {
+      if (expected_type == NULL || g_variant_is_of_type (ret, expected_type))
+        return g_variant_ref (ret);
+    }
+
+  return NULL;
+}
+
+/**
+ * ide_session_item_set_metadata_value:
+ * @self: a #IdeSessionItem
+ * @key: the metadata key
+ * @value: (nullable): the value for @key or %NULL
+ *
+ * Sets the value for metadata @key.
+ *
+ * If @value is %NULL, the metadata key is unset.
+ */
+void
+ide_session_item_set_metadata_value (IdeSessionItem *self,
+                                     const char     *key,
+                                     GVariant       *value)
+{
+  g_return_if_fail (IDE_IS_SESSION_ITEM (self));
+  g_return_if_fail (key != NULL);
+
+  if (value != NULL)
+    {
+      if G_UNLIKELY (self->metadata == NULL)
+        self->metadata = g_hash_table_new_full (g_str_hash,
+                                                g_str_equal,
+                                                g_free,
+                                                (GDestroyNotify)g_variant_unref);
+      g_hash_table_insert (self->metadata,
+                           g_strdup (key),
+                           g_variant_ref_sink (value));
+    }
+  else
+    {
+      if (self->metadata != NULL)
+        g_hash_table_remove (self->metadata, key);
+    }
+}
+
+void
+_ide_session_item_to_variant (IdeSessionItem  *self,
+                              GVariantBuilder *builder)
+{
+  g_return_if_fail (IDE_IS_SESSION_ITEM (self));
+  g_return_if_fail (builder != NULL);
+
+  g_variant_builder_open (builder, G_VARIANT_TYPE ("v"));
+  g_variant_builder_open (builder, G_VARIANT_TYPE ("a{sv}"));
+
+  if (self->position != NULL)
+    g_variant_builder_add_parsed (builder,
+                                  "{'position',<%v>}",
+                                  panel_position_to_variant (self->position));
+
+  if (self->id != NULL)
+    g_variant_builder_add_parsed (builder, "{'id',<%s>}", self->id);
+
+  if (self->type_hint != NULL)
+    g_variant_builder_add_parsed (builder, "{'type-hint',<%s>}", self->type_hint);
+
+  if (self->metadata != NULL && g_hash_table_size (self->metadata) > 0)
+    {
+      GHashTableIter iter;
+      const char *key;
+      GVariant *value;
+
+      g_variant_builder_open (builder, G_VARIANT_TYPE ("{sv}"));
+      g_variant_builder_add (builder, "s", "metadata");
+      g_variant_builder_open (builder, G_VARIANT_TYPE ("v"));
+      g_variant_builder_open (builder, G_VARIANT_TYPE ("a{sv}"));
+
+      g_hash_table_iter_init (&iter, self->metadata);
+      while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+        g_variant_builder_add_parsed (builder, "{%s,<%v>}", key, value);
+
+      g_variant_builder_close (builder);
+      g_variant_builder_close (builder);
+      g_variant_builder_close (builder);
+    }
+
+  g_variant_builder_close (builder);
+  g_variant_builder_close (builder);
+}
+
+IdeSessionItem *
+ide_session_item_new (void)
+{
+  return g_object_new (IDE_TYPE_SESSION_ITEM, NULL);
+}
+
+IdeSessionItem *
+_ide_session_item_new_from_variant (GVariant  *variant,
+                                    GError   **error)
+{
+  GVariant *positionv = NULL;
+  GVariant *metadatav = NULL;
+  IdeSessionItem *self;
+
+  g_return_val_if_fail (variant != NULL, NULL);
+  g_return_val_if_fail (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT), NULL);
+
+  self = g_object_new (IDE_TYPE_SESSION_ITEM, NULL);
+
+  g_variant_lookup (variant, "id", "s", &self->id);
+  g_variant_lookup (variant, "type-hint", "s", &self->type_hint);
+
+  if ((positionv = g_variant_lookup_value (variant, "position", NULL)))
+    {
+      GVariant *child = g_variant_get_variant (positionv);
+      self->position = panel_position_new_from_variant (child);
+      g_clear_pointer (&child, g_variant_unref);
+    }
+
+  if ((metadatav = g_variant_lookup_value (variant, "metadata", G_VARIANT_TYPE_VARDICT)))
+    {
+      GVariantIter iter;
+      GVariant *value;
+      char *key;
+
+      g_variant_iter_init (&iter, metadatav);
+
+      while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
+        {
+          GVariant *unwrapped = g_variant_get_variant (value);
+          ide_session_item_set_metadata_value (self, key, unwrapped);
+          g_clear_pointer (&unwrapped, g_variant_unref);
+        }
+    }
+
+  g_clear_pointer (&positionv, g_variant_unref);
+  g_clear_pointer (&metadatav, g_variant_unref);
+
+  return self;
+}
diff --git a/src/libide/gui/ide-session-item.h b/src/libide/gui/ide-session-item.h
new file mode 100644
index 000000000..82c522be9
--- /dev/null
+++ b/src/libide/gui/ide-session-item.h
@@ -0,0 +1,82 @@
+/* ide-session-item.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_GUI_INSIDE) && !defined (IDE_GUI_COMPILATION)
+# error "Only <libide-gui.h> can be included directly."
+#endif
+
+#include <libpanel.h>
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SESSION_ITEM (ide_session_item_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeSessionItem, ide_session_item, IDE, SESSION_ITEM, GObject)
+
+IDE_AVAILABLE_IN_ALL
+IdeSessionItem *ide_session_item_new                    (void);
+IDE_AVAILABLE_IN_ALL
+PanelPosition   *ide_session_item_get_position           (IdeSessionItem     *self);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_item_set_position           (IdeSessionItem     *self,
+                                                           PanelPosition       *position);
+IDE_AVAILABLE_IN_ALL
+const char      *ide_session_item_get_id                 (IdeSessionItem     *self);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_item_set_id                 (IdeSessionItem     *self,
+                                                           const char          *id);
+IDE_AVAILABLE_IN_ALL
+const char      *ide_session_item_get_type_hint          (IdeSessionItem     *self);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_item_set_type_hint          (IdeSessionItem     *self,
+                                                           const char          *type_hint);
+IDE_AVAILABLE_IN_ALL
+gboolean         ide_session_item_has_metadata           (IdeSessionItem     *self,
+                                                           const char          *key,
+                                                           const GVariantType **value_type);
+IDE_AVAILABLE_IN_ALL
+gboolean         ide_session_item_has_metadata_with_type (IdeSessionItem     *self,
+                                                           const char          *key,
+                                                           const GVariantType  *expected_type);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_item_get_metadata           (IdeSessionItem     *self,
+                                                           const char          *key,
+                                                           const char          *format,
+                                                           ...);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_item_set_metadata           (IdeSessionItem     *self,
+                                                           const char          *key,
+                                                           const char          *format,
+                                                           ...);
+IDE_AVAILABLE_IN_ALL
+GVariant        *ide_session_item_get_metadata_value     (IdeSessionItem     *self,
+                                                           const char          *key,
+                                                           const GVariantType  *expected_type);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_item_set_metadata_value     (IdeSessionItem     *self,
+                                                           const char          *key,
+                                                           GVariant            *value);
+
+G_END_DECLS
diff --git a/src/libide/gui/ide-session.c b/src/libide/gui/ide-session.c
index 1f69a27e7..f659fd535 100644
--- a/src/libide/gui/ide-session.c
+++ b/src/libide/gui/ide-session.c
@@ -1,6 +1,6 @@
 /* ide-session.c
  *
- * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2022 Christian Hergert <chergert redhat com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,982 +22,264 @@
 
 #include "config.h"
 
-#include <libpeas/peas.h>
-
-#include <libide-plugins.h>
-#include <libide-threading.h>
-
-#include "ide-frame.h"
-#include "ide-session-addin.h"
-#include "ide-session-private.h"
+#include "ide-session.h"
+#include "ide-session-item-private.h"
 
 struct _IdeSession
 {
-  IdeObject               parent_instance;
-  GPtrArray              *addins;
+  GObject    parent_instance;
+  GPtrArray *items;
 };
 
-typedef struct
-{
-  GPtrArray      *addins;
-  GVariantBuilder pages_state;
-  guint           active;
-  IdeGrid        *grid;
-} Save;
-
-typedef struct
-{
-  GPtrArray *addins;
-  GVariant  *state;
-  IdeGrid   *grid;
-  GArray    *pages;
-  guint      active;
-} Restore;
-
-typedef struct
-{
-  guint            column;
-  guint            row;
-  guint            depth;
-  IdeSessionAddin *addin;
-  GVariant        *state;
-  IdePage         *restored_page;
-} RestoreItem;
-
-G_DEFINE_FINAL_TYPE (IdeSession, ide_session, IDE_TYPE_OBJECT)
-
-static void
-restore_free (Restore *r)
-{
-  g_assert (r != NULL);
-  g_assert (r->active == 0);
-
-  g_clear_pointer (&r->state, g_variant_unref);
-  g_clear_pointer (&r->pages, g_array_unref);
-
-  g_slice_free (Restore, r);
-}
-
-static void
-save_free (Save *s)
-{
-  g_assert (s != NULL);
-  g_assert (s->active == 0);
-
-  g_slice_free (Save, s);
-}
-
-static void
-restore_item_clear (RestoreItem *item)
-{
-  g_assert (item != NULL);
-
-  g_clear_pointer (&item->state, g_variant_unref);
-}
-
-static gint
-compare_restore_items (gconstpointer a,
-                       gconstpointer b)
-{
-  const RestoreItem *item_a = a;
-  const RestoreItem *item_b = b;
-  gint ret;
-
-  if (!(ret = item_a->column - item_b->column))
-    {
-      if (!(ret = item_a->row - item_b->row))
-        ret = item_a->depth - item_b->depth;
-    }
-
-  return ret;
-}
-
-static void
-collect_addins_cb (IdeExtensionSetAdapter *set,
-                   PeasPluginInfo         *plugin_info,
-                   PeasExtension          *exten,
-                   gpointer                user_data)
-{
-  GPtrArray *ar = user_data;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
-  g_assert (plugin_info != NULL);
-  g_assert (IDE_IS_SESSION_ADDIN (exten));
-  g_assert (ar != NULL);
-
-  g_ptr_array_add (ar, g_object_ref (exten));
-}
-
-static IdeSessionAddin *
-find_suitable_addin_for_page (IdePage   *page,
-                              GPtrArray *addins)
-{
-  for (guint i = 0; i < addins->len; i++)
-    {
-      IdeSessionAddin *addin = g_ptr_array_index (addins, i);
-      if (ide_session_addin_can_save_page (addin, page))
-        return addin;
-    }
-  return NULL;
-}
-
-static void
-on_session_autosaved_cb (GObject      *object,
-                         GAsyncResult *result,
-                         gpointer      user_data)
-{
-  IdeSession *session = (IdeSession *)object;
-  g_autoptr(GError) error = NULL;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_SESSION (session));
-  g_assert (G_IS_ASYNC_RESULT (result));
-
-  if (!ide_session_save_finish (session, result, &error))
-    g_warning ("Couldn't autosave session: %s", error->message);
-}
-
-typedef struct {
-  IdeSession *session;
-  IdeGrid    *grid;
-  guint       session_autosave_source;
-} AutosaveGrid;
-
-static void
-autosave_grid_free (gpointer data,
-                    GClosure *closure)
-{
-  AutosaveGrid *self = (AutosaveGrid *)data;
-
-  if (self->session_autosave_source)
-    {
-      g_source_remove (self->session_autosave_source);
-      self->session_autosave_source = 0;
-    }
-  g_slice_free (AutosaveGrid, self);
-}
-
-static gboolean
-on_session_autosave_timeout_cb (gpointer user_data)
-{
-  AutosaveGrid *autosave_grid = (AutosaveGrid *)user_data;
-
-  g_assert (IDE_IS_SESSION (autosave_grid->session));
-  g_assert (IDE_IS_GRID (autosave_grid->grid));
-
-  ide_session_save_async (autosave_grid->session,
-                          autosave_grid->grid,
-                          NULL,
-                          on_session_autosaved_cb,
-                          NULL);
-
-  autosave_grid->session_autosave_source = 0;
-
-  return G_SOURCE_REMOVE;
-}
-
-static void
-schedule_session_autosave_timeout (AutosaveGrid *autosave_grid)
-{
-  if (!autosave_grid->session_autosave_source)
-    {
-      /* We don't want to be saving the state on each (small) change, so introduce a small
-       * timeout so changes are grouped when saving.
-       */
-      autosave_grid->session_autosave_source =
-        g_timeout_add_seconds (30,
-                               on_session_autosave_timeout_cb,
-                               autosave_grid);
-    }
-}
-
-static void
-on_autosave_property_changed_cb (GObject    *gobject,
-                                 GParamSpec *pspec,
-                                 gpointer    user_data)
-{
-  schedule_session_autosave_timeout ((AutosaveGrid *)user_data);
-}
-
-static void
-watch_pages_session_autosave (AutosaveGrid *autosave_grid,
-                              guint         start_pos,
-                              guint         end_pos)
-{
-  GListModel *list = (GListModel *)autosave_grid->grid;
-  IdeSession *session = (IdeSession *)autosave_grid->session;
-
-  g_assert (IDE_IS_SESSION (session));
-  g_assert (G_IS_LIST_MODEL (list));
-  g_assert (g_type_is_a (g_list_model_get_item_type (list), IDE_TYPE_PAGE));
-  g_assert (start_pos <= end_pos);
-
-  for (guint i = start_pos; i < end_pos; i++)
-    {
-      IdePage *page = IDE_PAGE (g_list_model_get_object (list, i));
-      IdeSessionAddin *addin;
-      g_auto(GStrv) props = NULL;
-
-      if ((addin = find_suitable_addin_for_page (page, session->addins)) &&
-          (props = ide_session_addin_get_autosave_properties (addin)))
-        {
-          for (guint j = 0; props[j] != NULL; j++)
-            {
-              char detailed_signal[256];
-              g_snprintf (detailed_signal, sizeof detailed_signal, "notify::%s", props[j]);
-
-              g_signal_connect (page, detailed_signal, G_CALLBACK (on_autosave_property_changed_cb), 
autosave_grid);
-            }
-        }
-    }
-}
-
-static void
-on_grid_items_changed_cb (GListModel *list,
-                          guint       position,
-                          guint       removed,
-                          guint       added,
-                          gpointer    user_data)
-{
-  AutosaveGrid *autosave_grid = (AutosaveGrid *)user_data;
-
-  g_assert (G_IS_LIST_MODEL (list));
-  g_assert (g_type_is_a (g_list_model_get_item_type (list), IDE_TYPE_PAGE));
-
-  /* We've nothing to do when no page were added here as signals are
-   * automatically disconnected, so avoid extra work by stopping here early.
-   */
-  if (added > 0)
-    watch_pages_session_autosave (autosave_grid, position, position + added);
-
-  /* Handles autosaving both when closing/opening a page and when moving a page in the grid. */
-  schedule_session_autosave_timeout (autosave_grid);
-}
-
-static void
-ide_session_destroy (IdeObject *object)
-{
-  IdeSession *self = (IdeSession *)object;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_SESSION (self));
-
-  g_clear_pointer (&self->addins, g_ptr_array_unref);
-
-  IDE_OBJECT_CLASS (ide_session_parent_class)->destroy (object);
-
-  IDE_EXIT;
-}
+G_DEFINE_FINAL_TYPE (IdeSession, ide_session, G_TYPE_OBJECT)
 
 static void
-ide_session_parent_set (IdeObject *object,
-                        IdeObject *parent)
+ide_session_dispose (GObject *object)
 {
   IdeSession *self = (IdeSession *)object;
-  g_autoptr(IdeExtensionSetAdapter) extension_set = NULL;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_SESSION (self));
-  g_assert (!parent || IDE_IS_OBJECT (parent));
-
-  if (parent == NULL)
-    return;
 
-  extension_set = ide_extension_set_adapter_new (IDE_OBJECT (self),
-                                                 peas_engine_get_default (),
-                                                 IDE_TYPE_SESSION_ADDIN,
-                                                 NULL, NULL);
+  g_clear_pointer (&self->items, g_ptr_array_unref);
 
-  self->addins = g_ptr_array_new_with_free_func (g_object_unref);
-  ide_extension_set_adapter_foreach (extension_set, collect_addins_cb, self->addins);
+  G_OBJECT_CLASS (ide_session_parent_class)->dispose (object);
 }
 
 static void
 ide_session_class_init (IdeSessionClass *klass)
 {
-  IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-  i_object_class->destroy = ide_session_destroy;
-  i_object_class->parent_set = ide_session_parent_set;
+  object_class->dispose = ide_session_dispose;
 }
 
 static void
 ide_session_init (IdeSession *self)
 {
+  self->items = g_ptr_array_new_with_free_func (g_object_unref);
 }
 
-static void
-restore_pages_to_grid (GArray  *r_items,
-                       IdeGrid *grid)
-{
-  IDE_ENTRY;
-  for (guint i = 0; i < r_items->len; i++)
-    {
-      RestoreItem *item = &g_array_index (r_items, RestoreItem, i);
-      PanelGridColumn *column;
-      PanelFrame *frame;
-
-      /* Ignore pages that couldn't be restored. */
-      if (item->restored_page == NULL)
-        continue;
-
-      /* This relies on the fact that the items are sorted. */
-      column = panel_grid_get_column (PANEL_GRID (grid), item->column);
-      frame = panel_grid_column_get_row (column, item->row);
-
-      panel_frame_add (frame, PANEL_WIDGET (item->restored_page));
-    }
-  IDE_EXIT;
-}
-
-typedef struct
-{
-  IdeTask     *task;
-  RestoreItem *item;
-} RestorePage;
-
-static void
-on_session_addin_page_restored_cb (GObject      *object,
-                                   GAsyncResult *result,
-                                   gpointer      user_data)
+/**
+ * ide_session_to_variant:
+ * @self: a #IdeSession
+ *
+ * Serializes a #IdeSession as a #GVariant
+ *
+ * The result of this function may be passed to
+ * ide_session_new_from_variant() to recreate a #IdeSession.
+ *
+ * Returns: (transfer full): a #GVariant
+ */
+GVariant *
+ide_session_to_variant (IdeSession *self)
 {
-  IdeSessionAddin *addin = (IdeSessionAddin *)object;
-  RestorePage *r_page = user_data;
-  g_autoptr(IdeTask) task = r_page->task;
-  g_autoptr(GError) error = NULL;
-  RestoreItem *item = r_page->item;
-  Restore *r;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_SESSION_ADDIN (addin));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
-
-  r = ide_task_get_task_data (task);
-
-  g_assert (r != NULL);
-  g_assert (r->addins != NULL);
-  g_assert (r->active > 0);
-  g_assert (r->state != NULL);
+  GVariantBuilder builder;
 
-  if (!(item->restored_page = ide_session_addin_restore_page_finish (addin, result, &error)))
-    g_warning ("Couldn't restore page with addin %s: %s", G_OBJECT_TYPE_NAME (addin), error->message);
+  g_return_val_if_fail (IDE_IS_SESSION (self), NULL);
 
-  r->active--;
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+    g_variant_builder_add_parsed (&builder, "{'version',<%u>}", 1);
+    g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sv}"));
+      g_variant_builder_add (&builder, "s", "items");
+      g_variant_builder_open (&builder, G_VARIANT_TYPE ("v"));
+        g_variant_builder_open (&builder, G_VARIANT_TYPE ("av"));
+        for (guint i = 0; i < self->items->len; i++)
+          {
+            IdeSessionItem *item = g_ptr_array_index (self->items, i);
 
-  if (r->active == 0)
-    {
-      restore_pages_to_grid (r->pages, r->grid);
-
-      ide_task_return_boolean (task, TRUE);
-    }
-
-  IDE_EXIT;
+            _ide_session_item_to_variant (item, &builder);
+          }
+        g_variant_builder_close (&builder);
+      g_variant_builder_close (&builder);
+    g_variant_builder_close (&builder);
+  return g_variant_builder_end (&builder);
 }
 
-static IdeSessionAddin *
-get_addin_for_name (GPtrArray  *addins,
-                    const char *addin_name)
-{
-  GType addin_type = g_type_from_name (addin_name);
-  for (guint i = 0; i < addins->len; i++)
-    {
-      if (G_OBJECT_TYPE (addins->pdata[i]) == addin_type)
-        return addins->pdata[i];
-    }
-  return NULL;
-}
-
-static void
-load_restore_items (Restore *r,
-                    GArray  *items)
+static gboolean
+ide_session_load_1 (IdeSession  *self,
+                    GVariant    *variant,
+                    GError     **error)
 {
-  GVariantIter iter;
-  RestoreItem item;
-  GVariant *page_state = NULL;
+  GVariant *items = NULL;
+  gboolean ret = FALSE;
 
-  g_assert (r != NULL);
-  g_assert (r->state != NULL);
-  g_assert (r->addins != NULL);
-  g_assert (items != NULL);
+  g_assert (IDE_IS_SESSION (self));
+  g_assert (variant != NULL);
+  g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT));
 
-  g_variant_iter_init (&iter, r->state);
-  while ((page_state = g_variant_iter_next_value (&iter)))
+  if ((items = g_variant_lookup_value (variant, "items", G_VARIANT_TYPE ("av"))))
     {
-      const char *addin_name = NULL;
-
-      g_variant_lookup (page_state, "column", "u", &item.column);
-      g_variant_lookup (page_state, "row", "u", &item.row);
-      g_variant_lookup (page_state, "depth", "u", &item.depth);
-      g_variant_lookup (page_state, "addin_name", "&s", &addin_name);
-      g_variant_lookup (page_state, "addin_page_state", "v", &item.state);
-
-      item.addin = get_addin_for_name (r->addins, addin_name);
-      g_array_append_val (items, item);
-
-      g_variant_unref (page_state);
-    }
-}
+      gsize n_children = g_variant_n_children (items);
 
-static GVariant *
-migrate_pre_api_rework (GVariant *pages_variant)
-{
-  GVariantIter iter;
-  const char *uri = NULL;
-  int column, row, depth;
-  /* Freed in the loop. */
-  GVariant *search_variant;
+      for (gsize i = 0; i < n_children; i++)
+        {
+          GVariant *itemv = g_variant_get_child_value (items, i);
+          GVariant *infov = g_variant_get_variant (itemv);
+          IdeSessionItem *item = _ide_session_item_new_from_variant (infov, error);
 
-  GVariantDict version_wrapper_dict;
-  GVariantBuilder addins_states;
+          g_clear_pointer (&infov, g_variant_unref);
+          g_clear_pointer (&itemv, g_variant_unref);
 
-  g_variant_dict_init (&version_wrapper_dict, NULL);
-  /* Migrate old format to first version of the new format. */
-  g_variant_dict_insert (&version_wrapper_dict, "version", "u", (guint32) 1);
+          if (item == NULL)
+            goto cleanup;
 
-  g_variant_builder_init (&addins_states, G_VARIANT_TYPE ("aa{sv}"));
+          g_ptr_array_add (self->items, g_steal_pointer (&item));
+        }
 
-  g_debug ("Handling migration of the project's session.gvariant, from prior to the Session API rework…");
+      ret = TRUE;
 
-  g_variant_iter_init (&iter, pages_variant);
-  while (g_variant_iter_next (&iter, "(&siiiv)", &uri, &column, &row, &depth, &search_variant))
-    {
-      GVariantDict addin_state;
-      GVariantDict editor_session_state;
-
-      g_variant_dict_init (&addin_state, NULL);
-      g_variant_dict_insert (&addin_state, "column", "u", (guint32) column);
-      g_variant_dict_insert (&addin_state, "row", "u", (guint32) row);
-      g_variant_dict_insert (&addin_state, "depth", "u", (guint32) depth);
-      g_variant_dict_insert (&addin_state, "addin_name", "s", "GbpEditorSessionAddin");
-
-      /* Since we need to migrate the data for the new API, let's also migrate to a dictionary
-       * instead of a tuple, for greater flexibility and extensibility in the future.
-       */
-      g_variant_dict_init (&editor_session_state, NULL);
-      g_variant_dict_insert (&editor_session_state, "uri", "s", uri);
-      /* Unbox the search_variant since we don't want to bother with multiple levels of variants,
-       * just have an a{sv}
-       */
-      g_variant_dict_insert_value (&editor_session_state, "search", g_variant_get_variant (search_variant));
-      g_variant_dict_insert (&addin_state, "addin_page_state", "v", g_variant_dict_end 
(&editor_session_state));
-      g_variant_builder_add_value (&addins_states, g_variant_dict_end (&addin_state));
-
-      g_variant_unref (search_variant);
+      goto cleanup;
     }
 
-  g_variant_dict_insert_value (&version_wrapper_dict, "data", g_variant_builder_end (&addins_states));
+  g_set_error_literal (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_DATA,
+                       "items missing from variant");
 
-  g_debug ("Successfully migrated old session.gvariant to new format.");
+cleanup:
+  g_clear_pointer (&items, g_variant_unref);
 
-  return g_variant_take_ref (g_variant_dict_end (&version_wrapper_dict));
+  return ret;
 }
 
-static GVariant *
-load_state_with_migrations (GBytes *bytes)
+static gboolean
+ide_session_load (IdeSession  *self,
+                  GVariant    *variant,
+                  GError     **error)
 {
-  g_autoptr(GVariant) variant = NULL;
-  /* This is the value of the "data" key in the final @variant. */
-  g_autoptr(GVariant) migrated_state = NULL;
-  g_autoptr(GVariant) old_api_state = NULL;
-  gboolean fully_migrated = FALSE;
+  guint version = 0;
 
-  g_assert (bytes != NULL);
-
-  variant = g_variant_take_ref (g_variant_new_from_bytes (G_VARIANT_TYPE_VARDICT, bytes, FALSE));
+  g_assert (IDE_IS_SESSION (self));
+  g_assert (variant != NULL);
+  g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT));
 
-  if (!variant)
+  if (g_variant_lookup (variant, "version", "u", &version))
     {
-      g_warning ("Couldn't load the array of pages' states from session.gvariant!");
-      return NULL;
+      if (version == 1)
+        return ide_session_load_1 (self, variant, error);
     }
 
-  /* Handle migrations from prior to the Session API rework, where there was only GbpEditorSessionAddin that 
used it */
-  old_api_state = g_variant_lookup_value (variant, "GbpEditorSessionAddin", G_VARIANT_TYPE ("a(siiiv)"));
-  if (old_api_state)
-    migrated_state = migrate_pre_api_rework (old_api_state);
-  else
-    migrated_state = g_steal_pointer (&variant);
+  g_set_error_literal (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_DATA,
+                       "Invalid version number in serialized session");
 
-  while (!fully_migrated)
-    {
-      guint32 version;
-      g_autoptr(GVariant) versioned_data = NULL;
-
-      if (!g_variant_lookup (migrated_state, "version", "u", &version))
-        {
-          g_warning ("session.gvariant isn't using the old format but doesn't have a version field, so 
cannot load it!");
-          fully_migrated = TRUE;
-          migrated_state = NULL;
-          break;
-        }
-
-      if (!(versioned_data = g_variant_lookup_value (migrated_state, "data", G_VARIANT_TYPE ("aa{sv}"))))
-        {
-          g_warning ("session.gvariant had a version field but the actual versioned data wasn't found, so 
cannot load it!");
-          fully_migrated = TRUE;
-          migrated_state = NULL;
-          break;
-        }
-
-      switch (version)
-        {
-          /* It's the current format so the rest of the code understands it natively. */
-          case 1:
-            migrated_state = g_steal_pointer (&migrated_state);
-            fully_migrated = TRUE;
-            break;
-
-          default:
-            g_warning ("Version %d of session.gvariant data is not known to Builder!", version);
-            migrated_state = NULL;
-            fully_migrated = TRUE;
-        }
-    }
-
-  if (migrated_state)
-    /* The current format (version 1) is an `aa{sv}` (array of dictionaries) with the dict's keys being:
-     * guint32 column, row, depth;
-     * char *addin_name;
-     * GVariant *addin_page_state;
-     */
-    return g_variant_lookup_value (migrated_state, "data", NULL);
-  else
-    return NULL;
+  return FALSE;
 }
 
-static void
-on_session_cache_loaded_cb (GObject      *object,
-                            GAsyncResult *result,
-                            gpointer      user_data)
+IdeSession *
+ide_session_new (void)
 {
-  GFile *file = (GFile *)object;
-  g_autoptr(IdeTask) task = user_data;
-  g_autoptr(GError) error = NULL;
-  g_autoptr(GBytes) bytes = NULL;
-  GArray *items = NULL;
-  GCancellable *cancellable;
-  Restore *r;
-
-  IDE_ENTRY;
-
-  g_assert (G_IS_FILE (file));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
-
-  r = ide_task_get_task_data (task);
-  cancellable = ide_task_get_cancellable (task);
-
-  g_assert (r != NULL);
-  g_assert (r->addins != NULL);
-  g_assert (r->addins->len > 0);
-  g_assert (r->state == NULL);
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  if (!(bytes = g_file_load_bytes_finish (file, result, NULL, &error)))
-    {
-      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
-        ide_task_return_boolean (task, TRUE);
-      else
-        ide_task_return_error (task, g_steal_pointer (&error));
-      IDE_EXIT;
-    }
-
-  if (g_bytes_get_size (bytes) == 0)
-    {
-      ide_task_return_boolean (task, TRUE);
-      IDE_EXIT;
-    }
-
-  r->state = load_state_with_migrations (bytes);
-
-  if (r->state == NULL)
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_INVALID_DATA,
-                                 "Failed to decode session state");
-      IDE_EXIT;
-    }
-
-  items = g_array_new (FALSE, FALSE, sizeof (RestoreItem));
-  g_array_set_clear_func (items, (GDestroyNotify)restore_item_clear);
-  load_restore_items (r, items);
-  r->pages = items;
-  r->active = items->len;
-  g_array_sort (items, compare_restore_items);
-
-  for (guint i = 0; i < items->len; i++)
-    {
-      RestoreItem *item = &g_array_index (items, RestoreItem, i);
-      RestorePage *r_page = g_slice_new0 (RestorePage);
-      r_page->task = g_object_ref (task);
-      r_page->item = item;
-
-      ide_session_addin_restore_page_async (item->addin,
-                                            item->state,
-                                            cancellable,
-                                            on_session_addin_page_restored_cb,
-                                            r_page);
-    }
-
-  if (r->active == 0)
-    {
-      ide_task_return_boolean (task, TRUE);
-      IDE_EXIT;
-    }
-
-  IDE_EXIT;
+  return g_object_new (IDE_TYPE_SESSION, NULL);
 }
 
 /**
- * ide_session_restore_async:
- * @self: an #IdeSession
- * @grid: an #IdeGrid
- * @cancellable: (nullable): a #GCancellable or %NULL
- * @callback: the callback to execute upon completion
- * @user_data: user data for callback
+ * ide_session_new_from_variant:
+ * @variant: a #GVariant from ide_session_to_variant()
+ * @error: a location for a #GError, or %NULL
  *
- * This function will asynchronously restore the state of the project to
- * the point it was last saved (typically upon shutdown). This includes
- * open documents and editor splits to the degree possible. Adding support
- * for a new page type requires implementing an #IdeSessionAddin.
+ * Creates a new #IdeSession from a #GVariant.
+ *
+ * This creates a new #IdeSession instance from a previous session
+ * which had been serialized to @variant.
+ *
+ * Returns: (transfer full): a #IdeSession
  */
-void
-ide_session_restore_async (IdeSession          *self,
-                           IdeGrid             *grid,
-                           GCancellable        *cancellable,
-                           GAsyncReadyCallback  callback,
-                           gpointer             user_data)
+IdeSession *
+ide_session_new_from_variant (GVariant  *variant,
+                              GError   **error)
 {
-  g_autoptr(IdeTask) task = NULL;
-  g_autoptr(GFile) file = NULL;
-  g_autoptr(GSettings) settings = NULL;
-  IdeContext *context;
-  Restore *r;
-
-  IDE_ENTRY;
-
-  g_return_if_fail (IDE_IS_SESSION (self));
-  g_return_if_fail (IDE_IS_GRID (grid));
-  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  task = ide_task_new (self, cancellable, callback, user_data);
-  ide_task_set_source_tag (task, ide_session_restore_async);
-
-  r = g_slice_new0 (Restore);
-  r->addins = self->addins;
-  r->grid = grid;
-  ide_task_set_task_data (task, r, restore_free);
+  IdeSession *self;
 
-  settings = g_settings_new ("org.gnome.builder");
-  if (!g_settings_get_boolean (settings, "restore-previous-files"))
-    {
-      ide_task_return_boolean (task, TRUE);
-      return;
-    }
+  g_return_val_if_fail (variant != NULL, NULL);
+  g_return_val_if_fail (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT), NULL);
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-  file = ide_context_cache_file (context, "session.gvariant", NULL);
+  self = g_object_new (IDE_TYPE_SESSION, NULL);
 
-  g_file_load_bytes_async (file,
-                           cancellable,
-                           on_session_cache_loaded_cb,
-                           g_steal_pointer (&task));
+  if (!ide_session_load (self, variant, error))
+    g_clear_object (&self);
 
-  IDE_EXIT;
+  return self;
 }
 
-gboolean
-ide_session_restore_finish (IdeSession    *self,
-                            GAsyncResult  *result,
-                            GError       **error)
+guint
+ide_session_get_n_items (IdeSession *self)
 {
-  gboolean ret;
-  Restore *r;
-  GListModel *list;
-  AutosaveGrid *autosave_grid;
-
-  IDE_ENTRY;
-
-  g_return_val_if_fail (IDE_IS_SESSION (self), FALSE);
-  g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
-
-  r = ide_task_get_task_data (IDE_TASK (result));
-  g_assert (r != NULL);
-  list = G_LIST_MODEL (r->grid);
-
-  autosave_grid = g_slice_new0 (AutosaveGrid);
-  autosave_grid->grid = r->grid;
-  autosave_grid->session = self;
-  autosave_grid->session_autosave_source = 0;
-
-  watch_pages_session_autosave (autosave_grid,
-                                0, g_list_model_get_n_items (list));
-  g_signal_connect_data (list,
-                         "items-changed",
-                         G_CALLBACK (on_grid_items_changed_cb),
-                         autosave_grid,
-                         autosave_grid_free,
-                         0);
+  g_return_val_if_fail (IDE_IS_SESSION (self), 0);
 
-  ret = ide_task_propagate_boolean (IDE_TASK (result), error);
-
-  IDE_RETURN (ret);
+  return self->items->len;
 }
 
-static void
-on_state_saved_to_cache_file_cb (GObject      *object,
-                                 GAsyncResult *result,
-                                 gpointer      user_data)
+/**
+ * ide_session_get_item:
+ * @self: a #IdeSession
+ * @position: the index of the item
+ *
+ * Gets the item at @position.
+ *
+ * Returns: (transfer none) (nullable): The #IdeSessionItem at @position
+ *   or %NULL if there is no item at that position.
+ */
+IdeSessionItem *
+ide_session_get_item (IdeSession *self,
+                      guint       position)
 {
-  GFile *file = (GFile *)object;
-  g_autoptr(GError) error = NULL;
-  g_autoptr(IdeTask) task = user_data;
-
-  IDE_ENTRY;
+  g_return_val_if_fail (IDE_IS_SESSION (self), NULL);
 
-  g_assert (G_IS_FILE (file));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
-
-  if (!g_file_replace_contents_finish (file, result, NULL, &error))
-    ide_task_return_error (task, g_steal_pointer (&error));
-  else
-    ide_task_return_boolean (task, TRUE);
+  if (position >= self->items->len)
+    return NULL;
 
-  IDE_EXIT;
+  return g_ptr_array_index (self->items, position);
 }
 
-typedef struct {
-  IdeTask *task;
-  IdePage *page;
-} SavePage;
-
-static void
-save_state_to_disk (IdeSession      *self,
-                    IdeTask         *task,
-                    GVariantBuilder *pages_state)
+void
+ide_session_remove (IdeSession     *self,
+                    IdeSessionItem *item)
 {
-  g_autoptr(GVariant) state = NULL;
-  g_autoptr(GBytes) bytes = NULL;
-  g_autoptr(GFile) file = NULL;
-  GCancellable *cancellable;
-  IdeContext *context;
-  GVariantDict final_dict;
+  guint position;
 
-  IDE_ENTRY;
+  g_return_if_fail (IDE_IS_SESSION (self));
+  g_return_if_fail (IDE_IS_SESSION_ITEM (item));
 
-  g_assert (IDE_IS_SESSION (self));
-  g_assert (IDE_IS_TASK (task));
-  g_assert (pages_state != NULL);
-
-  cancellable = ide_task_get_cancellable (task);
-
-  g_variant_dict_init (&final_dict, NULL);
-  g_variant_dict_insert (&final_dict, "version", "u", (guint32) 1);
-  g_variant_dict_insert_value (&final_dict, "data", g_variant_builder_end (pages_state));
-
-  state = g_variant_ref_sink (g_variant_dict_end (&final_dict));
-  bytes = g_variant_get_data_as_bytes (state);
-
-#ifdef IDE_ENABLE_TRACE
-  {
-    g_autofree char *str = g_variant_print (state, TRUE);
-    IDE_TRACE_MSG ("Saving session state to %s", str);
-  }
-#endif
-
-  context = ide_object_get_context (IDE_OBJECT (self));
-  file = ide_context_cache_file (context, "session.gvariant", NULL);
-
-  if (!ide_task_return_error_if_cancelled (task))
-    g_file_replace_contents_bytes_async (file,
-                                         bytes,
-                                         NULL,
-                                         FALSE,
-                                         G_FILE_CREATE_NONE,
-                                         cancellable,
-                                         on_state_saved_to_cache_file_cb,
-                                         g_object_ref (task));
-
-  IDE_EXIT;
+  if (g_ptr_array_find (self->items, item, &position))
+    ide_session_remove_at (self, position);
 }
 
-static void
-on_session_addin_page_saved_cb (GObject      *object,
-                                GAsyncResult *result,
-                                gpointer      user_data)
+void
+ide_session_remove_at (IdeSession *self,
+                       guint       position)
 {
-  IdeSessionAddin *addin = (IdeSessionAddin *)object;
-  g_autoptr(GVariant) page_state = NULL;
-  SavePage *save_page = user_data;
-  g_autoptr(IdeTask) task = save_page->task;
-  IdePage *page = save_page->page;
-  g_autoptr(GError) error = NULL;
-  IdeSession *self;
-  Save *s;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_SESSION_ADDIN (addin));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
-
-  self = ide_task_get_source_object (task);
-  s = ide_task_get_task_data (task);
-
-  g_assert (IDE_IS_SESSION (self));
-  g_assert (s != NULL);
-  g_assert (s->active > 0);
-
-  page_state = ide_session_addin_save_page_finish (addin, result, &error);
-
-  if (error != NULL)
-    g_warning ("Could not save page with addin %s: %s", G_OBJECT_TYPE_NAME (addin), error->message);
-
-  if (page_state != NULL)
-    {
-      guint frame_column, frame_row, frame_depth;
-      GVariantDict state_dict;
-
-      g_assert (!g_variant_is_floating (page_state));
-
-      ide_grid_get_page_position (s->grid, page, &frame_column, &frame_row, &frame_depth);
-
-      g_variant_dict_init (&state_dict, NULL);
-      g_variant_dict_insert (&state_dict, "column", "u", frame_column);
-      g_variant_dict_insert (&state_dict, "row", "u", frame_row);
-      g_variant_dict_insert (&state_dict, "depth", "u", frame_depth);
-      g_variant_dict_insert (&state_dict, "addin_name", "s", G_OBJECT_TYPE_NAME (addin));
-      g_variant_dict_insert (&state_dict, "addin_page_state", "v", page_state);
-
-      g_variant_builder_add_value (&s->pages_state, g_variant_dict_end (&state_dict));
-    }
-
-  g_slice_free (SavePage, save_page);
-
-  s->active--;
-
-  if (s->active == 0)
-    save_state_to_disk (self, task, &s->pages_state);
+  g_return_if_fail (IDE_IS_SESSION (self));
+  g_return_if_fail (position < self->items->len);
 
-  IDE_EXIT;
+  g_ptr_array_remove_index (self->items, position);
 }
 
-static void
-foreach_page_in_grid_save_cb (IdePage  *page,
-                              gpointer  user_data)
+void
+ide_session_append (IdeSession     *self,
+                    IdeSessionItem *item)
 {
-  IdeTask *task = user_data;
-  IdeSessionAddin *addin;
-  SavePage *save_page = NULL;
-  Save *s;
-
-  g_assert (IDE_IS_PAGE (page));
-  g_assert (IDE_IS_TASK (task));
-
-  s = ide_task_get_task_data (task);
-
-  g_assert (s != NULL);
-  g_assert (s->addins != NULL);
-
-  if (!(addin = find_suitable_addin_for_page (page, s->addins)))
-    {
-      /* It's not a saveable page. */
-      s->active--;
-      return;
-    }
-
-  save_page = g_slice_new0 (SavePage);
-  save_page->task = g_object_ref (task);
-  save_page->page = page;
+  g_return_if_fail (IDE_IS_SESSION (self));
+  g_return_if_fail (IDE_IS_SESSION_ITEM (item));
 
-  ide_session_addin_save_page_async (addin,
-                                     page,
-                                     ide_task_get_cancellable (task),
-                                     on_session_addin_page_saved_cb,
-                                     g_steal_pointer (&save_page));
+  g_ptr_array_add (self->items, g_object_ref (item));
 }
 
-/**
- * ide_session_save_async:
- * @self: an #IdeSession
- * @grid: an #IdeGrid
- * @cancellable: (nullable): a #GCancellable, or %NULL
- * @callback: a callback to execute upon completion
- * @user_data: user data for @callback
- *
- * This function will save the position and content of the pages in the @grid,
- * which can then be restored with ide_session_restore_async(), asking the
- * content of the pages to the appropriate #IdeSessionAddin.
- */
 void
-ide_session_save_async (IdeSession          *self,
-                        IdeGrid             *grid,
-                        GCancellable        *cancellable,
-                        GAsyncReadyCallback  callback,
-                        gpointer             user_data)
+ide_session_prepend (IdeSession     *self,
+                     IdeSessionItem *item)
 {
-  g_autoptr(IdeTask) task = NULL;
-  Save *s;
-
-  IDE_ENTRY;
-
   g_return_if_fail (IDE_IS_SESSION (self));
-  g_return_if_fail (IDE_IS_GRID (grid));
-  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  task = ide_task_new (self, cancellable, callback, user_data);
-  ide_task_set_source_tag (task, ide_session_save_async);
+  g_return_if_fail (IDE_IS_SESSION_ITEM (item));
 
-  s = g_slice_new0 (Save);
-  s->addins = self->addins;
-  s->grid = grid;
-  s->active = ide_grid_count_pages (s->grid);
-
-  g_variant_builder_init (&s->pages_state, G_VARIANT_TYPE ("aa{sv}"));
-  ide_task_set_task_data (task, s, save_free);
-
-  ide_grid_foreach_page (s->grid,
-                         foreach_page_in_grid_save_cb,
-                         task);
-
-  g_assert (s != NULL);
-
-  /* Save the empty pages state there too because it wouldn't have
-   * been done in foreach_page_in_grid_save_cb() since there's no
-   * pages to save.
-   */
-  if (s->active == 0)
-    save_state_to_disk (self, task, &s->pages_state);
-
-  IDE_EXIT;
+  g_ptr_array_insert (self->items, 0, g_object_ref (item));
 }
 
-gboolean
-ide_session_save_finish (IdeSession    *self,
-                         GAsyncResult  *result,
-                         GError       **error)
+void
+ide_session_insert (IdeSession     *self,
+                    guint           position,
+                    IdeSessionItem *item)
 {
-  gboolean ret;
-
-  IDE_ENTRY;
-
-  g_return_val_if_fail (IDE_IS_SESSION (self), FALSE);
-  g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
-
-  ret = ide_task_propagate_boolean (IDE_TASK (result), error);
-
-  IDE_RETURN (ret);
-}
+  g_return_if_fail (IDE_IS_SESSION (self));
+  g_return_if_fail (IDE_IS_SESSION_ITEM (item));
 
-IdeSession *
-ide_session_new (void)
-{
-  return g_object_new (IDE_TYPE_SESSION, NULL);
+  g_ptr_array_insert (self->items, position, g_object_ref (item));
 }
diff --git a/src/libide/gui/ide-session.h b/src/libide/gui/ide-session.h
new file mode 100644
index 000000000..39433cc84
--- /dev/null
+++ b/src/libide/gui/ide-session.h
@@ -0,0 +1,67 @@
+/* ide-session.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_GUI_INSIDE) && !defined (IDE_GUI_COMPILATION)
+# error "Only <libide-gui.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-session-item.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SESSION (ide_session_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeSession, ide_session, IDE, SESSION, GObject)
+
+IDE_AVAILABLE_IN_ALL
+IdeSession     *ide_session_new              (void);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_append           (IdeSession      *self,
+                                               IdeSessionItem  *item);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_prepend          (IdeSession      *self,
+                                               IdeSessionItem  *item);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_insert           (IdeSession      *self,
+                                               guint             position,
+                                               IdeSessionItem  *item);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_remove           (IdeSession      *self,
+                                               IdeSessionItem  *item);
+IDE_AVAILABLE_IN_ALL
+void             ide_session_remove_at        (IdeSession      *self,
+                                               guint             position);
+IDE_AVAILABLE_IN_ALL
+guint            ide_session_get_n_items      (IdeSession      *self);
+IDE_AVAILABLE_IN_ALL
+IdeSessionItem *ide_session_get_item          (IdeSession      *self,
+                                               guint             position);
+IDE_AVAILABLE_IN_ALL
+IdeSession     *ide_session_new_from_variant (GVariant         *variant,
+                                              GError          **error);
+IDE_AVAILABLE_IN_ALL
+GVariant        *ide_session_to_variant       (IdeSession      *self);
+
+G_END_DECLS
diff --git a/src/libide/gui/libide-gui.h b/src/libide/gui/libide-gui.h
index 332ecfbd8..94616a71d 100644
--- a/src/libide/gui/libide-gui.h
+++ b/src/libide/gui/libide-gui.h
@@ -49,7 +49,6 @@
 # include "ide-primary-workspace.h"
 # include "ide-run-button.h"
 # include "ide-search-popover.h"
-# include "ide-session-addin.h"
 # include "ide-shortcut-provider.h"
 # include "ide-workbench.h"
 # include "ide-workbench-addin.h"
diff --git a/src/libide/gui/meson.build b/src/libide/gui/meson.build
index 91bfbdfdb..067ddaf12 100644
--- a/src/libide/gui/meson.build
+++ b/src/libide/gui/meson.build
@@ -27,7 +27,8 @@ libide_gui_public_headers = [
   'ide-primary-workspace.h',
   'ide-run-button.h',
   'ide-search-popover.h',
-  'ide-session-addin.h',
+  'ide-session.h',
+  'ide-session-item.h',
   'ide-shortcut-provider.h',
   'ide-workbench.h',
   'ide-workbench-addin.h',
@@ -51,7 +52,6 @@ libide_gui_private_headers = [
   'ide-page-private.h',
   'ide-recoloring-private.h',
   'ide-search-popover-private.h',
-  'ide-session-private.h',
   'ide-shortcut-bundle-private.h',
   'ide-shortcut-manager-private.h',
   'ide-shortcut-window-private.h',
@@ -71,7 +71,6 @@ libide_gui_private_sources = [
   'ide-notification-view.c',
   'ide-recoloring.c',
   'ide-search-popover.c',
-  'ide-session.c',
   'ide-shortcut-bundle.c',
   'ide-shortcut-manager.c',
   'ide-shortcut-window.c',
@@ -99,7 +98,8 @@ libide_gui_public_sources = [
   'ide-panel-position.c',
   'ide-primary-workspace.c',
   'ide-run-button.c',
-  'ide-session-addin.c',
+  'ide-session.c',
+  'ide-session-item.c',
   'ide-shortcut-provider.c',
   'ide-workbench.c',
   'ide-workbench-addin.c',


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