[gnome-software/wip/rancell/paid: 393/393] Merge branch 'master' into wip/rancell/paid



commit 34aa3583f6583d91046f2b19aaa075625a6653b9
Merge: f1cebf3 9d61723
Author: Robert Ancell <robert ancell canonical com>
Date:   Tue Nov 8 14:16:10 2016 +1300

    Merge branch 'master' into wip/rancell/paid

 RELEASE                                            |    6 +-
 configure.ac                                       |  119 +-
 contrib/gnome-software.spec.in                     |   26 +-
 data/Makefile.am                                   |   13 +-
 data/appdata/org.gnome.Software.appdata.xml.in     |  226 +-
 ...org.gnome.software.external-appstream.policy.in |   20 +
 data/org.gnome.software.gschema.xml                |   42 +-
 data/tests/example.flatpakrepo                     |    1 +
 doc/api/Makefile.am                                |    2 +
 doc/api/gnome-software-docs.xml                    |    2 +
 doc/api/gnome-software.types                       |    1 +
 po/LINGUAS                                         |    2 +
 po/POTFILES.in                                     |    2 +
 po/ar.po                                           | 4104 ++++++++++++++------
 po/ca.po                                           |  382 ++-
 po/cs.po                                           | 2928 ++++++++------
 po/da.po                                           | 3271 +++++++++-------
 po/de.po                                           | 3593 +++++++++++------
 po/en_GB.po                                        | 3644 ++++++++++++++---
 po/eo.po                                           | 4104 +++++++++++++-------
 po/es.po                                           | 1555 +++++---
 po/fa.po                                           | 2954 ++++++++++++++
 po/fi.po                                           | 3144 ++++++++++------
 po/fr.po                                           |  738 ++--
 po/fur.po                                          | 1547 +++++---
 po/gl.po                                           |  234 +-
 po/he.po                                           | 3946 ++++++++++++-------
 po/hr.po                                           | 2972 ++++++++++++++
 po/hu.po                                           | 3122 +++++++--------
 po/it.po                                           | 2574 +++++++------
 po/ja.po                                           | 3859 ++++++++++++-------
 po/kk.po                                           | 1301 ++++---
 po/ko.po                                           | 2554 +++++++------
 po/lt.po                                           | 1736 +++++----
 po/lv.po                                           | 3424 +++++++++++------
 po/nb.po                                           | 2961 ++++++++------
 po/nl.po                                           | 3748 ++++++++++++------
 po/oc.po                                           | 3099 +++++++++-------
 po/pl.po                                           | 2658 +++++++------
 po/pt_BR.po                                        | 2765 ++++++++------
 po/ru.po                                           | 3127 +++++++++------
 po/sk.po                                           | 1248 ++++---
 po/sl.po                                           | 1650 +++++---
 po/sr.po                                           | 3171 ++++++++++------
 po/sr latin po                                     | 3236 ++++++++++------
 po/sv.po                                           | 3150 ++++++++++------
 po/uk.po                                           | 3368 +++++++++++------
 po/zh_TW.po                                        |  652 ++--
 src/Makefile.am                                    |   19 +-
 src/gd-notification.c                              |  875 +++++
 src/gd-notification.h                              |   67 +
 src/gnome-software-local-file.desktop.in           |    2 +-
 src/gnome-software-service.desktop.in              |    1 +
 src/gnome-software.ui                              |  230 +-
 src/gs-app-list-private.h                          |    2 +
 src/gs-app-list.c                                  |  207 +-
 src/gs-app-list.h                                  |    4 +
 src/gs-app-private.h                               |    5 +-
 src/gs-app-row.c                                   |   15 +-
 src/gs-app.c                                       |  704 +++-
 src/gs-app.h                                       |   22 +-
 src/gs-application.c                               |   95 +-
 src/gs-auth-dialog.c                               |    6 +-
 src/gs-auth.c                                      |    3 +-
 src/gs-auth.h                                      |   18 -
 src/gs-category.c                                  |   44 +
 src/gs-cmd.c                                       |   12 +-
 src/gs-common.c                                    |  387 +--
 src/gs-common.h                                    |   12 +-
 src/gs-content-rating.c                            |  569 +++
 src/gs-content-rating.h                            |   62 +
 src/gs-dbus-helper.c                               |    2 +-
 src/gs-folders.c                                   |   12 +-
 src/gs-main.c                                      |    1 +
 src/gs-os-release.c                                |   14 +
 src/gs-page.c                                      |  287 +-
 src/gs-payment-method.c                            |  144 -
 src/gs-payment-method.h                            |   49 -
 src/gs-plugin-event.c                              |  323 ++
 src/gs-plugin-event.h                              |   83 +
 src/gs-plugin-loader-sync.c                        |    6 +-
 src/gs-plugin-loader-sync.h                        |    6 +-
 src/gs-plugin-loader.c                             |  943 ++++--
 src/gs-plugin-loader.h                             |   55 +-
 src/gs-plugin-private.h                            |    4 +
 src/gs-plugin-types.h                              |  309 ++
 src/gs-plugin-vfuncs.h                             |   91 +-
 src/gs-plugin.c                                    |  426 ++-
 src/gs-plugin.h                                    |  200 +-
 src/gs-purchase-dialog.c                           |   28 -
 src/gs-purchase-dialog.h                           |    6 -
 src/gs-review-dialog.c                             |    4 +-
 src/gs-review-row.c                                |   32 +-
 src/gs-review-row.h                                |    2 +-
 src/gs-screenshot-image.c                          |    5 +-
 src/gs-self-test.c                                 |  279 ++-
 src/gs-shell-category.c                            |   11 +-
 src/gs-shell-details.c                             |  313 ++-
 src/gs-shell-details.h                             |    1 -
 src/gs-shell-details.ui                            |   93 +
 src/gs-shell-extras.c                              |   71 +-
 src/gs-shell-installed.c                           |   35 +-
 src/gs-shell-moderate.c                            |   15 +-
 src/gs-shell-overview.c                            |  219 +-
 src/gs-shell-overview.ui                           |  374 ++-
 src/gs-shell-search-provider.c                     |    4 +-
 src/gs-shell-search.c                              |   45 +-
 src/gs-shell-updates.c                             |  112 +-
 src/gs-shell.c                                     |  880 ++++-
 src/gs-shell.h                                     |    4 +-
 src/gs-sources-dialog-row.c                        |   92 +-
 src/gs-sources-dialog-row.h                        |    7 +
 src/gs-sources-dialog-row.ui                       |   67 +-
 src/gs-sources-dialog.c                            |  299 ++-
 src/gs-sources-dialog.ui                           |   25 +-
 src/gs-summary-tile.c                              |   10 -
 src/gs-update-dialog.c                             |    4 +-
 src/gs-update-monitor.c                            |  225 +-
 src/gs-update-monitor.h                            |    2 -
 src/gs-upgrade-banner.ui                           |    1 -
 src/gs-utils.c                                     |  303 ++-
 src/gs-utils.h                                     |    8 +
 src/gtk-style-hc.css                               |    4 +-
 src/gtk-style.css                                  |   17 +
 src/plugins/Makefile.am                            |   53 +-
 src/plugins/gnome-software-install-appstream.in    |    9 +
 src/plugins/gs-appstream.c                         |  503 +++-
 src/plugins/gs-appstream.h                         |   32 +
 src/plugins/gs-desktop-common.c                    |    5 +-
 src/plugins/gs-desktop-common.h                    |    2 +-
 src/plugins/gs-flatpak-symlinks.c                  |  190 +-
 src/plugins/gs-flatpak-symlinks.h                  |    2 +-
 src/plugins/gs-flatpak.c                           | 1200 +++++-
 src/plugins/gs-flatpak.h                           |   42 +-
 src/plugins/gs-plugin-appstream.c                  |  416 +--
 src/plugins/gs-plugin-desktop-menu-path.c          |    4 +-
 src/plugins/gs-plugin-dpkg.c                       |    6 +-
 src/plugins/gs-plugin-dummy.c                      |   92 +-
 src/plugins/gs-plugin-epiphany.c                   |   20 +-
 src/plugins/gs-plugin-external-appstream.c         |  238 ++
 src/plugins/gs-plugin-fedora-distro-upgrades.c     |   42 +-
 src/plugins/gs-plugin-flatpak-system.c             |  117 +-
 src/plugins/gs-plugin-flatpak-user.c               |   99 +-
 src/plugins/gs-plugin-fwupd.c                      |  433 ++-
 src/plugins/gs-plugin-hardcoded-featured.c         |    2 +-
 src/plugins/gs-plugin-icons.c                      |   55 +-
 src/plugins/gs-plugin-key-colors.c                 |    4 +-
 src/plugins/gs-plugin-limba.c                      |    2 +
 src/plugins/gs-plugin-modalias.c                   |  163 +
 src/plugins/gs-plugin-odrs.c                       |  110 +-
 src/plugins/gs-plugin-ostree.c                     |    8 +-
 src/plugins/gs-plugin-packagekit-history.c         |    2 +-
 src/plugins/gs-plugin-packagekit-local.c           |   10 +-
 src/plugins/gs-plugin-packagekit-refine.c          |   52 +-
 src/plugins/gs-plugin-packagekit-refresh.c         |    2 +-
 src/plugins/gs-plugin-packagekit-upgrade.c         |   14 +-
 src/plugins/gs-plugin-packagekit.c                 |   14 +-
 src/plugins/gs-plugin-repos.c                      |   12 +-
 src/plugins/gs-plugin-rpm.c                        |    2 +-
 src/plugins/gs-plugin-shell-extensions.c           |   77 +-
 src/plugins/gs-plugin-snap.c                       |  733 +---
 src/plugins/gs-plugin-steam.c                      |   60 +-
 src/plugins/gs-plugin-systemd-updates.c            |   38 +-
 src/plugins/gs-plugin-ubuntu-reviews.c             |   68 +-
 src/plugins/gs-plugin-ubuntuone.c                  |   16 +-
 src/plugins/gs-snapd.c                             |  492 +++-
 src/plugins/gs-snapd.h                             |   80 +-
 src/plugins/packagekit-common.c                    |   24 +-
 src/plugins/packagekit-common.h                    |    2 +-
 169 files changed, 71724 insertions(+), 36717 deletions(-)
