[gnome-software/1476-add-a-way-for-app-developers-to-test-their-metainfo-appdata-files: 20/20] application: Add '--show-metainfo' command line argument




commit 3b29e6cf6bf7d4a80ca781efd7fbf4860c9999e7
Author: Milan Crha <mcrha redhat com>
Date:   Thu Oct 14 12:27:05 2021 +0200

    application: Add '--show-metainfo' command line argument
    
    This can be used to view an appstream data file how it'll be shown
    in the Software, without a need to install it and search for it.
    
    Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1476

 README.md             |   9 ++++
 lib/gs-appstream.c    |   3 +-
 src/gs-application.c  |  30 +++++++++++
 src/gs-details-page.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++++--
 src/gs-details-page.h |   2 +
 src/gs-shell.c        |  33 +++++++++++-
 src/gs-shell.h        |   2 +
 7 files changed, 217 insertions(+), 6 deletions(-)
---
diff --git a/README.md b/README.md
index 15ab528b3..4c40f86bf 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,15 @@ Bug reports and merge requests should be filed on [GNOME GitLab](https://gitlab.
 
 For development discussion, join us on `#gnome-software` on 
[irc.gnome.org](https://wiki.gnome.org/Community/GettingInTouch/IRC).
 
+# Testing a metainfo/appdata file of an application
+
+If you are developing an application, and want to see how your under-development
+metainfo file will appear when it’s eventually published into GNOME Software,
+run:
+```
+$ gnome-software --show-metainfo=./path/to/your/org.example.App.appdata.xml.in
+```
+
 # Running a nightly build
 
 A [flatpak bundle](https://docs.flatpak.org/en/latest/single-file-bundles.html)
diff --git a/lib/gs-appstream.c b/lib/gs-appstream.c
index 74be46837..2a311cf9d 100644
--- a/lib/gs-appstream.c
+++ b/lib/gs-appstream.c
@@ -28,7 +28,8 @@ gs_appstream_create_app (GsPlugin *plugin, XbSilo *silo, XbNode *component, GErr
                                      error))
                return NULL;
 
-       /* never add wildcard apps to the plugin cache */
+       /* never add wildcard apps to the plugin cache, and only add to
+        * the cache if it’s available */
        if (gs_app_has_quirk (app_new, GS_APP_QUIRK_IS_WILDCARD) || plugin == NULL)
                return g_steal_pointer (&app_new);
 
diff --git a/src/gs-application.c b/src/gs-application.c
index 0f1728e3c..c9df3656c 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -140,6 +140,8 @@ gs_application_init (GsApplication *application)
                { "interaction", '\0', 0, G_OPTION_ARG_STRING, NULL,
                  _("The kind of interaction expected for this action: either "
                    "‘none’, ‘notify’, or ‘full’"), NULL },
+               { "show-metainfo", '\0', 0, G_OPTION_ARG_FILENAME, NULL,
+                 _("Show a local metainfo or appdata file"), _("FILENAME") },
                { "verbose", '\0', 0, G_OPTION_ARG_NONE, NULL,
                  _("Show verbose debugging information"), NULL },
                { "autoupdate", 0, 0, G_OPTION_ARG_NONE, NULL,
@@ -758,6 +760,23 @@ filename_activated (GSimpleAction *action,
        gs_shell_show_local_file (app->shell, file);
 }
 
+static void
+show_metainfo_activated (GSimpleAction *action,
+                        GVariant      *parameter,
+                        gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       const gchar *filename;
+       g_autoptr(GFile) file = NULL;
+
+       g_variant_get (parameter, "(^&ay)", &filename);
+
+       file = g_file_new_for_path (filename);
+
+       gs_shell_reset_state (app->shell);
+       gs_shell_show_metainfo (app->shell, file);
+}
+
 static void
 launch_activated (GSimpleAction *action,
                  GVariant      *parameter,
@@ -875,6 +894,7 @@ static GActionEntry actions_after_loading[] = {
        { "install", install_activated, "(su)", NULL, NULL },
        { "filename", filename_activated, "(s)", NULL, NULL },
        { "install-resources", install_resources_activated, "(sassss)", NULL, NULL },
+       { "show-metainfo", show_metainfo_activated, "(ay)", NULL, NULL },
        { "nop", NULL, NULL, NULL }
 };
 
@@ -1190,6 +1210,16 @@ gs_application_handle_local_options (GApplication *app, GVariantDict *options)
                                                "filename",
                                                g_variant_new ("(s)", absolute_filename));
                rc = 0;
+       } else if (g_variant_dict_lookup (options, "show-metainfo", "^&ay", &local_filename)) {
+               g_autoptr(GFile) file = NULL;
+               g_autofree gchar *absolute_filename = NULL;
+
+               file = g_file_new_for_path (local_filename);
+               absolute_filename = g_file_get_path (file);
+               g_action_group_activate_action (G_ACTION_GROUP (app),
+                                               "show-metainfo",
+                                               g_variant_new ("(^ay)", absolute_filename));
+               rc = 0;
        }
 
        return rc;
diff --git a/src/gs-details-page.c b/src/gs-details-page.c
index 7c52340a4..5d82881bb 100644
--- a/src/gs-details-page.c
+++ b/src/gs-details-page.c
@@ -14,6 +14,8 @@
 #include <string.h>
 #include <glib/gi18n.h>
 
+#include "lib/gs-appstream.h"
+
 #include "gs-common.h"
 #include "gs-utils.h"
 
@@ -1508,7 +1510,8 @@ _set_app (GsDetailsPage *self, GsApp *app)
 
 /* show the UI and do operations that should not block page load */
 static void
-gs_details_page_load_stage2 (GsDetailsPage *self)
+gs_details_page_load_stage2 (GsDetailsPage *self,
+                            gboolean continue_loading)
 {
        g_autofree gchar *tmp = NULL;
        g_autoptr(GsPluginJob) plugin_job1 = NULL;
@@ -1529,6 +1532,9 @@ gs_details_page_load_stage2 (GsDetailsPage *self)
        gs_details_page_refresh_all (self);
        gs_details_page_update_origin_button (self, FALSE);
 
+       if (!continue_loading)
+               return;
+
        /* if these tasks fail (e.g. because we have no networking) then it's
         * of no huge importance if we don't get the required data */
        plugin_job1 = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
@@ -1595,7 +1601,7 @@ gs_details_page_load_stage1_cb (GObject *source,
        }
 
        /* do 2nd stage refine */
-       gs_details_page_load_stage2 (self);
+       gs_details_page_load_stage2 (self, TRUE);
 }
 
 static void
@@ -1619,7 +1625,7 @@ gs_details_page_file_to_app_cb (GObject *source,
                GsApp *app = gs_app_list_index (list, 0);
                g_set_object (&self->app_local_file, app);
                _set_app (self, app);
-               gs_details_page_load_stage2 (self);
+               gs_details_page_load_stage2 (self, TRUE);
        }
 }
 
@@ -1643,7 +1649,7 @@ gs_details_page_url_to_app_cb (GObject *source,
        } else {
                GsApp *app = gs_app_list_index (list, 0);
                _set_app (self, app);
-               gs_details_page_load_stage2 (self);
+               gs_details_page_load_stage2 (self, TRUE);
        }
 }
 
