static void
shell_app_system_finalize (GObject *object)
{
  ShellAppSystem *self = SHELL_APP_SYSTEM (object);
  ShellAppSystemPrivate *priv = self->priv;

  gmenu_tree_remove_monitor (priv->apps_tree, on_tree_changed_cb, self);
  gmenu_tree_remove_monitor (priv->settings_tree, on_tree_changed_cb, self);

  gmenu_tree_unref (priv->apps_tree);
  gmenu_tree_unref (priv->settings_tree);

  g_hash_table_destroy (priv->app_id_to_info);
  g_hash_table_destroy (priv->app_id_to_app);

  g_slist_foreach (priv->cached_flattened_apps, (GFunc)shell_app_info_unref, NULL);
  g_slist_free (priv->cached_flattened_apps);
  priv->cached_flattened_apps = NULL;

  g_slist_foreach (priv->known_vendor_prefixes, (GFunc)g_free, NULL);
  g_slist_free (priv->known_vendor_prefixes);
  priv->known_vendor_prefixes = NULL;

  g_slist_foreach (priv->cached_settings, (GFunc)shell_app_info_unref, NULL);
  g_slist_free (priv->cached_settings);
  priv->cached_settings = NULL;

  G_OBJECT_CLASS (shell_app_system_parent_class)->finalize(object);
}
static gboolean
on_tree_changed (gpointer user_data)
{
  ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);

  reread_menus (self);

  g_signal_emit (self, signals[INSTALLED_CHANGED], 0);

  self->priv->app_change_timeout_id = 0;
  return FALSE;
}
static void
shell_app_system_finalize (GObject *object)
{
  ShellAppSystem *self = SHELL_APP_SYSTEM (object);
  ShellAppSystemPrivate *priv = self->priv;

  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_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
}
static void
shell_app_system_finalize (GObject *object)
{
  ShellAppSystem *self = SHELL_APP_SYSTEM (object);
  ShellAppSystemPrivate *priv = self->priv;

  g_object_unref (priv->apps_tree);
  g_object_unref (priv->settings_tree);

  g_hash_table_destroy (priv->running_apps);
  g_hash_table_destroy (priv->id_to_app);
  g_hash_table_destroy (priv->visible_id_to_app);
  g_hash_table_destroy (priv->setting_id_to_app);

  g_slist_free_full (priv->known_vendor_prefixes, g_free);
  priv->known_vendor_prefixes = NULL;

  G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object);
}
static void
on_settings_tree_changed_cb (GMenuTree *tree,
                             gpointer   user_data)
{
  ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
  GError *error = NULL;
  GHashTable *new_settings;
  GHashTableIter iter;
  gpointer key, value;

  g_assert (tree == self->priv->settings_tree);

  g_hash_table_remove_all (self->priv->setting_id_to_app);
  if (!gmenu_tree_load_sync (self->priv->settings_tree, &error))
    {
      if (error)
        {
          g_warning ("Failed to load apps: %s", error->message);
          g_error_free (error);
        }
      else
        {
          g_warning ("Failed to load apps");
        }
      return;
    }

  new_settings = get_flattened_entries_from_tree (tree);

  g_hash_table_iter_init (&iter, new_settings);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      const char *id = key;
      GMenuTreeEntry *entry = value;
      ShellApp *app;

      app = _shell_app_new (entry);
      g_hash_table_replace (self->priv->setting_id_to_app, (char*)id, app);
    }
  g_hash_table_destroy (new_settings);
}
static void
on_tree_changed_cb (GMenuTree *monitor, gpointer user_data)
{
  ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);

  /* GMenu currently gives us a separate notification on the entire
   * menu tree for each node in the tree that might potentially have
   * changed. (See http://bugzilla.gnome.org/show_bug.cgi?id=172046.)
   * We need to compress these to avoid doing large extra amounts of
   * work.
   *
   * Even when that bug is fixed, compression is still useful; for one
   * thing we want to need to compress across notifications of changes
   * to the settings tree. Second we want to compress if multiple
   * changes are made to the desktop files at different times but in
   * short succession.
   */

  if (self->priv->app_change_timeout_id != 0)
    return;
  self->priv->app_change_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 3000,
                                                          (GSourceFunc) on_tree_changed,
                                                          self, NULL);
}
static void
on_apps_tree_changed_cb (GMenuTree *tree,
                         gpointer   user_data)
{
  ShellAppSystem *self = SHELL_APP_SYSTEM (user_data);
  GError *error = NULL;
  GHashTable *new_apps;
  GHashTableIter iter;
  gpointer key, value;
  GSList *removed_apps = NULL;
  GSList *removed_node;

  g_assert (tree == self->priv->apps_tree);

  g_hash_table_remove_all (self->priv->visible_id_to_app);
  g_slist_free_full (self->priv->known_vendor_prefixes, g_free);
  self->priv->known_vendor_prefixes = NULL;

  if (!gmenu_tree_load_sync (self->priv->apps_tree, &error))
    {
      if (error)
        {
          g_warning ("Failed to load apps: %s", error->message);
          g_error_free (error);
        }
      else
        {
          g_warning ("Failed to load apps");
        }
      return;
    }

  new_apps = get_flattened_entries_from_tree (self->priv->apps_tree);
  g_hash_table_iter_init (&iter, new_apps);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      const char *id = key;
      GMenuTreeEntry *entry = value;
      GMenuTreeEntry *old_entry;
      char *prefix;
      ShellApp *app;
      
      prefix = get_prefix_for_entry (entry);
      
      if (prefix != NULL
          && !g_slist_find_custom (self->priv->known_vendor_prefixes, prefix,
                                   (GCompareFunc)g_strcmp0))
        self->priv->known_vendor_prefixes = g_slist_append (self->priv->known_vendor_prefixes,
                                                            prefix);
      else
        g_free (prefix);
      
      app = g_hash_table_lookup (self->priv->id_to_app, id);
      if (app != NULL)
        {
          /* We hold a reference to the original entry temporarily,
           * because otherwise the hash table would be referencing
           * potentially free'd memory until we replace it below with
           * the new data.
           */
          old_entry = shell_app_get_tree_entry (app);
          gmenu_tree_item_ref (old_entry);
          _shell_app_set_entry (app, entry);
          g_object_ref (app);  /* Extra ref, removed in _replace below */
        }
      else
        {
          old_entry = NULL;
          app = _shell_app_new (entry);
        }
      /* Note that "id" is owned by app->entry.  Since we're always
       * setting a new entry, even if the app already exists in the
       * hash table we need to replace the key so that the new id
       * string is pointed to.
       */
      g_hash_table_replace (self->priv->id_to_app, (char*)id, app);
      if (!gmenu_tree_entry_get_is_nodisplay_recurse (entry))
        g_hash_table_replace (self->priv->visible_id_to_app, (char*)id, app);

      if (old_entry)
        gmenu_tree_item_unref (old_entry);
    }
  /* Now iterate over the apps again; we need to unreference any apps
   * which have been removed.  The JS code may still be holding a
   * reference; that's fine.
   */
  g_hash_table_iter_init (&iter, self->priv->id_to_app);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      const char *id = key;
      
      if (!g_hash_table_lookup (new_apps, id))
        removed_apps = g_slist_prepend (removed_apps, (char*)id);
    }
  for (removed_node = removed_apps; removed_node; removed_node = removed_node->next)
    {
      const char *id = removed_node->data;
      g_hash_table_remove (self->priv->id_to_app, id);
    }
  g_slist_free (removed_apps);
      
  g_hash_table_destroy (new_apps);

  g_signal_emit (self, signals[INSTALLED_CHANGED], 0);
}