[gnome-software/wip/hughsie/flatpak: 1/3] Add a flatpak plugin



commit 6806c8e3fe28ad7d2a7cdee258fac8a84611fd3f
Author: Richard Hughes <richard hughsie com>
Date:   Tue May 10 10:45:47 2016 +0100

    Add a flatpak plugin
    
    Flatpak is the new name for xdg-app.

 configure.ac                                       |   25 +-
 data/tests/flatpak/.gitignore                      |    4 +
 data/tests/flatpak/build-flatpak.sh                |   13 +
 data/tests/flatpak/chiron/app/chiron.sh            |    2 +
 .../share/appdata/org.test.Chiron.appdata.xml      |   11 +
 .../share/applications/org.test.Chiron.desktop     |    6 +
 .../flatpak/chiron/files/share/icons/chiron.png    |  Bin 0 -> 334 bytes
 data/tests/flatpak/chiron/metadata                 |    4 +
 data/tests/flatpak/org.test.Runtime/metadata       |    4 +
 src/gs-self-test.c                                 |  237 +++
 src/plugins/Makefile.am                            |   15 +
 src/plugins/gs-plugin-appstream.c                  |    3 +-
 src/plugins/gs-plugin-flatpak.c                    | 1559 ++++++++++++++++++++
 src/plugins/gs-plugin-odrs.c                       |    1 +
 14 files changed, 1882 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 70c5cbe..efc7cf7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -61,7 +61,7 @@ dnl ---------------------------------------------------------------------------
 dnl - Check library dependencies
 dnl ---------------------------------------------------------------------------
 PKG_CHECK_MODULES(GTK, gtk+-3.0 >= 3.18.2 gio-unix-2.0 gtkspell3-3.0)
-PKG_CHECK_MODULES(APPSTREAM, appstream-glib >= 0.5.12)
+PKG_CHECK_MODULES(APPSTREAM, appstream-glib >= 0.5.15)
 PKG_CHECK_MODULES(GDK_PIXBUF, gdk-pixbuf-2.0 >= 2.31.5)
 PKG_CHECK_MODULES(JSON_GLIB, json-glib-1.0 >= 1.1.1)
 PKG_CHECK_MODULES(SQLITE, sqlite3)
@@ -185,6 +185,28 @@ AS_IF([test "x$have_xdg_app" = "xyes"], [
 ])
 AM_CONDITIONAL(HAVE_XDG_APP, test "$have_xdg_app" != no)
 
+# flatpak
+AC_ARG_ENABLE(flatpak,
+              [AS_HELP_STRING([--enable-flatpak],
+                              [enable flatpak support [default=auto]])],,
+              enable_flatpak=maybe)
+AS_IF([test "x$enable_flatpak" != "xno"], [
+    PKG_CHECK_MODULES(FLATPAK,
+                      [flatpak >= 0.4.14],
+                      [have_flatpak=yes],
+                      [have_flatpak=no])
+], [
+    have_flatpak=no
+])
+AS_IF([test "x$have_flatpak" = "xyes"], [
+    AC_DEFINE(HAVE_FLATPAK,1,[Build flatpak support])
+], [
+    AS_IF([test "x$enable_flatpak" = "xyes"], [
+          AC_MSG_ERROR([flatpak support requested but 'flatpak' was not found])
+    ])
+])
+AM_CONDITIONAL(HAVE_FLATPAK, test "$have_flatpak" != no)
+
 # ostree
 AC_ARG_ENABLE(ostree,
               [AS_HELP_STRING([--enable-ostree],
@@ -335,6 +357,7 @@ echo "
         Firmware support:          ${have_firmware}
         Limba support:             ${have_limba}
         XDG-APP support:           ${have_xdg_app}
+        Flatpak support:           ${have_flatpak}
         OSTree support:            ${have_ostree}
         RPM support:               ${have_rpm}
         Steam support:             ${enable_steam}
diff --git a/data/tests/flatpak/.gitignore b/data/tests/flatpak/.gitignore
new file mode 100644
index 0000000..dff4967
--- /dev/null
+++ b/data/tests/flatpak/.gitignore
@@ -0,0 +1,4 @@
+chiron/files/share/app-info
+chiron/app
+chiron/export
+repo
diff --git a/data/tests/flatpak/build-flatpak.sh b/data/tests/flatpak/build-flatpak.sh
new file mode 100755
index 0000000..97edddb
--- /dev/null
+++ b/data/tests/flatpak/build-flatpak.sh
@@ -0,0 +1,13 @@
+rm -rf repo
+
+# build test application
+rm chiron/export -rf
+flatpak build-finish chiron
+flatpak build-export repo chiron
+appstream-compose --origin=flatpak --basename=org.test.Chiron --prefix=chiron/files 
--output-dir=chiron/files/share/app-info/xmls org.test.Chiron
+flatpak build-export repo chiron --update-appstream
+
+# build test runtime
+rm org.test.Runtime/export -rf
+flatpak build-finish org.test.Runtime
+flatpak build-export repo org.test.Runtime --runtime
diff --git a/data/tests/flatpak/chiron/app/chiron.sh b/data/tests/flatpak/chiron/app/chiron.sh
new file mode 100644
index 0000000..e61d501
--- /dev/null
+++ b/data/tests/flatpak/chiron/app/chiron.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "Hello world"
diff --git a/data/tests/flatpak/chiron/files/share/appdata/org.test.Chiron.appdata.xml 
b/data/tests/flatpak/chiron/files/share/appdata/org.test.Chiron.appdata.xml
new file mode 100644
index 0000000..69d96d7
--- /dev/null
+++ b/data/tests/flatpak/chiron/files/share/appdata/org.test.Chiron.appdata.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2016 Richard Hughes <richard hughsie com> -->
+<component type="desktop">
+  <id>org.test.Chiron.desktop</id>
+  <metadata_license>CC0-1.0</metadata_license>
+  <project_license>GPL-2.0+</project_license>
+  <name>Chiron</name>
+  <summary>Single line synopsis</summary>
+  <description><p>Long description.</p></description>
+  <url type="homepage">http://127.0.0.1/</url>
+</component>
diff --git a/data/tests/flatpak/chiron/files/share/applications/org.test.Chiron.desktop 
b/data/tests/flatpak/chiron/files/share/applications/org.test.Chiron.desktop
new file mode 100644
index 0000000..5e54081
--- /dev/null
+++ b/data/tests/flatpak/chiron/files/share/applications/org.test.Chiron.desktop
@@ -0,0 +1,6 @@
+[Desktop Entry]
+Type=Application
+Name=Chiron
+Exec=chiron.sh
+Icon=chiron
+Keywords=Bingo;
diff --git a/data/tests/flatpak/chiron/files/share/icons/chiron.png 
b/data/tests/flatpak/chiron/files/share/icons/chiron.png
new file mode 100644
index 0000000..0c38f2f
Binary files /dev/null and b/data/tests/flatpak/chiron/files/share/icons/chiron.png differ
diff --git a/data/tests/flatpak/chiron/metadata b/data/tests/flatpak/chiron/metadata
new file mode 100644
index 0000000..ce57357
--- /dev/null
+++ b/data/tests/flatpak/chiron/metadata
@@ -0,0 +1,4 @@
+[Application]
+name=org.test.Chiron
+runtime=org.test.Runtime/x86_64/master
+command=chiron.sh
diff --git a/data/tests/flatpak/org.test.Runtime/metadata b/data/tests/flatpak/org.test.Runtime/metadata
new file mode 100644
index 0000000..cf5655d
--- /dev/null
+++ b/data/tests/flatpak/org.test.Runtime/metadata
@@ -0,0 +1,4 @@
+[Runtime]
+name=org.test.Runtime
+runtime=org.test.Runtime/x86_64/master
+sdk=org.test.Runtime/x86_64/master
diff --git a/data/tests/flatpak/org.test.Runtime/usr/share/libtest/README 
b/data/tests/flatpak/org.test.Runtime/usr/share/libtest/README
new file mode 100644
index 0000000..e69de29
diff --git a/src/gs-self-test.c b/src/gs-self-test.c
index f641b11..8062d2c 100644
--- a/src/gs-self-test.c
+++ b/src/gs-self-test.c
@@ -22,7 +22,9 @@
 #include "config.h"
 
 #include <glib-object.h>
+#include <glib/gstdio.h>
 #include <stdlib.h>
+#include <fnmatch.h>
 
 #include "gs-app-private.h"
 #include "gs-plugin.h"
@@ -46,6 +48,50 @@ gs_test_get_filename (const gchar *filename)
        return g_strdup (full_tmp);
 }
 
+/**
+ * gs_test_rmtree:
+ **/
+static gboolean
+gs_test_rmtree (const gchar *directory, GError **error)
+{
+       const gchar *filename;
+       g_autoptr(GDir) dir = NULL;
+
+       /* try to open */
+       dir = g_dir_open (directory, 0, error);
+       if (dir == NULL)
+               return FALSE;
+
+       /* find each */
+       while ((filename = g_dir_read_name (dir))) {
+               g_autofree gchar *src = NULL;
+               src = g_build_filename (directory, filename, NULL);
+               if (g_file_test (src, G_FILE_TEST_IS_DIR) &&
+                   !g_file_test (src, G_FILE_TEST_IS_SYMLINK)) {
+                       if (!gs_test_rmtree (src, error))
+                               return FALSE;
+               } else {
+                       g_debug ("deleting %s", src);
+                       if (g_unlink (src) != 0) {
+                               g_set_error (error,
+                                            GS_PLUGIN_ERROR,
+                                            GS_PLUGIN_ERROR_FAILED,
+                                            "Failed to delete: %s", src);
+                               return FALSE;
+                       }
+               }
+       }
+       g_debug ("removing empty %s", directory);
+       if (g_rmdir (directory) != 0) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "Failed to remove: %s", directory);
+               return FALSE;
+       }
+       return TRUE;
+}
+
 static gboolean
 gs_app_list_filter_cb (GsApp *app, gpointer user_data)
 {
@@ -563,9 +609,171 @@ gs_plugin_loader_fwupd_func (GsPluginLoader *plugin_loader)
        g_assert_cmpstr (gs_app_get_id (app), ==, NULL);
 }
 
