[gnome-software/wip/ubuntu-xenial: 204/208] Merge tag 'GNOME_SOFTWARE_3_20_2' into wip/ubuntu-xenial



commit 341c0baeda013b8421afb20df309ce2961f1c262
Merge: ab2fa9c b937c30
Author: Robert Ancell <robert ancell canonical com>
Date:   Mon Aug 15 15:54:26 2016 +1200

    Merge tag 'GNOME_SOFTWARE_3_20_2' into wip/ubuntu-xenial

 RELEASE                                            |    6 +-
 configure.ac                                       |   19 +-
 contrib/gnome-software.spec.in                     |    1 +
 data/Makefile.am                                   |    1 +
 data/appdata/org.gnome.Software.appdata.xml.in     |   27 +
 data/upgrade-bg.png                                |  Bin 0 -> 308691 bytes
 doc/kudos.md                                       |   42 +-
 doc/update-fedora-tagger.sh                        |    1 -
 po/LINGUAS                                         |    1 +
 po/POTFILES.in                                     |   13 +-
 po/fur.po                                          | 2833 ++++++++++++++++++++
 po/gd.po                                           | 1673 ++++++++----
 po/ja.po                                           |  655 +++--
 po/lv.po                                           |  402 ++--
 po/pt_BR.po                                        |   55 +-
 po/ru.po                                           |  130 +-
 po/sr.po                                           |   16 +-
 src/Makefile.am                                    |   23 +-
 src/gnome-software.gresource.xml                   |   18 +-
 src/gs-app-folder-dialog.c                         |    2 +-
 ...pp-folder-dialog.ui => gs-app-folder-dialog.ui} |    0
 src/gs-app-row.c                                   |   11 +-
 src/gs-app-tile.c                                  |    2 +-
 src/{app-tile.ui => gs-app-tile.ui}                |    0
 src/gs-app.c                                       |  405 ++--
 src/gs-app.h                                       |   19 +-
 src/gs-application.c                               |    4 +-
 src/gs-category-tile.c                             |    2 +-
 src/{category-tile.ui => gs-category-tile.ui}      |    0
 src/gs-category.c                                  |    3 +
 src/gs-cmd.c                                       |   30 +-
 src/gs-feature-tile.c                              |    2 +-
 src/{feature-tile.ui => gs-feature-tile.ui}        |    0
 src/{menus.ui => gs-menus.ui}                      |    0
 src/gs-page.c                                      |   28 +
 src/gs-plugin-loader-sync.c                        |    1 -
 src/gs-plugin-loader.c                             |  444 ++--
 src/gs-plugin-loader.h                             |    8 +-
 src/gs-plugin.c                                    |  137 +
 src/gs-plugin.h                                    |   54 +-
 src/gs-popular-tile.c                              |    2 +-
 src/{popular-tile.ui => gs-popular-tile.ui}        |    0
 src/gs-screenshot-image.c                          |   12 +-
 ...{screenshot-image.ui => gs-screenshot-image.ui} |    0
 src/gs-self-test.c                                 |  801 +++----
 src/gs-shell-details.c                             |   34 +-
 src/gs-shell-details.ui                            |    8 +-
 src/gs-shell-extras.c                              |    1 -
 src/gs-shell-installed.c                           |    9 +-
 src/gs-shell-moderate.c                            |    2 +-
 src/gs-shell-search.c                              |    2 +-
 src/gs-shell-updates.c                             |  429 ++--
 src/gs-shell-updates.ui                            |   48 +-
 src/gs-shell.c                                     |    1 +
 src/gs-update-dialog.c                             |   23 +-
 src/gs-update-monitor.c                            |    9 +-
 src/gs-upgrade-banner.c                            |  112 +-
 src/gs-upgrade-banner.h                            |    7 +-
 src/gs-upgrade-banner.ui                           |  125 +-
 src/gs-utils.c                                     |  135 +-
 src/gs-utils.h                                     |    5 +
 src/gtk-style.css                                  |   13 +-
 src/plugins/Makefile.am                            |   49 +-
 src/plugins/gs-appstream.c                         |  120 +-
 src/{ => plugins}/gs-markdown.c                    |    0
 src/{ => plugins}/gs-markdown.h                    |    0
 src/plugins/gs-plugin-appstream.c                  |  129 +-
 src/plugins/gs-plugin-apt.cc                       |    2 +-
 src/plugins/gs-plugin-dummy.c                      |  461 +++-
 src/plugins/gs-plugin-epiphany.c                   |   52 +-
 src/plugins/gs-plugin-fedora-distro-upgrades.c     |  201 ++-
 src/plugins/gs-plugin-fedora-tagger-usage.c        |    4 +
 src/plugins/gs-plugin-fwupd.c                      |   49 +-
 src/plugins/gs-plugin-hardcoded-blacklist.c        |   32 +-
 src/plugins/gs-plugin-hardcoded-featured.c         |    1 +
 src/plugins/gs-plugin-icons.c                      |   59 +-
 src/plugins/gs-plugin-limba.c                      |   70 +-
 src/plugins/gs-plugin-menu-spec-refine.c           |   46 +-
 src/plugins/gs-plugin-moduleset.c                  |    4 +-
 ...s-plugin-xdg-app-reviews.c => gs-plugin-odrs.c} |  198 +-
 src/plugins/gs-plugin-packagekit-history.c         |   15 +-
 src/plugins/gs-plugin-packagekit-offline.c         |    2 +-
 src/plugins/gs-plugin-packagekit-origin.c          |   53 +-
 src/plugins/gs-plugin-packagekit-proxy.c           |   47 +-
 src/plugins/gs-plugin-packagekit-refine.c          |   91 +-
 src/plugins/gs-plugin-packagekit-refresh.c         |   89 +-
 src/plugins/gs-plugin-packagekit-upgrade.c         |  141 +
 src/plugins/gs-plugin-packagekit.c                 |  134 +-
 src/plugins/gs-plugin-provenance.c                 |   78 +-
 src/plugins/gs-plugin-self-test.c                  |   68 -
 src/plugins/gs-plugin-systemd-updates.c            |   36 +-
 src/plugins/gs-plugin-ubuntu-reviews.c             |   36 +-
 src/plugins/gs-plugin-xdg-app.c                    |  200 +-
 src/plugins/gs-self-test.c                         |  185 ++-
 src/plugins/menu-spec-common.c                     |  354 ++--
 src/plugins/menu-spec-common.h                     |    1 +
 src/plugins/packagekit-common.c                    |  102 +-
 src/plugins/packagekit-common.h                    |    3 +
 98 files changed, 8128 insertions(+), 3830 deletions(-)
---
diff --cc po/POTFILES.in
index bbbd21d,dcb1383..02f8681
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@@ -55,15 -55,7 +55,10 @@@ src/gs-update-monitor.
  src/gs-upgrade-banner.c
  [type: gettext/glade]src/gs-upgrade-banner.ui
  src/gs-utils.c
- [type: gettext/glade]src/menus.ui
+ [type: gettext/glade]src/gs-menus.ui
  src/org.gnome.Software.desktop.in
- src/plugins/gs-plugin-appstream.c
- src/plugins/gs-plugin-epiphany.c
- src/plugins/gs-plugin-moduleset.c
- src/plugins/gs-plugin-packagekit.c
- src/plugins/gs-plugin-packagekit-refine.c
 +src/plugins/gs-ubuntuone-dialog.c
 +[type: gettext/glade]src/plugins/gs-ubuntuone-dialog.ui
 +src/plugins/gs-plugin-snap.c
  src/plugins/menu-spec-common.c
