[gnome-software] flatpak: Use FlatpakTransaction to install, remove and update



commit 1113bf5a0468d267153710e3ba3a2ed199e33c70
Author: Richard Hughes <richard hughsie com>
Date:   Wed Jun 27 15:19:36 2018 +0100

    flatpak: Use FlatpakTransaction to install, remove and update
    
    This ensures we will get the same set of extensions and runtimes as the flatpak
    CLI tool, and allows us to make the plugin considerably simpler. Rather than
    using the plugin cache for the transaction, add each app and runtime being
    processed to a per-transaction cache which cleans up the cache model a lot.
    
    This allows us to remove gs_app_{g|s}et_update_runtime(); nobody every properly
    understood what this was doing, and it's no longer required.
    
    The counterpart matching also gets cut out; it was a huge layering violation
    and didn't work very well when there were two FlatpakInstallations with the
    same scope. If the GsApp scope is unknown, ask each GsFlatpak instance to
    refine the state until it returns without error.
    
    This also allows the runtime to use a different FlatpakInstallation than the
    application itself.

 .gitlab-ci.yml                           |    4 +-
 lib/gs-app.c                             |   55 +-
 lib/gs-app.h                             |    3 -
 meson.build                              |    2 +-
 plugins/flatpak/gs-flatpak-app.c         |   15 +-
 plugins/flatpak/gs-flatpak-app.h         |    8 +-
 plugins/flatpak/gs-flatpak-transaction.c |  344 ++++++++
 plugins/flatpak/gs-flatpak-transaction.h |   46 ++
 plugins/flatpak/gs-flatpak-utils.c       |   41 +-
 plugins/flatpak/gs-flatpak.c             | 1281 +++++-------------------------
 plugins/flatpak/gs-flatpak.h             |   29 +-
 plugins/flatpak/gs-plugin-flatpak.c      |  551 +++++++++----
 plugins/flatpak/gs-self-test.c           |   28 +-
 plugins/flatpak/meson.build              |    1 +
 14 files changed, 1057 insertions(+), 1351 deletions(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 586f98c0..4376c5bc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: fedora:27
+image: fedora:28
 
 stages:
   - build
@@ -6,7 +6,7 @@ stages:
 before_script:
   # Update and use base build deps
   - dnf update -y && dnf -y install dnf-plugins-core @buildsys-build
-  - dnf -y builddep gnome-software
+  - dnf --enablerepo updates-testing -y builddep gnome-software
   # Some deps may not be sync'd
   - dnf -y install rpm-ostree-devel
 
diff --git a/lib/gs-app.c b/lib/gs-app.c
index c65a7b93..72586d9e 100644
--- a/lib/gs-app.c
+++ b/lib/gs-app.c
@@ -96,7 +96,6 @@ typedef struct
        gchar                   *update_version_ui;
        gchar                   *update_details;
        AsUrgencyKind            update_urgency;
-       GsApp                   *update_runtime;
        gchar                   *management_plugin;
        guint                    match_value;
        guint                    priority;
@@ -630,10 +629,6 @@ gs_app_to_string_append (GsApp *app, GString *str)
                g_string_append (str, "\n\tRuntime:\n\t");
                gs_app_to_string_append (priv->runtime, str);
        }
-       if (priv->update_runtime != NULL) {
-               g_string_append (str, "\n\tUpdate Runtime:\n\t");
-               gs_app_to_string_append (priv->update_runtime, str);
-       }
        g_string_append_printf (str, "\n");
 }
 
@@ -1818,9 +1813,7 @@ gs_app_get_runtime (GsApp *app)
 {
        GsAppPrivate *priv = gs_app_get_instance_private (app);
        g_return_val_if_fail (GS_IS_APP (app), NULL);
-       if (priv->runtime != NULL)
-               return priv->runtime;
-       return priv->update_runtime;
+       return priv->runtime;
 }
 
 /**
@@ -1843,46 +1836,6 @@ gs_app_set_runtime (GsApp *app, GsApp *runtime)
        g_set_object (&priv->runtime, runtime);
 }
 
-/**
- * gs_app_get_update_runtime:
- * @app: a #GsApp
- *
- * Gets the runtime required for the application update.
- *
- * Returns: (transfer none): a #GsApp, or %NULL for unset
- *
- * Since: 3.22
- **/
-GsApp *
-gs_app_get_update_runtime (GsApp *app)
-{
-       GsAppPrivate *priv = gs_app_get_instance_private (app);
-       g_return_val_if_fail (GS_IS_APP (app), NULL);
-       if (priv->update_runtime != NULL)
-               return priv->update_runtime;
-       return priv->runtime;
-}
-
-/**
- * gs_app_set_update_runtime:
- * @app: a #GsApp
- * @runtime: a #GsApp
- *
- * Sets the runtime that the application update requires.
- *
- * Since: 3.22
- **/
-void
-gs_app_set_update_runtime (GsApp *app, GsApp *runtime)
-{
-       GsAppPrivate *priv = gs_app_get_instance_private (app);
-       g_autoptr(GMutexLocker) locker = NULL;
-       g_return_if_fail (GS_IS_APP (app));
-       g_return_if_fail (app != runtime);
-       locker = g_mutex_locker_new (&priv->mutex);
-       g_set_object (&priv->update_runtime, runtime);
-}
-
 /**
  * gs_app_set_pixbuf:
  * @app: a #GsApp
@@ -3077,10 +3030,7 @@ gs_app_get_size_download (GsApp *app)
        sz = priv->size_download;
 
        /* add the runtime if this is not installed */
-       if (priv->update_runtime != NULL) {
-               if (gs_app_get_state (priv->update_runtime) == AS_APP_STATE_AVAILABLE)
-                       sz += gs_app_get_size_installed (priv->update_runtime);
-       } else if (priv->runtime != NULL) {
+       if (priv->runtime != NULL) {
                if (gs_app_get_state (priv->runtime) == AS_APP_STATE_AVAILABLE)
                        sz += gs_app_get_size_installed (priv->runtime);
        }
@@ -4207,7 +4157,6 @@ gs_app_dispose (GObject *object)
        GsAppPrivate *priv = gs_app_get_instance_private (app);
 
        g_clear_object (&priv->runtime);
-       g_clear_object (&priv->update_runtime);
 
        g_clear_pointer (&priv->addons, g_object_unref);
        g_clear_pointer (&priv->history, g_object_unref);
diff --git a/lib/gs-app.h b/lib/gs-app.h
index 9a507768..9cb2bc55 100644
--- a/lib/gs-app.h
+++ b/lib/gs-app.h
@@ -224,9 +224,6 @@ void                 gs_app_set_update_details      (GsApp          *app,
 AsUrgencyKind   gs_app_get_update_urgency      (GsApp          *app);
 void            gs_app_set_update_urgency      (GsApp          *app,
                                                 AsUrgencyKind   update_urgency);
-GsApp          *gs_app_get_update_runtime      (GsApp          *app);
-void            gs_app_set_update_runtime      (GsApp          *app,
-                                                GsApp          *runtime);
 const gchar    *gs_app_get_management_plugin   (GsApp          *app);
 void            gs_app_set_management_plugin   (GsApp          *app,
                                                 const gchar    *management_plugin);
diff --git a/meson.build b/meson.build
index 326bdcb4..02bf9357 100644
--- a/meson.build
+++ b/meson.build
@@ -150,7 +150,7 @@ if get_option('enable-fwupd')
 endif
 
 if get_option('enable-flatpak')
-  flatpak = dependency('flatpak', version : '>= 0.6.12')
+  flatpak = dependency('flatpak', version : '>= 0.99.3')
 endif
 
 if get_option('enable-rpm-ostree')
diff --git a/plugins/flatpak/gs-flatpak-app.c b/plugins/flatpak/gs-flatpak-app.c
index 69b3a0dd..071877e0 100644
--- a/plugins/flatpak/gs-flatpak-app.c
+++ b/plugins/flatpak/gs-flatpak-app.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2017-2018 Richard Hughes <richard hughsie com>
  *
  * Licensed under the GNU General Public License Version 2
  *
@@ -58,10 +58,10 @@ gs_flatpak_app_get_file_kind (GsApp *app)
        return g_variant_get_uint32 (tmp);
 }
 
-GsApp *
-gs_flatpak_app_get_runtime_repo (GsApp *app)
+const gchar *
+gs_flatpak_app_get_runtime_url (GsApp *app)
 {
-       return g_object_get_data (G_OBJECT (app), "flatpak::RuntimeRepo");
+       return gs_app_get_metadata_item (app, "flatpak::RuntimeUrl");
 }
 
 FlatpakRefKind
@@ -144,12 +144,9 @@ gs_flatpak_app_set_file_kind (GsApp *app, GsFlatpakAppFileKind file_kind)
 }
 
 void
-gs_flatpak_app_set_runtime_repo (GsApp *app, GsApp *runtime_repo)
+gs_flatpak_app_set_runtime_url (GsApp *app, const gchar *val)
 {
-       g_object_set_data_full (G_OBJECT (app),
-                               "flatpak::RuntimeRepo",
-                               g_object_ref (runtime_repo),
-                               (GDestroyNotify) g_object_unref);
+       gs_app_set_metadata (app, "flatpak::RuntimeUrl", val);
 }
 
 void
diff --git a/plugins/flatpak/gs-flatpak-app.h b/plugins/flatpak/gs-flatpak-app.h
index e44896ee..ebe06ab4 100644
--- a/plugins/flatpak/gs-flatpak-app.h
+++ b/plugins/flatpak/gs-flatpak-app.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2017-2018 Richard Hughes <richard hughsie com>
  *
  * Licensed under the GNU General Public License Version 2
  *
@@ -49,7 +49,7 @@ const gchar           *gs_flatpak_app_get_object_id           (GsApp          *app);
 const gchar            *gs_flatpak_app_get_repo_gpgkey         (GsApp          *app);
 const gchar            *gs_flatpak_app_get_repo_url            (GsApp          *app);
 GsFlatpakAppFileKind    gs_flatpak_app_get_file_kind           (GsApp          *app);
-GsApp                  *gs_flatpak_app_get_runtime_repo        (GsApp          *app);
+const gchar            *gs_flatpak_app_get_runtime_url         (GsApp          *app);
 
 void                    gs_flatpak_app_set_ref_name            (GsApp          *app,
                                                                 const gchar    *val);
@@ -70,8 +70,8 @@ void                   gs_flatpak_app_set_repo_url            (GsApp          *app,
                                                                 const gchar    *val);
 void                    gs_flatpak_app_set_file_kind           (GsApp          *app,
                                                                 GsFlatpakAppFileKind   file_kind);
-void                    gs_flatpak_app_set_runtime_repo        (GsApp          *app,
-                                                                GsApp          *runtime_repo);
+void                    gs_flatpak_app_set_runtime_url         (GsApp          *app,
+                                                                const gchar    *val);
 void                    gs_flatpak_app_set_main_app_ref_name   (GsApp          *app,
                                                                 const gchar    *main_app_ref);
 const gchar            *gs_flatpak_app_get_main_app_ref_name   (GsApp          *app);
