Blame SOURCES/0005-app-cache-add-ShellAppCache-for-GAppInfo-caching.patch

067a6b
From 0b96b08045f0a10a2f835a9fc5e184e3a66de8bd Mon Sep 17 00:00:00 2001
067a6b
From: Christian Hergert <chergert@redhat.com>
067a6b
Date: Thu, 27 Feb 2020 19:36:14 -0800
067a6b
Subject: [PATCH 5/6] app-cache: add ShellAppCache for GAppInfo caching
067a6b
067a6b
This caches GAppInfo so that the compositor thread does not have to perform
067a6b
costly disk access to load them. Instead, they are loaded from a worker
067a6b
thread and the ShellAppCache notifies of changes.
067a6b
067a6b
To simplify maintenance, ShellAppCache manages this directly and the
067a6b
existing ShellAppSystem wraps the cache. We may want to graft these
067a6b
together in the future, but now it provides the easiest way to backport
067a6b
changes to older Shell releases.
067a6b
067a6b
Another source of compositor thread disk access was in determining the
067a6b
name for an application directory. Translations are provided via GKeyFile
067a6b
installed in "desktop-directories". Each time we would build the name
067a6b
for a label (or update it) we would have to load all of these files.
067a6b
067a6b
Instead, the ShellAppCache caches that information and updates the cache
067a6b
in bulk when those change. We can reduce this in the future to do less
067a6b
work, but chances are these will come together anyway so that is probably
067a6b
worth fixing if we ever come across it.
067a6b
067a6b
https://gitlab.gnome.org/GNOME/gnome-shell/issues/2282
067a6b
---
067a6b
 js/ui/appDisplay.js           |  12 +-
067a6b
 src/meson.build               |   5 +-
067a6b
 src/shell-app-cache-private.h |  19 ++
067a6b
 src/shell-app-cache.c         | 404 ++++++++++++++++++++++++++++++++++
067a6b
 src/shell-app-system.c        |  34 ++-
067a6b
 src/shell-util.c              |  16 ++
067a6b
 src/shell-util.h              |   2 +
067a6b
 7 files changed, 463 insertions(+), 29 deletions(-)
067a6b
 create mode 100644 src/shell-app-cache-private.h
067a6b
 create mode 100644 src/shell-app-cache.c
