[gnome-software/wip/jrocha/fix-runtime-extensions-update: 5/6] flatpak: Set apps as updatable instead of their extensions



commit 4da37db6562b346316f52fd9d65f146dcd9c9b2a
Author: Joaquim Rocha <jrocha endlessm com>
Date:   Mon Nov 6 17:47:55 2017 +0100

    flatpak: Set apps as updatable instead of their extensions
    
    Some apps have extensions that are installed/removed together with them.
    Those extensions can be e.g. content packs, locales, etc. and thus
    shouldn't be shown as single updates, instead their main app should be
    shown as updatable itself. Then, upon calling update on the app, it will
    try to update itself (though nothing will occur if there's no update
    available) and its extensions (which is the point of this disguised
    update).
    
    These changes implement just that: whenever an extension has an update,
    don't add it to the list of updates but instead add its main app.

 plugins/flatpak/gs-flatpak-app.c          |  12 +++
 plugins/flatpak/gs-flatpak-app.h          |   3 +
 plugins/flatpak/gs-flatpak.c              | 132 +++++++++++++++++++++++++++---
 plugins/flatpak/gs-self-test.c            | 102 +++++++++++++++++++++++
 plugins/flatpak/tests/build.py            |   5 ++
 plugins/flatpak/tests/flatpakrepos.tar.gz | Bin 43104 -> 56538 bytes
 6 files changed, 243 insertions(+), 11 deletions(-)
---
diff --git a/plugins/flatpak/gs-flatpak-app.c b/plugins/flatpak/gs-flatpak-app.c
index 2f2638f7..69b3a0dd 100644
--- a/plugins/flatpak/gs-flatpak-app.c
+++ b/plugins/flatpak/gs-flatpak-app.c
@@ -183,4 +183,16 @@ gs_flatpak_app_new (const gchar *id)
        return GS_APP (g_object_new (GS_TYPE_APP, "id", id, NULL));
 }
 
+void
+gs_flatpak_app_set_main_app_ref_name (GsApp *app, const gchar *main_app_ref)
+{
+       gs_app_set_metadata (app, "flatpak::mainApp", main_app_ref);
+}
+
+const gchar *
+gs_flatpak_app_get_main_app_ref_name (GsApp *app)
+{
+       return gs_app_get_metadata_item (app, "flatpak::mainApp");
+}
+
 /* vim: set noexpandtab: */
diff --git a/plugins/flatpak/gs-flatpak-app.h b/plugins/flatpak/gs-flatpak-app.h
index 6e258c18..e44896ee 100644
--- a/plugins/flatpak/gs-flatpak-app.h
+++ b/plugins/flatpak/gs-flatpak-app.h
@@ -72,6 +72,9 @@ 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_main_app_ref_name   (GsApp          *app,
+                                                                const gchar    *main_app_ref);
+const gchar            *gs_flatpak_app_get_main_app_ref_name   (GsApp          *app);
 
 G_END_DECLS
 
diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c
index 2cb6b698..c6dc4b3a 100644
--- a/plugins/flatpak/gs-flatpak.c
+++ b/plugins/flatpak/gs-flatpak.c
@@ -778,6 +778,25 @@ gs_flatpak_set_metadata_installed (GsFlatpak *self, GsApp *app,
                gs_app_set_install_date (app, mtime);
        }
 
+       /* if it's a runtime, check if the main-app info should be set */
+       if (gs_app_get_kind (app) == AS_APP_KIND_RUNTIME &&
+           gs_flatpak_app_get_main_app_ref_name (app) == NULL) {
+               g_autoptr(GError) error = NULL;
+               g_autoptr(GKeyFile) metadata_file = NULL;
+               metadata_file = g_key_file_new ();
+               if (g_key_file_load_from_file (metadata_file, metadata_fn,
+                                              G_KEY_FILE_NONE, &error)) {
+                       g_autofree gchar *main_app = g_key_file_get_string (metadata_file,
+                                                                           "ExtensionOf",
+                                                                           "ref", NULL);
+                       if (main_app != NULL)
+                               gs_flatpak_app_set_main_app_ref_name (app, main_app);
+               } else {
+                       g_warning ("Error loading the metadata file for '%s': %s",
+                                  gs_app_get_unique_id (app), error->message);
+               }
+       }
+
        /* this is faster than resolving */
        if (gs_app_get_origin (app) == NULL)
                gs_app_set_origin (app, flatpak_installed_ref_get_origin (xref));