diff --git a/plugins/flatpak/gs-flatpak-transaction.c b/plugins/flatpak/gs-flatpak-transaction.c
new file mode 100644
index 00000000..dbc7af9c
--- /dev/null
+++ b/plugins/flatpak/gs-flatpak-transaction.c
@@ -0,0 +1,344 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include "gs-flatpak-app.h"
+#include "gs-flatpak-transaction.h"
+
+struct _GsFlatpakTransaction {
+       FlatpakTransaction       parent_instance;
+       FlatpakInstallation     *installation;
+       GHashTable              *refhash;       /* ref:GsApp */
+};
+
+enum {
+       SIGNAL_REF_TO_APP,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GsFlatpakTransaction, gs_flatpak_transaction, FLATPAK_TYPE_TRANSACTION)
+
+FlatpakInstallation *
+gs_flatpak_transaction_get_inst (FlatpakTransaction *transaction)
+{
+       GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction);
+       return self->installation;
+}
+
+static void
+gs_flatpak_transaction_finalize (GObject *object)
+{
+       GsFlatpakTransaction *self;
+       g_return_if_fail (GS_IS_FLATPAK_TRANSACTION (object));
+       self = GS_FLATPAK_TRANSACTION (object);
+
+       g_assert (self != NULL);
+       g_hash_table_unref (self->refhash);
+
+       G_OBJECT_CLASS (gs_flatpak_transaction_parent_class)->finalize (object);
+}
+
+GsApp *
+gs_flatpak_transaction_get_app_by_ref (FlatpakTransaction *transaction, const gchar *ref)
+{
+       GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction);
+       return g_hash_table_lookup (self->refhash, ref);
+}
+
+static void
+gs_flatpak_transaction_add_app_internal (GsFlatpakTransaction *self, GsApp *app)
+{
+       g_autofree gchar *ref = gs_flatpak_app_get_ref_display (app);
+       g_hash_table_insert (self->refhash, g_steal_pointer (&ref), g_object_ref (app));
+}
+
+void
+gs_flatpak_transaction_add_app (FlatpakTransaction *transaction, GsApp *app)
+{
+       GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction);
+       gs_flatpak_transaction_add_app_internal (self, app);
+       if (gs_app_get_runtime (app) != NULL)
+               gs_flatpak_transaction_add_app_internal (self, gs_app_get_runtime (app));
+}
+
+static GsApp *
+_ref_to_app (GsFlatpakTransaction *self, const gchar *ref)
+{
+       GsApp *app = g_hash_table_lookup (self->refhash, ref);
+       if (app != NULL)
+               return g_object_ref (app);
+       g_signal_emit (self, signals[SIGNAL_REF_TO_APP], 0, ref, &app);
+       return app;
+}
+
+static void
+_transaction_operation_set_app (FlatpakTransactionOperation *op, GsApp *app)
+{
+       g_object_set_data_full (G_OBJECT (op), "GsApp",
+                               g_object_ref (app), (GDestroyNotify) g_object_unref);
+}
+
+static GsApp *
+_transaction_operation_get_app (FlatpakTransactionOperation *op)
+{
+       return g_object_get_data (G_OBJECT (op), "GsApp");
+}
+
+static gboolean
+_transaction_ready (FlatpakTransaction *transaction)
+{
+       GsFlatpakTransaction *self = GS_FLATPAK_TRANSACTION (transaction);
+       g_autolist(GObject) ops = NULL;
+
+       /* nothing to do */
+       ops = flatpak_transaction_get_operations (transaction);
+       if (ops == NULL)
+               return TRUE; // FIXME: error?
+       for (GList *l = ops; l != NULL; l = l->next) {
+               FlatpakTransactionOperation *op = l->data;
+               const gchar *ref = flatpak_transaction_operation_get_ref (op);
+               g_autoptr(GsApp) app = _ref_to_app (self, ref);
+               if (app != NULL)
+                       _transaction_operation_set_app (op, app);
+       }
+       return TRUE;
+}
+
+static void
+_transaction_progress_changed_cb (FlatpakTransactionProgress *progress,
+                                 gpointer user_data)
+{
+       GsApp *app = GS_APP (user_data);
+       guint percent = flatpak_transaction_progress_get_progress (progress);
+       if (flatpak_transaction_progress_get_is_estimating (progress))
+               return;
+       gs_app_set_progress (app, percent);
+}
+
+static const gchar *
+_flatpak_transaction_operation_type_to_string (FlatpakTransactionOperationType ot)
+{
+       if (ot == FLATPAK_TRANSACTION_OPERATION_INSTALL)
+               return "install";
+       if (ot == FLATPAK_TRANSACTION_OPERATION_UPDATE)
+               return "update";
+       if (ot == FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE)
+               return "install-bundle";
+       if (ot == FLATPAK_TRANSACTION_OPERATION_UNINSTALL)
+               return "uninstall";
+       return NULL;
+}
+
+static void
+_transaction_new_operation (FlatpakTransaction *transaction,
+                           FlatpakTransactionOperation *operation,
+                           FlatpakTransactionProgress *progress)
+{
+       GsApp *app;
+
+       /* find app */
+       app = _transaction_operation_get_app (operation);
+       if (app == NULL) {
+               FlatpakTransactionOperationType ot;
+               ot = flatpak_transaction_operation_get_operation_type (operation);
+               g_warning ("failed to find app for %s during %s",
+                          flatpak_transaction_operation_get_ref (operation),
+                          _flatpak_transaction_operation_type_to_string (ot));
+               return;
+       }
+
+       /* report progress */
+       g_signal_connect_object (progress, "changed",
+                                G_CALLBACK (_transaction_progress_changed_cb),
+                                app, 0);
+       flatpak_transaction_progress_set_update_frequency (progress, 100); /* FIXME? */
+
+       /* set app status */
+       switch (flatpak_transaction_operation_get_operation_type (operation)) {
+       case FLATPAK_TRANSACTION_OPERATION_INSTALL:
+               if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+                       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+               gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+               break;
+       case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
+               if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+                       gs_app_set_state (app, AS_APP_STATE_AVAILABLE_LOCAL);
+               gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+               break;
+       case FLATPAK_TRANSACTION_OPERATION_UPDATE:
+               if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+                       gs_app_set_state (app, AS_APP_STATE_UPDATABLE);
+               gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+               break;
+       case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
+               gs_app_set_state (app, AS_APP_STATE_REMOVING);
+               break;
+       default:
+               break;
+       }
+}
+
+static void
+_transaction_operation_done (FlatpakTransaction *transaction,
+                            FlatpakTransactionOperation *operation,
+                            FlatpakTransactionResult details)
+{
+       /* invalidate */
+       GsApp *app = _transaction_operation_get_app (operation);
+       if (app == NULL) {
+               g_warning ("failed to find app for %s",
+                          flatpak_transaction_operation_get_ref (operation));
+               return;
+       }
+       switch (flatpak_transaction_operation_get_operation_type (operation)) {
+       case FLATPAK_TRANSACTION_OPERATION_INSTALL:
+       case FLATPAK_TRANSACTION_OPERATION_INSTALL_BUNDLE:
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+               break;
+       case FLATPAK_TRANSACTION_OPERATION_UPDATE:
+               gs_app_set_version (app, gs_app_get_update_version (app));
+               gs_app_set_update_details (app, NULL);
+               gs_app_set_update_urgency (app, AS_URGENCY_KIND_UNKNOWN);
+               gs_app_set_update_version (app, NULL);
+               /* force getting the new runtime */
+               gs_app_remove_kudo (app, GS_APP_KUDO_SANDBOXED);
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+               break;
+       case FLATPAK_TRANSACTION_OPERATION_UNINSTALL:
+               /* we don't actually know if this app is re-installable */
+               gs_flatpak_app_set_commit (app, NULL);
+               gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
+               break;
+       default:
+               gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
+               break;
+       }
+}
+
+
+static gboolean
+_transaction_operation_error (FlatpakTransaction *transaction,
+                             FlatpakTransactionOperation *operation,
+                             const GError *error,
+                             FlatpakTransactionErrorDetails detail)
+{
+       /* invalidate */
+       GsApp *app = _transaction_operation_get_app (operation);
+       if (app != NULL)
+               gs_app_set_state_recover (app);
+       if (g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_SKIPPED)) {
+               g_printerr ("%s", error->message);
+               return TRUE;
+       }
+       g_printerr ("%s", error->message);
+       return FALSE;
+}
+
+static int
+_transaction_choose_remote_for_ref (FlatpakTransaction *transaction,
+                                   const char *for_ref,
+                                   const char *runtime_ref,
+                                   const char * const *remotes)
+{
+       //FIXME: do something smarter
+       return 0;
+}
+
+static void
+_transaction_end_of_lifed (FlatpakTransaction *transaction,
+                          const gchar *ref,
+                          const gchar *reason,
+                          const gchar *rebase)
+{
+       if (rebase) {
+               g_printerr ("%s is end-of-life, in preference of %s\n", ref, rebase);
+       } else if (reason) {
+               g_printerr ("%s is end-of-life, with reason: %s\n", ref, reason);
+       }
+       //FIXME: show something in the UI
+}
+
+static gboolean
+_transaction_add_new_remote (FlatpakTransaction *transaction,
+                            FlatpakTransactionRemoteReason reason,
+                            const char *from_id,
+                            const char *remote_name,
+                            const char *url)
+{
+       /* additional applications */
+       if (reason == FLATPAK_TRANSACTION_REMOTE_GENERIC_REPO) {
+               g_debug ("configuring %s as new generic remote", url);
+               return TRUE; //FIXME?
+       }
+
+       /* runtime deps always make sense */
+       if (reason == FLATPAK_TRANSACTION_REMOTE_RUNTIME_DEPS) {
+               g_debug ("configuring %s as new remote for deps", url);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void
+gs_flatpak_transaction_class_init (GsFlatpakTransactionClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       FlatpakTransactionClass *transaction_class = FLATPAK_TRANSACTION_CLASS (klass);
+       object_class->finalize = gs_flatpak_transaction_finalize;
+       transaction_class->ready = _transaction_ready;
+       transaction_class->add_new_remote = _transaction_add_new_remote;
+       transaction_class->new_operation = _transaction_new_operation;
+       transaction_class->operation_done = _transaction_operation_done;
+       transaction_class->operation_error = _transaction_operation_error;
+       transaction_class->choose_remote_for_ref = _transaction_choose_remote_for_ref;
+       transaction_class->end_of_lifed = _transaction_end_of_lifed;
+
+       signals[SIGNAL_REF_TO_APP] =
+               g_signal_new ("ref-to-app",
+                             G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+                             0, NULL, NULL, NULL, G_TYPE_OBJECT, 1, G_TYPE_STRING);
+}
+
+static void
+gs_flatpak_transaction_init (GsFlatpakTransaction *self)
+{
+       self->refhash = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                              g_free, (GDestroyNotify) g_object_unref);
+}
+
+FlatpakTransaction *
+gs_flatpak_transaction_new (FlatpakInstallation        *installation,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       GsFlatpakTransaction *self;
+       self = g_initable_new (GS_TYPE_FLATPAK_TRANSACTION,
+                              cancellable, error,
+                              "installation", installation,
+                              NULL);
+       if (self == NULL)
+               return NULL;
+       return FLATPAK_TRANSACTION (self);
+}
diff --git a/plugins/flatpak/gs-flatpak-transaction.h b/plugins/flatpak/gs-flatpak-transaction.h
new file mode 100644
index 00000000..56b6a676
--- /dev/null
+++ b/plugins/flatpak/gs-flatpak-transaction.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2018 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GS_FLATPAK_TRANSACTION_H
+#define __GS_FLATPAK_TRANSACTION_H
+
+#include <gnome-software.h>
+#include <flatpak.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_FLATPAK_TRANSACTION (gs_flatpak_transaction_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsFlatpakTransaction, gs_flatpak_transaction, GS, FLATPAK_TRANSACTION, 
FlatpakTransaction)
+
+FlatpakTransaction     *gs_flatpak_transaction_new             (FlatpakInstallation    *installation,
+                                                                GCancellable           *cancellable,
+                                                                GError                 **error);
+FlatpakInstallation    *gs_flatpak_transaction_get_inst        (FlatpakTransaction     *transaction);
+GsApp                  *gs_flatpak_transaction_get_app_by_ref  (FlatpakTransaction     *transaction,
+                                                                const gchar            *ref);
+void                    gs_flatpak_transaction_add_app         (FlatpakTransaction     *transaction,
+                                                                GsApp                  *app);
+
+G_END_DECLS
+
+#endif /* __GS_FLATPAK_TRANSACTION_H */
+
diff --git a/plugins/flatpak/gs-flatpak-utils.c b/plugins/flatpak/gs-flatpak-utils.c
index c267ea01..58544d26 100644
--- a/plugins/flatpak/gs-flatpak-utils.c
+++ b/plugins/flatpak/gs-flatpak-utils.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2017-2018 Richard Hughes <richard hughsie com>
  *
  * Licensed under the GNU General Public License Version 2
  *
@@ -51,6 +51,8 @@ gs_flatpak_error_convert (GError **perror)
                switch (error->code) {
                case FLATPAK_ERROR_ALREADY_INSTALLED:
                case FLATPAK_ERROR_NOT_INSTALLED:
+               case FLATPAK_ERROR_REMOTE_NOT_FOUND:
+               case FLATPAK_ERROR_RUNTIME_NOT_FOUND:
                        error->code = GS_PLUGIN_ERROR_NOT_SUPPORTED;
                        break;
                default:
@@ -58,8 +60,9 @@ gs_flatpak_error_convert (GError **perror)
                        break;
                }
        } else {
-               g_warning ("can't reliably fixup error from domain %s",
-                          g_quark_to_string (error->domain));
+               g_warning ("can't reliably fixup error from domain %s: %s",
+                          g_quark_to_string (error->domain),
+                          error->message);
                error->code = GS_PLUGIN_ERROR_FAILED;
        }
        error->domain = GS_PLUGIN_ERROR;
@@ -145,14 +148,13 @@ gs_flatpak_app_new_from_repo_file (GFile *file,
        /* create source */
        repo_title = g_key_file_get_string (kf, "Flatpak Repo", "Title", NULL);
        repo_url = g_key_file_get_string (kf, "Flatpak Repo", "Url", NULL);
-       repo_gpgkey = g_key_file_get_string (kf, "Flatpak Repo", "GPGKey", NULL);
-       if (repo_title == NULL || repo_url == NULL || repo_gpgkey == NULL ||
-           repo_title[0] == '\0' || repo_url[0] == '\0' || repo_gpgkey[0] == '\0') {
+       if (repo_title == NULL || repo_url == NULL ||
+           repo_title[0] == '\0' || repo_url[0] == '\0') {
                g_set_error_literal (error,
                                     GS_PLUGIN_ERROR,
                                     GS_PLUGIN_ERROR_NOT_SUPPORTED,
                                     "not enough data in file, "
-                                    "expected Title, Url, GPGKey");
+                                    "expected at least Title and Url");
                return NULL;
        }
 
@@ -168,16 +170,6 @@ gs_flatpak_app_new_from_repo_file (GFile *file,
                }
        }
 
-       /* user specified a URL */
-       if (g_str_has_prefix (repo_gpgkey, "http://";) ||
-           g_str_has_prefix (repo_gpgkey, "https://";)) {
-               g_set_error_literal (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "Base64 encoded GPGKey required, not URL");
-               return NULL;
-       }
-
        /* create source */
        app = gs_flatpak_app_new (repo_id);
        gs_flatpak_app_set_file_kind (app, GS_FLATPAK_APP_FILE_KIND_REPO);
@@ -185,10 +177,23 @@ gs_flatpak_app_new_from_repo_file (GFile *file,
        gs_app_set_state (app, AS_APP_STATE_AVAILABLE_LOCAL);
        gs_app_add_quirk (app, AS_APP_QUIRK_NOT_LAUNCHABLE);
        gs_app_set_name (app, GS_APP_QUALITY_NORMAL, repo_title);
-       gs_flatpak_app_set_repo_gpgkey (app, repo_gpgkey);
        gs_flatpak_app_set_repo_url (app, repo_url);
        gs_app_set_origin_hostname (app, repo_url);
 
+       /* user specified a URL */
+       repo_gpgkey = g_key_file_get_string (kf, "Flatpak Repo", "GPGKey", NULL);
+       if (repo_gpgkey != NULL) {
+               if (g_str_has_prefix (repo_gpgkey, "http://";) ||
+                   g_str_has_prefix (repo_gpgkey, "https://";)) {
+                       g_set_error_literal (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                            "Base64 encoded GPGKey required, not URL");
+                       return NULL;
+               }
+               gs_flatpak_app_set_repo_gpgkey (app, repo_gpgkey);
+       }
+
        /* optional data */
        repo_homepage = g_key_file_get_string (kf, "Flatpak Repo", "Homepage", NULL);
        if (repo_homepage != NULL)
diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c
index e3dda2dd..0515f091 100644
--- a/plugins/flatpak/gs-flatpak.c
+++ b/plugins/flatpak/gs-flatpak.c
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
  * Copyright (C) 2016 Joaquim Rocha <jrocha endlessm com>
- * Copyright (C) 2016-2017 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2016-2018 Richard Hughes <richard hughsie com>
  * Copyright (C) 2016-2018 Kalev Lember <klember redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -66,21 +66,6 @@ gs_flatpak_build_id (FlatpakRef *xref)
        return g_strdup (flatpak_ref_get_name (xref));
 }
 
-static FlatpakInstalledRef *
-get_installed_ref_for_app (FlatpakInstallation *installation,
-                          GsApp *app,
-                          GCancellable *cancellable,
-                          GError **error)
-{
-       return flatpak_installation_get_installed_ref (installation,
-                                                      gs_flatpak_app_get_ref_kind (app),
-                                                      gs_flatpak_app_get_ref_name (app),
-                                                      gs_flatpak_app_get_ref_arch (app),
-                                                      gs_flatpak_app_get_ref_branch (app),
-                                                      cancellable,
-                                                      error);
-}
-
 static void
 gs_plugin_refine_item_scope (GsFlatpak *self, GsApp *app)
 {
@@ -91,11 +76,33 @@ gs_plugin_refine_item_scope (GsFlatpak *self, GsApp *app)
 }
 
 static void
-gs_flatpak_set_metadata (GsFlatpak *self, GsApp *app, FlatpakRef *xref)
+gs_flatpak_claim_app (GsFlatpak *self, GsApp *app)
 {
-       /* core */
+       if (gs_app_get_management_plugin (app) != NULL)
+               return;
        gs_app_set_management_plugin (app, gs_plugin_get_name (self->plugin));
        gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_FLATPAK);
