[gnome-software/1409-add-available-for-fedora-section-to-the-explore-page: 21/21] gs-overview-page: Add a deployment-featured section




commit 2321c58395c56e07c8fd7ef358c1241995060c00
Author: Milan Crha <mcrha redhat com>
Date:   Fri May 6 13:34:17 2022 +0200

    gs-overview-page: Add a deployment-featured section
    
    See the doc/vendor-customisation.md `Deployment Featured Apps` section
    for an information how this works.
    
    Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1409

 contrib/deployment-featured.ini                   |   4 +
 contrib/org.gnome.Software.DeploymentFeatured.xml |  11 ++
 doc/vendor-customisation.md                       |  29 ++++
 src/gs-overview-page.c                            | 186 ++++++++++++++++++++++
 src/gs-overview-page.ui                           |  27 ++++
 5 files changed, 257 insertions(+)
---
diff --git a/contrib/deployment-featured.ini b/contrib/deployment-featured.ini
new file mode 100644
index 000000000..d6cf51f53
--- /dev/null
+++ b/contrib/deployment-featured.ini
@@ -0,0 +1,4 @@
+[Deployment Featured Apps]
+Selector=foocorp
+Label=Featured by Foo Corp.
+Label[it]=Presentato da Foo Corp.
diff --git a/contrib/org.gnome.Software.DeploymentFeatured.xml 
b/contrib/org.gnome.Software.DeploymentFeatured.xml
new file mode 100644
index 000000000..b3ba38578
--- /dev/null
+++ b/contrib/org.gnome.Software.DeploymentFeatured.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This is only an example file, what the deployment-featured file can look like;
+     it should be installed beside the org.gnome.Software.Featured.xml file. -->
+<components>
+  <component merge="append">
+    <id>org.gnome.Software.desktop</id>
+    <custom>
+      <value key="GnomeSoftware::DeploymentFeatured">foocorp</value>
+    </custom>
+  </component>
+</components>
diff --git a/doc/vendor-customisation.md b/doc/vendor-customisation.md
index c15201ef7..2755276f0 100644
--- a/doc/vendor-customisation.md
+++ b/doc/vendor-customisation.md
@@ -180,3 +180,32 @@ section of the overview page.
 Pass `-Ddefault_featured_apps=false` when configuring GNOME Software to disable
 the default list of featured applications. Pass `-Dhardcoded_curated=false` to
 disable the default list of “Editor’s Choice” applications.
+
+Deployment Featured Apps
+------------------------
+
+Deployments can feature their own applications, which will be shown in the Explore
+page in its own section. To have the section shown two files needs to be provided.
+
+The first file  is `org.gnome.Software.DeploymentFeatured.xml`, which works similarly
+as the `org.gnome.Software.Featured.xml` and should be saved beside it, it only sets
+`GnomeSoftware::DeploymentFeatured` key for the apps. The value of this key is
+a string containing the deployment name. There is a limit of the found featured
+apps. The section is not shown when no enough apps are found.
+
+The second file is `deployment-featured.ini` file, which contains the label and
+the selector for the section. The label is a localized key, containing the label
+text for the section. The selector defines which apps should be picked. It is
+a comma-separated list of `GnomeSoftware::DeploymentFeatured` key values, thus
+the deployment can feature apps from more vendors. The `deployment-featured.ini`
+should be saved in one of the `sysconfdir`, system config dirs or system data
+dirs, which are checked, in this order, for existence of the `gnome-software/deployment-featured.ini`
+file and the first directory containing it will be used. The relevant file names
+contain `/etc/xdg/gnome-software/deployment-featured.ini`,
+`/usr/local/share/gnome-software/deployment-featured.ini` and
+`/usr/share/gnome-software/deployment-featured.ini`.
+
+Any changes of these files, including their adding/remove, require restart of
+the gnome-software process.
+
+Example files can be found in the `contrib/` directory.
diff --git a/src/gs-overview-page.c b/src/gs-overview-page.c
index e2274f044..f2c06ab2f 100644
--- a/src/gs-overview-page.c
+++ b/src/gs-overview-page.c
@@ -36,6 +36,7 @@ struct _GsOverviewPage
        gint                     action_cnt;
        gboolean                 loading_featured;
        gboolean                 loading_curated;