@@ -1109,6 +1128,85 @@ gs_flatpak_app_install_source (GsFlatpak *self, GsApp *app,
        return TRUE;
 }
 
+static GsApp *
+get_main_app_of_related (GsFlatpak *self,
+                        GsApp *related_app,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       g_autofree gchar *main_app_name = g_strdup (gs_flatpak_app_get_ref_name (related_app));
+       g_autoptr(FlatpakInstalledRef) ref = NULL;
+       const gchar *ref_name;
+       g_auto(GStrv) app_tokens = NULL;
+
+       ref_name = gs_flatpak_app_get_main_app_ref_name (related_app);
+       if (ref_name == NULL) {
+               g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+                            "%s doesn't have a main app set to it.",
+                            gs_app_get_unique_id (related_app));
+               return NULL;
+       }
+
+       app_tokens = g_strsplit (ref_name, "/", -1);
+       if (g_strv_length (app_tokens) != 4) {
+               g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+                            "The main app of %s has an invalid name: %s",
+                            gs_app_get_unique_id (related_app), ref_name);
+               return NULL;
+       }
+
+       /* this function only returns G_IO_ERROR_NOT_FOUND when the metadata file
+        * is missing, but if that's the case then things should have broken before
+        * this point */
+       ref = flatpak_installation_get_installed_ref (self->installation,
+                                                     FLATPAK_REF_KIND_APP,
+                                                     app_tokens[1],
+                                                     app_tokens[2],
+                                                     app_tokens[3],
+                                                     cancellable,
+                                                     error);
+
+       if (ref == NULL)
+               return NULL;
+
+       return gs_flatpak_create_installed (self, ref, error);
+}
+
+static GsApp *
+get_real_app_for_update (GsFlatpak *self,
+                        GsApp *app,
+                        GCancellable *cancellable,
+                        GError **error)
+{
+       GsApp *main_app = NULL;
+       g_autoptr(GError) error_local = NULL;
+
+       if (gs_app_get_kind (app) == AS_APP_KIND_RUNTIME)
+               main_app = get_main_app_of_related (self, app, cancellable, &error_local);
+
+       if (main_app == NULL) {
+               /* not all runtimes are extensions, and in that case we get the
+                * not-found error, so we only report other types of errors */
+               if (error_local != NULL &&
+                   !g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+                       g_warning ("Couldn't get the main app for related app %s: %s",
+                                  gs_app_get_unique_id (app),
+                                  error_local->message);
+                       g_propagate_error (error, g_steal_pointer (&error_local));
+                       return NULL;
+               }
+
+               main_app = g_object_ref (app);
+       } else {
+               g_debug ("Related extension app %s of main app %s is updatable, so "
+                        "setting the latter's state instead.", gs_app_get_unique_id (app),
+                        gs_app_get_unique_id (main_app));
+               gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
+       }
+
+       return main_app;
+}
+
 gboolean
 gs_flatpak_add_updates (GsFlatpak *self, GsAppList *list,
                        GCancellable *cancellable,
@@ -1132,6 +1230,7 @@ gs_flatpak_add_updates (GsFlatpak *self, GsAppList *list,
                const gchar *latest_commit;
                g_autoptr(GsApp) app = NULL;
                g_autoptr(GError) error_local = NULL;
+               g_autoptr(GsApp) main_app = NULL;
 
                /* check the application has already been downloaded */
                commit = flatpak_ref_get_commit (FLATPAK_REF (xref));
@@ -1156,12 +1255,17 @@ gs_flatpak_add_updates (GsFlatpak *self, GsAppList *list,
                        g_warning ("failed to add flatpak: %s", error_local->message);
                        continue;
                }
-               gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
-               gs_app_set_update_details (app, NULL);
-               gs_app_set_update_version (app, NULL);
-               gs_app_set_update_urgency (app, AS_URGENCY_KIND_UNKNOWN);
-               gs_app_set_size_download (app, 0);
-               gs_app_list_add (list, app);
+
+               main_app = get_real_app_for_update (self, app, cancellable, error);
+               if (main_app == NULL)
+                       return FALSE;
+
+               gs_app_set_state (main_app, AS_APP_STATE_UPDATABLE_LIVE);
+               gs_app_set_update_details (main_app, NULL);
+               gs_app_set_update_version (main_app, NULL);
+               gs_app_set_update_urgency (main_app, AS_URGENCY_KIND_UNKNOWN);
+               gs_app_set_size_download (main_app, 0);
+               gs_app_list_add (list, main_app);
        }
 
        /* success */
@@ -1188,6 +1292,7 @@ gs_flatpak_add_updates_pending (GsFlatpak *self, GsAppList *list,
                guint64 download_size = 0;
                g_autoptr(GsApp) app = NULL;
                g_autoptr(GError) error_local = NULL;
+               g_autoptr(GsApp) main_app = NULL;
 
                /* we have an update to show */
                g_debug ("%s has update", flatpak_ref_get_name (FLATPAK_REF (xref)));
@@ -1196,10 +1301,15 @@ gs_flatpak_add_updates_pending (GsFlatpak *self, GsAppList *list,
                        g_warning ("failed to add flatpak: %s", error_local->message);
                        continue;
                }
-               gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
+
+               main_app = get_real_app_for_update (self, app, cancellable, error);
+               if (main_app == NULL)
+                       return FALSE;
+
+               gs_app_set_state (main_app, AS_APP_STATE_UPDATABLE_LIVE);
 
                /* get the current download size */
-               if (gs_app_get_size_download (app) == 0) {
+               if (gs_app_get_size_download (main_app) == 0) {
                        if (!flatpak_installation_fetch_remote_size_sync (self->installation,
                                                                          gs_app_get_origin (app),
                                                                          FLATPAK_REF (xref),
@@ -1209,13 +1319,13 @@ gs_flatpak_add_updates_pending (GsFlatpak *self, GsAppList *list,
                                                                          &error_local)) {
                                g_warning ("failed to get download size: %s",
                                           error_local->message);
-                               gs_app_set_size_download (app, GS_APP_SIZE_UNKNOWABLE);
+                               gs_app_set_size_download (main_app, GS_APP_SIZE_UNKNOWABLE);
                        } else {
-                               gs_app_set_size_download (app, download_size);
+                               gs_app_set_size_download (main_app, download_size);
                        }
                }
 
-               gs_app_list_add (list, app);
+               gs_app_list_add (list, main_app);
        }
 
        /* success */
diff --git a/plugins/flatpak/gs-self-test.c b/plugins/flatpak/gs-self-test.c
index a3d1038f..a470b445 100644
--- a/plugins/flatpak/gs-self-test.c
+++ b/plugins/flatpak/gs-self-test.c
@@ -1466,8 +1466,18 @@ gs_plugins_flatpak_runtime_extension_func (GsPluginLoader *plugin_loader)
 {
        GsApp *app;
        GsApp *extension;
+       GsApp *app_tmp;
+       gboolean got_progress_installing = FALSE;
        gboolean ret;
+       guint notify_progress_id;
+       guint notify_state_id;
+       guint pending_app_changed_cnt = 0;
+       guint pending_apps_changed_id;
+       guint progress_cnt = 0;
+       guint updates_changed_cnt = 0;
+       guint updates_changed_id;
        g_autofree gchar *repodir1_fn = NULL;
+       g_autofree gchar *repodir2_fn = NULL;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsApp) app_source = NULL;
        g_autoptr(GsAppList) list = NULL;
@@ -1489,6 +1499,12 @@ gs_plugins_flatpak_runtime_extension_func (GsPluginLoader *plugin_loader)
                g_test_skip ("no flatpak test repo");
                return;
        }
+       repodir2_fn = gs_test_get_filename (TESTDATADIR, "app-extension-update/repo");
+       if (repodir2_fn == NULL ||
+           !g_file_test (repodir2_fn, G_FILE_TEST_EXISTS)) {
+               g_test_skip ("no flatpak test repo");
+               return;
+       }
 
        /* add indirection so we can switch this after install */
        g_assert_cmpint (symlink (repodir1_fn, "/var/tmp/self-test/repo"), ==, 0);
@@ -1554,6 +1570,92 @@ gs_plugins_flatpak_runtime_extension_func (GsPluginLoader *plugin_loader)
        g_assert_nonnull (extension);
        g_assert_cmpint (gs_app_get_state (extension), ==, AS_APP_STATE_INSTALLED);
 
+       /* switch to the new repo (to get the update) */
+       g_assert_cmpint (unlink ("/var/tmp/self-test/repo"), ==, 0);
+       g_assert_cmpint (symlink (repodir2_fn, "/var/tmp/self-test/repo"), ==, 0);
+
+       /* refresh the appstream metadata */
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                        "age", (guint64) 0, /* force now */
+                                        "refresh-flags", GS_PLUGIN_REFRESH_FLAGS_METADATA |
+                                                         GS_PLUGIN_REFRESH_FLAGS_PAYLOAD,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
+       g_assert_no_error (error);
+       g_assert_true (ret);
+
+       /* get the updates list */
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS,
+                                        NULL);
+       list_updates = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert_nonnull (list_updates);
+
+       g_assert_cmpint (gs_app_list_length (list_updates), ==, 1);
+       for (guint i = 0; i < gs_app_list_length (list_updates); i++) {
+               app_tmp = gs_app_list_index (list_updates, i);
+               g_debug ("got update %s", gs_app_get_unique_id (app_tmp));
+       }
+
+       /* check that the extension has no update */
+       app_tmp = gs_app_list_lookup (list_updates, "*/flatpak/test/*/org.test.Chiron.Extension/*");
+       g_assert_null (app_tmp);
+
+       /* check that the app has an update (it's affected by the extension's update) */
+       app_tmp = gs_app_list_lookup (list_updates, "*/flatpak/test/*/org.test.Chiron.desktop/*");
+       g_assert (app_tmp == app);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE_LIVE);
+
+       /* care about signals */
+       pending_apps_changed_id =
+               g_signal_connect (plugin_loader, "pending-apps-changed",
+                                 G_CALLBACK (gs_plugins_flatpak_count_signal_cb),
+                                 &pending_app_changed_cnt);
+       updates_changed_id =
+               g_signal_connect (plugin_loader, "updates-changed",
+                                 G_CALLBACK (gs_plugins_flatpak_count_signal_cb),
+                                 &updates_changed_cnt);
+       notify_state_id =
+               g_signal_connect (app, "notify::state",
+                                 G_CALLBACK (update_app_state_notify_cb),
+                                 &got_progress_installing);
+       notify_progress_id =
+               g_signal_connect (app, "notify::progress",
+                                 G_CALLBACK (update_app_progress_notify_cb),
+                                 &progress_cnt);
+
+       /* use a mainloop so we get the events in the default context */
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE,
+                                        "app", app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
+                                                         GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
+                                        NULL);
+       gs_plugin_loader_job_process_async (plugin_loader, plugin_job,
+                                           NULL,
+                                           update_app_action_finish_sync,
+                                           loop);
+       g_main_loop_run (loop);
+       gs_test_flush_main_context ();
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
+       g_assert_cmpstr (gs_app_get_version (app), ==, "1.2.3");
+       g_assert_true (got_progress_installing);
+       g_assert_cmpint (pending_app_changed_cnt, ==, 0);
+
+       /* check the extension's state after the update */
+       g_assert_cmpint (gs_app_get_state (extension), ==, AS_APP_STATE_INSTALLED);
+
+       /* no longer care */
+       g_signal_handler_disconnect (plugin_loader, pending_apps_changed_id);
+       g_signal_handler_disconnect (plugin_loader, updates_changed_id);
+       g_signal_handler_disconnect (app, notify_state_id);
+       g_signal_handler_disconnect (app, notify_progress_id);
+
        /* remove the app */
        g_object_unref (plugin_job);
        plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
diff --git a/plugins/flatpak/tests/build.py b/plugins/flatpak/tests/build.py
index 3db9e8af..992ba18a 100755
--- a/plugins/flatpak/tests/build.py
+++ b/plugins/flatpak/tests/build.py
@@ -105,3 +105,8 @@ build_flatpak('org.test.Chiron.Extension',
               'app-extension',
               'app-extension/repo',
               cleanrepodir=False)
+copy_repo('app-extension', 'app-extension-update')
+build_flatpak('org.test.Chiron.Extension',
+              'app-extension-update',
+              'app-extension-update/repo',
+              cleanrepodir=False)
diff --git a/plugins/flatpak/tests/flatpakrepos.tar.gz b/plugins/flatpak/tests/flatpakrepos.tar.gz
index 8ed3adf0..7e750e20 100644
Binary files a/plugins/flatpak/tests/flatpakrepos.tar.gz and b/plugins/flatpak/tests/flatpakrepos.tar.gz 
differ


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