---
diff --cc src/Makefile.am
index 333cada,2a2f3d3..5b40af7
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@@ -111,8 -113,6 +114,7 @@@ gnome_software_cmd_SOURCES =                               
        gs-app.c                                        \
        gs-app-list.c                                   \
        gs-auth.c                                       \
 +      gs-price.c                                      \
-       gs-review.c                                     \
        gs-cmd.c                                        \
        gs-common.c                                     \
        gs-debug.c                                      \
@@@ -194,18 -199,15 +201,19 @@@ gnome_software_SOURCES =                                
        gs-os-release.h                                 \
        gs-page.c                                       \
        gs-page.h                                       \
-       gs-payment-method.c                             \
-       gs-payment-method.h                             \
        gs-plugin.c                                     \
        gs-plugin.h                                     \
+       gs-plugin-event.c                               \
+       gs-plugin-event.h                               \
        gs-plugin-private.h                             \
+       gs-plugin-types.h                               \
        gs-plugin-vfuncs.h                              \
 +      gs-price.c                                      \
 +      gs-price.h                                      \
        gs-progress-button.c                            \
        gs-progress-button.h                            \
 +      gs-purchase-dialog.c                            \
 +      gs-purchase-dialog.h                            \
        gs-removal-dialog.c                             \
        gs-removal-dialog.h                             \
        gs-review-bar.c                                 \
