[gnome-software] Add GsPluginRequest to all the plugin loader methods



commit 7fed04f14b8b5e28af50f33d27156b97e9a1103b
Author: Richard Hughes <richard hughsie com>
Date:   Fri May 19 11:14:58 2017 +0100

    Add GsPluginRequest to all the plugin loader methods
    
    This allows us to add common parameters to each request without changing the
    API every time. It also allows all the different plugin loader actions to share
    one thread func, putting all the filter and post-resolve code in one place.
    
    Use this new functionality to add support for max-results and sort_func to
    every plugin loader method, which allows us to speed up loading the overview.

 lib/gs-cmd.c                            |  198 ++-
 lib/gs-plugin-job-private.h             |   60 +
 lib/gs-plugin-job.c                     |  576 ++++++
 lib/gs-plugin-job.h                     |   73 +
 lib/gs-plugin-loader-sync.c             |  701 +-------
 lib/gs-plugin-loader-sync.h             |   99 +-
 lib/gs-plugin-loader.c                  | 2979 +++++++------------------------
 lib/gs-plugin-loader.h                  |  207 +--
 lib/gs-plugin-types.h                   |    7 +-
 lib/meson.build                         |    1 +
 plugins/core/gs-self-test.c             |   26 +-
 plugins/dpkg/gs-self-test.c             |   11 +-
 plugins/dummy/gs-self-test.c            |  240 ++--
 plugins/epiphany/gs-self-test.c         |   11 +-
 plugins/flatpak/gs-self-test.c          |  555 +++---
 plugins/fwupd/gs-self-test.c            |   11 +-
 plugins/modalias/gs-self-test.c         |   13 +-
 plugins/packagekit/gs-self-test.c       |   11 +-
 plugins/repos/gs-self-test.c            |   11 +-
 plugins/shell-extensions/gs-self-test.c |   19 +-
 src/gs-application.c                    |   31 +-
 src/gs-auth-dialog.c                    |   26 +-
 src/gs-category-page.c                  |   28 +-
 src/gs-details-page.c                   |  238 ++--
 src/gs-extras-page.c                    |   95 +-
 src/gs-installed-page.c                 |   26 +-
 src/gs-loading-page.c                   |   12 +-
 src/gs-moderate-page.c                  |   54 +-
 src/gs-overview-page.c                  |  112 +-
 src/gs-page.c                           |  171 +-
 src/gs-search-page.c                    |   46 +-
 src/gs-shell-search-provider.c          |   29 +-
 src/gs-sources-dialog.c                 |   67 +-
 src/gs-update-dialog.c                  |   18 +-
 src/gs-update-monitor.c                 |   81 +-
 src/gs-updates-page.c                   |  155 +-
 36 files changed, 2704 insertions(+), 4294 deletions(-)
---
diff --git a/lib/gs-cmd.c b/lib/gs-cmd.c
index 903970b..582089b 100644
--- a/lib/gs-cmd.c
+++ b/lib/gs-cmd.c
@@ -296,13 +296,15 @@ main (int argc, char **argv)
        /* do action */
        if (argc == 2 && g_strcmp0 (argv[1], "installed") == 0) {
                for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        if (list != NULL)
                                g_object_unref (list);
-                       list = gs_plugin_loader_get_installed (plugin_loader,
-                                                              refine_flags,
-                                                              GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                              NULL,
-                                                              &error);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_INSTALLED,
+                                                        "refine-flags", refine_flags,
+                                                        "max-results", max_results,
+                                                        NULL);
+                       list = gs_plugin_loader_job_process (plugin_loader, plugin_job,
+                                                            NULL, &error);
                        if (list == NULL) {
                                ret = FALSE;
                                break;
@@ -310,40 +312,40 @@ main (int argc, char **argv)
                }
        } else if (argc == 3 && g_strcmp0 (argv[1], "search") == 0) {
                for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        if (list != NULL)
                                g_object_unref (list);
-                       list = gs_plugin_loader_search (plugin_loader,
-                                                       argv[2], max_results,
-                                                       NULL, NULL,
-                                                       refine_flags,
-                                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                       NULL,
-                                                       &error);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                                        "search", argv[2],
+                                                        "refine-flags", refine_flags,
+                                                        "max-results", max_results,
+                                                        NULL);
+                       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
                        if (list == NULL) {
                                ret = FALSE;
                                break;
                        }
                }
        } else if (argc == 3 && g_strcmp0 (argv[1], "action-upgrade-download") == 0) {
+               g_autoptr(GsPluginJob) plugin_job = NULL;
                app = gs_app_new (argv[2]);
                gs_app_set_kind (app, AS_APP_KIND_OS_UPGRADE);
-               ret = gs_plugin_loader_app_action (plugin_loader,
-                                                  app,
-                                                  GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
-                                                  GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                  NULL,
-                                                  &error);
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
+                                                "app", app,
+                                                NULL);
+               ret = gs_plugin_loader_job_action (plugin_loader, plugin_job,
+                                                   NULL, &error);
                if (ret)
                        gs_app_list_add (list, app);
        } else if (argc == 3 && g_strcmp0 (argv[1], "refine") == 0) {
                app = gs_app_new (argv[2]);
                for (i = 0; i < repeat; i++) {
-                       ret = gs_plugin_loader_app_refine (plugin_loader,
-                                                          app,
-                                                          refine_flags,
-                                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                          NULL,
-                                                          &error);
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                                        "app", app,
+                                                        NULL);
+                       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job,
+                                                           NULL, &error);
                        if (!ret)
                                break;
                }
@@ -352,23 +354,24 @@ main (int argc, char **argv)
        } else if (argc == 3 && g_strcmp0 (argv[1], "launch") == 0) {
                app = gs_app_new (argv[2]);
                for (i = 0; i < repeat; i++) {
-                       ret = gs_plugin_loader_app_action (plugin_loader,
-                                                          app,
-                                                          GS_PLUGIN_ACTION_LAUNCH,
-                                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                          NULL,
-                                                          &error);
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_LAUNCH,
+                                                        "app", app,
+                                                        NULL);
+                       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job,
+                                                           NULL, &error);
                        if (!ret)
                                break;
                }
        } else if (argc == 3 && g_strcmp0 (argv[1], "filename-to-app") == 0) {
+               g_autoptr(GsPluginJob) plugin_job = NULL;
                file = g_file_new_for_path (argv[2]);
-               app = gs_plugin_loader_file_to_app (plugin_loader,
-                                                   file,
-                                                   refine_flags,
-                                                   GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                   NULL,
-                                                   &error);
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                                "file", file,
+                                                "refine-flags", refine_flags,
+                                                "max-results", max_results,
+                                                NULL);
+               app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
                if (app == NULL) {
                        ret = FALSE;
                } else {
@@ -376,12 +379,14 @@ main (int argc, char **argv)
                        gs_app_list_add (list, app);
                }
        } else if (argc == 3 && g_strcmp0 (argv[1], "url-to-app") == 0) {
-               app = gs_plugin_loader_url_to_app (plugin_loader,
-                                                  argv[2],
-                                                  refine_flags,
-                                                  GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                  NULL,
-                                                  &error);
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_URL_TO_APP,
+                                                "search", argv[2],
+                                                "refine-flags", refine_flags,
+                                                "max-results", max_results,
+                                                NULL);
+               app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job,
+                                                   NULL, &error);
                if (app == NULL) {
                        ret = FALSE;
                } else {
@@ -390,13 +395,15 @@ main (int argc, char **argv)
                }
        } else if (argc == 2 && g_strcmp0 (argv[1], "updates") == 0) {
                for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        if (list != NULL)
                                g_object_unref (list);
-                       list = gs_plugin_loader_get_updates (plugin_loader,
-                                                            refine_flags,
-                                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                            NULL,
-                                                            &error);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES,
+                                                        "refine-flags", refine_flags,
+                                                        "max-results", max_results,
+                                                        NULL);
+                       list = gs_plugin_loader_job_process (plugin_loader, plugin_job,
+                                                            NULL, &error);
                        if (list == NULL) {
                                ret = FALSE;
                                break;
@@ -404,35 +411,43 @@ main (int argc, char **argv)
                }
        } else if (argc == 2 && g_strcmp0 (argv[1], "upgrades") == 0) {
                for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        if (list != NULL)
                                g_object_unref (list);
-                       list = gs_plugin_loader_get_distro_upgrades (plugin_loader,
-                                                                    refine_flags,
-                                                                    GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                                    NULL,
-                                                                    &error);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_DISTRO_UPDATES,
+                                                        "refine-flags", refine_flags,
+                                                        "max-results", max_results,
+                                                        NULL);
+                       list = gs_plugin_loader_job_process (plugin_loader, plugin_job,
+                                                            NULL, &error);
                        if (list == NULL) {
                                ret = FALSE;
                                break;
                        }
                }
        } else if (argc == 2 && g_strcmp0 (argv[1], "sources") == 0) {
-               list = gs_plugin_loader_get_sources (plugin_loader,
-                                                    refine_flags,
-                                                    GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES,
+                                                "refine-flags", refine_flags,
+                                                "max-results", max_results,
+                                                NULL);
+               list = gs_plugin_loader_job_process (plugin_loader,
+                                                    plugin_job,
                                                     NULL,
                                                     &error);
                if (list == NULL)
                        ret = FALSE;
        } else if (argc == 2 && g_strcmp0 (argv[1], "popular") == 0) {
                for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        if (list != NULL)
                                g_object_unref (list);
-                       list = gs_plugin_loader_get_popular (plugin_loader,
-                                                            refine_flags,
-                                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                            NULL,
-                                                            &error);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_POPULAR,
+                                                        "refine-flags", refine_flags,
+                                                        "max-results", max_results,
+                                                        NULL);
+                       list = gs_plugin_loader_job_process (plugin_loader, plugin_job,
+                                                            NULL, &error);
                        if (list == NULL) {
                                ret = FALSE;
                                break;
@@ -440,13 +455,15 @@ main (int argc, char **argv)
                }
        } else if (argc == 2 && g_strcmp0 (argv[1], "featured") == 0) {
                for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        if (list != NULL)
                                g_object_unref (list);
-                       list = gs_plugin_loader_get_featured (plugin_loader,
-                                                             refine_flags,
-                                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                             NULL,
-                                                             &error);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_FEATURED,
+                                                        "refine-flags", refine_flags,
+                                                        "max-results", max_results,
+                                                        NULL);
+                       list = gs_plugin_loader_job_process (plugin_loader, plugin_job,
+                                                             NULL, &error);
                        if (list == NULL) {
                                ret = FALSE;
                                break;
@@ -454,14 +471,16 @@ main (int argc, char **argv)
                }
        } else if (argc == 2 && g_strcmp0 (argv[1], "recent") == 0) {
                for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        if (list != NULL)
                                g_object_unref (list);
-                       list = gs_plugin_loader_get_recent (plugin_loader,
-                                                           60 * 60 * 24 * 60,
-                                                           refine_flags,
-                                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                           NULL,
-                                                           &error);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_RECENT,
+                                                        "age", 60 * 60 * 24 * 60,
+                                                        "refine-flags", refine_flags,
+                                                        "max-results", max_results,
+                                                        NULL);
+                       list = gs_plugin_loader_job_process (plugin_loader, plugin_job,
+                                                            NULL, &error);
                        if (list == NULL) {
                                ret = FALSE;
                                break;
@@ -469,13 +488,16 @@ main (int argc, char **argv)
                }
        } else if (argc == 2 && g_strcmp0 (argv[1], "get-categories") == 0) {
                for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        if (categories != NULL)
                                g_ptr_array_unref (categories);
-                       categories = gs_plugin_loader_get_categories (plugin_loader,
-                                                                     refine_flags,
-                                                                     GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                                     NULL,
-                                                                     &error);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_CATEGORIES,
+                                                        "refine-flags", refine_flags,
+                                                        "max-results", max_results,
+                                                        NULL);
+                       categories = gs_plugin_loader_job_get_categories (plugin_loader,
+                                                                        plugin_job,
+                                                                        NULL, &error);
                        if (categories == NULL) {
                                ret = FALSE;
                                break;
@@ -494,14 +516,15 @@ main (int argc, char **argv)
                        gs_category_add_child (parent, category);
                }
                for (i = 0; i < repeat; i++) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        if (list != NULL)
                                g_object_unref (list);
