[gnome-software] Add a vfunc to convert a URL to a GsApp
- From: Richard Hughes <rhughes src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software] Add a vfunc to convert a URL to a GsApp
- Date: Thu, 2 Feb 2017 12:51:01 +0000 (UTC)
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]