diff --cc src/gs-app.c
index 5713824,a87228b..380d43a
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@@ -383,13 -408,6 +410,13 @@@ gs_app_to_string (GsApp *app
                gs_app_kv_lpad (str, "origin-ui", app->origin_ui);
        if (app->origin_hostname != NULL && app->origin_hostname[0] != '\0')
                gs_app_kv_lpad (str, "origin-hostname", app->origin_hostname);
 +      for (i = 0; i < app->prices->len; i++) {
 +              GsPrice *price = g_ptr_array_index (app->prices, i);
 +              g_autofree gchar *key = NULL, *text = NULL;
-               key = g_strdup_printf ("price-%02i", i);
++              key = g_strdup_printf ("price-%02u", i);
 +              text = gs_price_to_string (price);
 +              gs_app_kv_lpad (str, key, text);
 +      }
        if (app->rating != -1)
                gs_app_kv_printf (str, "rating", "%i", app->rating);
        if (app->review_ratings != NULL) {
@@@ -2877,8 -3360,8 +3405,9 @@@ gs_app_dispose (GObject *object
        g_clear_pointer (&app->history, g_ptr_array_unref);
        g_clear_pointer (&app->related, g_ptr_array_unref);
        g_clear_pointer (&app->screenshots, g_ptr_array_unref);
 +      g_clear_pointer (&app->prices, g_ptr_array_unref);
        g_clear_pointer (&app->reviews, g_ptr_array_unref);
+       g_clear_pointer (&app->provides, g_ptr_array_unref);
        g_clear_pointer (&app->icons, g_ptr_array_unref);
  
        G_OBJECT_CLASS (gs_app_parent_class)->dispose (object);
@@@ -3037,8 -3525,8 +3571,9 @@@ gs_app_init (GsApp *app
        app->related = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        app->history = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        app->screenshots = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
 +      app->prices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        app->reviews = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+       app->provides = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        app->icons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        app->metadata = g_hash_table_new_full (g_str_hash,
                                                g_str_equal,
diff --cc src/gs-page.c
index ce08ea7,d0c7b27..196d594
--- a/src/gs-page.c
+++ b/src/gs-page.c
@@@ -29,8 -29,7 +29,9 @@@
  #include "gs-shell.h"
  #include "gs-common.h"
  #include "gs-auth-dialog.h"
+ #include "gs-screenshot-image.h"
 +#include "gs-price.h"
 +#include "gs-purchase-dialog.h"
  
  typedef struct
  {
@@@ -63,33 -70,6 +72,29 @@@ gs_page_helper_free (GsPageHelper *help
  G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPageHelper, gs_page_helper_free);
  
  static void
 +gs_page_app_purchased_cb (GObject *source,
 +                          GAsyncResult *res,
 +                          gpointer user_data)
 +{
 +      g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
 +      GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
 +      GsPage *page = helper->page;
 +      GsPagePrivate *priv = gs_page_get_instance_private (page);
 +      gboolean ret;
 +      g_autoptr(GError) error = NULL;
 +
 +      ret = gs_plugin_loader_app_action_finish (plugin_loader,
 +                                                res,
 +                                                &error);
 +      if (!ret) {
 +              g_warning ("failed to purchase %s: %s",
 +                         gs_app_get_id (helper->app),
 +                         error->message);
-               gs_app_notify_failed_modal (helper->app,
-                                           gs_shell_get_window (priv->shell),
-                                           GS_PLUGIN_LOADER_ACTION_PURCHASE,
-                                           error);
 +              return;
 +      }
 +}
 +
 +static void
  gs_page_app_installed_cb (GObject *source,
                            GAsyncResult *res,
                            gpointer user_data);
@@@ -320,137 -265,7 +290,57 @@@ gs_page_set_header_end_widget (GsPage *
        g_set_object (&priv->header_end_widget, widget);
  }
  
 +static void
 +gs_page_payment_methods_cb (GObject *source,
 +                          GAsyncResult *res,
 +                          gpointer user_data);
 +
 +static void
- gs_page_payment_methods_authenticate_cb (GtkDialog *dialog,
-                                        GtkResponseType response_type,
-                                        GsPageHelper *helper)
- {
-       GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
- 
-       /* unmap the dialog */
-       gtk_widget_destroy (GTK_WIDGET (dialog));
- 
-       if (response_type != GTK_RESPONSE_OK) {
-               gs_page_helper_free (helper);
-               return;
-       }
-       gs_plugin_loader_get_payment_methods_async (priv->plugin_loader,
-                                                   helper->cancellable,
-                                                   gs_page_payment_methods_cb,
-                                                   helper);
- }
- 
- static void
 +gs_page_purchase_app_response_cb (GtkDialog *dialog,
 +                                gint response,
 +                                GsPageHelper *helper)
 +{
 +      GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
 +      GPtrArray *prices;
 +
 +      /* not agreed */
 +      if (response != GTK_RESPONSE_OK) {
 +              gs_page_helper_free (helper);
 +              return;
 +      }
 +      g_debug ("purchase %s", gs_app_get_id (helper->app));
 +      prices = gs_app_get_prices (helper->app);
 +      gs_plugin_loader_app_purchase_async (priv->plugin_loader,
 +                                           helper->app,
 +                                           g_ptr_array_index (prices, 0), // FIXME: User should pick price, 
check if no prices
 +                                           helper->cancellable,
 +                                           gs_page_app_purchased_cb,
 +                                           helper);
 +}
 +
- static void
- gs_page_payment_methods_cb (GObject *source,
-                           GAsyncResult *res,
-                           gpointer user_data)
++void
++gs_page_purchase_app (GsPage *page, GsApp *app, GCancellable *cancellable)
 +{
-       g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       GsPage *page = helper->page;
 +      GsPagePrivate *priv = gs_page_get_instance_private (page);
-       g_autoptr(GPtrArray) payment_methods = NULL;
-       g_autoptr(GError) error = NULL;
++      GsPageHelper *helper;
 +      GtkWidget *dialog;
 +
-       payment_methods = gs_plugin_loader_get_payment_methods_finish (plugin_loader,
-                                                                      res,
-                                                                      &error);
-       if (g_error_matches (error,
-                            G_IO_ERROR,
-                            G_IO_ERROR_CANCELLED)) {
-               g_debug ("%s", error->message);
-               return;
-       }
-       if (payment_methods == NULL) {
-               /* try to authenticate then retry */
-               if (g_error_matches (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_AUTH_REQUIRED)) {
-                       g_autoptr(GError) error_local = NULL;
-                       GtkWidget *auth_dialog;
-                       auth_dialog = gs_auth_dialog_new (priv->plugin_loader,
-                                                         helper->app,
-                                                         gs_utils_get_error_value (error),
-                                                         &error_local);
-                       if (auth_dialog == NULL) {
-                               g_warning ("%s", error_local->message);
-                               return;
-                       }
-                       gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (auth_dialog));
-                       g_signal_connect (auth_dialog, "response",
-                                         G_CALLBACK (gs_page_payment_methods_authenticate_cb),
-                                         g_steal_pointer (&helper));
-                       return;
-               }
- 
-               g_warning ("failed to get payment methods: %s",
-                          error->message);
-               gs_app_notify_failed_modal (helper->app,
-                                           gs_shell_get_window (priv->shell),
-                                           GS_PLUGIN_LOADER_ACTION_PURCHASE,
-                                           error);
-               return;
-       }
++      helper = g_slice_new0 (GsPageHelper);
++      helper->app = g_object_ref (app);
++      helper->page = g_object_ref (page);
++      helper->cancellable = g_object_ref (cancellable);
 +
 +      /* ask for confirmation */
 +      dialog = gs_purchase_dialog_new ();
 +      gs_purchase_dialog_set_app (GS_PURCHASE_DIALOG (dialog), helper->app);
-       gs_purchase_dialog_set_payment_methods (GS_PURCHASE_DIALOG (dialog), payment_methods);
 +
 +      /* handle this async */
 +      g_signal_connect (dialog, "response",
 +                        G_CALLBACK (gs_page_purchase_app_response_cb), g_steal_pointer (&helper));
 +      gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
 +}
 +
  void
- gs_page_purchase_app (GsPage *page, GsApp *app, GCancellable *cancellable)
- {
-       GsPagePrivate *priv = gs_page_get_instance_private (page);
-       GsPageHelper *helper;
- 
-       helper = g_slice_new0 (GsPageHelper);
-       helper->app = g_object_ref (app);
-       helper->page = g_object_ref (page);
-       helper->cancellable = g_object_ref (cancellable);
- 
-       /* load payment methods */
-       gs_plugin_loader_get_payment_methods_async (priv->plugin_loader,
-                                                   helper->cancellable,
-                                                   gs_page_payment_methods_cb,
-                                                   helper);
- }
- 
- void
  gs_page_install_app (GsPage *page, GsApp *app, GCancellable *cancellable)
  {
        GsPagePrivate *priv = gs_page_get_instance_private (page);
diff --cc src/gs-plugin-loader.c
index 9377fde,125715c..4edbd8e
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@@ -107,15 -120,6 +120,11 @@@ typedef gboolean  (*GsPluginAuthFunc)            
                                                         GsAuth         *auth,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
- typedef gboolean       (*GsPluginPaymentMethodFunc)   (GsPlugin       *plugin,
-                                                        GPtrArray      *payment_methods,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
 +typedef gboolean       (*GsPluginPurchaseFunc)        (GsPlugin       *plugin,
 +                                                       GsApp          *app,
 +                                                       GsPrice        *price,
 +                                                       GCancellable   *cancellable,
 +                                                       GError         **error);
  typedef gboolean       (*GsPluginRefineFunc)          (GsPlugin       *plugin,
                                                         GsAppList      *list,
                                                         GsPluginRefineFlags flags,
@@@ -156,8 -166,7 +171,9 @@@ typedef struct 
        GsApp                           *app;
        AsReview                        *review;
        GsAuth                          *auth;
+       GsPluginAction                   action;
 +      GPtrArray                       *payment_methods;
 +      GsPrice                         *price;
  } GsPluginLoaderAsyncState;
  
  static void
@@@ -2845,143 -3112,6 +3123,71 @@@ gs_plugin_loader_review_action_thread_c
        g_task_return_boolean (task, TRUE);
  }
  
 +static void
- gs_plugin_loader_add_payment_methods_thread_cb (GTask *task,
-                                               gpointer object,
-                                               gpointer task_data,
-                                               GCancellable *cancellable)
- {
-       GError *error = NULL;
-       GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       GsPlugin *plugin;
-       GsPluginPaymentMethodFunc plugin_func = NULL;
-       gboolean anything_ran = FALSE;
-       gboolean exists;
-       gboolean ret;
-       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 (!gs_plugin_get_enabled (plugin))
-                       continue;
-               if (g_cancellable_set_error_if_cancelled (cancellable, &error))
-                       g_task_return_error (task, error);
- 
-               exists = g_module_symbol (gs_plugin_get_module (plugin),
-                                         state->function_name,
-                                         (gpointer *) &plugin_func);
-               if (!exists)
-                       continue;
-               ptask = as_profile_start (priv->profile,
-                                         "GsPlugin::%s(%s)",
-                                         gs_plugin_get_name (plugin),
-                                         state->function_name);
-               gs_plugin_loader_action_start (plugin_loader, plugin, FALSE);
-               ret = plugin_func (plugin, state->payment_methods,
-                                  cancellable, &error_local);
-               gs_plugin_loader_action_stop (plugin_loader, plugin);
-               if (!ret) {
-                       /* abort early to allow main thread to process */
-                       if (gs_plugin_loader_is_auth_error (error_local)) {
-                               g_task_return_error (task, error_local);
-                               error_local = NULL;
-                               return;
-                       }
- 
-                       g_warning ("failed to call %s on %s: %s",
-                                  state->function_name,
-                                  gs_plugin_get_name (plugin),
-                                  error_local->message);
-                       continue;
-               }
-               anything_ran = TRUE;
-               gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
-       }
- 
-       /* nothing ran */
-       if (!anything_ran) {
-               g_set_error (&error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "no plugin could handle %s",
-                            state->function_name);
-               g_task_return_error (task, error);
-       }
- 
-       g_task_return_pointer (task, g_object_ref (state->payment_methods), g_object_unref);
- }
- 
- static void
 +gs_plugin_loader_app_purchase_thread_cb (GTask *task,
 +                                       gpointer object,
 +                                       gpointer task_data,
 +                                       GCancellable *cancellable)
 +{
 +      GError *error = NULL;
 +      GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
 +      GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
 +      GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
 +      GsPlugin *plugin;
 +      GsPluginPurchaseFunc plugin_func = NULL;
 +      gboolean anything_ran = FALSE;
 +      gboolean exists;
 +      gboolean ret;
 +      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 (!gs_plugin_get_enabled (plugin))
 +                      continue;
 +              if (g_cancellable_set_error_if_cancelled (cancellable, &error))
 +                      g_task_return_error (task, error);
 +
 +              exists = g_module_symbol (gs_plugin_get_module (plugin),
 +                                        state->function_name,
 +                                        (gpointer *) &plugin_func);
 +              if (!exists)
 +                      continue;
 +              ptask = as_profile_start (priv->profile,
 +                                        "GsPlugin::%s(%s)",
 +                                        gs_plugin_get_name (plugin),
 +                                        state->function_name);
 +              gs_plugin_loader_action_start (plugin_loader, plugin, FALSE);
 +              ret = plugin_func (plugin, state->app, state->price,
 +                                 cancellable, &error_local);
 +              gs_plugin_loader_action_stop (plugin_loader, plugin);
 +              if (!ret) {
 +                      g_warning ("failed to call %s on %s: %s",
 +                                 state->function_name,
 +                                 gs_plugin_get_name (plugin),
 +                                 error_local->message);
 +                      continue;
 +              }
 +              anything_ran = TRUE;
 +              gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
 +      }
 +
 +      /* nothing ran */
 +      if (!anything_ran) {
 +              g_set_error (&error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_NOT_SUPPORTED,
 +                           "no plugin could handle %s",
 +                           state->function_name);
 +              g_task_return_error (task, error);
 +      }
 +
 +      g_task_return_boolean (task, TRUE);
 +}
 +
  static gboolean
  load_install_queue (GsPluginLoader *plugin_loader, GError **error)
  {
@@@ -3220,83 -3356,6 +3432,39 @@@ gs_plugin_loader_app_action_async (GsPl
        g_task_run_in_thread (task, gs_plugin_loader_app_action_thread_cb);
  }
  
 +/**
-  * gs_plugin_loader_get_payment_methods_async:
-  **/
- void
- gs_plugin_loader_get_payment_methods_async (GsPluginLoader *plugin_loader,
-                                           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->payment_methods = g_ptr_array_new_with_free_func (g_object_unref);
-       state->function_name = "gs_plugin_add_payment_methods";
- 
-       /* 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_run_in_thread (task, gs_plugin_loader_add_payment_methods_thread_cb);
- }
- 
- /**
-  * gs_plugin_loader_get_payment_methods_finish:
-  *
-  * Return value: (element-type GsPaymentMethod) (transfer full): A list of payment methods.
-  **/
- GPtrArray *
- gs_plugin_loader_get_payment_methods_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_app_purchase_async:
 + **/
 +void
 +gs_plugin_loader_app_purchase_async (GsPluginLoader *plugin_loader,
 +                                   GsApp *app,
 +                                   GsPrice *price,
 +                                   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 (GS_IS_APP (app));
 +      g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 +
 +      /* save state */
 +      state = g_slice_new0 (GsPluginLoaderAsyncState);
 +      state->app = g_object_ref (app);
 +      state->price = g_object_ref (price);
 +      state->function_name = "gs_plugin_app_purchase";
 +
 +      /* 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_run_in_thread (task, gs_plugin_loader_app_purchase_thread_cb);
 +}
 +
 +/**
 + * gs_plugin_loader_review_action_async:
 + **/
  void
  gs_plugin_loader_review_action_async (GsPluginLoader *plugin_loader,
                                      GsApp *app,
diff --cc src/gs-plugin-types.h
index 0000000,b4f6d42..7fc5526
mode 000000,100644..100644
--- a/src/gs-plugin-types.h
+++ b/src/gs-plugin-types.h
@@@ -1,0 -1,307 +1,309 @@@
+ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+  *
+  * Copyright (C) 2012-2016 Richard Hughes <richard hughsie com>
+  *
+  * Licensed under the GNU General Public License Version 2
+  *
+  * This program is free software; you can redistribute it and/or modify
+  * it under the terms of the GNU General Public License as published by
+  * the Free Software Foundation; either version 2 of the License, or
+  * (at your option) any later version.
+  *
+  * This program is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  * GNU General Public License for more details.
+  *
+  * You should have received a copy of the GNU General Public License
+  * along with this program; if not, write to the Free Software
+  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+  */
+ 
+ #ifndef __GS_PLUGIN_TYPES_H
+ #define __GS_PLUGIN_TYPES_H
+ 
+ #include <glib-object.h>
+ 
+ G_BEGIN_DECLS
+ 
+ /**
+  * GsPluginStatus:
+  * @GS_PLUGIN_STATUS_UNKNOWN:         Unknown status
+  * @GS_PLUGIN_STATUS_WAITING:         Waiting
+  * @GS_PLUGIN_STATUS_FINISHED:                Finished
+  * @GS_PLUGIN_STATUS_SETUP:           Setup in progress
+  * @GS_PLUGIN_STATUS_DOWNLOADING:     Downloading in progress
+  * @GS_PLUGIN_STATUS_QUERYING:                Querying in progress
+  * @GS_PLUGIN_STATUS_INSTALLING:      Installing in progress
+  * @GS_PLUGIN_STATUS_REMOVING:                Removing in progress
+  *
+  * The ststus of the plugin.
+  **/
+ typedef enum {
+       GS_PLUGIN_STATUS_UNKNOWN,
+       GS_PLUGIN_STATUS_WAITING,
+       GS_PLUGIN_STATUS_FINISHED,
+       GS_PLUGIN_STATUS_SETUP,
+       GS_PLUGIN_STATUS_DOWNLOADING,
+       GS_PLUGIN_STATUS_QUERYING,
+       GS_PLUGIN_STATUS_INSTALLING,
+       GS_PLUGIN_STATUS_REMOVING,
+       /*< private >*/
+       GS_PLUGIN_STATUS_LAST
+ } GsPluginStatus;
+ 
+ /**
+  * GsPluginFlags:
+  * @GS_PLUGIN_FLAGS_NONE:             No flags set
+  * @GS_PLUGIN_FLAGS_RUNNING_SELF:     The plugin is running
+  * @GS_PLUGIN_FLAGS_RUNNING_OTHER:    Another plugin is running
+  * @GS_PLUGIN_FLAGS_EXCLUSIVE:                An exclusive action is running
+  * @GS_PLUGIN_FLAGS_RECENT:           This plugin recently ran
+  * @GS_PLUGIN_FLAGS_GLOBAL_CACHE:     Use the global app cache
+  *
+  * The flags for the plugin at this point in time.
+  **/
+ #define GS_PLUGIN_FLAGS_NONE          (0u)
+ #define GS_PLUGIN_FLAGS_RUNNING_SELF  (1u << 0)
+ #define GS_PLUGIN_FLAGS_RUNNING_OTHER (1u << 1)
+ #define GS_PLUGIN_FLAGS_EXCLUSIVE     (1u << 2)
+ #define GS_PLUGIN_FLAGS_RECENT                (1u << 3)
+ #define GS_PLUGIN_FLAGS_GLOBAL_CACHE  (1u << 4)
+ typedef guint64 GsPluginFlags;
+ 
+ /**
+  * GsPluginError:
+  * @GS_PLUGIN_ERROR_FAILED:                   Generic failure
+  * @GS_PLUGIN_ERROR_NOT_SUPPORTED:            Action not supported
+  * @GS_PLUGIN_ERROR_CANCELLED:                        Action was cancelled
+  * @GS_PLUGIN_ERROR_NO_NETWORK:                       No network connection available
+  * @GS_PLUGIN_ERROR_NO_SECURITY:              Security policy forbid action
+  * @GS_PLUGIN_ERROR_NO_SPACE:                 No disk space to allow action
+  * @GS_PLUGIN_ERROR_AUTH_REQUIRED:            Authentication was required
+  * @GS_PLUGIN_ERROR_AUTH_INVALID:             Provided authentication was invalid
+  * @GS_PLUGIN_ERROR_PIN_REQUIRED:             PIN required for authentication
+  * @GS_PLUGIN_ERROR_ACCOUNT_SUSPENDED:                User account has been suspended
+  * @GS_PLUGIN_ERROR_ACCOUNT_DEACTIVATED:      User account has been deactivated
+  * @GS_PLUGIN_ERROR_PLUGIN_DEPSOLVE_FAILED:   The plugins installed are incompatible
+  * @GS_PLUGIN_ERROR_DOWNLOAD_FAILED:          The download action failed
+  * @GS_PLUGIN_ERROR_WRITE_FAILED:             The save-to-disk failed
+  * @GS_PLUGIN_ERROR_INVALID_FORMAT:           The data format is invalid
+  * @GS_PLUGIN_ERROR_DELETE_FAILED:            The delete action failed
+  *
+  * The failure error types.
+  **/
+ typedef enum {
+       GS_PLUGIN_ERROR_FAILED,
+       GS_PLUGIN_ERROR_NOT_SUPPORTED,
+       GS_PLUGIN_ERROR_CANCELLED,
+       GS_PLUGIN_ERROR_NO_NETWORK,
+       GS_PLUGIN_ERROR_NO_SECURITY,
+       GS_PLUGIN_ERROR_NO_SPACE,
+       GS_PLUGIN_ERROR_AUTH_REQUIRED,
+       GS_PLUGIN_ERROR_AUTH_INVALID,
+       GS_PLUGIN_ERROR_PIN_REQUIRED,
+       GS_PLUGIN_ERROR_ACCOUNT_SUSPENDED,
+       GS_PLUGIN_ERROR_ACCOUNT_DEACTIVATED,
+       GS_PLUGIN_ERROR_PLUGIN_DEPSOLVE_FAILED,
+       GS_PLUGIN_ERROR_DOWNLOAD_FAILED,
+       GS_PLUGIN_ERROR_WRITE_FAILED,
+       GS_PLUGIN_ERROR_INVALID_FORMAT,
+       GS_PLUGIN_ERROR_DELETE_FAILED,
+       /*< private >*/
+       GS_PLUGIN_ERROR_LAST
+ } GsPluginError;
+ 
+ /**
+  * GsPluginRefineFlags:
+  * @GS_PLUGIN_REFINE_FLAGS_DEFAULT:                   No explicit flags set
+  * @GS_PLUGIN_REFINE_FLAGS_USE_HISTORY:                       Get the historical view
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE:           Require the license
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL:                       Require the URL
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION:               Require the long description
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE:              Require the installed and download sizes
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING:            Require the rating
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION:           Require the version
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY:           Require the history
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION:      Require enough to install or remove the package
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS:    Require update details
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN:            Require the origin
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED:           Require related packages
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH:         Require the menu path
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS:            Require available addons
+  * @GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES:            Allow packages to be returned
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_SEVERITY:   Require update severity
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPGRADE_REMOVED:   Require distro upgrades
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE:                Require the provenance
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS:           Require user-reviews
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS:    Require user-ratings
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS:                Require the key colors
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON:              Require the icon to be loaded
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS:               Require the needed permissions
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME:   Require the origin hostname
+  * @GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_UI:         Require the origin for UI
+  *
+  * The refine flags.
+  **/
+ #define GS_PLUGIN_REFINE_FLAGS_DEFAULT                        (0u)
+ #define GS_PLUGIN_REFINE_FLAGS_USE_HISTORY            (1u << 0)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE                (1u << 1)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL            (1u << 2)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION    (1u << 3)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE           (1u << 4)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING         (1u << 5)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION                (1u << 6)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY                (1u << 7)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION   (1u << 8)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS (1u << 9)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN         (1u << 10)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED                (1u << 11)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH      (1u << 12)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS         (1u << 13)
+ #define GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES         (1u << 14)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_SEVERITY        (1u << 15)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPGRADE_REMOVED        (1u << 16)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE     (1u << 17)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS                (1u << 18)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS (1u << 19)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS     (1u << 20)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON           (1u << 21)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS    (1u << 22)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME        (1u << 23)
+ #define GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_UI      (1u << 24)
+ typedef guint64 GsPluginRefineFlags;
+ 
+ /**
+  * GsPluginRefreshFlags:
+  * @GS_PLUGIN_REFRESH_FLAGS_NONE:     Generate new metadata if possible
+  * @GS_PLUGIN_REFRESH_FLAGS_METADATA: Download new metadata
+  * @GS_PLUGIN_REFRESH_FLAGS_PAYLOAD:  Download any pending payload
+  * @GS_PLUGIN_REFRESH_FLAGS_INTERACTIVE: Running by user request
+  *
+  * The flags used for refresh. Regeneration and downloading is only
+  * done if the cache is older than the %cache_age.
+  *
+  * The %GS_PLUGIN_REFRESH_FLAGS_METADATA can be used to make sure
+  * there's enough metadata to start the application.
+  * The %GS_PLUGIN_REFRESH_FLAGS_PAYLOAD flag should only be used when
+  * the session is idle and bandwidth is unmetered as the amount of data
+  * and IO may be large.
+  **/
+ typedef enum {
+       GS_PLUGIN_REFRESH_FLAGS_NONE                    = 0,
+       GS_PLUGIN_REFRESH_FLAGS_METADATA                = 1 << 0,
+       GS_PLUGIN_REFRESH_FLAGS_PAYLOAD                 = 1 << 1,
+       GS_PLUGIN_REFRESH_FLAGS_INTERACTIVE             = 1 << 2,
+       /*< private >*/
+       GS_PLUGIN_REFRESH_FLAGS_LAST
+ } GsPluginRefreshFlags;
+ 
+ /**
+  * GsPluginRule:
+  * @GS_PLUGIN_RULE_CONFLICTS:         The plugin conflicts with another
+  * @GS_PLUGIN_RULE_RUN_AFTER:         Order the plugin after another
+  * @GS_PLUGIN_RULE_RUN_BEFORE:                Order the plugin before another
+  * @GS_PLUGIN_RULE_BETTER_THAN:               Results are better than another
+  *
+  * The rules used for ordering plugins.
+  * Plugins are expected to add rules in gs_plugin_initialize().
+  **/
+ typedef enum {
+       GS_PLUGIN_RULE_CONFLICTS,
+       GS_PLUGIN_RULE_RUN_AFTER,
+       GS_PLUGIN_RULE_RUN_BEFORE,
+       GS_PLUGIN_RULE_BETTER_THAN,
+       /*< private >*/
+       GS_PLUGIN_RULE_LAST
+ } GsPluginRule;
+ 
+ /**
+  * GsPluginAction:
+  * @GS_PLUGIN_ACTION_UNKNOWN:                 Action is unknown
+  * @GS_PLUGIN_ACTION_SETUP:                   Plugin setup (internal)
+  * @GS_PLUGIN_ACTION_INSTALL:                 Install an application
+  * @GS_PLUGIN_ACTION_REMOVE:                  Remove an application
+  * @GS_PLUGIN_ACTION_UPDATE:                  Update an application
+  * @GS_PLUGIN_ACTION_SET_RATING:              Set rating on an application
+  * @GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD:                Download a distro upgrade
+  * @GS_PLUGIN_ACTION_UPGRADE_TRIGGER:         Trigger a distro upgrade
+  * @GS_PLUGIN_ACTION_LAUNCH:                  Launch an application
+  * @GS_PLUGIN_ACTION_UPDATE_CANCEL:           Cancel the update
+  * @GS_PLUGIN_ACTION_ADD_SHORTCUT:            Add a shortcut to an application
+  * @GS_PLUGIN_ACTION_REMOVE_SHORTCUT:         Remove a shortcut to an application
+  * @GS_PLUGIN_ACTION_REVIEW_SUBMIT:           Submit a new review
+  * @GS_PLUGIN_ACTION_REVIEW_UPVOTE:           Upvote an existing review
+  * @GS_PLUGIN_ACTION_REVIEW_DOWNVOTE:         Downvote an existing review
+  * @GS_PLUGIN_ACTION_REVIEW_REPORT:           Report an existing review
+  * @GS_PLUGIN_ACTION_REVIEW_REMOVE:           Remove a review written by the user
+  * @GS_PLUGIN_ACTION_REVIEW_DISMISS:          Dismiss (ignore) a review when moderating
+  * @GS_PLUGIN_ACTION_GET_UPDATES:             Get the list of updates
+  * @GS_PLUGIN_ACTION_GET_DISTRO_UPDATES:      Get the list of distro updates
+  * @GS_PLUGIN_ACTION_GET_UNVOTED_REVIEWS:     Get the list of moderatable reviews
+  * @GS_PLUGIN_ACTION_GET_SOURCES:             Get the list of sources
+  * @GS_PLUGIN_ACTION_GET_INSTALLED:           Get the list of installed applications
+  * @GS_PLUGIN_ACTION_GET_POPULAR:             Get the list of popular applications
+  * @GS_PLUGIN_ACTION_GET_FEATURED:            Get the list of featured applications
+  * @GS_PLUGIN_ACTION_SEARCH:                  Get the search results for a query
+  * @GS_PLUGIN_ACTION_SEARCH_FILES:            Get the search results for a file query
+  * @GS_PLUGIN_ACTION_SEARCH_PROVIDES:         Get the search results for a provide query
+  * @GS_PLUGIN_ACTION_GET_CATEGORIES:          Get the list of categories
+  * @GS_PLUGIN_ACTION_GET_CATEGORY_APPS:               Get the apps for a specific category
+  * @GS_PLUGIN_ACTION_REFINE:                  Refine the application
+  * @GS_PLUGIN_ACTION_REFRESH:                 Refresh all the sources
+  * @GS_PLUGIN_ACTION_FILE_TO_APP:             Convert the file to an application
+  * @GS_PLUGIN_ACTION_AUTH_LOGIN:              Authentication login action
+  * @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_PURCHASE:                        Purchase an app
+  *
+  * The plugin action.
+  **/
+ typedef enum {
+       GS_PLUGIN_ACTION_UNKNOWN,
+       GS_PLUGIN_ACTION_SETUP,
+       GS_PLUGIN_ACTION_INSTALL,
+       GS_PLUGIN_ACTION_REMOVE,
+       GS_PLUGIN_ACTION_UPDATE,
+       GS_PLUGIN_ACTION_SET_RATING,
+       GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
+       GS_PLUGIN_ACTION_UPGRADE_TRIGGER,
+       GS_PLUGIN_ACTION_LAUNCH,
+       GS_PLUGIN_ACTION_UPDATE_CANCEL,
+       GS_PLUGIN_ACTION_ADD_SHORTCUT,
+       GS_PLUGIN_ACTION_REMOVE_SHORTCUT,
+       GS_PLUGIN_ACTION_REVIEW_SUBMIT,
+       GS_PLUGIN_ACTION_REVIEW_UPVOTE,
+       GS_PLUGIN_ACTION_REVIEW_DOWNVOTE,
+       GS_PLUGIN_ACTION_REVIEW_REPORT,
+       GS_PLUGIN_ACTION_REVIEW_REMOVE,
+       GS_PLUGIN_ACTION_REVIEW_DISMISS,
+       GS_PLUGIN_ACTION_GET_UPDATES,
+       GS_PLUGIN_ACTION_GET_DISTRO_UPDATES,
+       GS_PLUGIN_ACTION_GET_UNVOTED_REVIEWS,
+       GS_PLUGIN_ACTION_GET_SOURCES,
+       GS_PLUGIN_ACTION_GET_INSTALLED,
+       GS_PLUGIN_ACTION_GET_POPULAR,
+       GS_PLUGIN_ACTION_GET_FEATURED,
+       GS_PLUGIN_ACTION_SEARCH,
+       GS_PLUGIN_ACTION_SEARCH_FILES,
+       GS_PLUGIN_ACTION_SEARCH_PROVIDES,
+       GS_PLUGIN_ACTION_GET_CATEGORIES,
+       GS_PLUGIN_ACTION_GET_CATEGORY_APPS,
+       GS_PLUGIN_ACTION_REFINE,
+       GS_PLUGIN_ACTION_REFRESH,
+       GS_PLUGIN_ACTION_FILE_TO_APP,
+       GS_PLUGIN_ACTION_AUTH_LOGIN,
+       GS_PLUGIN_ACTION_AUTH_LOGOUT,
+       GS_PLUGIN_ACTION_AUTH_REGISTER,
+       GS_PLUGIN_ACTION_AUTH_LOST_PASSWORD,
++      GS_PLUGIN_ACTION_PURCHASE,
+       /*< private >*/
+       GS_PLUGIN_ACTION_LAST
+ } GsPluginAction;
+ 
+ G_END_DECLS
+ 
+ #endif /* __GS_PLUGIN_TYPES_H */
+ 
+ /* vim: set noexpandtab: */
diff --cc src/gs-plugin-vfuncs.h
index 5fef056,713d351..91bf3ed
--- a/src/gs-plugin-vfuncs.h
+++ b/src/gs-plugin-vfuncs.h
@@@ -40,8 -40,6 +40,7 @@@
  #include "gs-app.h"
  #include "gs-app-list.h"
  #include "gs-category.h"
 +#include "gs-price.h"
- #include "gs-payment-method.h"
  
  G_BEGIN_DECLS
  
@@@ -499,45 -542,6 +543,27 @@@ gboolean  gs_plugin_update_cancel                (GsP
                                                         GError         **error);
  
  /**
-  * gs_plugin_add_payment_methods:
-  * @plugin: a #GsPlugin
-  * @payment_methods: (element-type GsPaymentMethod): a #GPtrArray
-  * @cancellable: a #GCancellable, or %NULL
-  * @error: a #GError, or %NULL
-  *
-  * Get the available payment methods for purchasing apps.
-  *
-  * Returns: %TRUE for success or if not relevant
-  **/
- gboolean       gs_plugin_add_payment_methods          (GsPlugin               *plugin,
-                                                        GPtrArray              *payment_methods,
-                                                        GCancellable           *cancellable,
-                                                        GError                 **error);
- 
- /**
 + * gs_plugin_app_purchase:
 + * @plugin: a #GsPlugin
 + * @app: a #GsApp
 + * @price: a #GsPrice
-  * @payment_method: (allow-none): a #GsPaymentMethod, or %NULL
 + * @cancellable: a #GCancellable, or %NULL
 + * @error: a #GError, or %NULL
 + *
 + * Purchase the application.
 + *
 + * NOTE: Once the action is complete, the plugin must set the new state of @app
 + * to %AS_APP_STATE_AVAILABLE.
 + *
 + * Returns: %TRUE for success or if not relevant
 + **/
 +gboolean       gs_plugin_app_purchase                 (GsPlugin               *plugin,
 +                                                       GsApp                  *app,
 +                                                       GsPrice                *price,
-                                                        GsPaymentMethod        *payment_method,
 +                                                       GCancellable           *cancellable,
 +                                                       GError                 **error);
 +
 +/**
   * gs_plugin_app_install:
   * @plugin: a #GsPlugin
   * @app: a #GsApp
diff --cc src/gs-plugin.h
index 3400959,648bfcd..70690fe
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@@ -32,7 -32,8 +32,9 @@@
  #include "gs-app-list.h"
  #include "gs-auth.h"
  #include "gs-category.h"
+ #include "gs-plugin-event.h"
+ #include "gs-plugin-types.h"
 +#include "gs-price.h"
  
  G_BEGIN_DECLS
  
diff --cc src/gs-purchase-dialog.c
index 58d799f,0000000..a9bbbcd
mode 100644,000000..100644
--- a/src/gs-purchase-dialog.c
+++ b/src/gs-purchase-dialog.c
@@@ -1,107 -1,0 +1,79 @@@
 +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 + *
 + * Copyright (C) 2016 Canonical Ltd.
 + *
 + * Licensed under the GNU General Public License Version 2
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License as published by
 + * the Free Software Foundation; either version 2 of the License, or
 + * (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 + */
 +
 +#include "config.h"
 +
 +#include <glib/gi18n.h>
 +#include <gtk/gtk.h>
 +
 +#include "gs-purchase-dialog.h"
 +
 +struct _GsPurchaseDialog
 +{
 +      GtkDialog        parent_instance;
 +
 +      GtkWidget       *label_title;
 +      GtkWidget       *combo_payment_method;
 +      GtkListStore    *model_payment_method;
 +};
 +
 +G_DEFINE_TYPE (GsPurchaseDialog, gs_purchase_dialog, GTK_TYPE_DIALOG)
 +
 +void
 +gs_purchase_dialog_set_app (GsPurchaseDialog *dialog, GsApp *app)
 +{
 +      g_return_if_fail (GS_IS_PURCHASE_DIALOG (dialog));
 +}
 +
- void
- gs_purchase_dialog_set_payment_methods (GsPurchaseDialog *dialog, GPtrArray *payment_methods)
- {
-       guint i;
- 
-       g_return_if_fail (GS_IS_PURCHASE_DIALOG (dialog));
- 
-       gtk_list_store_clear (dialog->model_payment_method);
-       for (i = 0; i < payment_methods->len; i++) {
-               GtkTreeIter iter;
-               GsPaymentMethod *method = payment_methods->pdata[i];
- 
-               gtk_list_store_append (dialog->model_payment_method, &iter);
-               gtk_list_store_set (dialog->model_payment_method, &iter,
-                                   0, gs_payment_method_get_description (method),
-                                   1, method,
-                                   -1);
-       }
- }
- 
- 
 +GsPrice *
 +gs_purchase_dialog_get_price (GsPurchaseDialog *dialog)
 +{
 +      g_return_val_if_fail (GS_IS_PURCHASE_DIALOG (dialog), NULL);
 +      return NULL;
 +}
 +
- GsPaymentMethod *
- gs_purchase_dialog_get_payment_method (GsPurchaseDialog *dialog)
- {
-       g_return_val_if_fail (GS_IS_PURCHASE_DIALOG (dialog), NULL);
-       return NULL;
- }
- 
 +static void
 +gs_purchase_dialog_init (GsPurchaseDialog *dialog)
 +{
 +      gtk_widget_init_template (GTK_WIDGET (dialog));
 +}
 +
 +static void
 +gs_purchase_dialog_class_init (GsPurchaseDialogClass *klass)
 +{
 +      GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 +
 +      gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Software/gs-purchase-dialog.ui");
 +
 +      gtk_widget_class_bind_template_child (widget_class, GsPurchaseDialog, label_title);
 +      gtk_widget_class_bind_template_child (widget_class, GsPurchaseDialog, combo_payment_method);
 +      gtk_widget_class_bind_template_child (widget_class, GsPurchaseDialog, model_payment_method);
 +}
 +
 +GtkWidget *
 +gs_purchase_dialog_new (void)
 +{
 +      return GTK_WIDGET (g_object_new (GS_TYPE_PURCHASE_DIALOG,
 +                                       "use-header-bar", TRUE,
 +                                       NULL));
 +}
 +
 +/* vim: set noexpandtab: */
diff --cc src/gs-purchase-dialog.h
index 9ef63a1,0000000..fb3714e
mode 100644,000000..100644
--- a/src/gs-purchase-dialog.h
+++ b/src/gs-purchase-dialog.h
@@@ -1,53 -1,0 +1,47 @@@
 +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 + *
 + * Copyright (C) 2016 Canonical Ltd.
 + *
 + * Licensed under the GNU General Public License Version 2
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License as published by
 + * the Free Software Foundation; either version 2 of the License, or
 + * (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 + */
 +
 +#ifndef GS_PURCHASE_DIALOG_H
 +#define GS_PURCHASE_DIALOG_H
 +
 +#include <gtk/gtk.h>
 +
 +#include "gs-app.h"
 +#include "gs-price.h"
- #include "gs-payment-method.h"
 +
 +G_BEGIN_DECLS
 +
 +#define GS_TYPE_PURCHASE_DIALOG (gs_purchase_dialog_get_type ())
 +
 +G_DECLARE_FINAL_TYPE (GsPurchaseDialog, gs_purchase_dialog, GS, PURCHASE_DIALOG, GtkDialog)
 +
 +GtkWidget     *gs_purchase_dialog_new                 (void);
 +
 +void           gs_purchase_dialog_set_app             (GsPurchaseDialog       *dialog,
 +                                                       GsApp                  *app);
 +
- void           gs_purchase_dialog_set_payment_methods (GsPurchaseDialog       *dialog,
-                                                        GPtrArray              *payment_methods);
- 
 +GsPrice               *gs_purchase_dialog_get_price           (GsPurchaseDialog       *dialog);
 +
- GsPaymentMethod       *gs_purchase_dialog_get_payment_method  (GsPurchaseDialog       *dialog);
- 
 +G_END_DECLS
 +
 +#endif /* GS_PURCHASE_DIALOG_H */
 +
 +/* vim: set noexpandtab: */
diff --cc src/plugins/gs-plugin-dummy.c
index a9290f1,80cf496..994f158
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@@ -651,43 -658,6 +691,24 @@@ gs_plugin_update_cancel (GsPlugin *plug
        return TRUE;
  }
  
 +/**
-  * gs_plugin_add_payment_methods:
-  */
- gboolean
- gs_plugin_add_payment_methods (GsPlugin *plugin,
-                              GPtrArray *payment_methods,
-                              GCancellable *cancellable,
-                              GError **error)
- {
-       GsPaymentMethod *method;
-       g_debug ("Adding payment methods");
-       method = gs_payment_method_new ();
-       gs_payment_method_set_description (method, "Test Payment Method");
-       gs_payment_method_add_metadata (method, "card-number", "0000 0000 0000 0000");
-       g_ptr_array_add (payment_methods, method);
-       return TRUE;
- }
- 
- /**
 + * gs_plugin_app_purchase:
 + */
 +gboolean
 +gs_plugin_app_purchase (GsPlugin *plugin,
 +                      GsApp *app,
 +                      GsPrice *price,
-                       GsPaymentMethod *payment_method,
 +                      GCancellable *cancellable,
 +                      GError **error)
 +{
 +      g_debug ("Purchasing app");
 +      gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
 +      return TRUE;
 +}
 +
 +/**
 + * gs_plugin_review_submit:
 + */
  gboolean
  gs_plugin_review_submit (GsPlugin *plugin,
                         GsApp *app,
diff --cc src/plugins/gs-plugin-snap.c
index 80d2877,7310c9f..3b4edc7
--- a/src/plugins/gs-plugin-snap.c
+++ b/src/plugins/gs-plugin-snap.c
@@@ -672,71 -363,6 +380,35 @@@ progress_cb (JsonObject *result, gpoint
  }
  
  gboolean
 +gs_plugin_app_purchase (GsPlugin *plugin,
 +                      GsApp *app,
 +                      GsPrice *price,
-                       GsPaymentMethod *payment_method,
 +                      GCancellable *cancellable,
 +                      GError **error)
 +{
-       g_autoptr(JsonBuilder) builder = NULL;
-       g_autofree gchar *value = NULL;
-       g_autoptr(JsonNode) json_root = NULL;
-       g_autoptr(JsonGenerator) json_generator = NULL;
-       g_autofree gchar *data = NULL;
-       guint status_code;
-       g_autofree gchar *reason_phrase = NULL;
-       g_autofree gchar *response_type = NULL;
-       g_autofree gchar *response = NULL;
++      g_autofree gchar *macaroon = NULL;
++      g_auto(GStrv) discharges = NULL;
 +
 +      /* We can only buy apps we know of */
 +      if (g_strcmp0 (gs_app_get_management_plugin (app), "snap") != 0)
 +              return TRUE;
 +
-       builder = json_builder_new ();
-       json_builder_begin_object (builder);
-       json_builder_set_member_name (builder, "snap-id");
-       json_builder_add_string_value (builder, gs_app_get_metadata_item (app, "snap::id"));
-       json_builder_set_member_name (builder, "snap-name");
-       json_builder_add_string_value (builder, gs_app_get_id (app));
-       json_builder_set_member_name (builder, "snap-price");
-       value = g_strdup_printf ("%f", gs_price_get_amount (price));
-       json_builder_add_string_value (builder, value);
-       json_builder_set_member_name (builder, "currency");
-       json_builder_add_string_value (builder, gs_price_get_currency (price));
-       json_builder_end_object (builder);
- 
-       json_root = json_builder_get_root (builder);
-       json_generator = json_generator_new ();
-       json_generator_set_pretty (json_generator, TRUE);
-       json_generator_set_root (json_generator, json_root);
-       data = json_generator_to_data (json_generator, NULL);
-       if (data == NULL) {
-               g_set_error_literal (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_FAILED,
-                                    "Failed to generate JSON request");
-               return FALSE;
-       }
- 
-       if (!gs_snapd_request ("POST", "/v2/buy", data,
-                              NULL, NULL,
-                              &status_code, &reason_phrase,
-                              &response_type, &response, NULL,
-                              cancellable, error))
-               return FALSE;
- 
-       if (status_code != SOUP_STATUS_OK) {
-               // FIXME
-               return FALSE;
-       }
++      get_macaroon (plugin, &macaroon, &discharges);
 +
 +      gs_app_set_state (app, AS_APP_STATE_PURCHASING);
++      if (!gs_snapd_buy (macaroon, discharges,
++                         gs_app_get_metadata_item (app, "snap::id"),
++                         gs_price_get_amount (price),
++                         gs_price_get_currency (price),
++                         cancellable, error))
++              gs_app_set_state_recover (app);
++              return FALSE;
 +      gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
++
 +      return TRUE;
 +}
 +
 +gboolean
  gs_plugin_app_install (GsPlugin *plugin,
                       GsApp *app,
                       GCancellable *cancellable,
diff --cc src/plugins/gs-snapd.c
index a7e2cb8,4a75bb6..c0ce1ff
--- a/src/plugins/gs-snapd.c
+++ b/src/plugins/gs-snapd.c
@@@ -338,41 -336,327 +336,380 @@@ parse_result (const gchar *response, co
        if (!JSON_NODE_HOLDS_OBJECT (json_parser_get_root (parser))) {
                g_set_error_literal (error,
                                     GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_FAILED,
+                                    GS_PLUGIN_ERROR_INVALID_FORMAT,
                                     "snapd response does is not a valid JSON object");
-               return FALSE;
+               return NULL;
+       }
+ 
+       return g_object_ref (parser);
+ }
+ 
+ JsonObject *
+ gs_snapd_list_one (const gchar *macaroon, gchar **discharges,
+                  const gchar *name,
+                  GCancellable *cancellable, GError **error)
+ {
+       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, *result;
+ 
+       path = g_strdup_printf ("/v2/snaps/%s", name);
+       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_INVALID_FORMAT,
+                            "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_object_member (root, "result");
+       if (result == NULL) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_INVALID_FORMAT,
+                            "snapd returned no results for %s", name);
+               return NULL;
+       }
+ 
+       return json_object_ref (result);
+ }
+ 
+ JsonArray *
+ gs_snapd_list (const gchar *macaroon, gchar **discharges,
+              GCancellable *cancellable, GError **error)
+ {
+       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;
+ 
+       if (!send_request ("GET", "/v2/snaps", 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);
+ }
+ 
+ JsonArray *
+ gs_snapd_find (const gchar *macaroon, gchar **discharges,
+              gchar **values,
+              GCancellable *cancellable, GError **error)
+ {
+       g_autoptr(GString) path = NULL;
+       g_autofree gchar *query = NULL;
+       g_autofree gchar *escaped = 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;
+ 
+       path = g_string_new ("/v2/find?q=");
+       query = g_strjoinv (" ", values);
+       escaped = soup_uri_encode (query, NULL);
+       g_string_append (path, escaped);
+       if (!send_request ("GET", path->str, 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)
+ {
+       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;
+       JsonObject *result;
+ 
+       if (!send_request ("GET", "/v2/interfaces", 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));
-       if (!json_object_has_member (root, "result")) {
+       result = json_object_get_object_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_object_ref (result);
+ }
+ 
++gboolean
++gs_snapd_buy (const gchar *macaroon, gchar **discharges, const gchar *id, gdouble price, const gchar 
*currency, GCancellable *cancellable, GError **error)
++{
++      g_autoptr(JsonBuilder) builder = NULL;
++      g_autofree gchar *value = NULL;
++      g_autoptr(JsonNode) json_root = NULL;
++      g_autoptr(JsonGenerator) json_generator = NULL;
++      g_autofree gchar *data = NULL;
++      guint status_code;
++      g_autofree gchar *reason_phrase = NULL;
++
++      builder = json_builder_new ();
++      json_builder_begin_object (builder);
++      json_builder_set_member_name (builder, "snap-id");
++      json_builder_add_string_value (builder, id);
++      json_builder_set_member_name (builder, "price");
++      json_builder_add_double_value (builder, price);
++      json_builder_set_member_name (builder, "currency");
++      json_builder_add_string_value (builder, currency);
++      json_builder_end_object (builder);
++
++      json_root = json_builder_get_root (builder);
++      json_generator = json_generator_new ();
++      json_generator_set_pretty (json_generator, TRUE);
++      json_generator_set_root (json_generator, json_root);
++      data = json_generator_to_data (json_generator, NULL);
++      if (data == NULL) {
 +              g_set_error_literal (error,
 +                                   GS_PLUGIN_ERROR,
 +                                   GS_PLUGIN_ERROR_FAILED,
-                                    "snapd response does not contain a \"result\" field");
++                                   "Failed to generate JSON request");
++              return FALSE;
++      }
++
++      if (!send_request ("POST", "/v2/buy", NULL,
++                         macaroon, discharges,
++                         &status_code, &reason_phrase,
++                         NULL, NULL, NULL,
++                         cancellable, error))
++              return FALSE;
++
++      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 FALSE;
 +      }
-       if (result != NULL)
-               *result = json_object_ref (json_object_get_object_member (root, "result"));
 +
 +      return TRUE;
 +}
 +
- gboolean
- gs_snapd_parse_error (const gchar     *response_type,
-                     const gchar       *response,
-                     gchar             **message,
-                     gchar             **kind,
-                     GError            **error)
+ static JsonObject *
+ get_changes (const gchar *macaroon, gchar **discharges,
+            const gchar *change_id,
+            GCancellable *cancellable, GError **error)
  {
-       g_autoptr(JsonObject) result = 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, *result;
+ 
+       path = g_strdup_printf ("/v2/changes/%s", change_id);
+       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_object_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_object_ref (result);
+ }
  
-       if (!gs_snapd_parse_result (response_type, response, &result, error))
+ static gboolean
+ send_package_action (const gchar *macaroon,
+                    gchar **discharges,
+                    const gchar *name,
+                    const gchar *action,
+                    GsSnapdProgressCallback callback,
+                    gpointer user_data,
+                    GCancellable *cancellable,
+                    GError **error)
+ {
+       g_autofree gchar *content = NULL, *path = NULL;
+       guint status_code;
+       g_autofree gchar *reason_phrase = NULL;
+       g_autofree gchar *response_type = NULL;
+       g_autofree gchar *response = NULL;
+       g_autofree gchar *status = NULL;
+       g_autoptr(JsonParser) parser = NULL;
+       JsonObject *root, *result;
+       const gchar *type;
+ 
+       content = g_strdup_printf ("{\"action\": \"%s\"}", action);
+       path = g_strdup_printf ("/v2/snaps/%s", name);
+       if (!send_request ("POST", path, content,
+                          macaroon, discharges,
+                          &status_code, &reason_phrase,
+                          &response_type, &response, NULL,
+                          cancellable, error))
                return FALSE;
  
-       if (message != NULL)
-               *message = g_strdup (json_object_get_string_member (result, "message"));
-       if (kind != NULL)
-               *kind = json_object_has_member (result, "kind") ? g_strdup (json_object_get_string_member 
(result, "kind")) : NULL;
+       if (status_code == SOUP_STATUS_UNAUTHORIZED) {
+               g_set_error_literal (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_AUTH_REQUIRED,
+                                    "Requires authentication with @snapd");
+               return FALSE;
+       }
+ 
+       if (status_code != SOUP_STATUS_ACCEPTED) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "snapd returned status code %u: %s",
+                            status_code, reason_phrase);
+               return FALSE;
+       }
+ 
+       parser = parse_result (response, response_type, error);
+       if (parser == NULL)
+               return FALSE;
+ 
+       root = json_node_get_object (json_parser_get_root (parser));
+       type = json_object_get_string_member (root, "type");
+ 
+       if (g_strcmp0 (type, "async") == 0) {
+               const gchar *change_id;
+ 
+               change_id = json_object_get_string_member (root, "change");
+ 
+               while (TRUE) {
+                       /* Wait for a little bit before polling */
+                       g_usleep (100 * 1000);
+ 
+                       result = get_changes (macaroon, discharges, change_id, cancellable, error);
+                       if (result == NULL)
+                               return FALSE;
+ 
+                       status = g_strdup (json_object_get_string_member (result, "status"));
+ 
+                       if (g_strcmp0 (status, "Done") == 0)
+                               break;
+ 
+                       callback (result, user_data);
+               }
+       }
+ 
+       if (g_strcmp0 (status, "Done") != 0) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                            "snapd operation finished with status %s", status);
+               return FALSE;
+       }
  
        return TRUE;
  }
diff --cc src/plugins/gs-snapd.h
index 7c0eb9c,af28bd1..f2bcaa8
--- a/src/plugins/gs-snapd.h
+++ b/src/plugins/gs-snapd.h
@@@ -25,31 -25,53 +25,61 @@@
  #include <gio/gio.h>
  #include <json-glib/json-glib.h>
  
- gboolean gs_snapd_exists      (void);
- 
- gboolean gs_snapd_request     (const gchar    *method,
-                                const gchar    *path,
-                                const gchar    *content,
-                                const gchar    *macaroon,
-                                gchar          **discharges,
-                                guint          *status_code,
-                                gchar          **reason_phrase,
-                                gchar          **response_type,
-                                gchar          **response,
-                                gsize          *response_length,
-                                GCancellable   *cancellable,
-                                GError         **error);
- 
- gboolean gs_snapd_parse_result        (const gchar    *response_type,
-                                const gchar    *response,
-                                JsonObject     **result,
-                                GError         **error);
- 
- gboolean gs_snapd_parse_error (const gchar    *response_type,
-                                const gchar    *response,
-                                gchar          **message,
-                                gchar          **kind,
-                                GError         **error);
+ typedef void (*GsSnapdProgressCallback) (JsonObject *object, gpointer user_data);
  
+ gboolean gs_snapd_exists              (void);
+ 
+ JsonObject *gs_snapd_list_one         (const gchar    *macaroon,
+                                        gchar          **discharges,
+                                        const gchar    *name,
+                                        GCancellable   *cancellable,
+                                        GError         **error);
+ 
+ JsonArray *gs_snapd_list              (const gchar    *macaroon,
+                                        gchar          **discharges,
+                                        GCancellable   *cancellable,
+                                        GError         **error);
+ 
+ JsonArray *gs_snapd_find              (const gchar    *macaroon,
+                                        gchar          **discharges,
+                                        gchar          **values,
+                                        GCancellable   *cancellable,
+                                        GError         **error);
+ 
+ JsonObject *gs_snapd_get_interfaces   (const gchar    *macaroon,
+                                        gchar          **discharges,
+                                        GCancellable   *cancellable,
+                                        GError         **error);
+ 
++gboolean gs_snapd_buy                 (const gchar    *macaroon,
++                                       gchar          **discharges,
++                                       const gchar    *id,
++                                       gdouble         price,
++                                       const gchar    *currency,
++                                       GCancellable   *cancellable,
++                                       GError         **error);
++
+ gboolean gs_snapd_install             (const gchar    *macaroon,
+                                        gchar          **discharges,
+                                        const gchar    *name,
+                                        GsSnapdProgressCallback callback,
+                                        gpointer        user_data,
+                                        GCancellable   *cancellable,
+                                        GError         **error);
+ 
+ gboolean gs_snapd_remove              (const gchar    *macaroon,
+                                        gchar          **discharges,
+                                        const gchar    *name,
+                                        GsSnapdProgressCallback callback,
+                                        gpointer        user_data,
+                                        GCancellable   *cancellable,
+                                        GError         **error);
+ 
+ gchar *gs_snapd_get_resource          (const gchar    *macaroon,
+                                        gchar          **discharges,
+                                        const gchar    *path,
+                                        gsize          *data_length,
+                                        GCancellable   *cancellable,
+                                        GError         **error);
  
  #endif /* __GS_SNAPD_H__ */


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