067a6b
067a6b
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
067a6b
index 233deeb92..3798fac57 100644
067a6b
--- a/js/ui/appDisplay.js
067a6b
+++ b/js/ui/appDisplay.js
067a6b
@@ -93,15 +93,9 @@ function _getFolderName(folder) {
067a6b
     let name = folder.get_string('name');
067a6b
 
067a6b
     if (folder.get_boolean('translate')) {
067a6b
-        let keyfile = new GLib.KeyFile();
067a6b
-        let path = 'desktop-directories/' + name;
067a6b
-
067a6b
-        try {
067a6b
-            keyfile.load_from_data_dirs(path, GLib.KeyFileFlags.NONE);
067a6b
-            name = keyfile.get_locale_string('Desktop Entry', 'Name', null);
067a6b
-        } catch(e) {
067a6b
-            return name;
067a6b
-        }
067a6b
+        let translated = Shell.util_get_translated_folder_name(name);
067a6b
+        if (translated !== null)
067a6b
+            return translated;
067a6b
     }
067a6b
 
067a6b
     return name;
067a6b
diff --git a/src/meson.build b/src/meson.build
067a6b
index 1d655f407..568ee0c9a 100644
067a6b
--- a/src/meson.build
067a6b
+++ b/src/meson.build
067a6b
@@ -129,6 +129,7 @@ endif
067a6b
 
067a6b
 libshell_private_headers = [
067a6b
   'shell-app-private.h',
067a6b
+  'shell-app-cache-private.h',
067a6b
   'shell-app-system-private.h',
067a6b
   'shell-global-private.h',
067a6b
   'shell-window-tracker-private.h',
067a6b
@@ -170,7 +171,9 @@ if have_networkmanager
067a6b
   libshell_sources += 'shell-network-agent.c'
067a6b
 endif
067a6b
 
067a6b
-libshell_private_sources = []
067a6b
+libshell_private_sources = [
067a6b
+  'shell-app-cache.c',
067a6b
+]
067a6b
 
067a6b
 if enable_recorder
067a6b
     libshell_sources += ['shell-recorder.c']
067a6b
diff --git a/src/shell-app-cache-private.h b/src/shell-app-cache-private.h
067a6b
new file mode 100644
067a6b
index 000000000..b73094ab1
067a6b
--- /dev/null
067a6b
+++ b/src/shell-app-cache-private.h
067a6b
@@ -0,0 +1,19 @@
067a6b
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
067a6b
+#ifndef __SHELL_APP_CACHE_PRIVATE_H__
067a6b
+#define __SHELL_APP_CACHE_PRIVATE_H__
067a6b
+
067a6b
+#include <gio/gio.h>
067a6b
+#include <gio/gdesktopappinfo.h>
067a6b
+
067a6b
+#define SHELL_TYPE_APP_CACHE (shell_app_cache_get_type())
067a6b
+
067a6b
+G_DECLARE_FINAL_TYPE (ShellAppCache, shell_app_cache, SHELL, APP_CACHE, GObject)
067a6b
+
067a6b
+ShellAppCache   *shell_app_cache_get_default      (void);
067a6b
+GList           *shell_app_cache_get_all          (ShellAppCache *cache);
067a6b
+GDesktopAppInfo *shell_app_cache_get_info         (ShellAppCache *cache,
067a6b
+                                                   const char    *id);
067a6b
+char            *shell_app_cache_translate_folder (ShellAppCache *cache,
067a6b
+                                                   const char    *name);
067a6b
+
067a6b
+#endif /* __SHELL_APP_CACHE_PRIVATE_H__ */
067a6b
diff --git a/src/shell-app-cache.c b/src/shell-app-cache.c
067a6b
new file mode 100644
067a6b
index 000000000..15d4734d0
067a6b
--- /dev/null
067a6b
+++ b/src/shell-app-cache.c
067a6b
@@ -0,0 +1,404 @@
067a6b
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
067a6b
+
067a6b
+#include "config.h"
067a6b
+
067a6b
+#include "shell-app-cache-private.h"
067a6b
+
067a6b
+/**
067a6b
+ * SECTION:shell-app-cache
067a6b
+ * @title: ShellAppCache
067a6b
+ * @short_description: application information cache
067a6b
+ *
067a6b
+ * The #ShellAppCache is responsible for caching information about #GAppInfo
067a6b
+ * to ensure that the compositor thread never needs to perform disk reads to
067a6b
+ * access them. All of the work is done off-thread. When the new data has
067a6b
+ * been loaded, a #ShellAppCache::changed signal is emitted.
067a6b
+ *
067a6b
+ * Additionally, the #ShellAppCache caches information about translations for
067a6b
+ * directories. This allows translation provided in [Desktop Entry] GKeyFiles
067a6b
+ * to be available when building StLabel and other elements without performing
067a6b
+ * costly disk reads.
067a6b
+ *
067a6b
+ * Various monitors are used to keep this information up to date while the
067a6b
+ * Shell is running.
067a6b
+ */
067a6b
+
067a6b
+#define DEFAULT_TIMEOUT_SECONDS 5
067a6b
+
067a6b
+struct _ShellAppCache
067a6b
+{
067a6b
+  GObject          parent_instance;
067a6b
+
067a6b
+  GAppInfoMonitor *monitor;
067a6b
+  GPtrArray       *dir_monitors;
067a6b
+  GHashTable      *folders;
067a6b
+  GCancellable    *cancellable;
067a6b
+  GList           *app_infos;
067a6b
+
067a6b
+  guint            queued_update;
067a6b
+};
067a6b
+
067a6b
+typedef struct
067a6b
+{
067a6b
+  GList      *app_infos;
067a6b
+  GHashTable *folders;
067a6b
+} CacheState;
067a6b
+
067a6b
+G_DEFINE_TYPE (ShellAppCache, shell_app_cache, G_TYPE_OBJECT)
067a6b
+
067a6b
+enum {
067a6b
+  CHANGED,
067a6b
+  N_SIGNALS
067a6b
+};
067a6b
+
067a6b
+static guint signals [N_SIGNALS];
067a6b
+
067a6b
+static void
067a6b
+cache_state_free (CacheState *state)
067a6b
+{
067a6b
+  g_clear_pointer (&state->folders, g_hash_table_unref);
067a6b
+  g_list_free_full (state->app_infos, g_object_unref);
067a6b
+  g_slice_free (CacheState, state);
067a6b
+}
067a6b
+
067a6b
+static CacheState *
067a6b
+cache_state_new (void)
067a6b
+{
067a6b
+  CacheState *state;
067a6b
+
067a6b
+  state = g_slice_new0 (CacheState);
067a6b
+  state->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
067a6b
+
067a6b
+  return g_steal_pointer (&state);
067a6b
+}
067a6b
+
067a6b
+/**
067a6b
+ * shell_app_cache_get_default:
067a6b
+ *
067a6b
+ * Gets the default #ShellAppCache.
067a6b
+ *
067a6b
+ * Returns: (transfer none): a #ShellAppCache
067a6b
+ */
067a6b
+ShellAppCache *
067a6b
+shell_app_cache_get_default (void)
067a6b
+{
067a6b
+  static ShellAppCache *instance;
067a6b
+
067a6b
+  if (instance == NULL)
067a6b
+    {
067a6b
+      instance = g_object_new (SHELL_TYPE_APP_CACHE, NULL);
067a6b
+      g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance);
067a6b
+    }
067a6b
+
067a6b
+  return instance;
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+load_folder (GHashTable *folders,
067a6b
+             const char *path)
067a6b
+{
067a6b
+  g_autoptr(GDir) dir = NULL;
067a6b
+  const char *name;
067a6b
+
067a6b
+  g_assert (folders != NULL);
067a6b
+  g_assert (path != NULL);
067a6b
+
067a6b
+  dir = g_dir_open (path, 0, NULL);
067a6b
+  if (dir == NULL)
067a6b
+    return;
067a6b
+
067a6b
+  while ((name = g_dir_read_name (dir)))
067a6b
+    {
067a6b
+      g_autofree gchar *filename = NULL;
067a6b
+      g_autoptr(GKeyFile) keyfile = NULL;
067a6b
+
067a6b
+      /* First added wins */
067a6b
+      if (g_hash_table_contains (folders, name))
067a6b
+        continue;
067a6b
+
067a6b
+      filename = g_build_filename (path, name, NULL);
067a6b
+      keyfile = g_key_file_new ();
067a6b
+
067a6b
+      if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, NULL))
067a6b
+        {
067a6b
+          gchar *translated;
067a6b
+
067a6b
+          translated = g_key_file_get_locale_string (keyfile,
067a6b
+                                                     "Desktop Entry", "Name",
067a6b
+                                                     NULL, NULL);
067a6b
+
067a6b
+          if (translated != NULL)
067a6b
+            g_hash_table_insert (folders, g_strdup (name), translated);
067a6b
+        }
067a6b
+    }
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+load_folders (GHashTable *folders)
067a6b
+{
067a6b
+  const char * const *dirs;
067a6b
+  g_autofree gchar *userdir = NULL;
067a6b
+  guint i;
067a6b
+
067a6b
+  g_assert (folders != NULL);
067a6b
+
067a6b
+  userdir = g_build_filename (g_get_user_data_dir (), "desktop-directories", NULL);
067a6b
+  load_folder (folders, userdir);
067a6b
+
067a6b
+  dirs = g_get_system_data_dirs ();
067a6b
+  for (i = 0; dirs[i] != NULL; i++)
067a6b
+    {
067a6b
+      g_autofree gchar *sysdir = g_build_filename (dirs[i], "desktop-directories", NULL);
067a6b
+      load_folder (folders, sysdir);
067a6b
+    }
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+shell_app_cache_worker (GTask        *task,
067a6b
+                        gpointer      source_object,
067a6b
+                        gpointer      task_data,
067a6b
+                        GCancellable *cancellable)
067a6b
+{
067a6b
+  CacheState *state;
067a6b
+
067a6b
+  g_assert (G_IS_TASK (task));
067a6b
+  g_assert (SHELL_IS_APP_CACHE (source_object));
067a6b
+
067a6b
+  state = cache_state_new ();
067a6b
+  state->app_infos = g_app_info_get_all ();
067a6b
+  load_folders (state->folders);
067a6b
+
067a6b
+  g_task_return_pointer (task, state, (GDestroyNotify) cache_state_free);
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+apply_update_cb (GObject      *object,
067a6b
+                 GAsyncResult *result,
067a6b
+                 gpointer      user_data)
067a6b
+{
067a6b
+  ShellAppCache *cache = (ShellAppCache *)object;
067a6b
+  g_autoptr(GError) error = NULL;
067a6b
+  CacheState *state;
067a6b
+
067a6b
+  g_assert (SHELL_IS_APP_CACHE (cache));
067a6b
+  g_assert (G_IS_TASK (result));
067a6b
+  g_assert (user_data == NULL);
067a6b
+
067a6b
+  state = g_task_propagate_pointer (G_TASK (result), &error);
067a6b
+
067a6b
+  if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
067a6b
+    return;
067a6b
+
067a6b
+  g_list_free_full (cache->app_infos, g_object_unref);
067a6b
+  cache->app_infos = g_steal_pointer (&state->app_infos);
067a6b
+
067a6b
+  g_clear_pointer (&cache->folders, g_hash_table_unref);
067a6b
+  cache->folders = g_steal_pointer (&state->folders);
067a6b
+
067a6b
+  g_signal_emit (cache, signals[CHANGED], 0);
067a6b
+
067a6b
+  cache_state_free (state);
067a6b
+}
067a6b
+
067a6b
+static gboolean
067a6b
+shell_app_cache_do_update (gpointer user_data)
067a6b
+{
067a6b
+  ShellAppCache *cache = user_data;
067a6b
+  g_autoptr(GTask) task = NULL;
067a6b
+
067a6b
+  cache->queued_update = 0;
067a6b
+
067a6b
+  /* Reset the cancellable state so we don't race with
067a6b
+   * two updates coming back overlapped and applying the
067a6b
+   * information in the wrong order.
067a6b
+   */
067a6b
+  g_cancellable_cancel (cache->cancellable);
067a6b
+  g_clear_object (&cache->cancellable);
067a6b
+  cache->cancellable = g_cancellable_new ();
067a6b
+
067a6b
+  task = g_task_new (cache, cache->cancellable, apply_update_cb, NULL);
067a6b
+  g_task_set_source_tag (task, shell_app_cache_do_update);
067a6b
+  g_task_run_in_thread (task, shell_app_cache_worker);
067a6b
+
067a6b
+  return G_SOURCE_REMOVE;
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+shell_app_cache_queue_update (ShellAppCache *self)
067a6b
+{
067a6b
+  g_assert (SHELL_IS_APP_CACHE (self));
067a6b
+
067a6b
+  if (self->queued_update != 0)
067a6b
+    g_source_remove (self->queued_update);
067a6b
+
067a6b
+  self->queued_update = g_timeout_add_seconds (DEFAULT_TIMEOUT_SECONDS,
067a6b
+                                               shell_app_cache_do_update,
067a6b
+                                               self);
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+monitor_desktop_directories_for_data_dir (ShellAppCache *self,
067a6b
+                                          const gchar   *directory)
067a6b
+{
067a6b
+  g_autofree gchar *subdir = NULL;
067a6b
+  g_autoptr(GFile) file = NULL;
067a6b
+  g_autoptr(GFileMonitor) monitor = NULL;
067a6b
+
067a6b
+  g_assert (SHELL_IS_APP_CACHE (self));
067a6b
+
067a6b
+  if (directory == NULL)
067a6b
+    return;
067a6b
+
067a6b
+  subdir = g_build_filename (directory, "desktop-directories", NULL);
067a6b
+  file = g_file_new_for_path (subdir);
067a6b
+  monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
067a6b
+
067a6b
+  if (monitor != NULL)
067a6b
+    {
067a6b
+      g_file_monitor_set_rate_limit (monitor, DEFAULT_TIMEOUT_SECONDS * 1000);
067a6b
+      g_signal_connect_object (monitor,
067a6b
+                               "changed",
067a6b
+                               G_CALLBACK (shell_app_cache_queue_update),
067a6b
+                               self,
067a6b
+                               G_CONNECT_SWAPPED);
067a6b
+      g_ptr_array_add (self->dir_monitors, g_steal_pointer (&monitor));
067a6b
+    }
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+shell_app_cache_finalize (GObject *object)
067a6b
+{
067a6b
+  ShellAppCache *self = (ShellAppCache *)object;
067a6b
+
067a6b
+  g_clear_object (&self->monitor);
067a6b
+
067a6b
+  if (self->queued_update)
067a6b
+    {
067a6b
+      g_source_remove (self->queued_update);
067a6b
+      self->queued_update = 0;
067a6b
+    }
067a6b
+
067a6b
+  g_clear_pointer (&self->dir_monitors, g_ptr_array_unref);
067a6b
+  g_clear_pointer (&self->folders, g_hash_table_unref);
067a6b
+  g_list_free_full (self->app_infos, g_object_unref);
067a6b
+
067a6b
+  G_OBJECT_CLASS (shell_app_cache_parent_class)->finalize (object);
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+shell_app_cache_class_init (ShellAppCacheClass *klass)
067a6b
+{
067a6b
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
067a6b
+
067a6b
+  object_class->finalize = shell_app_cache_finalize;
067a6b
+
067a6b
+  /**
067a6b
+   * ShellAppCache::changed:
067a6b
+   *
067a6b
+   * The "changed" signal is emitted when the cache has updated
067a6b
+   * information about installed applications.
067a6b
+   */
067a6b
+  signals [CHANGED] =
067a6b
+    g_signal_new ("changed",
067a6b
+                  G_TYPE_FROM_CLASS (klass),
067a6b
+                  G_SIGNAL_RUN_LAST,
067a6b
+                  0, NULL, NULL, NULL,
067a6b
+                  G_TYPE_NONE, 0);
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+shell_app_cache_init (ShellAppCache *self)
067a6b
+{
067a6b
+  const gchar * const *sysdirs;
067a6b
+  guint i;
067a6b
+
067a6b
+  /* Monitor directories for translation changes */
067a6b
+  self->dir_monitors = g_ptr_array_new_with_free_func (g_object_unref);
067a6b
+  monitor_desktop_directories_for_data_dir (self, g_get_user_data_dir ());
067a6b
+  sysdirs = g_get_system_data_dirs ();
067a6b
+  for (i = 0; sysdirs[i] != NULL; i++)
067a6b
+    monitor_desktop_directories_for_data_dir (self, sysdirs[i]);
067a6b
+
067a6b
+  /* Load translated directory names immediately */
067a6b
+  self->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
067a6b
+  load_folders (self->folders);
067a6b
+
067a6b
+  /* Setup AppMonitor to track changes */
067a6b
+  self->monitor = g_app_info_monitor_get ();
067a6b
+  g_signal_connect_object (self->monitor,
067a6b
+                           "changed",
067a6b
+                           G_CALLBACK (shell_app_cache_queue_update),
067a6b
+                           self,
067a6b
+                           G_CONNECT_SWAPPED);
067a6b
+  self->app_infos = g_app_info_get_all ();
067a6b
+}
067a6b
+
067a6b
+/**
067a6b
+ * shell_app_cache_get_all:
067a6b
+ * @cache: (nullable): a #ShellAppCache or %NULL
067a6b
+ *
067a6b
+ * Like g_app_info_get_all() but always returns a
067a6b
+ * cached set of application info so the caller can be
067a6b
+ * sure that I/O will not happen on the current thread.
067a6b
+ *
067a6b
+ * Returns: (transfer none) (element-type GAppInfo):
067a6b
+ *   a #GList of references to #GAppInfo.
067a6b
+ */
067a6b
+GList *
067a6b
+shell_app_cache_get_all (ShellAppCache *cache)
067a6b
+{
067a6b
+  g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
067a6b
+
067a6b
+  return cache->app_infos;
067a6b
+}
067a6b
+
067a6b
+/**
067a6b
+ * shell_app_cache_get_info:
067a6b
+ * @cache: (nullable): a #ShellAppCache or %NULL
067a6b
+ * @id: the application id
067a6b
+ *
067a6b
+ * A replacement for g_desktop_app_info_new() that will lookup the
067a6b
+ * information from the cache instead of (re)loading from disk.
067a6b
+ *
067a6b
+ * Returns: (nullable) (transfer none): a #GDesktopAppInfo or %NULL
067a6b
+ */
067a6b
+GDesktopAppInfo *
067a6b
+shell_app_cache_get_info (ShellAppCache *cache,
067a6b
+                          const char    *id)
067a6b
+{
067a6b
+  const GList *iter;
067a6b
+
067a6b
+  g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
067a6b
+
067a6b
+  for (iter = cache->app_infos; iter != NULL; iter = iter->next)
067a6b
+    {
067a6b
+      GAppInfo *info = iter->data;
067a6b
+
067a6b
+      if (g_strcmp0 (id, g_app_info_get_id (info)) == 0)
067a6b
+        return G_DESKTOP_APP_INFO (info);
067a6b
+    }
067a6b
+
067a6b
+  return NULL;
067a6b
+}
067a6b
+
067a6b
+/**
067a6b
+ * shell_app_cache_translate_folder:
067a6b
+ * @cache: (nullable): a #ShellAppCache or %NULL
067a6b
+ * @name: the folder name
067a6b
+ *
067a6b
+ * Gets the translated folder name for @name if any exists.
067a6b
+ *
067a6b
+ * Returns: (nullable): the translated string or %NULL if there is no
067a6b
+ *   translation.
067a6b
+ */
067a6b
+char *
067a6b
+shell_app_cache_translate_folder (ShellAppCache *cache,
067a6b
+                                  const char    *name)
067a6b
+{
067a6b
+  g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
067a6b
+
067a6b
+  if (name == NULL)
067a6b
+    return NULL;
067a6b
+
067a6b
+  return g_strdup (g_hash_table_lookup (cache->folders, name));
067a6b
+}
067a6b
diff --git a/src/shell-app-system.c b/src/shell-app-system.c
067a6b
index 61ca1c35c..bb9163a18 100644
067a6b
--- a/src/shell-app-system.c
067a6b
+++ b/src/shell-app-system.c
067a6b
@@ -9,6 +9,7 @@
067a6b
 #include <gio/gio.h>
067a6b
 #include <glib/gi18n.h>
067a6b
 
067a6b
+#include "shell-app-cache-private.h"
067a6b
 #include "shell-app-private.h"
067a6b
 #include "shell-window-tracker-private.h"
067a6b
 #include "shell-app-system-private.h"
067a6b
@@ -94,14 +95,14 @@ static void
067a6b
 scan_startup_wm_class_to_id (ShellAppSystem *self)
067a6b
 {
067a6b
   ShellAppSystemPrivate *priv = self->priv;
067a6b
-  GList *l;
067a6b
+  const GList *l;
067a6b
+  GList *all;
067a6b
 
067a6b
   g_hash_table_remove_all (priv->startup_wm_class_to_id);
067a6b
 
067a6b
-  g_list_free_full (priv->installed_apps, g_object_unref);
067a6b
-  priv->installed_apps = g_app_info_get_all ();
067a6b
+  all = shell_app_cache_get_all (shell_app_cache_get_default ());
067a6b
 
067a6b
-  for (l = priv->installed_apps; l != NULL; l = l->next)
067a6b
+  for (l = all; l != NULL; l = l->next)
067a6b
     {
067a6b
       GAppInfo *info = l->data;
067a6b
       const char *startup_wm_class, *id, *old_id;
067a6b
@@ -131,7 +132,8 @@ app_is_stale (ShellApp *app)
067a6b
   if (shell_app_is_window_backed (app))
067a6b
     return FALSE;
067a6b
 
067a6b
-  info = g_desktop_app_info_new (shell_app_get_id (app));
067a6b
+  info = shell_app_cache_get_info (shell_app_cache_get_default (),
067a6b
+                                   shell_app_get_id (app));
067a6b
   if (!info)
067a6b
     return TRUE;
067a6b
 
067a6b
@@ -156,7 +158,6 @@ app_is_stale (ShellApp *app)
067a6b
     g_icon_equal (g_app_info_get_icon (old_info),
067a6b
                   g_app_info_get_icon (new_info));
067a6b
 
067a6b
-  g_object_unref (info);
067a6b
   return !is_unchanged;
067a6b
 }
067a6b
 
067a6b
@@ -210,11 +211,9 @@ rescan_icon_theme (ShellAppSystem *self)
067a6b
 }
067a6b
 
067a6b
 static void
067a6b
-installed_changed (GAppInfoMonitor *monitor,
067a6b
-                   gpointer         user_data)
067a6b
+installed_changed (ShellAppCache  *cache,
067a6b
+                   ShellAppSystem *self)
067a6b
 {
067a6b
-  ShellAppSystem *self = user_data;
067a6b
-
067a6b
   rescan_icon_theme (self);
067a6b
   scan_startup_wm_class_to_id (self);
067a6b
 
067a6b
@@ -227,7 +226,7 @@ static void
067a6b
 shell_app_system_init (ShellAppSystem *self)
067a6b
 {
067a6b
   ShellAppSystemPrivate *priv;
067a6b
-  GAppInfoMonitor *monitor;
067a6b
+  ShellAppCache *cache;
067a6b
 
067a6b
   self->priv = priv = shell_app_system_get_instance_private (self);
067a6b
 
067a6b
@@ -238,9 +237,9 @@ shell_app_system_init (ShellAppSystem *self)
067a6b
 
067a6b
   priv->startup_wm_class_to_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
067a6b
 
067a6b
-  monitor = g_app_info_monitor_get ();
067a6b
-  g_signal_connect (monitor, "changed", G_CALLBACK (installed_changed), self);
067a6b
-  installed_changed (monitor, self);
067a6b
+  cache = shell_app_cache_get_default ();
067a6b
+  g_signal_connect (cache, "changed", G_CALLBACK (installed_changed), self);
067a6b
+  installed_changed (cache, self);
067a6b
 }
067a6b
 
067a6b
 static void
067a6b
@@ -293,13 +292,12 @@ shell_app_system_lookup_app (ShellAppSystem   *self,
067a6b
   if (app)
067a6b
     return app;
067a6b
 
067a6b
-  info = g_desktop_app_info_new (id);
067a6b
+  info = shell_app_cache_get_info (shell_app_cache_get_default (), id);
067a6b
   if (!info)
067a6b
     return NULL;
067a6b
 
067a6b
   app = _shell_app_new (info);
067a6b
   g_hash_table_insert (priv->id_to_app, (char *) shell_app_get_id (app), app);
067a6b
-  g_object_unref (info);
067a6b
   return app;
067a6b
 }
067a6b
 
067a6b
@@ -507,7 +505,5 @@ shell_app_system_search (const char *search_string)
067a6b
 GList *
067a6b
 shell_app_system_get_installed (ShellAppSystem *self)
067a6b
 {
067a6b
-  ShellAppSystemPrivate *priv = self->priv;
067a6b
-
067a6b
-  return priv->installed_apps;
067a6b
+  return shell_app_cache_get_all (shell_app_cache_get_default ());
067a6b
 }
067a6b
diff --git a/src/shell-util.c b/src/shell-util.c
067a6b
index 70b8c0611..454058a05 100644
067a6b
--- a/src/shell-util.c
067a6b
+++ b/src/shell-util.c
067a6b
@@ -8,6 +8,7 @@
067a6b
 #include <GL/gl.h>
067a6b
 #include <cogl/cogl.h>
067a6b
 
067a6b
+#include "shell-app-cache-private.h"
067a6b
 #include "shell-util.h"
067a6b
 #include <glib/gi18n-lib.h>
067a6b
 #include <gtk/gtk.h>
067a6b
@@ -514,3 +515,18 @@ shell_util_composite_capture_images (ClutterCapture  *captures,
067a6b
 
067a6b
   return image;
067a6b
 }
067a6b
+
067a6b
+/**
067a6b
+ * shell_util_get_translated_folder_name:
067a6b
+ * @name: the untranslated folder name
067a6b
+ *
067a6b
+ * Attempts to translate the folder @name using translations provided
067a6b
+ * by .directory files.
067a6b
+ *
067a6b
+ * Returns: (nullable): a translated string or %NULL
067a6b
+ */
067a6b
+char *
067a6b
+shell_util_get_translated_folder_name (const char *name)
067a6b
+{
067a6b
+  return shell_app_cache_translate_folder (shell_app_cache_get_default (), name);
067a6b
+}
067a6b
diff --git a/src/shell-util.h b/src/shell-util.h
067a6b
index 2218594c1..4e30f3730 100644
067a6b
--- a/src/shell-util.h
067a6b
+++ b/src/shell-util.h
067a6b
@@ -60,6 +60,8 @@ cairo_surface_t * shell_util_composite_capture_images (ClutterCapture  *captures
067a6b
                                                        int              width,
067a6b
                                                        int              height);
067a6b
 
067a6b
+char *shell_util_get_translated_folder_name (const char *name);
067a6b
+
067a6b
 G_END_DECLS
067a6b
 
067a6b
 #endif /* __SHELL_UTIL_H__ */
067a6b
-- 
067a6b
2.26.0
067a6b