[gnome-builder] session: add plugin interface for session tracking



commit 4a0f1c6f97e84a057287ac1cf67ac054406d5b87
Author: Christian Hergert <chergert redhat com>
Date:   Tue Jul 24 00:13:51 2018 -0700

    session: add plugin interface for session tracking
    
    This can be used by plugins to save/restore session state when a project
    is closed/re-opened.

 doc/help/plugins/index.rst             |   1 +
 doc/help/plugins/session.rst           |  44 +++
 src/libide/ide-context.c               |  95 +++++++
 src/libide/ide.h                       |   2 +
 src/libide/meson.build                 |   1 +
 src/libide/session/ide-session-addin.c | 164 +++++++++++
 src/libide/session/ide-session-addin.h |  74 +++++
 src/libide/session/ide-session.c       | 485 +++++++++++++++++++++++++++++++++
 src/libide/session/ide-session.h       |  53 ++++
 src/libide/session/meson.build         |  14 +
 10 files changed, 933 insertions(+)
---
diff --git a/doc/help/plugins/index.rst b/doc/help/plugins/index.rst
index aa37f43ab..c84f3d14b 100644
--- a/doc/help/plugins/index.rst
+++ b/doc/help/plugins/index.rst
@@ -26,6 +26,7 @@ You may also chooser to implement extensions in C or Vala.
    keybindings
    langserv
    search
+   session
    menus
    preferences
    transfers
diff --git a/doc/help/plugins/session.rst b/doc/help/plugins/session.rst
new file mode 100644
index 000000000..6eb078bfd
--- /dev/null
+++ b/doc/help/plugins/session.rst
@@ -0,0 +1,44 @@
+################
+Session Tracking
+################
+
+Some plugins may want to save state when the user closes Builder.
+The `Ide.SessionAddin` allows for saving and restoring state when a project is closed or re-opened.
+
+.. code-block:: python3
+
+   # my_plugin.py
+
+   import gi
+
+   from gi.repository import GObject
+   from gi.repository import Gio
+   from gi.repository import Ide
+
+   class MySessionAddin(Ide.Object, Ide.SessionAddin):
+
+       def do_save_async(self, cancellable, callback, data):
+           # Create our async task
+           task = Ide.Task.new(sel, cancellable, callback)
+
+           # State is saved as a variant
+           task.result = GLib.Variant.new_int(123)
+
+           # Now complete task
+           task.return_boolean(True)
+
+       def do_save_finish(self, task):
+           if task.propagate_boolean():
+               return task.result
+
+       def do_restore_async(self, state, cancellable, callback, data):
+           # Create our async task
+           task = Ide.Task.new(sel, cancellable, callback)
+
+           # state is a GLib.Variant matching what we saved
+
+           # Now complete task
+           task.return_boolean(True)
+
+       def do_restore_finish(self, task):
+           return task.propagate_boolean()
diff --git a/src/libide/ide-context.c b/src/libide/ide-context.c
index 365879183..3d5cac419 100644
--- a/src/libide/ide-context.c
+++ b/src/libide/ide-context.c
@@ -52,6 +52,7 @@
 #include "runtimes/ide-runtime-manager.h"
 #include "search/ide-search-engine.h"
 #include "search/ide-search-provider.h"
+#include "session/ide-session.h"
 #include "snippets/ide-snippet-storage.h"
 #include "testing/ide-test-manager.h"
 #include "toolchain/ide-toolchain-manager.h"
@@ -122,6 +123,7 @@ struct _IdeContext
   IdeRuntimeManager        *runtime_manager;
   IdeToolchainManager      *toolchain_manager;
   IdeSearchEngine          *search_engine;
+  IdeSession               *session;
   IdeSnippetStorage        *snippets;
   IdeTestManager           *test_manager;
   IdeProject               *project;
@@ -594,6 +596,7 @@ ide_context_finalize (GObject *object)
   g_clear_object (&self->project_file);
   g_clear_object (&self->recent_manager);
   g_clear_object (&self->runtime_manager);
+  g_clear_object (&self->session);
   g_clear_object (&self->toolchain_manager);
   g_clear_object (&self->test_manager);
   g_clear_object (&self->unsaved_files);
