[gnome-software/wip/ubuntu-3-22] Support snap:// URL handling by backporting URL handling improvements from trunk



commit 3af36a47efc6d4bd6d1007ba4639d9b05460ae1b
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              |   64 +++++--------
 src/gs-cmd.c                      |   11 ++
 src/gs-plugin-loader-sync.c       |   50 ++++++++++
 src/gs-plugin-loader-sync.h       |    5 +
 src/gs-plugin-loader.c            |  190 +++++++++++++++++++++++++++++++++++++
 src/gs-plugin-loader.h            |    9 ++
 src/gs-plugin-vfuncs.h            |   24 +++++
 src/gs-shell-details.c            |   21 ++++
 src/gs-shell-details.h            |    2 +
 src/gs-shell.c                    |    3 +
 src/gs-utils.c                    |   61 ++++++++++++
 src/gs-utils.h                    |    2 +
 src/plugins/gs-plugin-appstream.c |   33 +++++++
 src/plugins/gs-plugin-snap.c      |   38 ++++++++
 src/plugins/gs-snapd.c            |   49 ++++++++++
 src/plugins/gs-snapd.h            |    6 +
 16 files changed, 528 insertions(+), 40 deletions(-)
---
diff --git a/src/gs-application.c b/src/gs-application.c
index c079d38..cf426a5 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -629,6 +629,26 @@ details_pkg_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, NULL);
+
+       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)
@@ -729,6 +749,7 @@ static GActionEntry actions[] = {
        { "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 },
@@ -943,46 +964,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 *host = soup_uri_get_host (uri);
-                       const gchar *path = soup_uri_get_path (uri);
-
-                       /* appstream://foo -> scheme: appstream, host: foo, path: / */
-                       /* appstream:foo -> scheme: appstream, host: (empty string), path: /foo */
-                       if (host != NULL && (strlen (host) > 0))
-                               path = host;
-
-                       /* trim any leading slashes */
-                       while (*path == '/')
-                               path++;
-
-                       g_action_group_activate_action (G_ACTION_GROUP (app),
-                                                       "details",
-                                                       g_variant_new ("(ss)", path, ""));
-               }
-               if (g_strcmp0 (soup_uri_get_scheme (uri), "apt") == 0) {
-                       const gchar *host = soup_uri_get_host (uri);
-                       const gchar *path = soup_uri_get_path (uri);
-
-                       /* trim any leading slashes */
-                       while (*path == '/')
-                               path++;
-
-                       /* apt://foo -> scheme: apt, host: foo, path: / */
-                       /* apt:foo -> scheme: apt, host: (empty string), path: /foo */
-                       if (host != NULL && (strlen (host) > 0))
-                               path = host;
-
-                       g_action_group_activate_action (G_ACTION_GROUP (app),
-                                                       "details-pkg",
-                                                       g_variant_new_string (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 a6a219d..20ed808 100644
--- a/src/gs-cmd.c
+++ b/src/gs-cmd.c
@@ -361,6 +361,17 @@ 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,
+                                                  NULL,
+                                                  &error);
+               if (app == NULL) {
+                       ret = FALSE;
+               } else {
+                       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 5da526e..45bc1d4 100644
--- a/src/gs-plugin-loader-sync.c
+++ b/src/gs-plugin-loader-sync.c
@@ -703,4 +703,54 @@ 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);
+}
+
+/**
+ * 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 8fd2abf..79be4d4 100644
--- a/src/gs-plugin-loader-sync.h
+++ b/src/gs-plugin-loader-sync.h
@@ -102,6 +102,11 @@ GsApp              *gs_plugin_loader_file_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 367a80a..f04c627 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -135,6 +135,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,
@@ -4534,6 +4539,191 @@ 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);
+       const gchar *function_name = "gs_plugin_url_to_app";
+       gboolean ret = TRUE;
+       GError *error = NULL;
+       guint i;
+       guint j;
+       GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
+       GsPlugin *plugin;
+       GsPluginUrlToAppFunc plugin_func = NULL;
+
+       /* 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 (!gs_plugin_get_enabled (plugin))
+                       continue;
+               if (g_task_return_error_if_cancelled (task))
+                       return;
+               g_module_symbol (gs_plugin_get_module (plugin),
+                                function_name,
+                                (gpointer *) &plugin_func);
+               if (plugin_func == NULL)
+                       continue;
+               ptask = as_profile_start (priv->profile,
+                                         "GsPlugin::%s(%s)",
+                                         gs_plugin_get_name (plugin),
+                                         function_name);
+               g_assert (ptask != NULL);
+               gs_plugin_loader_action_start (plugin_loader, plugin, FALSE);
+               ret = plugin_func (plugin, state->list, state->value,
+                                  cancellable, &error_local);
+               gs_plugin_loader_action_stop (plugin_loader, plugin);
+               if (!ret) {
+                       /* badly behaved plugin */
+                       if (error_local == NULL) {
+                               g_critical ("%s did not set error for %s",
+                                           gs_plugin_get_name (plugin),
+                                           function_name);
+                               continue;
+                       }
+                       g_warning ("failed to call %s on %s: %s",
+                                  function_name,
+                                  gs_plugin_get_name (plugin),
+                                  error_local->message);
+                       continue;
+               }
+               gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
+       }
+
+       /* set the local file on any of the returned results */
+       for (j = 0; j < gs_app_list_length (state->list); j++) {
+               GsApp *app = gs_app_list_index (state->list, j);
+               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_app_list_filter (state->list, gs_plugin_loader_app_set_prio, plugin_loader);
+       gs_app_list_filter_duplicates (state->list, GS_APP_LIST_FILTER_FLAG_PRIORITY);
+
+       /* check the apps have an icon set */
+       for (j = 0; j < gs_app_list_length (state->list); j++) {
+               GsApp *app = gs_app_list_index (state->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 */
+       ret = gs_plugin_loader_run_refine (plugin_loader,
+                                          function_name,
+                                          state->list,
+                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                          cancellable,
+                                          &error);
+       if (!ret) {
+               g_task_return_error (task, error);
+               return;
+       }
+
+       /* success */
+       if (gs_app_list_length (state->list) != 1) {
+               g_task_return_new_error (task,
+                                        GS_PLUGIN_ERROR,
+                                        GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                        "no application was created for %s",
+                                        g_file_get_path (state->file));
+               return;
+       }
+       g_task_return_pointer (task, g_object_ref (gs_app_list_index (state->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 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->list = gs_app_list_new ();
+       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_update_thread_cb:
+ **/
+static void
 gs_plugin_loader_update_thread_cb (GTask *task,
                                   gpointer object,
                                   gpointer task_data,
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index c43a2dd..d267a9d 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -160,6 +160,15 @@ 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,
+                                                        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,
                                                         GCancellable   *cancellable,
diff --git a/src/gs-plugin-vfuncs.h b/src/gs-plugin-vfuncs.h
index ce6ad7e..bee56fb 100644
--- a/src/gs-plugin-vfuncs.h
+++ b/src/gs-plugin-vfuncs.h
@@ -838,6 +838,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-shell-details.c b/src/gs-shell-details.c
index 14dd2a6..048a41c 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -1473,6 +1473,27 @@ gs_shell_details_set_local_file (GsShellDetails *self, GFile *file)
                                            self);
 }
 
+/**
+ * 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
 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 61d58bd..0a41c0a 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -320,6 +320,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);
                }
diff --git a/src/gs-utils.c b/src/gs-utils.c
index 58263d1..8c7f492 100644
--- a/src/gs-utils.c
+++ b/src/gs-utils.c
@@ -36,6 +36,7 @@
 #include <fnmatch.h>
 #include <math.h>
 #include <glib/gstdio.h>
+#include <string.h>
 
 #ifdef HAVE_POLKIT
 #include <polkit/polkit.h>
@@ -503,4 +504,64 @@ gs_utils_get_wilson_rating (guint64 star1,
        return (gint) ceil (val);
 }
 
+/**
+ * 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 0a9db60..0bfa5ab 100644
--- a/src/gs-utils.h
+++ b/src/gs-utils.h
@@ -70,6 +70,8 @@ gint           gs_utils_get_wilson_rating     (guint64         star1,
                                                 guint64         star3,
                                                 guint64         star4,
                                                 guint64         star5);
+gchar          *gs_utils_get_url_scheme        (const gchar    *url);
+gchar          *gs_utils_get_url_path          (const gchar    *url);
 
 G_END_DECLS
 
diff --git a/src/plugins/gs-plugin-appstream.c b/src/plugins/gs-plugin-appstream.c
index d6ab82a..8e34f7f 100644
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@ -323,6 +323,39 @@ gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
        return TRUE;
 }
 
+gboolean
+gs_plugin_url_to_app (GsPlugin *plugin,
+                     GsAppList *list,
+                     const gchar *url,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       GsPluginData *priv = gs_plugin_get_data (plugin);
+       AsApp *item;
+       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, "appstream") != 0 && g_strcmp0 (scheme, "apt") != 0)
+               return TRUE;
+
+       /* create app */
+       path = gs_utils_get_url_path (url);
+       if (g_strcmp0 (scheme, "appstream") == 0)
+               item = as_store_get_app_by_id (priv->store, path);
+       else
+               item = as_store_get_app_by_pkgname (priv->store, path);
+       if (item == NULL)
+               return TRUE;
+       app = gs_appstream_create_app (plugin, item, error);
+       if (app == NULL)
+               return FALSE;
+       gs_app_list_add (list, app);
+       return TRUE;
+}
+
 static gboolean
 gs_plugin_refine_from_id (GsPlugin *plugin,
                          GsApp *app,
diff --git a/src/plugins/gs-plugin-snap.c b/src/plugins/gs-plugin-snap.c
index 7310c9f..3cf1cc5 100644
--- a/src/plugins/gs-plugin-snap.c
+++ b/src/plugins/gs-plugin-snap.c
@@ -224,6 +224,44 @@ refine_app (GsPlugin *plugin, GsApp *app, JsonObject *package, gboolean from_sea
        }
 }
 
+gboolean
+gs_plugin_url_to_app (GsPlugin *plugin,
+                     GsAppList *list,
+                     const gchar *url,
+                     GCancellable *cancellable,
+                     GError **error)
+{
+       g_autofree gchar *scheme = NULL;
+       g_autofree gchar *macaroon = NULL;
+       g_auto(GStrv) discharges = NULL;
+       g_autoptr(JsonArray) snaps = NULL;
+       JsonObject *snap;
+       g_autofree gchar *path = NULL;
+       g_autoptr(GsApp) app = NULL;
+
+       /* not us */
+       scheme = gs_utils_get_url_scheme (url);
+       if (g_strcmp0 (scheme, "snap") != 0)
+               return TRUE;
+
+       get_macaroon (plugin, &macaroon, &discharges);
+
+       /* create app */
+       path = gs_utils_get_url_path (url);
+       snaps = gs_snapd_find_name (macaroon, discharges, path, cancellable, NULL);
+       if (snaps == NULL || json_array_get_length (snaps) < 1)
+               return TRUE;
+
+       snap = json_array_get_object_element (snaps, 0);
+       app = gs_app_new (json_object_get_string_member (snap, "name"));
+       gs_app_set_management_plugin (app, "snap");
+       gs_app_add_quirk (app, AS_APP_QUIRK_NOT_REVIEWABLE);
+       refine_app (plugin, app, snap, TRUE, cancellable);
+       gs_app_list_add (list, app);
+
+       return TRUE;
+}
+
 void
 gs_plugin_destroy (GsPlugin *plugin)
 {
diff --git a/src/plugins/gs-snapd.c b/src/plugins/gs-snapd.c
index 4a75bb6..a16e192 100644
--- a/src/plugins/gs-snapd.c
+++ b/src/plugins/gs-snapd.c
@@ -486,6 +486,55 @@ gs_snapd_find (const gchar *macaroon, gchar **discharges,
        return json_array_ref (result);
 }
 
+JsonArray *
+gs_snapd_find_name (const gchar *macaroon, gchar **discharges,
+                   const gchar *name,
+                   GCancellable *cancellable, GError **error)
+{
+       g_autofree gchar *escaped = NULL;
+       g_autofree gchar *path = NULL;
+       guint status_code;
+       g_autofree gchar *reason_phrase = NULL;
+       g_autofree gchar *response_type = NULL;
+       g_autofree gchar *response = NULL;
+       g_autoptr(JsonParser) parser = NULL;
+       JsonObject *root;
+       JsonArray *result;
+
+       escaped = soup_uri_encode (name, NULL);
+       path = g_strdup_printf ("/v2/find?name=%s", escaped);
+       if (!send_request ("GET", path, NULL,
+                          macaroon, discharges,
+                          &status_code, &reason_phrase,
+                          &response_type, &response, NULL,
+                          cancellable, error))
+               return NULL;
+
+       if (status_code != SOUP_STATUS_OK) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "snapd returned status code %u: %s",
+                            status_code, reason_phrase);
+               return NULL;
+       }
+
+       parser = parse_result (response, response_type, error);
+       if (parser == NULL)
+               return NULL;
+       root = json_node_get_object (json_parser_get_root (parser));
+       result = json_object_get_array_member (root, "result");
+       if (result == NULL) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "snapd returned no result");
+               return NULL;
+       }
+
+       return json_array_ref (result);
+}
+
 JsonObject *
 gs_snapd_get_interfaces (const gchar *macaroon, gchar **discharges, GCancellable *cancellable, GError 
**error)
 {
diff --git a/src/plugins/gs-snapd.h b/src/plugins/gs-snapd.h
index af28bd1..103bf56 100644
--- a/src/plugins/gs-snapd.h
+++ b/src/plugins/gs-snapd.h
@@ -51,6 +51,12 @@ JsonObject *gs_snapd_get_interfaces  (const gchar    *macaroon,
                                         GCancellable   *cancellable,
                                         GError         **error);
 
+JsonArray *gs_snapd_find_name          (const gchar    *macaroon,
+                                        gchar          **discharges,
+                                        const gchar    *name,
+                                        GCancellable   *cancellable,
+                                        GError         **error);
+
 gboolean gs_snapd_install              (const gchar    *macaroon,
                                         gchar          **discharges,
                                         const gchar    *name,


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