[gnome-software] Add a vfunc to convert a URL to a GsApp



commit fcc81c353ad49249eab609ec1c467c6a974f76f9
Author: Richard Hughes <richard hughsie com>
Date:   Thu Feb 2 10:22:18 2017 +0000

    Add a vfunc to convert a URL to a GsApp
    
    At the moment applications are manually specifying the management plugin, which
    is really the wrong thing to do. Before we can say 'don't do that' we have to
    have a better way to handle this kind of app creation.

 src/gs-cmd.c                  |   13 ++++
 src/gs-plugin-loader-sync.c   |   49 +++++++++++++
 src/gs-plugin-loader-sync.h   |    6 ++
 src/gs-plugin-loader.c        |  153 +++++++++++++++++++++++++++++++++++++++++
 src/gs-plugin-loader.h        |   10 +++
 src/gs-plugin-types.h         |    2 +
 src/gs-plugin-vfuncs.h        |   24 +++++++
 src/gs-plugin.c               |    2 +
 src/gs-self-test.c            |   21 ++++++
 src/gs-shell-details.c        |   25 +++++++
 src/gs-shell-details.h        |    2 +
 src/gs-shell.c                |    4 +
 src/plugins/gs-plugin-dummy.c |   26 +++++++
 13 files changed, 337 insertions(+), 0 deletions(-)
---
diff --git a/src/gs-cmd.c b/src/gs-cmd.c
index 3ea78dd..7fe107e 100644
--- a/src/gs-cmd.c
+++ b/src/gs-cmd.c
@@ -369,6 +369,19 @@ main (int argc, char **argv)
                        list = gs_app_list_new ();
                        gs_app_list_add (list, app);
                }