+       gboolean                 loading_deployment_featured;
        gboolean                 loading_recent;
        gboolean                 loading_categories;
        gboolean                 empty;
@@ -43,6 +44,7 @@ struct _GsOverviewPage
        GHashTable              *category_hash;         /* id : GsCategory */
        GsFedoraThirdParty      *third_party;
        gboolean                 third_party_needs_question;
+       gchar                   *deployment_featured_filename;
 
        GtkWidget               *infobar_third_party;
        GtkWidget               *label_third_party;
@@ -50,11 +52,13 @@ struct _GsOverviewPage
        GtkWidget               *box_overview;
        GtkWidget               *box_curated;
        GtkWidget               *box_recent;
+       GtkWidget               *box_deployment_featured;
        GtkWidget               *flowbox_categories;
        GtkWidget               *flowbox_iconless_categories;
        GtkWidget               *iconless_categories_heading;
        GtkWidget               *curated_heading;
        GtkWidget               *recent_heading;
+       GtkWidget               *deployment_featured_heading;
        GtkWidget               *scrolledwindow_overview;
        GtkWidget               *stack_overview;
 };
@@ -114,6 +118,7 @@ gs_overview_page_decrement_action_cnt (GsOverviewPage *self)
        self->cache_valid = TRUE;
        g_signal_emit (self, signals[SIGNAL_REFRESHED], 0);
        self->loading_categories = FALSE;
+       self->loading_deployment_featured = FALSE;
        self->loading_featured = FALSE;
        self->loading_curated = FALSE;
        self->loading_recent = FALSE;
@@ -311,6 +316,56 @@ out:
        gs_overview_page_decrement_action_cnt (self);
 }
 
+static void
+gs_overview_page_get_deployment_featured_cb (GObject *source_object,
+                                            GAsyncResult *res,
+                                            gpointer user_data)
+{
+       GsOverviewPage *self = GS_OVERVIEW_PAGE (user_data);
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+       guint i;
+       GsApp *app;
+       GtkWidget *tile;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+
+       /* get deployment-featured apps */
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
+       if (list == NULL) {
+               if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
+                       g_warning ("failed to get deployment-featured apps: %s", error->message);
+               goto out;
+       }
+
+       /* not enough to show */
+       if (gs_app_list_length (list) < N_TILES) {
+               g_warning ("Only %u apps for deployment-featured list, hiding",
+                          gs_app_list_length (list));
+               gtk_widget_set_visible (self->box_deployment_featured, FALSE);
+               gtk_widget_set_visible (self->deployment_featured_heading, FALSE);
+               goto out;
+       }
+
+       gs_app_list_randomize (list);
+
+       gs_widget_remove_all (self->box_deployment_featured, (GsRemoveFunc) gtk_flow_box_remove);
+
+       for (i = 0; i < gs_app_list_length (list) && i < N_TILES; i++) {
+               app = gs_app_list_index (list, i);
+               tile = gs_summary_tile_new (app);
+               g_signal_connect (tile, "clicked",
+                         G_CALLBACK (app_tile_clicked), self);
+               gtk_flow_box_insert (GTK_FLOW_BOX (self->box_deployment_featured), tile, -1);
+       }
+       gtk_widget_set_visible (self->box_deployment_featured, TRUE);
+       gtk_widget_set_visible (self->deployment_featured_heading, TRUE);
+
+       self->empty = FALSE;
+
+ out:
+       gs_overview_page_decrement_action_cnt (self);
+}
+
 static void
 category_tile_clicked (GsCategoryTile *tile, gpointer data)
 {
@@ -487,6 +542,96 @@ fedora_third_party_disable (GsOverviewPage *self)
        gs_fedora_third_party_opt_out (self->third_party, self->cancellable, 
fedora_third_party_disable_done_cb, g_object_ref (self));
 }
 