- [type: gettext/glade]src/popular-tile.ui
+ [type: gettext/glade]src/gs-popular-tile.ui
diff --cc src/gs-app-row.c
index 623d840,b40dab0..193e88e
--- a/src/gs-app-row.c
+++ b/src/gs-app-row.c
@@@ -85,6 -84,15 +84,16 @@@ gs_app_row_get_description (GsAppRow *a
        const gchar *tmp = NULL;
        g_autofree gchar *escaped = NULL;
  
+       /* convert the markdown update description into PangoMarkup */
 -      if (priv->show_update &&
++      /* NOTE: Disabled on Ubuntu since the Debian changelog doesn't display well */
++      /*if (priv->show_update &&
+           (gs_app_get_state (priv->app) == AS_APP_STATE_UPDATABLE ||
+            gs_app_get_state (priv->app) == AS_APP_STATE_UPDATABLE_LIVE)) {
+               tmp = gs_app_get_update_details (priv->app);
+               if (tmp != NULL && tmp[0] != '\0')
+                       return g_string_new (tmp);
 -      }
++      }*/
+ 
        if (gs_app_get_state (priv->app) == AS_APP_STATE_UNAVAILABLE)
                return g_string_new (gs_app_get_summary_missing (priv->app));
  
diff --cc src/gs-app.c
index 2770a03,6effba8..ad29b87
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@@ -101,9 -103,8 +103,9 @@@ struct _GsAp
        guint64                  kudos;
        gboolean                 to_be_installed;
        AsAppQuirk               quirk;
-       gboolean                 licence_is_free;
+       gboolean                 license_is_free;
        GsApp                   *runtime;
 +      GFile                   *local_file;
  };
  
  enum {
@@@ -237,33 -260,32 +261,36 @@@ gs_app_to_string (GsApp *app
                im = as_screenshot_get_image (ss, 0, 0);
                if (im == NULL)
                        continue;
-               g_string_append_printf (str, "\tscreenshot-%02i:\t%s [%s]\n",
-                                       i, as_image_get_url (im),
-                                       tmp != NULL ? tmp : "<none>");
+               key = g_strdup_printf ("screenshot-%02i", i);
+               gs_app_kv_printf (str, key, "%s [%s]",
+                                 as_image_get_url (im),
+                                 tmp != NULL ? tmp : "<none>");
        }
        for (i = 0; i < app->sources->len; i++) {
+               g_autofree gchar *key = NULL;
                tmp = g_ptr_array_index (app->sources, i);
-               g_string_append_printf (str, "\tsource-%02i:\t%s\n", i, tmp);
+               key = g_strdup_printf ("source-%02i", i);
+               gs_app_kv_lpad (str, key, tmp);
        }
        for (i = 0; i < app->source_ids->len; i++) {
+               g_autofree gchar *key = NULL;
                tmp = g_ptr_array_index (app->source_ids, i);
-               g_string_append_printf (str, "\tsource-id-%02i:\t%s\n", i, tmp);
+               key = g_strdup_printf ("source-id-%02i", i);
+               gs_app_kv_lpad (str, key, tmp);
        }
 +      if (app->local_file != NULL) {
 +              g_autofree gchar *fn = g_file_get_path (app->local_file);
-               g_string_append_printf (str, "\tlocal-filename:\t%s\n", fn);
++              gs_app_kv_lpad (str, "local-filename", fn);
 +      }
        tmp = g_hash_table_lookup (app->urls, as_url_kind_to_string (AS_URL_KIND_HOMEPAGE));
        if (tmp != NULL)
-               g_string_append_printf (str, "\turl{homepage}:\t%s\n", tmp);
-       if (app->licence != NULL)
-               g_string_append_printf (str, "\tlicence:\t%s\n", app->licence);
-       g_string_append_printf (str, "\topen source:\t%s\n",
-                               gs_app_get_license_is_free (app) ? "yes" : "no");
+               gs_app_kv_lpad (str, "url{homepage}", tmp);
+       if (app->license != NULL)
+               gs_app_kv_lpad (str, "license", app->license);
        if (app->management_plugin != NULL)
-               g_string_append_printf (str, "\tmanagement-plugin:\t%s\n", app->management_plugin);
+               gs_app_kv_lpad (str, "management-plugin", app->management_plugin);
        if (app->summary_missing != NULL)
-               g_string_append_printf (str, "\tsummary-missing:\t%s\n", app->summary_missing);
+               gs_app_kv_lpad (str, "summary-missing", app->summary_missing);
        if (app->menu_path != NULL &&
            app->menu_path[0] != NULL &&
            app->menu_path[0][0] != '\0') {
@@@ -2359,8 -2306,8 +2354,10 @@@ gs_app_finalize (GObject *object
        g_ptr_array_unref (app->categories);
        if (app->keywords != NULL)
                g_ptr_array_unref (app->keywords);
+       if (app->last_error != NULL)
+               g_error_free (app->last_error);
 +      if (app->local_file != NULL)
 +              g_object_unref (app->local_file);
  
        G_OBJECT_CLASS (gs_app_parent_class)->finalize (object);
  }
diff --cc src/gs-plugin-loader.c
index 131e14e,e128f0c..291726e
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@@ -2721,15 -2660,9 +2670,9 @@@ gs_plugin_loader_app_action_async (GsPl
                break;
        case GS_PLUGIN_LOADER_ACTION_LAUNCH:
                state->function_name = "gs_plugin_launch";
-               state->state_success = AS_APP_STATE_UNKNOWN;
-               state->state_failure = AS_APP_STATE_UNKNOWN;
                break;
 -      case GS_PLUGIN_LOADER_ACTION_OFFLINE_UPDATE_CANCEL:
 -              state->function_name = "gs_plugin_offline_update_cancel";
 +      case GS_PLUGIN_LOADER_ACTION_UPDATE_CANCEL:
 +              state->function_name = "gs_plugin_update_cancel";
-               state->state_success = AS_APP_STATE_UNKNOWN;
-               state->state_failure = AS_APP_STATE_UNKNOWN;
                break;
        default:
                g_assert_not_reached ();
@@@ -3940,8 -3823,7 +3887,7 @@@ gs_plugin_loader_update_async (GsPlugin
        /* run in a thread */
        task = g_task_new (plugin_loader, cancellable, callback, user_data);
        g_task_set_task_data (task, state, (GDestroyNotify) gs_plugin_loader_free_async_state);
-       g_task_set_return_on_cancel (task, TRUE);
 -      g_task_run_in_thread (task, gs_plugin_loader_offline_update_thread_cb);
 +      g_task_run_in_thread (task, gs_plugin_loader_update_thread_cb);
  }
  
  /**
diff --cc src/gs-plugin.h
index 731a219,c1862e1..49d0272
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@@ -116,9 -118,26 +118,28 @@@ typedef enum 
        GS_PLUGIN_REFINE_FLAGS_LAST
  } GsPluginRefineFlags;
  
+ /**
+  * GsPluginRefreshFlags:
+  * @GS_PLUGIN_REFRESH_FLAGS_NONE:     Generate new metadata if possible
+  * @GS_PLUGIN_REFRESH_FLAGS_METADATA: Download new metadata
+  * @GS_PLUGIN_REFRESH_FLAGS_PAYLOAD:  Download any pending payload
+  *
+  * The flags used for refresh. Regeneration and downloading is only
+  * done if the cache is older than the %cache_age.
+  *
+  * The %GS_PLUGIN_REFRESH_FLAGS_METADATA can be used to make sure
+  * there's enough metadata to start the application.
+  * The %GS_PLUGIN_REFRESH_FLAGS_PAYLOAD flag should only be used when
+  * the session is idle and bandwidth is unmetered as the amount of data
+  * and IO may be large.
+  **/
  typedef enum {
-       GS_PLUGIN_REFRESH_FLAGS_UPDATES                 = 1 << 0,
-       GS_PLUGIN_REFRESH_FLAGS_UI                      = 1 << 1,
+       GS_PLUGIN_REFRESH_FLAGS_NONE                    = 0,
+       GS_PLUGIN_REFRESH_FLAGS_METADATA                = 1 << 0,
+       GS_PLUGIN_REFRESH_FLAGS_PAYLOAD                 = 1 << 1,
++      GS_PLUGIN_REFRESH_FLAGS_UPDATES                 = 1 << 2,
++      GS_PLUGIN_REFRESH_FLAGS_UI                      = 1 << 3,
+       /*< private >*/
        GS_PLUGIN_REFRESH_FLAGS_LAST
  } GsPluginRefreshFlags;
  
diff --cc src/gs-self-test.c
index 98b9911,7a0cb1b..820d9e9
--- a/src/gs-self-test.c
+++ b/src/gs-self-test.c
@@@ -690,44 -419,10 +419,43 @@@ gs_plugin_loader_webapps_func (GsPlugin
                                           &error);
        g_assert_no_error (error);
        g_assert (ret);
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_UNAVAILABLE);
- 
-       g_unlink (path);
+       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
+       g_assert (gs_app_get_pixbuf (app) != NULL);
  }
  
 +static void
 +gs_plugin_loader_dpkg_func (GsPluginLoader *plugin_loader)
 +{
 +      g_autoptr(GsApp) app = NULL;
 +      g_autoptr(GError) error = NULL;
 +      g_autofree gchar *fn = NULL;
 +
 +      /* no dpkg, abort */
 +      if (!gs_plugin_loader_get_enabled (plugin_loader, "dpkg"))
 +              return;
 +
 +      /* load local file */
 +      fn = gs_test_get_filename ("tests/chiron-1.1-1.deb");
 +      g_assert (fn != NULL);
 +      app = gs_plugin_loader_filename_to_app (plugin_loader,
 +                                              fn,
 +                                              GS_PLUGIN_REFINE_FLAGS_DEFAULT,
 +                                              NULL,
 +                                              &error);
 +      g_assert_no_error (error);
 +      g_assert (app != NULL);
 +      g_assert_cmpstr (gs_app_get_id (app), ==, NULL);
 +      g_assert_cmpstr (gs_app_get_source_default (app), ==, "chiron");
 +      g_assert_cmpstr (gs_app_get_url (app, AS_URL_KIND_HOMEPAGE), ==, "http://127.0.0.1/";);
 +      g_assert_cmpstr (gs_app_get_name (app), ==, "chiron");
 +      g_assert_cmpstr (gs_app_get_version (app), ==, "1.1-1");
 +      g_assert_cmpstr (gs_app_get_summary (app), ==, "Single line synopsis");
 +      g_assert_cmpstr (gs_app_get_description (app), ==,
 +                       "This is the first paragraph in the example "
 +                       "package control file.\nThis is the second paragraph.");
 +      g_assert (gs_app_get_local_file (app) != NULL);
 +}
 +
  int
  main (int argc, char **argv)
  {
diff --cc src/gs-shell-details.c
index 7414701,7e1982f..09a6494
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@@ -81,8 -80,7 +81,8 @@@ struct _GsShellDetail
        GtkWidget               *label_details_category_value;
        GtkWidget               *label_details_developer_title;
        GtkWidget               *label_details_developer_value;
-       GtkWidget               *label_details_licence_title;
-       GtkWidget               *label_details_licence_value;
++      GtkWidget               *label_details_license_title;
+       GtkWidget               *label_details_license_value;
        GtkWidget               *label_details_origin_title;
        GtkWidget               *label_details_origin_value;
        GtkWidget               *label_details_size_value;
@@@ -664,19 -662,15 +664,19 @@@ gs_shell_details_refresh_all (GsShellDe
                gtk_widget_set_visible (self->label_details_developer_value, TRUE);
        }
  
-       /* set the licence */
+       /* set the license */
        tmp = gs_app_get_license (self->app);
        if (tmp == NULL) {
-               /* TRANSLATORS: this is where the licence is not known */
-               gtk_label_set_label (GTK_LABEL (self->label_details_licence_value), C_("license", "Unknown"));
-               gtk_widget_set_tooltip_text (self->label_details_licence_value, NULL);
-               gtk_widget_set_visible (self->label_details_licence_title, FALSE);
-               gtk_widget_set_visible (self->label_details_licence_value, FALSE);
+               /* TRANSLATORS: this is where the license is not known */
+               gtk_label_set_label (GTK_LABEL (self->label_details_license_value), C_("license", "Unknown"));
+               gtk_widget_set_tooltip_text (self->label_details_license_value, NULL);
++              gtk_widget_set_visible (self->label_details_license_title, FALSE);
++              gtk_widget_set_visible (self->label_details_license_value, FALSE);
        } else {
-               gtk_label_set_markup (GTK_LABEL (self->label_details_licence_value), tmp);
-               gtk_widget_set_tooltip_text (self->label_details_licence_value, NULL);
-               gtk_widget_set_visible (self->label_details_licence_title, TRUE);
-               gtk_widget_set_visible (self->label_details_licence_value, TRUE);
+               gtk_label_set_markup (GTK_LABEL (self->label_details_license_value), tmp);
+               gtk_widget_set_tooltip_text (self->label_details_license_value, NULL);
++              gtk_widget_set_visible (self->label_details_license_title, TRUE);
++              gtk_widget_set_visible (self->label_details_license_value, TRUE);
        }
  
        /* set version */
@@@ -1618,8 -1594,7 +1618,8 @@@ gs_shell_details_class_init (GsShellDet
        gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_category_value);
        gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_developer_title);
        gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_developer_value);
-       gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_licence_title);
-       gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_licence_value);
++      gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_license_title);
+       gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_license_value);
        gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_origin_title);
        gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_origin_value);
        gtk_widget_class_bind_template_child (widget_class, GsShellDetails, label_details_size_value);
diff --cc src/gs-shell-updates.c
index ceeceda,1e3156a..40a910f
--- a/src/gs-shell-updates.c
+++ b/src/gs-shell-updates.c
@@@ -1003,17 -979,32 +1025,34 @@@ gs_shell_updates_button_update_all_cb (
        g_autoptr(GError) error = NULL;
        g_autoptr(GList) apps = NULL;
  
 +      gtk_widget_set_sensitive (GTK_WIDGET (self->button_update_all), FALSE);
 +
        /* do the offline update */
        apps = gs_update_list_get_apps (GS_UPDATE_LIST (self->list_box_updates));
 -      gs_plugin_loader_offline_update_async (self->plugin_loader,
 -                                             apps,
 -                                             self->cancellable,
 -                                             (GAsyncReadyCallback) gs_shell_updates_offline_update_cb,
 -                                             self);
 +      gs_plugin_loader_update_async (self->plugin_loader,
 +                                     apps,
 +                                     self->cancellable,
 +                                     (GAsyncReadyCallback) gs_shell_updates_perform_update_cb,
 +                                     self);
  }
  
