[gnome-software/wip/hughsie/gs_plugin_add_alternates] Allow the front-end to get alternate versions of applications



commit 9db1693b8d144621d335f858a3ec9bb07cbd817e
Author: Richard Hughes <richard hughsie com>
Date:   Tue Sep 25 17:49:21 2018 +0100

    Allow the front-end to get alternate versions of applications
    
    The idea here is that the frontend can say 'get me all different variants of
    'gimp.desktop' and flatpak will return 'org.gimp.GIMP:stable', snap will return
    'gimp:candidate', and the package manager would return just 'gimp'.

 lib/gs-cmd.c                        | 19 ++++++++++++++-
 lib/gs-plugin-loader.c              | 22 +++++++++++++++++
 lib/gs-plugin-types.h               |  2 ++
 lib/gs-plugin-vfuncs.h              | 21 +++++++++++++++++
 lib/gs-plugin.c                     |  6 +++++
 plugins/core/gs-appstream.c         | 47 +++++++++++++++++++++++++++++++++++++
 plugins/core/gs-appstream.h         |  6 +++++
 plugins/core/gs-plugin-appstream.c  | 11 +++++++++
 plugins/dummy/gs-plugin-dummy.c     | 14 +++++++++++
 plugins/dummy/gs-self-test.c        | 33 ++++++++++++++++++++++++++
 plugins/flatpak/gs-flatpak.c        | 16 +++++++++++++
 plugins/flatpak/gs-flatpak.h        |  5 ++++
 plugins/flatpak/gs-plugin-flatpak.c | 16 +++++++++++++
 13 files changed, 217 insertions(+), 1 deletion(-)
---
diff --git a/lib/gs-cmd.c b/lib/gs-cmd.c
index 6a79d940..34cfc36d 100644
--- a/lib/gs-cmd.c
+++ b/lib/gs-cmd.c
@@ -434,6 +434,23 @@ main (int argc, char **argv)
                                break;
                        }
                }
