Blob Blame History Raw
From b593bf334a7aada890d8ec4ce358c9f431b605ff Mon Sep 17 00:00:00 2001
From: Carlos Garnacho <carlosg@gnome.org>
Date: Mon, 3 Dec 2018 13:09:47 +0100
Subject: [PATCH 1/4] shell-global: Make saving of persistent state
 asynchronous

This is an expensive operation that is best avoided in the main loop. Given
the call doesn't care much about returning error or status, it can just
be made async within.

Every operation on a given file will be destructive wrt previous
operations on the same file, so we just cancel any pending operation on
it before batching the current one.

Closes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/815
---
 src/shell-global.c | 85 ++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 75 insertions(+), 10 deletions(-)

diff --git a/src/shell-global.c b/src/shell-global.c
index 961fd3a70..8f5418677 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -88,6 +88,8 @@ struct _ShellGlobal {
   /* For sound notifications */
   ca_context *sound_context;
 
+  GHashTable *save_ops;
+
   gboolean has_modal;
   gboolean frame_timestamps;
   gboolean frame_finish_timestamp;
@@ -331,6 +333,10 @@ shell_global_init (ShellGlobal *global)
                                      NULL);
 
   g_strfreev (search_path);
+
+  global->save_ops = g_hash_table_new_full (g_file_hash,
+                                            (GEqualFunc) g_file_equal,
+                                            g_object_unref, g_object_unref);
 }
 
 static void
@@ -350,6 +356,8 @@ shell_global_finalize (GObject *object)
   g_free (global->imagedir);
   g_free (global->userdatadir);
 
+  g_hash_table_unref (global->save_ops);
+
   G_OBJECT_CLASS(shell_global_parent_class)->finalize (object);
 }
 
@@ -1775,20 +1783,77 @@ shell_global_get_session_mode (ShellGlobal *global)
 }
 
 static void
