[gnome-software/wip/rancell/ubuntu-3-20-rebase: 9/17] Support snap:// URL handling by backporting URL handling improvements from trunk



commit 272ec0fc88662a5ee50750ab295aab5c5d68c384
Author: Richard Hughes <richard hughsie com>
Date:   Thu Feb 2 10:11:54 2017 +0000

    Support snap:// URL handling by backporting URL handling improvements from trunk

 src/gs-application.c              |   42 ++++++----
 src/gs-cmd.c                      |   11 +++
 src/gs-plugin-loader-sync.c       |   50 ++++++++++++
 src/gs-plugin-loader-sync.h       |    5 +
 src/gs-plugin-loader.c            |  155 +++++++++++++++++++++++++++++++++++++
 src/gs-plugin-loader.h            |    9 ++
 src/gs-plugin.h                   |   10 +++
 src/gs-shell-details.c            |   18 ++++
 src/gs-shell-details.h            |    2 +
 src/gs-shell.c                    |    9 ++-
 src/gs-utils.c                    |   60 ++++++++++++++
 src/gs-utils.h                    |    3 +
 src/plugins/gs-plugin-appstream.c |   40 ++++++++++
 13 files changed, 395 insertions(+), 19 deletions(-)
---
diff --git a/src/gs-application.c b/src/gs-application.c
index 6b10e01..50c8520 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -597,6 +597,26 @@ details_activated (GSimpleAction *action,
 }
 
 static void
+details_url_activated (GSimpleAction *action,
+                      GVariant      *parameter,
+                      gpointer       data)
+{
+       GsApplication *app = GS_APPLICATION (data);
+       const gchar *url;
+       g_autoptr (GsApp) a = NULL;
+
+       initialize_ui_and_present_window (app);
+
+       g_variant_get (parameter, "(&s)", &url);
+
+       /* this is only used as a wrapper to transport the URL to
+        * the gs_shell_change_mode() function -- not in the GsAppList */
+       a = gs_app_new (NULL);
+       gs_app_set_metadata (a, "GnomeSoftware::from-url", url);
+       gs_shell_show_app (app->shell, a);
+}
+
+static void
 filename_activated (GSimpleAction *action,
                    GVariant      *parameter,
                    gpointer       data)