+static void
+gs_overview_page_init_deployment_featured_filename (GsOverviewPage *self)
+{
+       g_autoptr(GPtrArray) dirs = NULL;
+       g_autofree gchar *filename = NULL;
+       const gchar * const *sys_dirs;
+
+       g_return_if_fail (self->deployment_featured_filename == NULL);
+
+       #define FILENAME "deployment-featured.ini"
+
+       filename = g_build_filename (SYSCONFDIR, "gnome-software", FILENAME, NULL);
+       if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) {
+               g_debug ("Found '%s'", filename);
+               self->deployment_featured_filename = g_steal_pointer (&filename);
+               return;
+       }
+       g_debug ("File '%s' does not exist, trying next", filename);
+       g_clear_pointer (&filename, g_free);
+
+       sys_dirs = g_get_system_config_dirs ();
+
+       for (guint i = 0; sys_dirs != NULL && sys_dirs[i]; i++) {
+               g_autofree gchar *tmp = g_build_filename (sys_dirs[i], "gnome-software", FILENAME, NULL);
+               if (g_file_test (tmp, G_FILE_TEST_IS_REGULAR)) {
+                       g_debug ("Found '%s'", tmp);
+                       self->deployment_featured_filename = g_steal_pointer (&tmp);
+                       return;
+               }
+               g_debug ("File '%s' does not exist, trying next", tmp);
+       }
+
+       sys_dirs = g_get_system_data_dirs ();
+
+       for (guint i = 0; sys_dirs != NULL && sys_dirs[i]; i++) {
+               g_autofree gchar *tmp = g_build_filename (sys_dirs[i], "gnome-software", FILENAME, NULL);
+               if (g_file_test (tmp, G_FILE_TEST_IS_REGULAR)) {
+                       g_debug ("Found '%s'", tmp);
+                       self->deployment_featured_filename = g_steal_pointer (&tmp);
+                       return;
+               }
+               g_debug ("File '%s' does not exist, %s", tmp, sys_dirs[i + 1] ? "trying next" : "no more 
files to try");
+       }
+
+       #undef FILENAME
+}
+
+static gchar *
+gs_overview_page_read_deployment_featured_key (GsOverviewPage *self,
+                                              const gchar *key,
+                                              gboolean localized)
+{
+       g_autoptr(GKeyFile) key_file = NULL;
+       g_autoptr(GError) error = NULL;
+
+       if (!self->deployment_featured_filename)
+               return NULL;
+
+       key_file = g_key_file_new ();
+       if (!g_key_file_load_from_file (key_file, self->deployment_featured_filename, G_KEY_FILE_NONE, 
&error)) {
+               g_debug ("Failed to read '%s': %s", self->deployment_featured_filename, error ? 
error->message : "Unknown error");
+               g_clear_pointer (&self->deployment_featured_filename, g_free);
+               return NULL;
+       }
+
+       if (localized)
+               return g_key_file_get_locale_string (key_file, "Deployment Featured Apps", key, NULL, NULL);
+
+       return g_key_file_get_string (key_file, "Deployment Featured Apps", key, NULL);
+}
+
+static gchar *
+gs_overview_page_get_deployment_featured_label (GsOverviewPage *self)
+{
+       return gs_overview_page_read_deployment_featured_key (self, "Label", TRUE);
+}
+
+static gchar **
+gs_overview_page_get_featured_deployments (GsOverviewPage *self)
+{
+       g_autofree gchar *selector = gs_overview_page_read_deployment_featured_key (self, "Selector", FALSE);
+       g_auto(GStrv) deployments = NULL;
+       if (selector == NULL || *selector == '\0')
+               return NULL;
+       deployments = g_strsplit (selector, ",", -1);
+       if (deployments != NULL && deployments[0] != NULL && *deployments[0] != '\0')
+               return g_steal_pointer (&deployments);
+       return NULL;
+}
+
 static void
 gs_overview_page_load (GsOverviewPage *self)
 {
@@ -519,6 +664,36 @@ gs_overview_page_load (GsOverviewPage *self)
                self->action_cnt++;
        }
 