@@ -2500,3 +2506,133 @@ gs_details_page_set_is_narrow (GsDetailsPage *self, gboolean is_narrow)
        self->is_narrow = is_narrow;
        g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_IS_NARROW]);
 }
+
+static void
+gs_details_page_metainfo_ready_cb (GObject *source_object,
+                                  GAsyncResult *result,
+                                  gpointer user_data)
+{
+       GsDetailsPage *self = GS_DETAILS_PAGE (source_object);
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GError) error = NULL;
+
+       app = g_task_propagate_pointer (G_TASK (result), &error);
+       if (error) {
+               gtk_label_set_text (GTK_LABEL (self->label_failed), error->message);
+               gs_details_page_set_state (self, GS_DETAILS_PAGE_STATE_FAILED);
+               return;
+       }
+
+       g_set_object (&self->app_local_file, app);
+       _set_app (self, app);
+       gs_details_page_load_stage2 (self, FALSE);
+}
+
+static void
+gs_details_page_metainfo_thread (GTask *task,
+                                gpointer source_object,
+                                gpointer task_data,
+                                GCancellable *cancellable)
+{
+       const gchar *const *locales;
+       g_autofree gchar *xml = NULL;
+       g_autofree gchar *path = NULL;
+       g_autoptr(XbBuilder) builder = NULL;
+       g_autoptr(XbBuilderSource) builder_source = NULL;
+       g_autoptr(XbSilo) silo = NULL;
+       g_autoptr(GPtrArray) nodes = NULL;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsApp) app = NULL;
+       GFile *file = task_data;
+       XbNode *component;
+
+       builder_source = xb_builder_source_new ();
+       if (!xb_builder_source_load_file (builder_source, file, XB_BUILDER_SOURCE_FLAG_NONE, cancellable, 
&error)) {
+               g_task_return_error (task, g_steal_pointer (&error));
+               return;
+       }
+
+       builder = xb_builder_new ();
+       locales = g_get_language_names ();
+
+       /* add current locales */
+       for (guint i = 0; locales[i] != NULL; i++) {
+               xb_builder_add_locale (builder, locales[i]);
+       }
+
+       xb_builder_import_source (builder, builder_source);
+
+       silo = xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID | 
XB_BUILDER_COMPILE_FLAG_SINGLE_LANG, cancellable, &error);
+       if (silo == NULL) {
+               g_task_return_error (task, g_steal_pointer (&error));
+               return;
+       }
+
+       nodes = xb_silo_query (silo, "component", 0, NULL);
+       if (nodes == NULL)
+               nodes = xb_silo_query (silo, "application", 0, NULL);
+       if (nodes == NULL) {
+               g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "%s",
+                       "Passed-in file doesn't have a 'component' (nor 'application') top-level element");
+               return;
+       }
+
+       if (nodes->len != 1) {
+               g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Only one top-level element expected, received %u instead", nodes->len);
+               return;
+       }
+
+       component = g_ptr_array_index (nodes, 0);
+
+       app = gs_appstream_create_app (NULL, silo, component, &error);
+       if (app == NULL) {
+               g_task_return_error (task, g_steal_pointer (&error));
+               return;
+       }
+
+       if (!gs_appstream_refine_app (NULL, app, silo, component, GS_DETAILS_PAGE_REFINE_FLAGS, &error)) {
+               g_task_return_error (task, g_steal_pointer (&error));
+               return;
+       }
+
+       path = g_file_get_path (file);
+       gs_app_set_origin (app, path);
+
+       gs_app_set_state (app, GS_APP_STATE_UNKNOWN);
+
+       g_task_return_pointer (task, g_steal_pointer (&app), g_object_unref);
+}
+
+/**
+ * gs_details_page_set_metainfo:
+ * @self: a #GsDetailsPage
+ * @file: path to a metainfo file to display
+ *
+ * Load and show the given metainfo @file on the details page.
+ *
+ * The file must be a single metainfo file, not an appstream file
+ * containing multiple components. It will be shown as if it came
+ * from a configured repository. This function is intended to be
+ * used by application developers wanting to test how their metainfo
+ * will appear to users.
+ *
+ * Since: 42
+ */
+void
+gs_details_page_set_metainfo (GsDetailsPage *self,
+                             GFile *file)
+{
+       g_autoptr(GTask) task = NULL;
+
+       g_return_if_fail (GS_IS_DETAILS_PAGE (self));
+       g_return_if_fail (G_IS_FILE (file));
+       gs_details_page_set_state (self, GS_DETAILS_PAGE_STATE_LOADING);
+       g_clear_object (&self->app_local_file);
+       g_clear_object (&self->app);
+       self->origin_by_packaging_format = FALSE;
+       task = g_task_new (self, self->cancellable, gs_details_page_metainfo_ready_cb, NULL);
+       g_task_set_source_tag (task, gs_details_page_set_metainfo);
+       g_task_set_task_data (task, g_object_ref (file), g_object_unref);
+       g_task_run_in_thread (task, gs_details_page_metainfo_thread);
+}
diff --git a/src/gs-details-page.h b/src/gs-details-page.h
index 1ec5059d1..566e79b95 100644
--- a/src/gs-details-page.h
+++ b/src/gs-details-page.h
@@ -33,5 +33,7 @@ void           gs_details_page_set_odrs_provider      (GsDetailsPage  *self,
 gboolean        gs_details_page_get_is_narrow  (GsDetailsPage  *self);
 void            gs_details_page_set_is_narrow  (GsDetailsPage  *self,
                                                 gboolean        is_narrow);
+void            gs_details_page_set_metainfo   (GsDetailsPage *self,
+                                                GFile *file);
 
 G_END_DECLS
diff --git a/src/gs-shell.c b/src/gs-shell.c
index 02435ec42..6b781bb92 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -631,7 +631,10 @@ gs_shell_change_mode (GsShell *shell,
                gtk_editable_set_position (GTK_EDITABLE (shell->entry_search), -1);
        } else if (mode == GS_SHELL_MODE_DETAILS) {
                app = GS_APP (data);
-               if (gs_app_get_local_file (app) != NULL) {
+               if (gs_app_get_metadata_item (app, "GnomeSoftware::show-metainfo") != NULL) {
+                       gs_details_page_set_metainfo (GS_DETAILS_PAGE (page),
+                                                     gs_app_get_local_file (app));
+               } else if (gs_app_get_local_file (app) != NULL) {
                        gs_details_page_set_local_file (GS_DETAILS_PAGE (page),
                                                        gs_app_get_local_file (app));
                } else if (gs_app_get_metadata_item (app, "GnomeSoftware::from-url") != NULL) {
@@ -2380,6 +2383,34 @@ gs_shell_show_local_file (GsShell *shell, GFile *file)
        gs_shell_activate (shell);
 }
 
+/**
+ * gs_shell_show_metainfo:
+ * @shell: a #GsShell
+ * @file: path to a metainfo file to display
+ *
+ * Open a metainfo file and display it on the details page as if it were
+ * published in a repository configured on the system.
+ *
+ * This is intended for app developers to be able to test their metainfo files
+ * locally.
+ *
+ * Since: 42
+ */
+void
+gs_shell_show_metainfo (GsShell *shell, GFile *file)
+{
+       g_autoptr(GsApp) app = gs_app_new (NULL);
+
+       g_return_if_fail (GS_IS_SHELL (shell));
+       g_return_if_fail (G_IS_FILE (file));
+       save_back_entry (shell);
+       gs_app_set_metadata (app, "GnomeSoftware::show-metainfo", "1");
+       gs_app_set_local_file (app, file);
+       gs_shell_change_mode (shell, GS_SHELL_MODE_DETAILS,
+                             (gpointer) app, TRUE);
+       gs_shell_activate (shell);
+}
+
 void
 gs_shell_show_search_result (GsShell *shell, const gchar *id, const gchar *search)
 {
diff --git a/src/gs-shell.h b/src/gs-shell.h
index 0ce741348..a7b1f73cc 100644
--- a/src/gs-shell.h
+++ b/src/gs-shell.h
@@ -84,5 +84,7 @@ void           gs_shell_setup                 (GsShell        *shell,
 void            gs_shell_show_notification     (GsShell        *shell,
                                                 const gchar    *title);
 gboolean        gs_shell_get_is_narrow         (GsShell        *shell);
+void            gs_shell_show_metainfo         (GsShell        *shell,
+                                                GFile          *file);
 
 G_END_DECLS


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