+ typedef struct {
+       GsApp           *app;
+       GsShellUpdates  *self;
+ } GsPageHelper;
+ 
+ static void
+ gs_page_helper_free (GsPageHelper *helper)
+ {
+       if (helper->app != NULL)
+               g_object_unref (helper->app);
+       if (helper->self != NULL)
+               g_object_unref (helper->self);
+       g_slice_free (GsPageHelper, helper);
+ }
+ 
+ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPageHelper, gs_page_helper_free);
+ 
  static void
  upgrade_download_finished_cb (GObject *source,
                                GAsyncResult *res,
diff --cc src/gs-utils.c
index 9bc2572,956a721..7467b94
--- a/src/gs-utils.c
+++ b/src/gs-utils.c
@@@ -535,27 -564,63 +566,87 @@@ gs_utils_is_current_desktop (const gcha
  }
  
  /**
 + * gs_utils_get_desktop_app_info:
 + */
 +GDesktopAppInfo *
 +gs_utils_get_desktop_app_info (const gchar *id)
 +{
 +      GDesktopAppInfo *app_info;
 +
 +      /* try to get the standard app-id */
 +      app_info = g_desktop_app_info_new (id);
 +
 +      /* KDE is a special project because it believes /usr/share/applications
 +       * isn't KDE enough. For this reason we support falling back to the
 +       * "kde4-" prefixed ID to avoid educating various self-righteous
 +       * upstreams about the correct ID to use in the AppData file. */
 +      if (app_info == NULL) {
 +              g_autofree gchar *kde_id = NULL;
 +              kde_id = g_strdup_printf ("%s-%s", "kde4", id);
 +              app_info = g_desktop_app_info_new (kde_id);
 +      }
 +
 +      return app_info;
 +}
 +
++/**
+  * gs_utils_widget_css_parsing_error_cb:
+  */
+ static void
+ gs_utils_widget_css_parsing_error_cb (GtkCssProvider *provider,
+                                     GtkCssSection *section,
+                                     GError *error,
+                                     gpointer user_data)
+ {
+       g_warning ("CSS parse error %i:%i: %s",
+                  gtk_css_section_get_start_line (section),
+                  gtk_css_section_get_start_position (section),
+                  error->message);
+ }
+ 
+ /**
+  * gs_utils_widget_set_custom_css:
+  **/
+ void
+ gs_utils_widget_set_custom_css (GtkWidget *widget, const gchar *css)
+ {
+       GString *str = g_string_sized_new (1024);
+       GtkStyleContext *context;
+       g_autofree gchar *class_name = NULL;
+       g_autoptr(GtkCssProvider) provider = NULL;
+ 
+       /* invalid */
+       if (css == NULL)
+               return;
+ 
+       /* make into a proper CSS class */
+       class_name = g_strdup_printf ("themed-widget_%p", widget);
+       g_string_append_printf (str, ".%s {\n", class_name);
+       g_string_append_printf (str, "%s\n", css);
+       g_string_append (str, "}");
+ 
+       g_string_append_printf (str, ".%s:hover {\n", class_name);
+       g_string_append (str, "  opacity: 0.9;\n");
+       g_string_append (str, "}\n");
+ 
+       g_debug ("using custom CSS %s", str->str);
+ 
+       /* set the custom CSS class */
+       context = gtk_widget_get_style_context (widget);
+       gtk_style_context_add_class (context, class_name);
+ 
+       /* set up custom provider and store on the widget */
+       provider = gtk_css_provider_new ();
+       g_signal_connect (provider, "parsing-error",
+                         G_CALLBACK (gs_utils_widget_css_parsing_error_cb), NULL);
+       gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+                                                  GTK_STYLE_PROVIDER (provider),
+                                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_css_provider_load_from_data (provider, str->str, -1, NULL);
+       g_object_set_data_full (G_OBJECT (widget),
+                               "GnomeSoftware::provider",
+                               g_object_ref (provider),
+                               g_object_unref);
+ }
+ 
  /* vim: set noexpandtab: */
diff --cc src/gs-utils.h
index 0540a9f,89d9abf..c7cc175
--- a/src/gs-utils.h
+++ b/src/gs-utils.h
@@@ -65,11 -64,12 +65,16 @@@ gchar              *gs_utils_get_cachedir          (const g
                                                 GError         **error);
  gchar         *gs_utils_get_user_hash         (GError         **error);
  GPermission   *gs_utils_get_permission        (const gchar    *id);
+ gboolean       gs_utils_is_current_desktop    (const gchar    *name);
+ 
+ gboolean       gs_utils_is_current_desktop    (const gchar    *name);
+ void           gs_utils_widget_set_custom_css (GtkWidget      *widget,
+                                                const gchar    *css);
  
 +gboolean       gs_utils_is_current_desktop    (const gchar    *name);
 +
 +GDesktopAppInfo *gs_utils_get_desktop_app_info        (const gchar    *id);
 +
  G_END_DECLS
  
  #endif /* __GS_UTILS_H */
diff --cc src/plugins/Makefile.am
index 68b0e0d,a256cec..bfcf2fc
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@@ -41,16 -35,9 +38,15 @@@ plugin_LTLIBRARIES =                                        
        libgs_plugin_menu-spec-refine.la                \
        libgs_plugin_fedora_distro_upgrades.la          \
        libgs_plugin_provenance.la                      \
-       libgs_plugin_ubuntu-reviews.la                  \
        libgs_plugin_fedora_tagger_usage.la             \
        libgs_plugin_epiphany.la                        \
 -      libgs_plugin_icons.la
 +      libgs_plugin_icons.la                           \
 +      libgs_plugin_snap.la
 +
 +if HAVE_APT
 +plugin_LTLIBRARIES +=                                 \
 +      libgs_plugin_apt.la
 +endif
  
  if HAVE_PACKAGEKIT
  plugin_LTLIBRARIES +=                                 \
@@@ -76,11 -68,9 +77,11 @@@ if HAVE_LIMB
  plugin_LTLIBRARIES += libgs_plugin_limba.la
  endif
  
 +if HAVE_XDG_APP
  if HAVE_ODRS
- plugin_LTLIBRARIES += libgs_plugin_xdg_app_reviews.la
+ plugin_LTLIBRARIES += libgs_plugin_odrs.la
  endif
 +endif
  
  libgs_plugin_dummy_la_SOURCES = gs-plugin-dummy.c
  libgs_plugin_dummy_la_LIBADD = $(GS_PLUGIN_LIBS)
@@@ -142,36 -122,15 +138,36 @@@ libgs_plugin_xdg_app_la_SOURCES = gs-pl
  libgs_plugin_xdg_app_la_LIBADD = $(GS_PLUGIN_LIBS) $(XDG_APP_LIBS)
  libgs_plugin_xdg_app_la_LDFLAGS = -module -avoid-version
  libgs_plugin_xdg_app_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+ endif
  
  if HAVE_ODRS
- libgs_plugin_xdg_app_reviews_la_SOURCES = gs-plugin-xdg-app-reviews.c
- libgs_plugin_xdg_app_reviews_la_LIBADD = $(GS_PLUGIN_LIBS) $(JSON_GLIB_LIBS)
- libgs_plugin_xdg_app_reviews_la_LDFLAGS = -module -avoid-version
- libgs_plugin_xdg_app_reviews_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
- endif
+ libgs_plugin_odrs_la_SOURCES = gs-plugin-odrs.c
+ libgs_plugin_odrs_la_LIBADD = $(GS_PLUGIN_LIBS) $(JSON_GLIB_LIBS)
+ libgs_plugin_odrs_la_LDFLAGS = -module -avoid-version
+ libgs_plugin_odrs_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
  endif
  
 +if HAVE_APT
 +ubuntu-unity-launcher-proxy.c ubuntu-unity-launcher-proxy.h: com.canonical.Unity.Launcher.xml Makefile
 +      $(AM_V_GEN) gdbus-codegen --interface-prefix com.canonical.Unity.Launcher \
 +                       --generate-c-code ubuntu-unity-launcher-proxy \
 +                       --c-namespace UbuntuUnity \
 +                       --annotate 'com.canonical.Unity.Launcher' \
 +                                  org.gtk.GDBus.C.Name \
 +                                  Launcher \
 +                       $<
 +
 +CLEANFILES = ubuntu-unity-launcher-proxy.h ubuntu-unity-launcher-proxy.c
 +
 +libgs_plugin_apt_la_SOURCES = \
 +      ubuntu-unity-launcher-proxy.c \
 +      ubuntu-unity-launcher-proxy.h \
 +      gs-plugin-apt.cc
 +libgs_plugin_apt_la_LIBADD = $(GS_PLUGIN_LIBS) -lapt-pkg
 +libgs_plugin_apt_la_LDFLAGS = -module -avoid-version
 +libgs_plugin_apt_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
 +endif
 +
  libgs_plugin_moduleset_la_SOURCES =                   \
        gs-moduleset.c                                  \
        gs-moduleset.h                                  \
@@@ -206,23 -165,13 +202,25 @@@ libgs_plugin_hardcoded_blacklist_la_LIB
  libgs_plugin_hardcoded_blacklist_la_LDFLAGS = -module -avoid-version
  libgs_plugin_hardcoded_blacklist_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
  
+ if HAVE_UBUNTU_REVIEWS
  libgs_plugin_ubuntu_reviews_la_SOURCES =              \
 -      gs-plugin-ubuntu-reviews.c
 -libgs_plugin_ubuntu_reviews_la_LIBADD = $(GS_PLUGIN_LIBS) $(SOUP_LIBS) $(JSON_GLIB_LIBS) $(SQLITE_LIBS)
 +      gs-plugin-ubuntu-reviews.c                      \
 +      gs-ubuntuone.h                                  \
 +      gs-ubuntuone.c                                  \
 +      gs-ubuntuone-dialog.h                           \
 +      gs-ubuntuone-dialog.c                           \
 +      gs-snapd.h                                      \
 +      gs-snapd.c
 +libgs_plugin_ubuntu_reviews_la_LIBADD =                       \
 +      $(GS_PLUGIN_LIBS)                               \
 +      $(SOUP_LIBS)                                    \
 +      $(JSON_GLIB_LIBS)                               \
 +      $(OAUTH_LIBS)                                   \
 +      $(SQLITE_LIBS)                                  \
 +      $(LIBSECRET_LIBS)
  libgs_plugin_ubuntu_reviews_la_LDFLAGS = -module -avoid-version
  libgs_plugin_ubuntu_reviews_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
+ endif
  
  libgs_plugin_packagekit_la_SOURCES =                  \
        gs-plugin-packagekit.c                          \
@@@ -280,31 -239,16 +288,32 @@@ libgs_plugin_packagekit_proxy_la_LIBAD
  libgs_plugin_packagekit_proxy_la_LDFLAGS = -module -avoid-version
  libgs_plugin_packagekit_proxy_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
  
 +libgs_plugin_snap_la_SOURCES =                                \
 +      gs-plugin-snap.c                                \
 +      gs-ubuntuone.h                                  \
 +      gs-ubuntuone.c                                  \
 +      gs-ubuntuone-dialog.h                           \
 +      gs-ubuntuone-dialog.c                           \
 +      gs-snapd.h                                      \
 +      gs-snapd.c
 +libgs_plugin_snap_la_LIBADD =                         \
 +      $(GS_PLUGIN_LIBS)                               \
 +      $(SOUP_LIBS)                                    \
 +      $(JSON_GLIB_LIBS)                               \
 +      $(LIBSECRET_LIBS)
 +libgs_plugin_snap_la_LDFLAGS = -module -avoid-version
 +libgs_plugin_snap_la_CFLAGS = $(GS_PLUGIN_CFLAGS) $(WARN_CFLAGS)
 +
- check_PROGRAMS =                                      \
+ check_PROGRAMS =                                              \
        gs-self-test
  
- gs_self_test_SOURCES =                                        \
-       gs-moduleset.c                                  \
+ gs_self_test_SOURCES =                                                \
+       gs-markdown.c                                           \
+       gs-moduleset.c                                          \
        gs-self-test.c
  
- gs_self_test_LDADD =                                  \
-       $(GLIB_LIBS)                                    \
+ gs_self_test_LDADD =                                          \
+       $(GLIB_LIBS)                                            \
        $(GTK_LIBS)
  
  gs_self_test_CFLAGS = $(WARN_CFLAGS)
diff --cc src/plugins/gs-plugin-appstream.c
index 159f0a4,6dd5413..d036cb7
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@@ -69,24 -66,6 +67,21 @@@ gs_plugin_appstream_store_changed_cb (A
        gs_plugin_updates_changed (plugin);
  }
  
- static gboolean gs_plugin_appstream_startup (GsPlugin *plugin, GError **error);
- 
 +gboolean
 +gs_plugin_refresh (GsPlugin              *plugin,
 +                 guint                  cache_age,
 +                 GsPluginRefreshFlags   flags,
 +                 GCancellable          *cancellable,
 +                 GError               **error)
 +{
 +      if (flags & GS_PLUGIN_REFRESH_FLAGS_UI) {
-               plugin->priv->done_init = FALSE;
-               gs_plugin_appstream_startup (plugin, NULL);
++              gs_plugin_setup (plugin, cancellable, NULL);
 +              gs_plugin_updates_changed (plugin);
 +      }
 +
 +      return TRUE;
 +}
 +
  /**
   * gs_plugin_initialize:
   */
diff --cc src/plugins/gs-plugin-apt.cc
index a0c547d,0000000..04451b3
mode 100644,000000..100644
--- a/src/plugins/gs-plugin-apt.cc
+++ b/src/plugins/gs-plugin-apt.cc
@@@ -1,1147 -1,0 +1,1147 @@@
 +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 + *
 + * Copyright (C) 2016 Canonical Ltd
 + *
 + * Licensed under the GNU General Public License Version 2
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License as published by
 + * the Free Software Foundation; either version 2 of the License, or
 + * (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 + */
 +
 +#include <config.h>
 +
 +#include <stdlib.h>
 +#include <string.h>
 +#include <ctype.h>
 +
 +#include <apt-pkg/init.h>
 +#include <apt-pkg/cachefile.h>
 +#include <apt-pkg/cmndline.h>
 +#include <apt-pkg/version.h>
 +
 +#include <assert.h>
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <string>
 +#include <set>
 +#include <vector>
 +#include <fstream>
 +#include <iomanip>
 +#include <algorithm>
 +#include <stdexcept>
 +
 +#include <gs-plugin.h>
 +#include <gs-utils.h>
 +
 +#define LICENSE_URL "http://www.ubuntu.com/about/about-ubuntu/licensing";
 +
 +#define INFO_DIR "/var/lib/dpkg/info"
 +
 +typedef struct
 +{
 +      GMutex pending_mutex;
 +      GCond pending_cond;
 +
 +      GMutex dispatched_mutex;
 +      GCond dispatched_cond;
 +
 +      GMutex hashtable_mutex;
 +
 +      guint still_to_read;
 +      guint dispatched_reads;
 +
 +      GsPlugin *plugin;
 +} ReadListData;
 +
 +typedef struct {
 +      gchar           *name;
 +      gchar           *section;
 +      gchar           *installed_version;
 +      gchar           *update_version;
 +      gchar           *origin;
 +      gchar           *release;
 +      gchar           *component;
 +      gint             installed_size;
 +} PackageInfo;
 +
 +#include "ubuntu-unity-launcher-proxy.h"
 +
 +struct GsPluginPrivate {
 +      GMutex           mutex;
 +      gboolean         loaded;
 +      GHashTable      *package_info;
 +      GHashTable      *installed_files;
 +      GList           *installed_packages;
 +      GList           *updatable_packages;
 +};
 +
 +const gchar *
 +gs_plugin_get_name (void)
 +{
 +      return "apt";
 +}
 +
 +const gchar **
 +gs_plugin_order_after (GsPlugin *plugin)
 +{
 +      static const gchar *deps[] = {
 +              "appstream",            /* need pkgname */
 +              NULL };
 +      return deps;
 +}
 +
 +/**
 + * gs_plugin_get_conflicts:
 + */
 +const gchar **
 +gs_plugin_get_conflicts (GsPlugin *plugin)
 +{
 +
 +      static const gchar *deps[] = {
 +              "packagekit",
 +              "packagekit-history",
 +              "packagekit-offline",
 +              "packagekit-origin",
 +              "packagekit-proxy",
 +              "packagekit-refine",
 +              "packagekit-refresh",
 +              "systemd-updates",
 +              NULL };
 +      return deps;
 +}
 +
 +static void
 +free_package_info (gpointer data)
 +{
 +      PackageInfo *info = (PackageInfo *) data;
 +      g_free (info->section);
 +      g_free (info->installed_version);
 +      g_free (info->update_version);
 +      g_free (info->origin);
 +      g_free (info->release);
 +      g_free (info->component);
 +      g_free (info->name);
 +      g_free (info);
 +}
 +
 +void
 +gs_plugin_initialize (GsPlugin *plugin)
 +{
 +      plugin->priv = GS_PLUGIN_GET_PRIVATE (GsPluginPrivate);
 +      plugin->priv->package_info = g_hash_table_new_full (g_str_hash,
 +                                                          g_str_equal,
 +                                                          NULL,
 +                                                          free_package_info);
 +
 +      plugin->priv->installed_files = g_hash_table_new_full (g_str_hash,
 +                                                          g_str_equal,
 +                                                          g_free,
 +                                                          g_free);
 +
 +      g_mutex_init (&plugin->priv->mutex);
 +
 +      pkgInitConfig (*_config);
 +      pkgInitSystem (*_config, _system);
 +}
 +
 +void
 +gs_plugin_destroy (GsPlugin *plugin)
 +{
 +      g_mutex_lock (&plugin->priv->mutex);
 +      plugin->priv->loaded = FALSE;
 +      g_clear_pointer (&plugin->priv->package_info, g_hash_table_unref);
 +      g_clear_pointer (&plugin->priv->installed_files, g_hash_table_unref);
 +      g_clear_pointer (&plugin->priv->installed_packages, g_list_free);
 +      g_clear_pointer (&plugin->priv->updatable_packages, g_list_free);
 +      g_mutex_unlock (&plugin->priv->mutex);
 +      g_mutex_clear (&plugin->priv->mutex);
 +}
 +
 +
 +static void
 +read_list_file_cb (GObject *object,
 +                 GAsyncResult *res,
 +                 gpointer user_data)
 +{
 +      g_autoptr(GFileInputStream) stream = NULL;
 +      g_autoptr(GFile) file = NULL;
 +      ReadListData *data;
 +      g_autofree gchar *buffer = NULL;
 +      g_autofree gchar *filename = NULL;
 +      g_autoptr(GFileInfo) info = NULL;
 +      g_auto(GStrv) file_lines = NULL;
 +      g_auto(GStrv) file_components = NULL;
 +      gchar *line;
 +
 +      file = G_FILE (object);
 +      data = (ReadListData *) user_data;
 +      stream = g_file_read_finish (file, res, NULL);
 +
 +      info = g_file_input_stream_query_info (stream,
 +                                             G_FILE_ATTRIBUTE_STANDARD_SIZE,
 +                                             NULL,
 +                                             NULL);
 +
 +      if (!info)
 +              return;
 +
 +      if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE))
 +              return;
 +
 +      buffer = (gchar *) g_malloc0 (g_file_info_get_size (info) + 1);
 +
 +      if (!g_input_stream_read_all (G_INPUT_STREAM (stream),
 +                                    buffer,
 +                                    g_file_info_get_size (info),
 +                                    NULL,
 +                                    NULL,
 +                                    NULL))
 +              return;
 +
 +      g_input_stream_close (G_INPUT_STREAM (stream), NULL, NULL);
 +
 +      file_lines = g_strsplit (buffer, "\n", -1);
 +
 +      filename = g_file_get_basename (file);
 +      file_components = g_strsplit (filename, ".", 2);
 +
 +      for (int i = 0; file_lines[i]; ++i)
 +              if (g_str_has_suffix (file_lines[i], ".desktop") ||
 +                  g_str_has_suffix (file_lines[i], ".metainfo.xml") ||
 +                  g_str_has_suffix (file_lines[i], ".appdata.xml"))
 +              {
 +                      g_mutex_lock (&data->hashtable_mutex);
 +                      /* filename -> package */
 +                      g_hash_table_insert (data->plugin->priv->installed_files,
 +                                           g_strdup (file_lines[i]),
 +                                           g_strdup (file_components[0]));
 +                      g_mutex_unlock (&data->hashtable_mutex);
 +              }
 +
 +      g_mutex_lock (&data->dispatched_mutex);
 +      (data->dispatched_reads)--;
 +      g_cond_signal(&data->dispatched_cond);
 +      g_mutex_unlock(&data->dispatched_mutex);
 +
 +      g_mutex_lock (&data->pending_mutex);
 +      (data->still_to_read)--;
 +      g_cond_signal (&data->pending_cond);
 +      g_mutex_unlock (&data->pending_mutex);
 +}
 +
 +static void
 +read_list_file (GList *files,
 +              ReadListData *data)
 +{
 +      GFile *gfile;
 +      GList *files_iter;
 +
 +      for (files_iter = files; files_iter; files_iter = files_iter->next)
 +      {
 +              /* freed in read_list_file_cb */
 +              gfile = g_file_new_for_path ((gchar *) files_iter->data);
 +
 +              g_mutex_lock (&data->dispatched_mutex);
 +              g_file_read_async (gfile,
 +                              G_PRIORITY_DEFAULT,
 +                              NULL,
 +                              read_list_file_cb,
 +                              data);
 +
 +              (data->dispatched_reads)++;
 +
 +              while (data->dispatched_reads >= 500)
 +                      g_cond_wait (&data->dispatched_cond, &data->dispatched_mutex);
 +
 +              g_mutex_unlock (&data->dispatched_mutex);
 +      }
 +}
 +
 +static void
 +look_for_files (GsPlugin *plugin)
 +{
 +
 +      ReadListData data;
 +      GList *files = NULL;
 +
 +      data.still_to_read = 0;
 +      data.dispatched_reads = 0;
 +      g_cond_init (&data.pending_cond);
 +      g_mutex_init (&data.pending_mutex);
 +      g_cond_init (&data.dispatched_cond);
 +      g_mutex_init (&data.dispatched_mutex);
 +      g_mutex_init (&data.hashtable_mutex);
 +      data.plugin = plugin;
 +
 +      g_autoptr (GDir) dir = NULL;
 +      const gchar *file;
 +
 +      dir = g_dir_open (INFO_DIR, 0, NULL);
 +
 +      while (file = g_dir_read_name (dir))
 +              if (g_str_has_suffix (file, ".list") &&
 +                 /* app-install-data contains loads of .desktop files, but they aren't installed by it */
 +                 (!g_strcmp0 (file, "app-install-data.list") == 0))
 +              {
 +                      files = g_list_append (files, g_build_filename (INFO_DIR, file, NULL));
 +                      data.still_to_read++;
 +              }
 +
 +      read_list_file (files, &data);
 +
 +      /* Wait until all the reads are done */
 +      g_mutex_lock (&data.pending_mutex);
 +      while (data.still_to_read > 0)
 +              g_cond_wait (&data.pending_cond, &data.pending_mutex);
 +      g_mutex_unlock (&data.pending_mutex);
 +
 +      g_mutex_clear (&data.pending_mutex);
 +      g_cond_clear (&data.pending_cond);
 +      g_mutex_clear (&data.dispatched_mutex);
 +      g_cond_clear (&data.dispatched_cond);
 +      g_mutex_clear (&data.hashtable_mutex);
 +
 +      g_list_free_full (files, g_free);
 +}
 +
 +static gboolean
 +version_newer (const gchar *v0, const gchar *v1)
 +{
 +      return v0 ? _system->VS->CmpVersion(v0, v1) < 0 : TRUE;
 +}
 +
 +/* return FALSE for a fatal error */
 +static gboolean
 +look_at_pkg (const pkgCache::PkgIterator &P,
 +           pkgSourceList *list,
 +           pkgPolicy *policy,
 +           GsPlugin *plugin,
 +           GError **error)
 +{
 +      pkgCache::VerIterator current = P.CurrentVer();
 +      pkgCache::VerIterator candidate = policy->GetCandidateVer(P);
 +      pkgCache::VerFileIterator VF;
 +      FileFd PkgF;
 +      pkgTagSection Tags;
 +      gchar *name;
 +
 +      PackageInfo *info;
 +
 +      if (!candidate || !candidate.FileList ())
 +              return TRUE;
 +
 +      name = g_strdup (P.Name ());
 +      info = (PackageInfo *) g_hash_table_lookup (plugin->priv->package_info, name);
 +      if (info == NULL) {
 +              info = g_new0 (PackageInfo, 1);
 +              info->name = name;
 +              g_hash_table_insert (plugin->priv->package_info, name, info);
 +      } else
 +              g_free (name);
 +
 +      for (VF = candidate.FileList (); VF.IsGood (); VF++) {
 +              // see InRelease for the fields
 +              if (VF.File ().Archive ())
 +                      info->release = g_strdup (VF.File ().Archive ());
 +              if (VF.File ().Origin ())
 +                      info->origin = g_strdup (VF.File ().Origin ());
 +              if (VF.File ().Component ())
 +                      info->component = g_strdup (VF.File ().Component ());
 +              // also available: Codename, Label
 +              break;
 +      }
 +
 +
 +      pkgCache::PkgFileIterator I = VF.File ();
 +
 +      if (I.IsOk () == false) {
 +              g_set_error (error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_FAILED,
 +                           ("apt DB load failed: package file %s is out of sync."), I.FileName ());
 +              return FALSE;
 +      }
 +
 +      PkgF.Open (I.FileName (), FileFd::ReadOnly, FileFd::Extension);
 +
 +      pkgTagFile TagF (&PkgF);
 +
 +      if (TagF.Jump (Tags, current.FileList ()->Offset) == false) {
 +              if (TagF.Jump (Tags, candidate.FileList ()->Offset) == false)
 +                      return TRUE;
 +      }
 +
 +      if (Tags.FindI ("Installed-Size") > 0)
 +              info->installed_size = Tags.FindI ("Installed-Size")*1024;
 +      else
 +              info->installed_size = 0;
 +
 +      if (current)
 +              info->installed_version = g_strdup (current.VerStr ());
 +      if (candidate)
 +              info->update_version = g_strdup (candidate.VerStr ());
 +
 +      info->section = g_strdup (candidate.Section ());
 +      if (info->installed_version) {
 +              plugin->priv->installed_packages =
 +                      g_list_append (plugin->priv->installed_packages, info);
 +      }
 +
 +      /* no upgrade */
 +      if (g_strcmp0 (info->installed_version, info->update_version) == 0)
 +              g_clear_pointer (&info->update_version, g_free);
 +
 +      if (info->installed_version && info->update_version)
 +              plugin->priv->updatable_packages =
 +                      g_list_append (plugin->priv->updatable_packages, info);
 +
 +      return TRUE;
 +}
 +
 +static gboolean
 +load_apt_db (GsPlugin *plugin, GError **error)
 +{
 +      pkgSourceList *list;
 +      pkgPolicy *policy;
 +      pkgCacheFile cachefile;
 +      pkgCache *cache;
 +      pkgCache::PkgIterator P;
 +      g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&plugin->priv->mutex);
 +
 +      if (plugin->priv->loaded)
 +              return TRUE;
 +
 +      _error->Discard();
 +      cache = cachefile.GetPkgCache();
 +      list = cachefile.GetSourceList();
 +      policy = cachefile.GetPolicy();
 +      if (cache == NULL || _error->PendingError()) {
 +              _error->DumpErrors();
 +              g_set_error (error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_FAILED,
 +                           "apt DB load failed: error while initialising");
 +              return FALSE;
 +      }
 +
 +      for (pkgCache::GrpIterator grp = cache->GrpBegin(); grp != cache->GrpEnd(); grp++) {
 +              P = grp.FindPreferredPkg();
 +              if (P.end())
 +                      continue;
 +              if (!look_at_pkg (P, list, policy, plugin, error))
 +                      return FALSE;
 +      }
 +
 +      /* load filename -> package map into plugin->priv->installed_files */
 +      look_for_files (plugin);
 +
 +      plugin->priv->loaded = TRUE;
 +      return TRUE;
 +}
 +
 +static void
 +unload_apt_db (GsPlugin *plugin)
 +{
 +      g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&plugin->priv->mutex);
 +
 +      plugin->priv->loaded = FALSE;
 +      g_hash_table_remove_all (plugin->priv->package_info);
 +      g_hash_table_remove_all (plugin->priv->installed_files);
 +      g_clear_pointer (&plugin->priv->installed_packages, g_list_free);
 +      g_clear_pointer (&plugin->priv->updatable_packages, g_list_free);
 +}
 +
 +static void
 +get_changelog (GsPlugin *plugin, GsApp *app)
 +{
 +      guint i;
 +      guint status_code;
 +      g_autofree gchar *binary_source = NULL;
 +      g_autofree gchar *changelog_prefix = NULL;
 +      g_autofree gchar *current_version = NULL;
 +      g_autofree gchar *source_prefix = NULL;
 +      g_autofree gchar *update_version = NULL;
 +      g_autofree gchar *uri = NULL;
 +      g_auto(GStrv) lines = NULL;
 +      g_autoptr(GString) details = NULL;
 +      g_autoptr(SoupMessage) msg = NULL;
 +
 +      // Need to know the source and version to download changelog
 +      binary_source = g_strdup (gs_app_get_source_default (app));
 +      current_version = g_strdup (gs_app_get_version (app));
 +      update_version = g_strdup (gs_app_get_update_version (app));
 +      if (binary_source == NULL || update_version == NULL)
 +              return;
 +
 +      if (g_str_has_prefix (binary_source, "lib"))
 +              source_prefix = g_strdup_printf ("lib%c", binary_source[3]);
 +      else
 +              source_prefix = g_strdup_printf ("%c", binary_source[0]);
 +      uri = g_strdup_printf ("http://changelogs.ubuntu.com/changelogs/binary/%s/%s/%s/changelog";, 
source_prefix, binary_source, update_version);
 +
 +      /* download file */
 +      msg = soup_message_new (SOUP_METHOD_GET, uri);
 +      status_code = soup_session_send_message (plugin->soup_session, msg);
 +      if (status_code != SOUP_STATUS_OK) {
 +              g_warning ("Failed to get changelog for %s version %s from changelogs.ubuntu.com: %s", 
binary_source, update_version, soup_status_get_phrase (status_code));
 +              return;
 +      }
 +
 +      // Extract changelog entries newer than our current version
 +      lines = g_strsplit (msg->response_body->data, "\n", -1);
 +      details = g_string_new ("");
 +      for (i = 0; lines[i] != NULL; i++) {
 +              gchar *line = lines[i];
 +              const gchar *version_start, *version_end;
 +              g_autofree gchar *v = NULL;
 +
 +              // First line is in the form "package (version) distribution(s); urgency=urgency"
 +                version_start = strchr (line, '(');
 +                version_end = strchr (line, ')');
 +              if (line[0] == ' ' || version_start == NULL || version_end == NULL || version_end < 
version_start)
 +                      continue;
 +              v = g_strdup_printf ("%.*s", (int) (version_end - version_start - 1), version_start + 1);
 +
 +              // We're only interested in new versions
 +              if (!version_newer (current_version, v))
 +                      break;
 +
 +              g_string_append_printf (details, "%s\n", v);
 +              for (i++; lines[i] != NULL; i++) {
 +                      // Last line is in the form " -- maintainer name <email address>  date"
 +                      if (g_str_has_prefix (lines[i], " -- "))
 +                              break;
 +                      g_string_append_printf (details, "%s\n", lines[i]);
 +              }
 +      }
 +
 +      gs_app_set_update_details (app, details->str);
 +}
 +
 +static gboolean
 +is_official (PackageInfo *info)
 +{
 +      return g_strcmp0 (info->origin, "Ubuntu") == 0;
 +}
 +
 +static gboolean
 +is_open_source (PackageInfo *info)
 +{
 +      const gchar *open_source_components[] = { "main", "universe", NULL };
 +
 +      /* There's no valid apps in the libs section */
 +      return info->component != NULL && g_strv_contains (open_source_components, info->component);
 +}
 +
 +static gchar *
 +get_origin (PackageInfo *info)
 +{
 +      if (!info->origin)
 +              return NULL;
 +
 +      g_autofree gchar *origin_lower = g_strdup (info->origin);
 +      for (int i = 0; origin_lower[i]; ++i)
 +              origin_lower[i] = g_ascii_tolower (origin_lower[i]);
 +
 +      return g_strdup_printf ("%s-%s-%s", origin_lower, info->release, info->component);
 +}
 +
 +gboolean
 +gs_plugin_refine (GsPlugin *plugin,
 +                GList **list,
 +                GsPluginRefineFlags flags,
 +                GCancellable *cancellable,
 +                GError **error)
 +{
 +      GList *link;
 +      GsApp *app;
 +      PackageInfo *info;
 +      const gchar *tmp;
 +      g_autoptr(GMutexLocker) locker = NULL;
 +
 +      if (!load_apt_db (plugin, error))
 +              return FALSE;
 +
 +      locker = g_mutex_locker_new (&plugin->priv->mutex);
 +
 +      for (link = *list; link; link = link->next) {
 +              app = (GsApp *) link->data;
 +              g_autofree gchar *fn = NULL;
 +              g_autofree gchar *origin = NULL;
 +              gchar *package = NULL;
 +
 +              tmp = gs_app_get_id (app);
 +              if (gs_app_get_source_id_default (app) == NULL && tmp) {
 +                      switch (gs_app_get_kind (app)) {
 +                      case AS_APP_KIND_DESKTOP:
 +                              fn = g_strdup_printf ("/usr/share/applications/%s", tmp);
 +                              break;
 +                      case AS_APP_KIND_ADDON:
 +                              fn = g_strdup_printf ("/usr/share/appdata/%s.metainfo.xml", tmp);
 +                              break;
 +                      default:
 +                              break;
 +                      }
 +
 +                      if (!g_file_test (fn, G_FILE_TEST_EXISTS)) {
 +                              g_debug ("ignoring %s as does not exist", fn);
 +                      } else {
 +                              package = (gchar *) g_hash_table_lookup (plugin->priv->installed_files,
 +                                                                       fn);
 +                              if (package != NULL) {
 +                                      gs_app_add_source (app, package);
 +                                      gs_app_set_management_plugin (app, "apt");
 +                              }
 +                      }
 +              }
 +
 +              if (gs_app_get_source_default (app) == NULL)
 +                      continue;
 +
 +              info = (PackageInfo *) g_hash_table_lookup (plugin->priv->package_info, 
gs_app_get_source_default (app));
 +              if (info == NULL)
 +                      continue;
 +
 +              origin = get_origin (info);
 +              gs_app_set_origin (app, origin);
 +              gs_app_set_origin_ui (app, info->origin);
 +
 +              if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN) {
 +                      if (info->installed_version != NULL) {
 +                              if (info->update_version != NULL) {
 +                                      gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
 +                              } else {
 +                                      gs_app_set_state (app, AS_APP_STATE_INSTALLED);
 +                              }
 +                      } else {
 +                              gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
 +                      }
 +              }
 +              if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN) != 0) {
 +                      g_autofree gchar *origin = get_origin (info);
 +                      gs_app_set_origin (app, origin);
 +                      gs_app_set_origin_ui (app, info->origin);
 +              }
 +              if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE) != 0 && gs_app_get_size (app) == 0) {
 +                      gs_app_set_size (app, info->installed_size);
 +              }
 +              if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION) != 0) {
 +                      if (info->installed_version != NULL) {
 +                              gs_app_set_version (app, info->installed_version);
 +                      } else {
 +                              gs_app_set_version (app, info->update_version);
 +                      }
 +                      if (info->update_version != NULL) {
 +                              gs_app_set_update_version (app, info->update_version);
 +                      }
 +              }