@@ -909,6 +912,10 @@ ide_context_init (IdeContext *self)
 
   self->snippets = ide_snippet_storage_new ();
 
+  self->session = g_object_new (IDE_TYPE_SESSION,
+                                "context", self,
+                                NULL);
+
   IDE_EXIT;
 }
 
@@ -1297,6 +1304,46 @@ ide_context_init_tests (gpointer             source_object,
     ide_task_return_boolean (task, TRUE);
 }
 
+static void
+ide_context_init_session_cb (GObject      *object,
+                             GAsyncResult *result,
+                             gpointer      user_data)
+{
+  IdeSession *session = (IdeSession *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_SESSION (session));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!ide_session_restore_finish (session, result, &error))
+    g_warning ("Failed to restore session: %s", error->message);
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_context_init_session (gpointer             source_object,
+                          GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data)
+{
+  IdeContext *self = source_object;
+  g_autoptr(IdeTask) task = NULL;
+
+  g_assert (IDE_IS_CONTEXT (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_context_init_session);
+
+  ide_session_restore_async (self->session,
+                             cancellable,
+                             ide_context_init_session_cb,
+                             g_steal_pointer (&task));
+}
+
 static void
 ide_context_service_added (PeasExtensionSet *set,
                            PeasPluginInfo   *info,
@@ -1821,6 +1868,7 @@ ide_context_init_async (GAsyncInitable      *initable,
                         ide_context_init_run_manager,
                         ide_context_init_diagnostics_manager,
                         ide_context_init_tests,
+                        ide_context_init_session,
                         ide_context_init_loaded,
                         NULL);
 }
@@ -1986,6 +2034,52 @@ ide_context_unload_configuration_manager (gpointer             source_object,
   IDE_EXIT;
 }
 
+static void
+ide_context_unload_session_save_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  IdeSession *session = (IdeSession *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_SESSION (session));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  /* unfortunate if this happens, but not much we can do */
+  if (!ide_session_save_finish (session, result, &error))
+    g_warning ("Failed to save session: %s", error->message);
+
+  ide_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_context_unload_session (gpointer             source_object,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  IdeContext *self = source_object;
+  g_autoptr(IdeTask) task = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CONTEXT (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (IDE_IS_SESSION (self->session));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_context_unload_session);
+
+  ide_session_save_async (self->session,
+                          cancellable,
+                          ide_context_unload_session_save_cb,
+                          g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
 static void
 ide_context_unload__unsaved_files_save_cb (GObject      *object,
                                            GAsyncResult *result,
@@ -2086,6 +2180,7 @@ ide_context_do_unload_locked (IdeContext *self)
                         ide_task_get_cancellable (task),
                         ide_context_unload_cb,
                         g_object_ref (task),
+                        ide_context_unload_session,
                         ide_context_unload_configuration_manager,
                         ide_context_unload_buffer_manager,
                         ide_context_unload_unsaved_files,
diff --git a/src/libide/ide.h b/src/libide/ide.h
index 0f142554c..31f0f677c 100644
--- a/src/libide/ide.h
+++ b/src/libide/ide.h
@@ -157,6 +157,8 @@ G_BEGIN_DECLS
 #include "search/ide-search-reducer.h"
 #include "search/ide-search-result.h"
 #include "search/ide-tagged-entry.h"
+#include "session/ide-session.h"
+#include "session/ide-session-addin.h"
 #include "snippets/ide-snippet.h"
 #include "snippets/ide-snippet-chunk.h"
 #include "snippets/ide-snippet-context.h"
diff --git a/src/libide/meson.build b/src/libide/meson.build
index 4e8bc731b..e0643eda4 100644
--- a/src/libide/meson.build
+++ b/src/libide/meson.build
@@ -82,6 +82,7 @@ subdir('rename')
 subdir('runner')
 subdir('runtimes')
 subdir('search')
+subdir('session')
 subdir('snippets')
 subdir('sourceview')
 subdir('storage')
diff --git a/src/libide/session/ide-session-addin.c b/src/libide/session/ide-session-addin.c
new file mode 100644
index 000000000..7f86c4d19
--- /dev/null
+++ b/src/libide/session/ide-session-addin.c
@@ -0,0 +1,164 @@
+/* ide-session-addin.c
+ *
+ * Copyright 2018 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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: GPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#define G_LOG_DOMAIN "ide-session-addin"
+
+#include "ide-session-addin.h"
+
+G_DEFINE_INTERFACE (IdeSessionAddin, ide_session_addin, IDE_TYPE_OBJECT)
+
+static void
+ide_session_addin_real_save_async (IdeSessionAddin     *self,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  g_task_report_new_error (self, callback, user_data,
+                           ide_session_addin_real_save_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Save not supported");
+}
+
+static GVariant *
+ide_session_addin_real_save_finish (IdeSessionAddin  *self,
+                                    GAsyncResult     *result,
+                                    GError          **error)
+{
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+ide_session_addin_real_restore_async (IdeSessionAddin     *self,
+                                      GVariant            *state,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  g_task_report_new_error (self, callback, user_data,
+                           ide_session_addin_real_restore_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "Restore not supported");
+}
+
+static gboolean
+ide_session_addin_real_restore_finish (IdeSessionAddin  *self,
+                                       GAsyncResult     *result,
+                                       GError          **error)
+{
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_session_addin_default_init (IdeSessionAddinInterface *iface)
+{
+  iface->save_async = ide_session_addin_real_save_async;
+  iface->save_finish = ide_session_addin_real_save_finish;
+  iface->restore_async = ide_session_addin_real_restore_async;
+  iface->restore_finish = ide_session_addin_real_restore_finish;
+}
+
+/**
+ * ide_session_addin_save_async:
+ * @self: a #IdeSessionAddin
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: callback to execute upon completion
+ * @user_data: closure data for @callback
+ *
+ * Asynchronous request to save state about the session.
+ *
+ * The resulting state will be provided when restoring the addin
+ * at a future time.
+ *
+ * Since: 3.30
+ */
+void
+ide_session_addin_save_async (IdeSessionAddin     *self,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_SESSION_ADDIN (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_SESSION_ADDIN_GET_IFACE (self)->save_async (self, cancellable, callback, user_data);
+}
+
+/**
+ * ide_session_addin_save_finish:
+ * @self: a #IdeSessionAddin
+ *
+ * Completes an asynchronous request to save session state.
+ *
+ * The resulting #GVariant will be used to restore state at a future time.
+ *
+ * Returns: (transfer full) (nullable): a #GVariant or %NULL.
+ *
+ * Since: 3.30
+ */
+GVariant *
+ide_session_addin_save_finish (IdeSessionAddin  *self,
+                               GAsyncResult     *result,
+                               GError          **error)
+{
+  g_return_val_if_fail (IDE_IS_SESSION_ADDIN (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_SESSION_ADDIN_GET_IFACE (self)->save_finish (self, result, error);
+}
+
+/**
+ * ide_session_addin_restore_async:
+ * @self: a #IdeSessionAddin
+ * @state: a #GVariant of previous state
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: callback to execute upon completion
+ * @user_data: closure data for @callback
+ *
+ * Asynchronous request to restore session state by the addin.
+ *
+ * Since: 3.30
+ */
+void
+ide_session_addin_restore_async (IdeSessionAddin     *self,
+                                 GVariant            *state,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_SESSION_ADDIN (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_SESSION_ADDIN_GET_IFACE (self)->restore_async (self, state, cancellable, callback, user_data);
+}
+
+gboolean
+ide_session_addin_restore_finish (IdeSessionAddin  *self,
+                                  GAsyncResult     *result,
+                                  GError          **error)
+{
+  g_return_val_if_fail (IDE_IS_SESSION_ADDIN (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_SESSION_ADDIN_GET_IFACE (self)->restore_finish (self, result, error);
+}
diff --git a/src/libide/session/ide-session-addin.h b/src/libide/session/ide-session-addin.h
new file mode 100644
index 000000000..eb8723412
--- /dev/null
+++ b/src/libide/session/ide-session-addin.h
@@ -0,0 +1,74 @@
+/* ide-session-addin.h
+ *
+ * Copyright 2018 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "ide-object.h"
+#include "ide-version-macros.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SESSION_ADDIN (ide_session_addin_get_type ())
+
+IDE_AVAILABLE_IN_3_30
+G_DECLARE_INTERFACE (IdeSessionAddin, ide_session_addin, IDE, SESSION_ADDIN, IdeObject)
+
+struct _IdeSessionAddinInterface
+{
+  GTypeInterface parent;
+
+  void      (*save_async)     (IdeSessionAddin      *self,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data);
+  GVariant *(*save_finish)    (IdeSessionAddin      *self,
+                               GAsyncResult         *result,
+                               GError              **error);
+  void      (*restore_async)  (IdeSessionAddin      *self,
+                               GVariant             *state,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data);
+  gboolean  (*restore_finish) (IdeSessionAddin      *self,
+                               GAsyncResult         *result,
+                               GError              **error);
+};
+
+IDE_AVAILABLE_IN_3_30
+void      ide_session_addin_save_async     (IdeSessionAddin      *self,
+                                            GCancellable         *cancellable,
+                                            GAsyncReadyCallback   callback,
+                                            gpointer              user_data);
+IDE_AVAILABLE_IN_3_30
+GVariant *ide_session_addin_save_finish    (IdeSessionAddin      *self,
+                                            GAsyncResult         *result,
+                                            GError              **error);
+IDE_AVAILABLE_IN_3_30
+void      ide_session_addin_restore_async  (IdeSessionAddin      *self,
+                                            GVariant             *state,
+                                            GCancellable         *cancellable,
+                                            GAsyncReadyCallback   callback,
+                                            gpointer              user_data);
+IDE_AVAILABLE_IN_3_30
+gboolean  ide_session_addin_restore_finish (IdeSessionAddin      *self,
+                                            GAsyncResult         *result,
+                                            GError              **error);
+
+G_END_DECLS
diff --git a/src/libide/session/ide-session.c b/src/libide/session/ide-session.c
new file mode 100644
index 000000000..a6b52cc08
--- /dev/null
+++ b/src/libide/session/ide-session.c
@@ -0,0 +1,485 @@
+/* ide-session.c
+ *
+ * Copyright 2018 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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: GPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#define G_LOG_DOMAIN "ide-session"
+
+#include <libpeas/peas.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+#include "ide-global.h"
+#include "ide-session.h"
+
+#include "session/ide-session-addin.h"
+#include "threading/ide-task.h"
+
+struct _IdeSession
+{
+  IdeObject         parent_instance;
+  PeasExtensionSet *addins;
+};
+
+typedef struct
+{
+  GPtrArray    *addins;
+  GVariantDict  dict;
+  gint          active;
+} Save;
+
+typedef struct
+{
+  GPtrArray *addins;
+  GVariant  *state;
+  gint       active;
+} Restore;
+
+G_DEFINE_TYPE (IdeSession, ide_session, IDE_TYPE_OBJECT)
+
+static void
+restore_free (Restore *r)
+{
+  g_assert (r != NULL);
+
+  g_clear_pointer (&r->addins, g_ptr_array_unref);
+  g_clear_pointer (&r->state, g_variant_unref);
+  g_slice_free (Restore, r);
+}
+
+static void
+save_free (Save *s)
+{
+  g_assert (s != NULL);
+  g_assert (s->active == 0);
+
+  g_clear_pointer (&s->addins, g_ptr_array_unref);
+  g_slice_free (Save, s);
+}
+
+static void
+collect_addins_cb (PeasExtensionSet *set,
+                   PeasPluginInfo   *plugin_info,
+                   PeasExtension    *exten,
+                   gpointer          user_data)
+{
+  GPtrArray *ar = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (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 void
+ide_session_dispose (GObject *object)
+{
+  IdeSession *self = (IdeSession *)object;
+
+  IDE_ENTRY;
+
+  g_clear_object (&self->addins);
+
+  G_OBJECT_CLASS (ide_session_parent_class)->dispose (object);
+
+  IDE_EXIT;
+}
+
+static void
+ide_session_constructed (GObject *object)
+{
+  IdeSession *self = (IdeSession *)object;
+  IdeContext *context;
+
+  G_OBJECT_CLASS (ide_session_parent_class)->constructed (object);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  self->addins = peas_extension_set_new (peas_engine_get_default (),
+                                         IDE_TYPE_SESSION_ADDIN,
+                                         "context", context,
+                                         NULL);
+}
+
+static void
+ide_session_class_init (IdeSessionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_session_constructed;
+  object_class->dispose = ide_session_dispose;
+}
+
+static void
+ide_session_init (IdeSession *self)
+{
+}
+
+static void
+ide_session_restore_addin_restore_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  IdeSessionAddin *addin = (IdeSessionAddin *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  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);
+
+  if (!ide_session_addin_restore_finish (addin, result, &error))
+    g_warning ("%s: %s", G_OBJECT_TYPE_NAME (addin), error->message);
+
+  r->active--;
+
+  if (r->active == 0)
+    ide_task_return_boolean (task, TRUE);
+
+  IDE_EXIT;
+}
+
+static void
+ide_session_restore_load_cb (GObject      *object,
+                             GAsyncResult *result,
+                             gpointer      user_data)
+{
+  GFile *file = (GFile *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GBytes) bytes = 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->active > 0);
+  g_assert (r->state == NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (!(bytes = g_file_load_bytes_finish (file, result, NULL, &error)))
+    {
+      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 = g_variant_new_from_bytes (G_VARIANT_TYPE_VARDICT, bytes, FALSE);
+
+  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;
+    }
+
+  g_assert (r->addins != NULL);
+  g_assert (r->addins->len > 0);
+
+  for (guint i = 0; i < r->addins->len; i++)
+    {
+      IdeSessionAddin *addin = g_ptr_array_index (r->addins, i);
+      g_autoptr(GVariant) state = NULL;
+
+      g_assert (IDE_IS_SESSION_ADDIN (addin));
+
+      state = g_variant_lookup_value (r->state,
+                                      G_OBJECT_TYPE_NAME (addin),
+                                      NULL);
+
+      ide_session_addin_restore_async (addin,
+                                       state,
+                                       cancellable,
+                                       ide_session_restore_addin_restore_cb,
+                                       g_object_ref (task));
+    }
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_session_restore_async:
+ * @self: an #IdeSession
+ * @cancellable: (nullable): a #GCancellbale or %NULL
+ * @callback: the callback to execute upon completion
+ * @user_data: user data for callback
+ *
+ * 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.
+ *
+ * Since: 3.30
+ */
+void
+ide_session_restore_async (IdeSession          *self,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GFile) file = NULL;
+  IdeContext *context;
+  Restore *r;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_SESSION (self));
+  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 = g_ptr_array_new_with_free_func (g_object_unref);
+  peas_extension_set_foreach (self->addins, collect_addins_cb, r->addins);
+  r->active = r->addins->len;
+  ide_task_set_task_data (task, r, restore_free);
+
+  if (r->active == 0)
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  file = ide_context_cache_file (context, "session.gvariant", NULL);
+
+  g_file_load_bytes_async (file,
+                           cancellable,
+                           ide_session_restore_load_cb,
+                           g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+gboolean
+ide_session_restore_finish (IdeSession    *self,
+                            GAsyncResult  *result,
+                            GError       **error)
+{
+  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);
+}
+
+static void
+ide_session_save_cb (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+  GFile *file = (GFile *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(IdeTask) task = user_data;
+
+  IDE_ENTRY;
+
+  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);
+
+  IDE_EXIT;
+}
+
+static void
+ide_session_save_addin_save_cb (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer      user_data)
+{
+  IdeSessionAddin *addin = (IdeSessionAddin *)object;
+  g_autoptr(GVariant) variant = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  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->addins != NULL);
+  g_assert (s->active > 0);
+
+  variant = ide_session_addin_save_finish (addin, result, &error);
+
+  if (error != NULL)
+    g_warning ("%s: %s", G_OBJECT_TYPE_NAME (addin), error->message);
+
+  if (variant != NULL)
+    {
+      g_assert (!g_variant_is_floating (variant));
+      g_variant_dict_insert_value (&s->dict, G_OBJECT_TYPE_NAME (addin), variant);
+    }
+
+  s->active--;
+
+  if (s->active == 0)
+    {
+      g_autoptr(GVariant) state = NULL;
+      g_autoptr(GBytes) bytes = NULL;
+      g_autoptr(GFile) file = NULL;
+      GCancellable *cancellable;
+      IdeContext *context;
+
+      state = g_variant_take_ref (g_variant_dict_end (&s->dict));
+      bytes = g_variant_get_data_as_bytes (state);
+
+      cancellable = ide_task_get_cancellable (task);
+      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))
+        IDE_EXIT;
+
+      g_file_replace_contents_bytes_async (file,
+                                           bytes,
+                                           NULL,
+                                           FALSE,
+                                           G_FILE_CREATE_NONE,
+                                           cancellable,
+                                           ide_session_save_cb,
+                                           g_steal_pointer (&task));
+    }
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_session_save_async:
+ * @self: an #IdeSession
+ * @cancellable: (nullable): a #GCancellable, or %NULL
+ * @callback: a callback to execute upon completion
+ * @user_data: user data for @callback
+ *
+ * This function will request that various components save their active state
+ * so that the project may be restored to the current layout when the project
+ * is re-opened at a later time.
+ *
+ * Since: 3.30
+ */
+void
+ide_session_save_async (IdeSession          *self,
+                        GCancellable        *cancellable,
+                        GAsyncReadyCallback  callback,
+                        gpointer             user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+  Save *s;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_SESSION (self));
+  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);
+
+  s = g_slice_new0 (Save);
+  s->addins = g_ptr_array_new_with_free_func (g_object_unref);
+  g_variant_dict_init (&s->dict, NULL);
+  peas_extension_set_foreach (self->addins, collect_addins_cb, s->addins);
+  s->active = s->addins->len;
+  ide_task_set_task_data (task, s, save_free);
+
+  if (s->active == 0)
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  for (guint i = 0; i < s->addins->len; i++)
+    {
+      IdeSessionAddin *addin = g_ptr_array_index (s->addins, i);
+
+      ide_session_addin_save_async (addin,
+                                    cancellable,
+                                    ide_session_save_addin_save_cb,
+                                    g_object_ref (task));
+    }
+
+  g_assert (s != NULL);
+  g_assert (s->active > 0);
+  g_assert (s->addins->len == s->active);
+
+  IDE_EXIT;
+}
+
+gboolean
+ide_session_save_finish (IdeSession    *self,
+                         GAsyncResult  *result,
+                         GError       **error)
+{
+  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);
+}
diff --git a/src/libide/session/ide-session.h b/src/libide/session/ide-session.h
new file mode 100644
index 000000000..0db083913
--- /dev/null
+++ b/src/libide/session/ide-session.h
@@ -0,0 +1,53 @@
+/* ide-session.h
+ *
+ * Copyright 2018 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "ide-object.h"
+
+#include "ide-version-macros.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, IdeObject)
+
+IDE_AVAILABLE_IN_ALL
+void     ide_session_restore_async  (IdeSession           *self,
+                                     GCancellable         *cancellable,
+                                     GAsyncReadyCallback   callback,
+                                     gpointer              user_data);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_session_restore_finish (IdeSession           *self,
+                                     GAsyncResult         *result,
+                                     GError              **error);
+IDE_AVAILABLE_IN_ALL
+void     ide_session_save_async     (IdeSession           *self,
+                                     GCancellable         *cancellable,
+                                     GAsyncReadyCallback   callback,
+                                     gpointer              user_data);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_session_save_finish    (IdeSession           *self,
+                                     GAsyncResult         *result,
+                                     GError              **error);
+
+G_END_DECLS
diff --git a/src/libide/session/meson.build b/src/libide/session/meson.build
new file mode 100644
index 000000000..f14360332
--- /dev/null
+++ b/src/libide/session/meson.build
@@ -0,0 +1,14 @@
+session_headers = [
+  'ide-session.h',
+  'ide-session-addin.h',
+]
+
+session_sources = [
+  'ide-session.c',
+  'ide-session-addin.c',
+]
+
+libide_public_headers += files(session_headers)
+libide_public_sources += files(session_sources)
+
+install_headers(session_headers, subdir: join_paths(libide_header_subdir, 'session'))


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