+static void
+gs_plugin_loader_flatpak_func (GsPluginLoader *plugin_loader)
+{
+       GsApp *app;
+       const gchar *root;
+       gboolean ret;
+       gint kf_remote_repo_version;
+       g_autofree gchar *changed_fn = NULL;
+       g_autofree gchar *config_fn = NULL;
+       g_autofree gchar *desktop_fn = NULL;
+       g_autofree gchar *kf_remote_url = NULL;
+       g_autofree gchar *metadata_fn = NULL;
+       g_autofree gchar *runtime_fn = NULL;
+       g_autofree gchar *testdir = NULL;
+       g_autofree gchar *testdir_repourl = NULL;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GKeyFile) kf1 = g_key_file_new ();
+       g_autoptr(GKeyFile) kf2 = g_key_file_new ();
+       g_autoptr(GsApp) app_source = NULL;
+       g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsAppList) sources = NULL;
+
+       /* no flatpak, abort */
+       if (!gs_plugin_loader_get_enabled (plugin_loader, "flatpak"))
+               return;
+
+       /* check changed file exists */
+       root = g_getenv ("GS_SELF_TEST_FLATPACK_DATADIR");
+       changed_fn = g_build_filename (root, "flatpak", ".changed", NULL);
+       g_assert (g_file_test (changed_fn, G_FILE_TEST_IS_REGULAR));
+
+       /* check repo is set up */
+       config_fn = g_build_filename (root, "flatpak", "repo", "config", NULL);
+       ret = g_key_file_load_from_file (kf1, config_fn, G_KEY_FILE_NONE, &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       kf_remote_repo_version = g_key_file_get_integer (kf1, "core", "repo_version", &error);
+       g_assert_no_error (error);
+       g_assert_cmpint (kf_remote_repo_version, ==, 1);
+
+       /* add a remote */
+       app_source = gs_app_new ("test");
+       testdir = gs_test_get_filename ("tests/flatpak");
+       g_assert (testdir != NULL);
+       testdir_repourl = g_strdup_printf ("file://%s/repo", testdir);
+       gs_app_set_kind (app_source, AS_APP_KIND_SOURCE);
+       gs_app_set_management_plugin (app_source, "flatpak");
+       gs_app_set_state (app_source, AS_APP_STATE_AVAILABLE);
+       gs_app_set_url (app_source, AS_URL_KIND_HOMEPAGE, testdir_repourl);
+       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
+                                          GS_PLUGIN_LOADER_ACTION_ADD_SOURCE,
+                                          NULL,
+                                          &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpint (gs_app_get_state (app_source), ==, AS_APP_STATE_INSTALLED);
+
+       /* check remote was set up */
+       ret = g_key_file_load_from_file (kf2, config_fn, G_KEY_FILE_NONE, &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       kf_remote_url = g_key_file_get_string (kf2, "remote \"test\"", "url", &error);
+       g_assert_no_error (error);
+       g_assert_cmpstr (kf_remote_url, !=, NULL);
+
+       /* check the source now exists */
+       sources = gs_plugin_loader_get_sources (plugin_loader,
+                                               GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                               NULL,
+                                               &error);
+       g_assert_no_error (error);
+       g_assert (sources != NULL);
+       g_assert_cmpint (g_list_length (sources), ==, 1);
+       app = GS_APP (sources->data);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "test");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_SOURCE);
+
+       /* refresh the appstream metadata */
+       ret = gs_plugin_loader_refresh (plugin_loader,
+                                       G_MAXUINT,
+                                       GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                       NULL,
+                                       &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+
+       /* find available application */
+       list = gs_plugin_loader_search (plugin_loader,
+                                       "Bingo",
+                                       GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                       NULL,
+                                       &error);
+       g_assert_no_error (error);
+       g_assert (list != NULL);
+
+       /* make sure there is one entry, the flatpak app */
+       g_assert_cmpint (g_list_length (list), ==, 1);
+       app = GS_APP (list->data);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "org.test.Chiron.desktop");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
+
+       /* install, also installing runtime */
+       ret = gs_plugin_loader_app_action (plugin_loader, app,
+                                          GS_PLUGIN_LOADER_ACTION_INSTALL,
+                                          NULL,
+                                          &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
+
+       /* check the application exists in the right places */
+       metadata_fn = g_build_filename (root,
+                                       "flatpak",
+                                       "app",
+                                       "org.test.Chiron",
+                                       "current",
+                                       "active",
+                                       "metadata",
+                                       NULL);
+       g_assert (g_file_test (metadata_fn, G_FILE_TEST_IS_REGULAR));
+       desktop_fn = g_build_filename (root,
+                                       "flatpak",
+                                       "app",
+                                       "org.test.Chiron",
+                                       "current",
+                                       "active",
+                                       "export",
+                                       "share",
+                                       "applications",
+                                       "org.test.Chiron.desktop",
+                                       NULL);
+       g_assert (g_file_test (desktop_fn, G_FILE_TEST_IS_REGULAR));
+
+       /* check the runtime was installed as well */
+       runtime_fn = g_build_filename (root,
+                                       "flatpak",
+                                       "runtime",
+                                       "org.test.Runtime",
+                                       "x86_64",
+                                       "master",
+                                       "active",
+                                       "files",
+                                       "share",
+                                       "libtest",
+                                       "README",
+                                       NULL);
+       g_assert (g_file_test (runtime_fn, G_FILE_TEST_IS_REGULAR));
+
+       /* remove the application */
+       ret = gs_plugin_loader_app_action (plugin_loader, app,
+                                          GS_PLUGIN_LOADER_ACTION_REMOVE,
+                                          NULL,
+                                          &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
+       g_assert (!g_file_test (metadata_fn, G_FILE_TEST_IS_REGULAR));
+       g_assert (!g_file_test (desktop_fn, G_FILE_TEST_IS_REGULAR));
+}
+
 int
 main (int argc, char **argv)
 {
+       const gchar *tmp_root = "/var/tmp/self-test";
        gboolean ret;
        g_autofree gchar *fn = NULL;
        g_autofree gchar *xml = NULL;
@@ -576,6 +784,7 @@ main (int argc, char **argv)
                "dpkg",
                "dummy",
                "epiphany",
+               "flatpak",
                "fwupd",
                "hardcoded-blacklist",
                "icons",
@@ -596,6 +805,15 @@ main (int argc, char **argv)
        g_setenv ("GS_SELF_TEST_PROVENANCE_SOURCES", "london*,boston", TRUE);
        g_setenv ("GS_SELF_TEST_PROVENANCE_LICENSE_SOURCES", "london*,boston", TRUE);
        g_setenv ("GS_SELF_TEST_PROVENANCE_LICENSE_URL", "https://www.debian.org/";, TRUE);
+       g_setenv ("GS_SELF_TEST_FLATPACK_DATADIR", tmp_root, TRUE);
+
+       /* ensure test root does not exist */
+       if (g_file_test (tmp_root, G_FILE_TEST_EXISTS)) {
+               ret = gs_test_rmtree (tmp_root, &error);
+               g_assert_no_error (error);
+               g_assert (ret);
+               g_assert (!g_file_test (tmp_root, G_FILE_TEST_EXISTS));
+       }
 
        fn = gs_test_get_filename ("icons/hicolor/48x48/org.gnome.Software.png");
        g_assert (fn != NULL);
@@ -645,8 +863,24 @@ main (int argc, char **argv)
                "    <name>test</name>\n"
                "    <icon type=\"remote\">file://%s</icon>\n"
                "  </component>\n"
+               "  <component type=\"desktop\">\n"
+               "    <id>org.test.Chiron.desktop</id>\n"
+               "    <name>Chiron</name>\n"
+               "    <summary>Single line synopsis</summary>\n"
+               "    <description><p>Long description.</p></description>\n"
+               "    <icon height=\"128\" width=\"128\" type=\"cached\">128x128/org.test.Chiron.png</icon>\n"
+               "    <icon height=\"64\" width=\"64\" type=\"cached\">64x64/org.test.Chiron.png</icon>\n"
+               "    <keywords>\n"
+               "      <keyword>Bingo</keyword>\n"
+               "    </keywords>\n"
+               "    <project_license>GPL-2.0+</project_license>\n"
+               "    <url type=\"homepage\">http://127.0.0.1/</url>\n"
+               "    <bundle type=\"flatpak\" 
runtime=\"org.test.Runtime/x86_64/master\">app/org.test.Chiron/x86_64/master</bundle>\n"
+               "  </component>\n"
                "</components>\n", fn);
        g_setenv ("GS_SELF_TEST_APPSTREAM_XML", xml, TRUE);
+       g_setenv ("GS_SELF_TEST_APPSTREAM_ICON_ROOT",
+                 "/var/tmp/self-test/flatpak/appstream/test/x86_64/active/", TRUE);
 
        /* only critical and error are fatal */
        g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
@@ -670,6 +904,9 @@ main (int argc, char **argv)
        g_assert (gs_plugin_loader_get_enabled (plugin_loader, "dummy"));
 
        /* plugin tests go here */
+       g_test_add_data_func ("/gnome-software/plugin-loader{flatpak}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_flatpak_func);
        g_test_add_data_func ("/gnome-software/plugin-loader{fwupd}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_fwupd_func);
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index 696d720..0237315 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -14,6 +14,7 @@ AM_CPPFLAGS =                                         \
        $(LIMBA_CFLAGS)                                 \
        $(OSTREE_CFLAGS)                                \
        $(XDG_APP_CFLAGS)                               \
+       $(FLATPAK_CFLAGS)                               \
        $(RPM_CFLAGS)                                   \
        -DBINDIR=\"$(bindir)\"                          \
        -DDATADIR=\"$(datadir)\"                        \
@@ -71,6 +72,10 @@ if HAVE_XDG_APP
 plugin_LTLIBRARIES += libgs_plugin_xdg-app.la
 endif
 
+if HAVE_FLATPAK
+plugin_LTLIBRARIES += libgs_plugin_flatpak.la
+endif
+
 if HAVE_OSTREE
 plugin_LTLIBRARIES += libgs_plugin_ostree.la
 endif
@@ -168,6 +173,16 @@ libgs_plugin_xdg_app_la_LDFLAGS = -module -avoid-version
 libgs_plugin_xdg_app_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
 endif
 
+if HAVE_FLATPAK
+libgs_plugin_flatpak_la_SOURCES =                      \
+       gs-appstream.c                                  \
+       gs-appstream.h                                  \
+       gs-plugin-flatpak.c
+libgs_plugin_flatpak_la_LIBADD = $(GS_PLUGIN_LIBS) $(FLATPAK_LIBS)
+libgs_plugin_flatpak_la_LDFLAGS = -module -avoid-version
+libgs_plugin_flatpak_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+endif
+
 if HAVE_OSTREE
 libgs_plugin_ostree_la_SOURCES = gs-plugin-ostree.c
 libgs_plugin_ostree_la_LIBADD = $(GS_PLUGIN_LIBS) $(OSTREE_LIBS)
diff --git a/src/plugins/gs-plugin-appstream.c b/src/plugins/gs-plugin-appstream.c
index c8241c8..13f4b9c 100644
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@ -174,7 +174,8 @@ gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
                                     AS_STORE_LOAD_FLAG_APP_INFO_USER |
                                     AS_STORE_LOAD_FLAG_APPDATA |
                                     AS_STORE_LOAD_FLAG_DESKTOP |
-                                    AS_STORE_LOAD_FLAG_XDG_APP_USER |
+                                    AS_STORE_LOAD_FLAG_FLATPAK_USER |
+                                    AS_STORE_LOAD_FLAG_FLATPAK_SYSTEM |
                                     AS_STORE_LOAD_FLAG_APP_INSTALL,
                                     NULL,
                                     error);
diff --git a/src/plugins/gs-plugin-flatpak.c b/src/plugins/gs-plugin-flatpak.c
new file mode 100644
index 0000000..e5cfd16
--- /dev/null
+++ b/src/plugins/gs-plugin-flatpak.c
@@ -0,0 +1,1559 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015 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.
+ */
+
+/* Notes:
+ *
+ * All GsApp's created have management-plugin set to flatpak
+ * Some GsApp's created have have flatpak::kind of app or runtime
+ * The GsApp:origin is the remote name, e.g. test-repo
+ */
+
+#include <config.h>
+
+#include <flatpak.h>
+#include <gnome-software.h>
+
+#include "gs-appstream.h"
+
+static gboolean                gs_plugin_refine_item_metadata (GsPlugin *plugin,
+                                                       GsApp *app,
+                                                       GCancellable *cancellable,
+                                                       GError **error);
+
+struct GsPluginData {
+       FlatpakInstallation     *installation;
+       GFileMonitor            *monitor;
+};
+
+/**
+ * gs_plugin_order_after:
+ */
+const gchar **
+gs_plugin_order_after (GsPlugin *plugin)
+{
+       static const gchar *deps[] = {
+               "appstream",
+               NULL };
+       return deps;
+}
+
+/**
+ * gs_plugin_get_conflicts:
+ */
+const gchar **
+gs_plugin_get_conflicts (GsPlugin *plugin)
+{
+       static const gchar *deps[] = {
+               "xdg-app",
+               NULL };
+       return deps;
+}
+
+/**
+ * gs_plugin_initialize:
+ */
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+       gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
+}
+
+/**
+ * gs_plugin_destroy:
+ */
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       if (priv->installation != NULL)
+               g_object_unref (priv->installation);
+       if (priv->monitor != NULL)
+               g_object_unref (priv->monitor);
+}
+
+/**
+ * gs_plugin_adopt_app:
+ */
+void
+gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app)
+{
+       if (g_str_has_prefix (gs_app_get_id (app), "user-flatpak:") ||
+           g_str_has_prefix (gs_app_get_id (app), "flatpak:")) {
+               gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+       }
+}
+
+/* helpers */
+#define gs_app_get_flatpak_kind_as_str(app)    gs_app_get_metadata_item(app,"flatpak::kind")
+#define gs_app_get_flatpak_name(app)           gs_app_get_metadata_item(app,"flatpak::name")
+#define gs_app_get_flatpak_arch(app)           gs_app_get_metadata_item(app,"flatpak::arch")
+#define gs_app_get_flatpak_branch(app)         gs_app_get_metadata_item(app,"flatpak::branch")
+#define gs_app_get_flatpak_commit(app)         gs_app_get_metadata_item(app,"flatpak::commit")
+#define gs_app_set_flatpak_name(app,val)       gs_app_set_metadata(app,"flatpak::name",val)
+#define gs_app_set_flatpak_arch(app,val)       gs_app_set_metadata(app,"flatpak::arch",val)
+#define gs_app_set_flatpak_branch(app,val)     gs_app_set_metadata(app,"flatpak::branch",val)
+#define gs_app_set_flatpak_commit(app,val)     gs_app_set_metadata(app,"flatpak::commit",val)
+
+/**
+ * gs_app_get_flatpak_kind:
+ */
+static FlatpakRefKind
+gs_app_get_flatpak_kind (GsApp *app)
+{
+       const gchar *kind = gs_app_get_metadata_item (app, "flatpak::kind");
+       if (g_strcmp0 (kind, "app") == 0)
+               return FLATPAK_REF_KIND_APP;
+       if (g_strcmp0 (kind, "runtime") == 0)
+               return FLATPAK_REF_KIND_RUNTIME;
+       g_warning ("unknown flatpak kind: %s", kind);
+       return FLATPAK_REF_KIND_APP;
+}
+
+/**
+ * gs_app_set_flatpak_kind:
+ */
+static void
+gs_app_set_flatpak_kind (GsApp *app, FlatpakRefKind kind)
+{
+       if (kind == FLATPAK_REF_KIND_APP)
+               gs_app_set_metadata (app, "flatpak::kind", "app");
+       else if (kind == FLATPAK_REF_KIND_RUNTIME)
+               gs_app_set_metadata (app, "flatpak::kind", "runtime");
+       else
+               g_assert_not_reached ();
+}
+
+#ifndef HAVE_PACKAGEKIT
+/**
+ * gs_plugin_add_popular:
+ */
+gboolean
+gs_plugin_add_popular (GsPlugin *plugin,
+                      GList **list,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       guint i;
+       const gchar *apps[] = {
+               "org.gnome.Builder.desktop",
+               "org.gnome.Calculator.desktop",
+               "org.gnome.clocks.desktop",
+               "org.gnome.Dictionary.desktop",
+               "org.gnome.Documents.desktop",
+               "org.gnome.Evince.desktop",
+               "org.gnome.gedit.desktop",
+               "org.gnome.Maps.desktop",
+               "org.gnome.Weather.desktop",
+               NULL };
+
+       /* just add all */
+       for (i = 0; apps[i] != NULL; i++) {
+               g_autoptr(GsApp) app = NULL;
+               app = gs_app_new (apps[i]);
+               gs_app_list_add (list, app);
+       }
+       return TRUE;
+}
+#endif
+
+/**
+ * gs_plugin_flatpak_changed_cb:
+ */
+static void
+gs_plugin_flatpak_changed_cb (GFileMonitor *monitor,
+                             GFile *child,
+                             GFile *other_file,
+                             GFileMonitorEvent event_type,
+                             GsPlugin *plugin)
+{
+       gs_plugin_updates_changed (plugin);
+}
+
+/**
+ * gs_plugin_refresh_appstream:
+ */
+static gboolean
+gs_plugin_refresh_appstream (GsPlugin *plugin,
+                            guint cache_age,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       gboolean ret;
+       guint i;
+       g_autoptr(GPtrArray) xremotes = NULL;
+
+       xremotes = flatpak_installation_list_remotes (priv->installation,
+                                                     cancellable,
+                                                     error);
+       if (xremotes == NULL)
+               return FALSE;
+       for (i = 0; i < xremotes->len; i++) {
+               guint tmp;
+               g_autoptr(GError) error_local = NULL;
+               g_autoptr(GFile) file = NULL;
+               g_autoptr(GFile) file_timestamp = NULL;
+               g_autofree gchar *appstream_fn = NULL;
+               FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+
+               /* skip known-broken repos */
+               if (g_strcmp0 (flatpak_remote_get_name (xremote), "gnome-sdk") == 0)
+                       continue;
+               if (g_strcmp0 (flatpak_remote_get_name (xremote), "test-apps") == 0)
+                       continue;
+
+               /* is the timestamp new enough */
+               file_timestamp = flatpak_remote_get_appstream_timestamp (xremote, NULL);
+               tmp = gs_utils_get_file_age (file_timestamp);
+               if (tmp < cache_age) {
+                       g_autofree gchar *fn = g_file_get_path (file_timestamp);
+                       g_debug ("%s is only %i seconds old, so ignoring refresh",
+                                fn, tmp);
+                       continue;
+               }
+
+               /* download new data */
+               g_debug ("%s is %i seconds old, so downloading new data",
+                        flatpak_remote_get_name (xremote), tmp);
+               ret = flatpak_installation_update_appstream_sync (priv->installation,
+                                                                 flatpak_remote_get_name (xremote),
+                                                                 NULL, /* arch */
+                                                                 NULL, /* out_changed */
+                                                                 cancellable,
+                                                                 &error_local);
+               if (!ret) {
+                       if (g_error_matches (error_local,
+                                            G_IO_ERROR,
+                                            G_IO_ERROR_FAILED)) {
+                               g_debug ("Failed to get AppStream metadata: %s",
+                                        error_local->message);
+                               continue;
+                       }
+                       g_set_error (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                    "Failed to get AppStream metadata: %s",
+                                    error_local->message);
+                       return FALSE;
+               }
+
+               /* add the new AppStream repo to the shared store */
+               file = flatpak_remote_get_appstream_dir (xremote, NULL);
+               appstream_fn = g_file_get_path (file);
+               g_debug ("using AppStream metadata found at: %s", appstream_fn);
+       }
+       return TRUE;
+}
+
+/**
+ * gs_plugin_setup:
+ */
+gboolean
+gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       const gchar *destdir;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* we use a permissions helper to elevate privs */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "flatpak::ensure-origin");
+       destdir = g_getenv ("GS_SELF_TEST_FLATPACK_DATADIR");
+       if (destdir != NULL) {
+               g_autofree gchar *full_path = g_build_filename (destdir,
+                                                               "flatpak",
+                                                               NULL);
+               g_autoptr(GFile) file = g_file_new_for_path (full_path);
+               g_debug ("using custom flatpak path %s", full_path);
+               priv->installation = flatpak_installation_new_for_path (file,
+                                                                       TRUE,
+                                                                       cancellable,
+                                                                       error);
+       } else {
+               priv->installation = flatpak_installation_new_system (cancellable, error);
+       }
+       if (priv->installation == NULL)
+               return FALSE;
+
+       /* watch for changes */
+       priv->monitor =
+               flatpak_installation_create_monitor (priv->installation,
+                                                    cancellable,
+                                                    error);
+       if (priv->monitor == NULL)
+               return FALSE;
+       g_signal_connect (priv->monitor, "changed",
+                         G_CALLBACK (gs_plugin_flatpak_changed_cb), plugin);
+
+       /* success */
+       return TRUE;
+}
+
+/**
+ * gs_plugin_flatpak_set_metadata:
+ */
+static void
+gs_plugin_flatpak_set_metadata (GsApp *app, FlatpakRef *xref)
+{
+       gs_app_set_management_plugin (app, "flatpak");
+       gs_app_set_flatpak_kind (app, flatpak_ref_get_kind (xref));
+       gs_app_set_flatpak_name (app, flatpak_ref_get_name (xref));
+       gs_app_set_flatpak_arch (app, flatpak_ref_get_arch (xref));
+       gs_app_set_flatpak_branch (app, flatpak_ref_get_branch (xref));
+       gs_app_set_flatpak_commit (app, flatpak_ref_get_commit (xref));
+}
+
+/**
+ * gs_plugin_flatpak_set_metadata_installed:
+ */
+static void
+gs_plugin_flatpak_set_metadata_installed (GsApp *app, FlatpakInstalledRef *xref)
+{
+       guint64 mtime;
+       guint64 size_installed;
+       g_autofree gchar *metadata_fn = NULL;
+       g_autoptr(GFile) file = NULL;
+       g_autoptr(GFileInfo) info = NULL;
+
+       /* for all types */
+       gs_plugin_flatpak_set_metadata (app, FLATPAK_REF (xref));
+
+       /* get the last time the app was updated */
+       metadata_fn = g_build_filename (flatpak_installed_ref_get_deploy_dir (xref),
+                                       "..",
+                                       "active",
+                                       NULL);
+       file = g_file_new_for_path (metadata_fn);
+       info = g_file_query_info (file,
+                                 G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                 NULL, NULL);
+       if (info != NULL) {
+               mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+               gs_app_set_install_date (app, mtime);
+       }
+
+       /* this is faster than resolving */
+       gs_app_set_origin (app, flatpak_installed_ref_get_origin (xref));
+
+       /* this is faster than flatpak_installation_fetch_remote_size_sync() */
+       size_installed = flatpak_installed_ref_get_installed_size (xref);
+       if (size_installed != 0)
+               gs_app_set_size_installed (app, size_installed);
+}
+
+/**
+ * gs_plugin_flatpak_build_id:
+ */
+static gchar *
+gs_plugin_flatpak_build_id (FlatpakInstallation *installation, FlatpakRef *xref)
+{
+       const gchar *prefix = "flatpak";
+
+       /* use a different prefix if we're somehow running this as per-user */
+       if (flatpak_installation_get_is_user (installation))
+               prefix = "user:flatpak";
+
+       /* flatpak doesn't use a suffix; AppStream does */
+       if (flatpak_ref_get_kind (xref) == FLATPAK_REF_KIND_APP) {
+               return g_strdup_printf ("%s:%s.desktop",
+                                       prefix,
+                                       flatpak_ref_get_name (xref));
+       }
+       return g_strdup_printf ("%s:%s.runtime",
+                               prefix,
+                               flatpak_ref_get_name (xref));
+}
+
+/**
+ * gs_plugin_flatpak_create_installed:
+ */
+static GsApp *
+gs_plugin_flatpak_create_installed (GsPlugin *plugin,
+                                   FlatpakInstalledRef *xref,
+                                   GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autofree gchar *id = NULL;
+       g_autoptr(AsIcon) icon = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       g_return_val_if_fail (xref != NULL, NULL);
+
+       /*
+        * Only show the current application in GNOME Software
+        *
+        * You can have multiple versions/branches of a particular app-id
+        * installed but only one of them is "current" where this means:
+        *  1) the default to launch unless you specify a version
+        *  2) The one that gets its exported files exported
+        */
+       if (!flatpak_installed_ref_get_is_current (xref) &&
+           flatpak_ref_get_kind (FLATPAK_REF(xref)) == FLATPAK_REF_KIND_APP) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                            "%s not current, ignoring",
+                            flatpak_ref_get_name (FLATPAK_REF (xref)));
+               return NULL;
+       }
+
+       /* create new object */
+       id = gs_plugin_flatpak_build_id (priv->installation, FLATPAK_REF (xref));
+       app = gs_app_new (id);
+       gs_plugin_flatpak_set_metadata_installed (app, xref);
+
+       switch (flatpak_ref_get_kind (FLATPAK_REF(xref))) {
+       case FLATPAK_REF_KIND_APP:
+               gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
+               break;
+       case FLATPAK_REF_KIND_RUNTIME:
+               gs_app_set_flatpak_kind (app, FLATPAK_REF_KIND_RUNTIME);
+               gs_app_set_kind (app, AS_APP_KIND_RUNTIME);
+               gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
+                                flatpak_ref_get_name (FLATPAK_REF (xref)));
+               gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
+                                   "Framework for applications");
+               gs_app_set_version (app, flatpak_ref_get_branch (FLATPAK_REF (xref)));
+               icon = as_icon_new ();
+               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
+               as_icon_set_name (icon, "system-run-symbolic");
+               gs_app_set_icon (app, icon);
+               break;
+       default:
+               g_set_error_literal (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                    "FlatpakRefKind not known");
+               return NULL;
+       }
+       return g_object_ref (app);
+}
+
+/**
+ * gs_plugin_flatpak_progress_cb:
+ */
+static void
+gs_plugin_flatpak_progress_cb (const gchar *status,
+                              guint progress,
+                              gboolean estimating,
+                              gpointer user_data)
+{
+       GsApp *app = GS_APP (user_data);
+       gs_app_set_progress (app, progress);
+}
+
+/**
+ * gs_plugin_add_installed:
+ */
+gboolean
+gs_plugin_add_installed (GsPlugin *plugin,
+                        GList **list,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autoptr(GError) error_md = NULL;
+       g_autoptr(GPtrArray) xrefs = NULL;
+       guint i;
+
+       /* if we've never ever run before, get the AppStream data */
+       if (!gs_plugin_refresh_appstream (plugin,
+                                         G_MAXUINT,
+                                         cancellable,
+                                         &error_md)) {
+               g_warning ("failed to get initial available data: %s",
+                          error_md->message);
+       }
+
+       /* get apps and runtimes */
+       xrefs = flatpak_installation_list_installed_refs (priv->installation,
+                                                         cancellable, error);
+       if (xrefs == NULL)
+               return FALSE;
+       for (i = 0; i < xrefs->len; i++) {
+               FlatpakInstalledRef *xref = g_ptr_array_index (xrefs, i);
+               g_autoptr(GError) error_local = NULL;
+               g_autoptr(GsApp) app = NULL;
+
+               /* only apps */
+               if (flatpak_ref_get_kind (FLATPAK_REF (xref)) != FLATPAK_REF_KIND_APP)
+                       continue;
+
+               app = gs_plugin_flatpak_create_installed (plugin, xref, &error_local);
+               if (app == NULL) {
+                       g_warning ("failed to add flatpak: %s", error_local->message);
+                       continue;
+               }
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+               gs_app_list_add (list, app);
+       }
+
+       return TRUE;
+}
+
+/**
+ * gs_plugin_add_sources:
+ */
+gboolean
+gs_plugin_add_sources (GsPlugin *plugin,
+                      GList **list,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autoptr(GPtrArray) xremotes = NULL;
+       guint i;
+
+       xremotes = flatpak_installation_list_remotes (priv->installation,
+                                                     cancellable,
+                                                     error);
+       if (xremotes == NULL)
+               return FALSE;
+       for (i = 0; i < xremotes->len; i++) {
+               FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+               g_autoptr(GsApp) app = NULL;
+
+               /* apps installed from bundles add their own remote that only
+                * can be used for updating that app only -- so hide them */
+               if (flatpak_remote_get_noenumerate (xremote))
+                       continue;
+
+               app = gs_app_new (flatpak_remote_get_name (xremote));
+               gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+               gs_app_set_kind (app, AS_APP_KIND_SOURCE);
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+               gs_app_set_name (app,
+                                GS_APP_QUALITY_LOWEST,
+                                flatpak_remote_get_name (xremote));
+               gs_app_set_summary (app,
+                                   GS_APP_QUALITY_LOWEST,
+                                   flatpak_remote_get_title (xremote));
+               gs_app_set_url (app,
+                               AS_URL_KIND_HOMEPAGE,
+                               flatpak_remote_get_url (xremote));
+               gs_app_list_add (list, app);
+       }
+       return TRUE;
+}
+
+
+/**
+ * gs_plugin_add_source:
+ */
+gboolean
+gs_plugin_add_source (GsPlugin *plugin,
+                     GsApp *app,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autoptr(FlatpakRemote) xremote = NULL;
+
+       /* only process this source if was created for this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app),
+                      gs_plugin_get_name (plugin)) != 0)
+               return TRUE;
+
+       /* create a new remote */
+       xremote = flatpak_remote_new (gs_app_get_id (app));
+       flatpak_remote_set_gpg_verify (xremote, FALSE); // FIXME
+       flatpak_remote_set_url (xremote, gs_app_get_url (app, AS_URL_KIND_HOMEPAGE));
+       if (gs_app_get_summary (app) != NULL)
+               flatpak_remote_set_title (xremote, gs_app_get_summary (app));
+
+       /* install it */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+       if (!flatpak_installation_modify_remote (priv->installation,
+                                                xremote,
+                                                cancellable,
+                                                error)) {
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
+
+       /* success */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       return TRUE;
+}
+
+/**
+ * gs_plugin_add_updates:
+ */
+gboolean
+gs_plugin_add_updates (GsPlugin *plugin,
+                      GList **list,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       guint i;
+       g_autoptr(GPtrArray) xrefs = NULL;
+
+       /* manually drop the cache */
+       if (0&&!flatpak_installation_drop_caches (priv->installation,
+                                              cancellable,
+                                              error)) {
+               return FALSE;
+       }
+
+       /* get all the installed apps (no network I/O) */
+       xrefs = flatpak_installation_list_installed_refs (priv->installation,
+                                                         cancellable,
+                                                         error);
+       if (xrefs == NULL)
+               return FALSE;
+       for (i = 0; i < xrefs->len; i++) {
+               FlatpakInstalledRef *xref = g_ptr_array_index (xrefs, i);
+               const gchar *commit;
+               const gchar *latest_commit;
+               g_autoptr(GsApp) app = NULL;
+               g_autoptr(GError) error_local = NULL;
+
+               /* check the application has already been downloaded */
+               commit = flatpak_ref_get_commit (FLATPAK_REF (xref));
+               latest_commit = flatpak_installed_ref_get_latest_commit (xref);
+               if (g_strcmp0 (commit, latest_commit) == 0) {
+                       g_debug ("no downloaded update for %s",
+                                flatpak_ref_get_name (FLATPAK_REF (xref)));
+                       continue;
+               }
+
+               /* we have an update to show */
+               g_debug ("%s has a downloaded update %s->%s",
+                        flatpak_ref_get_name (FLATPAK_REF (xref)),
+                        commit, latest_commit);
+               app = gs_plugin_flatpak_create_installed (plugin, xref, &error_local);
+               if (app == NULL) {
+                       g_warning ("failed to add flatpak: %s", error_local->message);
+                       continue;
+               }
+               if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED)
+                       gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
+               gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
+               gs_app_list_add (list, app);
+       }
+
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refresh:
+ */
+gboolean
+gs_plugin_refresh (GsPlugin *plugin,
+                  guint cache_age,
+                  GsPluginRefreshFlags flags,
+                  GCancellable *cancellable,
+                  GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       guint i;
+       g_autoptr(GPtrArray) xrefs = NULL;
+
+       /* update AppStream metadata */
+       if (flags & GS_PLUGIN_REFRESH_FLAGS_METADATA) {
+               if (!gs_plugin_refresh_appstream (plugin, cache_age,
+                                                 cancellable, error))
+                       return FALSE;
+       }
+
+       /* no longer interesting */
+       if ((flags & GS_PLUGIN_REFRESH_FLAGS_PAYLOAD) == 0)
+               return TRUE;
+
+       /* get all the updates available from all remotes */
+       xrefs = flatpak_installation_list_installed_refs_for_update (priv->installation,
+                                                                    cancellable,
+                                                                    error);
+       if (xrefs == NULL)
+               return FALSE;
+       for (i = 0; i < xrefs->len; i++) {
+               FlatpakInstalledRef *xref = g_ptr_array_index (xrefs, i);
+               g_autoptr(GsApp) app = NULL;
+               g_autoptr(FlatpakInstalledRef) xref2 = NULL;
+
+               /* try to create a GsApp so we can do progress reporting */
+               app = gs_plugin_flatpak_create_installed (plugin, xref, NULL);
+
+               /* fetch but do not deploy */
+               g_debug ("pulling update for %s",
+                        flatpak_ref_get_name (FLATPAK_REF (xref)));
+               xref2 = flatpak_installation_update (priv->installation,
+                                                    FLATPAK_UPDATE_FLAGS_NO_DEPLOY,
+                                                    flatpak_ref_get_kind (FLATPAK_REF (xref)),
+                                                    flatpak_ref_get_name (FLATPAK_REF (xref)),
+                                                    flatpak_ref_get_arch (FLATPAK_REF (xref)),
+                                                    flatpak_ref_get_branch (FLATPAK_REF (xref)),
+                                                    gs_plugin_flatpak_progress_cb, app,
+                                                    cancellable, error);
+               if (xref2 == NULL)
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refine_item_origin_ui:
+ */
+static gboolean
+gs_plugin_refine_item_origin_ui (GsPlugin *plugin,
+                                GsApp *app,
+                                GCancellable *cancellable,
+                                GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       const gchar *origin;
+       guint i;
+       g_autoptr(GPtrArray) xremotes = NULL;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* already set */
+       origin = gs_app_get_origin_ui (app);
+       if (origin != NULL)
+               return TRUE;
+
+       /* find list of remotes */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "flatpak::refine-origin-ui");
+       xremotes = flatpak_installation_list_remotes (priv->installation,
+                                                     cancellable,
+                                                     error);
+       if (xremotes == NULL)
+               return FALSE;
+       for (i = 0; i < xremotes->len; i++) {
+               FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+               if (g_strcmp0 (gs_app_get_origin (app),
+                              flatpak_remote_get_name (xremote)) == 0) {
+                       gs_app_set_origin_ui (app, flatpak_remote_get_title (xremote));
+                       break;
+               }
+       }
+
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refine_item_origin:
+ */
+static gboolean
+gs_plugin_refine_item_origin (GsPlugin *plugin,
+                             GsApp *app,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       guint i;
+       g_autoptr(GPtrArray) xremotes = NULL;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* already set */
+       if (gs_app_get_origin (app) != NULL)
+               return TRUE;
+
+       /* ensure metadata exists */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "flatpak::refine-origin");
+       if (!gs_plugin_refine_item_metadata (plugin, app, cancellable, error))
+               return FALSE;
+
+       /* find list of remotes */
+       g_debug ("looking for a remote for %s/%s/%s",
+                gs_app_get_flatpak_name (app),
+                gs_app_get_flatpak_arch (app),
+                gs_app_get_flatpak_branch (app));
+       xremotes = flatpak_installation_list_remotes (priv->installation,
+                                                     cancellable,
+                                                     error);
+       if (xremotes == NULL)
+               return FALSE;
+       for (i = 0; i < xremotes->len; i++) {
+               const gchar *remote_name;
+               FlatpakRemote *xremote = g_ptr_array_index (xremotes, i);
+               g_autoptr(FlatpakRemoteRef) xref = NULL;
+               remote_name = flatpak_remote_get_name (xremote);
+               g_debug ("looking at remote %s", remote_name);
+               xref = flatpak_installation_fetch_remote_ref_sync (priv->installation,
+                                                                  remote_name,
+                                                                  gs_app_get_flatpak_kind (app),
+                                                                  gs_app_get_flatpak_name (app),
+                                                                  gs_app_get_flatpak_arch (app),
+                                                                  gs_app_get_flatpak_branch (app),
+                                                                  cancellable,
+                                                                  NULL);
+               if (xref != NULL) {
+                       g_debug ("found remote %s", remote_name);
+                       gs_app_set_origin (app, remote_name);
+                       return TRUE;
+               }
+       }
+       g_set_error (error,
+                    GS_PLUGIN_ERROR,
+                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                    "Not found %s/%s/%s",
+                    gs_app_get_flatpak_name (app),
+                    gs_app_get_flatpak_arch (app),
+                    gs_app_get_flatpak_branch (app));
+       return FALSE;
+}
+
+/**
+ * gs_plugin_flatpak_app_matches_xref:
+ */
+static gboolean
+gs_plugin_flatpak_app_matches_xref (GsPlugin *plugin, GsApp *app, FlatpakRef *xref)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autofree gchar *id = NULL;
+
+       /* check ID */
+       id = gs_plugin_flatpak_build_id (priv->installation, xref);
+       if (g_strcmp0 (id, gs_app_get_id (app)) == 0)
+               return TRUE;
+
+       /* do all the metadata items match? */
+       if (g_strcmp0 (gs_app_get_flatpak_name (app),
+                      flatpak_ref_get_name (xref)) == 0 &&
+           g_strcmp0 (gs_app_get_flatpak_arch (app),
+                      flatpak_ref_get_arch (xref)) == 0 &&
+           g_strcmp0 (gs_app_get_flatpak_branch (app),
+                      flatpak_ref_get_branch (xref)) == 0)
+               return TRUE;
+
+       /* sad panda */
+       return FALSE;
+}
+
+/**
+ * gs_plugin_flatpak_create_fake_ref:
+ */
+static FlatpakRef *
+gs_plugin_flatpak_create_fake_ref (GsApp *app, GError **error)
+{
+       g_autofree gchar *id = NULL;
+       id = g_strdup_printf ("%s/%s/%s/%s",
+                             gs_app_get_flatpak_kind_as_str (app),
+                             gs_app_get_flatpak_name (app),
+                             gs_app_get_flatpak_arch (app),
+                             gs_app_get_flatpak_branch (app));
+       return flatpak_ref_parse (id, error);
+}
+
+/**
+ * gs_plugin_refine_item_metadata:
+ */
+static gboolean
+gs_plugin_refine_item_metadata (GsPlugin *plugin,
+                               GsApp *app,
+                               GCancellable *cancellable,
+                               GError **error)
+{
+       g_autoptr(FlatpakRef) xref = NULL;
+
+       /* already set */
+       if (gs_app_get_metadata_item (app, "flatpak::kind") != NULL)
+               return TRUE;
+
+       /* not a valid type */
+       if (gs_app_get_kind (app) == AS_APP_KIND_SOURCE)
+               return TRUE;
+
+       /* AppStream sets the source to appname/arch/branch, if this isn't set
+        * we can't break out the fields */
+       if (gs_app_get_source_default (app) == NULL) {
+               g_autofree gchar *tmp = gs_app_to_string (app);
+               g_warning ("no source set by appstream for %s: %s",
+                          gs_plugin_get_name (plugin), tmp);
+               return TRUE;
+       }
+
+       /* parse the ref */
+       xref = flatpak_ref_parse (gs_app_get_source_default (app), error);
+       if (xref == NULL) {
+               g_prefix_error (error, "failed to parse '%s': ",
+                               gs_app_get_source_default (app));
+               return FALSE;
+       }
+       gs_plugin_flatpak_set_metadata (app, xref);
+
+       /* success */
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refine_item_state:
+ */
+static gboolean
+gs_plugin_refine_item_state (GsPlugin *plugin,
+                             GsApp *app,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       guint i;
+       g_autoptr(GPtrArray) xrefs = NULL;
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* already found */
+       if (gs_app_get_state (app) != AS_APP_STATE_UNKNOWN)
+               return TRUE;
+
+       /* need broken out metadata */
+       if (!gs_plugin_refine_item_metadata (plugin, app, cancellable, error))
+               return FALSE;
+
+       /* get apps and runtimes */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "flatpak::refine-action");
+       xrefs = flatpak_installation_list_installed_refs (priv->installation,
+                                                         cancellable, error);
+       if (xrefs == NULL)
+               return FALSE;
+       for (i = 0; i < xrefs->len; i++) {
+               FlatpakInstalledRef *xref = g_ptr_array_index (xrefs, i);
+
+               /* check xref is app */
+               if (!gs_plugin_flatpak_app_matches_xref (plugin, app, FLATPAK_REF(xref)))
+                       continue;
+
+               /* mark as installed */
+               g_debug ("marking %s as installed with flatpak",
+                        gs_app_get_id (app));
+               gs_plugin_flatpak_set_metadata_installed (app, xref);
+               if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       }
+
+       /* ensure origin set */
+       if (!gs_plugin_refine_item_origin (plugin, app, cancellable, 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) {
+               g_autoptr(FlatpakRemote) xremote = NULL;
+               xremote = flatpak_installation_get_remote_by_name (priv->installation,
+                                                                  gs_app_get_origin (app),
+                                                                  cancellable, NULL);
+               if (xremote != NULL) {
+                       g_debug ("marking %s as available with flatpak",
+                                gs_app_get_id (app));
+                       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+               } else {
+                       g_warning ("failed to find flatpak remote %s for %s",
+                                  gs_app_get_origin (app),
+                                  gs_app_get_id (app));
+               }
+       }
+
+       /* success */
+       return TRUE;
+}
+
+/**
+ * gs_plugin_flatpak_set_app_metadata:
+ */
+static gboolean
+gs_plugin_flatpak_set_app_metadata (GsApp *app,
+                                   const gchar *data,
+                                   gsize length,
+                                   GError **error)
+{
+       g_autofree gchar *name = NULL;
+       g_autofree gchar *runtime = NULL;
+       g_autofree gchar *source = NULL;
+       g_autoptr(GKeyFile) kf = NULL;
+       g_autoptr(GsApp) app_runtime = NULL;
+
+       kf = g_key_file_new ();
+       if (!g_key_file_load_from_data (kf, data, length, G_KEY_FILE_NONE, error))
+               return FALSE;
+       name = g_key_file_get_string (kf, "Application", "name", error);
+       if (name == NULL)
+               return FALSE;
+       gs_app_set_flatpak_name (app, name);
+       runtime = g_key_file_get_string (kf, "Application", "runtime", error);
+       if (runtime == NULL)
+               return FALSE;
+       g_debug ("runtime for %s is %s", name, runtime);
+
+       /* create runtime */
+       app_runtime = gs_appstream_create_runtime (app, runtime);
+       if (app_runtime != NULL)
+               gs_app_set_runtime (app, app_runtime);
+
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refine_item_runtime:
+ */
+static gboolean
+gs_plugin_refine_item_runtime (GsPlugin *plugin,
+                              GsApp *app,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       const gchar *str;
+       gsize len = -1;
+       g_autofree gchar *contents = NULL;
+       g_autofree gchar *installation_path_str = NULL;
+       g_autofree gchar *install_path = NULL;
+       g_autoptr(GBytes) data = NULL;
+       g_autoptr(GFile) installation_path = NULL;
+
+       /* not applicable */
+       if (gs_app_get_flatpak_kind (app) != FLATPAK_REF_KIND_APP)
+               return TRUE;
+
+       /* already exists */
+       if (gs_app_get_runtime (app) != NULL)
+               return TRUE;
+
+       /* this is quicker than doing network IO */
+       installation_path = flatpak_installation_get_path (priv->installation);
+       installation_path_str = g_file_get_path (installation_path);
+       install_path = g_build_filename (installation_path_str,
+                                        gs_app_get_flatpak_kind_as_str (app),
+                                        gs_app_get_flatpak_name (app),
+                                        gs_app_get_flatpak_arch (app),
+                                        gs_app_get_flatpak_branch (app),
+                                        "active",
+                                        "metadata",
+                                        NULL);
+       if (g_file_test (install_path, G_FILE_TEST_EXISTS)) {
+               if (!g_file_get_contents (install_path, &contents, &len, error))
+                       return FALSE;
+               str = contents;
+       } else {
+               g_autoptr(FlatpakRef) xref = NULL;
+
+               /* fetch from the server */
+               xref = gs_plugin_flatpak_create_fake_ref (app, error);
+               if (xref == NULL)
+                       return FALSE;
+               data = flatpak_installation_fetch_remote_metadata_sync (priv->installation,
+                                                                       gs_app_get_origin (app),
+                                                                       xref,
+                                                                       cancellable,
+                                                                       error);
+               if (data == NULL)
+                       return FALSE;
+               str = g_bytes_get_data (data, &len);
+       }
+
+       /* parse key file */
+       if (!gs_plugin_flatpak_set_app_metadata (app, str, len, error))
+               return FALSE;
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refine_item_size:
+ */
+static gboolean
+gs_plugin_refine_item_size (GsPlugin *plugin,
+                           GsApp *app,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       gboolean ret;
+       guint64 download_size;
+       guint64 installed_size;
+       g_autoptr(AsProfileTask) ptask = NULL;
+       g_autoptr(FlatpakRef) xref = NULL;
+       g_autoptr(GError) error_local = NULL;
+
+       /* already set */
+       if (gs_app_get_size_installed (app) > 0 &&
+           gs_app_get_size_download (app) > 0)
+               return TRUE;
+
+       /* need runtime */
+       if (!gs_plugin_refine_item_runtime (plugin, app, cancellable, error))
+               return FALSE;
+
+       /* calculate the platform size too if the app is not installed */
+       if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE &&
+           gs_app_get_flatpak_kind (app) == FLATPAK_REF_KIND_APP) {
+               GsApp *app_runtime;
+
+               /* find out what runtime the application depends on */
+               if (!gs_plugin_refine_item_runtime (plugin,
+                                                   app,
+                                                   cancellable,
+                                                   error))
+                       return FALSE;
+
+               /* is the app_runtime already installed? */
+               app_runtime = gs_app_get_runtime (app);
+               if (!gs_plugin_refine_item_state (plugin,
+                                                 app_runtime,
+                                                 cancellable,
+                                                 error))
+                       return FALSE;
+               if (gs_app_get_state (app_runtime) == AS_APP_STATE_INSTALLED) {
+                       g_debug ("runtime %s is already installed, so not adding size",
+                                gs_app_get_id (app_runtime));
+               } else {
+                       if (!gs_plugin_refine_item_size (plugin,
+                                                        app_runtime,
+                                                        cancellable,
+                                                        error))
+                               return FALSE;
+               }
+       }
+
+       /* just get the size of the app */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "flatpak::refine-size");
+       if (!gs_plugin_refine_item_origin (plugin, app, cancellable, error))
+               return FALSE;
+       xref = gs_plugin_flatpak_create_fake_ref (app, error);
+       if (xref == NULL)
+               return FALSE;
+       ret = flatpak_installation_fetch_remote_size_sync (priv->installation,
+                                                          gs_app_get_origin (app),
+                                                          xref,
+                                                          &download_size,
+                                                          &installed_size,
+                                                          cancellable, &error_local);
+       if (!ret) {
+               g_warning ("libflatpak failed to return application size: %s",
+                          error_local->message);
+               gs_app_set_size_installed (app, GS_APP_SIZE_UNKNOWABLE);
+               gs_app_set_size_download (app, GS_APP_SIZE_UNKNOWABLE);
+       } else {
+               gs_app_set_size_installed (app, installed_size);
+               gs_app_set_size_download (app, download_size);
+       }
+       return TRUE;
+}
+
+/**
+ * gs_plugin_flatpak_refine_app:
+ */
+static gboolean
+gs_plugin_flatpak_refine_app (GsPlugin *plugin,
+                             GsApp *app,
+                             GsPluginRefineFlags flags,
+                             GCancellable *cancellable,
+                             GError **error)
+{
+       g_autoptr(AsProfileTask) ptask = 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;
+
+       /* profile */
+       ptask = as_profile_start (gs_plugin_get_profile (plugin),
+                                 "flatpak::refine{%s}",
+                                 gs_app_get_id (app));
+
+       /* flatpak apps can always be removed */
+       gs_app_remove_quirk (app, AS_APP_QUIRK_COMPULSORY);
+
+       /* AppStream sets the source to appname/arch/branch */
+       if (!gs_plugin_refine_item_metadata (plugin, app, cancellable, error))
+               return FALSE;
+
+       /* check the installed state */
+       if (!gs_plugin_refine_item_state (plugin, app, cancellable, error))
+               return FALSE;
+
+       /* version fallback */
+       if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION) {
+               if (gs_app_get_version (app) == NULL) {
+                       const gchar *branch;
+                       branch = gs_app_get_flatpak_branch (app);
+                       gs_app_set_version (app, branch);
+               }
+       }
+
+       /* size */
+       if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE) {
+               if (!gs_plugin_refine_item_size (plugin, app, cancellable, error))
+                       return FALSE;
+       }
+
+       /* origin */
+       if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN) {
+               if (!gs_plugin_refine_item_origin_ui (plugin, app, cancellable, error))
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+/**
+ * gs_plugin_refine_app:
+ */
+gboolean
+gs_plugin_refine_app (GsPlugin *plugin,
+                     GsApp *app,
+                     GsPluginRefineFlags flags,
+                     GCancellable *cancellable,
+                     GError **error)
+{      return gs_plugin_flatpak_refine_app (plugin, app, flags, cancellable, error);
+}
+
+/**
+ * gs_plugin_launch:
+ */
+gboolean
+gs_plugin_launch (GsPlugin *plugin,
+                 GsApp *app,
+                 GCancellable *cancellable,
+                 GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       const gchar *branch = 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;
+
+       branch = gs_app_get_flatpak_branch (app);
+       if (branch == NULL)
+               branch = "master";
+       return flatpak_installation_launch (priv->installation,
+                                           gs_app_get_flatpak_name (app),
+                                           NULL,
+                                           branch,
+                                           NULL,
+                                           cancellable,
+                                           error);
+}
+
+/**
+ * gs_plugin_app_remove:
+ */
+gboolean
+gs_plugin_app_remove (GsPlugin *plugin,
+                     GsApp *app,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+
+       /* 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;
+
+       /* refine to get basics */
+       if (!gs_plugin_flatpak_refine_app (plugin, app,
+                                          GS_PLUGIN_REFINE_FLAGS_DEFAULT,
+                                          cancellable, error))
+               return FALSE;
+
+       /* remove */
+       gs_app_set_state (app, AS_APP_STATE_REMOVING);
+       if (!flatpak_installation_uninstall (priv->installation,
+                                            FLATPAK_REF_KIND_APP,
+                                            gs_app_get_flatpak_name (app),
+                                            gs_app_get_flatpak_arch (app),
+                                            gs_app_get_flatpak_branch (app),
+                                            gs_plugin_flatpak_progress_cb, app,
+                                            cancellable, 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, AS_APP_STATE_UNKNOWN);
+
+       /* refresh the state */
+       if (!gs_plugin_refine_item_state (plugin, app, cancellable, error))
+               return FALSE;
+
+       /* success */
+       return TRUE;
+}
+
+/**
+ * gs_plugin_app_install:
+ */
+gboolean
+gs_plugin_app_install (GsPlugin *plugin,
+                      GsApp *app,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autoptr(FlatpakInstalledRef) xref = 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;
+
+       /* ensure we have metadata and state */
+       if (!gs_plugin_flatpak_refine_app (plugin, app, 0, cancellable, error))
+               return FALSE;
+
+       /* install */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+
+       /* install required runtime if not already installed */
+       if (gs_app_get_kind (app) == AS_APP_KIND_DESKTOP) {
+               GsApp *runtime;
+               runtime = gs_app_get_runtime (app);
+
+               /* the runtime could come from a different remote to the app */
+               if (!gs_plugin_refine_item_metadata (plugin, runtime, cancellable, error))
+                       return FALSE;
+               if (!gs_plugin_refine_item_origin (plugin, runtime, cancellable, error))
+                       return FALSE;
+               if (!gs_plugin_refine_item_state (plugin, runtime, cancellable, error))
+                       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));
+                       return FALSE;
+               }
+
+               /* not installed */
+               if (gs_app_get_state (runtime) == AS_APP_STATE_AVAILABLE) {
+                       g_debug ("%s is not already installed, so installing",
+                                gs_app_get_id (runtime));
+                       gs_app_set_state (runtime, AS_APP_STATE_INSTALLING);
+                       xref = flatpak_installation_install (priv->installation,
+                                                            gs_app_get_origin (runtime),
+                                                            gs_app_get_flatpak_kind (runtime),
+                                                            gs_app_get_flatpak_name (runtime),
+                                                            gs_app_get_flatpak_arch (runtime),
+                                                            gs_app_get_flatpak_branch (runtime),
+                                                            gs_plugin_flatpak_progress_cb, app,
+                                                            cancellable, error);
+                       if (xref == NULL) {
+                               gs_app_set_state_recover (runtime);
+                               return FALSE;
+                       }
+                       gs_app_set_state (runtime, AS_APP_STATE_INSTALLED);
+               } else {
+                       g_debug ("%s is already installed, so skipping",
+                                gs_app_get_id (runtime));
+               }
+       }
+
+       /* use the source for local apps */
+       if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE_LOCAL) {
+               xref = flatpak_installation_install_bundle (priv->installation,
+                                                           gs_app_get_local_file (app),
+                                                           gs_plugin_flatpak_progress_cb,
+                                                           app,
+                                                           cancellable, error);
+       } else {
+               g_debug ("installing %s", gs_app_get_id (app));
+               xref = flatpak_installation_install (priv->installation,
+                                                    gs_app_get_origin (app),
+                                                    gs_app_get_flatpak_kind (app),
+                                                    gs_app_get_flatpak_name (app),
+                                                    gs_app_get_flatpak_arch (app),
+                                                    gs_app_get_flatpak_branch (app),
+                                                    gs_plugin_flatpak_progress_cb, app,
+                                                    cancellable, error);
+       }
+       if (xref == NULL) {
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
+
+       /* state is known */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       return TRUE;
+}
+
+/**
+ * gs_plugin_update_app:
+ */
+gboolean
+gs_plugin_update_app (GsPlugin *plugin,
+                     GsApp *app,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autoptr(FlatpakInstalledRef) xref = 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;
+
+       /* install */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+       xref = flatpak_installation_update (priv->installation,
+                                           FLATPAK_UPDATE_FLAGS_NONE,
+                                           gs_app_get_flatpak_kind (app),
+                                           gs_app_get_flatpak_name (app),
+                                           gs_app_get_flatpak_arch (app),
+                                           gs_app_get_flatpak_branch (app),
+                                           gs_plugin_flatpak_progress_cb, app,
+                                           cancellable, error);
+       if (xref == NULL) {
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
+
+       /* state is known */
+       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       return TRUE;
+}
+
+/**
+ * gs_plugin_file_to_app:
+ */
+gboolean
+gs_plugin_file_to_app (GsPlugin *plugin,
+                      GList **list,
+                      GFile *file,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       g_autofree gchar *content_type = NULL;
+       g_autofree gchar *id_prefixed = 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;
+       const gchar *mimetypes[] = {
+               "application/vnd.flatpak",
+               NULL };
+
+       /* does this match any of the mimetypes we support */
+       content_type = gs_utils_get_content_type (file, cancellable, error);
+       if (content_type == NULL)
+               return FALSE;
+       if (!g_strv_contains (mimetypes, content_type))
+               return TRUE;
+
+       /* load bundle */
+       xref_bundle = flatpak_bundle_ref_new (file, error);
+       if (xref_bundle == NULL) {
+               g_prefix_error (error, "error loading bundle: ");
+               return FALSE;
+       }
+
+       /* create a virtual ID */
+       id_prefixed = gs_plugin_flatpak_build_id (priv->installation,
+                                                 FLATPAK_REF (xref_bundle));
+
+       /* load metadata */
+       app = gs_app_new (id_prefixed);
+       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_plugin_flatpak_set_metadata (app, FLATPAK_REF (xref_bundle));
+       metadata = flatpak_bundle_ref_get_metadata (xref_bundle);
+       if (!gs_plugin_flatpak_set_app_metadata (app,
+                                                g_bytes_get_data (metadata, NULL),
+                                                g_bytes_get_size (metadata),
+                                                error))
+               return FALSE;
+
+       /* load AppStream */
+       appstream_gz = flatpak_bundle_ref_get_appstream (xref_bundle);
+       if (appstream_gz != NULL) {
+               g_autoptr(GZlibDecompressor) decompressor = NULL;
+               g_autoptr(GInputStream) stream_gz = NULL;
+               g_autoptr(GInputStream) stream_data = NULL;
+               g_autoptr(GBytes) appstream = NULL;
+               g_autoptr(AsStore) store = NULL;
+               g_autofree gchar *id = NULL;
+               AsApp *item;
+
+               /* decompress data */
+               decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP);
+               stream_gz = g_memory_input_stream_new_from_bytes (appstream_gz);
+               if (stream_gz == NULL)
+                       return FALSE;
+               stream_data = g_converter_input_stream_new (stream_gz,
+                                                           G_CONVERTER (decompressor));
+
+               appstream = g_input_stream_read_bytes (stream_data,
+                                                      0x100000, /* 1Mb */
+                                                      cancellable,
+                                                      error);
+               if (appstream == NULL)
+                       return FALSE;
+               store = as_store_new ();
+               if (!as_store_from_bytes (store, appstream, cancellable, error))
+                       return FALSE;
+
+               /* find app */
+               id = g_strdup_printf ("%s.desktop", gs_app_get_flatpak_name (app));
+               item = as_store_get_app_by_id (store, id);
+               if (item == NULL) {
+                       g_set_error (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_FAILED,
+                                    "application %s not found",
+                                    id);
+                       return FALSE;
+               }
+
+               /* copy details from AppStream to app */
+               if (!gs_appstream_refine_app (plugin, app, item, error))
+                       return FALSE;
+       }
+
+       /* load icon */
+       icon_data = flatpak_bundle_ref_get_icon (xref_bundle,
+                                                64 * gs_plugin_get_scale (plugin));
+       if (icon_data == NULL)
+               icon_data = flatpak_bundle_ref_get_icon (xref_bundle, 64);
+       if (icon_data != NULL) {
+               g_autoptr(GInputStream) stream_icon = NULL;
+               g_autoptr(GdkPixbuf) pixbuf = NULL;
+               stream_icon = g_memory_input_stream_new_from_bytes (icon_data);
+               pixbuf = gdk_pixbuf_new_from_stream (stream_icon, cancellable, error);
+               if (pixbuf == NULL)
+                       return FALSE;
+               gs_app_set_pixbuf (app, pixbuf);
+       } else {
+               g_autoptr(AsIcon) icon = NULL;
+               icon = as_icon_new ();
+               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
+               as_icon_set_name (icon, "application-x-executable");
+               gs_app_set_icon (app, icon);
+       }
+
+       /* not quite true: this just means we can update this specific app */
+       if (flatpak_bundle_ref_get_origin (xref_bundle))
+               gs_app_add_quirk (app, AS_APP_QUIRK_HAS_SOURCE);
+
+       g_debug ("created local app: %s", gs_app_to_string (app));
+       gs_app_list_add (list, app);
+       return TRUE;
+}
diff --git a/src/plugins/gs-plugin-odrs.c b/src/plugins/gs-plugin-odrs.c
index 40f5ac8..86d8f5c 100644
--- a/src/plugins/gs-plugin-odrs.c
+++ b/src/plugins/gs-plugin-odrs.c
@@ -78,6 +78,7 @@ gs_plugin_order_after (GsPlugin *plugin)
        static const gchar *deps[] = {
                "appstream",    /* need application IDs */
                "xdg-app",      /* need version */
+               "flatpak",      /* need version */
                NULL };
        return deps;
 }


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