-               if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENCE) != 0 && is_open_source(info)) {
++              if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE) != 0 && is_open_source(info)) {
 +                      gs_app_set_license (app, GS_APP_QUALITY_LOWEST, "@LicenseRef-free=" LICENSE_URL);
 +              }
 +
 +              if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS) != 0) {
 +                      get_changelog (plugin, app);
 +              }
 +      }
 +
 +      return TRUE;
 +}
 +
 +static gboolean
 +is_allowed_section (PackageInfo *info)
 +{
 +      const gchar *section_blacklist[] = { "libs", NULL };
 +
 +      /* There's no valid apps in the libs section */
 +      return info->section == NULL || !g_strv_contains (section_blacklist, info->section);
 +}
 +
 +gboolean
 +gs_plugin_add_installed (GsPlugin *plugin,
 +                       GList **list,
 +                       GCancellable *cancellable,
 +                       GError **error)
 +{
 +      GList *link;
 +      g_autoptr(GMutexLocker) locker = NULL;
 +
 +      if (!load_apt_db (plugin, error))
 +              return FALSE;
 +
 +      locker = g_mutex_locker_new (&plugin->priv->mutex);
 +
 +      for (link = plugin->priv->installed_packages; link; link = link->next) {
 +              PackageInfo *info = (PackageInfo *) link->data;
 +              g_autofree gchar *origin = get_origin (info);
 +              g_autoptr(GsApp) app = NULL;
 +
 +              if (!is_allowed_section (info))
 +                      continue;
 +
 +              app = gs_app_new (NULL);
 +              gs_app_set_management_plugin (app, "apt");
 +              gs_app_set_name (app, GS_APP_QUALITY_LOWEST, info->name);
 +              gs_app_add_source (app, info->name);
 +              gs_app_set_origin (app, origin);
 +              gs_app_set_origin_ui (app, info->origin);
 +              gs_app_set_state (app, AS_APP_STATE_INSTALLED);
 +              gs_plugin_add_app (list, app);
 +      }
 +
 +      return TRUE;
 +}
 +
 +typedef struct {
 +      GsPlugin *plugin;
 +      GMainLoop *loop;
 +      GsApp *app;
 +      GList *apps;
 +      gchar **result;
 +} TransactionData;
 +
 +static void
 +transaction_property_changed_cb (GDBusConnection *connection,
 +                               const gchar *sender_name,
 +                               const gchar *object_path,
 +                               const gchar *interface_name,
 +                               const gchar *signal_name,
 +                               GVariant *parameters,
 +                               gpointer user_data)
 +{
 +      TransactionData *data = (TransactionData *) user_data;
 +      const gchar *name;
 +      GList *i;
 +      g_autoptr(GVariant) value = NULL;
 +
 +      if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(sv)"))) {
 +              g_variant_get (parameters, "(&sv)", &name, &value);
 +              if (g_strcmp0 (name, "Progress") == 0) {
 +                      if (data->app)
 +                              gs_plugin_progress_update (data->plugin, data->app, g_variant_get_int32 
(value));
 +                      for (i = data->apps; i != NULL; i = i->next)
 +                              gs_plugin_progress_update (data->plugin, GS_APP (i->data), 
g_variant_get_int32 (value));
 +              }
 +      } else {
 +              g_warning ("Unknown parameters in %s.%s: %s", interface_name, signal_name, 
g_variant_get_type_string (parameters));
 +      }
 +}
 +
 +static void
 +transaction_finished_cb (GDBusConnection *connection,
 +                       const gchar *sender_name,
 +                       const gchar *object_path,
 +                       const gchar *interface_name,
 +                       const gchar *signal_name,
 +                       GVariant *parameters,
 +                       gpointer user_data)
 +{
 +      TransactionData *data = (TransactionData *) user_data;
 +
 +      if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(s)")))
 +              g_variant_get (parameters, "(s)", data->result);
 +      else
 +              g_warning ("Unknown parameters in %s.%s: %s", interface_name, signal_name, 
g_variant_get_type_string (parameters));
 +
 +      g_main_loop_quit (data->loop);
 +}
 +
 +static void
 +notify_unity_launcher (GsApp *app, const gchar *transaction_path)
 +{
 +      UbuntuUnityLauncher *launcher = NULL;
 +
 +      g_return_if_fail (GS_IS_APP (app));
 +      g_return_if_fail (transaction_path);
 +
 +      launcher = ubuntu_unity_launcher_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
 +              G_DBUS_PROXY_FLAGS_NONE,
 +              "com.canonical.Unity.Launcher",
 +              "/com/canonical/Unity/Launcher",
 +              NULL, NULL);
 +
 +      g_return_if_fail (launcher);
 +
 +      ubuntu_unity_launcher_call_add_launcher_item (launcher,
 +              gs_app_get_id (app),
 +              transaction_path,
 +              NULL, NULL, NULL);
 +
 +      g_object_unref (launcher);
 +}
 +
 +static gboolean
 +aptd_transaction (GsPlugin     *plugin,
 +                const gchar  *method,
 +                GsApp        *app,
 +                GList        *apps,
 +                GVariant     *parameters,
 +                GError      **error)
 +{
 +      g_autoptr(GDBusConnection) conn = NULL;
 +      g_autoptr(GVariant) result = NULL;
 +      g_autofree gchar *transaction_path = NULL, *transaction_result = NULL;
 +      g_autoptr(GMainLoop) loop = NULL;
 +      guint property_signal, finished_signal;
 +      TransactionData data;
 +
 +      conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
 +      if (conn == NULL)
 +              return FALSE;
 +
 +      if (parameters == NULL && app != NULL)
 +              parameters = g_variant_new_parsed ("([%s],)", gs_app_get_source_default (app));
 +
 +      result = g_dbus_connection_call_sync (conn,
 +                                            "org.debian.apt",
 +                                            "/org/debian/apt",
 +                                            "org.debian.apt",
 +                                            method,
 +                                            parameters,
 +                                            G_VARIANT_TYPE ("(s)"),
 +                                            G_DBUS_CALL_FLAGS_NONE,
 +                                            -1,
 +                                            NULL,
 +                                            error);
 +      if (result == NULL)
 +              return FALSE;
 +      g_variant_get (result, "(s)", &transaction_path);
 +      g_variant_unref (result);
 +
 +      if (!g_strcmp0(method, "InstallPackages"))
 +              notify_unity_launcher (app, transaction_path);
 +
 +      loop = g_main_loop_new (NULL, FALSE);
 +
 +      data.plugin = plugin;
 +      data.app = app;
 +      data.apps = apps;
 +      data.loop = loop;
 +      data.result = &transaction_result;
 +      property_signal = g_dbus_connection_signal_subscribe (conn,
 +                                                            "org.debian.apt",
 +                                                            "org.debian.apt.transaction",
 +                                                            "PropertyChanged",
 +                                                            transaction_path,
 +                                                            NULL,
 +                                                            G_DBUS_SIGNAL_FLAGS_NONE,
 +                                                            transaction_property_changed_cb,
 +                                                            &data,
 +                                                            NULL);
 +      finished_signal = g_dbus_connection_signal_subscribe (conn,
 +                                                            "org.debian.apt",
 +                                                            "org.debian.apt.transaction",
 +                                                            "Finished",
 +                                                            transaction_path,
 +                                                            NULL,
 +                                                            G_DBUS_SIGNAL_FLAGS_NONE,
 +                                                            transaction_finished_cb,
 +                                                            &data,
 +                                                            NULL);
 +      result = g_dbus_connection_call_sync (conn,
 +                                            "org.debian.apt",
 +                                            transaction_path,
 +                                            "org.debian.apt.transaction",
 +                                            "Run",
 +                                            g_variant_new ("()"),
 +                                            G_VARIANT_TYPE ("()"),
 +                                            G_DBUS_CALL_FLAGS_NONE,
 +                                            -1,
 +                                            NULL,
 +                                            error);
 +      if (result != NULL)
 +              g_main_loop_run (loop);
 +      g_dbus_connection_signal_unsubscribe (conn, property_signal);
 +      g_dbus_connection_signal_unsubscribe (conn, finished_signal);
 +      if (result == NULL)
 +              return FALSE;
 +
 +      if (g_strcmp0 (transaction_result, "exit-success") != 0) {
 +              g_set_error (error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_FAILED,
 +                           "apt transaction returned result %s", transaction_result);
 +              return FALSE;
 +      }
 +
 +      return TRUE;
 +}
 +
 +static gboolean
 +app_is_ours (GsApp *app)
 +{
 +      const gchar *management_plugin = gs_app_get_management_plugin (app);
 +
 +      // FIXME: Since appstream marks all packages as owned by PackageKit and
 +      // we are replacing PackageKit we need to accept those packages
 +      const gchar *our_management_plugins[] = { "PackageKit", "apt", NULL };
 +
 +      return g_strv_contains (our_management_plugins, management_plugin);
 +}
 +
 +gboolean
 +gs_plugin_app_install (GsPlugin *plugin,
 +                     GsApp *app,
 +                     GCancellable *cancellable,
 +                     GError **error)
 +{
 +      g_autofree gchar *filename = NULL;
 +      gboolean success = FALSE;
 +
 +      if (!app_is_ours (app))
 +              return TRUE;
 +
 +      if (gs_app_get_source_default (app) == NULL)
 +              return TRUE;
 +
 +      switch (gs_app_get_state (app)) {
 +      case AS_APP_STATE_AVAILABLE:
 +      case AS_APP_STATE_UPDATABLE:
 +              gs_app_set_state (app, AS_APP_STATE_INSTALLING);
 +              success = aptd_transaction (plugin, "InstallPackages", app, NULL, NULL, error);
 +              break;
 +      case AS_APP_STATE_AVAILABLE_LOCAL:
 +              filename = g_file_get_path (gs_app_get_local_file (app));
 +              gs_app_set_state (app, AS_APP_STATE_INSTALLING);
 +              success = aptd_transaction (plugin, "InstallFile", app, NULL,
 +                                          g_variant_new_parsed ("(%s, true)", filename),
 +                                          error);
 +              break;
 +      default:
 +              g_set_error (error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_FAILED,
 +                           "do not know how to install app in state %s",
 +                           as_app_state_to_string (gs_app_get_state (app)));
 +              return FALSE;
 +      }
 +
 +
 +      if (success)
 +              gs_app_set_state (app, AS_APP_STATE_INSTALLED);
 +      else
 +              gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
 +
 +      return success;
 +}
 +
 +gboolean
 +gs_plugin_app_remove (GsPlugin *plugin,
 +                    GsApp *app,
 +                    GCancellable *cancellable,
 +                    GError **error)
 +{
 +      if (!app_is_ours (app))
 +              return TRUE;
 +
 +      if (gs_app_get_source_default (app) == NULL)
 +              return TRUE;
 +
 +      gs_app_set_state (app, AS_APP_STATE_REMOVING);
 +      if (aptd_transaction (plugin, "RemovePackages", app, NULL, NULL, error))
 +              gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
 +      else {
 +              gs_app_set_state (app, AS_APP_STATE_INSTALLED);
 +              return FALSE;
 +      }
 +
 +      return TRUE;
 +}
 +
 +gboolean
 +gs_plugin_refresh (GsPlugin *plugin,
 +                 guint cache_age,
 +                 GsPluginRefreshFlags flags,
 +                 GCancellable *cancellable,
 +                 GError **error)
 +{
 +      if ((flags & GS_PLUGIN_REFRESH_FLAGS_UPDATES) == 0)
 +              return TRUE;
 +
 +      if (!aptd_transaction (plugin, "UpdateCache", NULL, NULL, NULL, error))
 +              return FALSE;
 +
 +      unload_apt_db (plugin);
 +
 +      gs_plugin_updates_changed (plugin);
 +
 +      return TRUE;
 +}
 +
 +gboolean
 +gs_plugin_add_updates (GsPlugin *plugin,
 +                       GList **list,
 +                       GCancellable *cancellable,
 +                       GError **error)
 +{
 +      GList *link;
 +      g_autoptr(GMutexLocker) locker = NULL;
 +
 +      if (!load_apt_db (plugin, error))
 +              return FALSE;
 +
 +      locker = g_mutex_locker_new (&plugin->priv->mutex);
 +
 +      for (link = plugin->priv->updatable_packages; link; link = link->next) {
 +              PackageInfo *info = (PackageInfo *) link->data;
 +              g_autoptr(GsApp) app = NULL;
 +
 +              if (!is_allowed_section (info))
 +                      continue;
 +
 +              app = gs_app_new (NULL);
 +              gs_app_set_management_plugin (app, "apt");
 +              gs_app_set_name (app, GS_APP_QUALITY_LOWEST, info->name);
 +              gs_app_set_kind (app, AS_APP_KIND_GENERIC);
 +              gs_app_add_source (app, info->name);
 +              gs_plugin_add_app (list, app);
 +      }
 +
 +      return TRUE;
 +}
 +
 +static void
 +set_list_state (GList      *apps,
 +              AsAppState  state)
 +{
 +      GList *i;
 +      guint j;
 +      GsApp *app_i;
 +      GsApp *app_j;
 +      GPtrArray *related;
 +
 +      for (i = apps; i != NULL; i = i->next) {
 +              app_i = GS_APP (i->data);
 +              gs_app_set_state (app_i, state);
 +
 +              if (g_strcmp0 (gs_app_get_id (app_i), "os-update.virtual") == 0) {
 +                      related = gs_app_get_related (app_i);
 +
 +                      for (j = 0; j < related->len; j++) {
 +                              app_j = GS_APP (g_ptr_array_index (related, j));
 +                              gs_app_set_state (app_j, state);
 +                      }
 +              }
 +      }
 +}
 +
 +gboolean
 +gs_plugin_update (GsPlugin      *plugin,
 +                GList         *apps,
 +                GCancellable  *cancellable,
 +                GError       **error)
 +{
 +      GList *i;
 +      GsApp *app_i;
 +
 +      for (i = apps; i != NULL; i = i->next) {
 +              app_i = GS_APP (i->data);
 +
 +              if (g_strcmp0 (gs_app_get_id (app_i), "os-update.virtual") == 0) {
 +                      set_list_state (apps, AS_APP_STATE_INSTALLING);
 +
 +                      if (aptd_transaction (plugin, "UpgradeSystem", NULL, apps, g_variant_new_parsed 
("(false,)"), error)) {
 +                              set_list_state (apps, AS_APP_STATE_INSTALLED);
 +
 +                              unload_apt_db (plugin);
 +
 +                              gs_plugin_updates_changed (plugin);
 +
 +                              return TRUE;
 +                      } else {
 +                              set_list_state (apps, AS_APP_STATE_UPDATABLE_LIVE);
 +
 +                              return FALSE;
 +                      }
 +              }
 +      }
 +
 +      return TRUE;
 +}
 +
 +gboolean
 +gs_plugin_update_app (GsPlugin *plugin,
 +                    GsApp *app,
 +                    GCancellable *cancellable,
 +                    GError **error)
 +{
 +      GPtrArray *apps;
 +      GsApp *app_i;
 +      guint i;
 +      GVariantBuilder builder;
 +
 +      if (g_strcmp0 (gs_app_get_id (app), "os-update.virtual") == 0) {
 +              apps = gs_app_get_related (app);
 +
 +              g_variant_builder_init (&builder, G_VARIANT_TYPE ("(as)"));
 +              g_variant_builder_open (&builder, G_VARIANT_TYPE ("as"));
 +
 +              gs_app_set_state (app, AS_APP_STATE_INSTALLING);
 +
 +              for (i = 0; i < apps->len; i++) {
 +                      app_i = GS_APP (g_ptr_array_index (apps, i));
 +                      gs_app_set_state (app_i, AS_APP_STATE_INSTALLING);
 +                      g_variant_builder_add (&builder, "s", gs_app_get_source_default (app_i));
 +              }
 +
 +              g_variant_builder_close (&builder);
 +
 +              if (aptd_transaction (plugin, "UpgradePackages", app, NULL, g_variant_builder_end (&builder), 
error)) {
 +                      gs_app_set_state (app, AS_APP_STATE_INSTALLED);
 +
 +                      for (i = 0; i < apps->len; i++)
 +                              gs_app_set_state (GS_APP (g_ptr_array_index (apps, i)), 
AS_APP_STATE_INSTALLED);
 +
 +                      unload_apt_db (plugin);
 +
 +                      gs_plugin_updates_changed (plugin);
 +              } else {
 +                      gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
 +
 +                      for (i = 0; i < apps->len; i++)
 +                              gs_app_set_state (GS_APP (g_ptr_array_index (apps, i)), 
AS_APP_STATE_UPDATABLE_LIVE);
 +
 +                      return FALSE;
 +              }
 +      } else if (app_is_ours (app)) {
 +              gs_app_set_state (app, AS_APP_STATE_INSTALLING);
 +
 +              if (aptd_transaction (plugin, "UpgradePackages", app, NULL, NULL, error)) {
 +                      gs_app_set_state (app, AS_APP_STATE_INSTALLED);
 +
 +                      unload_apt_db (plugin);
 +
 +                      gs_plugin_updates_changed (plugin);
 +              } else {
 +                      gs_app_set_state (app, AS_APP_STATE_UPDATABLE_LIVE);
 +
 +                      return FALSE;
 +              }
 +      }
 +
 +      return TRUE;
 +}
 +
 +gboolean
 +gs_plugin_launch (GsPlugin *plugin,
 +                GsApp *app,
 +                GCancellable *cancellable,
 +                GError **error)
 +{
 +      if (!app_is_ours (app))
 +              return TRUE;
 +
 +      return gs_plugin_app_launch (plugin, app, error);
 +}
 +
 +/* vim: set noexpandtab ts=8 sw=8: */