+       gs_app_set_scope (app, self->scope);
+
+       /* ony when we have a non-temp object */
+       if ((self->flags & GS_FLATPAK_FLAG_IS_TEMPORARY) == 0)
+               gs_flatpak_app_set_object_id (app, gs_flatpak_get_id (self));
+}
+
+static void
+gs_flatpak_claim_app_list (GsFlatpak *self, GsAppList *list)
+{
+       for (guint i = 0; i < gs_app_list_length (list); i++) {
+               GsApp *app = gs_app_list_index (list, i);
+               gs_flatpak_claim_app (self, app);
+       }
+}
+
+static void
+gs_flatpak_set_metadata (GsFlatpak *self, GsApp *app, FlatpakRef *xref)
+{
+       /* core */
+       gs_flatpak_claim_app (self, app);
        gs_app_set_branch (app, flatpak_ref_get_branch (xref));
        gs_plugin_refine_item_scope (self, app);
 
@@ -106,10 +113,6 @@ gs_flatpak_set_metadata (GsFlatpak *self, GsApp *app, FlatpakRef *xref)
        gs_flatpak_app_set_ref_branch (app, flatpak_ref_get_branch (xref));
        gs_flatpak_app_set_commit (app, flatpak_ref_get_commit (xref));
 
-       /* ony when we have a non-temp object */
-       if ((self->flags & GS_FLATPAK_FLAG_IS_TEMPORARY) == 0)
-               gs_flatpak_app_set_object_id (app, gs_flatpak_get_id (self));
-
        /* map the flatpak kind to the gnome-software kind */
        if (flatpak_ref_get_kind (xref) == FLATPAK_REF_KIND_APP) {
                gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
@@ -174,8 +177,7 @@ gs_flatpak_create_source (GsFlatpak *self, FlatpakRemote *xremote)
 
        /* create a temp GsApp */
        app = gs_flatpak_app_new_from_remote (xremote);
-       gs_app_set_scope (app, self->scope);
-       gs_app_set_management_plugin (app, gs_plugin_get_name (self->plugin));
+       gs_flatpak_claim_app (self, app);
 
        /* we already have one, returned the ref'd cached copy */
        app_cached = gs_plugin_cache_lookup (self->plugin, gs_app_get_unique_id (app));
@@ -610,8 +612,6 @@ gs_flatpak_refresh_appstream_remote (GsFlatpak *self,
                g_propagate_error (error, g_steal_pointer (&local_error));
                return FALSE;
        }
-
-#if FLATPAK_CHECK_VERSION(0,9,4)
        phelper = gs_flatpak_progress_helper_new (self->plugin, app_dl);
        if (!flatpak_installation_update_appstream_full_sync (self->installation,
                                                              remote_name,
@@ -624,18 +624,6 @@ gs_flatpak_refresh_appstream_remote (GsFlatpak *self,
                gs_flatpak_error_convert (error);
                return FALSE;
        }
-#else
-       gs_app_set_progress (app_dl, 0);
-       if (!flatpak_installation_update_appstream_sync (self->installation,
-                                                        remote_name,
-                                                        NULL,
-                                                        NULL,
-                                                        cancellable,
-                                                        error)) {
-               gs_flatpak_error_convert (error);
-               return FALSE;
-       }
-#endif
 
        /* success */
        gs_app_set_progress (app_dl, 100);
@@ -841,7 +829,7 @@ gs_flatpak_create_installed (GsFlatpak *self,
        /* create new object */
        app = gs_flatpak_create_app (self, FLATPAK_REF (xref));
        gs_flatpak_set_metadata_installed (self, app, xref);
-       return g_object_ref (app);
+       return g_steal_pointer (&app);
 }
 
 gboolean
@@ -940,46 +928,41 @@ gs_flatpak_add_sources (GsFlatpak *self, GsAppList *list,
        return TRUE;
 }
 
-gboolean
+GsApp *
 gs_flatpak_find_source_by_url (GsFlatpak *self,
                               const gchar *url,
-                              GsAppList *list,
                               GCancellable *cancellable,
                               GError **error)
 {
        g_autoptr(GPtrArray) xremotes = NULL;
 
-       g_return_val_if_fail (url != NULL, FALSE);
+       g_return_val_if_fail (url != NULL, NULL);
 
        xremotes = flatpak_installation_list_remotes (self->installation, cancellable, error);
        if (xremotes == NULL)
-               return FALSE;
+               return NULL;
        for (guint i = 0; i < xremotes->len; i++) {
                FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
                g_autofree gchar *url_tmp = flatpak_remote_get_url (xremote);
-               if (g_strcmp0 (url, url_tmp) == 0) {
-                       g_autoptr(GsApp) app = gs_flatpak_create_source (self, xremote);
-                       gs_app_list_add (list, app);
-               }
+               if (g_strcmp0 (url, url_tmp) == 0)
+                       return gs_flatpak_create_source (self, xremote);
        }
-       return TRUE;
+       g_set_error (error,
+                    GS_PLUGIN_ERROR,
+                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                    "cannot find %s", url);
+       return NULL;
 }
 
-gboolean
-gs_flatpak_find_app (GsFlatpak *self,
-                    FlatpakRefKind kind,
-                    const gchar *name,
-                    const gchar *arch,
-                    const gchar *branch,
-                    GsAppList *list,
-                    GCancellable *cancellable,
-                    GError **error)
+/* transfer full */
+GsApp *
+gs_flatpak_ref_to_app (GsFlatpak *self, const gchar *ref,
+                      GCancellable *cancellable, GError **error)
 {
        g_autoptr(GPtrArray) xremotes = NULL;
        g_autoptr(GPtrArray) xrefs = NULL;
 
-       g_return_val_if_fail (name != NULL, FALSE);
-       g_return_val_if_fail (branch != NULL, FALSE);
+       g_return_val_if_fail (ref != NULL, NULL);
 
        /* get all the installed apps (no network I/O) */
        xrefs = flatpak_installation_list_installed_refs (self->installation,
@@ -987,28 +970,21 @@ gs_flatpak_find_app (GsFlatpak *self,
                                                          error);
        if (xrefs == NULL) {
                gs_flatpak_error_convert (error);
-               return FALSE;
+               return NULL;
        }
-
-       /* look at each installed xref */
        for (guint i = 0; i < xrefs->len; i++) {
                FlatpakInstalledRef *xref = g_ptr_array_index (xrefs, i);
-               if (flatpak_ref_get_kind (FLATPAK_REF (xref)) == kind &&
-                   g_strcmp0 (flatpak_ref_get_name (FLATPAK_REF (xref)), name) == 0 &&
-                   g_strcmp0 (flatpak_ref_get_arch (FLATPAK_REF (xref)), arch) == 0 &&
-                   g_strcmp0 (flatpak_ref_get_branch (FLATPAK_REF (xref)), branch) == 0) {
-                       g_autoptr(GsApp) app = gs_flatpak_create_installed (self, xref, error);
-                       if (app == NULL)
-                               return FALSE;
-                       gs_app_list_add (list, app);
-               }
+               g_autofree gchar *ref_tmp = flatpak_ref_format_ref (FLATPAK_REF (xref));
+               if (g_strcmp0 (ref, ref_tmp) == 0)
+                       return gs_flatpak_create_installed (self, xref, error);
        }
 
        /* look at each remote xref */
-       xremotes = flatpak_installation_list_remotes (self->installation, cancellable, error);
+       xremotes = flatpak_installation_list_remotes (self->installation,
+                                                     cancellable, error);
        if (xremotes == NULL) {
                gs_flatpak_error_convert (error);
-               return FALSE;
+               return NULL;
        }
        for (guint i = 0; i < xremotes->len; i++) {
                FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
@@ -1030,36 +1006,25 @@ gs_flatpak_find_app (GsFlatpak *self,
                }
                for (guint j = 0; j < refs_remote->len; j++) {
                        FlatpakRef *xref = g_ptr_array_index (refs_remote, j);
-                       if (flatpak_ref_get_kind (FLATPAK_REF (xref)) == kind &&
-                           g_strcmp0 (flatpak_ref_get_name (xref), name) == 0 &&
-                           g_strcmp0 (flatpak_ref_get_arch (xref), arch) == 0 &&
-                           g_strcmp0 (flatpak_ref_get_branch (xref), branch) == 0) {
-                               g_autoptr(GsApp) app = gs_flatpak_create_app (self, xref);
-
-                               /* don't 'overwrite' installed apps */
-                               if (gs_app_list_lookup (list, gs_app_get_unique_id (app)) != NULL) {
-                                       g_debug ("ignoring installed %s",
-                                                gs_app_get_unique_id (app));
-                                       continue;
-                               }
-
-                               /* if we added a LOCAL runtime, and then we found
-                                * an already installed remote that provides the
-                                * exact same thing */
-                               if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE_LOCAL)
-                                       gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
-                               gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
-
+                       g_autofree gchar *ref_tmp = flatpak_ref_format_ref (xref);
+                       if (g_strcmp0 (ref, ref_tmp) == 0) {
+                               GsApp *app;
+                               app = gs_flatpak_create_app (self, xref);
                                gs_app_set_origin (app, flatpak_remote_get_name (xremote));
-                               gs_app_list_add (list, app);
+                               return app;
                        }
                }
        }
 
-       return TRUE;
+       /* nothing found */
+       g_set_error (error,
+                    GS_PLUGIN_ERROR,
+                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                    "cannot find %s", ref);
+       return NULL;
 }
 
-static gboolean
+gboolean
 gs_flatpak_app_install_source (GsFlatpak *self, GsApp *app,
                               GCancellable *cancellable,
                               GError **error)
@@ -1090,7 +1055,7 @@ gs_flatpak_app_install_source (GsFlatpak *self, GsApp *app,
 
        /* decode GPG key if set */
        gpg_key = gs_flatpak_app_get_repo_gpgkey (app);
-       if (gpg_key != NULL && g_strcmp0 (gpg_key, "FOOBAR==") != 0) {
+       if (gpg_key != NULL) {
                gsize data_len = 0;
                g_autofree guchar *data = NULL;
                g_autoptr(GBytes) bytes = NULL;
@@ -1511,23 +1476,42 @@ gs_refine_item_metadata (GsFlatpak *self, GsApp *app,
 }
 
 static gboolean
-gs_flatpak_refine_origin_from_installation (GsFlatpak *self,
-                                           FlatpakInstallation *installation,
-                                           GsApp *app,
-                                           GCancellable *cancellable,
-                                           GError **error)
+gs_plugin_refine_item_origin (GsFlatpak *self,
+                             GsApp *app,
+                             GCancellable *cancellable,
+                             GError **error)
 {
-       guint i;
+       g_autofree gchar *ref_display = NULL;
+       g_autoptr(AsProfileTask) ptask = NULL;
+       g_autoptr(GError) local_error = NULL;
        g_autoptr(GPtrArray) xremotes = NULL;
 
-       xremotes = flatpak_installation_list_remotes (installation,
-                                                     cancellable,
-                                                     error);
+       /* already set */
+       if (gs_app_get_origin (app) != NULL)
+               return TRUE;
+
+       /* not applicable */
+       if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE_LOCAL)
+               return TRUE;
+
+       /* ensure metadata exists */
+       ptask = as_profile_start (gs_plugin_get_profile (self->plugin),
+                                 "%s::refine-origin",
+                                 gs_flatpak_get_id (self));
+       g_assert (ptask != NULL);
+       if (!gs_refine_item_metadata (self, app, cancellable, error))
+               return FALSE;
+
+       /* find list of remotes */
+       ref_display = gs_flatpak_app_get_ref_display (app);
+       g_debug ("looking for a remote for %s", ref_display);
+       xremotes = flatpak_installation_list_remotes (self->installation,
+                                                     cancellable, error);
        if (xremotes == NULL) {
                gs_flatpak_error_convert (error);
                return FALSE;
        }
-       for (i = 0; i < xremotes->len; i++) {
+       for (guint i = 0; i < xremotes->len; i++) {
                const gchar *remote_name;
                FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
                g_autoptr(FlatpakRemoteRef) xref = NULL;
@@ -1540,7 +1524,7 @@ gs_flatpak_refine_origin_from_installation (GsFlatpak *self,
                /* sync */
                remote_name = flatpak_remote_get_name (xremote);
                g_debug ("looking at remote %s", remote_name);
-               xref = flatpak_installation_fetch_remote_ref_sync (installation,
+               xref = flatpak_installation_fetch_remote_ref_sync (self->installation,
                                                                   remote_name,
                                                                   gs_flatpak_app_get_ref_kind (app),
                                                                   gs_flatpak_app_get_ref_name (app),
@@ -1555,105 +1539,17 @@ gs_flatpak_refine_origin_from_installation (GsFlatpak *self,
                        gs_plugin_refine_item_scope (self, app);
                        return TRUE;
                }
-               g_debug ("failed to find remote %s: %s",
-                        remote_name, error_local->message);
+               g_debug ("%s failed to find remote %s: %s",
+                        ref_display, remote_name, error_local->message);
        }
 
        /* not found */
-       return TRUE;
-}
-
-static FlatpakInstallation *
-gs_flatpak_get_installation_counterpart (GsFlatpak *self,
-                                        GCancellable *cancellable,
-                                        GError **error)
-{
-       FlatpakInstallation *installation;
-       if (flatpak_installation_get_is_user (self->installation))
-               installation = flatpak_installation_new_system (cancellable, error);
-       else
-               installation = flatpak_installation_new_user (cancellable, error);
-       if (installation == NULL) {
-               gs_flatpak_error_convert (error);
-               return NULL;
-       }
-       return installation;
-}
-
-static gboolean
-gs_plugin_refine_item_origin (GsFlatpak *self,
-                             GsApp *app,
-                             GCancellable *cancellable,
-                             GError **error)
-{
-       g_autofree gchar *ref_display = NULL;
-       g_autoptr(AsProfileTask) ptask = NULL;
-       g_autoptr(GError) local_error = NULL;
-
-       /* already set */
-       if (gs_app_get_origin (app) != NULL)
-               return TRUE;
-
-       /* not applicable */
-       if (gs_app_get_state(app) == AS_APP_STATE_AVAILABLE_LOCAL)
-               return TRUE;
-
-       /* ensure metadata exists */
-       ptask = as_profile_start (gs_plugin_get_profile (self->plugin),
-                                 "%s::refine-origin",
-                                 gs_flatpak_get_id (self));
-       g_assert (ptask != NULL);
-       if (!gs_refine_item_metadata (self, app, cancellable, error))
-               return FALSE;
-
-       /* find list of remotes */
-       ref_display = gs_flatpak_app_get_ref_display (app);
-       g_debug ("looking for a remote for %s", ref_display);
-
-       /* first check the plugin's own flatpak installation */
-       if (!gs_flatpak_refine_origin_from_installation (self,
-                                                        self->installation,
-                                                        app,
-                                                        cancellable,
-                                                        error)) {
-               g_prefix_error (error, "failed to refine origin from self: ");
-               return FALSE;
-       }
-
-       /* check the system installation if we're on a user one */
-       if (gs_app_get_scope (app) == AS_APP_SCOPE_USER &&
-           gs_flatpak_app_get_ref_kind (app) == FLATPAK_REF_KIND_RUNTIME) {
-               g_autoptr(GError) error_local = NULL;
-               g_autoptr(FlatpakInstallation) installation =
-                       gs_flatpak_get_installation_counterpart (self,
-                                                                cancellable,
-                                                                &error_local);
-               if (installation == NULL) {
-                       if (g_error_matches (error_local,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_NO_SECURITY)) {
-                               g_debug ("ignoring: %s", error_local->message);
-                               return TRUE;
-                       }
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "failed to get counterpart: %s",
-                                    error_local->message);
-                       return FALSE;
-               }
-               if (!gs_flatpak_refine_origin_from_installation (self,
-                                                                installation,
-                                                                app,
-                                                                cancellable,
-                                                                error)) {
-                       g_prefix_error (error,
-                                       "failed to refine origin from counterpart: ");
-                       return FALSE;
-               }
-       }
-
-       return TRUE;
+       g_set_error (error,
+                    GS_PLUGIN_ERROR,
+                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                    "%s not found in any remote",
+                    ref_display);
+       return FALSE;
 }
 
 static FlatpakRef *
@@ -1674,16 +1570,16 @@ gs_flatpak_create_fake_ref (GsApp *app, GError **error)
        return xref;
 }
 
-static gboolean
-gs_plugin_refine_item_state (GsFlatpak *self,
-                            GsApp *app,
-                            GCancellable *cancellable,
-                            GError **error)
+gboolean
+gs_flatpak_refine_app_state (GsFlatpak *self,
+                             GsApp *app,
+                             GCancellable *cancellable,
+                             GError **error)
 {
        g_autoptr(GPtrArray) xrefs = NULL;
        g_autoptr(AsProfileTask) ptask = NULL;
        g_autoptr(FlatpakInstalledRef) ref = NULL;
-       g_autoptr(GError) ref_error = NULL;
+       g_autoptr(GError) error_local = NULL;
 
        /* already found */
        if (gs_app_get_state (app) != AS_APP_STATE_UNKNOWN)
@@ -1698,17 +1594,21 @@ gs_plugin_refine_item_state (GsFlatpak *self,
                                  "%s::refine-action",
                                  gs_flatpak_get_id (self));
        g_assert (ptask != NULL);
-
-       ref = get_installed_ref_for_app (self->installation, app, cancellable,
-                                        &ref_error);
+       ref = flatpak_installation_get_installed_ref (self->installation,
+                                                     gs_flatpak_app_get_ref_kind (app),
+                                                     gs_flatpak_app_get_ref_name (app),
+                                                     gs_flatpak_app_get_ref_arch (app),
+                                                     gs_flatpak_app_get_ref_branch (app),
+                                                     cancellable,
+                                                     &error_local);
        if (ref != NULL) {
                g_debug ("marking %s as installed with flatpak",
                         gs_app_get_id (app));
                gs_flatpak_set_metadata_installed (self, app, ref);
                if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
                        gs_app_set_state (app, AS_APP_STATE_INSTALLED);
-       } else if (!g_error_matches (ref_error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED)) {
-               g_propagate_error (error, g_steal_pointer (&ref_error));
+       } else if (!g_error_matches (error_local, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED)) {
+               g_propagate_error (error, g_steal_pointer (&error_local));
                gs_flatpak_error_convert (error);
                return FALSE;
        }
@@ -1717,50 +1617,6 @@ gs_plugin_refine_item_state (GsFlatpak *self,
        if (!gs_plugin_refine_item_origin (self, app, cancellable, error))
                return FALSE;
 
-       /* special case: if this is per-user instance and the runtime is
-        * available system-wide then mark it installed, and vice-versa */
-       if (gs_flatpak_app_get_ref_kind (app) == FLATPAK_REF_KIND_RUNTIME &&
-           gs_app_get_state (app) == AS_APP_STATE_UNKNOWN) {
-               g_autoptr(GError) error_local = NULL;
-               g_autoptr(FlatpakInstallation) installation =
-                       gs_flatpak_get_installation_counterpart (self,
-                                                                cancellable,
-                                                                &error_local);
-               if (installation == NULL) {
-                       if (g_error_matches (error_local,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_NO_SECURITY)) {
-                               g_debug ("ignoring: %s", error_local->message);
-                       } else {
-                               g_set_error (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                            "failed to get counterpart: %s",
-                                            error_local->message);
-                               return FALSE;
-                       }
-               } else {
-                       g_autoptr(FlatpakInstalledRef) runtime_ref = NULL;
-                       g_autoptr(GError) runtime_ref_error = NULL;
-                       runtime_ref = get_installed_ref_for_app (self->installation,
-                                                                app, cancellable,
-                                                                &runtime_ref_error);
-
-                       if (runtime_ref != NULL) {
-                               g_debug ("marking runtime %s as installed in the "
-                                        "counterpart installation", gs_app_get_id (app));
-                               gs_flatpak_set_metadata_installed (self, app, runtime_ref);
-                               if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
-                                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
-                       } else if (!g_error_matches (runtime_ref_error, FLATPAK_ERROR,
-                                                    FLATPAK_ERROR_NOT_INSTALLED)) {
-                               g_propagate_error (error, g_steal_pointer (&runtime_ref_error));
-                               gs_flatpak_error_convert (error);
-                               return FALSE;
-                       }
-               }
-       }
-
        /* anything not installed just check the remote is still present */
        if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN &&
            gs_app_get_origin (app) != NULL) {
@@ -1794,7 +1650,7 @@ gs_plugin_refine_item_state (GsFlatpak *self,
 }
 
 static GsApp *
-gs_flatpak_create_runtime (GsPlugin *plugin, GsApp *parent, const gchar *runtime)
+gs_flatpak_create_runtime (GsFlatpak *self, GsApp *parent, const gchar *runtime)
 {
        g_autofree gchar *source = NULL;
        g_auto(GStrv) split = NULL;
@@ -1808,15 +1664,14 @@ gs_flatpak_create_runtime (GsPlugin *plugin, GsApp *parent, const gchar *runtime
 
        /* create the complete GsApp from the single string */
        app = gs_app_new (split[0]);
+       gs_flatpak_claim_app (self, app);
        source = g_strdup_printf ("runtime/%s", runtime);
        gs_app_add_source (app, source);
-       gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_FLATPAK);
        gs_app_set_kind (app, AS_APP_KIND_RUNTIME);
        gs_app_set_branch (app, split[2]);
-       gs_app_set_scope (app, gs_app_get_scope (parent));
 
        /* search in the cache */
-       app_cache = gs_plugin_cache_lookup (plugin, gs_app_get_unique_id (app));
+       app_cache = gs_plugin_cache_lookup (self->plugin, gs_app_get_unique_id (app));
        if (app_cache != NULL) {
                /* since the cached runtime can have been created somewhere else
                 * (we're using a global cache), we need to make sure that a
@@ -1826,44 +1681,17 @@ gs_flatpak_create_runtime (GsPlugin *plugin, GsApp *parent, const gchar *runtime
                return g_steal_pointer (&app_cache);
        }
 
-
        /* set superclassed app properties */
        gs_flatpak_app_set_ref_kind (app, FLATPAK_REF_KIND_RUNTIME);
        gs_flatpak_app_set_ref_name (app, split[0]);
        gs_flatpak_app_set_ref_arch (app, split[1]);
        gs_flatpak_app_set_ref_branch (app, split[2]);
 
-       /* we own this */
-       gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
-
        /* save in the cache */
-       gs_plugin_cache_add (plugin, NULL, app);
+       gs_plugin_cache_add (self->plugin, NULL, app);
        return g_steal_pointer (&app);
 }
 
-static GsApp *
-gs_flatpak_create_runtime_from_metadata (GsFlatpak *self,
-                                        const GsApp *app,
-                                        const gchar *data,
-                                        const gsize length,
-                                        GError **error)
-{
-       g_autofree gchar *runtime = NULL;
-       g_autoptr(GKeyFile) kf = NULL;
-
-       kf = g_key_file_new ();
-       if (!g_key_file_load_from_data (kf, data, length, G_KEY_FILE_NONE, error)) {
-               gs_utils_error_convert_gio (error);
-               return NULL;
-       }
-       runtime = g_key_file_get_string (kf, "Application", "runtime", error);
-       if (runtime == NULL) {
-               gs_utils_error_convert_gio (error);
-               return NULL;
-       }
-       return gs_flatpak_create_runtime (self->plugin, app, runtime);
-}
-
 static gboolean
 gs_flatpak_set_app_metadata (GsFlatpak *self,
                             GsApp *app,
@@ -1925,12 +1753,10 @@ gs_flatpak_set_app_metadata (GsFlatpak *self,
                gs_app_add_kudo (app, GS_APP_KUDO_SANDBOXED_SECURE);
 
        /* create runtime */
-       if (gs_app_get_runtime (app) == NULL) {
-               app_runtime = gs_flatpak_create_runtime (self->plugin, app, runtime);
-               if (app_runtime != NULL) {
-                       gs_plugin_refine_item_scope (self, app_runtime);
-                       gs_app_set_runtime (app, app_runtime);
-               }
+       app_runtime = gs_flatpak_create_runtime (self, app, runtime);
+       if (app_runtime != NULL) {
+               gs_plugin_refine_item_scope (self, app_runtime);
+               gs_app_set_runtime (app, app_runtime);
        }
 
        return TRUE;
@@ -2000,8 +1826,10 @@ gs_plugin_refine_item_metadata (GsFlatpak *self,
                return TRUE;
 
        /* already done */
-       if (gs_app_has_kudo (app, GS_APP_KUDO_SANDBOXED))
+       if (gs_app_has_kudo (app, GS_APP_KUDO_SANDBOXED)) {
+               g_debug ("skipping reading metadata");
                return TRUE;
+       }
 
        /* this is quicker than doing network IO */
        installation_path = flatpak_installation_get_path (self->installation);
@@ -2090,7 +1918,7 @@ gs_plugin_refine_item_size (GsFlatpak *self,
 
                /* is the app_runtime already installed? */
                app_runtime = gs_app_get_runtime (app);
-               if (!gs_plugin_refine_item_state (self,
+               if (!gs_flatpak_refine_app_state (self,
                                                  app_runtime,
                                                  cancellable,
                                                  error))
@@ -2248,9 +2076,6 @@ gs_flatpak_refine_app (GsFlatpak *self,
        /* flatpak apps can always be removed */
        gs_app_remove_quirk (app, AS_APP_QUIRK_COMPULSORY);
 
-       /* scope is fast, do unconditionally */
-       gs_plugin_refine_item_scope (self, app);
-
        /* AppStream sets the source to appname/arch/branch */
        if (!gs_refine_item_metadata (self, app, cancellable, error)) {
                g_prefix_error (error, "failed to get metadata: ");
@@ -2258,11 +2083,14 @@ gs_flatpak_refine_app (GsFlatpak *self,
        }
 
        /* check the installed state */
-       if (!gs_plugin_refine_item_state (self, app, cancellable, error)) {
+       if (!gs_flatpak_refine_app_state (self, app, cancellable, error)) {
                g_prefix_error (error, "failed to get state: ");
                return FALSE;
        }
 
+       /* scope is fast, do unconditionally */
+       gs_plugin_refine_item_scope (self, app);
+
        /* if the state was changed, perhaps set the version from the release */
        if (old_state != gs_app_get_state (app)) {
                if (!gs_flatpak_refine_appstream (self, app, error))
@@ -2350,7 +2178,7 @@ gs_flatpak_refine_wildcard (GsFlatpak *self, GsApp *app,
                new = gs_appstream_create_app (self->plugin, item, NULL);
                if (new == NULL)
                        return FALSE;
-               gs_app_set_scope (new, self->scope);
+               gs_flatpak_claim_app (self, new);
                if (!gs_flatpak_refine_app (self, new, flags, cancellable, error))
                        return FALSE;
                gs_app_list_add (list, new);
@@ -2369,7 +2197,7 @@ gs_flatpak_launch (GsFlatpak *self,
        /* check the runtime is installed */
        runtime = gs_app_get_runtime (app);
        if (runtime != NULL) {
-               if (!gs_plugin_refine_item_state (self, runtime, cancellable, error))
+               if (!gs_flatpak_refine_app_state (self, runtime, cancellable, error))
                        return FALSE;
                if (!gs_app_is_installed (runtime)) {
                        g_set_error_literal (error,
@@ -2396,7 +2224,7 @@ gs_flatpak_launch (GsFlatpak *self,
        return TRUE;
 }
 
-static gboolean
+gboolean
 gs_flatpak_app_remove_source (GsFlatpak *self,
                              GsApp *app,
                              GCancellable *cancellable,
@@ -2430,741 +2258,46 @@ gs_flatpak_app_remove_source (GsFlatpak *self,
        return TRUE;
 }
 
-static GsAppList *
-gs_flatpak_get_list_for_remove (GsFlatpak *self, GsApp *app,
-                               GCancellable *cancellable, GError **error)
-{
-       g_autofree gchar *ref = NULL;
-       g_autoptr(GPtrArray) related = NULL;
-       g_autoptr(GsAppList) list = gs_app_list_new ();
-
-       /* lookup any related refs for this ref */
-       ref = g_strdup_printf ("%s/%s/%s/%s",
-                              gs_flatpak_app_get_ref_kind_as_str (app),
-                              gs_flatpak_app_get_ref_name (app),
-                              gs_flatpak_app_get_ref_arch (app),
-                              gs_flatpak_app_get_ref_branch (app));
-       related = flatpak_installation_list_installed_related_refs_sync (self->installation,
-                                                                        gs_app_get_origin (app),
-                                                                        ref, cancellable, error);
-       if (related == NULL) {
-               g_prefix_error (error, "using origin %s: ", gs_app_get_origin (app));
+GsApp *
+gs_flatpak_file_to_app_bundle (GsFlatpak *self,
+                              GFile *file,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       gint size;
+       g_autofree gchar *content_type = NULL;
+       g_autoptr(GBytes) appstream_gz = NULL;
+       g_autoptr(GBytes) icon_data = NULL;
+       g_autoptr(GBytes) metadata = NULL;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(FlatpakBundleRef) xref_bundle = NULL;
+
+       /* load bundle */
+       xref_bundle = flatpak_bundle_ref_new (file, error);
+       if (xref_bundle == NULL) {
                gs_flatpak_error_convert (error);
+               g_prefix_error (error, "error loading bundle: ");
                return NULL;
        }
 
-       /* any extra bits */
-       for (guint i = 0; i < related->len; i++) {
-               FlatpakRelatedRef *xref_related = g_ptr_array_index (related, i);
-               g_autoptr(GsApp) app_tmp = NULL;
-
-               if (!flatpak_related_ref_should_delete (xref_related))
-                       continue;
-               app_tmp = gs_flatpak_create_app (self, FLATPAK_REF (xref_related));
-               gs_app_set_origin (app_tmp, gs_app_get_origin (app));
-               if (!gs_plugin_refine_item_state (self, app_tmp, cancellable, error))
-                       return NULL;
-               gs_app_list_add (list, app_tmp);
+       /* load metadata */
+       app = gs_flatpak_create_app (self, FLATPAK_REF (xref_bundle));
+       if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED) {
+               if (gs_flatpak_app_get_ref_name (app) == NULL)
+                       gs_flatpak_set_metadata (self, app, FLATPAK_REF (xref_bundle));
+               return g_steal_pointer (&app);
        }
-
-       /* add the original app last unless it's a proxy app */
-       if (!gs_app_has_quirk (app, AS_APP_QUIRK_IS_PROXY))
-               gs_app_list_add (list, app);
-
-       return g_steal_pointer (&list);
-}
-
-static gboolean
-gs_flatpak_related_should_download (GsFlatpak *self, GsApp *app, FlatpakRelatedRef *xref_related)
-{
-       const gchar *name = flatpak_ref_get_name (FLATPAK_REF (xref_related));
-
-       /* architecture is different */
-       if (g_strcmp0 (gs_flatpak_app_get_ref_arch (app),
-           flatpak_ref_get_arch (FLATPAK_REF (xref_related))) != 0) {
-               g_autofree gchar *ref_display = NULL;
-               ref_display = flatpak_ref_format_ref (FLATPAK_REF (xref_related));
-               g_debug ("not using %s as architecture wrong!", ref_display);
-               return FALSE;
-       }
-
-       /* GTK theme */
-       if (g_str_has_prefix (name, "org.gtk.Gtk3theme.")) {
-               GtkSettings *gtk_settings = gtk_settings_get_default ();
-               g_autofree gchar *name_tmp = NULL;
-               g_object_get (gtk_settings, "gtk-theme-name", &name_tmp, NULL);
-               if (g_strcmp0 (name + 18, name_tmp) == 0) {
-                       g_autofree gchar *ref_display = NULL;
-                       ref_display = flatpak_ref_format_ref (FLATPAK_REF (xref_related));
-                       g_debug ("adding %s as matches GTK theme", ref_display);
-                       return TRUE;
-               }
-       }
-
-       /* icon theme */
-       if (g_str_has_prefix (name, "org.freedesktop.Platform.Icontheme.")) {
-               GtkSettings *gtk_settings = gtk_settings_get_default ();
-               g_autofree gchar *name_tmp = NULL;
-               g_object_get (gtk_settings, "gtk-icon-theme-name", &name_tmp, NULL);
-               if (g_strcmp0 (name + 35, name_tmp) == 0) {
-                       g_debug ("adding %s as matches icon theme", name);
-                       return TRUE;
-               }
-       }
-
-       return flatpak_related_ref_should_download (xref_related);
-}
-
-static gboolean
-gs_flatpak_refine_runtime_for_install (GsFlatpak *self,
-                                      GsApp *app,
-                                      GCancellable *cancellable,
-                                      GError **error)
-{
-       GsApp *runtime;
-       gsize len;
-       const gchar *str;
-       g_autoptr(GBytes) data = NULL;
-
-       if (gs_app_get_kind (app) == AS_APP_KIND_RUNTIME)
-               return TRUE;
-
-       /* ensure that we get the right runtime that will need to be installed */
-       data = gs_flatpak_fetch_remote_metadata (self, app, cancellable, error);
-       if (data == NULL) {
-               gs_utils_error_add_unique_id (error, app);
-               return FALSE;
-       }
-
-       str = g_bytes_get_data (data, &len);
-       runtime = gs_flatpak_create_runtime_from_metadata (self, app,
-                                                          str, len,
-                                                          error);
-
-       /* apps need to have a runtime */
-       if (runtime == NULL)
-               return FALSE;
-
-       gs_app_set_update_runtime (app, runtime);
-
-       /* the runtime could come from a different remote to the app */
-       if (!gs_refine_item_metadata (self, runtime, cancellable, error)) {
-               gs_utils_error_add_unique_id (error, runtime);
-               return FALSE;
-       }
-       if (!gs_plugin_refine_item_origin (self, runtime, cancellable, error)) {
-               gs_utils_error_add_unique_id (error, runtime);
-               return FALSE;
-       }
-       if (!gs_plugin_refine_item_state (self, runtime, cancellable, error)) {
-               gs_utils_error_add_unique_id (error, runtime);
-               return FALSE;
-       }
-       if (gs_app_get_state (runtime) == AS_APP_STATE_UNKNOWN) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "Failed to find runtime %s",
-                            gs_app_get_source_default (runtime));
-               gs_utils_error_add_unique_id (error, runtime);
-               return FALSE;
-       }
-
-       return TRUE;
-}
-
-static GsAppList *
-gs_flatpak_get_list_for_install (GsFlatpak *self, GsApp *app,
-                                GCancellable *cancellable, GError **error)
-{
-       GsApp *runtime;
-       g_autofree gchar *ref = NULL;
-       g_autoptr(GPtrArray) related = NULL;
-       g_autoptr(GPtrArray) xrefs_installed = NULL;
-       g_autoptr(GHashTable) hash_installed = NULL;
-       g_autoptr(GsAppList) list = gs_app_list_new ();
-
-       /* get the list of installed apps */
-       xrefs_installed = flatpak_installation_list_installed_refs (self->installation,
-                                                                   cancellable,
-                                                                   error);
-       if (xrefs_installed == NULL) {
-               gs_flatpak_error_convert (error);
-               return NULL;
-       }
-       hash_installed = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-       for (guint i = 0; i < xrefs_installed->len; i++) {
-               FlatpakInstalledRef *xref = g_ptr_array_index (xrefs_installed, i);
-               g_hash_table_add (hash_installed,
-                                 flatpak_ref_format_ref (FLATPAK_REF (xref)));
-       }
-
-       /* add runtime */
-       if (!gs_flatpak_refine_runtime_for_install (self, app, cancellable, error))
-               return NULL;
-       runtime = gs_app_get_update_runtime (app);
-       if (runtime != NULL) {
-               g_autofree gchar *ref_display = NULL;
-               ref_display = gs_flatpak_app_get_ref_display (runtime);
-               if (g_hash_table_contains (hash_installed, ref_display)) {
-                       g_debug ("%s is already installed, so skipping",
-                                gs_app_get_id (runtime));
-               } else {
-                       g_debug ("%s/%s is not already installed, so installing",
-                                gs_flatpak_app_get_ref_name (runtime),
-                                gs_flatpak_app_get_ref_branch (runtime));
-                       gs_app_list_add (list, runtime);
-               }
-       }
-
-       /* lookup any related refs for this ref */
-       ref = g_strdup_printf ("%s/%s/%s/%s",
-                              gs_flatpak_app_get_ref_kind_as_str (app),
-                              gs_flatpak_app_get_ref_name (app),
-                              gs_flatpak_app_get_ref_arch (app),
-                              gs_flatpak_app_get_ref_branch (app));
-       related = flatpak_installation_list_remote_related_refs_sync (self->installation,
-                                                                     gs_app_get_origin (app),
-                                                                     ref, cancellable, error);
-       if (related == NULL) {
-               g_prefix_error (error, "using origin %s: ", gs_app_get_origin (app));
-               gs_flatpak_error_convert (error);
-               return NULL;
-       }
-
-       /* any extra bits */
-       for (guint i = 0; i < related->len; i++) {
-               FlatpakRelatedRef *xref_related = g_ptr_array_index (related, i);
-               g_autofree gchar *ref_display = NULL;
-               g_autoptr(GsApp) app_tmp = NULL;
-
-               /* not included */
-               if (!gs_flatpak_related_should_download (self, app, xref_related))
-                       continue;
-
-               /* already installed? */
-               app_tmp = gs_flatpak_create_app (self, FLATPAK_REF (xref_related));
-               ref_display = gs_flatpak_app_get_ref_display (app_tmp);
-               if (g_hash_table_contains (hash_installed, ref_display)) {
-                       g_debug ("not adding related %s as already installed", ref_display);
-               } else {
-                       gs_app_set_origin (app_tmp, gs_app_get_origin (app));
-                       g_debug ("adding related %s for install", ref_display);
-
-                       if (!gs_plugin_refine_item_state (self, app_tmp, cancellable, error))
-                               return NULL;
-
-                       gs_app_list_add (list, app_tmp);
-               }
-       }
-
-       /* add the original app last unless it's a proxy app */
-       if (!gs_app_has_quirk (app, AS_APP_QUIRK_IS_PROXY))
-               gs_app_list_add (list, app);
-
-       return g_steal_pointer (&list);
-}
-
-gboolean
-gs_flatpak_app_remove (GsFlatpak *self,
-                      GsApp *app,
-                      GCancellable *cancellable,
-                      GError **error)
-{
-       g_autofree gchar *remote_name = NULL;
-       g_autoptr(FlatpakRemote) xremote = NULL;
-       g_autoptr(GsAppList) list = NULL;
-       g_autoptr(GsFlatpakProgressHelper) phelper = NULL;
-
-       /* refine to get basics */
-       if (!gs_flatpak_refine_app (self, app,
-                                   GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                   cancellable, error))
-               return FALSE;
-
-       /* is a source */
-       if (gs_app_get_kind (app) == AS_APP_KIND_SOURCE) {
-               return gs_flatpak_app_remove_source (self,
-                                                    app,
-                                                    cancellable,
-                                                    error);
-       }
-
-       /* get the list of apps to process */
-       list = gs_flatpak_get_list_for_remove (self, app, cancellable, error);
-       if (list == NULL) {
-               g_prefix_error (error, "failed to get related refs: ");
-               gs_app_set_state_recover (app);
-               return FALSE;
-       }
-
-       /* remove */
-       phelper = gs_flatpak_progress_helper_new (self->plugin, app);
-       phelper->job_max = gs_app_list_length (list);
-       for (phelper->job_now = 0; phelper->job_now < phelper->job_max; phelper->job_now++) {
-               GsApp *app_tmp = gs_app_list_index (list, phelper->job_now);
-               gs_app_set_state (app_tmp, AS_APP_STATE_REMOVING);
-       }
-       for (phelper->job_now = 0; phelper->job_now < phelper->job_max; phelper->job_now++) {
-               GsApp *app_tmp = gs_app_list_index (list, phelper->job_now);
-               g_debug ("removing %s", gs_flatpak_app_get_ref_name (app_tmp));
-               if (!flatpak_installation_uninstall (self->installation,
-                                                    gs_flatpak_app_get_ref_kind (app_tmp),
-                                                    gs_flatpak_app_get_ref_name (app_tmp),
-                                                    gs_flatpak_app_get_ref_arch (app_tmp),
-                                                    gs_flatpak_app_get_ref_branch (app_tmp),
-                                                    gs_flatpak_progress_cb, phelper,
-                                                    cancellable, error)) {
-                       gs_flatpak_error_convert (error);
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-
-               /* state is not known: we don't know if we can re-install this app */
-               gs_app_set_state (app_tmp, AS_APP_STATE_UNKNOWN);
-       }
-
-       /* did app also install a noenumerate=True remote */
-       remote_name = g_strdup_printf ("%s-origin", gs_flatpak_app_get_ref_name (app));
-       xremote = flatpak_installation_get_remote_by_name (self->installation,
-                                                          remote_name,
-                                                          cancellable,
-                                                          NULL);
-       if (xremote != NULL) {
-               g_debug ("removing enumerate=true %s remote", remote_name);
-               if (!flatpak_installation_remove_remote (self->installation,
-                                                        remote_name,
-                                                        cancellable,
-                                                        error)) {
-                       gs_flatpak_error_convert (error);
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-               if (!gs_flatpak_rescan_appstream_store (self, cancellable, error))
-                       return FALSE;
-       }
-
-       /* refresh the state */
-       if (!gs_plugin_refine_item_state (self, app, cancellable, error))
-               return FALSE;
-
-       /* success */
-       return TRUE;
-}
-
-static GsApp *
-gs_flatpak_create_runtime_repo (GsFlatpak *self,
-                               const gchar *uri,
-                               GCancellable *cancellable,
-                               GError **error)
-{
-       g_autofree gchar *cache_basename = NULL;
-       g_autofree gchar *cache_fn = NULL;
-       g_autoptr(GFile) file = NULL;
-       g_autoptr(GsApp) app = NULL;
-       g_autoptr(GsApp) app_dl = gs_app_new (gs_plugin_get_name (self->plugin));
-
-       /* TRANSLATORS: status text when downloading the RuntimeRepo */
-       gs_app_set_summary_missing (app_dl, _("Getting runtime source…"));
-       gs_plugin_status_update (self->plugin, app_dl, GS_PLUGIN_STATUS_DOWNLOADING);
-
-       /* download file */
-       cache_basename = g_path_get_basename (uri);
-       cache_fn = gs_utils_get_cache_filename ("flatpak",
-                                               cache_basename,
-                                               GS_UTILS_CACHE_FLAG_WRITEABLE,
-                                               error);
-       if (cache_fn == NULL)
-               return NULL;
-       if (!gs_plugin_download_file (self->plugin, app_dl, uri, cache_fn, cancellable, error))
-               return NULL;
-
-       /* get GsApp for local file */
-       file = g_file_new_for_path (cache_fn);
-       app = gs_flatpak_app_new_from_repo_file (file, cancellable, error);
-       if (app == NULL) {
-               g_prefix_error (error, "cannot create source from %s: ", cache_fn);
-               return NULL;
-       }
-       gs_flatpak_app_set_object_id (app, gs_flatpak_get_id (self));
-       gs_app_set_management_plugin (app, gs_plugin_get_name (self->plugin));
-       return g_steal_pointer (&app);
-}
-
-static gboolean
-app_has_local_source (GsApp *app)
-{
-       const gchar *url = gs_app_get_origin_hostname (app);
-       return url != NULL && g_str_has_prefix (url, "file://");
-}
-
-gboolean
-gs_flatpak_app_install (GsFlatpak *self,
-                       GsApp *app,
-                       GCancellable *cancellable,
-                       GError **error)
-{
-       /* queue for install if installation needs the network */
-       if (!app_has_local_source (app) &&
-           !gs_plugin_get_network_available (self->plugin)) {
-               gs_app_set_state (app, AS_APP_STATE_QUEUED_FOR_INSTALL);
-               return TRUE;
-       }
-
-       /* ensure we have metadata and state */
-       if (!gs_flatpak_refine_app (self, app,
-                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME,
-                                   cancellable, error))
-               return FALSE;
-
-       /* add a source */
-       if (gs_app_get_kind (app) == AS_APP_KIND_SOURCE) {
-               return gs_flatpak_app_install_source (self,
-                                                     app,
-                                                     cancellable,
-                                                     error);
-       }
-
-       /* update the UI */
-       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
-
-       /* flatpakref has to be done in two phases */
-       if (gs_flatpak_app_get_file_kind (app) == GS_FLATPAK_APP_FILE_KIND_REF) {
-               GsApp *runtime;
-               g_autoptr(FlatpakRemoteRef) xref2 = NULL;
-               gsize len = 0;
-               g_autofree gchar *contents = NULL;
-               g_autoptr(GBytes) data = NULL;
-               if (gs_app_get_local_file (app) == NULL) {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "no local file set for flatpakref %s",
-                                    gs_app_get_unique_id (app));
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-               g_debug ("installing flatpakref %s", gs_app_get_unique_id (app));
-               if (!g_file_load_contents (gs_app_get_local_file (app),
-                                          cancellable, &contents, &len,
-                                          NULL, error)) {
-                       gs_utils_error_convert_gio (error);
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-
-               /* we have a missing remote and a RuntimeRef */
-               runtime = gs_app_get_runtime (app);
-               if (runtime != NULL &&
-                   gs_app_get_state (runtime) == AS_APP_STATE_AVAILABLE_LOCAL) {
-                       GsApp *app_src = gs_flatpak_app_get_runtime_repo (app);
-                       if (app_src == NULL) {
-                               g_set_error (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                            "no runtime available for %s",
-                                            gs_app_get_unique_id (app));
-                               gs_utils_error_add_unique_id (error, runtime);
-                               gs_app_set_state_recover (app);
-                               return FALSE;
-                       }
-
-                       /* special case; we're moving from GsFlatpak-user-temp */
-                       gs_app_set_state (app_src, AS_APP_STATE_UNKNOWN);
-                       gs_app_set_state (app_src, AS_APP_STATE_AVAILABLE);
-
-                       /* install the flatpakrepo if not already installed */
-                       if (gs_app_get_state (app_src) != AS_APP_STATE_INSTALLED) {
-                               if (!gs_flatpak_app_install_source (self,
-                                                                   app_src,
-                                                                   cancellable,
-                                                                   error)) {
-                                       g_prefix_error (error, "cannot install source from %s: ",
-                                                       gs_flatpak_app_get_repo_url (app_src));
-                                       gs_app_set_state_recover (app);
-                                       return FALSE;
-                               }
-                       }
-
-                       /* get the new state */
-                       if (!gs_plugin_refine_item_state (self, runtime, cancellable, error)) {
-                               g_prefix_error (error, "cannot refine runtime using %s: ",
-                                               gs_flatpak_app_get_repo_url (app_src));
-                               gs_app_set_state_recover (app);
-                               return FALSE;
-                       }
-
-                       /* still not found */
-                       if (gs_app_get_state (runtime) == AS_APP_STATE_UNKNOWN) {
-                               g_set_error (error,
-                                            GS_PLUGIN_ERROR,
-                                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                            "no runtime available for %s",
-                                            gs_app_get_unique_id (app));
-                               gs_utils_error_add_unique_id (error, runtime);
-                               gs_app_set_state_recover (app);
-                               return FALSE;
-                       }
-               }
-
-               /* now install actual app */
-               data = g_bytes_new (contents, len);
-               xref2 = flatpak_installation_install_ref_file (self->installation,
-                                                             data,
-                                                             cancellable,
-                                                             error);
-               if (xref2 == NULL) {
-                       gs_flatpak_error_convert (error);
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-
-               /* the installation of the ref file above will not create a new remote for
-                * the app if its URL is already configured as another remote, thus we
-                * need to update the app origin to match that or it may end up with
-                * an nonexistent origin; and we first need to set the origin to NULL to
-                * circumvent the safety check... */
-               gs_app_set_origin (app, NULL);
-               gs_app_set_origin (app, flatpak_remote_ref_get_remote_name (xref2));
-
-               /* update search tokens for new remote */
-               if (!gs_flatpak_refresh_appstream (self, G_MAXUINT, 0, cancellable, error)) {
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-       }
-
-       if (gs_flatpak_app_get_file_kind (app) == GS_FLATPAK_APP_FILE_KIND_BUNDLE) {
-               g_autoptr(FlatpakInstalledRef) xref = NULL;
-               g_autoptr(GsFlatpakProgressHelper) phelper = NULL;
-               if (gs_app_get_local_file (app) == NULL) {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "no local file set for bundle %s",
-                                    gs_app_get_unique_id (app));
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-               g_debug ("installing bundle %s", gs_app_get_unique_id (app));
-               phelper = gs_flatpak_progress_helper_new (self->plugin, app);
-               xref = flatpak_installation_install_bundle (self->installation,
-                                                           gs_app_get_local_file (app),
-                                                           gs_flatpak_progress_cb,
-                                                           phelper,
-                                                           cancellable, error);
-               if (xref == NULL) {
-                       gs_flatpak_error_convert (error);
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-       } else {
-               g_autoptr(GsAppList) list = NULL;
-               g_autoptr(GsFlatpakProgressHelper) phelper = NULL;
-
-               /* no origin set */
-               if (gs_app_get_origin (app) == NULL) {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "no origin set for remote %s",
-                                    gs_app_get_unique_id (app));
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-
-               /* get the list of apps to process */
-               list = gs_flatpak_get_list_for_install (self, app, cancellable, error);
-               if (list == NULL) {
-                       g_prefix_error (error, "failed to get related refs: ");
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-
-               /* install all the required packages */
-               phelper = gs_flatpak_progress_helper_new (self->plugin, app);
-               phelper->job_max = gs_app_list_length (list);
-               for (phelper->job_now = 0; phelper->job_now < phelper->job_max; phelper->job_now++) {
-                       GsApp *app_tmp = gs_app_list_index (list, phelper->job_now);
-                       gs_app_set_state (app_tmp, AS_APP_STATE_INSTALLING);
-               }
-               for (phelper->job_now = 0; phelper->job_now < phelper->job_max; phelper->job_now++) {
-                       GsApp *app_tmp = gs_app_list_index (list, phelper->job_now);
-                       g_autoptr(FlatpakInstalledRef) xref = NULL;
-                       g_debug ("installing %s", gs_flatpak_app_get_ref_name (app_tmp));
-                       gs_app_set_state (app_tmp, AS_APP_STATE_INSTALLING);
-                       xref = flatpak_installation_install (self->installation,
-                                                            gs_app_get_origin (app_tmp),
-                                                            gs_flatpak_app_get_ref_kind (app_tmp),
-                                                            gs_flatpak_app_get_ref_name (app_tmp),
-                                                            gs_flatpak_app_get_ref_arch (app_tmp),
-                                                            gs_flatpak_app_get_ref_branch (app_tmp),
-                                                            gs_flatpak_progress_cb, phelper,
-                                                            cancellable, error);
-                       if (xref == NULL) {
-                               gs_flatpak_error_convert (error);
-                               gs_app_set_state_recover (app);
-                               gs_app_set_state_recover (app_tmp);
-                               return FALSE;
-                       }
-
-                       /* state is known */
-                       gs_app_set_state (app_tmp, AS_APP_STATE_INSTALLED);
-               }
-       }
-
-       /* set new version */
-       if (!gs_flatpak_refine_appstream (self, app, error))
-               return FALSE;
-
-       return TRUE;
-}
-
-gboolean
-gs_flatpak_update_app (GsFlatpak *self,
-                      GsApp *app,
-                      GCancellable *cancellable,
-                      GError **error)
-{
-       g_autoptr(GHashTable) hash_installed = NULL;
-       g_autoptr(GPtrArray) xrefs_installed = NULL;
-       g_autoptr(GsAppList) list = NULL;
-       g_autoptr(GsFlatpakProgressHelper) phelper = NULL;
-       GsApp *runtime = NULL;
-       GsApp *update_runtime = NULL;
-
-       /* install */
-       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
-
-       /* get the list of installed things from this remote */
-       xrefs_installed = flatpak_installation_list_installed_refs (self->installation,
-                                                                   cancellable,
-                                                                   error);
-       if (xrefs_installed == NULL) {
-               gs_flatpak_error_convert (error);
-               return FALSE;
-       }
-       hash_installed = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-       for (guint i = 0; i < xrefs_installed->len; i++) {
-               FlatpakInstalledRef *xref = g_ptr_array_index (xrefs_installed, i);
-               g_hash_table_add (hash_installed,
-                                 flatpak_ref_format_ref (FLATPAK_REF (xref)));
-       }
-
-       /* get the list of apps to process */
-       list = gs_flatpak_get_list_for_install (self, app, cancellable, error);
-       if (list == NULL) {
-               g_prefix_error (error, "failed to get related refs: ");
-               gs_app_set_state_recover (app);
-               return FALSE;
-       }
-
-       /* update all the required packages */
-       phelper = gs_flatpak_progress_helper_new (self->plugin, app);
-       phelper->job_max = gs_app_list_length (list);
-       for (phelper->job_now = 0; phelper->job_now < phelper->job_max; phelper->job_now++) {
-               GsApp *app_tmp = gs_app_list_index (list, phelper->job_now);
-               gs_app_set_state (app_tmp, AS_APP_STATE_INSTALLING);
-       }
-
-       for (phelper->job_now = 0; phelper->job_now < phelper->job_max; phelper->job_now++) {
-               GsApp *app_tmp = gs_app_list_index (list, phelper->job_now);
-               g_autofree gchar *ref_display = NULL;
-               g_autoptr(FlatpakInstalledRef) xref = NULL;
-
-               /* either install or update the ref */
-               ref_display = gs_flatpak_app_get_ref_display (app_tmp);
-               if (!g_hash_table_contains (hash_installed, ref_display)) {
-                       g_debug ("installing %s", ref_display);
-                       xref = flatpak_installation_install (self->installation,
-                                                            gs_app_get_origin (app_tmp),
-                                                            gs_flatpak_app_get_ref_kind (app_tmp),
-                                                            gs_flatpak_app_get_ref_name (app_tmp),
-                                                            gs_flatpak_app_get_ref_arch (app_tmp),
-                                                            gs_flatpak_app_get_ref_branch (app_tmp),
-                                                            gs_flatpak_progress_cb, phelper,
-                                                            cancellable, error);
-               } else {
-                       g_debug ("updating %s", ref_display);
-                       xref = flatpak_installation_update (self->installation,
-                                                           FLATPAK_UPDATE_FLAGS_NONE,
-                                                           gs_flatpak_app_get_ref_kind (app_tmp),
-                                                           gs_flatpak_app_get_ref_name (app_tmp),
-                                                           gs_flatpak_app_get_ref_arch (app_tmp),
-                                                           gs_flatpak_app_get_ref_branch (app_tmp),
-                                                           gs_flatpak_progress_cb, phelper,
-                                                           cancellable, error);
-               }
-               if (xref == NULL) {
-                       gs_flatpak_error_convert (error);
-                       gs_app_set_state_recover (app);
-                       return FALSE;
-               }
-               gs_app_set_state (app_tmp, AS_APP_STATE_INSTALLED);
-       }
-
-       /* update UI */
-       gs_plugin_updates_changed (self->plugin);
-
-       /* state is known */
-       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
-       gs_app_set_update_version (app, NULL);
-       gs_app_set_update_details (app, NULL);
-       gs_app_set_update_urgency (app, AS_URGENCY_KIND_UNKNOWN);
-
-       /* setup the new runtime if needed */
-       runtime = gs_app_get_runtime (app);
-       update_runtime = gs_app_get_update_runtime (app);
-       if (runtime != update_runtime && gs_app_is_installed (update_runtime))
-               gs_app_set_runtime (app, update_runtime);
-
-       /* set new version */
-       if (!gs_flatpak_refine_appstream (self, app, error))
-               return FALSE;
-
-       return TRUE;
-}
-
-GsApp *
-gs_flatpak_file_to_app_bundle (GsFlatpak *self,
-                              GFile *file,
-                              GCancellable *cancellable,
-                              GError **error)
-{
-       gint size;
-       g_autofree gchar *content_type = NULL;
-       g_autoptr(GBytes) appstream_gz = NULL;
-       g_autoptr(GBytes) icon_data = NULL;
-       g_autoptr(GBytes) metadata = NULL;
-       g_autoptr(GsApp) app = NULL;
-       g_autoptr(FlatpakBundleRef) xref_bundle = NULL;
-
-       /* load bundle */
-       xref_bundle = flatpak_bundle_ref_new (file, error);
-       if (xref_bundle == NULL) {
-               gs_flatpak_error_convert (error);
-               g_prefix_error (error, "error loading bundle: ");
-               return NULL;
-       }
-
-       /* load metadata */
-       app = gs_flatpak_create_app (self, FLATPAK_REF (xref_bundle));
-       if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED) {
-               if (gs_flatpak_app_get_ref_name (app) == NULL)
-                       gs_flatpak_set_metadata (self, app, FLATPAK_REF (xref_bundle));
-               return g_steal_pointer (&app);
-       }
-       gs_flatpak_app_set_file_kind (app, GS_FLATPAK_APP_FILE_KIND_BUNDLE);
-       gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
-       gs_app_set_state (app, AS_APP_STATE_AVAILABLE_LOCAL);
-       gs_app_set_size_installed (app, flatpak_bundle_ref_get_installed_size (xref_bundle));
-       gs_flatpak_set_metadata (self, app, FLATPAK_REF (xref_bundle));
-       metadata = flatpak_bundle_ref_get_metadata (xref_bundle);
-       if (!gs_flatpak_set_app_metadata (self, app,
-                                         g_bytes_get_data (metadata, NULL),
-                                         g_bytes_get_size (metadata),
-                                         error))
-               return NULL;
+       gs_flatpak_app_set_file_kind (app, GS_FLATPAK_APP_FILE_KIND_BUNDLE);
+       gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
+       gs_app_set_state (app, AS_APP_STATE_AVAILABLE_LOCAL);
+       gs_app_set_size_installed (app, flatpak_bundle_ref_get_installed_size (xref_bundle));
+       gs_flatpak_set_metadata (self, app, FLATPAK_REF (xref_bundle));
+       metadata = flatpak_bundle_ref_get_metadata (xref_bundle);
+       if (!gs_flatpak_set_app_metadata (self, app,
+                                         g_bytes_get_data (metadata, NULL),
+                                         g_bytes_get_size (metadata),
+                                         error))
+               return NULL;
 
        /* load AppStream */
        appstream_gz = flatpak_bundle_ref_get_appstream (xref_bundle);
@@ -3416,28 +2549,12 @@ gs_flatpak_file_to_app_ref (GsFlatpak *self,
        if (!gs_plugin_refine_item_metadata (self, app, cancellable, error))
                return NULL;
 
-       /* if the runtime is not already installed, download the RuntimeRepo */
+       /* the new runtime is available from the RuntimeRepo */
        runtime = gs_app_get_runtime (app);
-       if (runtime != NULL && gs_app_get_state (runtime) != AS_APP_STATE_INSTALLED) {
+       if (runtime != NULL && gs_app_get_state (runtime) == AS_APP_STATE_UNKNOWN) {
                g_autofree gchar *uri = NULL;
                uri = g_key_file_get_string (kf, "Flatpak Ref", "RuntimeRepo", NULL);
-               if (uri != NULL) {
-                       g_autoptr(GsApp) app_src = NULL;
-                       app_src = gs_flatpak_create_runtime_repo (self, uri, cancellable, error);
-                       if (app_src == NULL)
-                               return NULL;
-                       gs_flatpak_app_set_runtime_repo (app, app_src);
-
-                       /* lets install this, so we can get the size */
-                       if (!gs_flatpak_app_install_source (self, app_src, cancellable, error))
-                               return FALSE;
-
-                       /* this is now available to be installed if required */
-                       gs_app_set_state (runtime, AS_APP_STATE_AVAILABLE_LOCAL);
-
-                       /* we can install the runtime from this source */
-                       gs_app_set_origin (runtime, gs_app_get_id (app_src));
-               }
+               gs_flatpak_app_set_runtime_url (runtime, uri);
        }
 
        /* parse it */
@@ -3459,9 +2576,13 @@ gs_flatpak_search (GsFlatpak *self,
                   GCancellable *cancellable,
                   GError **error)
 {
-       return gs_appstream_store_search (self->plugin, self->store,
-                                         values, list,
-                                         cancellable, error);
+       g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+       if (!gs_appstream_store_search (self->plugin, self->store, values, list_tmp,
+                                       cancellable, error))
+               return FALSE;
+       gs_flatpak_claim_app_list (self, list_tmp);
+       gs_app_list_add_list (list, list_tmp);
+       return TRUE;
 }
 
 gboolean
@@ -3471,9 +2592,14 @@ gs_flatpak_add_category_apps (GsFlatpak *self,
                              GCancellable *cancellable,
                              GError **error)
 {
-       return gs_appstream_store_add_category_apps (self->plugin, self->store,
-                                                    category, list,
-                                                    cancellable, error);
+       g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+       if (!gs_appstream_store_add_category_apps (self->plugin, self->store,
+                                                  category, list_tmp,
+                                                  cancellable, error))
+               return FALSE;
+       gs_flatpak_claim_app_list (self, list_tmp);
+       gs_app_list_add_list (list, list_tmp);
+       return TRUE;
 }
 
 gboolean
@@ -3492,8 +2618,13 @@ gs_flatpak_add_popular (GsFlatpak *self,
                        GCancellable *cancellable,
                        GError **error)
 {
-       return gs_appstream_add_popular (self->plugin, self->store, list,
-                                        cancellable, error);
+       g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+       if (!gs_appstream_add_popular (self->plugin, self->store, list_tmp,
+                                      cancellable, error))
+               return FALSE;
+       gs_flatpak_claim_app_list (self, list_tmp);
+       gs_app_list_add_list (list, list_tmp);
+       return TRUE;
 }
 
 gboolean
@@ -3502,8 +2633,13 @@ gs_flatpak_add_featured (GsFlatpak *self,
                         GCancellable *cancellable,
                         GError **error)
 {
-       return gs_appstream_add_featured (self->plugin, self->store, list,
-                                         cancellable, error);
+       g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+       if (!gs_appstream_add_featured (self->plugin, self->store, list_tmp,
+                                       cancellable, error))
+               return FALSE;
+       gs_flatpak_claim_app_list (self, list_tmp);
+       gs_app_list_add_list (list, list_tmp);
+       return TRUE;
 }
 
 gboolean
@@ -3513,8 +2649,13 @@ gs_flatpak_add_recent (GsFlatpak *self,
                       GCancellable *cancellable,
                       GError **error)
 {
-       return gs_appstream_add_recent (self->plugin, self->store, list, age,
-                                       cancellable, error);
+       g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+       if (!gs_appstream_add_recent (self->plugin, self->store, list_tmp, age,
+                                     cancellable, error))
+               return FALSE;
+       gs_flatpak_claim_app_list (self, list_tmp);
+       gs_app_list_add_list (list, list_tmp);
+       return TRUE;
 }
 
 static void
@@ -3554,6 +2695,12 @@ gs_flatpak_get_scope (GsFlatpak *self)
        return self->scope;
 }
 
+FlatpakInstallation *
+gs_flatpak_get_installation (GsFlatpak *self)
+{
+       return self->installation;
+}
+
 static void
 gs_flatpak_finalize (GObject *object)
 {
diff --git a/plugins/flatpak/gs-flatpak.h b/plugins/flatpak/gs-flatpak.h
index 09fae489..e78bffd8 100644
--- a/plugins/flatpak/gs-flatpak.h
+++ b/plugins/flatpak/gs-flatpak.h
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
  * Copyright (C) 2016 Joaquim Rocha <jrocha endlessm com>
- * Copyright (C) 2016-2017 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2016-2018 Richard Hughes <richard hughsie com>
  *
  * Licensed under the GNU General Public License Version 2
  *
@@ -42,6 +42,10 @@ typedef enum {
 GsFlatpak      *gs_flatpak_new                 (GsPlugin               *plugin,
                                                 FlatpakInstallation    *installation,
                                                 GsFlatpakFlags          flags);
+FlatpakInstallation *gs_flatpak_get_installation (GsFlatpak            *self);
+
+GsApp  *gs_flatpak_ref_to_app (GsFlatpak *self, const gchar *ref, GCancellable *cancellable, GError **error);
+
 AsAppScope     gs_flatpak_get_scope            (GsFlatpak              *self);
 const gchar    *gs_flatpak_get_id              (GsFlatpak              *self);
 gboolean       gs_flatpak_setup                (GsFlatpak              *self,
@@ -73,6 +77,10 @@ gboolean     gs_flatpak_refine_app           (GsFlatpak              *self,
                                                 GsPluginRefineFlags    flags,
                                                 GCancellable           *cancellable,
                                                 GError                 **error);
+gboolean       gs_flatpak_refine_app_state     (GsFlatpak              *self,
+                                                GsApp                  *app,
+                                                GCancellable           *cancellable,
+                                                GError                 **error);
 gboolean       gs_flatpak_refine_wildcard      (GsFlatpak              *self,
                                                 GsApp                  *app,
                                                 GsAppList              *list,
@@ -83,15 +91,11 @@ gboolean    gs_flatpak_launch               (GsFlatpak              *self,
                                                 GsApp                  *app,
                                                 GCancellable           *cancellable,
                                                 GError                 **error);
-gboolean       gs_flatpak_app_remove           (GsFlatpak              *self,
-                                                GsApp                  *app,
-                                                GCancellable           *cancellable,
-                                                GError                 **error);
-gboolean       gs_flatpak_app_install          (GsFlatpak              *self,
+gboolean       gs_flatpak_app_remove_source    (GsFlatpak              *self,
                                                 GsApp                  *app,
                                                 GCancellable           *cancellable,
                                                 GError                 **error);
-gboolean       gs_flatpak_update_app           (GsFlatpak              *self,
+gboolean       gs_flatpak_app_install_source   (GsFlatpak              *self,
                                                 GsApp                  *app,
                                                 GCancellable           *cancellable,
                                                 GError                 **error);
@@ -103,17 +107,8 @@ GsApp              *gs_flatpak_file_to_app_bundle  (GsFlatpak              *self,
                                                 GFile                  *file,
                                                 GCancellable           *cancellable,
                                                 GError                 **error);
-gboolean        gs_flatpak_find_source_by_url  (GsFlatpak              *self,
+GsApp          *gs_flatpak_find_source_by_url  (GsFlatpak              *self,
                                                 const gchar            *name,
-                                                GsAppList              *list,
-                                                GCancellable           *cancellable,
-                                                GError                 **error);
-gboolean        gs_flatpak_find_app            (GsFlatpak              *self,
-                                                FlatpakRefKind          kind,
-                                                const gchar            *id,
-                                                const gchar            *arch,
-                                                const gchar            *branch,
-                                                GsAppList              *list,
                                                 GCancellable           *cancellable,
                                                 GError                 **error);
 gboolean       gs_flatpak_search               (GsFlatpak              *self,
diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c
index affcdd87..2c4cfe36 100644
--- a/plugins/flatpak/gs-plugin-flatpak.c
+++ b/plugins/flatpak/gs-plugin-flatpak.c
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
  * Copyright (C) 2016 Joaquim Rocha <jrocha endlessm com>
- * Copyright (C) 2016-2017 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2016-2018 Richard Hughes <richard hughsie com>
  * Copyright (C) 2017 Kalev Lember <klember redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -36,6 +36,7 @@
 #include "gs-appstream.h"
 #include "gs-flatpak-app.h"
 #include "gs-flatpak.h"
+#include "gs-flatpak-transaction.h"
 #include "gs-flatpak-utils.h"
 
 struct GsPluginData {
@@ -306,6 +307,38 @@ gs_plugin_flatpak_get_handler (GsPlugin *plugin, GsApp *app)
        return NULL;
 }
 
+static gboolean
+gs_plugin_flatpak_refine_app (GsPlugin *plugin,
+                             GsApp *app,
+                             GsPluginRefineFlags flags,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       GsFlatpak *flatpak = NULL;
+
+       /* we have to look for the app in all GsFlatpak stores */
+       if (gs_app_get_scope (app) == AS_APP_SCOPE_UNKNOWN) {
+               for (guint i = 0; i < priv->flatpaks->len; i++) {
+                       GsFlatpak *flatpak_tmp = g_ptr_array_index (priv->flatpaks, i);
+                       g_autoptr(GError) error_local = NULL;
+                       if (gs_flatpak_refine_app_state (flatpak_tmp, app,
+                                                        cancellable, &error_local)) {
+                               flatpak = flatpak_tmp;
+                               break;
+                       } else {
+                               g_debug ("%s", error_local->message);
+                       }
+               }
+       } else {
+               flatpak = gs_plugin_flatpak_get_handler (plugin, app);
+       }
+       if (flatpak == NULL)
+               return TRUE;
+       return gs_flatpak_refine_app (flatpak, app, flags, cancellable, error);
+}
+
+
 gboolean
 gs_plugin_refine_app (GsPlugin *plugin,
                      GsApp *app,
@@ -313,10 +346,29 @@ gs_plugin_refine_app (GsPlugin *plugin,
                      GCancellable *cancellable,
                      GError **error)
 {
-       GsFlatpak *flatpak = gs_plugin_flatpak_get_handler (plugin, app);
-       if (flatpak == NULL)
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app),
+                      gs_plugin_get_name (plugin)) != 0) {
                return TRUE;
-       return gs_flatpak_refine_app (flatpak, app, flags, cancellable, error);
+       }
+
+       /* get the runtime first */
+       if (!gs_plugin_flatpak_refine_app (plugin, app, flags, cancellable, error))
+               return FALSE;
+
+       /* the runtime might be installed in a different scope */
+       if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME) {
+               GsApp *runtime = gs_app_get_runtime (app);
+               if (runtime != NULL) {
+                       if (!gs_plugin_flatpak_refine_app (plugin, app,
+                                                          flags,
+                                                          cancellable,
+                                                          error)) {
+                               return FALSE;
+                       }
+               }
+       }
+       return TRUE;
 }
 
 gboolean
@@ -350,16 +402,132 @@ gs_plugin_launch (GsPlugin *plugin,
        return gs_flatpak_launch (flatpak, app, cancellable, error);
 }
 
+/* ref full */
+static GsApp *
+gs_plugin_flatpak_find_app_by_ref (GsPlugin *plugin, const gchar *ref,
+                                  GCancellable *cancellable, GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autoptr(GError) error_local = NULL;
+
+       g_debug ("finding ref %s", ref);
+       for (guint i = 0; i < priv->flatpaks->len; i++) {
+               GsFlatpak *flatpak_tmp = g_ptr_array_index (priv->flatpaks, i);
+               g_autoptr(GsApp) app = NULL;
+               app = gs_flatpak_ref_to_app (flatpak_tmp, ref, cancellable, &error_local);
+               if (app == NULL) {
+                       g_debug ("%s", error_local->message);
+                       continue;
+               }
+               g_debug ("found ref=%s->%s", ref, gs_app_get_unique_id (app));
+               return g_steal_pointer (&app);
+       }
+       return NULL;
+}
+
+/* ref full */
+static GsApp *
+_ref_to_app (FlatpakTransaction *transaction, const gchar *ref, GsPlugin *plugin)
+{
+       g_return_val_if_fail (GS_IS_FLATPAK_TRANSACTION (transaction), NULL);
+       g_return_val_if_fail (ref != NULL, NULL);
+       g_return_val_if_fail (GS_IS_PLUGIN (plugin), NULL);
+
+       /* search through each GsFlatpak */
+       return gs_plugin_flatpak_find_app_by_ref (plugin, ref, NULL, NULL);
+}
+
+static FlatpakTransaction *
+_build_transaction (GsPlugin *plugin, GsFlatpak *flatpak,
+                   GCancellable *cancellable, GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       FlatpakInstallation *installation;
+       g_autoptr(FlatpakTransaction) transaction = NULL;
+
+       /* create transaction */
+       installation = gs_flatpak_get_installation (flatpak);
+       transaction = gs_flatpak_transaction_new (installation, cancellable, error);
+       if (transaction == NULL) {
+               g_prefix_error (error, "failed to build transaction: ");
+               gs_flatpak_error_convert (error);
+               return NULL;
+       }
+
+       /* connect up signals */
+       g_signal_connect (transaction, "ref-to-app",
+                         G_CALLBACK (_ref_to_app), plugin);
+
+       /* add the counterpart installations */
+       for (guint i = 0; i < priv->flatpaks->len; i++) {
+               GsFlatpak *flatpak_tmp = g_ptr_array_index (priv->flatpaks, i);
+               if (flatpak_tmp == flatpak)
+                       continue;
+               installation = gs_flatpak_get_installation (flatpak_tmp);
+               flatpak_transaction_add_dependency_source (transaction, installation);
+       }
+       return g_steal_pointer (&transaction);
+}
+
 gboolean
 gs_plugin_app_remove (GsPlugin *plugin,
                      GsApp *app,
                      GCancellable *cancellable,
                      GError **error)
 {
-       GsFlatpak *flatpak = gs_plugin_flatpak_get_handler (plugin, app);
+       GsFlatpak *flatpak;
+       g_autoptr(FlatpakTransaction) transaction = NULL;
+       g_autofree gchar *ref = NULL;
+
+       /* not supported */
+       flatpak = gs_plugin_flatpak_get_handler (plugin, app);
        if (flatpak == NULL)
                return TRUE;
-       return gs_flatpak_app_remove (flatpak, app, cancellable, error);
+
+       /* is a source */
+       if (gs_app_get_kind (app) == AS_APP_KIND_SOURCE)
+               return gs_flatpak_app_remove_source (flatpak, app, cancellable, error);
+
+       /* build and run transaction */
+       transaction = _build_transaction (plugin, flatpak, cancellable, error);
+       if (transaction == NULL) {
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       ref = gs_flatpak_app_get_ref_display (app);
+       if (!flatpak_transaction_add_uninstall (transaction, ref, error)) {
+               g_prefix_error (error, "failed to add uninstall ref %s: ", ref);
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       if (!flatpak_transaction_run (transaction, cancellable, error)) {
+               g_prefix_error (error, "failed to run transaction for %s: ", ref);
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+
+       /* get any new state */
+       if (!gs_flatpak_refresh (flatpak, G_MAXUINT,
+                                GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                cancellable, error)) {
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       if (!gs_flatpak_refine_app (flatpak, app,
+                                   GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                   cancellable, error)) {
+               g_prefix_error (error, "failed to run refine for %s: ", ref);
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       return TRUE;
+}
+
+static gboolean
+app_has_local_source (GsApp *app)
+{
+       const gchar *url = gs_app_get_origin_hostname (app);
+       return url != NULL && g_str_has_prefix (url, "file://");
 }
 
 gboolean
@@ -370,6 +538,14 @@ gs_plugin_app_install (GsPlugin *plugin,
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
        GsFlatpak *flatpak;
+       g_autoptr(FlatpakTransaction) transaction = NULL;
+
+       /* queue for install if installation needs the network */
+       if (!app_has_local_source (app) &&
+           !gs_plugin_get_network_available (plugin)) {
+               gs_app_set_state (app, AS_APP_STATE_QUEUED_FOR_INSTALL);
+               return TRUE;
+       }
 
        /* set the app scope */
        if (gs_app_get_scope (app) == AS_APP_SCOPE_UNKNOWN) {
@@ -388,11 +564,103 @@ gs_plugin_app_install (GsPlugin *plugin,
                }
        }
 
-       /* actually install */
+       /* not supported */
        flatpak = gs_plugin_flatpak_get_handler (plugin, app);
        if (flatpak == NULL)
                return TRUE;
-       return gs_flatpak_app_install (flatpak, app, cancellable, error);
+
+       /* is a source */
+       if (gs_app_get_kind (app) == AS_APP_KIND_SOURCE)
+               return gs_flatpak_app_install_source (flatpak, app, cancellable, error);
+
+       /* build */
+       transaction = _build_transaction (plugin, flatpak, cancellable, error);
+       if (transaction == NULL) {
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+
+       /* add to the transaction cache for quick look up -- other unrelated
+        * refs will be matched using gs_plugin_flatpak_find_app_by_ref() */
+       gs_flatpak_transaction_add_app (transaction, app);
+
+       /* add flatpakref */
+       if (gs_flatpak_app_get_file_kind (app) == GS_FLATPAK_APP_FILE_KIND_REF) {
+               GFile *file = gs_app_get_local_file (app);
+               g_autoptr(GBytes) blob = NULL;
+               if (file == NULL) {
+                       g_set_error (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                    "no local file set for bundle %s",
+                                    gs_app_get_unique_id (app));
+                       return FALSE;
+               }
+               blob = g_file_load_bytes (file, cancellable, NULL, error);
+               if (blob == NULL) {
+                       gs_flatpak_error_convert (error);
+                       return FALSE;
+               }
+               if (!flatpak_transaction_add_install_flatpakref (transaction, blob, error)) {
+                       gs_flatpak_error_convert (error);
+                       return FALSE;
+               }
+
+       /* add bundle */
+       } else if (gs_flatpak_app_get_file_kind (app) == GS_FLATPAK_APP_FILE_KIND_BUNDLE) {
+               GFile *file = gs_app_get_local_file (app);
+               if (file == NULL) {
+                       g_set_error (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                    "no local file set for bundle %s",
+                                    gs_app_get_unique_id (app));
+                       return FALSE;
+               }
+               if (!flatpak_transaction_add_install_bundle (transaction, file,
+                                                            NULL, error)) {
+                       g_autofree gchar *fn = g_file_get_path (file);
+                       g_prefix_error (error, "failed to add install ref %s: ", fn);
+                       gs_flatpak_error_convert (error);
+                       return FALSE;
+               }
+
+       /* add normal ref */
+       } else {
+               g_autofree gchar *ref = gs_flatpak_app_get_ref_display (app);
+               if (!flatpak_transaction_add_install (transaction,
+                                                     gs_app_get_origin (app),
+                                                     ref, NULL, error)) {
+                       g_prefix_error (error, "failed to add install ref %s: ", ref);
+                       gs_flatpak_error_convert (error);
+                       return FALSE;
+               }
+       }
+
+       /* run transaction */
+       if (!flatpak_transaction_run (transaction, cancellable, error)) {
+               g_prefix_error (error, "failed to run transaction for %s: ",
+                               gs_app_get_unique_id (app));
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+
+       /* get any new state */
+       if (!gs_flatpak_refresh (flatpak, G_MAXUINT,
+                                GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                cancellable, error)) {
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       if (!gs_flatpak_refine_app (flatpak, app,
+                                   GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                   cancellable, error)) {
+               g_prefix_error (error, "failed to run refine for %s: ",
+                               gs_app_get_unique_id (app));
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       return TRUE;
 }
 
 gboolean
@@ -401,56 +669,83 @@ gs_plugin_update_app (GsPlugin *plugin,
                      GCancellable *cancellable,
                      GError **error)
 {
-       GsFlatpak *flatpak = gs_plugin_flatpak_get_handler (plugin, app);
+       GsFlatpak *flatpak;
+       g_autoptr(FlatpakTransaction) transaction = NULL;
+       g_autofree gchar *ref = NULL;
+
+       /* not supported */
+       flatpak = gs_plugin_flatpak_get_handler (plugin, app);
        if (flatpak == NULL)
                return TRUE;
-       return gs_flatpak_update_app (flatpak, app, cancellable, error);
+
+       /* build and run transaction */
+       transaction = _build_transaction (plugin, flatpak, cancellable, error);
+       if (transaction == NULL) {
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       ref = gs_flatpak_app_get_ref_display (app);
+       if (!flatpak_transaction_add_update (transaction, ref, NULL, NULL, error)) {
+               g_prefix_error (error, "failed to add update ref %s: ", ref);
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       if (!flatpak_transaction_run (transaction, cancellable, error)) {
+               g_prefix_error (error, "failed to run transaction for %s: ", ref);
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       gs_plugin_updates_changed (plugin);
+
+       /* get any new state */
+       if (!gs_flatpak_refresh (flatpak, G_MAXUINT,
+                                GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                cancellable, error)) {
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       if (!gs_flatpak_refine_app (flatpak, app,
+                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME,
+                                   cancellable, error)) {
+               g_prefix_error (error, "failed to run refine for %s: ", ref);
+               gs_flatpak_error_convert (error);
+               return FALSE;
+       }
+       return TRUE;
 }
 
-static gboolean
+static GsApp *
 gs_plugin_flatpak_file_to_app_repo (GsPlugin *plugin,
-                                   GsAppList *list,
                                    GFile *file,
                                    GCancellable *cancellable,
                                    GError **error)
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
-       g_autoptr(GsApp) app_tmp = NULL;
-       g_autoptr(GsAppList) list_tmp = NULL;
+       g_autoptr(GsApp) app = NULL;
 
        /* parse the repo file */
-       app_tmp = gs_flatpak_app_new_from_repo_file (file, cancellable, error);
-       if (app_tmp == NULL)
-               return FALSE;
+       app = gs_flatpak_app_new_from_repo_file (file, cancellable, error);
+       if (app == NULL)
+               return NULL;
 
-       /* does already exist in either the user or system scope */
-       list_tmp = gs_app_list_new ();
+       /* already exists */
        for (guint i = 0; i < priv->flatpaks->len; i++) {
                GsFlatpak *flatpak = g_ptr_array_index (priv->flatpaks, i);
-               if (!gs_flatpak_find_source_by_url (flatpak,
-                                                   gs_flatpak_app_get_repo_url (app_tmp),
-                                                   list_tmp, cancellable, error))
-                       return FALSE;
-       }
-       for (guint i = 0; i < gs_app_list_length (list_tmp); i++) {
-               GsApp *app_old = gs_app_list_index (list_tmp, i);
-               if (gs_app_get_state (app_old) == AS_APP_STATE_INSTALLED) {
-                       g_debug ("already have %s, using instead of %s",
-                                gs_app_get_unique_id (app_old),
-                                gs_app_get_unique_id (app_tmp));
-                       gs_app_list_add (list, app_old);
-                       return TRUE;
-               } else {
-                       g_warning ("non-installed source %s : %s",
-                                  gs_app_get_name (app_old),
-                                  as_app_state_to_string (gs_app_get_state (app_old)));
+               g_autoptr(GError) error_local = NULL;
+               g_autoptr(GsApp) app_tmp = NULL;
+               app_tmp = gs_flatpak_find_source_by_url (flatpak,
+                                                        gs_flatpak_app_get_repo_url (app),
+                                                        cancellable, &error_local);
+               if (app_tmp == NULL) {
+                       g_debug ("%s", error_local->message);
+                       continue;
                }
+               return g_steal_pointer (&app_tmp);
        }
 
        /* this is new */
-       gs_app_list_add (list, app_tmp);
-       gs_app_set_management_plugin (app_tmp, gs_plugin_get_name (plugin));
-       return TRUE;
+       gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+       return g_steal_pointer (&app);
 }
 
 static GsFlatpak *
@@ -480,69 +775,49 @@ gs_plugin_flatpak_create_temporary (GsPlugin *plugin, GCancellable *cancellable,
        return gs_flatpak_new (plugin, installation, GS_FLATPAK_FLAG_IS_TEMPORARY);
 }
 
-static gboolean
+static GsApp *
 gs_plugin_flatpak_file_to_app_bundle (GsPlugin *plugin,
-                                     GsAppList *list,
                                      GFile *file,
                                      GCancellable *cancellable,
                                      GError **error)
 {
-       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autofree gchar *ref = NULL;
+       g_autoptr(GsApp) app = NULL;
        g_autoptr(GsApp) app_tmp = NULL;
-       g_autoptr(GsAppList) list_tmp = NULL;
        g_autoptr(GsFlatpak) flatpak_tmp = NULL;
 
        /* only use the temporary GsFlatpak to avoid the auth dialog */
        flatpak_tmp = gs_plugin_flatpak_create_temporary (plugin, cancellable, error);
        if (flatpak_tmp == NULL)
-               return FALSE;
+               return NULL;
 
        /* add object */
-       app_tmp = gs_flatpak_file_to_app_bundle (flatpak_tmp, file, cancellable, error);
-       if (app_tmp == NULL)
-               return FALSE;
+       app = gs_flatpak_file_to_app_bundle (flatpak_tmp, file, cancellable, error);
+       if (app == NULL)
+               return NULL;
 
-       /* does already exist in either the user or system scope */
-       list_tmp = gs_app_list_new ();
-       for (guint i = 0; i < priv->flatpaks->len; i++) {
-               GsFlatpak *flatpak = g_ptr_array_index (priv->flatpaks, i);
-               if (!gs_flatpak_find_app (flatpak,
-                                         gs_flatpak_app_get_ref_kind (app_tmp),
-                                         gs_flatpak_app_get_ref_name (app_tmp),
-                                         gs_flatpak_app_get_ref_arch (app_tmp),
-                                         gs_flatpak_app_get_ref_branch (app_tmp),
-                                         list_tmp, cancellable, error))
-                       return FALSE;
-       }
-       for (guint i = 0; i < gs_app_list_length (list_tmp); i++) {
-               GsApp *app_old = gs_app_list_index (list_tmp, i);
-               if (gs_app_get_state (app_old) == AS_APP_STATE_INSTALLED) {
-                       g_debug ("already have %s, using instead of %s",
-                                gs_app_get_unique_id (app_old),
-                                gs_app_get_unique_id (app_tmp));
-                       gs_app_list_add (list, app_old);
-                       return TRUE;
-               }
-       }
+       /* is this already installed or available in a configured remote */
+       ref = gs_flatpak_app_get_ref_display (app);
+       app_tmp = gs_plugin_flatpak_find_app_by_ref (plugin, ref, cancellable, NULL);
+       if (app_tmp != NULL)
+               return g_steal_pointer (&app);
 
        /* force this to be 'any' scope for installation */
-       gs_app_set_scope (app_tmp, AS_APP_SCOPE_UNKNOWN);
+       gs_app_set_scope (app, AS_APP_SCOPE_UNKNOWN);
 
        /* this is new */
-       gs_app_list_add (list, app_tmp);
-       gs_app_set_management_plugin (app_tmp, gs_plugin_get_name (plugin));
-       return TRUE;
+       return g_steal_pointer (&app);
 }
 
-static gboolean
+static GsApp *
 gs_plugin_flatpak_file_to_app_ref (GsPlugin *plugin,
-                                  GsAppList *list,
                                   GFile *file,
                                   GCancellable *cancellable,
                                   GError **error)
 {
-       GsPluginData *priv = gs_plugin_get_data (plugin);
-       GsApp *runtime_app;
+       GsApp *runtime;
+       g_autofree gchar *ref = NULL;
+       g_autoptr(GsApp) app = NULL;
        g_autoptr(GsApp) app_tmp = NULL;
        g_autoptr(GsAppList) list_tmp = NULL;
        g_autoptr(GsFlatpak) flatpak_tmp = NULL;
@@ -550,78 +825,42 @@ gs_plugin_flatpak_file_to_app_ref (GsPlugin *plugin,
        /* only use the temporary GsFlatpak to avoid the auth dialog */
        flatpak_tmp = gs_plugin_flatpak_create_temporary (plugin, cancellable, error);
        if (flatpak_tmp == NULL)
-               return FALSE;
+               return NULL;
 
        /* add object */
-       app_tmp = gs_flatpak_file_to_app_ref (flatpak_tmp, file, cancellable, error);
-       if (app_tmp == NULL)
-               return FALSE;
+       app = gs_flatpak_file_to_app_ref (flatpak_tmp, file, cancellable, error);
+       if (app == NULL)
+               return NULL;
 
-       /* does already exist in either the user or system scope */
-       list_tmp = gs_app_list_new ();
-       for (guint i = 0; i < priv->flatpaks->len; i++) {
-               GsFlatpak *flatpak = g_ptr_array_index (priv->flatpaks, i);
-               if (!gs_flatpak_find_app (flatpak,
-                                         gs_flatpak_app_get_ref_kind (app_tmp),
-                                         gs_flatpak_app_get_ref_name (app_tmp),
-                                         gs_flatpak_app_get_ref_arch (app_tmp),
-                                         gs_flatpak_app_get_ref_branch (app_tmp),
-                                         list_tmp, cancellable, error)) {
-                       g_prefix_error (error, "failed to find in existing remotes: ");
-                       return FALSE;
-               }
-       }
-       for (guint i = 0; i < gs_app_list_length (list_tmp); i++) {
-               GsApp *app_old = gs_app_list_index (list_tmp, i);
-               if (gs_app_get_state (app_old) == AS_APP_STATE_INSTALLED) {
-                       g_debug ("already have %s, using instead of %s",
-                                gs_app_get_unique_id (app_old),
-                                gs_app_get_unique_id (app_tmp));
-                       gs_app_list_add (list, app_old);
-                       return TRUE;
-               }
-       }
+       /* is this already installed or available in a configured remote */
+       ref = gs_flatpak_app_get_ref_display (app);
+       app_tmp = gs_plugin_flatpak_find_app_by_ref (plugin, ref, cancellable, NULL);
+       if (app_tmp != NULL)
+               return g_steal_pointer (&app);
 
        /* force this to be 'any' scope for installation */
-       gs_app_set_scope (app_tmp, AS_APP_SCOPE_UNKNOWN);
+       gs_app_set_scope (app, AS_APP_SCOPE_UNKNOWN);
 
        /* do we have a system runtime available */
-       runtime_app = gs_app_get_runtime (app_tmp);
-       if (runtime_app != NULL &&
-           gs_app_get_state (runtime_app) != AS_APP_STATE_INSTALLED) {
-               g_autofree gchar *ref_display = gs_flatpak_app_get_ref_display (runtime_app);
-               g_autoptr(GsAppList) list_runtimes = gs_app_list_new ();
-               for (guint i = 0; i < priv->flatpaks->len; i++) {
-                       GsFlatpak *flatpak = g_ptr_array_index (priv->flatpaks, i);
-                       g_debug ("looking for %s in %s",
-                                ref_display, gs_flatpak_get_id (flatpak));
-                       if (!gs_flatpak_find_app (flatpak,
-                                                 gs_flatpak_app_get_ref_kind (runtime_app),
-                                                 gs_flatpak_app_get_ref_name (runtime_app),
-                                                 gs_flatpak_app_get_ref_arch (runtime_app),
-                                                 gs_flatpak_app_get_ref_branch (runtime_app),
-                                                 list_runtimes,
-                                                 cancellable, error))
-                               return FALSE;
-               }
-               for (guint i = 0; i < gs_app_list_length (list_runtimes); i++) {
-                       GsApp *runtime_old = gs_app_list_index (list_runtimes, i);
-                       if (gs_app_get_state (runtime_old) == AS_APP_STATE_INSTALLED ||
-                           gs_app_get_state (runtime_old) == AS_APP_STATE_AVAILABLE) {
-                               g_debug ("already have %s, using instead of %s",
-                                        gs_app_get_unique_id (runtime_old),
-                                        gs_app_get_unique_id (runtime_app));
-                               gs_app_set_runtime (app_tmp, runtime_old);
-                               gs_app_set_update_runtime (app_tmp, runtime_old);
-                               break;
-                       }
+       runtime = gs_app_get_runtime (app);
+       if (runtime != NULL) {
+               g_autoptr(GsApp) runtime_tmp = NULL;
+               g_autofree gchar *runtime_ref = gs_flatpak_app_get_ref_display (runtime);
+               runtime_tmp = gs_plugin_flatpak_find_app_by_ref (plugin,
+                                                                runtime_ref,
+                                                                cancellable,
+                                                                NULL);
+               if (runtime_tmp != NULL) {
+                       gs_app_set_runtime (app, runtime_tmp);
+               } else {
+                       /* the new runtime is available from the RuntimeRepo */
+                       if (gs_flatpak_app_get_runtime_url (runtime) != NULL)
+                               gs_app_set_state (runtime, AS_APP_STATE_AVAILABLE_LOCAL);
                }
        }
 
        /* this is new */
-       gs_app_list_add (list, app_tmp);
-       gs_app_set_management_plugin (app_tmp, gs_plugin_get_name (plugin));
-       return TRUE;
+       return g_steal_pointer (&app);
 }
 
 gboolean
@@ -632,6 +871,7 @@ gs_plugin_file_to_app (GsPlugin *plugin,
                       GError **error)
 {
        g_autofree gchar *content_type = NULL;
+       g_autoptr(GsApp) app = NULL;
        const gchar *mimetypes_bundle[] = {
                "application/vnd.flatpak",
                NULL };
@@ -647,26 +887,23 @@ gs_plugin_file_to_app (GsPlugin *plugin,
        if (content_type == NULL)
                return FALSE;
        if (g_strv_contains (mimetypes_bundle, content_type)) {
-               return gs_plugin_flatpak_file_to_app_bundle (plugin,
-                                                            list,
-                                                            file,
-                                                            cancellable,
-                                                            error);
-       }
-       if (g_strv_contains (mimetypes_repo, content_type)) {
-               return gs_plugin_flatpak_file_to_app_repo (plugin,
-                                                          list,
-                                                          file,
-                                                          cancellable,
-                                                          error);
-       }
-       if (g_strv_contains (mimetypes_ref, content_type)) {
-               return gs_plugin_flatpak_file_to_app_ref (plugin,
-                                                         list,
-                                                         file,
-                                                         cancellable,
-                                                         error);
+               app = gs_plugin_flatpak_file_to_app_bundle (plugin, file,
+                                                           cancellable, error);
+               if (app == NULL)
+                       return FALSE;
+       } else if (g_strv_contains (mimetypes_repo, content_type)) {
+               app = gs_plugin_flatpak_file_to_app_repo (plugin, file,
+                                                         cancellable, error);
+               if (app == NULL)
+                       return FALSE;
+       } else if (g_strv_contains (mimetypes_ref, content_type)) {
+               app = gs_plugin_flatpak_file_to_app_ref (plugin, file,
+                                                        cancellable, error);
+               if (app == NULL)
+                       return FALSE;
        }
+       if (app != NULL)
+               gs_app_list_add (list, app);
        return TRUE;
 }
 
diff --git a/plugins/flatpak/gs-self-test.c b/plugins/flatpak/gs-self-test.c
index fa327a7b..144f3680 100644
--- a/plugins/flatpak/gs-self-test.c
+++ b/plugins/flatpak/gs-self-test.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2018 Richard Hughes <richard hughsie com>
  * Copyright (C) 2017 Kalev Lember <klember redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -44,7 +44,6 @@ gs_flatpak_test_write_repo_file (const gchar *fn, const gchar *testdir, GError *
        g_string_append (str, "DefaultBranch=master\n");
        g_string_append_printf (str, "Url=%s\n", testdir_repourl);
        g_string_append (str, "Homepage=http://foo.bar\n";);
-       g_string_append (str, "GPGKey=FOOBAR==\n");
        return g_file_set_contents (fn, str->str, -1, error);
 }
 
@@ -632,17 +631,6 @@ update_app_action_finish_sync (GObject *source, GAsyncResult *res, gpointer user
        g_timeout_add_seconds (5, update_app_action_delay_cb, user_data);
 }
 
-static gboolean
-gs_plugins_flatpak_check_app_installing_cb (gpointer user_data)
-{
-       GsApp *app = GS_APP (user_data);
-       if (gs_app_get_state (app) != AS_APP_STATE_INSTALLING) {
-               g_autofree gchar *str = gs_app_to_string (app);
-               g_warning ("expected to be installing: %s", str);
-       }
-       return G_SOURCE_REMOVE;
-}
-
 static void
 gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
 {
@@ -704,7 +692,7 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
 
        /* get runtime */
        runtime = gs_app_get_runtime (app);
-       g_assert_cmpstr (gs_app_get_unique_id (runtime), ==, 
"user/flatpak/test/runtime/org.test.Runtime/master");
+       g_assert_cmpstr (gs_app_get_unique_id (runtime), ==, 
"user/flatpak/*/runtime/org.test.Runtime/master");
        g_assert_cmpint (gs_app_get_state (runtime), ==, AS_APP_STATE_AVAILABLE_LOCAL);
 
        /* check the number of sources */
@@ -721,7 +709,6 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
        plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
                                         "app", app,
                                         NULL);
-       g_timeout_add (10, gs_plugins_flatpak_check_app_installing_cb, app);
        gs_plugin_loader_job_process_async (plugin_loader, plugin_job,
                                            NULL,
                                            update_app_action_finish_sync,
@@ -764,7 +751,7 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
        /* remove the remote */
        app_source = gs_app_list_index (sources2, 0);
        g_assert (app_source != NULL);
-       g_assert_cmpstr (gs_app_get_unique_id (app_source), ==, "user/*/*/source/test/*");
+       g_assert_cmpstr (gs_app_get_unique_id (app_source), ==, "user/flatpak/*/source/test/*");
        g_object_unref (plugin_job);
        plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
                                         "app", app_source,
@@ -926,7 +913,7 @@ gs_plugins_flatpak_runtime_repo_redundant_func (GsPluginLoader *plugin_loader)
        /* remove the remote */
        app_source = gs_app_list_index (sources2, 0);
        g_assert (app_source != NULL);
-       g_assert_cmpstr (gs_app_get_unique_id (app_source), ==, "user/*/*/source/test/*");
+       g_assert_cmpstr (gs_app_get_unique_id (app_source), ==, "user/flatpak/*/source/test/*");
        g_object_unref (plugin_job);
        plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
                                         "app", app_source,
@@ -990,8 +977,7 @@ gs_plugins_flatpak_broken_remote_func (GsPluginLoader *plugin_loader)
        g_object_unref (plugin_job);
        plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
                                         "file", file,
-                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION,
                                         NULL);
        app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
@@ -1316,7 +1302,8 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        g_object_unref (plugin_job);
        plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
                                         "search", "Bingo",
-                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME,
                                         NULL);
        list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
@@ -1433,6 +1420,7 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        /* check that the app's runtime has changed */
        runtime = gs_app_get_runtime (app);
        g_assert (runtime != NULL);
+       g_assert_cmpstr (gs_app_get_unique_id (runtime), ==, 
"user/flatpak/test/runtime/org.test.Runtime/new_master");
        g_assert (old_runtime != runtime);
        g_assert_cmpstr (gs_app_get_branch (runtime), ==, "new_master");
        g_assert (gs_app_get_state (runtime) == AS_APP_STATE_INSTALLED);
diff --git a/plugins/flatpak/meson.build b/plugins/flatpak/meson.build
index 5797ab14..542a0d05 100644
--- a/plugins/flatpak/meson.build
+++ b/plugins/flatpak/meson.build
@@ -8,6 +8,7 @@ shared_module(
     'gs-appstream.c',
     'gs-flatpak-app.c',
     'gs-flatpak.c',
+    'gs-flatpak-transaction.c',
     'gs-flatpak-utils.c',
     'gs-plugin-flatpak.c'
   ],


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