+       } else if (argc == 3 && g_strcmp0 (argv[1], "get-alternates") == 0) {
+               app = gs_app_new (argv[2]);
+               for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
+                       if (list != NULL)
+                               g_object_unref (list);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_ALTERNATES,
+                                                        "app", app,
+                                                        "refine-flags", self->refine_flags,
+                                                        "max-results", self->max_results,
+                                                        NULL);
+                       list = gs_plugin_loader_job_process (self->plugin_loader, plugin_job, NULL, &error);
+                       if (list == NULL) {
+                               ret = FALSE;
+                               break;
+                       }
+               }
        } else if (argc == 4 && g_strcmp0 (argv[1], "action") == 0) {
                GsPluginAction action = gs_plugin_action_from_string (argv[2]);
                if (action == GS_PLUGIN_ACTION_UNKNOWN) {
@@ -674,7 +691,7 @@ main (int argc, char **argv)
                                     GS_PLUGIN_ERROR_FAILED,
                                     "Did not recognise option, use 'installed', "
                                     "'updates', 'popular', 'get-categories', "
-                                    "'get-category-apps', 'filename-to-app', "
+                                    "'get-category-apps', 'get-alternates', 'filename-to-app', "
                                     "'action install', 'action remove', "
                                     "'sources', 'refresh', 'launch' or 'search'");
        }
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 6a6d5c81..996467c0 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -109,6 +109,11 @@ typedef gboolean    (*GsPluginSearchFunc)          (GsPlugin       *plugin,
                                                         GsAppList      *list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+typedef gboolean        (*GsPluginAlternatesFunc)      (GsPlugin       *plugin,
+                                                        GsApp          *app,
+                                                        GsAppList      *list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 typedef gboolean        (*GsPluginCategoryFunc)        (GsPlugin       *plugin,
                                                         GsCategory     *category,
                                                         GsAppList      *list,
@@ -688,6 +693,13 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper,
                                           cancellable, &error_local);
                }
                break;
+       case GS_PLUGIN_ACTION_GET_ALTERNATES:
+               {
+                       GsPluginAlternatesFunc plugin_func = func;
+                       ret = plugin_func (plugin, app, list,
+                                          cancellable, &error_local);
+               }
+               break;
        case GS_PLUGIN_ACTION_GET_CATEGORIES:
                {
                        GsPluginCategoriesFunc plugin_func = func;
@@ -3304,6 +3316,7 @@ gs_plugin_loader_process_thread_cb (GTask *task,
        case GS_PLUGIN_ACTION_SEARCH:
        case GS_PLUGIN_ACTION_SEARCH_FILES:
        case GS_PLUGIN_ACTION_SEARCH_PROVIDES:
+       case GS_PLUGIN_ACTION_GET_ALTERNATES:
                gs_plugin_loader_convert_unavailable (list, gs_plugin_job_get_search (helper->plugin_job));
                break;
        default:
@@ -3353,6 +3366,7 @@ gs_plugin_loader_process_thread_cb (GTask *task,
        case GS_PLUGIN_ACTION_SEARCH:
        case GS_PLUGIN_ACTION_SEARCH_FILES:
        case GS_PLUGIN_ACTION_SEARCH_PROVIDES:
+       case GS_PLUGIN_ACTION_GET_ALTERNATES:
                gs_app_list_filter (list, gs_plugin_loader_app_is_valid, helper);
                gs_app_list_filter (list, gs_plugin_loader_filter_qt_for_gtk, NULL);
                gs_app_list_filter (list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
@@ -3595,6 +3609,13 @@ gs_plugin_loader_job_process_async (GsPluginLoader *plugin_loader,
                                                GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION);
        }
 
+       /* always add the original app to the list so it can be sorted */
+       if (action == GS_PLUGIN_ACTION_GET_ALTERNATES) {
+               GsApp *app = gs_plugin_job_get_app (plugin_job);
+               GsAppList *list = gs_plugin_job_get_list (plugin_job);
+               gs_app_list_add (list, app);
+       }
+
        /* check required args */
        task = g_task_new (plugin_loader, cancellable_job, callback, user_data);
        switch (action) {
@@ -3685,6 +3706,7 @@ gs_plugin_loader_job_process_async (GsPluginLoader *plugin_loader,
 
        /* set up a hang handler */
        switch (action) {
+       case GS_PLUGIN_ACTION_GET_ALTERNATES:
        case GS_PLUGIN_ACTION_GET_CATEGORY_APPS:
        case GS_PLUGIN_ACTION_GET_FEATURED:
        case GS_PLUGIN_ACTION_GET_INSTALLED:
diff --git a/lib/gs-plugin-types.h b/lib/gs-plugin-types.h
index 14dfdffc..1576d34a 100644
--- a/lib/gs-plugin-types.h
+++ b/lib/gs-plugin-types.h
@@ -251,6 +251,7 @@ typedef enum {
  * @GS_PLUGIN_ACTION_DESTROY:                  Destroy the plugin
  * @GS_PLUGIN_ACTION_PURCHASE:                 Purchase an app
  * @GS_PLUGIN_ACTION_DOWNLOAD:                 Download an application
+ * @GS_PLUGIN_ACTION_GET_ALTERNATES:           Get the alternates for a specific application
  *
  * The plugin action.
  **/
@@ -299,6 +300,7 @@ typedef enum {
        GS_PLUGIN_ACTION_DESTROY,
        GS_PLUGIN_ACTION_PURCHASE,
        GS_PLUGIN_ACTION_DOWNLOAD,
+       GS_PLUGIN_ACTION_GET_ALTERNATES,
        /*< private >*/
        GS_PLUGIN_ACTION_LAST
 } GsPluginAction;
diff --git a/lib/gs-plugin-vfuncs.h b/lib/gs-plugin-vfuncs.h
index fcf53a98..52995c4d 100644
--- a/lib/gs-plugin-vfuncs.h
+++ b/lib/gs-plugin-vfuncs.h
@@ -146,6 +146,27 @@ gboolean    gs_plugin_add_search_what_provides     (GsPlugin       *plugin,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
 
+/**
+ * gs_plugin_add_alternates
+ * @plugin: a #GsPlugin
+ * @app: a #GsApp
+ * @list: a #GsAppList
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Called when trying to find alternates to a specific app, for instance
+ * finding a flatpak version of an existing distro packaged application.
+ *
+ * Plugins are expected to add new apps using gs_app_list_add().
+ *
+ * Returns: %TRUE for success or if not relevant
+ **/
+gboolean        gs_plugin_add_alternates               (GsPlugin       *plugin,
+                                                        GsApp          *app,
+                                                        GsAppList      *list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+
 /**
  * gs_plugin_setup:
  * @plugin: a #GsPlugin
diff --git a/lib/gs-plugin.c b/lib/gs-plugin.c
index ad8d66e7..8543aade 100644
--- a/lib/gs-plugin.c
+++ b/lib/gs-plugin.c
@@ -1800,6 +1800,8 @@ gs_plugin_action_to_function_name (GsPluginAction action)
                return "gs_plugin_destroy";
        if (action == GS_PLUGIN_ACTION_PURCHASE)
                return "gs_plugin_app_purchase";
+       if (action == GS_PLUGIN_ACTION_GET_ALTERNATES)
+               return "gs_plugin_add_alternates";
        return NULL;
 }
 
@@ -1902,6 +1904,8 @@ gs_plugin_action_to_string (GsPluginAction action)
                return "destroy";
        if (action == GS_PLUGIN_ACTION_PURCHASE)
                return "purchase";
+       if (action == GS_PLUGIN_ACTION_GET_ALTERNATES)
+               return "get-alternates";
        return NULL;
 }
 
@@ -2004,6 +2008,8 @@ gs_plugin_action_from_string (const gchar *action)
                return GS_PLUGIN_ACTION_DESTROY;
        if (g_strcmp0 (action, "purchase") == 0)
                return GS_PLUGIN_ACTION_PURCHASE;
+       if (g_strcmp0 (action, "get-alternates") == 0)
+               return GS_PLUGIN_ACTION_GET_ALTERNATES;
        return GS_PLUGIN_ACTION_UNKNOWN;
 }
 
diff --git a/plugins/core/gs-appstream.c b/plugins/core/gs-appstream.c
index c7d10bdb..8ae3e4bd 100644
--- a/plugins/core/gs-appstream.c
+++ b/plugins/core/gs-appstream.c
@@ -1096,6 +1096,53 @@ gs_appstream_add_recent (GsPlugin *plugin,
        return TRUE;
 }
 
+gboolean
+gs_appstream_add_alternates (GsPlugin *plugin,
+                            AsStore *store,
+                            GsApp *app,
+                            GsAppList *list,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       GPtrArray *apps = as_store_get_apps (store);
+       g_autoptr(AsProfileTask) ptask = NULL;
+
+       /* find out how many packages are in each category */
+       ptask = as_profile_start_literal (gs_plugin_get_profile (plugin),
+                                         "appstream::add-alternatives");
+       g_assert (ptask != NULL);
+
+       /* find apps that provide the new name */
+       for (guint i = 0; i < apps->len; i++) {
+               AsApp *item = g_ptr_array_index (apps, i);
+               GPtrArray *provides = as_app_get_provides (item);
+               if (g_strcmp0 (as_app_get_id (item), gs_app_get_id (app)) == 0) {
+                       for (guint j = 0; j < provides->len; j++) {
+                               AsProvide *tmp = g_ptr_array_index (provides, j);
+                               if (as_provide_get_kind (tmp) == AS_PROVIDE_KIND_ID) {
+                                       g_autoptr(GsApp) app2 = NULL;
+                                       app2 = gs_app_new (as_app_get_id (item));
+                                       gs_app_add_quirk (app2, AS_APP_QUIRK_MATCH_ANY_PREFIX);
+                                       gs_app_list_add (list, app2);
+                               }
+                       }
+               } else if (as_app_get_id (item) != NULL) {
+                       for (guint j = 0; j < provides->len; j++) {
+                               AsProvide *tmp = g_ptr_array_index (provides, j);
+                               if (as_provide_get_kind (tmp) == AS_PROVIDE_KIND_ID &&
+                                   g_strcmp0 (as_provide_get_value (tmp), gs_app_get_id (app)) == 0) {
+                                       g_autoptr(GsApp) app2 = NULL;
+                                       app2 = gs_app_new (as_app_get_id (item));
+                                       gs_app_add_quirk (app2, AS_APP_QUIRK_MATCH_ANY_PREFIX);
+                                       gs_app_list_add (list, app2);
+                               }
+                       }
+               }
+       }
+
+       return TRUE;
+}
+
 gboolean
 gs_appstream_add_featured (GsPlugin *plugin,
                           AsStore *store,
diff --git a/plugins/core/gs-appstream.h b/plugins/core/gs-appstream.h
index 55a3d3f5..29ccc11a 100644
--- a/plugins/core/gs-appstream.h
+++ b/plugins/core/gs-appstream.h
@@ -60,6 +60,12 @@ gboolean      gs_appstream_add_featured              (GsPlugin       *plugin,
                                                         GsAppList      *list,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+gboolean        gs_appstream_add_alternates            (GsPlugin       *plugin,
+                                                        AsStore        *store,
+                                                        GsApp          *app,
+                                                        GsAppList      *list,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 gboolean        gs_appstream_add_recent                (GsPlugin       *plugin,
                                                         AsStore        *store,
                                                         GsAppList      *list,
diff --git a/plugins/core/gs-plugin-appstream.c b/plugins/core/gs-plugin-appstream.c
index 22138a37..79c833a1 100644
--- a/plugins/core/gs-plugin-appstream.c
+++ b/plugins/core/gs-plugin-appstream.c
@@ -671,6 +671,17 @@ gs_plugin_add_recent (GsPlugin *plugin,
                                        cancellable, error);
 }
 
+gboolean
+gs_plugin_add_alternates (GsPlugin *plugin,
+                         GsApp *app,
+                         GsAppList *list,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       return gs_appstream_add_alternates (plugin, priv->store, app, list, cancellable, error);
+}
+
 gboolean
 gs_plugin_refresh (GsPlugin *plugin,
                   guint cache_age,
diff --git a/plugins/dummy/gs-plugin-dummy.c b/plugins/dummy/gs-plugin-dummy.c
index 6f739ff7..9db12879 100644
--- a/plugins/dummy/gs-plugin-dummy.c
+++ b/plugins/dummy/gs-plugin-dummy.c
@@ -280,6 +280,20 @@ gs_plugin_dummy_timeout_add (guint timeout_ms, GCancellable *cancellable)
        g_main_loop_run (helper->loop);
 }
 
+gboolean
+gs_plugin_add_alternates (GsPlugin *plugin,
+                         GsApp *app,
+                         GsAppList *list,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       if (g_strcmp0 (gs_app_get_id (app), "zeus.desktop") == 0) {
+               g_autoptr(GsApp) app2 = gs_app_new ("chiron.desktop");
+               gs_app_list_add (list, app2);
+       }
+       return TRUE;
+}
+
 gboolean
 gs_plugin_add_search (GsPlugin *plugin,
                      gchar **values,
diff --git a/plugins/dummy/gs-self-test.c b/plugins/dummy/gs-self-test.c
index f7d2675f..513e43b8 100644
--- a/plugins/dummy/gs-self-test.c
+++ b/plugins/dummy/gs-self-test.c
@@ -439,6 +439,36 @@ gs_plugins_dummy_search_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
 }
 
+static void
+gs_plugins_dummy_search_alternate_func (GsPluginLoader *plugin_loader)
+{
+       GsApp *app_tmp;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+
+       /* get search result based on addon keyword */
+       app = gs_app_new ("zeus.desktop");
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_ALTERNATES,
+                                        "app", app,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
+       gs_test_flush_main_context ();
+       g_assert_no_error (error);
+       g_assert (list != NULL);
+
+       /* make sure there is the original app, and the alternate */
+       g_assert_cmpint (gs_app_list_length (list), ==, 2);
+       app_tmp = gs_app_list_index (list, 0);
+       g_assert_cmpstr (gs_app_get_id (app_tmp), ==, "zeus.desktop");
+       g_assert_cmpint (gs_app_get_kind (app_tmp), ==, AS_APP_KIND_DESKTOP);
+       app_tmp = gs_app_list_index (list, 1);
+       g_assert_cmpstr (gs_app_get_id (app_tmp), ==, "chiron.desktop");
+       g_assert_cmpint (gs_app_get_kind (app_tmp), ==, AS_APP_KIND_DESKTOP);
+}
+
 static void
 gs_plugins_dummy_hang_func (GsPluginLoader *plugin_loader)
 {
@@ -908,6 +938,9 @@ main (int argc, char **argv)
        g_test_add_data_func ("/gnome-software/plugins/dummy/search",
                              plugin_loader,
                              (GTestDataFunc) gs_plugins_dummy_search_func);
+       g_test_add_data_func ("/gnome-software/plugins/dummy/search-alternate",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugins_dummy_search_alternate_func);
        g_test_add_data_func ("/gnome-software/plugins/dummy/hang",
                              plugin_loader,
                              (GTestDataFunc) gs_plugins_dummy_hang_func);
diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c
index 62694f01..b03d8ac8 100644
--- a/plugins/flatpak/gs-flatpak.c
+++ b/plugins/flatpak/gs-flatpak.c
@@ -2537,6 +2537,22 @@ gs_flatpak_add_featured (GsFlatpak *self,
        return TRUE;
 }
 
+gboolean
+gs_flatpak_add_alternates (GsFlatpak *self,
+                          GsApp *app,
+                          GsAppList *list,
+                          GCancellable *cancellable,
+                          GError **error)
+{
+       g_autoptr(GsAppList) list_tmp = gs_app_list_new ();
+       if (!gs_appstream_add_alternates (self->plugin, self->store, app, list_tmp,
+                                         cancellable, error))
+               return FALSE;
+       gs_flatpak_claim_app_list (self, list_tmp);
+       gs_app_list_add_list (list, list_tmp);
+       return TRUE;
+}
+
 gboolean
 gs_flatpak_add_recent (GsFlatpak *self,
                       GsAppList *list,
diff --git a/plugins/flatpak/gs-flatpak.h b/plugins/flatpak/gs-flatpak.h
index fde3396d..5bead17d 100644
--- a/plugins/flatpak/gs-flatpak.h
+++ b/plugins/flatpak/gs-flatpak.h
@@ -128,6 +128,11 @@ gboolean   gs_flatpak_add_featured         (GsFlatpak              *self,
                                                 GsAppList              *list,
                                                 GCancellable           *cancellable,
                                                 GError                 **error);
+gboolean       gs_flatpak_add_alternates       (GsFlatpak              *self,
+                                                GsApp                  *app,
+                                                GsAppList              *list,
+                                                GCancellable           *cancellable,
+                                                GError                 **error);
 gboolean       gs_flatpak_add_recent           (GsFlatpak              *self,
                                                 GsAppList              *list,
                                                 guint64                 age,
diff --git a/plugins/flatpak/gs-plugin-flatpak.c b/plugins/flatpak/gs-plugin-flatpak.c
index 4aedd182..af74f910 100644
--- a/plugins/flatpak/gs-plugin-flatpak.c
+++ b/plugins/flatpak/gs-plugin-flatpak.c
@@ -993,6 +993,22 @@ gs_plugin_add_popular (GsPlugin *plugin,
        return TRUE;
 }
 
+gboolean
+gs_plugin_add_alternates (GsPlugin *plugin,
+                         GsApp *app,
+                         GsAppList *list,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       for (guint i = 0; i < priv->flatpaks->len; i++) {
+               GsFlatpak *flatpak = g_ptr_array_index (priv->flatpaks, i);
+               if (!gs_flatpak_add_alternates (flatpak, app, list, cancellable, error))
+                       return FALSE;
+       }
+       return TRUE;
+}
+
 gboolean
 gs_plugin_add_featured (GsPlugin *plugin,
                        GsAppList *list,


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