-save_variant (GFile      *dir,
-              const char *property_name,
-              GVariant   *variant)
+delete_variant_cb (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  ShellGlobal *global = user_data;
+  GError *error = NULL;
+
+  if (!g_file_delete_finish (G_FILE (object), result, &error))
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_warning ("Could not delete runtime/persistent state file: %s\n",
+                     error->message);
+        }
+
+      g_error_free (error);
+    }
+
+  g_hash_table_remove (global->save_ops, object);
+}
+
+static void
+replace_variant_cb (GObject      *object,
+                    GAsyncResult *result,
+                    gpointer      user_data)
+{
+  ShellGlobal *global = user_data;
+  GError *error = NULL;
+
+  if (!g_file_replace_contents_finish (G_FILE (object), result, NULL, &error))
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        {
+          g_warning ("Could not replace runtime/persistent state file: %s\n",
+                     error->message);
+        }
+
+      g_error_free (error);
+    }
+
+  g_hash_table_remove (global->save_ops, object);
+}
+
+static void
+save_variant (ShellGlobal *global,
+              GFile       *dir,
+              const char  *property_name,
+              GVariant    *variant)
 {
   GFile *path = g_file_get_child (dir, property_name);
+  GCancellable *cancellable;
+
+  cancellable = g_hash_table_lookup (global->save_ops, path);
+  g_cancellable_cancel (cancellable);
+
+  cancellable = g_cancellable_new ();
+  g_hash_table_insert (global->save_ops, g_object_ref (path), cancellable);
 
   if (variant == NULL || g_variant_get_data (variant) == NULL)
-    (void) g_file_delete (path, NULL, NULL);
+    {
+      g_file_delete_async (path, G_PRIORITY_DEFAULT, cancellable,
+                           delete_variant_cb, global);
+    }
   else
     {
-      gsize size = g_variant_get_size (variant);
-      g_file_replace_contents (path, g_variant_get_data (variant), size,
-                               NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION,
-                               NULL, NULL, NULL);
+      g_file_replace_contents_async (path,
+                                     g_variant_get_data (variant),
+                                     g_variant_get_size (variant),
+                                     NULL, FALSE,
+                                     G_FILE_CREATE_REPLACE_DESTINATION,
+                                     cancellable, replace_variant_cb, global);
     }
 
   g_object_unref (path);
@@ -1842,7 +1907,7 @@ shell_global_set_runtime_state (ShellGlobal  *global,
                                 const char   *property_name,
                                 GVariant     *variant)
 {
-  save_variant (global->runtime_state_path, property_name, variant);
+  save_variant (global, global->runtime_state_path, property_name, variant);
 }
 
 /**
@@ -1877,7 +1942,7 @@ shell_global_set_persistent_state (ShellGlobal *global,
                                    const char  *property_name,
                                    GVariant    *variant)
 {
-  save_variant (global->userdatadir_path, property_name, variant);
+  save_variant (global, global->userdatadir_path, property_name, variant);
 }
 
 /**
-- 
2.23.0


From 6e898656365c18ad48349b7e3586e2d10b9ead9a Mon Sep 17 00:00:00 2001
From: Carlos Garnacho <carlosg@gnome.org>
Date: Mon, 3 Dec 2018 13:18:19 +0100
Subject: [PATCH 2/4] appDisplay: Reduce g_app_info_get_all() calls

Whenever the AllView needs (re)populating, we used to do one general
g_app_info_get_all() to get all GAppInfo, plus one per app folder in order
to check the ones that fall within that category. This calls results in a
fair amount of I/O blocking the main loop.

In order to ease this, keep the GAppInfo list around in AllView, and make
the AppFolders use it when figuring out the contained apps. Since reloading
the AllView results in AppFolders regenerated from scratch, the app info
list is ensured to be up-to-date for any later change within the AppFolder
(eg. through the GSettings key changing).

As the list was already filtered in the first place, we can also remove
the try{}catch() in AppFolder in order to discard desktop files with
invalid encoding.

Related: https://gitlab.gnome.org/GNOME/gnome-shell/issues/832
---
 js/ui/appDisplay.js | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 070057c69..115eb8f92 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -498,15 +498,21 @@ var AllView = new Lang.Class({
         });
     },
 
+    getAppInfos() {
+        return this._appInfoList;
+    },
+
     _loadApps() {
-        let apps = Gio.AppInfo.get_all().filter(appInfo => {
+        this._appInfoList = Gio.AppInfo.get_all().filter(appInfo => {
             try {
                 let id = appInfo.get_id(); // catch invalid file encodings
             } catch(e) {
                 return false;
             }
             return appInfo.should_show();
-        }).map(app => app.get_id());
+        });
+
+        let apps = this._appInfoList.map(app => app.get_id());
 
         let appSys = Shell.AppSystem.get_default();
 
@@ -1329,15 +1335,13 @@ var FolderIcon = new Lang.Class({
         folderApps.forEach(addAppId);
 
         let folderCategories = this._folder.get_strv('categories');
-        Gio.AppInfo.get_all().forEach(appInfo => {
+        let appInfos = this._parentView.getAppInfos();
+        appInfos.forEach(appInfo => {
             let appCategories = _getCategories(appInfo);
             if (!_listsIntersect(folderCategories, appCategories))
                 return;
 
-            try {
-                addAppId(appInfo.get_id()); // catch invalid file encodings
-            } catch(e) {
-            }
+            addAppId(appInfo.get_id());
         });
 
         this.actor.visible = this.view.getAllItems().length > 0;
-- 
2.23.0


From cf823de60bff23872d20dfbb05ee4c51e3ebe9a3 Mon Sep 17 00:00:00 2001
From: Carlos Garnacho <carlosg@gnome.org>
Date: Wed, 19 Dec 2018 13:13:42 +0100
Subject: [PATCH 3/4] shell-app-system: Cache GAppInfos around

This was called here just to end up emitting ::installed-changed,
which would trigger other g_app_info_get_all() calls. Cache it here
so it may be reused later on.
---
 src/shell-app-system.c | 30 +++++++++++++++++++++++++-----
 src/shell-app-system.h |  2 ++
 2 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/src/shell-app-system.c b/src/shell-app-system.c
index 8edf757a9..58e0185d1 100644
--- a/src/shell-app-system.c
+++ b/src/shell-app-system.c
@@ -50,6 +50,7 @@ struct _ShellAppSystemPrivate {
   GHashTable *running_apps;
   GHashTable *id_to_app;
   GHashTable *startup_wm_class_to_id;
+  GList *installed_apps;
 };
 
 static void shell_app_system_finalize (GObject *object);
@@ -82,12 +83,14 @@ static void
 scan_startup_wm_class_to_id (ShellAppSystem *self)
 {
   ShellAppSystemPrivate *priv = self->priv;
-  GList *apps, *l;
+  GList *l;
 
   g_hash_table_remove_all (priv->startup_wm_class_to_id);
 
-  apps = g_app_info_get_all ();
-  for (l = apps; l != NULL; l = l->next)
+  g_list_free_full (priv->installed_apps, g_object_unref);
+  priv->installed_apps = g_app_info_get_all ();
+
+  for (l = priv->installed_apps; l != NULL; l = l->next)
     {
       GAppInfo *info = l->data;
       const char *startup_wm_class, *id, *old_id;
@@ -105,8 +108,6 @@ scan_startup_wm_class_to_id (ShellAppSystem *self)
         g_hash_table_insert (priv->startup_wm_class_to_id,
                              g_strdup (startup_wm_class), g_strdup (id));
     }
-
-  g_list_free_full (apps, g_object_unref);
 }
 
 static gboolean
@@ -198,6 +199,7 @@ shell_app_system_finalize (GObject *object)
   g_hash_table_destroy (priv->running_apps);
   g_hash_table_destroy (priv->id_to_app);
   g_hash_table_destroy (priv->startup_wm_class_to_id);
+  g_list_free_full (priv->installed_apps, g_object_unref);
 
   G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
 }
@@ -437,3 +439,21 @@ shell_app_system_search (const char *search_string)
 
   return results;
 }
+
+/**
+ * shell_app_system_get_installed:
+ * @self: the #ShellAppSystem
+ *
+ * Returns all installed apps, as a list of #GAppInfo
+ *
+ * Returns: (transfer none) (element-type GAppInfo): a list of #GAppInfo
+ *   describing all known applications. This memory is owned by the
+ *   #ShellAppSystem and should not be freed.
+ **/
+GList *
+shell_app_system_get_installed (ShellAppSystem *self)
+{
+  ShellAppSystemPrivate *priv = self->priv;
+
+  return priv->installed_apps;
+}
diff --git a/src/shell-app-system.h b/src/shell-app-system.h
index 445671f5b..8719dbcf2 100644
--- a/src/shell-app-system.h
+++ b/src/shell-app-system.h
@@ -27,4 +27,6 @@ ShellApp       *shell_app_system_lookup_desktop_wmclass       (ShellAppSystem *s
 GSList         *shell_app_system_get_running               (ShellAppSystem  *self);
 char         ***shell_app_system_search                    (const char *search_string);
 
+GList          *shell_app_system_get_installed             (ShellAppSystem  *self);
+
 #endif /* __SHELL_APP_SYSTEM_H__ */
-- 
2.23.0


From e294793e3d5525897e1c293cd9fd77ccf8983ca7 Mon Sep 17 00:00:00 2001
From: Carlos Garnacho <carlosg@gnome.org>
Date: Wed, 19 Dec 2018 13:26:54 +0100
Subject: [PATCH 4/4] appDisplay: Use GAppInfo list from ShellAppSystem

It is now cached there, so the number of g_app_info_get_all() calls
is reduced to one per change.
---
 js/ui/appDisplay.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 115eb8f92..233deeb92 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -503,7 +503,7 @@ var AllView = new Lang.Class({
     },
 
     _loadApps() {
-        this._appInfoList = Gio.AppInfo.get_all().filter(appInfo => {
+        this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => {
             try {
                 let id = appInfo.get_id(); // catch invalid file encodings
             } catch(e) {
-- 
2.23.0