+       } else if (argc == 3 && g_strcmp0 (argv[1], "url-to-app") == 0) {
+               app = gs_plugin_loader_url_to_app (plugin_loader,
+                                                  argv[2],
+                                                  refine_flags,
+                                                  GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                                  NULL,
+                                                  &error);
+               if (app == NULL) {
+                       ret = FALSE;
+               } else {
+                       list = gs_app_list_new ();
+                       gs_app_list_add (list, app);
+               }
        } else if (argc == 2 && g_strcmp0 (argv[1], "updates") == 0) {
                for (i = 0; i < repeat; i++) {
                        if (list != NULL)
diff --git a/src/gs-plugin-loader-sync.c b/src/gs-plugin-loader-sync.c
index 9c1cc24..0951c63 100644
--- a/src/gs-plugin-loader-sync.c
+++ b/src/gs-plugin-loader-sync.c
@@ -734,4 +734,53 @@ gs_plugin_loader_file_to_app (GsPluginLoader *plugin_loader,
        return helper.app;
 }
 
+static void
+gs_plugin_loader_url_to_app_finish_sync (GObject *source_object,
+                                        GAsyncResult *res,
+                                        gpointer user_data)
+{
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
+       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) user_data;
+       helper->app = gs_plugin_loader_url_to_app_finish (plugin_loader,
+                                                         res,
+                                                         helper->error);
+       g_main_loop_quit (helper->loop);
+}
+
+GsApp *
+gs_plugin_loader_url_to_app (GsPluginLoader *plugin_loader,
+                            const gchar *url,
+                            GsPluginRefineFlags refine_flags,
+                            GsPluginFailureFlags failure_flags,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       GsPluginLoaderHelper helper;
+
+       /* create temp object */
+       helper.app = NULL;
+       helper.context = g_main_context_new ();
+       helper.loop = g_main_loop_new (helper.context, FALSE);
+       helper.error = error;
+
+       g_main_context_push_thread_default (helper.context);
+
+       /* run async method */
+       gs_plugin_loader_url_to_app_async (plugin_loader,
+                                          url,
+                                          refine_flags,
+                                          failure_flags,
+                                          cancellable,
+                                          gs_plugin_loader_url_to_app_finish_sync,
+                                          &helper);
+       g_main_loop_run (helper.loop);
+
+       g_main_context_pop_thread_default (helper.context);
+
+       g_main_loop_unref (helper.loop);
+       g_main_context_unref (helper.context);
+
+       return helper.app;
+}
+
 /* vim: set noexpandtab: */
diff --git a/src/gs-plugin-loader-sync.h b/src/gs-plugin-loader-sync.h
index d9e6836..687a406 100644
--- a/src/gs-plugin-loader-sync.h
+++ b/src/gs-plugin-loader-sync.h
@@ -117,6 +117,12 @@ GsApp              *gs_plugin_loader_file_to_app           (GsPluginLoader 
*plugin_loader,
                                                         GsPluginFailureFlags failure_flags,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+GsApp          *gs_plugin_loader_url_to_app            (GsPluginLoader *plugin_loader,
+                                                        const gchar    *url,
+                                                        GsPluginRefineFlags refine_flags,
+                                                        GsPluginFailureFlags failure_flags,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 
 G_END_DECLS
 
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 2f7622a..8d7eb13 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -160,6 +160,11 @@ typedef gboolean    (*GsPluginFileToAppFunc)       (GsPlugin       *plugin,
                                                         GFile          *file,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+typedef gboolean        (*GsPluginUrlToAppFunc)        (GsPlugin       *plugin,
+                                                        GsAppList      *list,
+                                                        const gchar    *url,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 typedef gboolean        (*GsPluginUpdateFunc)          (GsPlugin       *plugin,
                                                         GsAppList      *apps,
                                                         GCancellable   *cancellable,
@@ -632,6 +637,13 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderJob *job,
                                           cancellable, &error_local);
                }
                break;
+       case GS_PLUGIN_ACTION_URL_TO_APP:
+               {
+                       GsPluginUrlToAppFunc plugin_func = func;
+                       ret = plugin_func (plugin, list, job->value,
+                                          cancellable, &error_local);
+               }
+               break;
        case GS_PLUGIN_ACTION_AUTH_LOGIN:
        case GS_PLUGIN_ACTION_AUTH_LOGOUT:
        case GS_PLUGIN_ACTION_AUTH_REGISTER:
@@ -4360,6 +4372,147 @@ gs_plugin_loader_file_to_app_finish (GsPluginLoader *plugin_loader,
 
 /******************************************************************************/
 
+static void
+gs_plugin_loader_url_to_app_thread_cb (GTask *task,
+                                       gpointer object,
+                                       gpointer task_data,
+                                       GCancellable *cancellable)
+{
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       GError *error = NULL;
+       GsPluginLoaderJob *job = (GsPluginLoaderJob *) task_data;
+
+       /* run each plugin */
+       for (guint i = 0; i < priv->plugins->len; i++) {
+               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
+               if (g_task_return_error_if_cancelled (task))
+                       return;
+               if (!gs_plugin_loader_call_vfunc (job, plugin, NULL, NULL,
+                                                 cancellable, &error)) {
+                       g_task_return_error (task, error);
+                       return;
+               }
+       }
+
+       /* set the local file on any of the returned results */
+       for (guint j = 0; j < gs_app_list_length (job->list); j++) {
+               GsApp *app = gs_app_list_index (job->list, j);
+               if (gs_app_get_local_file (app) == NULL)
+                       gs_app_set_local_file (app, job->file);
+       }
+
+       /* run refine() on each one */
+       if (!gs_plugin_loader_run_refine (job, job->list, cancellable, &error)) {
+               g_task_return_error (task, error);
+               return;
+       }
+
+       /* filter package list */
+       gs_app_list_filter (job->list, gs_plugin_loader_app_set_prio, plugin_loader);
+       gs_app_list_filter_duplicates (job->list, GS_APP_LIST_FILTER_FLAG_PRIORITY);
+
+       /* check the apps have an icon set */
+       for (guint j = 0; j < gs_app_list_length (job->list); j++) {
+               GsApp *app = gs_app_list_index (job->list, j);
+               if (_gs_app_get_icon_by_kind (app, AS_ICON_KIND_STOCK) == NULL &&
+                   _gs_app_get_icon_by_kind (app, AS_ICON_KIND_LOCAL) == NULL &&
+                   _gs_app_get_icon_by_kind (app, AS_ICON_KIND_CACHED) == NULL) {
+                       g_autoptr(AsIcon) ic = as_icon_new ();
+                       as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
+                       if (gs_app_get_kind (app) == AS_APP_KIND_SOURCE)
+                               as_icon_set_name (ic, "x-package-repository");
+                       else
+                               as_icon_set_name (ic, "application-x-executable");
+                       gs_app_add_icon (app, ic);
+               }
+       }
+
+       /* run refine() on each one again to pick up any icons */
+       job->refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON;
+       if (!gs_plugin_loader_run_refine (job, job->list, cancellable, &error)) {
+               g_task_return_error (task, error);
+               return;
+       }
+
+       /* success */
+       if (gs_app_list_length (job->list) != 1) {
+               g_task_return_new_error (task,
+                                        GS_PLUGIN_ERROR,
+                                        GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                        "no application was created for %s",
+                                        job->value);
+               return;
+       }
+       g_task_return_pointer (task, g_object_ref (gs_app_list_index (job->list, 0)), (GDestroyNotify) 
g_object_unref);
+}
+
+/**
+ * gs_plugin_loader_url_to_app_async:
+ *
+ * This method calls all plugins that implement the gs_plugin_add_url_to_app()
+ * function. The plugins can either return #GsApp objects of kind
+ * %AS_APP_KIND_DESKTOP for bonafide applications, or #GsApp's of kind
+ * %AS_APP_KIND_GENERIC for packages that may or may not be applications.
+ *
+ * Once the list of updates is refined, some of the #GsApp's of kind
+ * %AS_APP_KIND_GENERIC will have been promoted to a kind of %AS_APP_KIND_DESKTOP,
+ * or if they are core applications.
+ *
+ * Files that are supported will have the GFile used to create them available
+ * from the gs_app_get_local_file() method.
+ **/
+void
+gs_plugin_loader_url_to_app_async (GsPluginLoader *plugin_loader,
+                                   const gchar *url,
+                                   GsPluginRefineFlags refine_flags,
+                                   GsPluginFailureFlags failure_flags,
+                                   GCancellable *cancellable,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+       GsPluginLoaderJob *job;
+       g_autoptr(GTask) task = NULL;
+
+       g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       /* save job */
+       job = gs_plugin_loader_job_new (plugin_loader);
+       job->refine_flags = refine_flags;
+       job->failure_flags = failure_flags;
+       job->list = gs_app_list_new ();
+       job->value = g_strdup (url);
+       job->action = GS_PLUGIN_ACTION_URL_TO_APP;
+       job->function_name = "gs_plugin_url_to_app";
+
+       /* run in a thread */
+       task = g_task_new (plugin_loader, cancellable, callback, user_data);
+       g_task_set_task_data (task, job, (GDestroyNotify) gs_plugin_loader_job_free);
+       g_task_run_in_thread (task, gs_plugin_loader_url_to_app_thread_cb);
+}
+
+/**
+ * gs_plugin_loader_url_to_app_finish:
+ *
+ * Return value: (element-type GsApp) (transfer full): An application, or %NULL
+ **/
+GsApp *
+gs_plugin_loader_url_to_app_finish (GsPluginLoader *plugin_loader,
+                                    GAsyncResult *res,
+                                    GError **error)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
+       g_return_val_if_fail (G_IS_TASK (res), NULL);
+       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
+       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+       gs_utils_error_convert_gio (error);
+       return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+/******************************************************************************/
+
 static GPtrArray *
 get_updatable_apps (GPtrArray *apps)
 {
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index c7507e5..212cc65 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -174,6 +174,16 @@ void                gs_plugin_loader_file_to_app_async     (GsPluginLoader 
*plugin_loader,
 GsApp          *gs_plugin_loader_file_to_app_finish    (GsPluginLoader *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
+void            gs_plugin_loader_url_to_app_async      (GsPluginLoader *plugin_loader,
+                                                        const gchar    *url,
+                                                        GsPluginRefineFlags refine_flags,
+                                                        GsPluginFailureFlags failure_flags,
+                                                        GCancellable   *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer        user_data);
+GsApp          *gs_plugin_loader_url_to_app_finish     (GsPluginLoader *plugin_loader,
+                                                        GAsyncResult   *res,
+                                                        GError         **error);
 void            gs_plugin_loader_update_async          (GsPluginLoader *plugin_loader,
                                                         GsAppList      *apps,
                                                         GsPluginFailureFlags failure_flags,
diff --git a/src/gs-plugin-types.h b/src/gs-plugin-types.h
index a361caa..e0519f3 100644
--- a/src/gs-plugin-types.h
+++ b/src/gs-plugin-types.h
@@ -261,6 +261,7 @@ typedef enum {
  * @GS_PLUGIN_ACTION_AUTH_LOGOUT:              Authentication logout action
  * @GS_PLUGIN_ACTION_AUTH_REGISTER:            Authentication register action
  * @GS_PLUGIN_ACTION_AUTH_LOST_PASSWORD:       Authentication lost password action
+ * @GS_PLUGIN_ACTION_URL_TO_APP:               Convert the file to an application
  *
  * The plugin action.
  **/
@@ -302,6 +303,7 @@ typedef enum {
        GS_PLUGIN_ACTION_AUTH_LOGOUT,
        GS_PLUGIN_ACTION_AUTH_REGISTER,
        GS_PLUGIN_ACTION_AUTH_LOST_PASSWORD,
+       GS_PLUGIN_ACTION_URL_TO_APP,
        /*< private >*/
        GS_PLUGIN_ACTION_LAST
 } GsPluginAction;
diff --git a/src/gs-plugin-vfuncs.h b/src/gs-plugin-vfuncs.h
index a69f3ae..ae6a29b 100644
--- a/src/gs-plugin-vfuncs.h
+++ b/src/gs-plugin-vfuncs.h
@@ -860,6 +860,30 @@ gboolean    gs_plugin_file_to_app                  (GsPlugin       *plugin,
                                                         GError         **error);
 
 /**
+ * gs_plugin_url_to_app:
+ * @plugin: a #GsPlugin
+ * @list: a #GsAppList
+ * @url: a #URL, e.g. "apt://gimp"
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Converts a URL to a #GsApp. It's expected that only one plugin will
+ * match the scheme of @url and that a single #GsApp will be in the returned
+ * list. If no plugins can handle the file, the list will be empty.
+ *
+ * For example, the apt plugin can turn apt://gimp into a application.
+ *
+ * Plugins are expected to add new apps using gs_app_list_add().
+ *
+ * Returns: %TRUE for success or if not relevant
+ **/
+gboolean        gs_plugin_url_to_app                   (GsPlugin       *plugin,
+                                                        GsAppList      *list,
+                                                        const gchar    *url,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+
+/**
  * gs_plugin_update:
  * @plugin: a #GsPlugin
  * @apps: a #GsAppList
diff --git a/src/gs-plugin.c b/src/gs-plugin.c
index eebe777..2fa888b 100644
--- a/src/gs-plugin.c
+++ b/src/gs-plugin.c
@@ -1535,6 +1535,8 @@ gs_plugin_action_to_string (GsPluginAction action)
                return "refresh";
        if (action == GS_PLUGIN_ACTION_FILE_TO_APP)
                return "file-to-app";
+       if (action == GS_PLUGIN_ACTION_URL_TO_APP)
+               return "url-to-app";
        if (action == GS_PLUGIN_ACTION_AUTH_LOGIN)
                return "auth-login";
        if (action == GS_PLUGIN_ACTION_AUTH_LOGOUT)
diff --git a/src/gs-self-test.c b/src/gs-self-test.c
index 8a72ece..b3aeffb 100644
--- a/src/gs-self-test.c
+++ b/src/gs-self-test.c
@@ -794,6 +794,24 @@ gs_plugin_loader_search_func (GsPluginLoader *plugin_loader)
 }
 
 static void
+gs_plugin_loader_url_to_app_func (GsPluginLoader *plugin_loader)
+{
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       app = gs_plugin_loader_url_to_app (plugin_loader,
+                                          "dummy://chiron.desktop",
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                          NULL,
+                                          &error);
+       g_assert_no_error (error);
+       g_assert (app != NULL);
+       g_assert_cmpstr (gs_app_get_id (app), ==, "chiron.desktop");
+       g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
+}
+
+static void
 gs_plugin_loader_modalias_func (GsPluginLoader *plugin_loader)
 {
        GsApp *app;
@@ -1622,6 +1640,9 @@ main (int argc, char **argv)
        g_test_add_data_func ("/gnome-software/plugin-loader{search}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_search_func);
+       g_test_add_data_func ("/gnome-software/plugin-loader{url-to-app}",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugin_loader_url_to_app_func);
        g_test_add_data_func ("/gnome-software/plugin-loader{install}",
                              plugin_loader,
                              (GTestDataFunc) gs_plugin_loader_install_func);
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index 7e0e516..ddff6d1 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -1523,6 +1523,31 @@ gs_shell_details_set_local_file (GsShellDetails *self, GFile *file)
                                            self);
 }
 
+void
+gs_shell_details_set_url (GsShellDetails *self, const gchar *url)
+{
+       gs_shell_details_set_state (self, GS_SHELL_DETAILS_STATE_LOADING);
+       gs_plugin_loader_url_to_app_async (self->plugin_loader,
+                                          url,
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_UI |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS,
+                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                          self->cancellable,
+                                          gs_shell_details_file_to_app_cb,
+                                          self);
+}
+
 static void
 gs_shell_details_load (GsShellDetails *self)
 {
diff --git a/src/gs-shell-details.h b/src/gs-shell-details.h
index 45da1d5..5ad1086 100644
--- a/src/gs-shell-details.h
+++ b/src/gs-shell-details.h
@@ -41,6 +41,8 @@ void           gs_shell_details_set_app       (GsShellDetails         *self,
                                                 GsApp                  *app);
 void            gs_shell_details_set_local_file(GsShellDetails         *self,
                                                 GFile                  *file);
+void            gs_shell_details_set_url       (GsShellDetails         *self,
+                                                const gchar            *url);
 GsApp          *gs_shell_details_get_app       (GsShellDetails         *self);
 void            gs_shell_details_setup         (GsShellDetails         *self,
                                                 GsShell                *shell,
diff --git a/src/gs-shell.c b/src/gs-shell.c
index 323beb7..9744777 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -341,6 +341,9 @@ gs_shell_change_mode (GsShell *shell,
                if (gs_app_get_local_file (app) != NULL) {
                        gs_shell_details_set_local_file (priv->shell_details,
                                                         gs_app_get_local_file (app));
+               } else if (gs_app_get_metadata_item (app, "GnomeSoftware::from-url") != NULL) {
+                       gs_shell_details_set_url (priv->shell_details,
+                                                 gs_app_get_metadata_item (app, "GnomeSoftware::from-url"));
                } else {
                        gs_shell_details_set_app (priv->shell_details, data);
                }
@@ -1501,6 +1504,7 @@ gs_shell_show_event (GsShell *shell, GsPluginEvent *event)
        case GS_PLUGIN_ACTION_LAUNCH:
                return gs_shell_show_event_launch (shell, event);
        case GS_PLUGIN_ACTION_FILE_TO_APP:
+       case GS_PLUGIN_ACTION_URL_TO_APP:
                return gs_shell_show_event_file_to_app (shell, event);
        default:
                break;
diff --git a/src/plugins/gs-plugin-dummy.c b/src/plugins/gs-plugin-dummy.c
index f4a4686..4f34db7 100644
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@ -172,6 +172,32 @@ gs_plugin_dummy_poll_cb (gpointer user_data)
 }
 
 gboolean
+gs_plugin_url_to_app (GsPlugin *plugin,
+                     GsAppList *list,
+                     const gchar *url,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       g_autofree gchar *path = NULL;
+       g_autofree gchar *scheme = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       /* not us */
+       scheme = gs_utils_get_url_scheme (url);
+       if (g_strcmp0 (scheme, "dummy") != 0)
+               return TRUE;
+
+       /* create app */
+       path = gs_utils_get_url_path (url);
+       app = gs_app_new (path);
+       gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+       gs_app_set_metadata (app, "GnomeSoftware::Creator",
+                            gs_plugin_get_name (plugin));
+       gs_app_list_add (list, app);
+       return TRUE;
+}
+
+gboolean
 gs_plugin_add_search (GsPlugin *plugin,
                      gchar **values,
                      GsAppList *list,


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