+       if (!self->loading_deployment_featured) {
+               g_auto(GStrv) deployments = gs_overview_page_get_featured_deployments (self);
+
+               self->loading_deployment_featured = TRUE;
+
+               if (deployments != NULL) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
+                       g_autoptr(GsAppQuery) query = NULL;
+                       GsPluginListAppsFlags flags = GS_PLUGIN_LIST_APPS_FLAGS_INTERACTIVE;
+
+                       query = gs_app_query_new ("deployment-featured", deployments,
+                                                 "max-results", N_TILES,
+                                                 "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
+                                                                 GS_PLUGIN_REFINE_FLAGS_REQUIRE_CATEGORIES |
+                                                                 GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                                 "dedupe-flags", GS_APP_LIST_FILTER_FLAG_PREFER_INSTALLED |
+                                                                 GS_APP_LIST_FILTER_FLAG_KEY_ID_PROVIDES,
+                                                 NULL);
+
+                       plugin_job = gs_plugin_job_list_apps_new (query, flags);
+
+                       gs_plugin_loader_job_process_async (self->plugin_loader,
+                                                           plugin_job,
+                                                           self->cancellable,
+                                                           gs_overview_page_get_deployment_featured_cb,
+                                                           self);
+                       self->action_cnt++;
+               }
+       }
+
        if (!self->loading_curated) {
                g_autoptr(GsPluginJob) plugin_job = NULL;
                g_autoptr(GsAppQuery) query = NULL;
@@ -727,11 +902,19 @@ refreshed_cb (GsOverviewPage *self, gpointer user_data)
 static void
 gs_overview_page_init (GsOverviewPage *self)
 {
+       g_autofree gchar *tmp_label = NULL;
+
        gtk_widget_init_template (GTK_WIDGET (self));
 
        gs_featured_carousel_set_apps (GS_FEATURED_CAROUSEL (self->featured_carousel), NULL);
 
        g_signal_connect (self, "refreshed", G_CALLBACK (refreshed_cb), self);
+
+       gs_overview_page_init_deployment_featured_filename (self);
+
+       tmp_label = gs_overview_page_get_deployment_featured_label (self);
+       if (tmp_label != NULL)
+               gtk_label_set_text (GTK_LABEL (self->deployment_featured_heading), tmp_label);
 }
 
 static void
@@ -783,6 +966,7 @@ gs_overview_page_dispose (GObject *object)
        g_clear_object (&self->cancellable);
        g_clear_object (&self->third_party);
        g_clear_pointer (&self->category_hash, g_hash_table_unref);
+       g_clear_pointer (&self->deployment_featured_filename, g_free);
 
        G_OBJECT_CLASS (gs_overview_page_parent_class)->dispose (object);
 }
@@ -819,11 +1003,13 @@ gs_overview_page_class_init (GsOverviewPageClass *klass)
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, box_overview);
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, box_curated);
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, box_recent);
+       gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, box_deployment_featured);
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, flowbox_categories);
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, flowbox_iconless_categories);
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, iconless_categories_heading);
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, curated_heading);
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, recent_heading);
+       gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, deployment_featured_heading);
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, scrolledwindow_overview);
        gtk_widget_class_bind_template_child (widget_class, GsOverviewPage, stack_overview);
        gtk_widget_class_bind_template_callback (widget_class, featured_carousel_app_clicked_cb);
diff --git a/src/gs-overview-page.ui b/src/gs-overview-page.ui
index a556aa04e..55c9dd795 100644
--- a/src/gs-overview-page.ui
+++ b/src/gs-overview-page.ui
@@ -151,6 +151,33 @@
                                   </object>
                                 </child>
 
+                                <child>
+                                  <object class="GtkLabel" id="deployment_featured_heading">
+                                    <property name="visible">False</property>
+                                    <property name="xalign">0</property>
+                                    <property name="label">Available for Deployment</property> <!-- 
placeholder, set in the code -->
+                                    <property name="margin-top">21</property>
+                                    <property name="margin-bottom">6</property>
+                                    <style>
+                                      <class name="heading"/>
+                                    </style>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkFlowBox" id="box_deployment_featured">
+                                    <property name="visible">False</property>
+                                    <property name="row_spacing">14</property>
+                                    <property name="column_spacing">14</property>
+                                    <property name="homogeneous">True</property>
+                                    <property name="min_children_per_line">2</property>
+                                    <property name="max_children_per_line">3</property>
+                                    <property name="selection_mode">none</property>
+                                    <accessibility>
+                                      <relation name="labelled-by">deployment_featured_heading</relation>
+                                    </accessibility>
+                                  </object>
+                                </child>
+
                                 <child>
                                   <object class="GtkLabel" id="iconless_categories_heading">
                                     <property name="xalign">0</property>


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