@@ -688,6 +708,8 @@ static GActionEntry actions[] = {
        { "set-mode", set_mode_activated, "s", NULL, NULL },
        { "search", search_activated, "s", NULL, NULL },
        { "details", details_activated, "(ss)", NULL, NULL },
+       { "details-pkg", details_pkg_activated, "s", NULL, NULL },
+       { "details-url", details_url_activated, "(s)", NULL, NULL },
        { "filename", filename_activated, "(s)", NULL, NULL },
        { "launch", launch_activated, "s", NULL, NULL },
        { "show-offline-update-error", show_offline_updates_error, NULL, NULL, NULL },
@@ -820,23 +842,9 @@ gs_application_open (GApplication  *application,
 
        for (i = 0; i < n_files; i++) {
                g_autofree gchar *str = g_file_get_uri (files[i]);
-               g_autoptr(SoupURI) uri = NULL;
-
-               uri = soup_uri_new (str);
-               if (!SOUP_URI_IS_VALID (uri))
-                       continue;
-
-               if (g_strcmp0 (soup_uri_get_scheme (uri), "appstream") == 0) {
-                       const gchar *path = soup_uri_get_path (uri);
-
-                       /* trim any leading slashes */
-                       while (*path == '/')
-                               path++;
-
-                       g_action_group_activate_action (G_ACTION_GROUP (app),
-                                                       "details",
-                                                       g_variant_new ("(ss)", path, ""));
-               }
+               g_action_group_activate_action (G_ACTION_GROUP (app),
+                                               "details-url",
+                                               g_variant_new ("(s)", str));
        }
 }
 
diff --git a/src/gs-cmd.c b/src/gs-cmd.c
index c169d33..4c61952 100644
--- a/src/gs-cmd.c
+++ b/src/gs-cmd.c
@@ -324,6 +324,17 @@ main (int argc, char **argv)
                } else {
                        gs_plugin_add_app (&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,
+                                                  NULL,
+                                                  &error);
+               if (app == NULL) {
+                       ret = FALSE;
+               } else {
+                       gs_plugin_add_app (&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 254b8b9..e2fed01 100644
--- a/src/gs-plugin-loader-sync.c
+++ b/src/gs-plugin-loader-sync.c
@@ -664,4 +664,54 @@ gs_plugin_loader_filename_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);
+}
+
+/**
+ * gs_plugin_loader_url_to_app:
+ **/
+GsApp *
+gs_plugin_loader_url_to_app (GsPluginLoader *plugin_loader,
+                            const gchar *url,
+                            GsPluginRefineFlags 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,
+                                          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 b1b88ec..158c81d 100644
--- a/src/gs-plugin-loader-sync.h
+++ b/src/gs-plugin-loader-sync.h
@@ -91,6 +91,11 @@ GsApp                *gs_plugin_loader_filename_to_app       (GsPluginLoader 
*plugin_loader,
                                                         GsPluginRefineFlags flags,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+GsApp          *gs_plugin_loader_url_to_app            (GsPluginLoader *plugin_loader,
+                                                        const gchar    *url,
+                                                        GsPluginRefineFlags flags,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 
 G_END_DECLS
 
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 656cf2a..a648a6a 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -3752,6 +3752,161 @@ gs_plugin_loader_filename_to_app_finish (GsPluginLoader *plugin_loader,
 /******************************************************************************/
 
 /**
+ * gs_plugin_loader_url_to_app_thread_cb:
+ **/
+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);
+       const gchar *function_name = "gs_plugin_url_to_app";
+       gboolean ret = TRUE;
+       GError *error = NULL;
+       GList *l;
+       GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
+       GsPlugin *plugin;
+       GsPluginUrlToAppFunc plugin_func = NULL;
+       guint i;
+
+       /* run each plugin */
+       for (i = 0; i < priv->plugins->len; i++) {
+               g_autoptr(AsProfileTask) ptask = NULL;
+               g_autoptr(GError) error_local = NULL;
+               plugin = g_ptr_array_index (priv->plugins, i);
+               if (!plugin->enabled)
+                       continue;
+               ret = g_task_return_error_if_cancelled (task);
+               if (ret)
+                       return;
+               ret = g_module_symbol (plugin->module,
+                                      function_name,
+                                      (gpointer *) &plugin_func);
+               if (!ret)
+                       continue;
+               ptask = as_profile_start (priv->profile,
+                                         "GsPlugin::%s(%s)",
+                                         plugin->name,
+                                         function_name);
+               ret = plugin_func (plugin, &state->list, state->value,
+                                  cancellable, &error_local);
+               if (!ret) {
+                       g_warning ("failed to call %s on %s: %s",
+                                  function_name, plugin->name,
+                                  error_local->message);
+                       continue;
+               }
+               gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
+       }
+
+       /* dedupe applications we already know about */
+       gs_plugin_loader_list_dedupe (plugin_loader, state->list);
+
+       /* set the local file on any of the returned results */
+       for (l = state->list; l != NULL; l = l->next) {
+               GsApp *app = GS_APP (l->data);
+               if (gs_app_get_local_file (app) == NULL)
+                       gs_app_set_local_file (app, state->file);
+       }
+
+       /* run refine() on each one */
+       ret = gs_plugin_loader_run_refine (plugin_loader,
+                                          function_name,
+                                          &state->list,
+                                          state->flags,
+                                          cancellable,
+                                          &error);
+       if (!ret) {
+               g_task_return_error (task, error);
+               return;
+       }
+
+       /* filter package list */
+       gs_plugin_list_filter_duplicates (&state->list);
+       if (state->list == NULL) {
+               g_task_return_new_error (task,
+                                        GS_PLUGIN_LOADER_ERROR,
+                                        GS_PLUGIN_LOADER_ERROR_NO_RESULTS,
+                                        "no url_to_app results to show");
+               return;
+       }
+
+       /* success */
+       if (g_list_length (state->list) != 1) {
+               g_task_return_new_error (task,
+                                        GS_PLUGIN_LOADER_ERROR,
+                                        GS_PLUGIN_LOADER_ERROR_NO_RESULTS,
+                                        "no application was created for %s",
+                                        state->value);
+               return;
+       }
+       g_task_return_pointer (task, g_object_ref (state->list->data), (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 flags,
+                                  GCancellable *cancellable,
+                                  GAsyncReadyCallback callback,
+                                  gpointer user_data)
+{
+       GsPluginLoaderAsyncState *state;
+       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 state */
+       state = g_slice_new0 (GsPluginLoaderAsyncState);
+       state->flags = flags;
+       state->value = g_strdup (url);
+
+       /* run in a thread */
+       task = g_task_new (plugin_loader, cancellable, callback, user_data);
+       g_task_set_task_data (task, state, (GDestroyNotify) gs_plugin_loader_free_async_state);
+       g_task_set_return_on_cancel (task, TRUE);
+       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);
+
+       return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+/******************************************************************************/
+
+/**
  * gs_plugin_loader_offline_update_thread_cb:
  **/
 static void
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index 2365cb0..1a3a3d2 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -180,6 +180,15 @@ void                gs_plugin_loader_filename_to_app_async (GsPluginLoader 
*plugin_loader,
 GsApp          *gs_plugin_loader_filename_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,
+                                                        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_offline_update_async  (GsPluginLoader *plugin_loader,
                                                         GList          *apps,
                                                         GCancellable   *cancellable,
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index 4c40680..f49d1eb 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -165,6 +165,11 @@ typedef gboolean    (*GsPluginFilenameToAppFunc)   (GsPlugin       *plugin,
                                                         const gchar    *filename,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+typedef gboolean        (*GsPluginUrlToAppFunc)        (GsPlugin       *plugin,
+                                                        GList          **list,
+                                                        const gchar    *url,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 typedef gboolean        (*GsPluginOfflineUpdateFunc)   (GsPlugin       *plugin,
                                                         GList          *apps,
                                                         GCancellable   *cancellable,
@@ -332,6 +337,11 @@ gboolean    gs_plugin_filename_to_app              (GsPlugin       *plugin,
                                                         const gchar    *filename,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+gboolean        gs_plugin_url_to_app                   (GsPlugin       *plugin,
+                                                        GList          **list,
+                                                        const gchar    *url,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 gboolean        gs_plugin_offline_update               (GsPlugin       *plugin,
                                                         GList          *apps,
                                                         GCancellable   *cancellable,
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index 1d59993..af097c8 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -1244,6 +1244,24 @@ gs_shell_details_set_filename (GsShellDetails *self, const gchar *filename)
 }
 
 /**
+ * gs_shell_details_set_url:
+ **/
+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_DEFAULT |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS,
+                                          self->cancellable,
+                                          gs_shell_details_file_to_app_cb,
+                                          self);
+}
+
+/**
  * gs_shell_details_load:
  **/
 static void
diff --git a/src/gs-shell-details.h b/src/gs-shell-details.h
index 3ffd72a..cbe78a8 100644
--- a/src/gs-shell-details.h
+++ b/src/gs-shell-details.h
@@ -42,6 +42,8 @@ void           gs_shell_details_set_app       (GsShellDetails         *self,
                                                 GsApp                  *app);
 void            gs_shell_details_set_filename  (GsShellDetails         *self,
                                                 const gchar            *filename);
+void            gs_shell_details_set_url       (GsShellDetails         *self,
+                                                const gchar            *url);
 GsApp          *gs_shell_details_get_app       (GsShellDetails         *self);
 void            gs_shell_details_reload        (GsShellDetails         *self);
 void            gs_shell_details_setup         (GsShellDetails         *self,
diff --git a/src/gs-shell.c b/src/gs-shell.c
index bd2deb0..154b4c0 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -297,8 +297,13 @@ gs_shell_change_mode (GsShell *shell,
                new_page = GS_PAGE (priv->shell_updates);
                break;
        case GS_SHELL_MODE_DETAILS:
-               if (app != NULL)
-                       gs_shell_details_set_app (priv->shell_details, app);
+               if (app != NULL) {
+                       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, app);
+               }
                if (data != NULL)
                        gs_shell_details_set_filename (priv->shell_details, data);
                new_page = GS_PAGE (priv->shell_details);
diff --git a/src/gs-utils.c b/src/gs-utils.c
index 4cd1799..86f99af 100644
--- a/src/gs-utils.c
+++ b/src/gs-utils.c
@@ -517,4 +517,64 @@ gs_utils_get_content_type (const gchar *filename,
        return g_strdup (tmp);
 }
 
+/**
+ * gs_utils_get_url_scheme:
+ * @url: A URL, e.g. "appstream://gimp.desktop"
+ *
+ * Gets the scheme from the URL string.
+ *
+ * Returns: the URL scheme, e.g. "appstream"
+ */
+gchar *
+gs_utils_get_url_scheme        (const gchar *url)
+{
+       g_autoptr(SoupURI) uri = NULL;
+
+       /* no data */
+       if (url == NULL)
+               return NULL;
+
+       /* create URI from URL */
+       uri = soup_uri_new (url);
+       if (!SOUP_URI_IS_VALID (uri))
+               return NULL;
+
+       /* success */
+       return g_strdup (soup_uri_get_scheme (uri));
+}
+
+/**
+ * gs_utils_get_url_path:
+ * @url: A URL, e.g. "appstream://gimp.desktop"
+ *
+ * Gets the path from the URL string, removing any leading slashes.
+ *
+ * Returns: the URL path, e.g. "gimp.desktop"
+ */
+gchar *
+gs_utils_get_url_path (const gchar *url)
+{
+       g_autoptr(SoupURI) uri = NULL;
+       const gchar *host;
+       const gchar *path;
+
+       uri = soup_uri_new (url);
+       if (!SOUP_URI_IS_VALID (uri))
+               return NULL;
+
+       /* foo://bar -> scheme: foo, host: bar, path: / */
+       /* foo:bar -> scheme: foo, host: (empty string), path: /bar */
+       host = soup_uri_get_host (uri);
+       path = soup_uri_get_path (uri);
+       if (host != NULL && (strlen (host) > 0))
+               path = host;
+
+       /* trim any leading slashes */
+       while (*path == '/')
+               path++;
+
+       /* success */
+       return g_strdup (path);
+}
+
 /* vim: set noexpandtab: */
diff --git a/src/gs-utils.h b/src/gs-utils.h
index 561054c..6882063 100644
--- a/src/gs-utils.h
+++ b/src/gs-utils.h
@@ -65,6 +65,9 @@ gchar         *gs_utils_get_cachedir          (const gchar    *kind,
 gchar          *gs_utils_get_user_hash         (GError         **error);
 GPermission    *gs_utils_get_permission        (const gchar    *id);
 
+gchar          *gs_utils_get_url_scheme        (const gchar    *url);
+gchar          *gs_utils_get_url_path          (const gchar    *url);
+
 G_END_DECLS
 
 #endif /* __GS_UTILS_H */
diff --git a/src/plugins/gs-plugin-appstream.c b/src/plugins/gs-plugin-appstream.c
index 14f7db2..357e390 100644
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@ -246,6 +246,46 @@ gs_plugin_appstream_startup (GsPlugin *plugin, GError **error)
 }
 
 /**
+ * gs_plugin_url_to_app:
+ */
+gboolean
+gs_plugin_url_to_app (GsPlugin *plugin,
+                     GList **list,
+                     const gchar *url,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       AsApp *item;
+       g_autofree gchar *path = NULL;
+       g_autofree gchar *scheme = NULL;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&plugin->priv->store_mutex);
+
+       /* not us */
+       scheme = gs_utils_get_url_scheme (url);
+       if (g_strcmp0 (scheme, "appstream") != 0 && g_strcmp0 (scheme, "apt") != 0)
+               return TRUE;
+
+       /* load XML files */
+       if (!gs_plugin_appstream_startup (plugin, error))
+               return FALSE;
+
+       /* create app */
+       path = gs_utils_get_url_path (url);
+       if (g_strcmp0 (scheme, "appstream") == 0)
+               item = as_store_get_app_by_id (plugin->priv->store, path);
+       else
+               item = as_store_get_app_by_pkgname (plugin->priv->store, path);
+       if (item == NULL)
+               return TRUE;
+       app = gs_app_new (as_app_get_id (item));
+       if (!gs_appstream_refine_app (plugin, app, item, error))
+               return FALSE;
+       gs_plugin_add_app (list, app);
+       return TRUE;
+}
+
+/**
  * gs_plugin_refine_from_id:
  */
 static gboolean


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