-                       list = gs_plugin_loader_get_category_apps (plugin_loader,
-                                                                  category,
-                                                                  refine_flags,
-                                                                  GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                                  NULL,
-                                                                  &error);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_CATEGORY_APPS,
+                                                        "category", category,
+                                                        "refine-flags", refine_flags,
+                                                        "max-results", max_results,
+                                                        NULL);
+                       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
                        if (list == NULL) {
                                ret = FALSE;
                                break;
@@ -509,11 +532,14 @@ main (int argc, char **argv)
                }
        } else if (argc >= 2 && g_strcmp0 (argv[1], "refresh") == 0) {
                GsPluginRefreshFlags refresh_flags;
+               g_autoptr(GsPluginJob) plugin_job = NULL;
                refresh_flags = gs_cmd_refresh_flag_from_string (argv[2]);
-               ret = gs_plugin_loader_refresh (plugin_loader, cache_age,
-                                               refresh_flags,
-                                               GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                               NULL, &error);
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                                "age", cache_age,
+                                                "refresh-flags", refresh_flags,
+                                                NULL);
+               ret = gs_plugin_loader_job_action (plugin_loader, plugin_job,
+                                                   NULL, &error);
        } else {
                ret = FALSE;
                g_set_error_literal (&error,
diff --git a/lib/gs-plugin-job-private.h b/lib/gs-plugin-job-private.h
new file mode 100644
index 0000000..9771482
--- /dev/null
+++ b/lib/gs-plugin-job-private.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 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_JOB_PRIVATE
+#define __GS_PLUGIN_JOB_PRIVATE
+
+#include <glib-object.h>
+
+#include "gs-plugin-job.h"
+
+G_BEGIN_DECLS
+
+GsPluginAction          gs_plugin_job_get_action               (GsPluginJob    *self);
+GsPluginRefreshFlags    gs_plugin_job_get_refresh_flags        (GsPluginJob    *self);
+GsPluginRefineFlags     gs_plugin_job_get_refine_flags         (GsPluginJob    *self);
+gboolean                gs_plugin_job_has_refine_flags         (GsPluginJob    *self,
+                                                                GsPluginRefineFlags refine_flags);
+void                    gs_plugin_job_add_refine_flags         (GsPluginJob    *self,
+                                                                GsPluginRefineFlags refine_flags);
+void                    gs_plugin_job_remove_refine_flags      (GsPluginJob    *self,
+                                                                GsPluginRefineFlags refine_flags);
+GsPluginFailureFlags    gs_plugin_job_get_failure_flags        (GsPluginJob    *self);
+guint                   gs_plugin_job_get_max_results          (GsPluginJob    *self);
+guint64                         gs_plugin_job_get_age                  (GsPluginJob    *self);
+GsAppListSortFunc       gs_plugin_job_get_sort_func            (GsPluginJob    *self);
+gpointer                gs_plugin_job_get_sort_func_data       (GsPluginJob    *self);
+const gchar            *gs_plugin_job_get_search               (GsPluginJob    *self);
+GsAuth                 *gs_plugin_job_get_auth                 (GsPluginJob    *self);
+GsApp                  *gs_plugin_job_get_app                  (GsPluginJob    *self);
+GsAppList              *gs_plugin_job_get_list                 (GsPluginJob    *self);
+GFile                  *gs_plugin_job_get_file                 (GsPluginJob    *self);
+GsCategory             *gs_plugin_job_get_category             (GsPluginJob    *self);
+AsReview               *gs_plugin_job_get_review               (GsPluginJob    *self);
+gchar                  *gs_plugin_job_to_string                (GsPluginJob    *self);
+void                    gs_plugin_job_set_action               (GsPluginJob    *self,
+                                                                GsPluginAction  action);
+
+G_END_DECLS
+
+#endif /* __GS_PLUGIN_JOB_PRIVATE */
+
+/* vim: set noexpandtab: */
diff --git a/lib/gs-plugin-job.c b/lib/gs-plugin-job.c
new file mode 100644
index 0000000..c51166e
--- /dev/null
+++ b/lib/gs-plugin-job.c
@@ -0,0 +1,576 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 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.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+
+#include "gs-plugin-private.h"
+#include "gs-plugin-job-private.h"
+
+struct _GsPluginJob
+{
+       GObject                  parent_instance;
+       GsPluginRefineFlags      refine_flags;
+       GsPluginRefreshFlags     refresh_flags;
+       GsPluginFailureFlags     failure_flags;
+       guint                    max_results;
+       guint64                  age;
+       GsPluginAction           action;
+       GsAppListSortFunc        sort_func;
+       gpointer                 sort_func_data;
+       gchar                   *search;
+       GsAuth                  *auth;
+       GsApp                   *app;
+       GsAppList               *list;
+       GFile                   *file;
+       GsCategory              *category;
+       AsReview                *review;
+};
+
+enum {
+       PROP_0,
+       PROP_ACTION,
+       PROP_AGE,
+       PROP_SEARCH,
+       PROP_REFINE_FLAGS,
+       PROP_REFRESH_FLAGS,
+       PROP_FAILURE_FLAGS,
+       PROP_AUTH,
+       PROP_APP,
+       PROP_LIST,
+       PROP_FILE,
+       PROP_CATEGORY,
+       PROP_REVIEW,
+       PROP_MAX_RESULTS,
+       PROP_LAST
+};
+
+G_DEFINE_TYPE (GsPluginJob, gs_plugin_job, G_TYPE_OBJECT)
+
+gchar *
+gs_plugin_job_to_string (GsPluginJob *self)
+{
+       GString *str = g_string_new (NULL);
+       g_string_append_printf (str, "running %s",
+                               gs_plugin_action_to_string (self->action));
+       if (self->refine_flags > 0) {
+               g_autofree gchar *tmp = gs_plugin_refine_flags_to_string (self->refine_flags);
+               g_string_append_printf (str, " with refine-flags=%s", tmp);
+       }
+       if (self->failure_flags > 0) {
+               g_autofree gchar *tmp = gs_plugin_failure_flags_to_string (self->failure_flags);
+               g_string_append_printf (str, " with failure-flags=%s", tmp);
+       }
+       if (self->age != 0) {
+               if (self->age == G_MAXUINT) {
+                       g_string_append (str, " with cache age=any");
+               } else {
+                       g_string_append_printf (str, " with cache age=%" G_GUINT64_FORMAT,
+                                               self->age);
+               }
+       }
+       if (self->search != NULL) {
+               g_string_append_printf (str, " with search=%s",
+                                       self->search);
+       }
+       if (self->category != NULL) {
+               GsCategory *parent = gs_category_get_parent (self->category);
+               if (parent != NULL) {
+                       g_string_append_printf (str, " with category=%s/%s",
+                                               gs_category_get_id (parent),
+                                               gs_category_get_id (self->category));
+               } else {
+                       g_string_append_printf (str, " with category=%s",
+                                               gs_category_get_id (self->category));
+               }
+       }
+       if (self->review != NULL) {
+               g_string_append_printf (str, " with review=%s",
+                                       as_review_get_id (self->review));
+       }
+       if (self->review != NULL) {
+               g_string_append_printf (str, " with review=%s",
+                                       as_review_get_id (self->review));
+       }
+       if (self->auth != NULL) {
+               g_string_append_printf (str, " with auth=%s",
+                                       gs_auth_get_provider_id (self->auth));
+       }
+       if (self->file != NULL) {
+               g_autofree gchar *path = g_file_get_path (self->file);
+               g_string_append_printf (str, " with file=%s", path);
+       }
+       if (self->list != NULL && gs_app_list_length (self->list) > 0) {
+               g_autofree const gchar **unique_ids = NULL;
+               g_autofree gchar *unique_ids_str = NULL;
+               unique_ids = g_new0 (const gchar *, gs_app_list_length (self->list) + 1);
+               for (guint i = 0; i < gs_app_list_length (self->list); i++) {
+                       GsApp *app = gs_app_list_index (self->list, i);
+                       unique_ids[i] = gs_app_get_unique_id (app);
+               }
+               unique_ids_str = g_strjoinv (",", (gchar**) unique_ids);
+               g_string_append_printf (str, " on apps %s", unique_ids_str);
+       }
+       return g_string_free (str, FALSE);
+}
+
+void
+gs_plugin_job_set_refine_flags (GsPluginJob *self, GsPluginRefineFlags refine_flags)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->refine_flags = refine_flags;
+}
+
+GsPluginRefineFlags
+gs_plugin_job_get_refine_flags (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
+       return self->refine_flags;
+}
+
+void
+gs_plugin_job_set_refresh_flags (GsPluginJob *self, GsPluginRefreshFlags refresh_flags)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->refresh_flags = refresh_flags;
+}
+
+GsPluginRefreshFlags
+gs_plugin_job_get_refresh_flags (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
+       return self->refresh_flags;
+}
+
+gboolean
+gs_plugin_job_has_refine_flags (GsPluginJob *self, GsPluginRefineFlags refine_flags)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), FALSE);
+       return (self->refine_flags & refine_flags) > 0;
+}
+
+void
+gs_plugin_job_add_refine_flags (GsPluginJob *self, GsPluginRefineFlags refine_flags)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->refine_flags |= refine_flags;
+}
+
+void
+gs_plugin_job_remove_refine_flags (GsPluginJob *self, GsPluginRefineFlags refine_flags)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->refine_flags &= ~refine_flags;
+}
+
+void
+gs_plugin_job_set_failure_flags (GsPluginJob *self, GsPluginFailureFlags failure_flags)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->failure_flags = failure_flags;
+}
+
+GsPluginFailureFlags
+gs_plugin_job_get_failure_flags (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
+       return self->failure_flags;
+}
+
+void
+gs_plugin_job_set_max_results (GsPluginJob *self, guint max_results)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->max_results = max_results;
+}
+
+guint
+gs_plugin_job_get_max_results (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
+       return self->max_results;
+}
+
+void
+gs_plugin_job_set_age (GsPluginJob *self, guint64 age)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->age = age;
+}
+
+guint64
+gs_plugin_job_get_age (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
+       return self->age;
+}
+
+void
+gs_plugin_job_set_action (GsPluginJob *self, GsPluginAction action)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->action = action;
+}
+
+GsPluginAction
+gs_plugin_job_get_action (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
+       return self->action;
+}
+
+void
+gs_plugin_job_set_sort_func (GsPluginJob *self, GsAppListSortFunc sort_func)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->sort_func = sort_func;
+}
+
+GsAppListSortFunc
+gs_plugin_job_get_sort_func (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
+       return self->sort_func;
+}
+
+void
+gs_plugin_job_set_sort_func_data (GsPluginJob *self, gpointer sort_func_data)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       self->sort_func_data = sort_func_data;
+}
+
+gpointer
+gs_plugin_job_get_sort_func_data (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
+       return self->sort_func_data;
+}
+
+void
+gs_plugin_job_set_search (GsPluginJob *self, const gchar *search)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       g_free (self->search);
+       self->search = g_strdup (search);
+}
+
+const gchar *
+gs_plugin_job_get_search (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
+       return self->search;
+}
+
+void
+gs_plugin_job_set_auth (GsPluginJob *self, GsAuth *auth)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       g_set_object (&self->auth, auth);
+}
+
+GsAuth *
+gs_plugin_job_get_auth (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
+       return self->auth;
+}
+
+void
+gs_plugin_job_set_app (GsPluginJob *self, GsApp *app)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       g_set_object (&self->app, app);
+
+       /* ensure we can always operate on a list object */
+       if (self->list != NULL && app != NULL && gs_app_list_length (self->list) == 0)
+               gs_app_list_add (self->list, self->app);
+}
+
+GsApp *
+gs_plugin_job_get_app (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
+       return self->app;
+}
+
+void
+gs_plugin_job_set_list (GsPluginJob *self, GsAppList *list)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       if (list == NULL)
+               g_warning ("trying to set list to NULL, not a god idea");
+       g_set_object (&self->list, list);
+}
+
+GsAppList *
+gs_plugin_job_get_list (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
+       return self->list;
+}
+
+void
+gs_plugin_job_set_file (GsPluginJob *self, GFile *file)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       g_set_object (&self->file, file);
+}
+
+GFile *
+gs_plugin_job_get_file (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
+       return self->file;
+}
+
+void
+gs_plugin_job_set_category (GsPluginJob *self, GsCategory *category)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       g_set_object (&self->category, category);
+}
+
+GsCategory *
+gs_plugin_job_get_category (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
+       return self->category;
+}
+
+void
+gs_plugin_job_set_review (GsPluginJob *self, AsReview *review)
+{
+       g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+       g_set_object (&self->review, review);
+}
+
+AsReview *
+gs_plugin_job_get_review (GsPluginJob *self)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
+       return self->review;
+}
+
+static void
+gs_plugin_job_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+       GsPluginJob *self = GS_PLUGIN_JOB (obj);
+
+       switch (prop_id) {
+       case PROP_ACTION:
+               g_value_set_uint (value, self->action);
+               break;
+       case PROP_AGE:
+               g_value_set_uint64 (value, self->age);
+               break;
+       case PROP_REFINE_FLAGS:
+               g_value_set_uint64 (value, self->refine_flags);
+               break;
+       case PROP_REFRESH_FLAGS:
+               g_value_set_uint64 (value, self->refresh_flags);
+               break;
+       case PROP_FAILURE_FLAGS:
+               g_value_set_uint64 (value, self->failure_flags);
+               break;
+       case PROP_SEARCH:
+               g_value_set_string (value, self->search);
+               break;
+       case PROP_AUTH:
+               g_value_set_object (value, self->auth);
+               break;
+       case PROP_APP:
+               g_value_set_object (value, self->app);
+               break;
+       case PROP_LIST:
+               g_value_set_object (value, self->list);
+               break;
+       case PROP_FILE:
+               g_value_set_object (value, self->file);
+               break;
+       case PROP_CATEGORY:
+               g_value_set_object (value, self->category);
+               break;
+       case PROP_REVIEW:
+               g_value_set_object (value, self->review);
+               break;
+       case PROP_MAX_RESULTS:
+               g_value_set_uint (value, self->max_results);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_plugin_job_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+       GsPluginJob *self = GS_PLUGIN_JOB (obj);
+
+       switch (prop_id) {
+       case PROP_ACTION:
+               gs_plugin_job_set_action (self, g_value_get_uint (value));
+               break;
+       case PROP_AGE:
+               gs_plugin_job_set_age (self, g_value_get_uint64 (value));
+               break;
+       case PROP_REFINE_FLAGS:
+               gs_plugin_job_set_refine_flags (self, g_value_get_uint64 (value));
+               break;
+       case PROP_REFRESH_FLAGS:
+               gs_plugin_job_set_refresh_flags (self, g_value_get_uint64 (value));
+               break;
+       case PROP_FAILURE_FLAGS:
+               gs_plugin_job_set_failure_flags (self, g_value_get_uint64 (value));
+               break;
+       case PROP_SEARCH:
+               gs_plugin_job_set_search (self, g_value_get_string (value));
+               break;
+       case PROP_AUTH:
+               gs_plugin_job_set_auth (self, g_value_get_object (value));
+               break;
+       case PROP_APP:
+               gs_plugin_job_set_app (self, g_value_get_object (value));
+               break;
+       case PROP_LIST:
+               gs_plugin_job_set_list (self, g_value_get_object (value));
+               break;
+       case PROP_FILE:
+               gs_plugin_job_set_file (self, g_value_get_object (value));
+               break;
+       case PROP_CATEGORY:
+               gs_plugin_job_set_category (self, g_value_get_object (value));
+               break;
+       case PROP_REVIEW:
+               gs_plugin_job_set_review (self, g_value_get_object (value));
+               break;
+       case PROP_MAX_RESULTS:
+               gs_plugin_job_set_max_results (self, g_value_get_uint (value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_plugin_job_finalize (GObject *obj)
+{
+       GsPluginJob *self = GS_PLUGIN_JOB (obj);
+       g_free (self->search);
+       g_clear_object (&self->auth);
+       g_clear_object (&self->app);
+       g_clear_object (&self->list);
+       g_clear_object (&self->file);
+       g_clear_object (&self->category);
+       g_clear_object (&self->review);
+       G_OBJECT_CLASS (gs_plugin_job_parent_class)->finalize (obj);
+}
+
+static void
+gs_plugin_job_class_init (GsPluginJobClass *klass)
+{
+       GParamSpec *pspec;
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = gs_plugin_job_finalize;
+       object_class->get_property = gs_plugin_job_get_property;
+       object_class->set_property = gs_plugin_job_set_property;
+
+       pspec = g_param_spec_uint ("action", NULL, NULL,
+                                  GS_PLUGIN_ACTION_UNKNOWN,
+                                  GS_PLUGIN_ACTION_LAST,
+                                  GS_PLUGIN_ACTION_UNKNOWN,
+                                  G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_ACTION, pspec);
+
+       pspec = g_param_spec_uint64 ("age", NULL, NULL,
+                                    0, G_MAXUINT64, 0,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_AGE, pspec);
+
+       pspec = g_param_spec_uint64 ("refine-flags", NULL, NULL,
+                                    0, G_MAXUINT64, 0,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_REFINE_FLAGS, pspec);
+
+       pspec = g_param_spec_uint64 ("refresh-flags", NULL, NULL,
+                                    0, G_MAXUINT64, 0,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_REFRESH_FLAGS, pspec);
+
+       pspec = g_param_spec_uint64 ("failure-flags", NULL, NULL,
+                                    0, G_MAXUINT64, 0,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_FAILURE_FLAGS, pspec);
+
+       pspec = g_param_spec_string ("search", NULL, NULL,
+                                    NULL,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_SEARCH, pspec);
+
+       pspec = g_param_spec_object ("auth", NULL, NULL,
+                                    GS_TYPE_AUTH,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_AUTH, pspec);
+
+       pspec = g_param_spec_object ("app", NULL, NULL,
+                                    GS_TYPE_APP,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_APP, pspec);
+
+       pspec = g_param_spec_object ("list", NULL, NULL,
+                                    GS_TYPE_APP_LIST,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_LIST, pspec);
+
+       pspec = g_param_spec_object ("file", NULL, NULL,
+                                    G_TYPE_FILE,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_FILE, pspec);
+
+       pspec = g_param_spec_object ("category", NULL, NULL,
+                                    GS_TYPE_CATEGORY,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_CATEGORY, pspec);
+
+       pspec = g_param_spec_object ("review", NULL, NULL,
+                                    AS_TYPE_REVIEW,
+                                    G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_REVIEW, pspec);
+
+       pspec = g_param_spec_uint ("max-results", NULL, NULL,
+                                  0, G_MAXUINT, 0,
+                                  G_PARAM_READWRITE);
+       g_object_class_install_property (object_class, PROP_MAX_RESULTS, pspec);
+}
+
+static void
+gs_plugin_job_init (GsPluginJob *self)
+{
+       self->failure_flags = GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY;
+       self->refine_flags = GS_PLUGIN_REFINE_FLAGS_DEFAULT;
+       self->refresh_flags = GS_PLUGIN_REFRESH_FLAGS_NONE;
+       self->list = gs_app_list_new ();
+}
+
+/* vim: set noexpandtab: */
diff --git a/lib/gs-plugin-job.h b/lib/gs-plugin-job.h
new file mode 100644
index 0000000..557da8a
--- /dev/null
+++ b/lib/gs-plugin-job.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 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_JOB
+#define __GS_PLUGIN_JOB
+
+#include <glib-object.h>
+
+#include "gs-app-list.h"
+#include "gs-auth.h"
+#include "gs-category.h"
+#include "gs-plugin-types.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_PLUGIN_JOB (gs_plugin_job_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsPluginJob, gs_plugin_job, GS, PLUGIN_JOB, GObject)
+
+void            gs_plugin_job_set_refine_flags         (GsPluginJob    *self,
+                                                        GsPluginRefineFlags refine_flags);
+void            gs_plugin_job_set_refresh_flags        (GsPluginJob    *self,
+                                                        GsPluginRefreshFlags refresh_flags);
+void            gs_plugin_job_set_failure_flags        (GsPluginJob    *self,
+                                                        GsPluginFailureFlags failure_flags);
+void            gs_plugin_job_set_max_results          (GsPluginJob    *self,
+                                                        guint           max_results);
+void            gs_plugin_job_set_age                  (GsPluginJob    *self,
+                                                        guint64         age);
+void            gs_plugin_job_set_sort_func            (GsPluginJob    *self,
+                                                        GsAppListSortFunc sort_func);
+void            gs_plugin_job_set_sort_func_data       (GsPluginJob    *self,
+                                                        gpointer        sort_func_data);
+void            gs_plugin_job_set_search               (GsPluginJob    *self,
+                                                        const gchar    *search);
+void            gs_plugin_job_set_auth                 (GsPluginJob    *self,
+                                                        GsAuth         *auth);
+void            gs_plugin_job_set_app                  (GsPluginJob    *self,
+                                                        GsApp          *app);
+void            gs_plugin_job_set_list                 (GsPluginJob    *self,
+                                                        GsAppList      *list);
+void            gs_plugin_job_set_file                 (GsPluginJob    *self,
+                                                        GFile          *file);
+void            gs_plugin_job_set_category             (GsPluginJob    *self,
+                                                        GsCategory     *category);
+void            gs_plugin_job_set_review               (GsPluginJob    *self,
+                                                        AsReview       *review);
+
+#define                 gs_plugin_job_newv(a,...)              
GS_PLUGIN_JOB(g_object_new(GS_TYPE_PLUGIN_JOB, "action", a, __VA_ARGS__))
+
+G_END_DECLS
+
+#endif /* __GS_PLUGIN_JOB */
+
+/* vim: set noexpandtab: */
diff --git a/lib/gs-plugin-loader-sync.c b/lib/gs-plugin-loader-sync.c
index d7f4e6e..8dc6789 100644
--- a/lib/gs-plugin-loader-sync.c
+++ b/lib/gs-plugin-loader-sync.c
@@ -35,248 +35,19 @@ typedef struct {
 } GsPluginLoaderHelper;
 
 static void
-gs_plugin_loader_get_installed_finish_sync (GsPluginLoader *plugin_loader,
-                                           GAsyncResult *res,
-                                           GsPluginLoaderHelper *helper)
+_job_process_finish_sync (GsPluginLoader *plugin_loader,
+                         GAsyncResult *res,
+                         GsPluginLoaderHelper *helper)
 {
-       helper->list = gs_plugin_loader_get_installed_finish (plugin_loader,
-                                                             res,
-                                                             helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-GsAppList *
-gs_plugin_loader_get_installed (GsPluginLoader *plugin_loader,
-                               GsPluginRefineFlags refine_flags,
-                               GsPluginFailureFlags failure_flags,
-                               GCancellable *cancellable,
-                               GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_get_installed_async (plugin_loader,
-                                             refine_flags,
-                                             failure_flags,
-                                             cancellable,
-                                             (GAsyncReadyCallback) 
gs_plugin_loader_get_installed_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.list;
-}
-
-static void
-gs_plugin_loader_search_finish_sync (GsPluginLoader *plugin_loader,
-                                    GAsyncResult *res,
-                                    GsPluginLoaderHelper *helper)
-{
-       helper->list = gs_plugin_loader_search_finish (plugin_loader,
-                                                      res,
-                                                      helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-GsAppList *
-gs_plugin_loader_search (GsPluginLoader *plugin_loader,
-                        const gchar *value,
-                        guint max_results,
-                        GsAppListSortFunc sort_func,
-                        gpointer sort_func_data,
-                        GsPluginRefineFlags refine_flags,
-                        GsPluginFailureFlags failure_flags,
-                        GCancellable *cancellable,
-                        GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_search_async (plugin_loader,
-                                      value,
-                                      max_results,
-                                      sort_func,
-                                      sort_func_data,
-                                      refine_flags,
-                                      failure_flags,
-                                      cancellable,
-                                      (GAsyncReadyCallback) gs_plugin_loader_search_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.list;
-}
-
-static void
-gs_plugin_loader_get_updates_finish_sync (GsPluginLoader *plugin_loader,
-                                         GAsyncResult *res,
-                                         GsPluginLoaderHelper *helper)
-{
-       helper->list = gs_plugin_loader_get_updates_finish (plugin_loader,
-                                                           res,
-                                                           helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-GsAppList *
-gs_plugin_loader_get_updates (GsPluginLoader *plugin_loader,
-                             GsPluginRefineFlags refine_flags,
-                             GsPluginFailureFlags failure_flags,
-                             GCancellable *cancellable,
-                             GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_get_updates_async (plugin_loader,
-                                           refine_flags,
-                                           failure_flags,
-                                           cancellable,
-                                           (GAsyncReadyCallback) gs_plugin_loader_get_updates_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.list;
-}
-
-static void
-gs_plugin_loader_get_distro_upgrades_finish_sync (GsPluginLoader *plugin_loader,
-                                         GAsyncResult *res,
-                                         GsPluginLoaderHelper *helper)
-{
-       helper->list = gs_plugin_loader_get_distro_upgrades_finish (plugin_loader,
-                                                                   res,
-                                                                   helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-GsAppList *
-gs_plugin_loader_get_distro_upgrades (GsPluginLoader *plugin_loader,
-                                     GsPluginRefineFlags refine_flags,
-                                     GsPluginFailureFlags failure_flags,
-                                     GCancellable *cancellable,
-                                     GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_get_distro_upgrades_async (plugin_loader,
-                                                   refine_flags,
-                                                   failure_flags,
-                                                   cancellable,
-                                                   (GAsyncReadyCallback) 
gs_plugin_loader_get_distro_upgrades_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.list;
-}
-
-static void
-gs_plugin_loader_get_sources_finish_sync (GsPluginLoader *plugin_loader,
-                                         GAsyncResult *res,
-                                         GsPluginLoaderHelper *helper)
-{
-       helper->list = gs_plugin_loader_get_sources_finish (plugin_loader,
-                                                           res,
-                                                           helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-GsAppList *
-gs_plugin_loader_get_sources (GsPluginLoader *plugin_loader,
-                             GsPluginRefineFlags refine_flags,
-                             GsPluginFailureFlags failure_flags,
-                             GCancellable *cancellable,
-                             GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_get_sources_async (plugin_loader,
-                                           refine_flags,
-                                           failure_flags,
-                                           cancellable,
-                                           (GAsyncReadyCallback) gs_plugin_loader_get_sources_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.list;
-}
-
-static void
-gs_plugin_loader_get_popular_finish_sync (GsPluginLoader *plugin_loader,
-                                         GAsyncResult *res,
-                                         GsPluginLoaderHelper *helper)
-{
-       helper->list = gs_plugin_loader_get_popular_finish (plugin_loader,
+       helper->list = gs_plugin_loader_job_process_finish (plugin_loader,
                                                            res,
                                                            helper->error);
        g_main_loop_quit (helper->loop);
 }
 
 GsAppList *
-gs_plugin_loader_get_popular (GsPluginLoader *plugin_loader,
-                             GsPluginRefineFlags refine_flags,
-                             GsPluginFailureFlags failure_flags,
+gs_plugin_loader_job_process (GsPluginLoader *plugin_loader,
+                             GsPluginJob *plugin_job,
                              GCancellable *cancellable,
                              GError **error)
 {
@@ -290,11 +61,10 @@ gs_plugin_loader_get_popular (GsPluginLoader *plugin_loader,
        g_main_context_push_thread_default (helper.context);
 
        /* run async method */
-       gs_plugin_loader_get_popular_async (plugin_loader,
-                                           refine_flags,
-                                           failure_flags,
+       gs_plugin_loader_job_process_async (plugin_loader,
+                                           plugin_job,
                                            cancellable,
-                                           (GAsyncReadyCallback) gs_plugin_loader_get_popular_finish_sync,
+                                           (GAsyncReadyCallback) _job_process_finish_sync,
                                            &helper);
        g_main_loop_run (helper.loop);
 
@@ -307,109 +77,19 @@ gs_plugin_loader_get_popular (GsPluginLoader *plugin_loader,
 }
 
 static void
-gs_plugin_loader_get_featured_finish_sync (GsPluginLoader *plugin_loader,
-                                         GAsyncResult *res,
-                                         GsPluginLoaderHelper *helper)
+_job_get_categories_finish_sync (GsPluginLoader *plugin_loader,
+                                GAsyncResult *res,
+                                GsPluginLoaderHelper *helper)
 {
-       helper->list = gs_plugin_loader_get_featured_finish (plugin_loader,
-                                                            res,
-                                                            helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-GsAppList *
-gs_plugin_loader_get_featured (GsPluginLoader *plugin_loader,
-                              GsPluginRefineFlags refine_flags,
-                              GsPluginFailureFlags failure_flags,
-                              GCancellable *cancellable,
-                              GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_get_featured_async (plugin_loader,
-                                            refine_flags,
-                                            failure_flags,
-                                            cancellable,
-                                            (GAsyncReadyCallback) gs_plugin_loader_get_featured_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.list;
-}
-
-static void
-gs_plugin_loader_get_categories_finish_sync (GsPluginLoader *plugin_loader,
-                                            GAsyncResult *res,
-                                            GsPluginLoaderHelper *helper)
-{
-       helper->catlist = gs_plugin_loader_get_categories_finish (plugin_loader,
-                                                              res,
-                                                              helper->error);
+       helper->catlist = gs_plugin_loader_job_get_categories_finish (plugin_loader,
+                                                                     res,
+                                                                     helper->error);
        g_main_loop_quit (helper->loop);
 }
 
 GPtrArray *
-gs_plugin_loader_get_categories (GsPluginLoader *plugin_loader,
-                                GsPluginRefineFlags refine_flags,
-                                GsPluginFailureFlags failure_flags,
-                                GCancellable *cancellable,
-                                GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_get_categories_async (plugin_loader,
-                                              refine_flags,
-                                              failure_flags,
-                                              cancellable,
-                                              (GAsyncReadyCallback) 
gs_plugin_loader_get_categories_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.catlist;
-}
-
-static void
-gs_plugin_loader_get_category_apps_finish_sync (GsPluginLoader *plugin_loader,
-                                               GAsyncResult *res,
-                                               GsPluginLoaderHelper *helper)
-{
-       helper->list = gs_plugin_loader_get_category_apps_finish (plugin_loader,
-                                                                 res,
-                                                                 helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-GsAppList *
-gs_plugin_loader_get_category_apps (GsPluginLoader *plugin_loader,
-                                   GsCategory *category,
-                                   GsPluginRefineFlags refine_flags,
-                                   GsPluginFailureFlags failure_flags,
+gs_plugin_loader_job_get_categories (GsPluginLoader *plugin_loader,
+                                   GsPluginJob *plugin_job,
                                    GCancellable *cancellable,
                                    GError **error)
 {
@@ -423,13 +103,11 @@ gs_plugin_loader_get_category_apps (GsPluginLoader *plugin_loader,
        g_main_context_push_thread_default (helper.context);
 
        /* run async method */
-       gs_plugin_loader_get_category_apps_async (plugin_loader,
-                                                 category,
-                                                 refine_flags,
-                                                 failure_flags,
-                                                 cancellable,
-                                                 (GAsyncReadyCallback) 
gs_plugin_loader_get_category_apps_finish_sync,
-                                                 &helper);
+       gs_plugin_loader_job_get_categories_async (plugin_loader,
+                                                  plugin_job,
+                                                  cancellable,
+                                                  (GAsyncReadyCallback) _job_get_categories_finish_sync,
+                                                  &helper);
        g_main_loop_run (helper.loop);
 
        g_main_context_pop_thread_default (helper.context);
@@ -437,211 +115,23 @@ gs_plugin_loader_get_category_apps (GsPluginLoader *plugin_loader,
        g_main_loop_unref (helper.loop);
        g_main_context_unref (helper.context);
 
-       return helper.list;
+       return helper.catlist;
 }
 
 static void
-_get_recent_finish_sync (GsPluginLoader *plugin_loader,
+_job_action_finish_sync (GsPluginLoader *plugin_loader,
                         GAsyncResult *res,
                         GsPluginLoaderHelper *helper)
 {
-       helper->list = gs_plugin_loader_get_recent_finish (plugin_loader,
-                                                          res,
-                                                          helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-GsAppList *
-gs_plugin_loader_get_recent (GsPluginLoader *plugin_loader,
-                            guint64 age,
-                            GsPluginRefineFlags refine_flags,
-                            GsPluginFailureFlags failure_flags,
-                            GCancellable *cancellable,
-                            GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_get_recent_async (plugin_loader,
-                                          age,
-                                          refine_flags,
-                                          failure_flags,
-                                          cancellable,
-                                          (GAsyncReadyCallback) _get_recent_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.list;
-}
-
-static void
-gs_plugin_loader_app_refine_finish_sync (GsPluginLoader *plugin_loader,
-                                        GAsyncResult *res,
-                                        GsPluginLoaderHelper *helper)
-{
-       helper->ret = gs_plugin_loader_app_refine_finish (plugin_loader,
-                                                         res,
-                                                         helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-gboolean
-gs_plugin_loader_app_refine (GsPluginLoader *plugin_loader,
-                            GsApp *app,
-                            GsPluginRefineFlags refine_flags,
-                            GsPluginFailureFlags failure_flags,
-                            GCancellable *cancellable,
-                            GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_app_refine_async (plugin_loader,
-                                          app,
-                                          refine_flags,
-                                          failure_flags,
-                                          cancellable,
-                                          (GAsyncReadyCallback) gs_plugin_loader_app_refine_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.ret;
-}
-
-static void
-gs_plugin_loader_app_action_finish_sync (GsPluginLoader *plugin_loader,
-                                        GAsyncResult *res,
-                                        GsPluginLoaderHelper *helper)
-{
-       helper->ret = gs_plugin_loader_app_action_finish (plugin_loader,
+       helper->ret = gs_plugin_loader_job_action_finish (plugin_loader,
                                                          res,
                                                          helper->error);
        g_main_loop_quit (helper->loop);
 }
 
 gboolean
-gs_plugin_loader_app_action (GsPluginLoader *plugin_loader,
-                            GsApp *app,
-                            GsPluginAction action,
-                            GsPluginFailureFlags failure_flags,
-                            GCancellable *cancellable,
-                            GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_app_action_async (plugin_loader,
-                                          app,
-                                          action,
-                                          failure_flags,
-                                          cancellable,
-                                          (GAsyncReadyCallback) gs_plugin_loader_app_action_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.ret;
-}
-
-static void
-gs_plugin_loader_review_action_finish_sync (GsPluginLoader *plugin_loader,
-                                           GAsyncResult *res,
-                                           GsPluginLoaderHelper *helper)
-{
-       helper->ret = gs_plugin_loader_review_action_finish (plugin_loader,
-                                                            res,
-                                                            helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-gboolean
-gs_plugin_loader_review_action (GsPluginLoader *plugin_loader,
-                               GsApp *app,
-                               AsReview *review,
-                               GsPluginAction action,
-                               GsPluginFailureFlags failure_flags,
-                               GCancellable *cancellable,
-                               GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_review_action_async (plugin_loader,
-                                             app,
-                                             review,
-                                             action,
-                                             failure_flags,
-                                             cancellable,
-                                             (GAsyncReadyCallback) 
gs_plugin_loader_review_action_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.ret;
-}
-
-static void
-gs_plugin_loader_auth_action_finish_sync (GsPluginLoader *plugin_loader,
-                                         GAsyncResult *res,
-                                         GsPluginLoaderHelper *helper)
-{
-       helper->ret = gs_plugin_loader_auth_action_finish (plugin_loader,
-                                                          res,
-                                                          helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-gboolean
-gs_plugin_loader_auth_action (GsPluginLoader *plugin_loader,
-                             GsAuth *auth,
-                             GsPluginAction action,
-                             GsPluginFailureFlags failure_flags,
+gs_plugin_loader_job_action (GsPluginLoader *plugin_loader,
+                             GsPluginJob *plugin_job,
                              GCancellable *cancellable,
                              GError **error)
 {
@@ -655,12 +145,10 @@ gs_plugin_loader_auth_action (GsPluginLoader *plugin_loader,
        g_main_context_push_thread_default (helper.context);
 
        /* run async method */
-       gs_plugin_loader_auth_action_async (plugin_loader,
-                                           auth,
-                                           action,
-                                           failure_flags,
+       gs_plugin_loader_job_process_async (plugin_loader,
+                                           plugin_job,
                                            cancellable,
-                                           (GAsyncReadyCallback) gs_plugin_loader_auth_action_finish_sync,
+                                           (GAsyncReadyCallback) _job_action_finish_sync,
                                            &helper);
        g_main_loop_run (helper.loop);
 
@@ -673,71 +161,27 @@ gs_plugin_loader_auth_action (GsPluginLoader *plugin_loader,
 }
 
 static void
-gs_plugin_loader_refresh_finish_sync (GsPluginLoader *plugin_loader,
-                                     GAsyncResult *res,
-                                     GsPluginLoaderHelper *helper)
-{
-       helper->ret = gs_plugin_loader_refresh_finish (plugin_loader,
-                                                      res,
-                                                      helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-gboolean
-gs_plugin_loader_refresh (GsPluginLoader *plugin_loader,
-                         guint cache_age,
-                         GsPluginRefreshFlags refresh_flags,
-                         GsPluginFailureFlags failure_flags,
-                         GCancellable *cancellable,
-                         GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       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_refresh_async (plugin_loader,
-                                       cache_age,
-                                       refresh_flags,
-                                       failure_flags,
-                                       cancellable,
-                                       (GAsyncReadyCallback) gs_plugin_loader_refresh_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.ret;
-}
-
-static void
-gs_plugin_loader_file_to_app_finish_sync (GObject *source_object,
-                                         GAsyncResult *res,
-                                         gpointer user_data)
+_job_process_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_file_to_app_finish (plugin_loader,
-                                                          res,
-                                                          helper->error);
+       g_autoptr(GsAppList) list = NULL;
+
+       list = gs_plugin_loader_job_process_finish (plugin_loader,
+                                                   res,
+                                                   helper->error);
+       if (list != NULL)
+               helper->app = g_object_ref (gs_app_list_index (list, 0));
        g_main_loop_quit (helper->loop);
 }
 
 GsApp *
-gs_plugin_loader_file_to_app (GsPluginLoader *plugin_loader,
-                             GFile *file,
-                             GsPluginRefineFlags refine_flags,
-                             GsPluginFailureFlags failure_flags,
-                             GCancellable *cancellable,
-                             GError **error)
+gs_plugin_loader_job_process_app (GsPluginLoader *plugin_loader,
+                                 GsPluginJob *plugin_job,
+                                 GCancellable *cancellable,
+                                 GError **error)
 {
        GsPluginLoaderHelper helper;
 
@@ -750,12 +194,10 @@ gs_plugin_loader_file_to_app (GsPluginLoader *plugin_loader,
        g_main_context_push_thread_default (helper.context);
 
        /* run async method */
-       gs_plugin_loader_file_to_app_async (plugin_loader,
-                                           file,
-                                           refine_flags,
-                                           failure_flags,
+       gs_plugin_loader_job_process_async (plugin_loader,
+                                           plugin_job,
                                            cancellable,
-                                           gs_plugin_loader_file_to_app_finish_sync,
+                                           _job_process_app_finish_sync,
                                            &helper);
        g_main_loop_run (helper.loop);
 
@@ -767,53 +209,4 @@ gs_plugin_loader_file_to_app (GsPluginLoader *plugin_loader,
        return helper.app;
 }
 
-static void
-gs_plugin_loader_url_to_app_finish_sync (GObject *source_object,
-                                        GAsyncResult *res,
-                                        gpointer user_data)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) user_data;
-       helper->app = gs_plugin_loader_url_to_app_finish (plugin_loader,
-                                                         res,
-                                                         helper->error);
-       g_main_loop_quit (helper->loop);
-}
-
-GsApp *
-gs_plugin_loader_url_to_app (GsPluginLoader *plugin_loader,
-                            const gchar *url,
-                            GsPluginRefineFlags refine_flags,
-                            GsPluginFailureFlags failure_flags,
-                            GCancellable *cancellable,
-                            GError **error)
-{
-       GsPluginLoaderHelper helper;
-
-       /* create temp object */
-       helper.app = NULL;
-       helper.context = g_main_context_new ();
-       helper.loop = g_main_loop_new (helper.context, FALSE);
-       helper.error = error;
-
-       g_main_context_push_thread_default (helper.context);
-
-       /* run async method */
-       gs_plugin_loader_url_to_app_async (plugin_loader,
-                                          url,
-                                          refine_flags,
-                                          failure_flags,
-                                          cancellable,
-                                          gs_plugin_loader_url_to_app_finish_sync,
-                                          &helper);
-       g_main_loop_run (helper.loop);
-
-       g_main_context_pop_thread_default (helper.context);
-
-       g_main_loop_unref (helper.loop);
-       g_main_context_unref (helper.context);
-
-       return helper.app;
-}
-
 /* vim: set noexpandtab: */
diff --git a/lib/gs-plugin-loader-sync.h b/lib/gs-plugin-loader-sync.h
index b52f42c..3800fc6 100644
--- a/lib/gs-plugin-loader-sync.h
+++ b/lib/gs-plugin-loader-sync.h
@@ -28,103 +28,20 @@
 
 G_BEGIN_DECLS
 
-GsAppList      *gs_plugin_loader_get_installed         (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
+GsAppList      *gs_plugin_loader_job_process           (GsPluginLoader *plugin_loader,
+                                                        GsPluginJob    *plugin_job,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
-GsAppList      *gs_plugin_loader_search                (GsPluginLoader *plugin_loader,
-                                                        const gchar    *value,
-                                                        guint           max_results,
-                                                        GsAppListSortFunc sort_func,
-                                                        gpointer        sort_func_data,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
+GsApp          *gs_plugin_loader_job_process_app       (GsPluginLoader *plugin_loader,
+                                                        GsPluginJob    *plugin_job,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
-GsAppList      *gs_plugin_loader_get_updates           (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
+gboolean        gs_plugin_loader_job_action            (GsPluginLoader *plugin_loader,
+                                                        GsPluginJob    *plugin_job,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
-GsAppList      *gs_plugin_loader_get_distro_upgrades   (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-GsAppList      *gs_plugin_loader_get_sources           (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-GsAppList      *gs_plugin_loader_get_popular           (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-GsAppList      *gs_plugin_loader_get_featured          (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-GPtrArray      *gs_plugin_loader_get_categories        (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-GsAppList      *gs_plugin_loader_get_category_apps     (GsPluginLoader *plugin_loader,
-                                                        GsCategory     *category,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-GsAppList      *gs_plugin_loader_get_recent            (GsPluginLoader *plugin_loader,
-                                                        guint64         age,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-gboolean        gs_plugin_loader_app_refine            (GsPluginLoader *plugin_loader,
-                                                        GsApp          *app,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-gboolean        gs_plugin_loader_app_action            (GsPluginLoader *plugin_loader,
-                                                        GsApp          *app,
-                                                        GsPluginAction  action,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-gboolean        gs_plugin_loader_review_action         (GsPluginLoader *plugin_loader,
-                                                        GsApp          *app,
-                                                        AsReview       *review,
-                                                        GsPluginAction  action,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-gboolean        gs_plugin_loader_auth_action           (GsPluginLoader *plugin_loader,
-                                                        GsAuth         *auth,
-                                                        GsPluginAction  action,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-gboolean        gs_plugin_loader_refresh               (GsPluginLoader *plugin_loader,
-                                                        guint           cache_age,
-                                                        GsPluginRefreshFlags refresh_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-GsApp          *gs_plugin_loader_file_to_app           (GsPluginLoader *plugin_loader,
-                                                        GFile          *file,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GError         **error);
-GsApp          *gs_plugin_loader_url_to_app            (GsPluginLoader *plugin_loader,
-                                                        const gchar    *url,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
+GPtrArray      *gs_plugin_loader_job_get_categories    (GsPluginLoader *plugin_loader,
+                                                        GsPluginJob    *plugin_job,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
 
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 0238f4d..7e1530d 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -31,6 +31,7 @@
 #include "gs-plugin-loader.h"
 #include "gs-plugin.h"
 #include "gs-plugin-event.h"
+#include "gs-plugin-job-private.h"
 #include "gs-plugin-private.h"
 #include "gs-utils.h"
 
@@ -155,7 +156,7 @@ typedef gboolean     (*GsPluginRefineWildcardFunc)  (GsPlugin       *plugin,
                                                         GsPluginRefineFlags refine_flags,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
-typedef gboolean        (*GsPluginRefreshFunc  )       (GsPlugin       *plugin,
+typedef gboolean        (*GsPluginRefreshFunc)         (GsPlugin       *plugin,
                                                         guint           cache_age,
                                                         GsPluginRefreshFlags refresh_flags,
                                                         GCancellable   *cancellable,
@@ -182,32 +183,19 @@ typedef struct {
        GsPluginLoader                  *plugin_loader;
        const gchar                     *function_name;
        const gchar                     *function_name_parent;
-       GsAppList                       *list;
        GPtrArray                       *catlist;
-       GsPluginRefineFlags              refine_flags;
-       GsPluginRefreshFlags             refresh_flags;
-       GsPluginFailureFlags             failure_flags;
-       gchar                           *value;
-       gchar                           **values;
-       GFile                           *file;
-       guint                            cache_age;
-       GsCategory                      *category;
-       GsApp                           *app;
-       AsReview                        *review;
-       GsAuth                          *auth;
-       GsPluginAction                   action;
+       GsPluginJob                     *plugin_job;
        gboolean                         anything_ran;
-       guint                            max_results;
-       guint64                          age;
-       GsAppListSortFunc                sort_func;
-       gpointer                         sort_func_data;
 } GsPluginLoaderHelper;
 
 static GsPluginLoaderHelper *
-gs_plugin_loader_helper_new (GsPluginLoader *plugin_loader)
+gs_plugin_loader_helper_new (GsPluginLoader *plugin_loader, GsPluginJob *plugin_job)
 {
        GsPluginLoaderHelper *helper = g_slice_new0 (GsPluginLoaderHelper);
+       GsPluginAction action = gs_plugin_job_get_action (plugin_job);
        helper->plugin_loader = g_object_ref (plugin_loader);
+       helper->plugin_job = g_object_ref (plugin_job);
+       helper->function_name = gs_plugin_action_to_function_name (action);
        return helper;
 }
 
@@ -215,91 +203,18 @@ static void
 gs_plugin_loader_helper_free (GsPluginLoaderHelper *helper)
 {
        g_object_unref (helper->plugin_loader);
-       if (helper->category != NULL)
-               g_object_unref (helper->category);
-       if (helper->app != NULL)
-               g_object_unref (helper->app);
-       if (helper->auth != NULL)
-               g_object_unref (helper->auth);
-       if (helper->review != NULL)
-               g_object_unref (helper->review);
-       if (helper->file != NULL)
-               g_object_unref (helper->file);
-       if (helper->list != NULL)
-               g_object_unref (helper->list);
+       if (helper->plugin_job != NULL)
+               g_object_unref (helper->plugin_job);
        if (helper->catlist != NULL)
                g_ptr_array_unref (helper->catlist);
-
-       g_free (helper->value);
-       g_strfreev (helper->values);
        g_slice_free (GsPluginLoaderHelper, helper);
 }
 
 static void
-gs_plugin_loader_helper_debug (GsPluginLoaderHelper *helper)
+gs_plugin_loader_job_debug (GsPluginLoaderHelper *helper)
 {
-       g_autoptr(GString) str = g_string_new (NULL);
-       g_string_append_printf (str, "running %s",
-                               gs_plugin_action_to_string (helper->action));
-       if (helper->refine_flags > 0) {
-               g_autofree gchar *tmp = gs_plugin_refine_flags_to_string (helper->refine_flags);
-               g_string_append_printf (str, " with refine-flags=%s", tmp);
-       }
-       if (helper->failure_flags > 0) {
-               g_autofree gchar *tmp = gs_plugin_failure_flags_to_string (helper->failure_flags);
-               g_string_append_printf (str, " with failure-flags=%s", tmp);
-       }
-       if (helper->cache_age != 0) {
-               if (helper->cache_age == G_MAXUINT) {
-                       g_string_append (str, " with cache age=any");
-               } else {
-                       g_string_append_printf (str, " with cache age=%u",
-                                               helper->cache_age);
-               }
-       }
-       if (helper->value != NULL) {
-               g_string_append_printf (str, " with value=%s",
-                                       helper->value);
-       }
-       if (helper->category != NULL) {
-               GsCategory *parent = gs_category_get_parent (helper->category);
-               if (parent != NULL) {
-                       g_string_append_printf (str, " with category=%s/%s",
-                                               gs_category_get_id (parent),
-                                               gs_category_get_id (helper->category));
-               } else {
-                       g_string_append_printf (str, " with category=%s",
-                                               gs_category_get_id (helper->category));
-               }
-       }
-       if (helper->review != NULL) {
-               g_string_append_printf (str, " with review=%s",
-                                       as_review_get_id (helper->review));
-       }
-       if (helper->review != NULL) {
-               g_string_append_printf (str, " with review=%s",
-                                       as_review_get_id (helper->review));
-       }
-       if (helper->auth != NULL) {
-               g_string_append_printf (str, " with auth=%s",
-                                       gs_auth_get_provider_id (helper->auth));
-       }
-       if (helper->file != NULL) {
-               g_autofree gchar *path = g_file_get_path (helper->file);
-               g_string_append_printf (str, " with file=%s", path);
-       }
-       if (helper->list != NULL && gs_app_list_length (helper->list) > 0) {
-               g_autofree const gchar **unique_ids = NULL;
-               g_autofree gchar *unique_ids_str = NULL;
-               unique_ids = g_new0 (const gchar *, gs_app_list_length (helper->list) + 1);
-               for (guint i = 0; i < gs_app_list_length (helper->list); i++) {
-                       GsApp *app = gs_app_list_index (helper->list, i);
-                       unique_ids[i] = gs_app_get_unique_id (app);
-               }
-               unique_ids_str = g_strjoinv (",", (gchar**) unique_ids);
-               g_string_append_printf (str, " on apps %s", unique_ids_str);
-       }
-       g_info ("%s", str->str);
+       g_autofree gchar *str = gs_plugin_job_to_string (helper->plugin_job);
+       g_info ("%s", str);
 }
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPluginLoaderHelper, gs_plugin_loader_helper_free)
@@ -311,12 +226,6 @@ gs_plugin_loader_app_sort_name_cb (GsApp *app1, GsApp *app2, gpointer user_data)
                          gs_app_get_name (app2));
 }
 
-static gint
-gs_plugin_loader_app_sort_id_cb (GsApp *app1, GsApp *app2, gpointer user_data)
-{
-       return g_strcmp0 (gs_app_get_id (app1), gs_app_get_id (app2));
-}
-
 GsPlugin *
 gs_plugin_loader_find_plugin (GsPluginLoader *plugin_loader,
                              const gchar *plugin_name)
@@ -460,6 +369,8 @@ gs_plugin_error_handle_failure (GsPluginLoaderHelper *helper,
                                const GError *error_local,
                                GError **error)
 {
+       GsPluginFailureFlags flags;
+
        /* badly behaved plugin */
        if (error_local == NULL) {
                g_critical ("%s did not set error for %s",
@@ -469,23 +380,24 @@ gs_plugin_error_handle_failure (GsPluginLoaderHelper *helper,
        }
 
        /* abort early to allow main thread to process */
-       if (gs_plugin_loader_is_error_fatal (helper->failure_flags, error_local)) {
+       flags = gs_plugin_job_get_failure_flags (helper->plugin_job);
+       if (gs_plugin_loader_is_error_fatal (flags, error_local)) {
                if (error != NULL)
                        *error = g_error_copy (error_local);
                return FALSE;
        }
 
        /* create event which is handled by the GsShell */
-       if (helper->failure_flags & GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS) {
+       if (flags & GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS) {
                gs_plugin_loader_create_event_from_error (helper->plugin_loader,
-                                                         helper->action,
+                                                         gs_plugin_job_get_action (helper->plugin_job),
                                                          plugin,
-                                                         helper->app,
+                                                         gs_plugin_job_get_app (helper->plugin_job),
                                                          error_local);
        }
 
        /* fallback to console warning */
-       if ((helper->failure_flags & GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE) == 0) {
+       if ((flags & GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE) == 0) {
                g_warning ("failed to call %s on %s: %s",
                           helper->function_name,
                           gs_plugin_get_name (plugin),
@@ -585,13 +497,13 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper,
 
        /* fallback if unset */
        if (app == NULL)
-               app = helper->app;
+               app = gs_plugin_job_get_app (helper->plugin_job);
        if (list == NULL)
-               list = helper->list;
+               list = gs_plugin_job_get_list (helper->plugin_job);
 
        /* run the correct vfunc */
        gs_plugin_loader_action_start (helper->plugin_loader, plugin, FALSE);
-       switch (helper->action) {
+       switch (gs_plugin_job_get_action (helper->plugin_job)) {
        case GS_PLUGIN_ACTION_INITIALIZE:
        case GS_PLUGIN_ACTION_DESTROY:
                {
@@ -608,20 +520,23 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper,
        case GS_PLUGIN_ACTION_REFINE:
                if (g_strcmp0 (helper->function_name, "gs_plugin_refine_wildcard") == 0) {
                        GsPluginRefineWildcardFunc plugin_func = func;
-                       ret = plugin_func (plugin, app, list, helper->refine_flags,
+                       ret = plugin_func (plugin, app, list,
+                                          gs_plugin_job_get_refine_flags (helper->plugin_job),
                                           cancellable, &error_local);
                } else if (g_strcmp0 (helper->function_name, "gs_plugin_refine_app") == 0) {
                        GsPluginRefineAppFunc plugin_func = func;
-                       ret = plugin_func (plugin, app, helper->refine_flags,
+                       ret = plugin_func (plugin, app,
+                                          gs_plugin_job_get_refine_flags (helper->plugin_job),
                                           cancellable, &error_local);
                } else if (g_strcmp0 (helper->function_name, "gs_plugin_refine") == 0) {
                        GsPluginRefineFunc plugin_func = func;
-                       ret = plugin_func (plugin, list, helper->refine_flags,
+                       ret = plugin_func (plugin, list,
+                                          gs_plugin_job_get_refine_flags (helper->plugin_job),
                                           cancellable, &error_local);
                } else {
                        g_critical ("function_name %s invalid for %s",
                                    helper->function_name,
-                                   gs_plugin_action_to_string (helper->action));
+                                   gs_plugin_action_to_string (gs_plugin_job_get_action 
(helper->plugin_job)));
                }
                break;
        case GS_PLUGIN_ACTION_UPDATE:
@@ -634,7 +549,7 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper,
                } else {
                        g_critical ("function_name %s invalid for %s",
                                    helper->function_name,
-                                   gs_plugin_action_to_string (helper->action));
+                                   gs_plugin_action_to_string (gs_plugin_job_get_action 
(helper->plugin_job)));
                }
                break;
        case GS_PLUGIN_ACTION_INSTALL:
@@ -659,18 +574,21 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper,
        case GS_PLUGIN_ACTION_REVIEW_DISMISS:
                {
                        GsPluginReviewFunc plugin_func = func;
-                       ret = plugin_func (plugin, app, helper->review,
+                       ret = plugin_func (plugin, app,
+                                          gs_plugin_job_get_review (helper->plugin_job),
                                           cancellable, &error_local);
                }
                break;
        case GS_PLUGIN_ACTION_GET_RECENT:
                {
                        GsPluginGetRecentFunc plugin_func = func;
-                       ret = plugin_func (plugin, list, helper->age,
+                       ret = plugin_func (plugin, list,
+                                          gs_plugin_job_get_age (helper->plugin_job),
                                           cancellable, &error_local);
                }
                break;
        case GS_PLUGIN_ACTION_GET_UPDATES:
+       case GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL:
        case GS_PLUGIN_ACTION_GET_DISTRO_UPDATES:
        case GS_PLUGIN_ACTION_GET_UNVOTED_REVIEWS:
        case GS_PLUGIN_ACTION_GET_SOURCES:
@@ -687,7 +605,9 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper,
        case GS_PLUGIN_ACTION_SEARCH_PROVIDES:
                {
                        GsPluginSearchFunc plugin_func = func;
-                       ret = plugin_func (plugin, helper->values, list,
+                       g_auto(GStrv) tokens = NULL;
+                       tokens = as_utils_search_tokenize (gs_plugin_job_get_search (helper->plugin_job));
+                       ret = plugin_func (plugin, tokens, list,
                                           cancellable, &error_local);
                }
                break;
@@ -701,28 +621,34 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper,
        case GS_PLUGIN_ACTION_GET_CATEGORY_APPS:
                {
                        GsPluginCategoryFunc plugin_func = func;
-                       ret = plugin_func (plugin, helper->category, list,
+                       ret = plugin_func (plugin,
+                                          gs_plugin_job_get_category (helper->plugin_job),
+                                          list,
                                           cancellable, &error_local);
                }
                break;
        case GS_PLUGIN_ACTION_REFRESH:
                {
                        GsPluginRefreshFunc plugin_func = func;
-                       ret = plugin_func (plugin, helper->cache_age, helper->refresh_flags,
+                       ret = plugin_func (plugin,
+                                          gs_plugin_job_get_age (helper->plugin_job),
+                                          gs_plugin_job_get_refresh_flags (helper->plugin_job),
                                           cancellable, &error_local);
                }
                break;
        case GS_PLUGIN_ACTION_FILE_TO_APP:
                {
                        GsPluginFileToAppFunc plugin_func = func;
-                       ret = plugin_func (plugin, list, helper->file,
+                       ret = plugin_func (plugin, list,
+                                          gs_plugin_job_get_file (helper->plugin_job),
                                           cancellable, &error_local);
                }
                break;
        case GS_PLUGIN_ACTION_URL_TO_APP:
                {
                        GsPluginUrlToAppFunc plugin_func = func;
-                       ret = plugin_func (plugin, list, helper->value,
+                       ret = plugin_func (plugin, list,
+                                          gs_plugin_job_get_search (helper->plugin_job),
                                           cancellable, &error_local);
                }
                break;
@@ -732,7 +658,8 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper,
        case GS_PLUGIN_ACTION_AUTH_LOST_PASSWORD:
                {
                        GsPluginAuthFunc plugin_func = func;
-                       ret = plugin_func (plugin, helper->auth,
+                       ret = plugin_func (plugin,
+                                          gs_plugin_job_get_auth (helper->plugin_job),
                                           cancellable, &error_local);
                }
                break;
@@ -766,14 +693,6 @@ gs_plugin_loader_run_refine_internal (GsPluginLoaderHelper *helper,
        GPtrArray *related;
        GsApp *app;
 
-       /* this implies the other */
-       if (helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_UI)
-               helper->refine_flags |= GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN;
-       if (helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME)
-               helper->refine_flags |= GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN;
-       if (helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE)
-               helper->refine_flags |= GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME;
-
        /* try to adopt each application with a plugin */
        gs_plugin_loader_run_adopt (helper->plugin_loader, list);
 
@@ -811,7 +730,8 @@ gs_plugin_loader_run_refine_internal (GsPluginLoaderHelper *helper,
        }
 
        /* ensure these are sorted by score */
-       if ((helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS) > 0) {
+       if (gs_plugin_job_has_refine_flags (helper->plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS)) {
                GPtrArray *reviews;
                for (i = 0; i < gs_app_list_length (list); i++) {
                        app = gs_app_list_index (list, i);
@@ -822,12 +742,13 @@ gs_plugin_loader_run_refine_internal (GsPluginLoaderHelper *helper,
        }
 
        /* refine addons one layer deep */
-       if ((helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS) > 0) {
+       if (gs_plugin_job_has_refine_flags (helper->plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS)) {
                g_autoptr(GsAppList) addons_list = NULL;
-
-               helper->refine_flags &= ~GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS;
-               helper->refine_flags &= ~GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS;
-               helper->refine_flags &= ~GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS;
+               gs_plugin_job_remove_refine_flags (helper->plugin_job,
+                                                  GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS |
+                                                  GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS |
+                                                  GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS);
                addons_list = gs_app_list_new ();
                for (i = 0; i < gs_app_list_length (list); i++) {
                        app = gs_app_list_index (list, i);
@@ -851,7 +772,8 @@ gs_plugin_loader_run_refine_internal (GsPluginLoaderHelper *helper,
        }
 
        /* also do runtime */
-       if ((helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME) > 0) {
+       if (gs_plugin_job_has_refine_flags (helper->plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME)) {
                g_autoptr(GsAppList) list2 = gs_app_list_new ();
                for (i = 0; i < gs_app_list_length (list); i++) {
                        GsApp *runtime;
@@ -871,10 +793,11 @@ gs_plugin_loader_run_refine_internal (GsPluginLoaderHelper *helper,
        }
 
        /* also do related packages one layer deep */
-       if ((helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED) > 0) {
+       if (gs_plugin_job_has_refine_flags (helper->plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED)) {
                g_autoptr(GsAppList) related_list = NULL;
-
-               helper->refine_flags &= ~GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED;
+               gs_plugin_job_remove_refine_flags (helper->plugin_job,
+                                                  GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED);
                related_list = gs_app_list_new ();
                for (i = 0; i < gs_app_list_length (list); i++) {
                        app = gs_app_list_index (list, i);
@@ -909,9 +832,9 @@ gs_plugin_loader_run_refine (GsPluginLoaderHelper *helper,
 {
        gboolean has_match_any_prefix = FALSE;
        gboolean ret;
-       guint i;
        g_autoptr(GsAppList) freeze_list = NULL;
        g_autoptr(GsPluginLoaderHelper) helper2 = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* nothing to do */
        if (gs_app_list_length (list) == 0)
@@ -919,24 +842,25 @@ gs_plugin_loader_run_refine (GsPluginLoaderHelper *helper,
 
        /* freeze all apps */
        freeze_list = gs_app_list_copy (list);
-       for (i = 0; i < gs_app_list_length (freeze_list); i++) {
+       for (guint i = 0; i < gs_app_list_length (freeze_list); i++) {
                GsApp *app = gs_app_list_index (freeze_list, i);
                g_object_freeze_notify (G_OBJECT (app));
        }
 
        /* first pass */
-       helper2 = gs_plugin_loader_helper_new (helper->plugin_loader);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "list", list,
+                                        "refine-flags", gs_plugin_job_get_refine_flags (helper->plugin_job),
+                                        "failure-flags", gs_plugin_job_get_failure_flags 
(helper->plugin_job),
+                                        NULL);
+       helper2 = gs_plugin_loader_helper_new (helper->plugin_loader, plugin_job);
        helper2->function_name_parent = helper->function_name;
-       helper2->action = GS_PLUGIN_ACTION_REFINE;
-       helper2->list = g_object_ref (list);
-       helper2->refine_flags = helper->refine_flags;
-       helper2->failure_flags = helper->failure_flags;
        ret = gs_plugin_loader_run_refine_internal (helper2, list, cancellable, error);
        if (!ret)
                goto out;
 
        /* second pass for any unadopted apps */
-       for (i = 0; i < gs_app_list_length (list); i++) {
+       for (guint i = 0; i < gs_app_list_length (list); i++) {
                GsApp *app = gs_app_list_index (list, i);
                if (gs_app_has_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX)) {
                        has_match_any_prefix = TRUE;
@@ -952,7 +876,7 @@ gs_plugin_loader_run_refine (GsPluginLoaderHelper *helper,
        }
 
        /* remove any addons that have the same source as the parent app */
-       for (i = 0; i < gs_app_list_length (list); i++) {
+       for (guint i = 0; i < gs_app_list_length (list); i++) {
                g_autoptr(GPtrArray) to_remove = g_ptr_array_new ();
                GsApp *app = gs_app_list_index (list, i);
                GPtrArray *addons = gs_app_get_addons (app);
@@ -982,21 +906,73 @@ gs_plugin_loader_run_refine (GsPluginLoaderHelper *helper,
 
 out:
        /* now emit all the changed signals */
-       for (i = 0; i < gs_app_list_length (freeze_list); i++) {
+       for (guint i = 0; i < gs_app_list_length (freeze_list); i++) {
                GsApp *app = gs_app_list_index (freeze_list, i);
                g_object_thaw_notify (G_OBJECT (app));
        }
        return ret;
 }
 
-static GsAppList *
+static void
+gs_plugin_loader_job_sorted_truncation_again (GsPluginLoaderHelper *helper)
+{
+       GsAppListSortFunc sort_func;
+       gpointer sort_func_data;
+
+       /* not valid */
+       if (gs_plugin_job_get_list (helper->plugin_job) == NULL)
+               return;
+
+       /* unset */
+       sort_func = gs_plugin_job_get_sort_func (helper->plugin_job);
+       if (sort_func == NULL)
+               return;
+       sort_func_data = gs_plugin_job_get_sort_func_data (helper->plugin_job);
+       gs_app_list_sort (gs_plugin_job_get_list (helper->plugin_job), sort_func, sort_func_data);
+}
+
+static void
+gs_plugin_loader_job_sorted_truncation (GsPluginLoaderHelper *helper)
+{
+       GsAppListSortFunc sort_func;
+       guint max_results;
+       GsAppList *list = gs_plugin_job_get_list (helper->plugin_job);
+
+       /* not valid */
+       if (list == NULL)
+               return;
+
+       /* unset */
+       max_results = gs_plugin_job_get_max_results (helper->plugin_job);
+       if (max_results == 0)
+               return;
+
+       /* already small enough */
+       if (gs_app_list_length (list) <= max_results)
+               return;
+
+       /* nothing set */
+       g_debug ("truncating results to %u from %u",
+                max_results, gs_app_list_length (list));
+       sort_func = gs_plugin_job_get_sort_func (helper->plugin_job);
+       if (sort_func == NULL) {
+               g_warning ("no ->sort_func() set, using random!");
+               gs_app_list_randomize (list);
+       } else {
+               gpointer sort_func_data;
+               sort_func_data = gs_plugin_job_get_sort_func_data (helper->plugin_job);
+               gs_app_list_sort (list, sort_func, sort_func_data);
+       }
+       gs_app_list_truncate (list, max_results);
+}
+
+static gboolean
 gs_plugin_loader_run_results (GsPluginLoaderHelper *helper,
                              GCancellable *cancellable,
                              GError **error)
 {
        GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (helper->plugin_loader);
        g_autoptr(AsProfileTask) ptask = NULL;
-       g_autoptr(GsAppList) list = gs_app_list_new ();
 
        /* profile */
        ptask = as_profile_start (priv->profile, "GsPlugin::*(%s)",
@@ -1008,19 +984,15 @@ gs_plugin_loader_run_results (GsPluginLoaderHelper *helper,
                GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
                if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
                        gs_utils_error_convert_gio (error);
-                       return NULL;
+                       return FALSE;
                }
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, list,
+               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
                                                  cancellable, error)) {
-                       return NULL;
+                       return FALSE;
                }
+               gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
        }
-
-       /* run refine() on each one */
-       if (!gs_plugin_loader_run_refine (helper, list, cancellable, error))
-               return NULL;
-
-       return g_steal_pointer (&list);
+       return TRUE;
 }
 
 static const gchar *
@@ -1164,7 +1136,8 @@ gs_plugin_loader_app_is_valid (GsApp *app, gpointer user_data)
        }
 
        /* don't show unconverted packages in the application view */
-       if (((helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES) == 0) &&
+       if (!gs_plugin_job_has_refine_flags (helper->plugin_job,
+                                                GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES) &&
            (gs_app_get_kind (app) == AS_APP_KIND_GENERIC)) {
 //             g_debug ("app invalid as only a %s: %s",
 //                      as_app_kind_to_string (gs_app_get_kind (app)),
@@ -1293,612 +1266,6 @@ gs_plugin_loader_get_event_by_error (GsPluginLoader *plugin_loader, GsPluginErro
        return NULL;
 }
 
-static gboolean
-gs_plugin_loader_run_action (GsPluginLoaderHelper *helper,
-                            GCancellable *cancellable,
-                            GError **error)
-{
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (helper->plugin_loader);
-
-       /* make sure the progress is properly initialized */
-       gs_app_set_progress (helper->app, 0);
-
-       /* run each plugin */
-       for (guint i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
-                       gs_utils_error_convert_gio (error);
-                       return FALSE;
-               }
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, helper->app, NULL,
-                                                 cancellable, error)) {
-                       return FALSE;
-               }
-               gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
-       }
-
-       /* nothing ran */
-       if (!helper->anything_ran) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "no plugin could handle %s",
-                            helper->function_name);
-               return FALSE;
-       }
-       return TRUE;
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_get_updates_thread_cb (GTask *task,
-                                       gpointer object,
-                                       gpointer task_data,
-                                       GCancellable *cancellable)
-{
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       GError *error = NULL;
-
-       /* do things that would block */
-       if (helper->action == GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL) {
-               helper->list = gs_plugin_loader_run_results (helper, cancellable, &error);
-               if (helper->list == NULL) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       } else {
-               /* get downloaded updates */
-               helper->list = gs_plugin_loader_run_results (helper, cancellable, &error);
-               if (helper->list == NULL) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-
-               /* get not-yet-downloaded updates */
-               if (!g_settings_get_boolean (priv->settings, "download-updates")) {
-                       g_autoptr(GsAppList) list = NULL;
-                       helper->function_name = "gs_plugin_add_updates_pending";
-                       list = gs_plugin_loader_run_results (helper, cancellable, &error);
-                       if (list == NULL) {
-                               g_task_return_error (task, error);
-                               return;
-                       }
-                       gs_app_list_add_list (helper->list, list);
-               }
-       }
-
-       /* remove any apps that are not proper applications or OS updates */
-       gs_app_list_filter (helper->list,
-                           gs_plugin_loader_app_is_valid_updatable, helper);
-
-       /* filter duplicates with priority */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_NONE);
-
-       /* predictable return order */
-       gs_app_list_sort (helper->list, gs_plugin_loader_app_sort_id_cb, NULL);
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
-/**
- * gs_plugin_loader_get_updates_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_updates()
- * 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, set as compulsory.
- *
- * Any #GsApp's of kind %AS_APP_KIND_GENERIC that remain after refining are
- * added to a new virtual #GsApp of kind %AS_APP_KIND_OS_UPDATE and all the
- * %AS_APP_KIND_GENERIC objects are moved to related packages of this object.
- *
- * This means all of the #GsApp's returning from this function are of kind
- * %AS_APP_KIND_DESKTOP or %AS_APP_KIND_OS_UPDATE.
- *
- * The #GsApps may be in helper %AS_APP_STATE_INSTALLED or %AS_APP_STATE_AVAILABLE
- * and the UI may want to filter the two classes of applications differently.
- **/
-void
-gs_plugin_loader_get_updates_async (GsPluginLoader *plugin_loader,
-                                   GsPluginRefineFlags refine_flags,
-                                   GsPluginFailureFlags failure_flags,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       if ((helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_USE_HISTORY) > 0)
-               helper->action = GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL;
-       else
-               helper->action = GS_PLUGIN_ACTION_GET_UPDATES;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_updates_thread_cb);
-}
-
-/**
- * gs_plugin_loader_get_updates_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_get_updates_finish (GsPluginLoader *plugin_loader,
-                                      GAsyncResult *res,
-                                      GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_get_distro_upgrades_thread_cb (GTask *task,
-                                               gpointer object,
-                                               gpointer task_data,
-                                               GCancellable *cancellable)
-{
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GError *error = NULL;
-
-       helper->function_name = "gs_plugin_add_distro_upgrades";
-       helper->list = gs_plugin_loader_run_results (helper, cancellable, &error);
-       if (helper->list == NULL) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* filter duplicates with priority */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_NONE);
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
-/**
- * gs_plugin_loader_get_distro_upgrades_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_distro_upgrades()
- * function. The plugins can returns #GsApp objects of kind %AS_APP_KIND_OS_UPGRADE.
- **/
-void
-gs_plugin_loader_get_distro_upgrades_async (GsPluginLoader *plugin_loader,
-                                           GsPluginRefineFlags refine_flags,
-                                           GsPluginFailureFlags failure_flags,
-                                           GCancellable *cancellable,
-                                           GAsyncReadyCallback callback,
-                                           gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->action = GS_PLUGIN_ACTION_GET_DISTRO_UPDATES;
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_distro_upgrades_thread_cb);
-}
-
-/**
- * gs_plugin_loader_get_distro_upgrades_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_get_distro_upgrades_finish (GsPluginLoader *plugin_loader,
-                                            GAsyncResult *res,
-                                            GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_get_unvoted_reviews_thread_cb (GTask *task,
-                                               gpointer object,
-                                               gpointer task_data,
-                                               GCancellable *cancellable)
-{
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GError *error = NULL;
-
-       helper->function_name = "gs_plugin_add_unvoted_reviews";
-       helper->list = gs_plugin_loader_run_results (helper, cancellable, &error);
-       if (helper->list == NULL) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* filter duplicates with priority */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_NONE);
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
-/**
- * gs_plugin_loader_get_unvoted_reviews_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_unvoted_reviews()
- * function.
- **/
-void
-gs_plugin_loader_get_unvoted_reviews_async (GsPluginLoader *plugin_loader,
-                                           GsPluginRefineFlags refine_flags,
-                                           GsPluginFailureFlags failure_flags,
-                                           GCancellable *cancellable,
-                                           GAsyncReadyCallback callback,
-                                           gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->action = GS_PLUGIN_ACTION_GET_UNVOTED_REVIEWS;
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_unvoted_reviews_thread_cb);
-}
-
-/**
- * gs_plugin_loader_get_unvoted_reviews_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_get_unvoted_reviews_finish (GsPluginLoader *plugin_loader,
-                                            GAsyncResult *res,
-                                            GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_get_sources_thread_cb (GTask *task,
-                                       gpointer object,
-                                       gpointer task_data,
-                                       GCancellable *cancellable)
-{
-       GsPluginLoaderHelper *helper = task_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GError *error = NULL;
-
-       helper->function_name = "gs_plugin_add_sources";
-       helper->list = gs_plugin_loader_run_results (helper, cancellable, &error);
-       if (helper->list == NULL) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* filter duplicates with priority */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_NONE);
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
-/**
- * gs_plugin_loader_get_sources_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_sources()
- * function. The plugins return #GsApp objects of kind %AS_APP_KIND_SOURCE..
- *
- * The *applications* installed from each source can be obtained using
- * gs_app_get_related() if this information is available.
- **/
-void
-gs_plugin_loader_get_sources_async (GsPluginLoader *plugin_loader,
-                                   GsPluginRefineFlags refine_flags,
-                                   GsPluginFailureFlags failure_flags,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->action = GS_PLUGIN_ACTION_GET_SOURCES;
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_sources_thread_cb);
-}
-
-/**
- * gs_plugin_loader_get_sources_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_get_sources_finish (GsPluginLoader *plugin_loader,
-                                      GAsyncResult *res,
-                                      GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_get_installed_thread_cb (GTask *task,
-                                         gpointer object,
-                                         gpointer task_data,
-                                         GCancellable *cancellable)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GError *error = NULL;
-
-       /* do things that would block */
-       helper->function_name = "gs_plugin_add_installed";
-       helper->list = gs_plugin_loader_run_results (helper, cancellable, &error);
-       if (helper->list == NULL) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* filter package list */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_is_valid, helper);
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_is_valid_installed, helper);
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
-/**
- * gs_plugin_loader_get_installed_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_installed()
- * 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, set as compulsory.
- *
- * Any #GsApp's of kind %AS_APP_KIND_GENERIC or %AS_APP_KIND_UNKNOWN that remain
- * after refining are automatically removed.
- *
- * This means all of the #GsApp's returning from this function are of kind
- * %AS_APP_KIND_DESKTOP.
- *
- * The #GsApps will all initially be in helper %AS_APP_STATE_INSTALLED.
- **/
-void
-gs_plugin_loader_get_installed_async (GsPluginLoader *plugin_loader,
-                                     GsPluginRefineFlags refine_flags,
-                                     GsPluginFailureFlags failure_flags,
-                                     GCancellable *cancellable,
-                                     GAsyncReadyCallback callback,
-                                     gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->action = GS_PLUGIN_ACTION_GET_INSTALLED;
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_installed_thread_cb);
-}
-
-/**
- * gs_plugin_loader_get_installed_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_get_installed_finish (GsPluginLoader *plugin_loader,
-                                      GAsyncResult *res,
-                                      GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_get_popular_thread_cb (GTask *task,
-                                       gpointer object,
-                                       gpointer task_data,
-                                       GCancellable *cancellable)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       GError *error = NULL;
-       g_auto(GStrv) apps = NULL;
-
-       /* debugging only */
-       if (g_getenv ("GNOME_SOFTWARE_POPULAR"))
-               apps = g_strsplit (g_getenv ("GNOME_SOFTWARE_POPULAR"), ",", 0);
-
-       /* are we using a corporate build */
-       if (apps == NULL)
-               apps = g_settings_get_strv (priv->settings, "popular-overrides");
-       if (apps != NULL && g_strv_length (apps) > 0) {
-               guint i;
-               helper->list = gs_app_list_new ();
-               for (i = 0; apps[i] != NULL; i++) {
-                       g_autoptr(GsApp) app = gs_app_new (apps[i]);
-                       gs_app_add_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX);
-                       gs_app_list_add (helper->list, app);
-               }
-
-               /* prepare refine helper */
-               helper->action = GS_PLUGIN_ACTION_REFINE;
-               helper->function_name = "gs_plugin_refine";
-               helper->failure_flags = GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS;
-               if (!gs_plugin_loader_run_refine (helper, helper->list, cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       } else {
-               /* do things that would block */
-               helper->function_name = "gs_plugin_add_popular";
-               helper->list = gs_plugin_loader_run_results (helper, cancellable, &error);
-               if (helper->list == NULL) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       }
-
-       /* filter package list */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_is_valid, helper);
-       gs_app_list_filter (helper->list, gs_plugin_loader_filter_qt_for_gtk, NULL);
-       gs_app_list_filter (helper->list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
-
-       /* filter duplicates with priority */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
-void
-gs_plugin_loader_get_popular_async (GsPluginLoader *plugin_loader,
-                                   GsPluginRefineFlags refine_flags,
-                                   GsPluginFailureFlags failure_flags,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->action = GS_PLUGIN_ACTION_GET_POPULAR;
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_popular_thread_cb);
-}
-
-/**
- * gs_plugin_loader_get_popular_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_get_popular_finish (GsPluginLoader *plugin_loader,
-                                    GAsyncResult *res,
-                                    GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
 /******************************************************************************/
 
 static gboolean
@@ -1910,105 +1277,24 @@ gs_plugin_loader_featured_debug (GsApp *app, gpointer user_data)
        return FALSE;
 }
 
-static void
-gs_plugin_loader_get_featured_thread_cb (GTask *task,
-                                        gpointer object,
-                                        gpointer task_data,
-                                        GCancellable *cancellable)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GError *error = NULL;
-
-       /* do things that would block */
-       helper->function_name = "gs_plugin_add_featured";
-       helper->list = gs_plugin_loader_run_results (helper, cancellable, &error);
-       if (helper->list == NULL) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* filter package list */
-       if (g_getenv ("GNOME_SOFTWARE_FEATURED") != NULL) {
-               gs_app_list_filter (helper->list, gs_plugin_loader_featured_debug, NULL);
-       } else {
-               gs_app_list_filter (helper->list, gs_plugin_loader_app_is_valid, helper);
-               gs_app_list_filter (helper->list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
-       }
-
-       /* filter duplicates with priority */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
-/**
- * gs_plugin_loader_get_featured_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_featured()
- * 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, set as compulsory.
- *
- * Any #GsApp's of kind %AS_APP_KIND_GENERIC that remain after refining are
- * automatically removed.
- *
- * This means all of the #GsApp's returning from this function are of kind
- * %AS_APP_KIND_DESKTOP.
- *
- * The #GsApps may be in helper %AS_APP_STATE_INSTALLED or %AS_APP_STATE_AVAILABLE
- * and the UI may want to filter the two classes of applications differently.
- **/
-void
-gs_plugin_loader_get_featured_async (GsPluginLoader *plugin_loader,
-                                    GsPluginRefineFlags refine_flags,
-                                    GsPluginFailureFlags failure_flags,
-                                    GCancellable *cancellable,
-                                    GAsyncReadyCallback callback,
-                                    gpointer user_data)
+static gint
+gs_plugin_loader_app_sort_kind_cb (GsApp *app1, GsApp *app2, gpointer user_data)
 {
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->action = GS_PLUGIN_ACTION_GET_FEATURED;
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_featured_thread_cb);
+       if (gs_app_get_kind (app1) > gs_app_get_kind (app2))
+               return -1;
+       if (gs_app_get_kind (app1) < gs_app_get_kind (app2))
+               return 1;
+       return 0;
 }
 
-/**
- * gs_plugin_loader_get_featured_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_get_featured_finish (GsPluginLoader *plugin_loader,
-                                     GAsyncResult *res,
-                                     GError **error)
+static gint
+gs_plugin_loader_app_sort_match_value_cb (GsApp *app1, GsApp *app2, gpointer user_data)
 {
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
+       if (gs_app_get_match_value (app1) > gs_app_get_match_value (app2))
+               return -1;
+       if (gs_app_get_match_value (app1) < gs_app_get_match_value (app2))
+               return 1;
+       return 0;
 }
 
 /******************************************************************************/
@@ -2071,238 +1357,15 @@ gs_plugin_loader_convert_unavailable (GsAppList *list, const gchar *search)
        }
 }
 
-static gint
-gs_plugin_loader_app_sort_match_value_cb (GsApp *app1, GsApp *app2, gpointer user_data)
-{
-       if (gs_app_get_match_value (app1) > gs_app_get_match_value (app2))
-               return -1;
-       if (gs_app_get_match_value (app1) < gs_app_get_match_value (app2))
-               return 1;
-       return 0;
-}
-
-static void
-gs_plugin_loader_search_thread_cb (GTask *task,
-                                  gpointer object,
-                                  gpointer task_data,
-                                  GCancellable *cancellable)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-
-       /* run each plugin */
-       if (helper->values == NULL) {
-               g_task_return_new_error (task,
-                                        GS_PLUGIN_ERROR,
-                                        GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                        "no valid search terms");
-               return;
-       }
-       for (guint i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       }
-
-       /* fallback to the match value */
-       if (helper->sort_func == NULL)
-               helper->sort_func = gs_plugin_loader_app_sort_match_value_cb;
-
-       /* too many results */
-       if (helper->max_results > 0 &&
-           gs_app_list_length (helper->list) > helper->max_results) {
-               gs_app_list_sort (helper->list, helper->sort_func, helper->sort_func_data);
-               g_debug ("truncating results to %u from %u",
-                        helper->max_results, gs_app_list_length (helper->list));
-               gs_app_list_truncate (helper->list, helper->max_results);
-       }
-
-       /* run refine() on each one */
-       if (!gs_plugin_loader_run_refine (helper, helper->list, cancellable, &error)) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* convert any unavailables */
-       gs_plugin_loader_convert_unavailable (helper->list, helper->value);
-
-       /* filter package list */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_is_valid, helper);
-       gs_app_list_filter (helper->list, gs_plugin_loader_filter_qt_for_gtk, NULL);
-       gs_app_list_filter (helper->list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
-
-       /* filter duplicates with priority, taking into account the source name
-        * & version, so we combine available updates with the installed app */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_KEY_ID |
-                                                 GS_APP_LIST_FILTER_FLAG_KEY_SOURCE |
-                                                 GS_APP_LIST_FILTER_FLAG_KEY_VERSION);
-
-       /* sort these again as the refine may have added useful metadata */
-       gs_app_list_sort (helper->list, helper->sort_func, helper->sort_func_data);
-
-       /* too many */
-       if (gs_app_list_length (helper->list) > 500) {
-               g_task_return_new_error (task,
-                                        GS_PLUGIN_ERROR,
-                                        GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                        "Too many search results returned");
-               return;
-       }
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
 /**
- * gs_plugin_loader_search_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_search()
- * 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, set as compulsory.
- *
- * Any #GsApp's of kind %AS_APP_KIND_GENERIC or %AS_APP_KIND_UNKNOWN that remain
- * after refining are automatically removed.
- *
- * This means all of the #GsApp's returning from this function are of kind
- * %AS_APP_KIND_DESKTOP.
- *
- * The #GsApps may be in helper %AS_APP_STATE_INSTALLED or %AS_APP_STATE_AVAILABLE
- * and the UI may want to filter the two classes of applications differently.
- **/
-void
-gs_plugin_loader_search_async (GsPluginLoader *plugin_loader,
-                              const gchar *value,
-                              guint max_results,
-                              GsAppListSortFunc sort_func,
-                              gpointer sort_func_data,
-                              GsPluginRefineFlags refine_flags,
-                              GsPluginFailureFlags failure_flags,
-                              GCancellable *cancellable,
-                              GAsyncReadyCallback callback,
-                              gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->list = gs_app_list_new ();
-       helper->max_results = max_results;
-       helper->sort_func = sort_func;
-       helper->sort_func_data = sort_func_data;
-       helper->value = g_strdup (value);
-       helper->values = as_utils_search_tokenize (helper->value);
-       helper->action = GS_PLUGIN_ACTION_SEARCH;
-       helper->function_name = "gs_plugin_add_search";
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_search_thread_cb);
-}
-
-/**
- * gs_plugin_loader_search_finish:
+ * gs_plugin_loader_job_process_finish:
  *
  * Return value: (element-type GsApp) (transfer full): A list of applications
  **/
 GsAppList *
-gs_plugin_loader_search_finish (GsPluginLoader *plugin_loader,
-                               GAsyncResult *res,
-                               GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-/**
- * gs_plugin_loader_search_files_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_search_files()
- * 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, set as compulsory.
- *
- * Any #GsApp's of kind %AS_APP_KIND_GENERIC or %AS_APP_KIND_UNKNOWN that remain
- * after refining are automatically removed.
- *
- * This means all of the #GsApp's returning from this function are of kind
- * %AS_APP_KIND_DESKTOP.
- *
- * The #GsApps may be in helper %AS_APP_STATE_INSTALLED or %AS_APP_STATE_AVAILABLE
- * and the UI may want to filter the two classes of applications differently.
- **/
-void
-gs_plugin_loader_search_files_async (GsPluginLoader *plugin_loader,
-                                     const gchar *value,
-                                     GsPluginRefineFlags refine_flags,
-                                     GsPluginFailureFlags failure_flags,
-                                     GCancellable *cancellable,
-                                     GAsyncReadyCallback callback,
-                                     gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->list = gs_app_list_new ();
-       helper->value = g_strdup (value);
-       helper->values = g_new0 (gchar *, 2);
-       helper->values[0] = g_strdup (helper->value);
-       helper->action = GS_PLUGIN_ACTION_SEARCH_FILES;
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_search_thread_cb);
-}
-
-/**
- * gs_plugin_loader_search_files_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_search_files_finish (GsPluginLoader *plugin_loader,
-                                      GAsyncResult *res,
-                                      GError **error)
+gs_plugin_loader_job_process_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);
@@ -2313,79 +1376,22 @@ gs_plugin_loader_search_files_finish (GsPluginLoader *plugin_loader,
        return g_task_propagate_pointer (G_TASK (res), error);
 }
 
-/******************************************************************************/
-
-/**
- * gs_plugin_loader_search_what_provides_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_search_what_provides()
- * 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, set as compulsory.
- *
- * Any #GsApp's of kind %AS_APP_KIND_GENERIC or %AS_APP_KIND_UNKNOWN that remain
- * after refining are automatically removed.
- *
- * This means all of the #GsApp's returning from this function are of kind
- * %AS_APP_KIND_DESKTOP.
- *
- * The #GsApps may be in helper %AS_APP_STATE_INSTALLED or %AS_APP_STATE_AVAILABLE
- * and the UI may want to filter the two classes of applications differently.
- **/
-void
-gs_plugin_loader_search_what_provides_async (GsPluginLoader *plugin_loader,
-                                             const gchar *value,
-                                             GsPluginRefineFlags refine_flags,
-                                             GsPluginFailureFlags failure_flags,
-                                             GCancellable *cancellable,
-                                             GAsyncReadyCallback callback,
-                                             gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->list = gs_app_list_new ();
-       helper->value = g_strdup (value);
-       helper->values = g_new0 (gchar *, 2);
-       helper->values[0] = g_strdup (helper->value);
-       helper->action = GS_PLUGIN_ACTION_SEARCH_PROVIDES;
-       helper->function_name = "gs_plugin_add_search_what_provides";
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_search_thread_cb);
-}
-
 /**
- * gs_plugin_loader_search_what_provides_finish:
+ * gs_plugin_loader_job_action_finish:
  *
- * Return value: (element-type GsApp) (transfer full): A list of applications
+ * Return value: success
  **/
-GsAppList *
-gs_plugin_loader_search_what_provides_finish (GsPluginLoader *plugin_loader,
-                                              GAsyncResult *res,
-                                              GError **error)
+gboolean
+gs_plugin_loader_job_action_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);
+       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
+       g_return_val_if_fail (G_IS_TASK (res), FALSE);
+       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
+       return g_task_propagate_pointer (G_TASK (res), error) != NULL;
 }
 
 /******************************************************************************/
@@ -2437,40 +1443,29 @@ gs_plugin_loader_fix_category_all (GsCategory *category)
 }
 
 static void
-gs_plugin_loader_get_categories_thread_cb (GTask *task,
-                                          gpointer object,
-                                          gpointer task_data,
-                                          GCancellable *cancellable)
+gs_plugin_loader_job_get_categories_thread_cb (GTask *task,
+                                             gpointer object,
+                                             gpointer task_data,
+                                             GCancellable *cancellable)
 {
        GError *error = NULL;
        GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       const gchar *function_name = "gs_plugin_add_categories";
-       guint i;
 
        /* run each plugin */
-       helper->function_name = function_name;
-       for (i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
+       if (!gs_plugin_loader_run_results (helper, cancellable, &error)) {
+               g_task_return_error (task, error);
+               return;
        }
 
        /* make sure 'All' has the right categories */
-       for (i = 0; i < helper->catlist->len; i++) {
+       for (guint i = 0; i < helper->catlist->len; i++) {
                GsCategory *cat = g_ptr_array_index (helper->catlist, i);
                gs_plugin_loader_fix_category_all (cat);
        }
 
        /* sort by name */
        g_ptr_array_sort (helper->catlist, gs_plugin_loader_category_sort_cb);
-       for (i = 0; i < helper->catlist->len; i++) {
+       for (guint i = 0; i < helper->catlist->len; i++) {
                GsCategory *cat = GS_CATEGORY (g_ptr_array_index (helper->catlist, i));
                gs_category_sort_children (cat);
        }
@@ -2489,15 +1484,14 @@ gs_plugin_loader_get_categories_thread_cb (GTask *task,
 }
 
 /**
- * gs_plugin_loader_get_categories_async:
+ * gs_plugin_loader_job_get_categories_async:
  *
  * This method calls all plugins that implement the gs_plugin_add_categories()
  * function. The plugins return #GsCategory objects.
  **/
 void
-gs_plugin_loader_get_categories_async (GsPluginLoader *plugin_loader,
-                                      GsPluginRefineFlags refine_flags,
-                                      GsPluginFailureFlags failure_flags,
+gs_plugin_loader_job_get_categories_async (GsPluginLoader *plugin_loader,
+                                      GsPluginJob *plugin_job,
                                       GCancellable *cancellable,
                                       GAsyncReadyCallback callback,
                                       gpointer user_data)
@@ -2506,148 +1500,27 @@ gs_plugin_loader_get_categories_async (GsPluginLoader *plugin_loader,
        g_autoptr(GTask) task = NULL;
 
        g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
+       g_return_if_fail (GS_IS_PLUGIN_JOB (plugin_job));
        g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
        /* save helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
+       helper = gs_plugin_loader_helper_new (plugin_loader, plugin_job);
        helper->catlist = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
-       helper->action = GS_PLUGIN_ACTION_GET_CATEGORIES;
-       gs_plugin_loader_helper_debug (helper);
+       gs_plugin_loader_job_debug (helper);
 
        /* run in a thread */
        task = g_task_new (plugin_loader, cancellable, callback, user_data);
        g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_categories_thread_cb);
+       g_task_run_in_thread (task, gs_plugin_loader_job_get_categories_thread_cb);
 }
 
 /**
- * gs_plugin_loader_get_categories_finish:
+ * gs_plugin_loader_job_get_categories_finish:
  *
  * Return value: (element-type GsCategory) (transfer full): A list of applications
  **/
 GPtrArray *
-gs_plugin_loader_get_categories_finish (GsPluginLoader *plugin_loader,
-                                       GAsyncResult *res,
-                                       GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_get_category_apps_thread_cb (GTask *task,
-                                             gpointer object,
-                                             gpointer task_data,
-                                             GCancellable *cancellable)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-
-       /* run each plugin */
-       for (guint i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       }
-
-       /* run refine() on each one */
-       if (!gs_plugin_loader_run_refine (helper, helper->list, cancellable, &error)) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* filter package list */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_is_non_compulsory, NULL);
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_is_valid, helper);
-       gs_app_list_filter (helper->list, gs_plugin_loader_filter_qt_for_gtk, NULL);
-       gs_app_list_filter (helper->list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
-
-       /* filter duplicates with priority */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
-
-       /* sort, just in case the UI doesn't do this */
-       gs_app_list_sort (helper->list, gs_plugin_loader_app_sort_name_cb, NULL);
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
-/**
- * gs_plugin_loader_get_category_apps_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_category_apps()
- * 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, set as compulsory.
- *
- * Any #GsApp's of kind %AS_APP_KIND_GENERIC or %AS_APP_KIND_UNKNOWN that remain
- * after refining are automatically removed.
- *
- * This means all of the #GsApp's returning from this function are of kind
- * %AS_APP_KIND_DESKTOP.
- *
- * The #GsApps may be in helper %AS_APP_STATE_INSTALLED or %AS_APP_STATE_AVAILABLE
- * and the UI may want to filter the two classes of applications differently.
- **/
-void
-gs_plugin_loader_get_category_apps_async (GsPluginLoader *plugin_loader,
-                                         GsCategory *category,
-                                         GsPluginRefineFlags refine_flags,
-                                         GsPluginFailureFlags failure_flags,
-                                         GCancellable *cancellable,
-                                         GAsyncReadyCallback callback,
-                                         gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->list = gs_app_list_new ();
-       helper->category = g_object_ref (category);
-       helper->action = GS_PLUGIN_ACTION_GET_CATEGORY_APPS;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_category_apps_thread_cb);
-}
-
-/**
- * gs_plugin_loader_get_category_apps_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_get_category_apps_finish (GsPluginLoader *plugin_loader,
+gs_plugin_loader_job_get_categories_finish (GsPluginLoader *plugin_loader,
                                           GAsyncResult *res,
                                           GError **error)
 {
@@ -2662,200 +1535,6 @@ gs_plugin_loader_get_category_apps_finish (GsPluginLoader *plugin_loader,
 
 /******************************************************************************/
 
-static void
-gs_plugin_loader_get_recent_thread_cb (GTask *task,
-                                      gpointer object,
-                                      gpointer task_data,
-                                      GCancellable *cancellable)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       const guint max_results = 20;
-
-       /* run each plugin */
-       for (guint i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       }
-
-       /* limit: TODO, use sort func */
-       if (gs_app_list_length (helper->list) > max_results) {
-               gs_app_list_randomize (helper->list);
-               g_debug ("truncating results to %u from %u",
-                        max_results, gs_app_list_length (helper->list));
-               gs_app_list_truncate (helper->list, max_results);
-       }
-
-       /* run refine() on each one */
-       if (!gs_plugin_loader_run_refine (helper, helper->list, cancellable, &error)) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* filter package list */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_is_non_compulsory, NULL);
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_is_valid, helper);
-       gs_app_list_filter (helper->list, gs_plugin_loader_filter_qt_for_gtk, NULL);
-       gs_app_list_filter (helper->list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
-
-       /* filter duplicates with priority */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
-
-       /* sort, just in case the UI doesn't do this */
-       gs_app_list_sort (helper->list, gs_plugin_loader_app_sort_name_cb, NULL);
-
-       /* success */
-       g_task_return_pointer (task, g_object_ref (helper->list), (GDestroyNotify) g_object_unref);
-}
-
-/**
- * gs_plugin_loader_get_recent_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_recent()
- * function. The plugins return applications that have has upstream releases
- * within the duration of @age;
- **/
-void
-gs_plugin_loader_get_recent_async (GsPluginLoader *plugin_loader,
-                                  guint64 age,
-                                  GsPluginRefineFlags refine_flags,
-                                  GsPluginFailureFlags failure_flags,
-                                  GCancellable *cancellable,
-                                  GAsyncReadyCallback callback,
-                                  gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->list = gs_app_list_new ();
-       helper->age = age;
-       helper->action = GS_PLUGIN_ACTION_GET_RECENT;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_get_recent_thread_cb);
-}
-
-/**
- * gs_plugin_loader_get_recent_finish:
- *
- * Return value: (element-type GsApp) (transfer full): A list of applications
- **/
-GsAppList *
-gs_plugin_loader_get_recent_finish (GsPluginLoader *plugin_loader,
-                                   GAsyncResult *res,
-                                   GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_app_refine_thread_cb (GTask *task,
-                                      gpointer object,
-                                      gpointer task_data,
-                                      GCancellable *cancellable)
-{
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-
-       if (!gs_plugin_loader_run_refine (helper, helper->list, cancellable, &error)) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* success */
-       g_task_return_boolean (task, TRUE);
-}
-
-/**
- * gs_plugin_loader_app_refine_async:
- *
- * This method calls all plugins that implement the gs_plugin_refine()
- * function.
- **/
-void
-gs_plugin_loader_app_refine_async (GsPluginLoader *plugin_loader,
-                                  GsApp *app,
-                                  GsPluginRefineFlags refine_flags,
-                                  GsPluginFailureFlags failure_flags,
-                                  GCancellable *cancellable,
-                                  GAsyncReadyCallback callback,
-                                  gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->app = g_object_ref (app);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->action = GS_PLUGIN_ACTION_REFINE;
-       helper->list = gs_app_list_new ();
-       gs_app_list_add (helper->list, helper->app);
-
-       /* enforce this */
-       if (helper->refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS)
-               helper->refine_flags |= GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON;
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_app_refine_thread_cb);
-}
-
-/**
- * gs_plugin_loader_app_refine_finish:
- *
- * Return value: success
- **/
-gboolean
-gs_plugin_loader_app_refine_finish (GsPluginLoader *plugin_loader,
-                                   GAsyncResult *res,
-                                   GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
-       g_return_val_if_fail (G_IS_TASK (res), FALSE);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), FALSE);
-       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-       return g_task_propagate_boolean (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
 static gboolean
 emit_pending_apps_idle (gpointer loader)
 {
@@ -2866,127 +1545,51 @@ emit_pending_apps_idle (gpointer loader)
 }
 
 static void
-gs_plugin_loader_app_action_thread_cb (GTask *task,
-                                      gpointer object,
-                                      gpointer task_data,
-                                      GCancellable *cancellable)
+gs_plugin_loader_pending_apps_add (GsPluginLoader *plugin_loader,
+                                  GsPluginLoaderHelper *helper)
 {
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+       GsAppList *list = gs_plugin_job_get_list (helper->plugin_job);
        GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GPtrArray *addons;
-       gboolean ret;
-       guint i;
-
-       /* add to list */
-       g_mutex_lock (&priv->pending_apps_mutex);
-       g_ptr_array_add (priv->pending_apps, g_object_ref (helper->app));
-       g_mutex_unlock (&priv->pending_apps_mutex);
-       g_idle_add (emit_pending_apps_idle, g_object_ref (plugin_loader));
-
-       /* perform action */
-       if (gs_plugin_loader_run_action (helper, cancellable, &error)) {
-               g_autoptr(GsPluginLoaderHelper) helper2 = NULL;
-
-               /* reset the progress after successful operations */
-               gs_app_set_progress (helper->app, 0);
-
-               /* unstage addons */
-               addons = gs_app_get_addons (helper->app);
-               for (i = 0; i < addons->len; i++) {
-                       GsApp *addon = g_ptr_array_index (addons, i);
-                       if (gs_app_get_to_be_installed (addon))
-                               gs_app_set_to_be_installed (addon, FALSE);
-               }
-
-               /* refine again to make sure we pick up new source id */
-               helper2 = gs_plugin_loader_helper_new (helper->plugin_loader);
-               helper2->function_name = "gs_plugin_refine_app";
-               helper2->function_name_parent = helper->function_name;
-               helper2->action = GS_PLUGIN_ACTION_REFINE;
-               helper2->list = gs_app_list_new ();
-               helper2->refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN;
-               helper2->failure_flags = helper->failure_flags;
-               gs_app_list_add (helper2->list, helper->app);
-               ret = gs_plugin_loader_run_refine_internal (helper2,
-                                                           helper2->list,
-                                                           cancellable,
-                                                           &error);
-               if (ret) {
-                       g_task_return_boolean (task, TRUE);
-               } else {
-                       g_task_return_error (task, error);
-               }
-       } else {
-               gs_app_set_state_recover (helper->app);
-               g_task_return_error (task, error);
-       }
+       g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->pending_apps_mutex);
 
-       /* check the app is not still in an action helper */
-       switch (gs_app_get_state (helper->app)) {
-       case AS_APP_STATE_INSTALLING:
-       case AS_APP_STATE_REMOVING:
-               g_warning ("application %s left in %s helper",
-                          gs_app_get_unique_id (helper->app),
-                          as_app_state_to_string (gs_app_get_state (helper->app)));
-               gs_app_set_state (helper->app, AS_APP_STATE_UNKNOWN);
-               break;
-       default:
-               break;
+       g_assert (gs_app_list_length (list) > 0);
+       for (guint i = 0; i < gs_app_list_length (list); i++) {
+               GsApp *app = gs_app_list_index (list, i);
+               g_ptr_array_add (priv->pending_apps, g_object_ref (app));
+               /* make sure the progress is properly initialized */
+               gs_app_set_progress (app, 0);
        }
-
-       /* remove from list */
-       g_mutex_lock (&priv->pending_apps_mutex);
-       g_ptr_array_remove (priv->pending_apps, helper->app);
-       g_mutex_unlock (&priv->pending_apps_mutex);
        g_idle_add (emit_pending_apps_idle, g_object_ref (plugin_loader));
 }
 
 static void
-gs_plugin_loader_review_action_thread_cb (GTask *task,
-                                         gpointer object,
-                                         gpointer task_data,
-                                         GCancellable *cancellable)
+gs_plugin_loader_pending_apps_remove (GsPluginLoader *plugin_loader,
+                                     GsPluginLoaderHelper *helper)
 {
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+       GsAppList *list = gs_plugin_job_get_list (helper->plugin_job);
        GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       guint i;
+       g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->pending_apps_mutex);
 
-       /* run each plugin */
-       for (i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
+       g_assert (gs_app_list_length (list) > 0);
+       for (guint i = 0; i < gs_app_list_length (list); i++) {
+               GsApp *app = gs_app_list_index (list, i);
+               g_ptr_array_remove (priv->pending_apps, app);
+
+               /* check the app is not still in an action helper */
+               switch (gs_app_get_state (app)) {
+               case AS_APP_STATE_INSTALLING:
+               case AS_APP_STATE_REMOVING:
+                       g_warning ("application %s left in %s helper",
+                                  gs_app_get_unique_id (app),
+                                  as_app_state_to_string (gs_app_get_state (app)));
+                       gs_app_set_state (app, AS_APP_STATE_UNKNOWN);
+                       break;
+               default:
+                       break;
                }
-               gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
-       }
 
-       /* nothing ran */
-       if (!helper->anything_ran) {
-               g_set_error (&error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "no plugin could handle %s",
-                            helper->function_name);
-               g_task_return_error (task, error);
        }
-
-       /* add this to the app */
-       if (g_strcmp0 (helper->function_name, "gs_plugin_review_submit") == 0)
-               gs_app_add_review (helper->app, helper->review);
-
-       /* remove this from the app */
-       if (g_strcmp0 (helper->function_name, "gs_plugin_review_remove") == 0)
-               gs_app_remove_review (helper->app, helper->review);
-
-       g_task_return_boolean (task, TRUE);
+       g_idle_add (emit_pending_apps_idle, g_object_ref (plugin_loader));
 }
 
 static gboolean
@@ -3032,11 +1635,11 @@ load_install_queue (GsPluginLoader *plugin_loader, GError **error)
        /* refine */
        if (gs_app_list_length (list) > 0) {
                g_autoptr(GsPluginLoaderHelper) helper = NULL;
-               helper = gs_plugin_loader_helper_new (plugin_loader);
-               helper->action = GS_PLUGIN_ACTION_REFINE;
-               helper->function_name = "gs_plugin_refine";
-               helper->refine_flags = GS_PLUGIN_REFINE_FLAGS_DEFAULT;
-               helper->failure_flags = GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS;
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE, NULL);
+               helper = gs_plugin_loader_helper_new (plugin_loader, plugin_job);
+               gs_plugin_job_set_failure_flags (helper->plugin_job,
+                                                GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS);
                if (!gs_plugin_loader_run_refine (helper, list, NULL, error))
                        return FALSE;
        }
@@ -3140,205 +1743,6 @@ remove_app_from_install_queue (GsPluginLoader *plugin_loader, GsApp *app)
        return ret;
 }
 
-/**
- * gs_plugin_loader_app_action_async:
- *
- * This method calls all plugins that implement the gs_plugin_action()
- * function.
- **/
-void
-gs_plugin_loader_app_action_async (GsPluginLoader *plugin_loader,
-                                  GsApp *app,
-                                  GsPluginAction action,
-                                  GsPluginFailureFlags failure_flags,
-                                  GCancellable *cancellable,
-                                  GAsyncReadyCallback callback,
-                                  gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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));
-
-       /* handle with a fake list */
-       if (action == GS_PLUGIN_ACTION_UPDATE) {
-               g_autoptr(GsAppList) list = gs_app_list_new ();
-               gs_app_list_add (list, app);
-               gs_plugin_loader_update_async (plugin_loader, list,
-                                              failure_flags,
-                                              cancellable, callback,
-                                              user_data);
-               return;
-       }
-
-       if (action == GS_PLUGIN_ACTION_REMOVE) {
-               if (remove_app_from_install_queue (plugin_loader, app)) {
-                       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-                       g_task_return_boolean (task, TRUE);
-                       return;
-               }
-       }
-
-       if (action == GS_PLUGIN_ACTION_INSTALL &&
-           !gs_plugin_loader_get_network_available (plugin_loader)) {
-               add_app_to_install_queue (plugin_loader, app);
-               task = g_task_new (plugin_loader, cancellable, callback, user_data);
-               g_task_return_boolean (task, TRUE);
-               return;
-       }
-
-       /* save helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->app = g_object_ref (app);
-       helper->action = action;
-       helper->failure_flags = failure_flags;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_app_action_thread_cb);
-}
-
-void
-gs_plugin_loader_review_action_async (GsPluginLoader *plugin_loader,
-                                     GsApp *app,
-                                     AsReview *review,
-                                     GsPluginAction action,
-                                     GsPluginFailureFlags failure_flags,
-                                     GCancellable *cancellable,
-                                     GAsyncReadyCallback callback,
-                                     gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->app = g_object_ref (app);
-       helper->review = g_object_ref (review);
-       helper->action = action;
-       helper->failure_flags = failure_flags;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_review_action_thread_cb);
-}
-
-gboolean
-gs_plugin_loader_review_action_finish (GsPluginLoader *plugin_loader,
-                                      GAsyncResult *res,
-                                      GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
-       g_return_val_if_fail (G_IS_TASK (res), FALSE);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), FALSE);
-       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-       return g_task_propagate_boolean (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_auth_action_thread_cb (GTask *task,
-                                         gpointer object,
-                                         gpointer task_data,
-                                         GCancellable *cancellable)
-{
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-
-       /* run each plugin */
-       for (guint i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       }
-
-       g_task_return_boolean (task, TRUE);
-}
-
-void
-gs_plugin_loader_auth_action_async (GsPluginLoader *plugin_loader,
-                                   GsAuth *auth,
-                                   GsPluginAction action,
-                                   GsPluginFailureFlags failure_flags,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       g_autoptr(GTask) task = NULL;
-
-       g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
-       g_return_if_fail (GS_IS_AUTH (auth));
-       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
-
-       /* save helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->auth = g_object_ref (auth);
-       helper->action = action;
-       helper->failure_flags = failure_flags;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_auth_action_thread_cb);
-}
-
-gboolean
-gs_plugin_loader_auth_action_finish (GsPluginLoader *plugin_loader,
-                                    GAsyncResult *res,
-                                    GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
-       g_return_val_if_fail (G_IS_TASK (res), FALSE);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), FALSE);
-       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-       return g_task_propagate_boolean (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-/**
- * gs_plugin_loader_app_action_finish:
- *
- * Return value: success
- **/
-gboolean
-gs_plugin_loader_app_action_finish (GsPluginLoader *plugin_loader,
-                                   GAsyncResult *res,
-                                   GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
-       g_return_val_if_fail (G_IS_TASK (res), FALSE);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), FALSE);
-       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-       return g_task_propagate_boolean (G_TASK (res), error);
-}
-
 /******************************************************************************/
 
 gboolean
@@ -3539,7 +1943,7 @@ gs_plugin_loader_status_changed_cb (GsPlugin *plugin,
 }
 
 static gboolean
-gs_plugin_loader_updates_changed_delay_cb (gpointer user_data)
+gs_plugin_loader_job_actions_changed_delay_cb (gpointer user_data)
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (user_data);
        GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
@@ -3554,7 +1958,7 @@ gs_plugin_loader_updates_changed_delay_cb (gpointer user_data)
 }
 
 static void
-gs_plugin_loader_updates_changed_cb (GsPlugin *plugin,
+gs_plugin_loader_job_actions_changed_cb (GsPlugin *plugin,
                                     GsPluginLoader *plugin_loader)
 {
        GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
@@ -3562,7 +1966,7 @@ gs_plugin_loader_updates_changed_cb (GsPlugin *plugin,
                return;
        priv->updates_changed_id =
                g_timeout_add_seconds (GS_PLUGIN_LOADER_UPDATES_CHANGED_DELAY,
-                                      gs_plugin_loader_updates_changed_delay_cb,
+                                      gs_plugin_loader_job_actions_changed_delay_cb,
                                       g_object_ref (plugin_loader));
 }
 
@@ -3609,7 +2013,7 @@ gs_plugin_loader_open_plugin (GsPluginLoader *plugin_loader,
                return;
        }
        g_signal_connect (plugin, "updates-changed",
-                         G_CALLBACK (gs_plugin_loader_updates_changed_cb),
+                         G_CALLBACK (gs_plugin_loader_job_actions_changed_cb),
                          plugin_loader);
        g_signal_connect (plugin, "reload",
                          G_CALLBACK (gs_plugin_loader_reload_cb),
@@ -3763,13 +2167,16 @@ gs_plugin_loader_setup_again (GsPluginLoader *plugin_loader)
        for (guint j = 0; actions[j] != GS_PLUGIN_ACTION_UNKNOWN; j++) {
                for (guint i = 0; i < priv->plugins->len; i++) {
                        g_autoptr(GError) error_local = NULL;
-                       g_autoptr(GsPluginLoaderHelper) helper = gs_plugin_loader_helper_new (plugin_loader);
+                       g_autoptr(GsPluginLoaderHelper) helper = NULL;
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
                        if (!gs_plugin_get_enabled (plugin))
                                continue;
-                       helper->action = actions[j];
-                       helper->failure_flags = GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE;
-                       helper->function_name = gs_plugin_action_to_function_name (helper->action);
+
+                       plugin_job = gs_plugin_job_newv (actions[j],
+                                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
+                                                        NULL);
+                       helper = gs_plugin_loader_helper_new (plugin_loader, plugin_job);
                        if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
                                                          NULL, &error_local)) {
                                g_warning ("resetup of %s failed: %s",
@@ -3815,6 +2222,7 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
        guint j;
        g_autoptr(AsProfileTask) ptask = NULL;
        g_autoptr(GsPluginLoaderHelper) helper = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* use the default, but this requires a 'make install' */
        if (priv->locations->len == 0) {
@@ -3898,15 +2306,13 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
        }
 
        /* run the plugins */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->action = GS_PLUGIN_ACTION_INITIALIZE;
-       helper->failure_flags = failure_flags | GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       for (i = 0; i < priv->plugins->len; i++) {
-               plugin = g_ptr_array_index (priv->plugins, i);
-               gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                            cancellable, NULL);
-       }
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INITIALIZE, NULL);
+       helper = gs_plugin_loader_helper_new (plugin_loader, plugin_job);
+       gs_plugin_job_set_failure_flags (helper->plugin_job,
+                                        failure_flags |
+                                        GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE);
+       if (!gs_plugin_loader_run_results (helper, cancellable, error))
+               return FALSE;
 
        /* order by deps */
        do {
@@ -4049,8 +2455,8 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
        } while (changes);
 
        /* run setup */
-       helper->action = GS_PLUGIN_ACTION_SETUP;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
+       gs_plugin_job_set_action (helper->plugin_job, GS_PLUGIN_ACTION_SETUP);
+       helper->function_name = "gs_plugin_setup";
        for (i = 0; i < priv->plugins->len; i++) {
                g_autoptr(GError) error_local = NULL;
                plugin = g_ptr_array_index (priv->plugins, i);
@@ -4136,13 +2542,10 @@ gs_plugin_loader_dispose (GObject *object)
 
        if (priv->plugins != NULL) {
                g_autoptr(GsPluginLoaderHelper) helper = NULL;
-               helper = gs_plugin_loader_helper_new (plugin_loader);
-               helper->action = GS_PLUGIN_ACTION_DESTROY;
-               helper->function_name = gs_plugin_action_to_function_name (helper->action);
-               for (guint i = 0; i < priv->plugins->len; i++) {
-                       GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-                       gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL, NULL, NULL);
-               }
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_DESTROY, NULL);
+               helper = gs_plugin_loader_helper_new (plugin_loader, plugin_job);
+               gs_plugin_loader_run_results (helper, NULL, NULL);
                g_clear_pointer (&priv->plugins, g_ptr_array_unref);
        }
        if (priv->updates_changed_id != 0) {
@@ -4358,9 +2761,9 @@ gs_plugin_loader_app_installed_cb (GObject *source,
        g_autoptr(GError) error = NULL;
        g_autoptr(GsApp) app = GS_APP (user_data);
 
-       ret = gs_plugin_loader_app_action_finish (plugin_loader,
-                                                 res,
-                                                 &error);
+       ret = gs_plugin_loader_job_action_finish (plugin_loader,
+                                                  res,
+                                                  &error);
        if (!ret) {
                remove_app_from_install_queue (plugin_loader, app);
                g_warning ("failed to install %s: %s",
@@ -4415,13 +2818,15 @@ gs_plugin_loader_network_changed_cb (GNetworkMonitor *monitor,
                g_mutex_unlock (&priv->pending_apps_mutex);
                for (guint i = 0; i < gs_app_list_length (queue); i++) {
                        GsApp *app = gs_app_list_index (queue, i);
-                       gs_plugin_loader_app_action_async (plugin_loader,
-                                                          app,
-                                                          GS_PLUGIN_ACTION_INSTALL,
-                                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                          NULL,
-                                                          gs_plugin_loader_app_installed_cb,
-                                                          g_object_ref (app));
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                                        "app", app,
+                                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                        NULL);
+                       gs_plugin_loader_job_process_async (plugin_loader, plugin_job,
+                                                           NULL,
+                                                           gs_plugin_loader_app_installed_cb,
+                                                           g_object_ref (app));
                }
        }
 }
@@ -4448,100 +2853,6 @@ gs_plugin_loader_monitor_network (GsPluginLoader *plugin_loader)
 
 /******************************************************************************/
 
-static void
-gs_plugin_loader_refresh_thread_cb (GTask *task,
-                                   gpointer object,
-                                   gpointer task_data,
-                                   GCancellable *cancellable)
-{
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-
-       /* run each plugin */
-       for (guint i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       }
-
-       /* nothing ran */
-       if (!helper->anything_ran) {
-               g_set_error (&error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "no plugin could handle refresh");
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* success */
-       g_task_return_boolean (task, TRUE);
-}
-
-/**
- * gs_plugin_loader_refresh_async:
- * @cache_age, the age in seconds, or %G_MAXUINT for "any"
- *
- * This method calls all plugins that implement the gs_plugin_refresh()
- * function.
- **/
-void
-gs_plugin_loader_refresh_async (GsPluginLoader *plugin_loader,
-                               guint cache_age,
-                               GsPluginRefreshFlags refresh_flags,
-                               GsPluginFailureFlags failure_flags,
-                               GCancellable *cancellable,
-                               GAsyncReadyCallback callback,
-                               gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refresh_flags = refresh_flags;
-       helper->failure_flags = failure_flags;
-       helper->cache_age = cache_age;
-       helper->action = GS_PLUGIN_ACTION_REFRESH;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_refresh_thread_cb);
-}
-
-/**
- * gs_plugin_loader_refresh_finish:
- *
- * Return value: success
- **/
-gboolean
-gs_plugin_loader_refresh_finish (GsPluginLoader *plugin_loader,
-                                GAsyncResult *res,
-                                GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
-       g_return_val_if_fail (G_IS_TASK (res), FALSE);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), FALSE);
-       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
-       return g_task_propagate_boolean (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
 static AsIcon *
 _gs_app_get_icon_by_kind (GsApp *app, AsIconKind kind)
 {
@@ -4555,276 +2866,6 @@ _gs_app_get_icon_by_kind (GsApp *app, AsIconKind kind)
        return NULL;
 }
 
-static void
-gs_plugin_loader_file_to_app_thread_cb (GTask *task,
-                                       gpointer object,
-                                       gpointer task_data,
-                                       GCancellable *cancellable)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-
-       /* run each plugin */
-       for (guint i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       }
-
-       /* set the local file on any of the returned results */
-       for (guint j = 0; j < gs_app_list_length (helper->list); j++) {
-               GsApp *app = gs_app_list_index (helper->list, j);
-               if (gs_app_get_local_file (app) == NULL)
-                       gs_app_set_local_file (app, helper->file);
-       }
-
-       /* run refine() on each one */
-       if (!gs_plugin_loader_run_refine (helper, helper->list, cancellable, &error)) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* filter package list */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
-
-       /* check the apps have an icon set */
-       for (guint j = 0; j < gs_app_list_length (helper->list); j++) {
-               GsApp *app = gs_app_list_index (helper->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 */
-       helper->refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON;
-       if (!gs_plugin_loader_run_refine (helper, helper->list, cancellable, &error)) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* success */
-       if (gs_app_list_length (helper->list) == 0) {
-               g_task_return_new_error (task,
-                                        GS_PLUGIN_ERROR,
-                                        GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                        "no application was created for %s",
-                                        g_file_get_path (helper->file));
-               return;
-       }
-       if (gs_app_list_length (helper->list) > 1) {
-               g_task_return_new_error (task,
-                                        GS_PLUGIN_ERROR,
-                                        GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                        "more than one application was created for %s",
-                                        g_file_get_path (helper->file));
-               return;
-       }
-       g_task_return_pointer (task, g_object_ref (gs_app_list_index (helper->list, 0)), (GDestroyNotify) 
g_object_unref);
-}
-
-/**
- * gs_plugin_loader_file_to_app_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_file_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_file_to_app_async (GsPluginLoader *plugin_loader,
-                                   GFile *file,
-                                   GsPluginRefineFlags refine_flags,
-                                   GsPluginFailureFlags failure_flags,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->list = gs_app_list_new ();
-       helper->file = g_object_ref (file);
-       helper->action = GS_PLUGIN_ACTION_FILE_TO_APP;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_file_to_app_thread_cb);
-}
-
-/**
- * gs_plugin_loader_file_to_app_finish:
- *
- * Return value: (element-type GsApp) (transfer full): An application, or %NULL
- **/
-GsApp *
-gs_plugin_loader_file_to_app_finish (GsPluginLoader *plugin_loader,
-                                    GAsyncResult *res,
-                                    GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
-static void
-gs_plugin_loader_url_to_app_thread_cb (GTask *task,
-                                       gpointer object,
-                                       gpointer task_data,
-                                       GCancellable *cancellable)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
-       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       GError *error = NULL;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-
-       /* run each plugin */
-       for (guint i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, NULL,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       }
-
-       /* set the local file on any of the returned results */
-       for (guint j = 0; j < gs_app_list_length (helper->list); j++) {
-               GsApp *app = gs_app_list_index (helper->list, j);
-               if (gs_app_get_local_file (app) == NULL)
-                       gs_app_set_local_file (app, helper->file);
-       }
-
-       /* run refine() on each one */
-       if (!gs_plugin_loader_run_refine (helper, helper->list, cancellable, &error)) {
-               g_task_return_error (task, error);
-               return;
-       }
-
-       /* filter package list */
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_is_valid, helper);
-       gs_app_list_filter (helper->list, gs_plugin_loader_app_set_prio, plugin_loader);
-       gs_app_list_filter_duplicates (helper->list, GS_APP_LIST_FILTER_FLAG_KEY_ID);
-
-       /* success */
-       if (gs_app_list_length (helper->list) != 1) {
-               g_task_return_new_error (task,
-                                        GS_PLUGIN_ERROR,
-                                        GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                        "no application was created for %s",
-                                        helper->value);
-               return;
-       }
-       g_task_return_pointer (task, g_object_ref (gs_app_list_index (helper->list, 0)), (GDestroyNotify) 
g_object_unref);
-}
-
-/**
- * gs_plugin_loader_url_to_app_async:
- *
- * This method calls all plugins that implement the gs_plugin_add_url_to_app()
- * function. The plugins can either return #GsApp objects of kind
- * %AS_APP_KIND_DESKTOP for bonafide applications, or #GsApp's of kind
- * %AS_APP_KIND_GENERIC for packages that may or may not be applications.
- *
- * Once the list of updates is refined, some of the #GsApp's of kind
- * %AS_APP_KIND_GENERIC will have been promoted to a kind of %AS_APP_KIND_DESKTOP,
- * or if they are core applications.
- *
- * Files that are supported will have the GFile used to create them available
- * from the gs_app_get_local_file() method.
- **/
-void
-gs_plugin_loader_url_to_app_async (GsPluginLoader *plugin_loader,
-                                   const gchar *url,
-                                   GsPluginRefineFlags refine_flags,
-                                   GsPluginFailureFlags failure_flags,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data)
-{
-       GsPluginLoaderHelper *helper;
-       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 helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->refine_flags = refine_flags;
-       helper->failure_flags = failure_flags;
-       helper->list = gs_app_list_new ();
-       helper->value = g_strdup (url);
-       helper->action = GS_PLUGIN_ACTION_URL_TO_APP;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
-
-       /* run in a thread */
-       task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_url_to_app_thread_cb);
-}
-
-/**
- * gs_plugin_loader_url_to_app_finish:
- *
- * Return value: (element-type GsApp) (transfer full): An application, or %NULL
- **/
-GsApp *
-gs_plugin_loader_url_to_app_finish (GsPluginLoader *plugin_loader,
-                                    GAsyncResult *res,
-                                    GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), NULL);
-       g_return_val_if_fail (G_IS_TASK (res), NULL);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-
-       gs_utils_error_convert_gio (error);
-       return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-/******************************************************************************/
-
 static GPtrArray *
 get_updatable_apps (GPtrArray *apps)
 {
@@ -4883,52 +2924,37 @@ gs_proxy_update_helper_free (GsProxyUpdateHelper *proxy_helper)
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsProxyUpdateHelper, gs_proxy_update_helper_free)
 
-static void
-gs_plugin_loader_update_thread_cb (GTask *task,
-                                  gpointer object,
-                                  gpointer task_data,
-                                  GCancellable *cancellable)
+static gboolean
+gs_plugin_loader_generic_update (GsPluginLoader *plugin_loader,
+                                GsPluginLoaderHelper *helper,
+                                GCancellable *cancellable,
+                                GError **error)
 {
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
        GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
-       gboolean ret = TRUE;
-       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
-       GError *error = NULL;
-       guint i;
-
-       /* run each plugin */
-       for (i = 0; i < priv->plugins->len; i++) {
-               GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
-               if (!gs_plugin_loader_call_vfunc (helper, plugin, NULL, helper->list,
-                                                 cancellable, &error)) {
-                       g_task_return_error (task, error);
-                       return;
-               }
-       }
+       GsAppList *list;
 
        /* run each plugin, per-app version */
-       helper->function_name = "gs_plugin_update_app";
-       for (i = 0; i < priv->plugins->len; i++) {
+       list = gs_plugin_job_get_list (helper->plugin_job);
+       for (guint i = 0; i < priv->plugins->len; i++) {
                GsPluginActionFunc plugin_app_func = NULL;
-               guint j;
 
                GsPlugin *plugin = g_ptr_array_index (priv->plugins, i);
-               if (g_task_return_error_if_cancelled (task))
-                       return;
+               if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+                       gs_utils_error_convert_gio (error);
+                       return FALSE;
+               }
                plugin_app_func = gs_plugin_get_symbol (plugin, helper->function_name);
                if (plugin_app_func == NULL)
                        continue;
 
                /* for each app */
-               for (j = 0; j < gs_app_list_length (helper->list); j++) {
-                       GsApp *app_tmp = gs_app_list_index (helper->list, j);
+               for (guint j = 0; j < gs_app_list_length (list); j++) {
+                       GsApp *app_tmp = gs_app_list_index (list, j);
                        g_autoptr(GPtrArray) apps = NULL;
-                       gboolean is_proxy_update =
-                               gs_app_has_quirk (app_tmp, AS_APP_QUIRK_IS_PROXY);
+                       gboolean is_proxy_update;
 
                        /* operate on the parent app or the related apps */
+                       is_proxy_update = gs_app_has_quirk (app_tmp, AS_APP_QUIRK_IS_PROXY);
                        if (is_proxy_update) {
                                apps = get_updatable_apps (gs_app_get_related (app_tmp));
                                if (apps->len > 0) {
@@ -4942,17 +2968,19 @@ gs_plugin_loader_update_thread_cb (GTask *task,
                                g_ptr_array_add (apps, app_tmp);
                        }
                        for (guint k = 0; k < apps->len; k++) {
-                               g_autoptr(GError) error_local = NULL;
-                               g_autoptr(AsProfileTask) ptask = NULL;
                                GsApp *app = g_ptr_array_index (apps, k);
+                               gboolean ret;
+                               g_autoptr(AsProfileTask) ptask = NULL;
+                               g_autoptr(GError) error_local = NULL;
                                g_autoptr(GsProxyUpdateHelper) proxy_helper = NULL;
-                               g_set_object (&helper->app, app);
+
+                               gs_plugin_job_set_app (helper->plugin_job, app);
 
                                if (is_proxy_update) {
                                        proxy_helper = gs_proxy_update_helper_new (app_tmp,
-                                                                                   app,
-                                                                                   apps->len,
-                                                                                   k);
+                                                                                  app,
+                                                                                  apps->len,
+                                                                                  k);
                                        g_assert (proxy_helper != NULL);
                                }
 
@@ -4967,73 +2995,446 @@ gs_plugin_loader_update_thread_cb (GTask *task,
                                                       cancellable,
                                                       &error_local);
                                gs_plugin_loader_action_stop (plugin_loader, plugin);
-
                                if (!ret) {
                                        if (!gs_plugin_error_handle_failure (helper,
                                                                             plugin,
                                                                             error_local,
-                                                                            &error)) {
-                                               g_task_return_error (task, error);
-                                               return;
+                                                                            error)) {
+                                               return FALSE;
                                        }
                                }
                        }
                        if (is_proxy_update)
                                gs_app_set_state (app_tmp, AS_APP_STATE_INSTALLED);
                }
+               helper->anything_ran = TRUE;
                gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
        }
+       return TRUE;
+}
 
-       g_task_return_boolean (task, TRUE);
+static void
+gs_plugin_loader_process_thread_cb (GTask *task,
+                                   gpointer object,
+                                   gpointer task_data,
+                                   GCancellable *cancellable)
+{
+       GError *error = NULL;
+       GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) task_data;
+       GsAppList *list = gs_plugin_job_get_list (helper->plugin_job);
+       GsPluginAction action = gs_plugin_job_get_action (helper->plugin_job);
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       gboolean add_to_pending_array = FALSE;
+
+       /* these change the pending count on the installed panel */
+       switch (action) {
+       case GS_PLUGIN_ACTION_INSTALL:
+       case GS_PLUGIN_ACTION_REMOVE:
+               add_to_pending_array = TRUE;
+               break;
+       default:
+               break;
+       }
+
+       /* add to pending list */
+       if (add_to_pending_array)
+               gs_plugin_loader_pending_apps_add (plugin_loader, helper);
+
+       /* run each plugin */
+       if (action != GS_PLUGIN_ACTION_REFINE) {
+               if (!gs_plugin_loader_run_results (helper, cancellable, &error)) {
+                       if (add_to_pending_array) {
+                               gs_app_set_state_recover (gs_plugin_job_get_app (helper->plugin_job));
+                               gs_plugin_loader_pending_apps_remove (plugin_loader, helper);
+                       }
+                       g_task_return_error (task, error);
+                       return;
+               }
+       }
+
+       /* run per-app version */
+       if (action == GS_PLUGIN_ACTION_UPDATE) {
+               helper->function_name = "gs_plugin_update_app";
+               if (!gs_plugin_loader_generic_update (plugin_loader, helper,
+                                                     cancellable, &error)) {
+                       g_task_return_error (task, error);
+                       return;
+               }
+       }
+
+       /* remove from pending list */
+       if (add_to_pending_array)
+               gs_plugin_loader_pending_apps_remove (plugin_loader, helper);
+
+       /* append extra things when we want the list of pending updates */
+       if (action == GS_PLUGIN_ACTION_GET_UPDATES &&
+           !g_settings_get_boolean (priv->settings, "download-updates")) {
+               helper->function_name = "gs_plugin_add_updates_pending";
+               if (!gs_plugin_loader_run_results (helper, cancellable, &error)) {
+                       g_task_return_error (task, error);
+                       return;
+               }
+       }
+
+       /* nothing ran */
+       if (action != GS_PLUGIN_ACTION_REFINE) {
+               if (!helper->anything_ran) {
+                       g_set_error (&error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                    "no plugin could handle %s",
+                                    gs_plugin_action_to_string (action));
+                       g_task_return_error (task, error);
+                       return;
+               }
+       }
+
+       /* unstage addons */
+       if (add_to_pending_array) {
+               GPtrArray *addons;
+               addons = gs_app_get_addons (gs_plugin_job_get_app (helper->plugin_job));
+               for (guint i = 0; i < addons->len; i++) {
+                       GsApp *addon = g_ptr_array_index (addons, i);
+                       if (gs_app_get_to_be_installed (addon))
+                               gs_app_set_to_be_installed (addon, FALSE);
+               }
+       }
+
+       /* modify the local app */
+       switch (action) {
+       case GS_PLUGIN_ACTION_REVIEW_SUBMIT:
+               gs_app_add_review (gs_plugin_job_get_app (helper->plugin_job), gs_plugin_job_get_review 
(helper->plugin_job));
+               break;
+       case GS_PLUGIN_ACTION_REVIEW_REMOVE:
+               gs_app_remove_review (gs_plugin_job_get_app (helper->plugin_job), gs_plugin_job_get_review 
(helper->plugin_job));
+               break;
+       default:
+               break;
+       }
+
+       /* filter to reduce to a sane set */
+       gs_plugin_loader_job_sorted_truncation (helper);
+
+       /* set the local file on any of the returned results */
+       switch (action) {
+       case GS_PLUGIN_ACTION_FILE_TO_APP:
+               for (guint j = 0; j < gs_app_list_length (list); j++) {
+                       GsApp *app = gs_app_list_index (list, j);
+                       if (gs_app_get_local_file (app) == NULL)
+                               gs_app_set_local_file (app, gs_plugin_job_get_file (helper->plugin_job));
+               }
+       default:
+               break;
+       }
+
+       /* pick up new source id */
+       if (add_to_pending_array) {
+               gs_plugin_job_add_refine_flags (helper->plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN);
+       }
+
+       /* run refine() on each one if required */
+       if (gs_plugin_job_get_refine_flags (helper->plugin_job) != 0) {
+               if (!gs_plugin_loader_run_refine (helper, list, cancellable, &error)) {
+                       g_task_return_error (task, error);
+                       return;
+               }
+       }
+
+       /* convert any unavailable codecs */
+       switch (action) {
+       case GS_PLUGIN_ACTION_SEARCH:
+       case GS_PLUGIN_ACTION_SEARCH_FILES:
+       case GS_PLUGIN_ACTION_SEARCH_PROVIDES:
+               gs_plugin_loader_convert_unavailable (list, gs_plugin_job_get_search (helper->plugin_job));
+               break;
+       default:
+               break;
+       }
+
+       /* check the local files have an icon set */
+       switch (action) {
+       case GS_PLUGIN_ACTION_URL_TO_APP:
+       case GS_PLUGIN_ACTION_FILE_TO_APP:
+               for (guint j = 0; j < gs_app_list_length (list); j++) {
+                       GsApp *app = gs_app_list_index (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 */
+               gs_plugin_job_set_refine_flags (helper->plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON);
+               if (!gs_plugin_loader_run_refine (helper, list, cancellable, &error)) {
+                       g_task_return_error (task, error);
+                       return;
+               }
+               break;
+       default:
+               break;
+       }
+
+       /* filter package list */
+       switch (action) {
+       case GS_PLUGIN_ACTION_URL_TO_APP:
+               gs_app_list_filter (list, gs_plugin_loader_app_is_valid, helper);
+               break;
+       case GS_PLUGIN_ACTION_SEARCH:
+       case GS_PLUGIN_ACTION_SEARCH_FILES:
+       case GS_PLUGIN_ACTION_SEARCH_PROVIDES:
+               gs_app_list_filter (list, gs_plugin_loader_app_is_valid, helper);
+               gs_app_list_filter (list, gs_plugin_loader_filter_qt_for_gtk, NULL);
+               gs_app_list_filter (list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
+               break;
+       case GS_PLUGIN_ACTION_GET_CATEGORY_APPS:
+               gs_app_list_filter (list, gs_plugin_loader_app_is_non_compulsory, NULL);
+               gs_app_list_filter (list, gs_plugin_loader_app_is_valid, helper);
+               gs_app_list_filter (list, gs_plugin_loader_filter_qt_for_gtk, NULL);
+               gs_app_list_filter (list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
+               break;
+       case GS_PLUGIN_ACTION_GET_INSTALLED:
+               gs_app_list_filter (list, gs_plugin_loader_app_is_valid, helper);
+               gs_app_list_filter (list, gs_plugin_loader_app_is_valid_installed, helper);
+               break;
+       case GS_PLUGIN_ACTION_GET_FEATURED:
+               if (g_getenv ("GNOME_SOFTWARE_FEATURED") != NULL) {
+                       gs_app_list_filter (list, gs_plugin_loader_featured_debug, NULL);
+               } else {
+                       gs_app_list_filter (list, gs_plugin_loader_app_is_valid, helper);
+                       gs_app_list_filter (list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
+               }
+               break;
+       case GS_PLUGIN_ACTION_GET_UPDATES:
+               gs_app_list_filter (list, gs_plugin_loader_app_is_valid_updatable, helper);
+               break;
+       case GS_PLUGIN_ACTION_GET_RECENT:
+               gs_app_list_filter (list, gs_plugin_loader_app_is_non_compulsory, NULL);
+               gs_app_list_filter (list, gs_plugin_loader_app_is_valid, helper);
+               gs_app_list_filter (list, gs_plugin_loader_filter_qt_for_gtk, NULL);
+               gs_app_list_filter (list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
+               break;
+       case GS_PLUGIN_ACTION_GET_POPULAR:
+               gs_app_list_filter (list, gs_plugin_loader_app_is_valid, helper);
+               gs_app_list_filter (list, gs_plugin_loader_filter_qt_for_gtk, NULL);
+               gs_app_list_filter (list, gs_plugin_loader_get_app_is_compatible, plugin_loader);
+               break;
+       default:
+               break;
+       }
+
+       /* only allow one result */
+       if (action == GS_PLUGIN_ACTION_URL_TO_APP ||
+           action == GS_PLUGIN_ACTION_FILE_TO_APP) {
+               if (gs_app_list_length (list) == 0) {
+                       g_autofree gchar *str = gs_plugin_job_to_string (helper->plugin_job);
+                       g_task_return_new_error (task,
+                                                GS_PLUGIN_ERROR,
+                                                GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                                "no application was created for %s",
+                                                str);
+                       return;
+               }
+               if (gs_app_list_length (list) > 1) {
+                       g_autofree gchar *str = gs_plugin_job_to_string (helper->plugin_job);
+                       g_task_return_new_error (task,
+                                                GS_PLUGIN_ERROR,
+                                                GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                                "more than one application was created for %s",
+                                                str);
+                       return;
+               }
+       }
+
+       /* too many */
+       if (gs_app_list_length (list) > 500) {
+               g_task_return_new_error (task,
+                                        GS_PLUGIN_ERROR,
+                                        GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                        "too many results returned");
+               return;
+       }
+
+       /* filter duplicates with priority, taking into account the source name
+        * & version, so we combine available updates with the installed app */
+       gs_app_list_filter (list, gs_plugin_loader_app_set_prio, plugin_loader);
+       gs_app_list_filter_duplicates (list,
+                                      GS_APP_LIST_FILTER_FLAG_KEY_ID |
+                                      GS_APP_LIST_FILTER_FLAG_KEY_SOURCE |
+                                      GS_APP_LIST_FILTER_FLAG_KEY_VERSION);
+
+       /* sort these again as the refine may have added useful metadata */
+       gs_plugin_loader_job_sorted_truncation_again (helper);
+
+       /* success */
+       g_task_return_pointer (task, g_object_ref (list), (GDestroyNotify) g_object_unref);
 }
 
 /**
- * gs_plugin_loader_update_async:
+ * gs_plugin_loader_job_process_async:
  *
- * This method calls all plugins that implement the gs_plugin_update()
- * or gs_plugin_update_app() functions.
+ * This method calls all plugins.
  **/
 void
-gs_plugin_loader_update_async (GsPluginLoader *plugin_loader,
-                              GsAppList *apps,
-                              GsPluginFailureFlags failure_flags,
-                              GCancellable *cancellable,
-                              GAsyncReadyCallback callback,
-                              gpointer user_data)
+gs_plugin_loader_job_process_async (GsPluginLoader *plugin_loader,
+                                   GsPluginJob *plugin_job,
+                                   GCancellable *cancellable,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
 {
+       GsPluginAction action;
        GsPluginLoaderHelper *helper;
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
        g_autoptr(GTask) task = NULL;
 
        g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
+       g_return_if_fail (GS_IS_PLUGIN_JOB (plugin_job));
        g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
-       /* save helper */
-       helper = gs_plugin_loader_helper_new (plugin_loader);
-       helper->list = gs_app_list_copy (apps);
-       helper->action = GS_PLUGIN_ACTION_UPDATE;
-       helper->failure_flags = failure_flags;
-       helper->function_name = gs_plugin_action_to_function_name (helper->action);
-       gs_plugin_loader_helper_debug (helper);
+       /* deal with the install queue */
+       action = gs_plugin_job_get_action (plugin_job);
+       if (action == GS_PLUGIN_ACTION_REMOVE) {
+               if (remove_app_from_install_queue (plugin_loader, gs_plugin_job_get_app (plugin_job))) {
+                       GsAppList *list = gs_plugin_job_get_list (plugin_job);
+                       task = g_task_new (plugin_loader, cancellable, callback, user_data);
+                       g_task_return_pointer (task, g_object_ref (list), (GDestroyNotify) g_object_unref);
+                       return;
+               }
+       }
+       if (action == GS_PLUGIN_ACTION_INSTALL &&
+           !gs_plugin_loader_get_network_available (plugin_loader)) {
+               GsAppList *list = gs_plugin_job_get_list (plugin_job);
+               add_app_to_install_queue (plugin_loader, gs_plugin_job_get_app (plugin_job));
+               task = g_task_new (plugin_loader, cancellable, callback, user_data);
+               g_task_return_pointer (task, g_object_ref (list), (GDestroyNotify) g_object_unref);
+               return;
+       }
 
-       /* run in a thread */
+       /* hardcoded, so resolve a set list */
+       if (action == GS_PLUGIN_ACTION_GET_POPULAR) {
+               g_auto(GStrv) apps = NULL;
+               if (g_getenv ("GNOME_SOFTWARE_POPULAR") != NULL) {
+                       apps = g_strsplit (g_getenv ("GNOME_SOFTWARE_POPULAR"), ",", 0);
+               } else {
+                       apps = g_settings_get_strv (priv->settings, "popular-overrides");
+               }
+               if (apps != NULL && g_strv_length (apps) > 0) {
+                       GsAppList *list = gs_plugin_job_get_list (plugin_job);
+                       for (guint i = 0; apps[i] != NULL; i++) {
+                               g_autoptr(GsApp) app = gs_app_new (apps[i]);
+                               gs_app_add_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX);
+                               gs_app_list_add (list, app);
+                       }
+                       gs_plugin_job_set_action (plugin_job, GS_PLUGIN_ACTION_REFINE);
+               }
+       }
+
+       /* FIXME: the plugins should specify this, rather than hardcoding */
+       if (gs_plugin_job_has_refine_flags (plugin_job,
+                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS)) {
+               gs_plugin_job_add_refine_flags (plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON);
+       }
+       if (gs_plugin_job_has_refine_flags (plugin_job,
+                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_UI)) {
+               gs_plugin_job_add_refine_flags (plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN);
+       }
+       if (gs_plugin_job_has_refine_flags (plugin_job,
+                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME)) {
+               gs_plugin_job_add_refine_flags (plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN);
+       }
+       if (gs_plugin_job_has_refine_flags (plugin_job,
+                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE)) {
+               gs_plugin_job_add_refine_flags (plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME);
+       }
+
+       /* FIXME: this is probably a bug */
+       if (action == GS_PLUGIN_ACTION_GET_DISTRO_UPDATES ||
+           action == GS_PLUGIN_ACTION_GET_SOURCES) {
+               gs_plugin_job_add_refine_flags (plugin_job,
+                                               GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION);
+       }
+
+       /* check required args */
        task = g_task_new (plugin_loader, cancellable, callback, user_data);
-       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
-       g_task_run_in_thread (task, gs_plugin_loader_update_thread_cb);
-}
+       switch (action) {
+       case GS_PLUGIN_ACTION_SEARCH:
+       case GS_PLUGIN_ACTION_SEARCH_FILES:
+       case GS_PLUGIN_ACTION_SEARCH_PROVIDES:
+       case GS_PLUGIN_ACTION_URL_TO_APP:
+               if (gs_plugin_job_get_search (plugin_job) == NULL) {
+                       g_task_return_new_error (task,
+                                                GS_PLUGIN_ERROR,
+                                                GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                                "no valid search terms");
+                       return;
+               }
+               break;
+       case GS_PLUGIN_ACTION_REVIEW_SUBMIT:
+       case GS_PLUGIN_ACTION_REVIEW_UPVOTE:
+       case GS_PLUGIN_ACTION_REVIEW_DOWNVOTE:
+       case GS_PLUGIN_ACTION_REVIEW_REPORT:
+       case GS_PLUGIN_ACTION_REVIEW_REMOVE:
+       case GS_PLUGIN_ACTION_REVIEW_DISMISS:
+               if (gs_plugin_job_get_review (plugin_job) == NULL) {
+                       g_task_return_new_error (task,
+                                                GS_PLUGIN_ERROR,
+                                                GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                                "no valid review object");
+                       return;
+               }
+               break;
+       default:
+               break;
+       }
 
-gboolean
-gs_plugin_loader_update_finish (GsPluginLoader *plugin_loader,
-                               GAsyncResult *res,
-                               GError **error)
-{
-       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
-       g_return_val_if_fail (G_IS_TASK (res), FALSE);
-       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), FALSE);
-       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+       /* sorting fallbacks */
+       switch (action) {
+       case GS_PLUGIN_ACTION_SEARCH:
+               if (gs_plugin_job_get_sort_func (plugin_job) == NULL) {
+                       gs_plugin_job_set_sort_func (plugin_job,
+                                                    gs_plugin_loader_app_sort_match_value_cb);
+               }
+               break;
+       case GS_PLUGIN_ACTION_GET_RECENT:
+               if (gs_plugin_job_get_sort_func (plugin_job) == NULL) {
+                       gs_plugin_job_set_sort_func (plugin_job,
+                                                    gs_plugin_loader_app_sort_kind_cb);
+               }
+               break;
+       case GS_PLUGIN_ACTION_GET_CATEGORY_APPS:
+               if (gs_plugin_job_get_sort_func (plugin_job) == NULL) {
+                       gs_plugin_job_set_sort_func (plugin_job,
+                                                    gs_plugin_loader_app_sort_name_cb);
+               }
+               break;
+       default:
+               break;
+       }
+
+       /* save helper */
+       helper = gs_plugin_loader_helper_new (plugin_loader, plugin_job);
+       g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
+       gs_plugin_loader_job_debug (helper);
 
-       return g_task_propagate_boolean (G_TASK (res), error);
+       /* run in a thread */
+       g_task_run_in_thread (task, gs_plugin_loader_process_thread_cb);
 }
 
+/******************************************************************************/
+
 /**
  * gs_plugin_loader_get_plugin_supported:
  *
@@ -5092,8 +3493,6 @@ gs_plugin_loader_get_system_app (GsPluginLoader *plugin_loader)
        return gs_plugin_loader_app_create (plugin_loader, "*/*/*/*/system/*");
 }
 
-/******************************************************************************/
-
 AsProfile *
 gs_plugin_loader_get_profile (GsPluginLoader *plugin_loader)
 {
diff --git a/lib/gs-plugin-loader.h b/lib/gs-plugin-loader.h
index 25ae050..c4d9921 100644
--- a/lib/gs-plugin-loader.h
+++ b/lib/gs-plugin-loader.h
@@ -29,6 +29,7 @@
 #include "gs-category.h"
 #include "gs-plugin-event.h"
 #include "gs-plugin-private.h"
+#include "gs-plugin-job.h"
 
 G_BEGIN_DECLS
 
@@ -48,158 +49,23 @@ struct _GsPluginLoaderClass
 };
 
 GsPluginLoader *gs_plugin_loader_new                   (void);
-void            gs_plugin_loader_get_installed_async   (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_get_installed_finish  (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_get_updates_async     (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_get_updates_finish    (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_get_distro_upgrades_async (GsPluginLoader     *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_get_distro_upgrades_finish (GsPluginLoader    *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_get_unvoted_reviews_async (GsPluginLoader     *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_get_unvoted_reviews_finish (GsPluginLoader    *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_get_sources_async     (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_get_sources_finish    (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_get_popular_async     (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_get_popular_finish    (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_get_featured_async    (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_get_featured_finish   (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_get_categories_async  (GsPluginLoader *plugin_loader,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GPtrArray      *gs_plugin_loader_get_categories_finish (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_get_category_apps_async (GsPluginLoader       *plugin_loader,
-                                                        GsCategory     *category,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_get_category_apps_finish (GsPluginLoader      *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_get_recent_async      (GsPluginLoader *plugin_loader,
-                                                        guint64         age,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_get_recent_finish     (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_search_async          (GsPluginLoader *plugin_loader,
-                                                        const gchar    *value,
-                                                        guint           max_results,
-                                                        GsAppListSortFunc sort_func,
-                                                        gpointer        sort_func_data,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_search_finish         (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_search_files_async    (GsPluginLoader *plugin_loader,
-                                                        const gchar    *value,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_search_files_finish   (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_search_what_provides_async (GsPluginLoader    *plugin_loader,
-                                                        const gchar    *value,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsAppList      *gs_plugin_loader_search_what_provides_finish (GsPluginLoader   *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_file_to_app_async     (GsPluginLoader *plugin_loader,
-                                                        GFile          *file,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
+void            gs_plugin_loader_job_process_async     (GsPluginLoader *plugin_loader,
+                                                        GsPluginJob    *plugin_job,
                                                         GCancellable   *cancellable,
                                                         GAsyncReadyCallback callback,
                                                         gpointer        user_data);
-GsApp          *gs_plugin_loader_file_to_app_finish    (GsPluginLoader *plugin_loader,
+GsAppList      *gs_plugin_loader_job_process_finish    (GsPluginLoader *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
-void            gs_plugin_loader_url_to_app_async      (GsPluginLoader *plugin_loader,
-                                                        const gchar    *url,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-GsApp          *gs_plugin_loader_url_to_app_finish     (GsPluginLoader *plugin_loader,
+gboolean        gs_plugin_loader_job_action_finish     (GsPluginLoader *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
-void            gs_plugin_loader_update_async          (GsPluginLoader *plugin_loader,
-                                                        GsAppList      *apps,
-                                                        GsPluginFailureFlags failure_flags,
+void            gs_plugin_loader_job_get_categories_async (GsPluginLoader *plugin_loader,
+                                                        GsPluginJob    *plugin_job,
                                                         GCancellable   *cancellable,
                                                         GAsyncReadyCallback callback,
                                                         gpointer        user_data);
-gboolean        gs_plugin_loader_update_finish         (GsPluginLoader *plugin_loader,
+GPtrArray      *gs_plugin_loader_job_get_categories_finish (GsPluginLoader *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
 gboolean        gs_plugin_loader_setup                 (GsPluginLoader *plugin_loader,
@@ -218,57 +84,6 @@ GsAuth              *gs_plugin_loader_get_auth_by_id        (GsPluginLoader 
*plugin_loader,
 guint           gs_plugin_loader_get_scale             (GsPluginLoader *plugin_loader);
 void            gs_plugin_loader_set_scale             (GsPluginLoader *plugin_loader,
                                                         guint           scale);
-void            gs_plugin_loader_app_refine_async      (GsPluginLoader *plugin_loader,
-                                                        GsApp          *app,
-                                                        GsPluginRefineFlags refine_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-gboolean        gs_plugin_loader_app_refine_finish     (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_app_action_async      (GsPluginLoader *plugin_loader,
-                                                        GsApp          *app,
-                                                        GsPluginAction  a,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-gboolean        gs_plugin_loader_app_action_finish     (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-gboolean        gs_plugin_loader_review_action_finish  (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_review_action_async   (GsPluginLoader *plugin_loader,
-                                                        GsApp          *app,
-                                                        AsReview       *review,
-                                                        GsPluginAction  action,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-gboolean        gs_plugin_loader_auth_action_finish    (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_auth_action_async     (GsPluginLoader *plugin_loader,
-                                                        GsAuth         *auth,
-                                                        GsPluginAction  action,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
-gboolean        gs_plugin_loader_refresh_finish        (GsPluginLoader *plugin_loader,
-                                                        GAsyncResult   *res,
-                                                        GError         **error);
-void            gs_plugin_loader_refresh_async         (GsPluginLoader *plugin_loader,
-                                                        guint           cache_age,
-                                                        GsPluginRefreshFlags refresh_flags,
-                                                        GsPluginFailureFlags failure_flags,
-                                                        GCancellable   *cancellable,
-                                                        GAsyncReadyCallback callback,
-                                                        gpointer        user_data);
 GsAppList      *gs_plugin_loader_get_pending           (GsPluginLoader *plugin_loader);
 gboolean        gs_plugin_loader_get_allow_updates     (GsPluginLoader *plugin_loader);
 gboolean        gs_plugin_loader_get_network_available (GsPluginLoader *plugin_loader);
@@ -291,9 +106,9 @@ GsApp               *gs_plugin_loader_get_system_app        (GsPluginLoader 
*plugin_loader);
 
 /* only useful from the self tests */
 void            gs_plugin_loader_setup_again           (GsPluginLoader *plugin_loader);
-void            gs_plugin_loader_clear_caches  (GsPluginLoader *plugin_loader);
-GsPlugin       *gs_plugin_loader_find_plugin   (GsPluginLoader *plugin_loader,
-                                                const gchar    *plugin_name);
+void            gs_plugin_loader_clear_caches          (GsPluginLoader *plugin_loader);
+GsPlugin       *gs_plugin_loader_find_plugin           (GsPluginLoader *plugin_loader,
+                                                        const gchar    *plugin_name);
 
 
 G_END_DECLS
diff --git a/lib/gs-plugin-types.h b/lib/gs-plugin-types.h
index 7e9a650..ad0e8dd 100644
--- a/lib/gs-plugin-types.h
+++ b/lib/gs-plugin-types.h
@@ -120,7 +120,7 @@ typedef enum {
 /**
  * GsPluginRefineFlags:
  * @GS_PLUGIN_REFINE_FLAGS_DEFAULT:                    No explicit flags set
- * @GS_PLUGIN_REFINE_FLAGS_USE_HISTORY:                        Get the historical view
+ * @GS_PLUGIN_REFINE_FLAGS_USE_HISTORY:                        Get the historical view (unused)
  * @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
@@ -150,7 +150,7 @@ typedef enum {
  * The refine flags.
  **/
 #define GS_PLUGIN_REFINE_FLAGS_DEFAULT                 (0u)
-#define GS_PLUGIN_REFINE_FLAGS_USE_HISTORY             (1u << 0)
+#define GS_PLUGIN_REFINE_FLAGS_USE_HISTORY             (1u << 0) /* unused, TODO: perhaps ->STATE */
 #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)
@@ -164,7 +164,7 @@ typedef enum {
 #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_ALLOW_PACKAGES          (1u << 14) /* TODO: move to request */
 #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)
@@ -263,7 +263,6 @@ typedef enum {
  * @GS_PLUGIN_ACTION_AUTH_LOST_PASSWORD:       Authentication lost password action
  * @GS_PLUGIN_ACTION_URL_TO_APP:               Convert the file to an application
  * @GS_PLUGIN_ACTION_GET_RECENT:               Get the apps recently released
- * @GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL:   Get the list of historical updates
  * @GS_PLUGIN_ACTION_INITIALIZE:               Initialize the plugin
  * @GS_PLUGIN_ACTION_DESTROY:                  Destroy the plugin
  *
diff --git a/lib/meson.build b/lib/meson.build
index b7944e6..95a5852 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -63,6 +63,7 @@ libgnomesoftware = static_library(
     'gs-os-release.c',
     'gs-plugin.c',
     'gs-plugin-event.c',
+    'gs-plugin-job.c',
     'gs-plugin-loader.c',
     'gs-plugin-loader-sync.c',
     'gs-test.c',
diff --git a/plugins/core/gs-self-test.c b/plugins/core/gs-self-test.c
index f8b3a70..df46dc6 100644
--- a/plugins/core/gs-self-test.c
+++ b/plugins/core/gs-self-test.c
@@ -94,6 +94,7 @@ gs_plugins_core_search_repo_name_func (GsPluginLoader *plugin_loader)
        g_autoptr(GError) error = NULL;
        g_autoptr(GsApp) app_tmp = NULL;
        g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
        gs_plugin_loader_setup_again (plugin_loader);
@@ -103,13 +104,11 @@ gs_plugins_core_search_repo_name_func (GsPluginLoader *plugin_loader)
        gs_app_set_state (app_tmp, AS_APP_STATE_INSTALLED);
 
        /* get search result based on addon keyword */
-       list = gs_plugin_loader_search (plugin_loader,
-                                       "yellow", 0,
-                                       NULL, NULL,
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", "yellow",
+                                        NULL);
+       gs_plugin_job_set_refine_flags (plugin_job, GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list != NULL);
@@ -128,6 +127,7 @@ gs_plugins_core_os_release_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsApp) app = NULL;
        g_autoptr(GsApp) app2 = NULL;
        g_autoptr(GsApp) app3 = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(GError) error = NULL;
 
        /* drop all caches */
@@ -135,12 +135,12 @@ gs_plugins_core_os_release_func (GsPluginLoader *plugin_loader)
 
        /* refine system application */
        app = gs_plugin_loader_get_system_app (plugin_loader);
-       ret = gs_plugin_loader_app_refine (plugin_loader,
-                                          app,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "app", app,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
diff --git a/plugins/dpkg/gs-self-test.c b/plugins/dpkg/gs-self-test.c
index f4eef98..da1d2a5 100644
--- a/plugins/dpkg/gs-self-test.c
+++ b/plugins/dpkg/gs-self-test.c
@@ -29,6 +29,7 @@ static void
 gs_plugins_dpkg_func (GsPluginLoader *plugin_loader)
 {
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(GError) error = NULL;
        g_autofree gchar *fn = NULL;
        g_autoptr(GFile) file = NULL;
@@ -43,12 +44,10 @@ gs_plugins_dpkg_func (GsPluginLoader *plugin_loader)
        fn = gs_test_get_filename (TESTDATADIR, "chiron-1.1-1.deb");
        g_assert (fn != NULL);
        file = g_file_new_for_path (fn);
-       app = gs_plugin_loader_file_to_app (plugin_loader,
-                                           file,
-                                           GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL,
-                                           &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                        "file", file,
+                                        NULL);
+       app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (app != NULL);
diff --git a/plugins/dummy/gs-self-test.c b/plugins/dummy/gs-self-test.c
index cf2bb0b..4f48dac 100644
--- a/plugins/dummy/gs-self-test.c
+++ b/plugins/dummy/gs-self-test.c
@@ -41,28 +41,28 @@ gs_plugins_dummy_install_func (GsPluginLoader *plugin_loader)
 {
        gboolean ret;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(GError) error = NULL;
 
        /* install */
        app = gs_app_new ("chiron.desktop");
        gs_app_set_management_plugin (app, "dummy");
        gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
 
        /* remove */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -78,6 +78,7 @@ gs_plugins_dummy_error_func (GsPluginLoader *plugin_loader)
        g_autoptr(GError) error = NULL;
        g_autoptr(GPtrArray) events = NULL;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
        gs_plugin_loader_setup_again (plugin_loader);
@@ -86,12 +87,12 @@ gs_plugins_dummy_error_func (GsPluginLoader *plugin_loader)
        app = gs_app_new ("chiron.desktop");
        gs_app_set_management_plugin (app, "dummy");
        gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_UPDATE,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS |
-                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE,
+                                        "app", app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS |
+                                                         GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -125,17 +126,18 @@ gs_plugins_dummy_refine_func (GsPluginLoader *plugin_loader)
        gboolean ret;
        g_autoptr(GsApp) app = NULL;
        g_autoptr(GError) error = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* get the extra bits */
        app = gs_app_new ("chiron.desktop");
        gs_app_set_management_plugin (app, "dummy");
-       ret = gs_plugin_loader_app_refine (plugin_loader, app,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "app", app,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -152,15 +154,16 @@ gs_plugins_dummy_key_colors_func (GsPluginLoader *plugin_loader)
        gboolean ret;
        guint i;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(GError) error = NULL;
 
        /* get the extra bits */
        app = gs_app_new ("zeus.desktop");
-       ret = gs_plugin_loader_app_refine (plugin_loader, app,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "app", app,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -187,14 +190,14 @@ gs_plugins_dummy_updates_func (GsPluginLoader *plugin_loader)
        GsApp *app;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* get the updates list */
-       list = gs_plugin_loader_get_updates (plugin_loader,
-                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS,
-                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                            NULL,
-                                            &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list != NULL);
@@ -232,13 +235,11 @@ gs_plugins_dummy_distro_upgrades_func (GsPluginLoader *plugin_loader)
        gboolean ret;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* get the updates list */
-       list = gs_plugin_loader_get_distro_upgrades (plugin_loader,
-                                                    GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                                    GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                    NULL,
-                                                    &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_DISTRO_UPDATES, NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list != NULL);
@@ -254,24 +255,22 @@ gs_plugins_dummy_distro_upgrades_func (GsPluginLoader *plugin_loader)
        g_assert_cmpstr (gs_app_get_summary (app), ==, "Release specific tagline");
 
        /* download the update */
-       ret = gs_plugin_loader_app_action (plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UPDATABLE);
 
        /* trigger the update */
-       ret = gs_plugin_loader_app_action (plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_UPGRADE_TRIGGER,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPGRADE_TRIGGER,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -288,18 +287,18 @@ gs_plugins_dummy_installed_func (GsPluginLoader *plugin_loader)
        g_autofree gchar *menu_path = NULL;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* get installed packages */
-       list = gs_plugin_loader_get_installed (plugin_loader,
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE,
-                                              GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                              NULL,
-                                              &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_INSTALLED,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list != NULL);
@@ -352,15 +351,14 @@ gs_plugins_dummy_search_func (GsPluginLoader *plugin_loader)
        g_autofree gchar *menu_path = NULL;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* get search result based on addon keyword */
-       list = gs_plugin_loader_search (plugin_loader,
-                                       "spell", 0,
-                                       NULL, NULL,
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", "spell",
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list != NULL);
@@ -377,13 +375,13 @@ gs_plugins_dummy_url_to_app_func (GsPluginLoader *plugin_loader)
 {
        g_autoptr(GError) error = NULL;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
-       app = gs_plugin_loader_url_to_app (plugin_loader,
-                                          "dummy://chiron.desktop",
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_URL_TO_APP,
+                                        "search", "dummy://chiron.desktop",
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (app != NULL);
@@ -399,24 +397,20 @@ gs_plugins_dummy_plugin_cache_func (GsPluginLoader *plugin_loader)
        g_autoptr(GError) error = NULL;
        g_autoptr(GsAppList) list1 = NULL;
        g_autoptr(GsAppList) list2 = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* ensure we get the same results back from calling the methods twice */
-       list1 = gs_plugin_loader_get_distro_upgrades (plugin_loader,
-                                                     GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                                     GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                     NULL,
-                                                     &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_DISTRO_UPDATES, NULL);
+       list1 = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list1 != NULL);
        g_assert_cmpint (gs_app_list_length (list1), ==, 1);
        app1 = gs_app_list_index (list1, 0);
 
-       list2 = gs_plugin_loader_get_distro_upgrades (plugin_loader,
-                                                     GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                                     GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                     NULL,
-                                                     &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_DISTRO_UPDATES, NULL);
+       list2 = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list2 != NULL);
@@ -435,8 +429,10 @@ gs_plugins_dummy_authentication_func (GsPluginLoader *plugin_loader)
        gboolean ret;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsAppList) list = NULL;
        g_autoptr(AsReview) review = NULL;
        g_autoptr(AsReview) review2 = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* check initial state */
        auth = gs_plugin_loader_get_auth_by_id (plugin_loader, "dummy");
@@ -444,57 +440,69 @@ gs_plugins_dummy_authentication_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_auth_get_flags (auth), ==, 0);
 
        /* do an action that returns a URL */
-       ret = gs_plugin_loader_auth_action (plugin_loader, auth,
-                                           GS_PLUGIN_ACTION_AUTH_REGISTER,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL, &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_AUTH_REGISTER,
+                                        "auth", auth,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE |
+                                                         GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_INVALID);
-       g_assert (!ret);
+       g_assert (list == NULL);
        g_clear_error (&error);
        g_assert (!gs_auth_has_flag (auth, GS_AUTH_FLAG_VALID));
 
        /* do an action that requires a login */
        app = gs_app_new (NULL);
        review = as_review_new ();
-       ret = gs_plugin_loader_review_action (plugin_loader, app, review,
-                                             GS_PLUGIN_ACTION_REVIEW_REMOVE,
-                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                             NULL, &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REVIEW_REMOVE,
+                                        "app", app,
+                                        "review", review,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE |
+                                                         GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_REQUIRED);
        g_assert (!ret);
        g_clear_error (&error);
 
        /* pretend to auth with no credentials */
-       ret = gs_plugin_loader_auth_action (plugin_loader, auth,
-                                           GS_PLUGIN_ACTION_AUTH_LOGIN,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL, &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_AUTH_LOGIN,
+                                        "auth", auth,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE |
+                                                         GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_INVALID);
-       g_assert (!ret);
+       g_assert (list == NULL);
        g_clear_error (&error);
        g_assert (!gs_auth_has_flag (auth, GS_AUTH_FLAG_VALID));
 
        /* auth again with correct credentials */
        gs_auth_set_username (auth, "dummy");
        gs_auth_set_password (auth, "dummy");
-       ret = gs_plugin_loader_auth_action (plugin_loader, auth,
-                                           GS_PLUGIN_ACTION_AUTH_LOGIN,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL, &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_AUTH_LOGIN,
+                                                "auth", auth,
+                                                NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
-       g_assert (ret);
+       g_assert (list != NULL);
        g_assert (gs_auth_has_flag (auth, GS_AUTH_FLAG_VALID));
 
        /* do the action that requires a login */
        review2 = as_review_new ();
-       ret = gs_plugin_loader_review_action (plugin_loader, app, review2,
-                                             GS_PLUGIN_ACTION_REVIEW_REMOVE,
-                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                             NULL, &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REVIEW_REMOVE,
+                                        "app", app,
+                                        "review", review2,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -508,13 +516,13 @@ gs_plugins_dummy_wildcard_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsAppList) list2 = NULL;
        const gchar *popular_override = "chiron.desktop,zeus.desktop";
        g_auto(GStrv) apps = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* use the plugin's add_popular function */
-       list1 = gs_plugin_loader_get_popular (plugin_loader,
-                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                            NULL,
-                                            &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_POPULAR,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       list1 = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list1 != NULL);
@@ -522,11 +530,9 @@ gs_plugins_dummy_wildcard_func (GsPluginLoader *plugin_loader)
 
        /* override the popular list (do not use the add_popular function) */
        g_setenv ("GNOME_SOFTWARE_POPULAR", popular_override, TRUE);
-       list2 = gs_plugin_loader_get_popular (plugin_loader,
-                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                            NULL,
-                                            &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_POPULAR, NULL);
+       list2 = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list2 != NULL);
diff --git a/plugins/epiphany/gs-self-test.c b/plugins/epiphany/gs-self-test.c
index 68c5e77..1f2fbbc 100644
--- a/plugins/epiphany/gs-self-test.c
+++ b/plugins/epiphany/gs-self-test.c
@@ -31,6 +31,7 @@ gs_plugins_epiphany_func (GsPluginLoader *plugin_loader)
        gboolean ret;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* no epiphany, abort */
        if (!gs_plugin_loader_get_enabled (plugin_loader, "epiphany"))
@@ -39,11 +40,11 @@ gs_plugins_epiphany_func (GsPluginLoader *plugin_loader)
        /* a webapp with a local icon */
        app = gs_app_new ("arachne.desktop");
        gs_app_set_kind (app, AS_APP_KIND_WEB_APP);
-       ret = gs_plugin_loader_app_refine (plugin_loader, app,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "app", app,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
diff --git a/plugins/flatpak/gs-self-test.c b/plugins/flatpak/gs-self-test.c
index b2b03f7..8746fe2 100644
--- a/plugins/flatpak/gs-self-test.c
+++ b/plugins/flatpak/gs-self-test.c
@@ -41,6 +41,7 @@ gs_plugins_flatpak_repo_func (GsPluginLoader *plugin_loader)
        g_autoptr(GKeyFile) kf = NULL;
        g_autoptr(GsApp) app2 = NULL;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(GString) str = g_string_new (NULL);
 
        /* no flatpak, abort */
@@ -69,13 +70,12 @@ gs_plugins_flatpak_repo_func (GsPluginLoader *plugin_loader)
 
        /* load local file */
        file = g_file_new_for_path (fn);
-       app = gs_plugin_loader_file_to_app (plugin_loader,
-                                           file,
-                                           GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                           GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE |
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL,
-                                           &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                        "file", file,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE |
+                                                         GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+                                        NULL);
+       app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (app != NULL);
@@ -93,11 +93,11 @@ gs_plugins_flatpak_repo_func (GsPluginLoader *plugin_loader)
        g_assert (gs_app_get_pixbuf (app) != NULL);
 
        /* now install the remote */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -121,23 +121,22 @@ gs_plugins_flatpak_repo_func (GsPluginLoader *plugin_loader)
        g_assert_cmpstr (remote_url, ==, testdir_repourl);
 
        /* try again, check state is correct */
-       app2 = gs_plugin_loader_file_to_app (plugin_loader,
-                                            file,
-                                            GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                            GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                            NULL,
-                                            &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                        "file", file,
+                                        NULL);
+       app2 = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (app2 != NULL);
        g_assert_cmpint (gs_app_get_state (app2), ==, AS_APP_STATE_INSTALLED);
 
        /* remove it */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -168,6 +167,7 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsApp) app_source = NULL;
        g_autoptr(GsAppList) list = NULL;
        g_autoptr(GsAppList) sources = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
        gs_plugin_loader_setup_again (plugin_loader);
@@ -208,11 +208,10 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        gs_app_set_management_plugin (app_source, "flatpak");
        gs_app_set_state (app_source, AS_APP_STATE_AVAILABLE);
        gs_app_set_metadata (app_source, "flatpak::url", testdir_repourl);
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app_source,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -227,11 +226,9 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        g_assert_cmpstr (kf_remote_url, !=, NULL);
 
        /* check the source now exists */
-       sources = gs_plugin_loader_get_sources (plugin_loader,
-                                               GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                               GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                               NULL,
-                                               &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES, NULL);
+       sources = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (sources != NULL);
        g_assert_cmpint (gs_app_list_length (sources), ==, 1);
@@ -240,26 +237,25 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_SOURCE);
 
        /* refresh the appstream metadata */
-       ret = gs_plugin_loader_refresh (plugin_loader,
-                                       G_MAXUINT,
-                                       GS_PLUGIN_REFRESH_FLAGS_METADATA,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                        "age", G_MAXUINT,
+                                        "refresh-flags", GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
 
        /* find available application */
-       list = gs_plugin_loader_search (plugin_loader,
-                                       "Bingo", 0,
-                                       NULL, NULL,
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS |
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", "Bingo",
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (list != NULL);
 
@@ -282,11 +278,11 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_update_urgency (app), ==, AS_URGENCY_KIND_UNKNOWN);
 
        /* install, also installing runtime */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -333,11 +329,11 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        g_assert (g_file_test (runtime_fn, G_FILE_TEST_IS_REGULAR));
 
        /* remove the application */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
@@ -345,11 +341,11 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        g_assert (!g_file_test (desktop_fn, G_FILE_TEST_IS_REGULAR));
 
        /* install again, to check whether the progress gets initialized */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
 
        /* progress should be set to zero right before installing */
        g_assert_cmpint (gs_app_get_progress (app), ==, 0);
@@ -362,11 +358,11 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_progress (app), ==, 0);
 
        /* remove the application */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
@@ -374,12 +370,13 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        g_assert (!g_file_test (desktop_fn, G_FILE_TEST_IS_REGULAR));
 
        /* remove the remote (fail, as the runtime is still installed) */
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
-                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app_source,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
+                                                         GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED);
        g_assert (!ret);
        g_clear_error (&error);
@@ -389,21 +386,21 @@ gs_plugins_flatpak_app_with_runtime_func (GsPluginLoader *plugin_loader)
        runtime = gs_app_get_runtime (app);
        g_assert (runtime != NULL);
        g_assert_cmpstr (gs_app_get_unique_id (runtime), ==, 
"user/flatpak/test/runtime/org.test.Runtime/master");
-       ret = gs_plugin_loader_app_action (plugin_loader, runtime,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", runtime,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
 
        /* remove the remote */
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app_source,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -421,6 +418,7 @@ gs_plugins_flatpak_app_missing_runtime_func (GsPluginLoader *plugin_loader)
        g_autoptr(GError) error = NULL;
        g_autoptr(GsApp) app_source = NULL;
        g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* drop all caches */
        gs_plugin_loader_setup_again (plugin_loader);
@@ -447,34 +445,32 @@ gs_plugins_flatpak_app_missing_runtime_func (GsPluginLoader *plugin_loader)
        gs_app_set_management_plugin (app_source, "flatpak");
        gs_app_set_state (app_source, AS_APP_STATE_AVAILABLE);
        gs_app_set_metadata (app_source, "flatpak::url", testdir_repourl);
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app_source,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (app_source), ==, AS_APP_STATE_INSTALLED);
 
        /* refresh the appstream metadata */
-       ret = gs_plugin_loader_refresh (plugin_loader,
-                                       G_MAXUINT,
-                                       GS_PLUGIN_REFRESH_FLAGS_METADATA,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                        "age", G_MAXUINT,
+                                        "refresh-flags", GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
 
        /* find available application */
-       list = gs_plugin_loader_search (plugin_loader,
-                                       "Bingo", 0,
-                                       NULL, NULL,
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", "Bingo",
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list != NULL);
@@ -486,12 +482,13 @@ gs_plugins_flatpak_app_missing_runtime_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
 
        /* install, also installing runtime */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
-                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
+                                                         GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED);
        g_assert (!ret);
        g_clear_error (&error);
@@ -499,11 +496,11 @@ gs_plugins_flatpak_app_missing_runtime_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_progress (app), ==, 0);
 
        /* remove the remote */
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app_source,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -545,7 +542,7 @@ update_app_action_finish_sync (GObject *source, GAsyncResult *res, gpointer user
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        gboolean ret;
        g_autoptr(GError) error = NULL;
-       ret = gs_plugin_loader_app_action_finish (plugin_loader, res, &error);
+       ret = gs_plugin_loader_job_action_finish (plugin_loader, res, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -570,6 +567,7 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsApp) app = NULL;
        g_autoptr(GsAppList) sources2 = NULL;
        g_autoptr(GsAppList) sources = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(GString) str2 = g_string_new (NULL);
        g_autoptr(GString) str = g_string_new (NULL);
 
@@ -613,14 +611,12 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
 
        /* convert it to a GsApp */
        file = g_file_new_for_path (fn_ref);
-       app = gs_plugin_loader_file_to_app (plugin_loader,
-                                           file,
-                                           GS_PLUGIN_REFINE_FLAGS_DEFAULT |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL,
-                                           &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                        "file", file,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME,
+                                        NULL);
+       app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (app != NULL);
@@ -637,22 +633,20 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_state (runtime), ==, AS_APP_STATE_UNKNOWN);
 
        /* check the number of sources */
-       sources = gs_plugin_loader_get_sources (plugin_loader,
-                                               GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                               GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                               NULL,
-                                               &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES, NULL);
+       sources = gs_plugin_loader_job_process (plugin_loader, plugin_job,
+                                               NULL, &error);
        g_assert_no_error (error);
        g_assert (sources != NULL);
        g_assert_cmpint (gs_app_list_length (sources), ==, 0);
 
        /* install, which will install the runtime from the new remote */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
-                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -660,32 +654,30 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_state (runtime), ==, AS_APP_STATE_INSTALLED);
 
        /* check the number of sources */
-       sources2 = gs_plugin_loader_get_sources (plugin_loader,
-                                               GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                               GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                               NULL,
-                                               &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES, NULL);
+       sources2 = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (sources2 != NULL);
        g_assert_cmpint (gs_app_list_length (sources2), ==, 1);
 
        /* remove the app */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UNKNOWN);
 
        /* remove the runtime */
-       ret = gs_plugin_loader_app_action (plugin_loader, runtime,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", runtime,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -695,11 +687,11 @@ gs_plugins_flatpak_runtime_repo_func (GsPluginLoader *plugin_loader)
        app_source = gs_app_list_index (sources2, 0);
        g_assert (app_source != NULL);
        g_assert_cmpstr (gs_app_get_unique_id (app_source), ==, "user/*/*/source/test/*");
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app_source,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -725,6 +717,7 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsAppList) search1 = NULL;
        g_autoptr(GsAppList) search2 = NULL;
        g_autoptr(GsAppList) sources = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(GString) str = g_string_new (NULL);
 
        /* drop all caches */
@@ -744,33 +737,31 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        gs_app_set_management_plugin (app_source, "flatpak");
        gs_app_set_state (app_source, AS_APP_STATE_AVAILABLE);
        gs_app_set_metadata (app_source, "flatpak::url", testdir_repourl);
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app_source,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (app_source), ==, AS_APP_STATE_INSTALLED);
 
        /* refresh the appstream metadata */
-       ret = gs_plugin_loader_refresh (plugin_loader,
-                                       0,
-                                       GS_PLUGIN_REFRESH_FLAGS_METADATA,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                        "age", 0,
+                                        "refresh-flags", GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
 
        /* find available application */
-       list = gs_plugin_loader_search (plugin_loader,
-                                       "runtime", 0,
-                                       NULL, NULL,
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", "runtime",
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (list != NULL);
 
@@ -782,12 +773,11 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_state (runtime), ==, AS_APP_STATE_AVAILABLE);
 
        /* install the runtime ahead of time */
-       ret = gs_plugin_loader_app_action (plugin_loader, runtime,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
-                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", runtime,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (runtime), ==, AS_APP_STATE_INSTALLED);
@@ -812,14 +802,13 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
 
        /* convert it to a GsApp */
        file = g_file_new_for_path (fn);
-       app = gs_plugin_loader_file_to_app (plugin_loader,
-                                           file,
-                                           GS_PLUGIN_REFINE_FLAGS_DEFAULT |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL,
-                                           &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                        "file", file,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME,
+                                        NULL);
+       app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (app != NULL);
        g_assert_cmpint (gs_app_get_kind (app), ==, AS_APP_KIND_DESKTOP);
@@ -840,12 +829,11 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_state (runtime), ==, AS_APP_STATE_INSTALLED);
 
        /* install */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
-                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
@@ -854,13 +842,12 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        g_assert_cmpstr (gs_app_get_update_details (app), ==, NULL);
 
        /* search for the application */
-       search1 = gs_plugin_loader_search (plugin_loader,
-                                          "chiron", 0,
-                                          NULL, NULL,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", "chiron",
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       search1 = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (search1 != NULL);
@@ -869,50 +856,47 @@ gs_plugins_flatpak_ref_func (GsPluginLoader *plugin_loader)
        g_assert_cmpstr (gs_app_get_id (app_tmp), ==, "org.test.Chiron.desktop");
 
        /* remove app */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
 
        /* remove runtime */
-       ret = gs_plugin_loader_app_action (plugin_loader, runtime,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", runtime,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
 
        /* remove source */
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app_source,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
 
        /* there should be no sources now */
-       sources = gs_plugin_loader_get_sources (plugin_loader,
-                                               GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                               GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                               NULL,
-                                               &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES, NULL);
+       sources = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (sources != NULL);
        g_assert_cmpint (gs_app_list_length (sources), ==, 0);
 
        /* there should be no matches now */
-       search2 = gs_plugin_loader_search (plugin_loader,
-                                          "chiron", 0,
-                                          NULL, NULL,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", "chiron",
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       search2 = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (search2 != NULL);
@@ -947,6 +931,7 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsApp) app_source = NULL;
        g_autoptr(GsAppList) list = NULL;
        g_autoptr(GsAppList) list_updates = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(GMainLoop) loop = g_main_loop_new (NULL, FALSE);
 
        /* drop all caches */
@@ -979,35 +964,33 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        gs_app_set_management_plugin (app_source, "flatpak");
        gs_app_set_state (app_source, AS_APP_STATE_AVAILABLE);
        gs_app_set_metadata (app_source, "flatpak::url", "file:///var/tmp/self-test/repo");
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app_source,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
        g_assert_cmpint (gs_app_get_state (app_source), ==, AS_APP_STATE_INSTALLED);
 
        /* refresh the appstream metadata */
-       ret = gs_plugin_loader_refresh (plugin_loader,
-                                       G_MAXUINT,
-                                       GS_PLUGIN_REFRESH_FLAGS_METADATA,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                        "age", G_MAXUINT,
+                                        "refresh-flags", GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
 
        /* find available application */
-       list = gs_plugin_loader_search (plugin_loader,
-                                       "Bingo", 0,
-                                       NULL, NULL,
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", "Bingo",
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list != NULL);
@@ -1019,12 +1002,11 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
 
        /* install, also installing runtime */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
-                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
@@ -1039,23 +1021,23 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        g_assert (symlink (repodir2_fn, "/var/tmp/self-test/repo") == 0);
 
        /* refresh the appstream metadata */
-       ret = gs_plugin_loader_refresh (plugin_loader,
-                                       0, /* force now */
-                                       GS_PLUGIN_REFRESH_FLAGS_METADATA |
-                                       GS_PLUGIN_REFRESH_FLAGS_PAYLOAD,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                        "age", 0, /* force now */
+                                        "refresh-flags", GS_PLUGIN_REFRESH_FLAGS_METADATA |
+                                                         GS_PLUGIN_REFRESH_FLAGS_PAYLOAD,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
 
        /* get the updates list */
-       list_updates = gs_plugin_loader_get_updates (plugin_loader,
-                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS,
-                                                    GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                    NULL,
-                                                    &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS,
+                                        NULL);
+       list_updates = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list_updates != NULL);
@@ -1093,13 +1075,16 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
                                  &progress_cnt);
 
        /* use a mainloop so we get the events in the default context */
-       gs_plugin_loader_app_action_async (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_UPDATE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
-                                          GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
-                                          NULL,
-                                          update_app_action_finish_sync,
-                                          loop);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE,
+                                        "app", app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
+                                                         GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
+                                        NULL);
+       gs_plugin_loader_job_process_async (plugin_loader, plugin_job,
+                                           NULL,
+                                           update_app_action_finish_sync,
+                                           loop);
        g_main_loop_run (loop);
        gs_test_flush_main_context ();
        g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_INSTALLED);
@@ -1119,11 +1104,11 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        g_signal_handler_disconnect (app, notify_progress_id);
 
        /* remove the app */
-       ret = gs_plugin_loader_app_action (plugin_loader, app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
 
@@ -1131,21 +1116,21 @@ gs_plugins_flatpak_app_update_func (GsPluginLoader *plugin_loader)
        runtime = gs_app_get_runtime (app);
        g_assert (runtime != NULL);
        g_assert_cmpstr (gs_app_get_unique_id (runtime), ==, 
"user/flatpak/test/runtime/org.test.Runtime/master");
-       ret = gs_plugin_loader_app_action (plugin_loader, runtime,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", runtime,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
 
        /* remove the remote */
-       ret = gs_plugin_loader_app_action (plugin_loader, app_source,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       g_object_unref (plugin_job);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app_source,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
diff --git a/plugins/fwupd/gs-self-test.c b/plugins/fwupd/gs-self-test.c
index 0405671..bb9318a 100644
--- a/plugins/fwupd/gs-self-test.c
+++ b/plugins/fwupd/gs-self-test.c
@@ -32,6 +32,7 @@ gs_plugins_fwupd_func (GsPluginLoader *plugin_loader)
        g_autoptr(GError) error = NULL;
        g_autoptr(GFile) file = NULL;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* no fwupd, abort */
        if (!gs_plugin_loader_get_enabled (plugin_loader, "fwupd")) {
@@ -43,12 +44,10 @@ gs_plugins_fwupd_func (GsPluginLoader *plugin_loader)
        fn = gs_test_get_filename (TESTDATADIR, "chiron-0.2.cab");
        g_assert (fn != NULL);
        file = g_file_new_for_path (fn);
-       app = gs_plugin_loader_file_to_app (plugin_loader,
-                                           file,
-                                           GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL,
-                                           &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                        "file", file,
+                                        NULL);
+       app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (app != NULL);
diff --git a/plugins/modalias/gs-self-test.c b/plugins/modalias/gs-self-test.c
index 1b70317..81358f7 100644
--- a/plugins/modalias/gs-self-test.c
+++ b/plugins/modalias/gs-self-test.c
@@ -32,15 +32,14 @@ gs_plugins_modalias_func (GsPluginLoader *plugin_loader)
        g_autofree gchar *menu_path = NULL;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* get search result based on addon keyword */
-       list = gs_plugin_loader_search (plugin_loader,
-                                       "colorhug2", 0,
-                                       NULL, NULL,
-                                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", "colorhug2",
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list != NULL);
diff --git a/plugins/packagekit/gs-self-test.c b/plugins/packagekit/gs-self-test.c
index fcc080d..d32065c 100644
--- a/plugins/packagekit/gs-self-test.c
+++ b/plugins/packagekit/gs-self-test.c
@@ -213,6 +213,7 @@ gs_plugins_packagekit_local_func (GsPluginLoader *plugin_loader)
        g_autoptr(GError) error = NULL;
        g_autofree gchar *fn = NULL;
        g_autoptr(GFile) file = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* no packagekit, abort */
        if (!gs_plugin_loader_get_enabled (plugin_loader, "packagekit-local")) {
@@ -224,12 +225,10 @@ gs_plugins_packagekit_local_func (GsPluginLoader *plugin_loader)
        fn = gs_test_get_filename (TESTDATADIR, "chiron-1.1-1.fc24.x86_64.rpm");
        g_assert (fn != NULL);
        file = g_file_new_for_path (fn);
-       app = gs_plugin_loader_file_to_app (plugin_loader,
-                                           file,
-                                           GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                           NULL,
-                                           &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                        "file", file,
+                                        NULL);
+       app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED)) {
                g_test_skip ("rpm files not supported");
diff --git a/plugins/repos/gs-self-test.c b/plugins/repos/gs-self-test.c
index 9265c2c..e0bbd9d 100644
--- a/plugins/repos/gs-self-test.c
+++ b/plugins/repos/gs-self-test.c
@@ -31,15 +31,16 @@ gs_plugins_repos_func (GsPluginLoader *plugin_loader)
        gboolean ret;
        g_autoptr(GsApp) app = NULL;
        g_autoptr(GError) error = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* get the extra bits */
        app = gs_app_new ("testrepos.desktop");
        gs_app_set_origin (app, "utopia");
-       ret = gs_plugin_loader_app_refine (plugin_loader, app,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          NULL,
-                                          &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "app", app,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (ret);
diff --git a/plugins/shell-extensions/gs-self-test.c b/plugins/shell-extensions/gs-self-test.c
index 0bb98e5..1d92a21 100644
--- a/plugins/shell-extensions/gs-self-test.c
+++ b/plugins/shell-extensions/gs-self-test.c
@@ -33,6 +33,7 @@ gs_plugins_shell_extensions_installed_func (GsPluginLoader *plugin_loader)
        GsApp *app;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsAppList) list = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* no shell-extensions, abort */
        if (!gs_plugin_loader_get_enabled (plugin_loader, "shell-extensions")) {
@@ -41,10 +42,8 @@ gs_plugins_shell_extensions_installed_func (GsPluginLoader *plugin_loader)
        }
 
        /* get installed packages */
-       list = gs_plugin_loader_get_installed (plugin_loader,
-                                              GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                              GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                              NULL, &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_INSTALLED, NULL);
+       list = gs_plugin_loader_job_process (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
        g_assert (list != NULL);
@@ -80,6 +79,7 @@ gs_plugins_shell_extensions_remote_func (GsPluginLoader *plugin_loader)
        gboolean ret;
        g_autoptr(GError) error = NULL;
        g_autoptr(GFile) file = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(AsStore) store = NULL;
 
        /* ensure files are removed */
@@ -87,12 +87,11 @@ gs_plugins_shell_extensions_remote_func (GsPluginLoader *plugin_loader)
 
        /* refresh the metadata */
        g_setenv ("GS_SELF_TEST_SHELL_EXTENSIONS_XML_FN", xml_fn, TRUE);
-       ret = gs_plugin_loader_refresh (plugin_loader,
-                                       G_MAXUINT,
-                                       GS_PLUGIN_REFRESH_FLAGS_METADATA,
-                                       GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                       NULL,
-                                       &error);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                        "age", G_MAXUINT,
+                                        "refresh-flags", GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                        NULL);
+       ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        g_assert_no_error (error);
        g_assert (ret);
 
diff --git a/src/gs-application.c b/src/gs-application.c
index 1a0d885..264443e 100644
--- a/src/gs-application.c
+++ b/src/gs-application.c
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
  * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
- * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  *
  * Licensed under the GNU General Public License Version 2
  *
@@ -344,7 +344,7 @@ cancel_trigger_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data
 {
        GsApplication *app = GS_APPLICATION (user_data);
        g_autoptr(GError) error = NULL;
-       if (!gs_plugin_loader_app_action_finish (app->plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (app->plugin_loader, res, &error)) {
                g_warning ("failed to cancel trigger: %s", error->message);
                return;
        }
@@ -356,6 +356,7 @@ reboot_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data)
        GsApplication *app = GS_APPLICATION (user_data);
        g_autoptr(GError) error = NULL;
        g_autoptr(GVariant) retval = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* get result */
        retval = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
@@ -368,13 +369,11 @@ reboot_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data)
        }
 
        /* cancel trigger */
-       gs_plugin_loader_app_action_async (app->plugin_loader,
-                                          NULL, /* everything! */
-                                          GS_PLUGIN_ACTION_UPDATE_CANCEL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          app->cancellable,
-                                          cancel_trigger_failed_cb,
-                                          app);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE_CANCEL, NULL);
+       gs_plugin_loader_job_process_async (app->plugin_loader, plugin_job,
+                                           app->cancellable,
+                                           cancel_trigger_failed_cb,
+                                           app);
 }
 
 static void
@@ -384,7 +383,7 @@ offline_update_cb (GsPluginLoader *plugin_loader,
 {
        g_autoptr(GDBusConnection) bus = NULL;
        g_autoptr(GError) error = NULL;
-       if (!gs_plugin_loader_update_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("Failed to trigger offline update: %s", error->message);
                return;
        }
@@ -453,13 +452,13 @@ reboot_and_install (GSimpleAction *action,
                    gpointer       data)
 {
        GsApplication *app = GS_APPLICATION (data);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        gs_application_initialize_plugins (app);
-       gs_plugin_loader_update_async (app->plugin_loader,
-                                      NULL,
-                                      GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                      app->cancellable,
-                                      (GAsyncReadyCallback) offline_update_cb,
-                                      app);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE, NULL);
+       gs_plugin_loader_job_process_async (app->plugin_loader, plugin_job,
+                                           app->cancellable,
+                                           (GAsyncReadyCallback) offline_update_cb,
+                                           app);
 }
 
 static void
diff --git a/src/gs-auth-dialog.c b/src/gs-auth-dialog.c
index 2aacecd..08a6cb1 100644
--- a/src/gs-auth-dialog.c
+++ b/src/gs-auth-dialog.c
@@ -116,7 +116,7 @@ gs_auth_dialog_authenticate_cb (GObject *source,
        gtk_widget_set_visible (dialog->box_error, FALSE);
 
        /* we failed */
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                const gchar *url;
 
                if (g_error_matches (error,
@@ -167,20 +167,26 @@ gs_auth_dialog_authenticate_cb (GObject *source,
 static void
 gs_auth_dialog_continue_cb (GtkWidget *widget, GsAuthDialog *dialog)
 {
-       GsPluginAction action = GS_PLUGIN_ACTION_AUTH_LOGIN;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        gtk_widget_set_sensitive (dialog->box_dialog, FALSE);
        gtk_widget_set_sensitive (dialog->button_continue, FALSE);
 
        /* alternate actions */
-       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_lost_pwd)))
-               action = GS_PLUGIN_ACTION_AUTH_LOST_PASSWORD;
-       else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_register)))
-               action = GS_PLUGIN_ACTION_AUTH_REGISTER;
-       gs_plugin_loader_auth_action_async (dialog->plugin_loader,
-                                           dialog->auth,
-                                           action,
-                                           GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
+       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_lost_pwd))) {
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_AUTH_LOST_PASSWORD,
+                                                "auth", dialog->auth,
+                                                NULL);
+       } else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_register))) {
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_AUTH_REGISTER,
+                                                "auth", dialog->auth,
+                                                NULL);
+       } else {
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_AUTH_LOGIN,
+                                                "auth", dialog->auth,
+                                                NULL);
+       }
+       gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
                                            dialog->cancellable,
                                            gs_auth_dialog_authenticate_cb,
                                            dialog);
diff --git a/src/gs-category-page.c b/src/gs-category-page.c
index 6f41c78..9a3e7b8 100644
--- a/src/gs-category-page.c
+++ b/src/gs-category-page.c
@@ -86,9 +86,9 @@ gs_category_page_get_apps_cb (GObject *source_object,
        /* show an empty space for no results */
        gs_container_remove_all (GTK_CONTAINER (self->category_detail_box));
 
-       list = gs_plugin_loader_get_category_apps_finish (plugin_loader,
-                                                         res,
-                                                         &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader,
+                                                   res,
+                                                   &error);
        if (list == NULL) {
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        g_warning ("failed to get apps for category apps: %s", error->message);
@@ -114,6 +114,7 @@ gs_category_page_reload (GsPage *page)
        GsCategoryPage *self = GS_CATEGORY_PAGE (page);
        GtkWidget *tile;
        guint i, count;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        if (self->subcategory == NULL)
                return;
@@ -144,15 +145,18 @@ gs_category_page_reload (GsPage *page)
                gtk_widget_set_can_focus (gtk_widget_get_parent (tile), FALSE);
        }
 
-       gs_plugin_loader_get_category_apps_async (self->plugin_loader,
-                                                 self->subcategory,
-                                                 GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                                 GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                                 GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING,
-                                                 GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                 self->cancellable,
-                                                 gs_category_page_get_apps_cb,
-                                                 self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_CATEGORY_APPS,
+                                        "category", self->subcategory,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader,
+                                           plugin_job,
+                                           self->cancellable,
+                                           gs_category_page_get_apps_cb,
+                                           self);
 }
 
 static void
diff --git a/src/gs-details-page.c b/src/gs-details-page.c
index ee6055c..6879105 100644
--- a/src/gs-details-page.c
+++ b/src/gs-details-page.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -1096,6 +1096,8 @@ gs_details_page_authenticate_cb (GtkDialog *dialog,
                                  GtkResponseType response_type,
                                  GsDetailsPageReviewHelper *helper)
 {
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+
        /* unmap the dialog */
        gtk_widget_destroy (GTK_WIDGET (dialog));
 
@@ -1103,15 +1105,16 @@ gs_details_page_authenticate_cb (GtkDialog *dialog,
                gs_details_page_review_helper_free (helper);
                return;
        }
-       gs_plugin_loader_review_action_async (helper->self->plugin_loader,
-                                             helper->app,
-                                             helper->review,
-                                             helper->action,
-                                             GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS |
-                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                             helper->self->cancellable,
-                                             gs_details_page_app_set_review_cb,
-                                             helper);
+       plugin_job = gs_plugin_job_newv (helper->action,
+                                        "app", helper->app,
+                                        "review", helper->review,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
+                                                         GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (helper->self->plugin_loader, plugin_job,
+                                           helper->self->cancellable,
+                                           gs_details_page_app_set_review_cb,
+                                           helper);
 }
 
 static void
@@ -1123,7 +1126,7 @@ gs_details_page_app_set_review_cb (GObject *source,
        g_autoptr(GsDetailsPageReviewHelper) helper = (GsDetailsPageReviewHelper *) user_data;
        g_autoptr(GError) error = NULL;
 
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                /* try to authenticate then retry */
                if (g_error_matches (error,
                                     GS_PLUGIN_ERROR,
@@ -1157,19 +1160,22 @@ gs_details_page_review_button_clicked_cb (GsReviewRow *row,
                                           GsDetailsPage *self)
 {
        GsDetailsPageReviewHelper *helper = g_new0 (GsDetailsPageReviewHelper, 1);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+
        helper->self = g_object_ref (self);
        helper->app = g_object_ref (self->app);
        helper->review = g_object_ref (gs_review_row_get_review (row));
        helper->action = action;
-       gs_plugin_loader_review_action_async (self->plugin_loader,
-                                             helper->app,
-                                             helper->review,
-                                             helper->action,
-                                             GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS |
-                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                             self->cancellable,
-                                             gs_details_page_app_set_review_cb,
-                                             helper);
+       plugin_job = gs_plugin_job_newv (helper->action,
+                                        "app", helper->app,
+                                        "review", helper->review,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
+                                                         GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           gs_details_page_app_set_review_cb,
+                                           helper);
 }
 
 static void
@@ -1311,7 +1317,7 @@ gs_details_page_app_refine2_cb (GObject *source,
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        GsDetailsPage *self = GS_DETAILS_PAGE (user_data);
        g_autoptr(GError) error = NULL;
-       if (!gs_plugin_loader_app_refine_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("failed to refine %s: %s",
                           gs_app_get_id (self->app),
                           error->message);
@@ -1326,14 +1332,18 @@ gs_details_page_app_refine2_cb (GObject *source,
 static void
 gs_details_page_app_refine2 (GsDetailsPage *self)
 {
-       gs_plugin_loader_app_refine_async (self->plugin_loader, self->app,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                          self->cancellable,
-                                          gs_details_page_app_refine2_cb,
-                                          self);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "app", self->app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           gs_details_page_app_refine2_cb,
+                                           self);
 }
 
 static void
@@ -1409,7 +1419,7 @@ gs_details_page_app_refine_cb (GObject *source,
        g_autoptr(GError) error = NULL;
        g_autofree gchar *app_dump = NULL;
 
-       ret = gs_plugin_loader_app_refine_finish (plugin_loader,
+       ret = gs_plugin_loader_job_action_finish (plugin_loader,
                                                  res,
                                                  &error);
        if (!ret) {
@@ -1501,15 +1511,18 @@ gs_details_page_file_to_app_cb (GObject *source,
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        GsDetailsPage *self = GS_DETAILS_PAGE (user_data);
-       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsAppList) list = NULL;
        g_autoptr(GError) error = NULL;
 
-       app = gs_plugin_loader_file_to_app_finish (plugin_loader,
-                                                  res,
-                                                  &error);
-       if (app == NULL)
+       list = gs_plugin_loader_job_process_finish (plugin_loader,
+                                                   res,
+                                                   &error);
+       if (list == NULL) {
                g_warning ("failed to convert file to GsApp: %s", error->message);
-       set_app (self, app);
+       } else {
+               GsApp *app = gs_app_list_index (list, 0);
+               set_app (self, app);
+       }
 }
 
 static void
@@ -1519,37 +1532,43 @@ gs_details_page_url_to_app_cb (GObject *source,
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        GsDetailsPage *self = GS_DETAILS_PAGE (user_data);
-       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsAppList) list = NULL;
        g_autoptr(GError) error = NULL;
 
-       app = gs_plugin_loader_url_to_app_finish (plugin_loader,
-                                                 res,
-                                                 &error);
-       if (app == NULL)
+       list = gs_plugin_loader_job_process_finish (plugin_loader,
+                                                   res,
+                                                   &error);
+       if (list == NULL) {
                g_warning ("failed to convert URL to GsApp: %s", error->message);
-       set_app (self, app);
+       } else {
+               GsApp *app = gs_app_list_index (list, 0);
+               set_app (self, app);
+       }
 }
 
 void
 gs_details_page_set_local_file (GsDetailsPage *self, GFile *file)
 {
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        gs_details_page_set_state (self, GS_DETAILS_PAGE_STATE_LOADING);
-       gs_plugin_loader_file_to_app_async (self->plugin_loader,
-                                           file,
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS,
-                                           GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                        "file", file,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
                                            self->cancellable,
                                            gs_details_page_file_to_app_cb,
                                            self);
@@ -1558,50 +1577,57 @@ gs_details_page_set_local_file (GsDetailsPage *self, GFile *file)
 void
 gs_details_page_set_url (GsDetailsPage *self, const gchar *url)
 {
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        gs_details_page_set_state (self, GS_DETAILS_PAGE_STATE_LOADING);
-       gs_plugin_loader_url_to_app_async (self->plugin_loader,
-                                          url,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                          self->cancellable,
-                                          gs_details_page_url_to_app_cb,
-                                          self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_URL_TO_APP,
+                                        "search", url,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           gs_details_page_url_to_app_cb,
+                                           self);
 }
 
 static void
 gs_details_page_load (GsDetailsPage *self)
 {
-       gs_plugin_loader_app_refine_async (self->plugin_loader, self->app,
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME |
-                                          GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                          self->cancellable,
-                                          gs_details_page_app_refine_cb,
-                                          self);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "app", self->app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           gs_details_page_app_refine_cb,
+                                           self);
 }
 
 static void
@@ -1788,6 +1814,7 @@ gs_details_page_review_response_cb (GtkDialog *dialog,
        g_autofree gchar *text = NULL;
        g_autoptr(GDateTime) now = NULL;
        g_autoptr(AsReview) review = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        GsDetailsPageReviewHelper *helper;
        GsReviewDialog *rdialog = GS_REVIEW_DIALOG (dialog);
 
@@ -1812,15 +1839,16 @@ gs_details_page_review_response_cb (GtkDialog *dialog,
        helper->app = g_object_ref (self->app);
        helper->review = g_object_ref (review);
        helper->action = GS_PLUGIN_ACTION_REVIEW_SUBMIT;
-       gs_plugin_loader_review_action_async (self->plugin_loader,
-                                             helper->app,
-                                             helper->review,
-                                             helper->action,
-                                             GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS |
-                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                             self->cancellable,
-                                             gs_details_page_app_set_review_cb,
-                                             helper);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REVIEW_SUBMIT,
+                                        "app", helper->app,
+                                        "review", helper->review,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
+                                                         GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           gs_details_page_app_set_review_cb,
+                                           helper);
 
        /* unmap the dialog */
        gtk_widget_destroy (GTK_WIDGET (dialog));
diff --git a/src/gs-extras-page.c b/src/gs-extras-page.c
index 8ab22ed..e81c5fc 100644
--- a/src/gs-extras-page.c
+++ b/src/gs-extras-page.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2014 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  *
  * Licensed under the GNU General Public License Version 2
  *
@@ -524,7 +524,7 @@ search_files_cb (GObject *source_object,
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
        g_autoptr(GError) error = NULL;
 
-       list = gs_plugin_loader_search_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                g_autofree gchar *str = NULL;
                if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
@@ -568,12 +568,13 @@ file_to_app_cb (GObject *source_object,
 {
        SearchData *search_data = (SearchData *) user_data;
        GsExtrasPage *self = search_data->self;
-       GsApp *app;
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
        g_autoptr(GError) error = NULL;
+       g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsAppList) list = NULL;
 
-       app = gs_plugin_loader_file_to_app_finish (plugin_loader, res, &error);
-       if (app == NULL) {
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
+       if (list == NULL) {
                if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
                        g_debug ("extras: search what provides cancelled");
                        return;
@@ -592,6 +593,8 @@ file_to_app_cb (GObject *source_object,
                        gs_extras_page_set_state (self, GS_EXTRAS_PAGE_STATE_FAILED);
                        return;
                }
+       } else {
+               app = g_object_ref (gs_app_list_index (list, 0));
        }
 
        g_debug ("%s\n\n", gs_app_to_string (app));
@@ -602,8 +605,6 @@ file_to_app_cb (GObject *source_object,
        /* have all searches finished? */
        if (self->pending_search_cnt == 0)
                show_search_results (self);
-
-       g_object_unref (app);
 }
 
 static void
@@ -618,7 +619,7 @@ get_search_what_provides_cb (GObject *source_object,
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source_object);
        g_autoptr(GError) error = NULL;
 
-       list = gs_plugin_loader_search_what_provides_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                g_autofree gchar *str = NULL;
                if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
@@ -687,46 +688,56 @@ gs_extras_page_load (GsExtrasPage *self, GPtrArray *array_search_data)
 
                search_data = g_ptr_array_index (self->array_search_data, i);
                if (search_data->search_filename != NULL) {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH_FILES,
+                                                        "search", search_data->search_filename,
+                                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES,
+                                                        NULL);
                        g_debug ("searching filename: '%s'", search_data->search_filename);
-                       gs_plugin_loader_search_files_async (self->plugin_loader,
-                                                            search_data->search_filename,
-                                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                                            GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
-                                                            GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES,
-                                                            GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                            self->search_cancellable,
-                                                            search_files_cb,
-                                                            search_data);
+                       gs_plugin_loader_job_process_async (self->plugin_loader,
+                                                           plugin_job,
+                                                           self->search_cancellable,
+                                                           search_files_cb,
+                                                           search_data);
                } else if (search_data->package_filename != NULL) {
                        g_autoptr (GFile) file = NULL;
-                       g_debug ("resolving filename to app: '%s'", search_data->package_filename);
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        file = g_file_new_for_path (search_data->package_filename);
-                       gs_plugin_loader_file_to_app_async (self->plugin_loader,
-                                                           file,
-                                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
-                                                           GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES,
-                                                           GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                           self->search_cancellable,
-                                                           file_to_app_cb,
-                                                           search_data);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP,
+                                                        "file", file,
+                                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES,
+                                                        NULL);
+                       g_debug ("resolving filename to app: '%s'", search_data->package_filename);
+                       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                                           self->search_cancellable,
+                                                           file_to_app_cb,
+                                                           search_data);
                } else {
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
                        g_debug ("searching what provides: '%s'", search_data->search);
-                       gs_plugin_loader_search_what_provides_async (self->plugin_loader,
-                                                                    search_data->search,
-                                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                                                    
GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
-                                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
-                                                                    
GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
-                                                                    
GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
-                                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
-                                                                    GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES,
-                                                                    GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                                    self->search_cancellable,
-                                                                    get_search_what_provides_cb,
-                                                                    search_data);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH_PROVIDES,
+                                                        "search", search_data->search_filename,
+                                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING |
+                                                                        
GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES,
+                                                        NULL);
+                       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                                           self->search_cancellable,
+                                                           get_search_what_provides_cb,
+                                                           search_data);
                }
                self->pending_search_cnt++;
        }
diff --git a/src/gs-installed-page.c b/src/gs-installed-page.c
index 1747f66..1c54be7 100644
--- a/src/gs-installed-page.c
+++ b/src/gs-installed-page.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -231,9 +231,9 @@ gs_installed_page_get_installed_cb (GObject *source_object,
        self->waiting = FALSE;
        self->cache_valid = TRUE;
 
-       list = gs_plugin_loader_get_installed_finish (plugin_loader,
-                                                     res,
-                                                     &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader,
+                                                   res,
+                                                   &error);
        if (list == NULL) {
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        g_warning ("failed to get installed apps: %s", error->message);
@@ -254,6 +254,7 @@ static void
 gs_installed_page_load (GsInstalledPage *self)
 {
        GsPluginRefineFlags flags;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        if (self->waiting)
                return;
@@ -276,13 +277,16 @@ gs_installed_page_load (GsInstalledPage *self)
        if (should_show_installed_size (self))
                flags |= GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE;
 
-       /* get popular apps */
-       gs_plugin_loader_get_installed_async (self->plugin_loader,
-                                             flags,
-                                             GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                             self->cancellable,
-                                             gs_installed_page_get_installed_cb,
-                                             self);
+       /* get installed apps */
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_INSTALLED,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", flags,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader,
+                                           plugin_job,
+                                           self->cancellable,
+                                           gs_installed_page_get_installed_cb,
+                                           self);
        gs_start_spinner (GTK_SPINNER (self->spinner_install));
        gtk_stack_set_visible_child_name (GTK_STACK (self->stack_install), "spinner");
 }
diff --git a/src/gs-loading-page.c b/src/gs-loading-page.c
index c5e7edf..5b6cd55 100644
--- a/src/gs-loading-page.c
+++ b/src/gs-loading-page.c
@@ -86,7 +86,7 @@ gs_loading_page_refresh_cb (GObject *source_object, GAsyncResult *res, gpointer
        g_signal_handlers_disconnect_by_data (plugin_loader, self);
 
        /* not sure how to handle this */
-       if (!gs_plugin_loader_refresh_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("failed to load metadata: %s", error->message);
                return;
        }
@@ -99,12 +99,16 @@ static void
 gs_loading_page_load (GsLoadingPage *self)
 {
        GsLoadingPagePrivate *priv = gs_loading_page_get_instance_private (self);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* ensure that at least some metadata of any age is present, and also
         * spin up the plugins enough as to prime caches */
-       gs_plugin_loader_refresh_async (priv->plugin_loader, G_MAXUINT,
-                                       GS_PLUGIN_REFRESH_FLAGS_METADATA,
-                                       GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                        "age", G_MAXUINT,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refresh-flags", GS_PLUGIN_REFRESH_FLAGS_METADATA,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
                                        priv->cancellable,
                                        gs_loading_page_refresh_cb,
                                        self);
diff --git a/src/gs-moderate-page.c b/src/gs-moderate-page.c
index e99dda5..43cc3a4 100644
--- a/src/gs-moderate-page.c
+++ b/src/gs-moderate-page.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -57,7 +57,7 @@ gs_moderate_page_app_set_review_cb (GObject *source,
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        g_autoptr(GError) error = NULL;
 
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("failed to set review: %s", error->message);
                return;
        }
@@ -69,15 +69,17 @@ gs_moderate_page_review_clicked_cb (GsReviewRow *row,
                                     GsModeratePage *self)
 {
        GsApp *app = g_object_get_data (G_OBJECT (row), "GsApp");
-       gs_plugin_loader_review_action_async (self->plugin_loader,
-                                             app,
-                                             gs_review_row_get_review (row),
-                                             action,
-                                             GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
-                                             GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                             self->cancellable,
-                                             gs_moderate_page_app_set_review_cb,
-                                             self);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+       plugin_job = gs_plugin_job_newv (action,
+                                        "app", app,
+                                        "review", gs_review_row_get_review (row),
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY |
+                                                         GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           gs_moderate_page_app_set_review_cb,
+                                           self);
        gtk_widget_set_visible (GTK_WIDGET (row), FALSE);
 }
 
@@ -148,7 +150,7 @@ gs_moderate_page_get_unvoted_reviews_cb (GObject *source_object,
        gs_stop_spinner (GTK_SPINNER (self->spinner_install));
        gtk_stack_set_visible_child_name (GTK_STACK (self->stack_install), "view");
 
-       list = gs_plugin_loader_get_unvoted_reviews_finish (plugin_loader,
+       list = gs_plugin_loader_job_process_finish (plugin_loader,
                                                            res,
                                                            &error);
        if (list == NULL) {
@@ -176,22 +178,26 @@ gs_moderate_page_get_unvoted_reviews_cb (GObject *source_object,
 static void
 gs_moderate_page_load (GsModeratePage *self)
 {
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+
        /* remove old entries */
        gs_container_remove_all (GTK_CONTAINER (self->list_box_install));
 
        /* get unvoted reviews as apps */
-       gs_plugin_loader_get_unvoted_reviews_async (self->plugin_loader,
-                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
-                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
-                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
-                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS,
-                                                   GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                   self->cancellable,
-                                                   gs_moderate_page_get_unvoted_reviews_cb,
-                                                   self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UNVOTED_REVIEWS,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           gs_moderate_page_get_unvoted_reviews_cb,
+                                           self);
        gs_start_spinner (GTK_SPINNER (self->spinner_install));
        gtk_stack_set_visible_child_name (GTK_STACK (self->stack_install), "spinner");
 }
diff --git a/src/gs-overview-page.c b/src/gs-overview-page.c
index c6566c0..1c4665a 100644
--- a/src/gs-overview-page.c
+++ b/src/gs-overview-page.c
@@ -166,7 +166,7 @@ gs_overview_page_get_popular_cb (GObject *source_object,
        g_autoptr(GsAppList) list = NULL;
 
        /* get popular apps */
-       list = gs_plugin_loader_get_popular_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        gtk_widget_set_visible (priv->box_popular, list != NULL);
        gtk_widget_set_visible (priv->popular_heading, list != NULL);
        if (list == NULL) {
@@ -207,7 +207,7 @@ gs_overview_page_get_recent_cb (GObject *source_object, GAsyncResult *res, gpoin
        g_autoptr(GsAppList) list = NULL;
 
        /* get recent apps */
-       list = gs_plugin_loader_get_recent_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        g_warning ("failed to get recent apps: %s", error->message);
@@ -281,7 +281,7 @@ gs_overview_page_get_category_apps_cb (GObject *source_object,
        g_autoptr(GsAppList) list = NULL;
 
        /* get popular apps */
-       list = gs_plugin_loader_get_category_apps_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        goto out;
@@ -364,7 +364,7 @@ gs_overview_page_get_featured_cb (GObject *source_object,
        g_autoptr(GError) error = NULL;
        g_autoptr(GsAppList) list = NULL;
 
-       list = gs_plugin_loader_get_featured_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                goto out;
 
@@ -430,7 +430,7 @@ gs_overview_page_get_categories_cb (GObject *source_object,
        g_autoptr(GError) error = NULL;
        g_autoptr(GPtrArray) list = NULL;
 
-       list = gs_plugin_loader_get_categories_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_get_categories_finish (plugin_loader, res, &error);
        if (list == NULL) {
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        g_warning ("failed to get categories: %s", error->message);
@@ -544,22 +544,34 @@ gs_overview_page_load (GsOverviewPage *self)
        priv->empty = TRUE;
 
        if (!priv->loading_featured) {
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+
                priv->loading_featured = TRUE;
-               gs_plugin_loader_get_featured_async (priv->plugin_loader,
-                                                    GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                                    GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                    priv->cancellable,
-                                                    gs_overview_page_get_featured_cb,
-                                                    self);
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_FEATURED,
+                                                "max-results", 5,
+                                                "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                                NULL);
+               gs_plugin_loader_job_process_async (priv->plugin_loader,
+                                                   plugin_job,
+                                                   priv->cancellable,
+                                                   gs_overview_page_get_featured_cb,
+                                                   self);
                priv->action_cnt++;
        }
 
        if (!priv->loading_popular) {
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+
                priv->loading_popular = TRUE;
-               gs_plugin_loader_get_popular_async (priv->plugin_loader,
-                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
-                                                   GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                                   GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_POPULAR,
+                                                "max-results", 20,
+                                                "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                "refine-flags", 
GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
+                                                                GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                                NULL);
+               gs_plugin_loader_job_process_async (priv->plugin_loader,
+                                                   plugin_job,
                                                    priv->cancellable,
                                                    gs_overview_page_get_popular_cb,
                                                    self);
@@ -567,15 +579,21 @@ gs_overview_page_load (GsOverviewPage *self)
        }
 
        if (!priv->loading_recent) {
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+
                priv->loading_recent = TRUE;
-               gs_plugin_loader_get_recent_async (priv->plugin_loader,
-                                                  60 * 60 * 24 * 60,
-                                                  GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
-                                                  GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                                  GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                  priv->cancellable,
-                                                  gs_overview_page_get_recent_cb,
-                                                  self);
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_RECENT,
+                                                "age", 60 * 60 * 24 * 60,
+                                                "max-results", 20,
+                                                "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                "refine-flags", 
GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
+                                                                GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                                NULL);
+               gs_plugin_loader_job_process_async (priv->plugin_loader,
+                                                   plugin_job,
+                                                   priv->cancellable,
+                                                   gs_overview_page_get_recent_cb,
+                                                   self);
                priv->action_cnt++;
        }
 
@@ -590,6 +608,7 @@ gs_overview_page_load (GsOverviewPage *self)
                        const gchar *cat_id;
                        g_autoptr(GsCategory) category = NULL;
                        g_autoptr(GsCategory) featured_category = NULL;
+                       g_autoptr(GsPluginJob) plugin_job = NULL;
 
                        cat_id = g_ptr_array_index (cats_random, i);
                        if (i == 0) {
@@ -604,27 +623,33 @@ gs_overview_page_load (GsOverviewPage *self)
                        load_data->category = g_object_ref (category);
                        load_data->self = g_object_ref (self);
                        load_data->title = gs_overview_page_get_category_label (cat_id);
-                       gs_plugin_loader_get_category_apps_async (priv->plugin_loader,
-                                                                 featured_category,
-                                                                 
GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
-                                                                 GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                                                 GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                                 priv->cancellable,
-                                                                 gs_overview_page_get_category_apps_cb,
-                                                                 load_data);
+                       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_CATEGORY_APPS,
+                                                        "max-results", 20,
+                                                        "category", featured_category,
+                                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                        "refine-flags", 
GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
+                                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                                        NULL);
+                       gs_plugin_loader_job_process_async (priv->plugin_loader,
+                                                           plugin_job,
+                                                           priv->cancellable,
+                                                           gs_overview_page_get_category_apps_cb,
+                                                           load_data);
                        priv->action_cnt++;
                }
                priv->loading_popular_rotating = TRUE;
        }
 
        if (!priv->loading_categories) {
+               g_autoptr(GsPluginJob) plugin_job = NULL;
                priv->loading_categories = TRUE;
-               gs_plugin_loader_get_categories_async (priv->plugin_loader,
-                                                      GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                                      GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                      priv->cancellable,
-                                                      gs_overview_page_get_categories_cb,
-                                                      self);
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_CATEGORIES,
+                                                "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                NULL);
+               gs_plugin_loader_job_get_categories_async (priv->plugin_loader, plugin_job,
+                                                         priv->cancellable,
+                                                         gs_overview_page_get_categories_cb,
+                                                         self);
                priv->action_cnt++;
        }
 }
@@ -716,7 +741,7 @@ gs_overview_page_get_sources_cb (GsPluginLoader *plugin_loader,
        GsOverviewPagePrivate *priv = gs_overview_page_get_instance_private (self);
 
        /* get the results */
-       list = gs_plugin_loader_get_sources_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                if (g_error_matches (error,
                                     GS_PLUGIN_ERROR,
@@ -763,9 +788,14 @@ static void
 gs_overview_page_rescan_proprietary_sources (GsOverviewPage *self)
 {
        GsOverviewPagePrivate *priv = gs_overview_page_get_instance_private (self);
-       gs_plugin_loader_get_sources_async (priv->plugin_loader,
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION,
-                                           GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader,
+                                           plugin_job,
                                            priv->cancellable,
                                            (GAsyncReadyCallback) gs_overview_page_get_sources_cb,
                                            self);
diff --git a/src/gs-page.c b/src/gs-page.c
index 818adf6..8f4441c 100644
--- a/src/gs-page.c
+++ b/src/gs-page.c
@@ -80,6 +80,7 @@ gs_page_install_authenticate_cb (GtkDialog *dialog,
                                 GsPageHelper *helper)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* unmap the dialog */
        gtk_widget_destroy (GTK_WIDGET (dialog));
@@ -88,13 +89,13 @@ gs_page_install_authenticate_cb (GtkDialog *dialog,
                gs_page_helper_free (helper);
                return;
        }
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          helper->app,
-                                          GS_PLUGIN_ACTION_INSTALL,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          helper->cancellable,
-                                          gs_page_app_installed_cb,
-                                          helper);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                        "app", helper->app,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+                                           helper->cancellable,
+                                           gs_page_app_installed_cb,
+                                           helper);
 }
 
 static void
@@ -108,6 +109,7 @@ gs_page_remove_authenticate_cb (GtkDialog *dialog,
                                GsPageHelper *helper)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* unmap the dialog */
        gtk_widget_destroy (GTK_WIDGET (dialog));
@@ -116,13 +118,13 @@ gs_page_remove_authenticate_cb (GtkDialog *dialog,
                gs_page_helper_free (helper);
                return;
        }
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          helper->app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          helper->cancellable,
-                                          gs_page_app_removed_cb,
-                                          helper);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", helper->app,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+                                           helper->cancellable,
+                                           gs_page_app_removed_cb,
+                                           helper);
 }
 
 static void
@@ -137,9 +139,9 @@ gs_page_app_installed_cb (GObject *source,
        gboolean ret;
        g_autoptr(GError) error = NULL;
 
-       ret = gs_plugin_loader_app_action_finish (plugin_loader,
-                                                 res,
-                                                 &error);
+       ret = gs_plugin_loader_job_action_finish (plugin_loader,
+                                                  res,
+                                                  &error);
        if (g_error_matches (error,
                             GS_PLUGIN_ERROR,
                             GS_PLUGIN_ERROR_CANCELLED)) {
@@ -199,9 +201,9 @@ gs_page_app_removed_cb (GObject *source,
        gboolean ret;
        g_autoptr(GError) error = NULL;
 
-       ret = gs_plugin_loader_app_action_finish (plugin_loader,
-                                                 res,
-                                                 &error);
+       ret = gs_plugin_loader_job_action_finish (plugin_loader,
+                                                  res,
+                                                  &error);
        if (g_error_matches (error,
                             GS_PLUGIN_ERROR,
                             GS_PLUGIN_ERROR_CANCELLED)) {
@@ -281,6 +283,7 @@ gs_page_install_app (GsPage *page,
        GsPagePrivate *priv = gs_page_get_instance_private (page);
        GsPageHelper *helper;
        GtkResponseType response;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* probably non-free */
        if (gs_app_get_state (app) == AS_APP_STATE_UNAVAILABLE) {
@@ -295,13 +298,14 @@ gs_page_install_app (GsPage *page,
        helper->page = g_object_ref (page);
        helper->cancellable = g_object_ref (cancellable);
        helper->interaction = interaction;
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          app,
-                                          helper->action,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          helper->cancellable,
-                                          gs_page_app_installed_cb,
-                                          helper);
+       plugin_job = gs_plugin_job_newv (helper->action,
+                                        "app", helper->app,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader,
+                                           plugin_job,
+                                           helper->cancellable,
+                                           gs_page_app_installed_cb,
+                                           helper);
 }
 
 static void
@@ -310,6 +314,7 @@ gs_page_update_app_response_cb (GtkDialog *dialog,
                                GsPageHelper *helper)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* unmap the dialog */
        gtk_widget_destroy (GTK_WIDGET (dialog));
@@ -320,13 +325,14 @@ gs_page_update_app_response_cb (GtkDialog *dialog,
                return;
        }
        g_debug ("update %s", gs_app_get_id (helper->app));
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          helper->app,
-                                          GS_PLUGIN_ACTION_UPDATE,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          helper->cancellable,
-                                          gs_page_app_installed_cb,
-                                          helper);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE,
+                                        "app", helper->app,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader,
+                                           plugin_job,
+                                           helper->cancellable,
+                                           gs_page_app_installed_cb,
+                                           helper);
 }
 
 static void
@@ -394,6 +400,7 @@ gs_page_update_app (GsPage *page, GsApp *app, GCancellable *cancellable)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (page);
        GsPageHelper *helper;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* non-firmware applications do not have to be prepared */
        helper = g_slice_new0 (GsPageHelper);
@@ -416,13 +423,13 @@ gs_page_update_app (GsPage *page, GsApp *app, GCancellable *cancellable)
        }
 
        /* generic fallback */
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          helper->app,
-                                          helper->action,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          helper->cancellable,
-                                          gs_page_app_installed_cb,
-                                          helper);
+       plugin_job = gs_plugin_job_newv (helper->action,
+                                        "app", app,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+                                           helper->cancellable,
+                                           gs_page_app_installed_cb,
+                                           helper);
 }
 
 static void
@@ -431,6 +438,7 @@ gs_page_remove_app_response_cb (GtkDialog *dialog,
                                GsPageHelper *helper)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* unmap the dialog */
        gtk_widget_destroy (GTK_WIDGET (dialog));
@@ -441,13 +449,13 @@ gs_page_remove_app_response_cb (GtkDialog *dialog,
                return;
        }
        g_debug ("remove %s", gs_app_get_id (helper->app));
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          helper->app,
-                                          helper->action,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          helper->cancellable,
-                                          gs_page_app_removed_cb,
-                                          helper);
+       plugin_job = gs_plugin_job_newv (helper->action,
+                                        "app", helper->app,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+                                           helper->cancellable,
+                                           gs_page_app_removed_cb,
+                                           helper);
 }
 
 void
@@ -466,14 +474,15 @@ gs_page_remove_app (GsPage *page, GsApp *app, GCancellable *cancellable)
        helper->page = g_object_ref (page);
        helper->cancellable = g_object_ref (cancellable);
        if (gs_app_get_state (app) == AS_APP_STATE_QUEUED_FOR_INSTALL) {
+               g_autoptr(GsPluginJob) plugin_job = NULL;
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                                "app", app,
+                                                NULL);
                g_debug ("remove %s", gs_app_get_id (app));
-               gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                                  app,
-                                                  GS_PLUGIN_ACTION_REMOVE,
-                                                  GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                  helper->cancellable,
-                                                  gs_page_app_removed_cb,
-                                                  helper);
+               gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+                                                   helper->cancellable,
+                                                   gs_page_app_removed_cb,
+                                                   helper);
                return;
        }
 
@@ -528,7 +537,7 @@ gs_page_app_launched_cb (GObject *source,
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        g_autoptr(GError) error = NULL;
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("failed to launch GsApp: %s", error->message);
                return;
        }
@@ -538,13 +547,15 @@ void
 gs_page_launch_app (GsPage *page, GsApp *app, GCancellable *cancellable)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (page);
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_LAUNCH,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                          cancellable,
-                                          gs_page_app_launched_cb,
-                                          NULL);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_LAUNCH,
+                                        "app", app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+                                           cancellable,
+                                           gs_page_app_launched_cb,
+                                           NULL);
 }
 
 static void
@@ -554,7 +565,7 @@ gs_page_app_shortcut_added_cb (GObject *source,
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        g_autoptr(GError) error = NULL;
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("failed to add a shortcut to GsApp: %s", error->message);
                return;
        }
@@ -564,13 +575,14 @@ void
 gs_page_shortcut_add (GsPage *page, GsApp *app, GCancellable *cancellable)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (page);
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_ADD_SHORTCUT,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          cancellable,
-                                          gs_page_app_shortcut_added_cb,
-                                          NULL);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_ADD_SHORTCUT,
+                                        "app", app,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+                                           cancellable,
+                                           gs_page_app_shortcut_added_cb,
+                                           NULL);
 }
 
 static void
@@ -580,7 +592,7 @@ gs_page_app_shortcut_removed_cb (GObject *source,
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
        g_autoptr(GError) error = NULL;
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("failed to remove the shortcut to GsApp: %s", error->message);
                return;
        }
@@ -590,13 +602,14 @@ void
 gs_page_shortcut_remove (GsPage *page, GsApp *app, GCancellable *cancellable)
 {
        GsPagePrivate *priv = gs_page_get_instance_private (page);
-       gs_plugin_loader_app_action_async (priv->plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_REMOVE_SHORTCUT,
-                                          GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                          cancellable,
-                                          gs_page_app_shortcut_removed_cb,
-                                          NULL);
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE_SHORTCUT,
+                                        "app", app,
+                                        NULL);
+       gs_plugin_loader_job_process_async (priv->plugin_loader, plugin_job,
+                                           cancellable,
+                                           gs_page_app_shortcut_removed_cb,
+                                           NULL);
 }
 
 gboolean
diff --git a/src/gs-search-page.c b/src/gs-search-page.c
index 2eb7b81..518fa00 100644
--- a/src/gs-search-page.c
+++ b/src/gs-search-page.c
@@ -112,7 +112,7 @@ gs_search_page_get_search_cb (GObject *source_object,
        /* don't do the delayed spinner */
        gs_search_page_waiting_cancel (self);
 
-       list = gs_plugin_loader_search_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
                        g_debug ("search cancelled");
@@ -256,6 +256,8 @@ gs_search_page_sort_cb (GsApp *app1, GsApp *app2, gpointer user_data)
 static void
 gs_search_page_load (GsSearchPage *self)
 {
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+
        /* cancel any pending searches */
        if (self->search_cancellable != NULL) {
                g_cancellable_cancel (self->search_cancellable);
@@ -266,26 +268,28 @@ gs_search_page_load (GsSearchPage *self)
        /* search for apps */
        gs_search_page_waiting_cancel (self);
        self->waiting_id = g_timeout_add (250, gs_search_page_waiting_show_cb, self);
-
-       gs_plugin_loader_search_async (self->plugin_loader,
-                                      self->value,
-                                      GS_SEARCH_PAGE_MAX_RESULTS,
-                                      gs_search_page_sort_cb, self,
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING,
-                                      GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                      self->search_cancellable,
-                                      gs_search_page_get_search_cb,
-                                      self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", self->value,
+                                        "max-results", GS_SEARCH_PAGE_MAX_RESULTS,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING,
+                                        NULL);
+       gs_plugin_job_set_sort_func (plugin_job, gs_search_page_sort_cb);
+       gs_plugin_job_set_sort_func_data (plugin_job, self);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->search_cancellable,
+                                           gs_search_page_get_search_cb,
+                                           self);
 }
 
 static void
diff --git a/src/gs-shell-search-provider.c b/src/gs-shell-search-provider.c
index 0d4ac26..8fbde4e 100644
--- a/src/gs-shell-search-provider.c
+++ b/src/gs-shell-search-provider.c
@@ -79,7 +79,7 @@ search_done_cb (GObject *source,
        GVariantBuilder builder;
        g_autoptr(GsAppList) list = NULL;
 
-       list = gs_plugin_loader_search_finish (self->plugin_loader, res, NULL);
+       list = gs_plugin_loader_job_process_finish (self->plugin_loader, res, NULL);
        if (list == NULL) {
                g_dbus_method_invocation_return_value (search->invocation, g_variant_new ("(as)", NULL));
                pending_search_free (search);
@@ -153,9 +153,10 @@ execute_search (GsShellSearchProvider  *self,
                gchar            **terms)
 {
        PendingSearch *pending_search;
-       g_autofree gchar *string = NULL;
+       g_autofree gchar *value = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
-       string = g_strjoinv (" ", terms);
+       value = g_strjoinv (" ", terms);
 
        if (self->cancellable != NULL) {
                g_cancellable_cancel (self->cancellable);
@@ -175,15 +176,19 @@ execute_search (GsShellSearchProvider  *self,
 
        g_application_hold (g_application_get_default ());
        self->cancellable = g_cancellable_new ();
-       gs_plugin_loader_search_async (self->plugin_loader,
-                                      string,
-                                      GS_SHELL_SEARCH_PROVIDER_MAX_RESULTS,
-                                      gs_shell_search_provider_sort_cb, self,
-                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                      GS_PLUGIN_FAILURE_FLAGS_NONE,
-                                      self->cancellable,
-                                      search_done_cb,
-                                      pending_search);
+
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+                                        "search", value,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
+                                        "max-results", GS_SHELL_SEARCH_PROVIDER_MAX_RESULTS,
+                                        NULL);
+       gs_plugin_job_set_sort_func (plugin_job, gs_shell_search_provider_sort_cb);
+       gs_plugin_job_set_sort_func_data (plugin_job, self);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           search_done_cb,
+                                           pending_search);
 }
 
 static gboolean
diff --git a/src/gs-sources-dialog.c b/src/gs-sources-dialog.c
index b594abe..94a5d2c 100644
--- a/src/gs-sources-dialog.c
+++ b/src/gs-sources-dialog.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -171,7 +171,7 @@ source_modified_cb (GObject *source,
        GsSourcesDialog *dialog = GS_SOURCES_DIALOG (user_data);
        g_autoptr(GError) error = NULL;
 
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("failed to remove: %s", error->message);
        } else {
                reload_sources (dialog);
@@ -199,23 +199,27 @@ gs_sources_dialog_rescan_proprietary_sources (GsSourcesDialog *dialog)
                /* depending on the new policy, add or remove the source */
                if (g_settings_get_boolean (dialog->settings, "show-nonfree-software")) {
                        if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE) {
-                               gs_plugin_loader_app_action_async (dialog->plugin_loader,
-                                                                  app,
-                                                                  GS_PLUGIN_ACTION_INSTALL,
-                                                                  GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                                  dialog->cancellable,
-                                                                  source_modified_cb,
-                                                                  dialog);
+                               g_autoptr(GsPluginJob) plugin_job = NULL;
+                               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_INSTALL,
+                                                                "app", app,
+                                                                NULL);
+                               gs_plugin_loader_job_process_async (dialog->plugin_loader,
+                                                                   plugin_job,
+                                                                   dialog->cancellable,
+                                                                   source_modified_cb,
+                                                                   dialog);
                        }
                } else {
                        if (gs_app_get_state (app) == AS_APP_STATE_INSTALLED) {
-                               gs_plugin_loader_app_action_async (dialog->plugin_loader,
-                                                                  app,
-                                                                  GS_PLUGIN_ACTION_REMOVE,
-                                                                  GS_PLUGIN_FAILURE_FLAGS_FATAL_ANY,
-                                                                  dialog->cancellable,
-                                                                  source_modified_cb,
-                                                                  dialog);
+                               g_autoptr(GsPluginJob) plugin_job = NULL;
+                               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                                                "app", app,
+                                                                NULL);
+                               gs_plugin_loader_job_process_async (dialog->plugin_loader,
+                                                                   plugin_job,
+                                                                   dialog->cancellable,
+                                                                   source_modified_cb,
+                                                                   dialog);
                        }
                }
        }
@@ -316,7 +320,7 @@ get_sources_cb (GsPluginLoader *plugin_loader,
        gs_stop_spinner (GTK_SPINNER (dialog->spinner));
 
        /* get the results */
-       list = gs_plugin_loader_get_sources_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                if (g_error_matches (error,
                                     GS_PLUGIN_ERROR,
@@ -357,16 +361,19 @@ get_sources_cb (GsPluginLoader *plugin_loader,
 static void
 reload_sources (GsSourcesDialog *dialog)
 {
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+
        gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "waiting");
        gs_start_spinner (GTK_SPINNER (dialog->spinner));
        gtk_widget_hide (dialog->button_back);
        gs_container_remove_all (GTK_CONTAINER (dialog->listbox));
 
        /* get the list of non-core software sources */
-       gs_plugin_loader_get_sources_async (dialog->plugin_loader,
-                                           GS_PLUGIN_REFINE_FLAGS_DEFAULT |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED,
-                                           GS_PLUGIN_FAILURE_FLAGS_NONE,
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_SOURCES,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED,
+                                        NULL);
+       gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
                                            dialog->cancellable,
                                            (GAsyncReadyCallback) get_sources_cb,
                                            dialog);
@@ -480,7 +487,7 @@ app_removed_cb (GObject *source,
        GsSourcesDialog *dialog = GS_SOURCES_DIALOG (user_data);
        g_autoptr(GError) error = NULL;
 
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("failed to remove: %s", error->message);
        } else {
                reload_sources (dialog);
@@ -499,6 +506,7 @@ static void
 remove_button_cb (GtkWidget *widget, GsSourcesDialog *dialog)
 {
        GsApp *app;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* disable button */
        gtk_widget_set_sensitive (dialog->button_remove, FALSE);
@@ -511,13 +519,14 @@ remove_button_cb (GtkWidget *widget, GsSourcesDialog *dialog)
        /* remove source */
        app = GS_APP (g_object_get_data (G_OBJECT (dialog->stack), "GsShell::app"));
        g_debug ("removing source '%s'", gs_app_get_name (app));
-       gs_plugin_loader_app_action_async (dialog->plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_REMOVE,
-                                          GS_PLUGIN_FAILURE_FLAGS_NONE,
-                                          dialog->cancellable,
-                                          app_removed_cb,
-                                          dialog);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REMOVE,
+                                        "app", app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                        NULL);
+       gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
+                                           dialog->cancellable,
+                                           app_removed_cb,
+                                           dialog);
 }
 
 static gboolean
diff --git a/src/gs-update-dialog.c b/src/gs-update-dialog.c
index 4023874..32e94a4 100644
--- a/src/gs-update-dialog.c
+++ b/src/gs-update-dialog.c
@@ -175,7 +175,7 @@ get_installed_updates_cb (GsPluginLoader *plugin_loader,
        gs_stop_spinner (GTK_SPINNER (dialog->spinner));
 
        /* get the results */
-       list = gs_plugin_loader_get_updates_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                if (g_error_matches (error,
                                    GS_PLUGIN_ERROR,
@@ -229,7 +229,7 @@ get_installed_updates_cb (GsPluginLoader *plugin_loader,
 void
 gs_update_dialog_show_installed_updates (GsUpdateDialog *dialog)
 {
-       guint64 refine_flags;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* TRANSLATORS: this is the title of the installed updates dialog window */
        gtk_window_set_title (GTK_WINDOW (dialog), _("Installed Updates"));
@@ -238,14 +238,12 @@ gs_update_dialog_show_installed_updates (GsUpdateDialog *dialog)
        gs_start_spinner (GTK_SPINNER (dialog->spinner));
        gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "spinner");
 
-       refine_flags = GS_PLUGIN_REFINE_FLAGS_DEFAULT |
-                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS |
-                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
-                      GS_PLUGIN_REFINE_FLAGS_USE_HISTORY;
-
-       gs_plugin_loader_get_updates_async (dialog->plugin_loader,
-                                           refine_flags,
-                                           GS_PLUGIN_FAILURE_FLAGS_NONE,
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION,
+                                        NULL);
+       gs_plugin_loader_job_process_async (dialog->plugin_loader, plugin_job,
                                            dialog->cancellable,
                                            (GAsyncReadyCallback) get_installed_updates_cb,
                                            dialog);
diff --git a/src/gs-update-monitor.c b/src/gs-update-monitor.c
index fa3a115..811ca75 100644
--- a/src/gs-update-monitor.c
+++ b/src/gs-update-monitor.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
  *
  * Licensed under the GNU General Public License Version 2
@@ -160,7 +160,7 @@ get_updates_finished_cb (GObject *object,
        g_autoptr(GsAppList) apps = NULL;
 
        /* get result */
-       apps = gs_plugin_loader_get_updates_finish (GS_PLUGIN_LOADER (object), res, &error);
+       apps = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (object), res, &error);
        if (apps == NULL) {
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        g_warning ("failed to get updates: %s", error->message);
@@ -233,7 +233,7 @@ get_system_finished_cb (GObject *object, GAsyncResult *res, gpointer data)
        g_autoptr(GsApp) app = NULL;
 
        /* get result */
-       if (!gs_plugin_loader_app_refine_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        g_warning ("failed to get system: %s", error->message);
                return;
@@ -272,7 +272,7 @@ get_upgrades_finished_cb (GObject *object,
        g_autoptr(GsAppList) apps = NULL;
 
        /* get result */
-       apps = gs_plugin_loader_get_distro_upgrades_finish (GS_PLUGIN_LOADER (object), res, &error);
+       apps = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (object), res, &error);
        if (apps == NULL) {
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
                        g_warning ("failed to get upgrades: %s",
@@ -315,13 +315,17 @@ get_upgrades_finished_cb (GObject *object,
 static void
 get_updates (GsUpdateMonitor *monitor)
 {
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        /* NOTE: this doesn't actually do any network access, instead it just
         * returns already downloaded-and-depsolved packages */
        g_debug ("Getting updates");
-       gs_plugin_loader_get_updates_async (monitor->plugin_loader,
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS |
-                                           GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_SEVERITY,
-                                           GS_PLUGIN_FAILURE_FLAGS_NONE,
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS |
+                                                        GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_SEVERITY,
+                                        NULL);
+       gs_plugin_loader_job_process_async (monitor->plugin_loader,
+                                           plugin_job,
                                            monitor->cancellable,
                                            get_updates_finished_cb,
                                            monitor);
@@ -330,31 +334,38 @@ get_updates (GsUpdateMonitor *monitor)
 static void
 get_upgrades (GsUpdateMonitor *monitor)
 {
+       g_autoptr(GsPluginJob) plugin_job = NULL;
+
        /* NOTE: this doesn't actually do any network access, it relies on the
         * AppStream data being up to date, either by the appstream-data
         * package being up-to-date, or the metadata being auto-downloaded */
        g_debug ("Getting upgrades");
-       gs_plugin_loader_get_distro_upgrades_async (monitor->plugin_loader,
-                                                   GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                                   GS_PLUGIN_FAILURE_FLAGS_NONE,
-                                                   monitor->cancellable,
-                                                   get_upgrades_finished_cb,
-                                                   monitor);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_DISTRO_UPDATES,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                        NULL);
+       gs_plugin_loader_job_process_async (monitor->plugin_loader,
+                                           plugin_job,
+                                           monitor->cancellable,
+                                           get_upgrades_finished_cb,
+                                           monitor);
 }
 
 static void
 get_system (GsUpdateMonitor *monitor)
 {
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        g_debug ("Getting system");
        app = gs_plugin_loader_get_system_app (monitor->plugin_loader);
-       gs_plugin_loader_app_refine_async (monitor->plugin_loader, app,
-                                          GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                          GS_PLUGIN_FAILURE_FLAGS_NONE,
-                                          monitor->cancellable,
-                                          get_system_finished_cb,
-                                          monitor);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "app", app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                        NULL);
+       gs_plugin_loader_job_process_async (monitor->plugin_loader, plugin_job,
+                                           monitor->cancellable,
+                                           get_system_finished_cb,
+                                           monitor);
 }
 
 static void
@@ -365,7 +376,7 @@ refresh_cache_finished_cb (GObject *object,
        GsUpdateMonitor *monitor = data;
        g_autoptr(GError) error = NULL;
 
-       if (!gs_plugin_loader_refresh_finish (GS_PLUGIN_LOADER (object), res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (GS_PLUGIN_LOADER (object), res, &error)) {
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        g_warning ("failed to refresh the cache: %s", error->message);
                return;
@@ -391,6 +402,7 @@ check_updates (GsUpdateMonitor *monitor)
        gboolean refresh_on_metered;
        g_autoptr(GDateTime) last_refreshed = NULL;
        g_autoptr(GDateTime) now_refreshed = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        GsPluginRefreshFlags refresh_flags = GS_PLUGIN_REFRESH_FLAGS_METADATA;
 
        /* never check for updates when offline */
@@ -458,13 +470,15 @@ check_updates (GsUpdateMonitor *monitor)
                g_debug ("Refreshing for metadata only");
        }
 
-       gs_plugin_loader_refresh_async (monitor->plugin_loader,
-                                       60 * 60 * 24,
-                                       refresh_flags,
-                                       GS_PLUGIN_FAILURE_FLAGS_NONE,
-                                       monitor->cancellable,
-                                       refresh_cache_finished_cb,
-                                       monitor);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UNKNOWN,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                        "refresh-flags", refresh_flags,
+                                        "age", 60 * 60 * 24,
+                                        NULL);
+       gs_plugin_loader_job_process_async (monitor->plugin_loader, plugin_job,
+                                           monitor->cancellable,
+                                           refresh_cache_finished_cb,
+                                           monitor);
 }
 
 static gboolean
@@ -584,7 +598,7 @@ get_updates_historical_cb (GObject *object, GAsyncResult *res, gpointer data)
        g_autoptr(GNotification) notification = NULL;
 
        /* get result */
-       apps = gs_plugin_loader_get_updates_finish (GS_PLUGIN_LOADER (object), res, &error);
+       apps = gs_plugin_loader_job_process_finish (GS_PLUGIN_LOADER (object), res, &error);
        if (apps == NULL) {
 
                /* save this in case the user clicks the
@@ -647,12 +661,15 @@ static gboolean
 cleanup_notifications_cb (gpointer user_data)
 {
        GsUpdateMonitor *monitor = user_data;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* this doesn't do any network access */
        g_debug ("getting historical updates for fresh session");
-       gs_plugin_loader_get_updates_async (monitor->plugin_loader,
-                                           GS_PLUGIN_REFINE_FLAGS_USE_HISTORY,
-                                           GS_PLUGIN_FAILURE_FLAGS_NONE,
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_NONE,
+                                        NULL);
+       gs_plugin_loader_job_process_async (monitor->plugin_loader,
+                                           plugin_job,
                                            monitor->cancellable,
                                            get_updates_historical_cb,
                                            monitor);
diff --git a/src/gs-updates-page.c b/src/gs-updates-page.c
index 606ed49..699d970 100644
--- a/src/gs-updates-page.c
+++ b/src/gs-updates-page.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  *
- * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  *
  * Licensed under the GNU General Public License Version 2
  *
@@ -455,7 +455,7 @@ gs_updates_page_get_updates_cb (GsPluginLoader *plugin_loader,
        self->cache_valid = TRUE;
 
        /* get the results */
-       list = gs_plugin_loader_get_updates_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPDATES);
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
@@ -537,7 +537,7 @@ gs_updates_page_get_upgrades_cb (GObject *source_object,
        g_autoptr(GsAppList) list = NULL;
 
        /* get the results */
-       list = gs_plugin_loader_get_distro_upgrades_finish (plugin_loader, res, &error);
+       list = gs_plugin_loader_job_process_finish (plugin_loader, res, &error);
        if (list == NULL) {
                gs_updates_page_clear_flag (self, GS_UPDATES_PAGE_FLAG_HAS_UPGRADES);
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
@@ -572,7 +572,7 @@ gs_updates_page_get_system_finished_cb (GObject *source_object,
        g_autoptr(GString) str = g_string_new (NULL);
 
        /* get result */
-       if (!gs_plugin_loader_app_refine_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                if (!g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        g_warning ("failed to get system: %s", error->message);
                return;
@@ -615,6 +615,7 @@ gs_updates_page_load (GsUpdatesPage *self)
 {
        guint64 refine_flags;
        g_autoptr(GsApp) app = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        if (self->action_cnt > 0)
                return;
@@ -625,31 +626,40 @@ gs_updates_page_load (GsUpdatesPage *self)
                       GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION;
        gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_ACTION_GET_UPDATES);
        self->action_cnt++;
-       gs_plugin_loader_get_updates_async (self->plugin_loader,
-                                           refine_flags,
-                                           GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_UPDATES,
+                                        "refine-flags", refine_flags,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
                                            self->cancellable,
                                            (GAsyncReadyCallback) gs_updates_page_get_updates_cb,
                                            self);
 
        /* get the system state */
+       g_object_unref (plugin_job);
        app = gs_plugin_loader_get_system_app (self->plugin_loader);
-       gs_plugin_loader_app_refine_async (self->plugin_loader, app,
-                                          GS_PLUGIN_REFINE_FLAGS_DEFAULT,
-                                          GS_PLUGIN_FAILURE_FLAGS_NONE,
-                                          self->cancellable,
-                                          gs_updates_page_get_system_finished_cb,
-                                          self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
+                                        "app", app,
+                                        "refine-flags", refine_flags,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           gs_updates_page_get_system_finished_cb,
+                                           self);
 
        /* don't refresh every each time */
        if ((self->result_flags & GS_UPDATES_PAGE_FLAG_HAS_UPGRADES) == 0) {
                refine_flags |= GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPGRADE_REMOVED;
-               gs_plugin_loader_get_distro_upgrades_async (self->plugin_loader,
-                                                           refine_flags,
-                                                           GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                                           self->cancellable,
-                                                           gs_updates_page_get_upgrades_cb,
-                                                           self);
+               g_object_unref (plugin_job);
+               plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_GET_DISTRO_UPDATES,
+                                                "refine-flags", refine_flags,
+                                                "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                                NULL);
+               gs_plugin_loader_job_process_async (self->plugin_loader,
+                                                   plugin_job,
+                                                   self->cancellable,
+                                                   gs_updates_page_get_upgrades_cb,
+                                                   self);
                self->action_cnt++;
        }
 }
@@ -752,7 +762,7 @@ gs_updates_page_refresh_cb (GsPluginLoader *plugin_loader,
        g_autoptr(GError) error = NULL;
 
        /* get the results */
-       ret = gs_plugin_loader_refresh_finish (plugin_loader, res, &error);
+       ret = gs_plugin_loader_job_action_finish (plugin_loader, res, &error);
        if (!ret) {
                /* user cancel */
                if (g_error_matches (error,
@@ -783,6 +793,7 @@ static void
 gs_updates_page_get_new_updates (GsUpdatesPage *self)
 {
        GsPluginRefreshFlags refresh_flags = GS_PLUGIN_REFRESH_FLAGS_NONE;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        /* force a check for updates and download */
        gs_updates_page_set_state (self, GS_UPDATES_PAGE_STATE_ACTION_REFRESH);
@@ -797,13 +808,16 @@ gs_updates_page_get_new_updates (GsUpdatesPage *self)
        refresh_flags |= GS_PLUGIN_REFRESH_FLAGS_METADATA;
        if (g_settings_get_boolean (self->settings, "download-updates"))
                refresh_flags |= GS_PLUGIN_REFRESH_FLAGS_PAYLOAD;
-       gs_plugin_loader_refresh_async (self->plugin_loader,
-                                       10 * 60,
-                                       refresh_flags,
-                                       GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                       self->cancellable_refresh,
-                                       (GAsyncReadyCallback) gs_updates_page_refresh_cb,
-                                       self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFRESH,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        "refine-flags", refresh_flags,
+                                        "refresh-flags", refresh_flags,
+                                        "age", 10 * 60,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable_refresh,
+                                           (GAsyncReadyCallback) gs_updates_page_refresh_cb,
+                                           self);
 }
 
 static void
@@ -938,7 +952,7 @@ cancel_trigger_failed_cb (GObject *source, GAsyncResult *res, gpointer user_data
 {
        GsUpdatesPage *self = GS_UPDATES_PAGE (user_data);
        g_autoptr(GError) error = NULL;
-       if (!gs_plugin_loader_app_action_finish (self->plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (self->plugin_loader, res, &error)) {
                g_warning ("failed to cancel trigger: %s", error->message);
                return;
        }
@@ -950,6 +964,7 @@ gs_updates_page_reboot_failed_cb (GObject *source, GAsyncResult *res, gpointer u
        GsUpdatesPage *self = GS_UPDATES_PAGE (user_data);
        g_autoptr(GError) error = NULL;
        g_autoptr(GsAppList) apps = NULL;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
        g_autoptr(GVariant) retval = NULL;
 
        /* get result */
@@ -964,13 +979,14 @@ gs_updates_page_reboot_failed_cb (GObject *source, GAsyncResult *res, gpointer u
 
        /* cancel trigger */
        apps = gs_update_list_get_apps (GS_UPDATE_LIST (self->list_box_updates));
-       gs_plugin_loader_app_action_async (self->plugin_loader,
-                                          gs_app_list_index (apps, 0),
-                                          GS_PLUGIN_ACTION_UPDATE_CANCEL,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                          self->cancellable,
-                                          cancel_trigger_failed_cb,
-                                          self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE_CANCEL,
+                                        "app", gs_app_list_index (apps, 0),
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           cancel_trigger_failed_cb,
+                                           self);
 }
 
 static void
@@ -984,7 +1000,7 @@ gs_updates_page_perform_update_cb (GsPluginLoader *plugin_loader,
        gtk_widget_set_sensitive (GTK_WIDGET (self->button_update_all), TRUE);
 
        /* get the results */
-       if (!gs_plugin_loader_update_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                g_warning ("Failed to perform update: %s", error->message);
                return;
        }
@@ -1025,14 +1041,17 @@ update_all (GsUpdatesPage *self, GsAppList *apps)
 {
        g_autoptr(GError) error = NULL;
        g_autoptr(GCancellable) cancellable = g_cancellable_new ();
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        g_set_object (&self->cancellable, cancellable);
-       gs_plugin_loader_update_async (self->plugin_loader,
-                                      apps,
-                                      GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                      self->cancellable,
-                                      (GAsyncReadyCallback) gs_updates_page_perform_update_cb,
-                                      self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE,
+                                        "list", apps,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           (GAsyncReadyCallback) gs_updates_page_perform_update_cb,
+                                           self);
 }
 
 static void
@@ -1080,7 +1099,7 @@ upgrade_download_finished_cb (GObject *source,
        g_autoptr(GError) error = NULL;
        g_autoptr(GsPageHelper) helper = (GsPageHelper *) user_data;
 
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (plugin_loader, res, &error)) {
                if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED))
                        return;
                g_warning ("failed to upgrade-download: %s", error->message);
@@ -1093,6 +1112,7 @@ gs_updates_page_upgrade_download_cb (GsUpgradeBanner *upgrade_banner,
 {
        GsApp *app;
        GsPageHelper *helper;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        app = gs_upgrade_banner_get_app (upgrade_banner);
        if (app == NULL) {
@@ -1107,13 +1127,15 @@ gs_updates_page_upgrade_download_cb (GsUpgradeBanner *upgrade_banner,
        if (self->cancellable_upgrade_download != NULL)
                g_object_unref (self->cancellable_upgrade_download);
        self->cancellable_upgrade_download = g_cancellable_new ();
-       gs_plugin_loader_app_action_async (self->plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                          self->cancellable_upgrade_download,
-                                          upgrade_download_finished_cb,
-                                          helper);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD,
+                                        "failure-flags",
+                                        "app", app,
+                                        GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable_upgrade_download,
+                                           upgrade_download_finished_cb,
+                                           helper);
 }
 
 static void
@@ -1124,6 +1146,7 @@ upgrade_reboot_failed_cb (GObject *source,
        GsUpdatesPage *self = (GsUpdatesPage *) user_data;
        GsApp *app;
        g_autoptr(GError) error = NULL;
+       g_autoptr(GsPluginJob) plugin_job;
        g_autoptr(GVariant) retval = NULL;
 
        /* get result */
@@ -1143,13 +1166,14 @@ upgrade_reboot_failed_cb (GObject *source,
        }
 
        /* cancel trigger */
-       gs_plugin_loader_app_action_async (self->plugin_loader,
-                                          app,
-                                          GS_PLUGIN_ACTION_UPDATE_CANCEL,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                          self->cancellable,
-                                          cancel_trigger_failed_cb,
-                                          self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPDATE_CANCEL,
+                                        "app", app,
+                                        "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           cancel_trigger_failed_cb,
+                                           self);
 }
 
 static void
@@ -1162,7 +1186,7 @@ upgrade_trigger_finished_cb (GObject *source,
        g_autoptr(GError) error = NULL;
 
        /* get the results */
-       if (!gs_plugin_loader_update_finish (self->plugin_loader, res, &error)) {
+       if (!gs_plugin_loader_job_action_finish (self->plugin_loader, res, &error)) {
                g_warning ("Failed to trigger offline update: %s", error->message);
                return;
        }
@@ -1184,6 +1208,7 @@ static void
 trigger_upgrade (GsUpdatesPage *self)
 {
        GsApp *upgrade;
+       g_autoptr(GsPluginJob) plugin_job = NULL;
 
        upgrade = gs_upgrade_banner_get_app (GS_UPGRADE_BANNER (self->upgrade_banner));
        if (upgrade == NULL) {
@@ -1191,13 +1216,13 @@ trigger_upgrade (GsUpdatesPage *self)
                return;
        }
 
-       gs_plugin_loader_app_action_async (self->plugin_loader,
-                                          upgrade,
-                                          GS_PLUGIN_ACTION_UPGRADE_TRIGGER,
-                                          GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
-                                          self->cancellable,
-                                          upgrade_trigger_finished_cb,
-                                          self);
+       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_UPGRADE_TRIGGER,
+                                        "app", upgrade,
+                                        NULL);
+       gs_plugin_loader_job_process_async (self->plugin_loader, plugin_job,
+                                           self->cancellable,
+                                           upgrade_trigger_finished_cb,
+                                           self);
 }
 
 static void


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