diff --cc src/plugins/gs-plugin-dummy.c
index 213c17f,059e590..4ec457b
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@@ -140,34 -284,64 +284,64 @@@ gs_plugin_app_install (GsPlugin *plugin
  }
  
  /**
-  * gs_plugin_refine:
 - * gs_plugin_app_update:
++ * gs_plugin_update_app:
   */
  gboolean
- gs_plugin_refine (GsPlugin *plugin,
-                 GList **list,
-                 GsPluginRefineFlags flags,
-                 GCancellable *cancellable,
-                 GError **error)
 -gs_plugin_app_update (GsPlugin *plugin,
++gs_plugin_update_app (GsPlugin *plugin,
+                     GsApp *app,
+                     GCancellable *cancellable,
+                     GError **error)
  {
-       GsApp *app;
-       GList *l;
- 
-       for (l = *list; l != NULL; l = l->next) {
-               app = GS_APP (l->data);
-               if (gs_app_get_name (app) == NULL) {
-                       if (g_strcmp0 (gs_app_get_id (app), "gnome-boxes") == 0) {
-                               gs_app_set_license (app, GS_APP_QUALITY_NORMAL,
-                                                   "GPL-2.0+");
-                               gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
-                                                "Boxes");
-                               gs_app_set_url (app, AS_URL_KIND_HOMEPAGE,
-                                               "http://www.gimp.org/";);
-                               gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
-                                                   "A simple GNOME 3 application "
-                                                   "to access remote or virtual systems");
-                               gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
-                                                       "<p>long description!</p>");
-                       }
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
+               return TRUE;
+ 
+       /* always fail */
+       g_set_error_literal (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_NO_NETWORK,
+                            "no network connection is available");
+       return FALSE;
+ }
+ 
+ /**
+  * gs_plugin_refine_app:
+  */
+ gboolean
+ gs_plugin_refine_app (GsPlugin *plugin,
+                     GsApp *app,
+                     GsPluginRefineFlags flags,
+                     GCancellable *cancellable,
+                     GError **error)
+ {
+       /* default */
+       if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0 ||
+           g_strcmp0 (gs_app_get_id (app), "mate-spell.desktop") == 0 ||
+           g_strcmp0 (gs_app_get_id (app), "zeus.desktop") == 0) {
+               if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN)
+                       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       }
+ 
+       /* license */
+       if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE) {
+               if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0 ||
+                   g_strcmp0 (gs_app_get_id (app), "zeus.desktop") == 0)
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-2.0+");
+       }
+ 
+       /* homepage */
+       if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL) {
+               if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0) {
+                       gs_app_set_url (app, AS_URL_KIND_HOMEPAGE,
+                                       "http://www.test.org/";);
+               }
+       }
+ 
+       /* description */
+       if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION) {
+               if (g_strcmp0 (gs_app_get_id (app), "chiron.desktop") == 0) {
+                       gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
+                                               "long description!");
                }
        }
  
@@@ -268,6 -462,75 +462,65 @@@ gs_plugin_add_distro_upgrades (GsPlugi
  }
  
  /**
+  * gs_plugin_refresh:
+  */
+ gboolean
+ gs_plugin_refresh (GsPlugin *plugin,
+                  guint cache_age,
+                  GsPluginRefreshFlags flags,
+                  GCancellable *cancellable,
+                  GError **error)
+ {
+       guint delay_ms = 100;
+       g_autoptr(GsApp) app = gs_app_new (NULL);
+ 
+       /* each one takes more time */
+       if (flags & GS_PLUGIN_REFRESH_FLAGS_METADATA)
+               delay_ms += 3000;
+       if (flags & GS_PLUGIN_REFRESH_FLAGS_PAYLOAD)
+               delay_ms += 5000;
+ 
+       /* do delay */
+       return gs_plugin_dummy_delay (plugin, app, delay_ms, cancellable, error);
+ }
+ 
+ /**
+  * gs_plugin_app_upgrade_download:
+  */
+ gboolean
+ gs_plugin_app_upgrade_download (GsPlugin *plugin, GsApp *app,
+                               GCancellable *cancellable, GError **error)
+ {
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
+               return TRUE;
+ 
+       g_debug ("starting download");
+       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+       if (!gs_plugin_dummy_delay (plugin, app, 5000, cancellable, error)) {
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
+       gs_app_set_state (app, AS_APP_STATE_UPDATABLE);
+       return TRUE;
+ }
+ 
+ /**
+  * gs_plugin_app_upgrade_trigger:
+  */
+ gboolean
+ gs_plugin_app_upgrade_trigger (GsPlugin *plugin, GsApp *app,
+                              GCancellable *cancellable, GError **error)
+ {
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
+               return TRUE;
+ 
+       /* NOP */
+       return TRUE;
+ }
+ 
+ /**
 - * gs_plugin_offline_update_cancel:
 - */
 -gboolean
 -gs_plugin_offline_update_cancel (GsPlugin *plugin, GsApp *app,
 -                       GCancellable *cancellable, GError **error)
 -{
 -      return TRUE;
 -}
 -
 -/**
   * gs_plugin_review_submit:
   */
  gboolean
diff --cc src/plugins/gs-plugin-fwupd.c
index 38294d9,b968005..05ef7ac
--- a/src/plugins/gs-plugin-fwupd.c
+++ b/src/plugins/gs-plugin-fwupd.c
@@@ -940,11 -907,11 +916,11 @@@ gs_plugin_fwupd_install (GsPlugin *plug
        gboolean offline = FALSE;
  
        /* only process this app if was created by this plugin */
-       if (g_strcmp0 (gs_app_get_management_plugin (app), "fwupd") != 0)
+       if (g_strcmp0 (gs_app_get_management_plugin (app), plugin->name) != 0)
                return TRUE;
  
 -      filename = gs_app_get_source_id_default (app);
 -      if (filename == NULL) {
 +      /* not set */
 +      if (gs_app_get_local_file (app) == NULL) {
                g_set_error (error,
                             GS_PLUGIN_ERROR,
                             GS_PLUGIN_ERROR_FAILED,
diff --cc src/plugins/gs-plugin-packagekit.c
index 2ce848d,189e1f1..555d1b3
--- a/src/plugins/gs-plugin-packagekit.c
+++ b/src/plugins/gs-plugin-packagekit.c
@@@ -325,11 -323,9 +323,11 @@@ gs_plugin_app_install (GsPlugin *plugin
        ProgressData data;
        const gchar *package_id;
        guint i, j;
 +      g_autoptr(PkError) error_code = NULL;
 +      g_autofree gchar *local_filename = NULL;
 +      g_auto(GStrv) package_ids = NULL;
-       g_autoptr(GPtrArray) array_package_ids = NULL;
        g_autoptr(PkResults) results = NULL;
+       g_autoptr(GPtrArray) array_package_ids = NULL;
 -      g_auto(GStrv) package_ids = NULL;
  
        data.app = app;
        data.plugin = plugin;
@@@ -431,11 -430,19 +432,18 @@@
                                                         cancellable,
                                                         gs_plugin_packagekit_progress_cb, &data,
                                                         error);
-               if (results == NULL)
+               if (results == NULL) {
+                       gs_plugin_packagekit_convert_gerror (error);
+                       gs_app_set_state_recover (app);
                        return FALSE;
+               }
+ 
+               /* state is known */
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ 
                break;
        case AS_APP_STATE_AVAILABLE_LOCAL:
 -              package_id = gs_app_get_metadata_item (app, "packagekit::local-filename");
 -              if (package_id == NULL) {
 +              if (gs_app_get_local_file (app) == NULL) {
                        g_set_error_literal (error,
                                             GS_PLUGIN_ERROR,
                                             GS_PLUGIN_ERROR_NOT_SUPPORTED,
@@@ -450,11 -456,17 +458,17 @@@
                                                      cancellable,
                                                      gs_plugin_packagekit_progress_cb, &data,
                                                      error);
-               if (results == NULL)
+               if (results == NULL) {
+                       gs_plugin_packagekit_convert_gerror (error);
+                       gs_app_set_state_recover (app);
                        return FALSE;
+               }
+ 
+               /* state is known */
+               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
  
                /* get the new icon from the package */
 -              gs_app_set_metadata (app, "packagekit::local-filename", NULL);
 +              gs_app_set_local_file (app, NULL);
                gs_app_set_icon (app, NULL);
                gs_app_set_pixbuf (app, NULL);
                break;
diff --cc src/plugins/gs-plugin-provenance.c
index bb360d3,dd424a4..a692345
--- a/src/plugins/gs-plugin-provenance.c
+++ b/src/plugins/gs-plugin-provenance.c
@@@ -136,11 -157,9 +158,11 @@@ gs_plugin_refine_app (GsPlugin *plugin
  
        /* simple case */
        origin = gs_app_get_origin (app);
 +      g_debug ("prov: considering %s", gs_app_get_id (app));
        if (origin != NULL && gs_utils_strv_fnmatch (sources, origin)) {
                gs_app_add_quirk (app, AS_APP_QUIRK_PROVENANCE);
 +              g_debug ("prov: %s", gs_app_to_string (app));
-               return;
+               return TRUE;
        }
  
        /* this only works for packages */
diff --cc src/plugins/gs-plugin-systemd-updates.c
index ab14388,e433ca2..eb1b4ec
--- a/src/plugins/gs-plugin-systemd-updates.c
+++ b/src/plugins/gs-plugin-systemd-updates.c
@@@ -172,14 -164,17 +164,17 @@@ gs_plugin_update (GsPlugin *plugin
  }
  
  /**
 - * gs_plugin_offline_update_cancel:
 + * gs_plugin_update_cancel:
   */
  gboolean
 -gs_plugin_offline_update_cancel (GsPlugin *plugin,
 -                               GsApp *app,
 -                               GCancellable *cancellable,
 -                               GError **error)
 +gs_plugin_update_cancel (GsPlugin *plugin,
 +                       GsApp *app,
 +                       GCancellable *cancellable,
 +                       GError **error)
  {
+       /* only process this app if was created by this plugin */
+       if (g_strcmp0 (gs_app_get_management_plugin (app), "packagekit") != 0)
+               return TRUE;
        return pk_offline_cancel (NULL, error);
  }
  
diff --cc src/plugins/gs-plugin-ubuntu-reviews.c
index 6f40572,21a9e1f..11cd1af
--- a/src/plugins/gs-plugin-ubuntu-reviews.c
+++ b/src/plugins/gs-plugin-ubuntu-reviews.c
@@@ -89,9 -80,7 +89,10 @@@ gs_plugin_initialize (GsPlugin *plugin
  const gchar **
  gs_plugin_order_after (GsPlugin *plugin)
  {
 -      static const gchar *deps[] = { NULL };
 +      static const gchar *deps[] = {
 +              "appstream",
++              "odrs",
 +              NULL };
        return deps;
  }
  
@@@ -829,367 -665,21 +830,360 @@@ refine_reviews (GsPlugin *plugin, GsAp
  }
  
  gboolean
- gs_plugin_refine (GsPlugin *plugin,
-                 GList **list,
-                 GsPluginRefineFlags flags,
-                 GCancellable *cancellable,
-                 GError **error)
- {
-       GList *l;
-       gboolean ret = TRUE;
- 
-       for (l = *list; l != NULL; l = l->next) {
-               GsApp *app = GS_APP (l->data);
- 
-               if ((flags & (GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING | 
GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS)) != 0) {
-                       if (!refine_rating (plugin, app, cancellable, error))
-                               return FALSE;
-               }
-               if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS) != 0) {
-                       if (!refine_reviews (plugin, app, cancellable, error))
-                               return FALSE;
-               }
+ gs_plugin_refine_app (GsPlugin *plugin,
+                     GsApp *app,
+                     GsPluginRefineFlags flags,
+                     GCancellable *cancellable,
+                     GError **error)
+ {
+       if ((flags & (GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING | GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS)) 
!= 0) {
 -              if (!refine_rating (plugin, app, error))
++              if (!refine_rating (plugin, app, cancellable, error))
+                       return FALSE;
+       }
+       if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS) != 0) {
 -              if (!refine_reviews (plugin, app, error))
++              if (!refine_reviews (plugin, app, cancellable, error))
+                       return FALSE;
        }
  
-       return ret;
+       return TRUE;
  }
  
 +static void
 +add_string_member (JsonBuilder *builder, const gchar *name, const gchar *value)
 +{
 +      json_builder_set_member_name (builder, name);
 +      json_builder_add_string_value (builder, value);
 +}
 +
 +static void
 +add_int_member (JsonBuilder *builder, const gchar *name, gint64 value)
 +{
 +      json_builder_set_member_name (builder, name);
 +      json_builder_add_int_value (builder, value);
 +}
 +
 +gboolean
 +gs_plugin_review_submit (GsPlugin *plugin,
 +                       GsApp *app,
 +                       GsReview *review,
 +                       GCancellable *cancellable,
 +                       GError **error)
 +{
 +      GsPluginPrivate *priv = plugin->priv;
 +      gint rating;
 +      gint n_stars;
 +      g_autofree gchar *os_id = NULL, *os_ubuntu_codename = NULL, *language = NULL, *architecture = NULL;
 +      g_autoptr(JsonBuilder) request = NULL;
 +      guint status_code;
 +      g_autoptr(JsonParser) result = NULL;
 +
 +      /* Load database once */
 +      if (g_once_init_enter (&plugin->priv->db_loaded)) {
 +              gboolean ret = load_database (plugin, cancellable, error);
 +              g_once_init_leave (&plugin->priv->db_loaded, TRUE);
 +              if (!ret)
 +                      return FALSE;
 +      }
 +
 +      if (!get_ubuntuone_credentials (plugin, TRUE, error))
 +              return FALSE;
 +
 +      /* Ubuntu reviews require a summary and description - just make one up for now */
 +      rating = gs_review_get_rating (review);
 +      if (rating > 80)
 +              n_stars = 5;
 +      else if (rating > 60)
 +              n_stars = 4;
 +      else if (rating > 40)
 +              n_stars = 3;
 +      else if (rating > 20)
 +              n_stars = 2;
 +      else
 +              n_stars = 1;
 +
 +      os_id = gs_os_release_get_id (error);
 +      if (os_id == NULL)
 +              return FALSE;
 +      os_ubuntu_codename = gs_os_release_get ("UBUNTU_CODENAME", error);
 +      if (os_ubuntu_codename == NULL)
 +              return FALSE;
 +
 +      language = get_language (plugin);
 +
 +      // FIXME: Need to get Apt::Architecture configuration value from APT
 +      architecture = g_strdup ("amd64");
 +
 +      /* Create message for reviews.ubuntu.com */
 +      request = json_builder_new ();
 +      json_builder_begin_object (request);
 +      add_string_member (request, "package_name", gs_app_get_source_default (app));
 +      add_string_member (request, "summary", gs_review_get_summary (review));
 +      add_string_member (request, "review_text", gs_review_get_text (review));
 +      add_string_member (request, "language", language);
 +      add_string_member (request, "origin", os_id);
 +      add_string_member (request, "distroseries", os_ubuntu_codename);
 +      add_string_member (request, "version", gs_review_get_version (review));
 +      add_int_member (request, "rating", n_stars);
 +      add_string_member (request, "arch_tag", architecture);
 +      json_builder_end_object (request);
 +
 +      if (!send_review_request (plugin, SOUP_METHOD_POST, "/api/1.0/reviews/",
 +                                request, TRUE,
 +                                &status_code, &result, cancellable, error))
 +              return FALSE;
 +
 +      if (status_code != SOUP_STATUS_OK) {
 +              g_set_error (error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_FAILED,
 +                           "Failed to submit review, server returned status code %d",
 +                           status_code);
 +              return FALSE;
 +      }
 +
 +      // Extract new fields from posted review
 +      parse_review (review, priv->consumer_key, json_parser_get_root (result));
 +
 +      return TRUE;
 +}
 +
 +gboolean
 +gs_plugin_review_report (GsPlugin *plugin,
 +                       GsApp *app,
 +                       GsReview *review,
 +                       GCancellable *cancellable,
 +                       GError **error)
 +{
 +      const gchar *review_id;
 +      g_autofree gchar *reason = NULL;
 +      g_autofree gchar *text = NULL;
 +      g_autofree gchar *path = NULL;
 +      guint status_code;
 +
 +      /* Can only modify Ubuntu reviews */
 +      review_id = gs_review_get_metadata_item (review, "ubuntu-id");
 +      if (review_id == NULL)
 +              return TRUE;
 +
 +      if (!get_ubuntuone_credentials (plugin, TRUE, error))
 +              return FALSE;
 +
 +      /* Create message for reviews.ubuntu.com */
 +      reason = soup_uri_encode ("FIXME: gnome-software", NULL);
 +      text = soup_uri_encode ("FIXME: gnome-software", NULL);
 +      path = g_strdup_printf ("/api/1.0/reviews/%s/recommendations/?reason=%s&text=%s",
 +                              review_id, reason, text);
 +      if (!send_review_request (plugin, SOUP_METHOD_POST, path,
 +                                NULL, TRUE,
 +                                &status_code, NULL, cancellable, error))
 +              return FALSE;
 +
 +      if (status_code != SOUP_STATUS_CREATED) {
 +              g_set_error (error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_FAILED,
 +                           "Failed to report review, server returned status code %d",
 +                           status_code);
 +              return FALSE;
 +      }
 +
 +      gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
 +      return TRUE;
 +}
 +
 +static gboolean
 +set_review_usefulness (GsPlugin *plugin,
 +                     const gchar *review_id,
 +                     gboolean is_useful,
 +                     GCancellable *cancellable,
 +                     GError **error)
 +{
 +      g_autofree gchar *path = NULL;
 +      guint status_code;
 +
 +      if (!get_ubuntuone_credentials (plugin, TRUE, error))
 +              return FALSE;
 +
 +      /* Create message for reviews.ubuntu.com */
 +      path = g_strdup_printf ("/api/1.0/reviews/%s/recommendations/?useful=%s",
 +                              review_id, is_useful ? "True" : "False");
 +      if (!send_review_request (plugin, SOUP_METHOD_POST, path,
 +                                NULL, TRUE,
 +                                &status_code, NULL, cancellable, error))
 +              return FALSE;
 +
 +      if (status_code != SOUP_STATUS_CREATED) {
 +              g_set_error (error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_FAILED,
 +                           "Got status code %d from reviews.ubuntu.com",
 +                           status_code);
 +              return FALSE;
 +      }
 +
 +      return TRUE;
 +}
 +
 +gboolean
 +gs_plugin_review_upvote (GsPlugin *plugin,
 +                       GsApp *app,
 +                       GsReview *review,
 +                       GCancellable *cancellable,
 +                       GError **error)
 +{
 +      const gchar *review_id;
 +
 +      /* Can only modify Ubuntu reviews */
 +      review_id = gs_review_get_metadata_item (review, "ubuntu-id");
 +      if (review_id == NULL)
 +              return TRUE;
 +
 +      if (!set_review_usefulness (plugin, review_id, TRUE, cancellable, error))
 +              return FALSE;
 +
 +      gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
 +      return TRUE;
 +}
 +
 +gboolean
 +gs_plugin_review_downvote (GsPlugin *plugin,
 +                         GsApp *app,
 +                         GsReview *review,
 +                         GCancellable *cancellable,
 +                         GError **error)
 +{
 +      const gchar *review_id;
 +
 +      /* Can only modify Ubuntu reviews */
 +      review_id = gs_review_get_metadata_item (review, "ubuntu-id");
 +      if (review_id == NULL)
 +              return TRUE;
 +
 +      if (!set_review_usefulness (plugin, review_id, FALSE, cancellable, error))
 +              return FALSE;
 +
 +      gs_review_add_flags (review, GS_REVIEW_FLAG_VOTED);
 +      return TRUE;
 +}
 +
 +gboolean
 +gs_plugin_review_remove (GsPlugin *plugin,
 +                       GsApp *app,
 +                       GsReview *review,
 +                       GCancellable *cancellable,
 +                       GError **error)
 +{
 +      const gchar *review_id;
 +      g_autofree gchar *path = NULL;
 +      guint status_code;
 +
 +      /* Can only modify Ubuntu reviews */
 +      review_id = gs_review_get_metadata_item (review, "ubuntu-id");
 +      if (review_id == NULL)
 +              return TRUE;
 +
 +      /* Create message for reviews.ubuntu.com */
 +      path = g_strdup_printf ("/api/1.0/reviews/delete/%s/", review_id);
 +      if (!send_review_request (plugin, SOUP_METHOD_POST, path,
 +                                NULL, TRUE,
 +                                &status_code, NULL, cancellable, error))
 +              return FALSE;
 +
 +      if (status_code != SOUP_STATUS_OK) {
 +              g_set_error (error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_FAILED,
 +                           "Failed to remove review, server returned status code %d",
 +                           status_code);
 +              return FALSE;
 +      }
 +
 +      return TRUE;
 +}
 +
 +typedef struct {
 +      gchar           *package_name;
 +      gint             rating;
 +} PopularEntry;
 +
 +static gint
 +popular_sqlite_cb (void *data,
 +                 gint argc,
 +                 gchar **argv,
 +                 gchar **col_name)
 +{
 +      GList **list = data;
 +      PopularEntry *entry;
 +
 +      entry = g_slice_new (PopularEntry);
 +      entry->package_name = g_strdup (argv[0]);
 +      entry->rating = get_rating (g_ascii_strtoll (argv[1], NULL, 10), g_ascii_strtoll (argv[2], NULL, 10), 
g_ascii_strtoll (argv[3], NULL, 10), g_ascii_strtoll (argv[4], NULL, 10), g_ascii_strtoll (argv[5], NULL, 
10));
 +      *list = g_list_prepend (*list, entry);
 +
 +      return 0;
 +}
 +
 +static gint
 +compare_popular_entry (gconstpointer a, gconstpointer b)
 +{
 +      PopularEntry *ea = a, *eb = b;
 +      return eb->rating - ea->rating;
 +}
 +
 +static void
 +free_popular_entry (gpointer data)
 +{
 +      PopularEntry *entry = data;
 +      g_free (entry->package_name);
 +      g_slice_free (PopularEntry, entry);
 +}
 +
 +gboolean
 +gs_plugin_add_popular (GsPlugin *plugin,
 +                     GList **list,
 +                     GCancellable *cancellable,
 +                     GError **error)
 +{
 +      gint result;
 +      GList *entries = NULL, *link;
 +      char *error_msg = NULL;
 +
 +      /* Load database once */
 +      if (g_once_init_enter (&plugin->priv->db_loaded)) {
 +              gboolean ret = load_database (plugin, cancellable, error);
 +              g_once_init_leave (&plugin->priv->db_loaded, TRUE);
 +              if (!ret)
 +                      return FALSE;
 +      }
 +
 +      result = sqlite3_exec (plugin->priv->db,
 +                             "SELECT package_name, one_star_count, two_star_count, three_star_count, 
four_star_count, five_star_count FROM review_stats",
 +                             popular_sqlite_cb,
 +                             &entries,
 +                             &error_msg);
 +      if (result != SQLITE_OK) {
 +              g_set_error (error,
 +                           GS_PLUGIN_ERROR,
 +                           GS_PLUGIN_ERROR_FAILED,
 +                           "SQL error: %s", error_msg);
 +              sqlite3_free (error_msg);
 +              return FALSE;
 +      }
 +
 +      entries = g_list_sort (entries, compare_popular_entry);
 +      for (link = entries; link; link = link->next) {
 +              PopularEntry *entry = link->data;
 +              g_autoptr(GsApp) app = NULL;
 +
 +              /* Need four stars to show */
 +              if (entry->rating < 80)
 +                      break;
 +
 +              app = gs_app_new (NULL);
 +              gs_app_add_source (app, entry->package_name);
 +              gs_plugin_add_app (list, app);
 +      }
 +      g_list_free_full (entries, free_popular_entry);
 +
 +      return TRUE;
 +}


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