summaryrefslogtreecommitdiffstats
path: root/plugins/fwupd/gs-plugin-fwupd.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugins/fwupd/gs-plugin-fwupd.c1132
1 files changed, 1132 insertions, 0 deletions
diff --git a/plugins/fwupd/gs-plugin-fwupd.c b/plugins/fwupd/gs-plugin-fwupd.c
new file mode 100644
index 0000000..d032640
--- /dev/null
+++ b/plugins/fwupd/gs-plugin-fwupd.c
@@ -0,0 +1,1132 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2013-2018 Richard Hughes <richard@hughsie.com>
+ * Copyright (C) 2015-2018 Kalev Lember <klember@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <config.h>
+
+#include <fwupd.h>
+#include <fcntl.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <gnome-software.h>
+
+#include "gs-fwupd-app.h"
+#include "gs-metered.h"
+
+/*
+ * SECTION:
+ * Queries for new firmware and schedules it to be installed as required.
+ *
+ * This plugin calls UpdatesChanged() if any updatable devices are
+ * added or removed or if a device has been updated live.
+ */
+
+struct GsPluginData {
+ FwupdClient *client;
+ GsApp *app_current;
+ GsApp *cached_origin;
+};
+
+static void
+gs_plugin_fwupd_error_convert (GError **perror)
+{
+ GError *error = perror != NULL ? *perror : NULL;
+
+ /* not set */
+ if (error == NULL)
+ return;
+
+ /* already correct */
+ if (error->domain == GS_PLUGIN_ERROR)
+ return;
+
+ /* this are allowed for low-level errors */
+ if (gs_utils_error_convert_gio (perror))
+ return;
+
+ /* this are allowed for low-level errors */
+ if (gs_utils_error_convert_gdbus (perror))
+ return;
+
+ /* custom to this plugin */
+ if (error->domain == FWUPD_ERROR) {
+ switch (error->code) {
+ case FWUPD_ERROR_ALREADY_PENDING:
+ case FWUPD_ERROR_INVALID_FILE:
+ case FWUPD_ERROR_NOT_SUPPORTED:
+ error->code = GS_PLUGIN_ERROR_NOT_SUPPORTED;
+ break;
+ case FWUPD_ERROR_AUTH_FAILED:
+ error->code = GS_PLUGIN_ERROR_AUTH_INVALID;
+ break;
+ case FWUPD_ERROR_SIGNATURE_INVALID:
+ error->code = GS_PLUGIN_ERROR_NO_SECURITY;
+ break;
+ case FWUPD_ERROR_AC_POWER_REQUIRED:
+ error->code = GS_PLUGIN_ERROR_AC_POWER_REQUIRED;
+ break;
+#if FWUPD_CHECK_VERSION(1,2,10)
+ case FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW:
+ error->code = GS_PLUGIN_ERROR_BATTERY_LEVEL_TOO_LOW;
+ break;
+#endif
+ default:
+ error->code = GS_PLUGIN_ERROR_FAILED;
+ break;
+ }
+ } else {
+ g_warning ("can't reliably fixup error from domain %s",
+ g_quark_to_string (error->domain));
+ error->code = GS_PLUGIN_ERROR_FAILED;
+ }
+ error->domain = GS_PLUGIN_ERROR;
+}
+
+void
+gs_plugin_initialize (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
+ priv->client = fwupd_client_new ();
+
+ /* set name of MetaInfo file */
+ gs_plugin_set_appstream_id (plugin, "org.gnome.Software.Plugin.Fwupd");
+}
+
+void
+gs_plugin_destroy (GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ if (priv->cached_origin != NULL)
+ g_object_unref (priv->cached_origin);
+ g_object_unref (priv->client);
+}
+
+void
+gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app)
+{
+ if (gs_app_get_kind (app) == AS_APP_KIND_FIRMWARE)
+ gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+}
+
+static void
+gs_plugin_fwupd_changed_cb (FwupdClient *client, GsPlugin *plugin)
+{
+}
+
+static void
+gs_plugin_fwupd_device_changed_cb (FwupdClient *client,
+ FwupdDevice *dev,
+ GsPlugin *plugin)
+{
+ /* limit number of UI refreshes */
+ if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED)) {
+ g_debug ("%s changed (not supported) so ignoring",
+ fwupd_device_get_id (dev));
+ return;
+ }
+
+ /* If the flag is set the device matches something in the
+ * metadata as therefor is worth refreshing the update list */
+ g_debug ("%s changed (supported) so reloading",
+ fwupd_device_get_id (dev));
+ gs_plugin_updates_changed (plugin);
+}
+
+static void
+gs_plugin_fwupd_notify_percentage_cb (GObject *object,
+ GParamSpec *pspec,
+ GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+
+ /* nothing in progress */
+ if (priv->app_current == NULL) {
+ g_debug ("fwupd percentage: %u%%",
+ fwupd_client_get_percentage (priv->client));
+ return;
+ }
+ g_debug ("fwupd percentage for %s: %u%%",
+ gs_app_get_unique_id (priv->app_current),
+ fwupd_client_get_percentage (priv->client));
+ gs_app_set_progress (priv->app_current,
+ fwupd_client_get_percentage (priv->client));
+}
+
+static void
+gs_plugin_fwupd_notify_status_cb (GObject *object,
+ GParamSpec *pspec,
+ GsPlugin *plugin)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+
+ /* nothing in progress */
+ if (priv->app_current == NULL) {
+ g_debug ("fwupd status: %s",
+ fwupd_status_to_string (fwupd_client_get_status (priv->client)));
+ return;
+ }
+
+ g_debug ("fwupd status for %s: %s",
+ gs_app_get_unique_id (priv->app_current),
+ fwupd_status_to_string (fwupd_client_get_status (priv->client)));
+ switch (fwupd_client_get_status (priv->client)) {
+ case FWUPD_STATUS_DECOMPRESSING:
+ case FWUPD_STATUS_DEVICE_RESTART:
+ case FWUPD_STATUS_DEVICE_WRITE:
+ case FWUPD_STATUS_DEVICE_VERIFY:
+ gs_app_set_state (priv->app_current, AS_APP_STATE_INSTALLING);
+ break;
+ case FWUPD_STATUS_IDLE:
+ g_clear_object (&priv->app_current);
+ break;
+ default:
+ break;
+ }
+}
+
+static gchar *
+gs_plugin_fwupd_get_file_checksum (const gchar *filename,
+ GChecksumType checksum_type,
+ GError **error)
+{
+ gsize len;
+ g_autofree gchar *data = NULL;
+
+ if (!g_file_get_contents (filename, &data, &len, error)) {
+ gs_utils_error_convert_gio (error);
+ return NULL;
+ }
+ return g_compute_checksum_for_data (checksum_type, (const guchar *)data, len);
+}
+
+gboolean
+gs_plugin_setup (GsPlugin *plugin, GCancellable *cancellable, GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(SoupSession) soup_session = NULL;
+
+#if FWUPD_CHECK_VERSION(1,4,5)
+ g_autoptr(GError) error_local = NULL;
+ /* send our implemented feature set */
+ if (!fwupd_client_set_feature_flags (priv->client,
+ FWUPD_FEATURE_FLAG_UPDATE_ACTION |
+ FWUPD_FEATURE_FLAG_DETACH_ACTION,
+ cancellable, &error_local))
+ g_debug ("Failed to set front-end features: %s", error_local->message);
+
+ /* we know the runtime daemon version now */
+ fwupd_client_set_user_agent_for_package (priv->client, PACKAGE_NAME, PACKAGE_VERSION);
+ if (!fwupd_client_ensure_networking (priv->client, error)) {
+ gs_plugin_fwupd_error_convert (error);
+ g_prefix_error (error, "Failed to setup networking: ");
+ return FALSE;
+ }
+ g_object_get (priv->client, "soup-session", &soup_session, NULL);
+#else
+ g_autofree gchar *user_agent = NULL;
+ /* use a custom user agent to provide the fwupd version */
+ user_agent = fwupd_build_user_agent (PACKAGE_NAME, PACKAGE_VERSION);
+ soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent,
+ SOUP_SESSION_TIMEOUT, 10,
+ NULL);
+ soup_session_remove_feature_by_type (soup_session, SOUP_TYPE_CONTENT_DECODER);
+#endif
+
+ /* use for gnome-software downloads */
+ gs_plugin_set_soup_session (plugin, soup_session);
+
+ /* add source */
+ priv->cached_origin = gs_app_new (gs_plugin_get_name (plugin));
+ gs_app_set_kind (priv->cached_origin, AS_APP_KIND_SOURCE);
+ gs_app_set_bundle_kind (priv->cached_origin, AS_BUNDLE_KIND_CABINET);
+
+ /* add the source to the plugin cache which allows us to match the
+ * unique ID to a GsApp when creating an event */
+ gs_plugin_cache_add (plugin,
+ gs_app_get_unique_id (priv->cached_origin),
+ priv->cached_origin);
+
+ /* register D-Bus errors */
+ fwupd_error_quark ();
+ g_signal_connect (priv->client, "changed",
+ G_CALLBACK (gs_plugin_fwupd_changed_cb), plugin);
+ g_signal_connect (priv->client, "device-added",
+ G_CALLBACK (gs_plugin_fwupd_device_changed_cb), plugin);
+ g_signal_connect (priv->client, "device-removed",
+ G_CALLBACK (gs_plugin_fwupd_device_changed_cb), plugin);
+ g_signal_connect (priv->client, "device-changed",
+ G_CALLBACK (gs_plugin_fwupd_device_changed_cb), plugin);
+ g_signal_connect (priv->client, "notify::percentage",
+ G_CALLBACK (gs_plugin_fwupd_notify_percentage_cb), plugin);
+ g_signal_connect (priv->client, "notify::status",
+ G_CALLBACK (gs_plugin_fwupd_notify_status_cb), plugin);
+ return TRUE;
+}
+
+static GsApp *
+gs_plugin_fwupd_new_app_from_device (GsPlugin *plugin, FwupdDevice *dev)
+{
+ FwupdRelease *rel = fwupd_device_get_release_default (dev);
+ GsApp *app;
+ g_autofree gchar *id = NULL;
+ g_autoptr(AsIcon) icon = NULL;
+
+ /* older versions of fwups didn't record this for historical devices */
+ if (fwupd_release_get_appstream_id (rel) == NULL)
+ return NULL;
+
+ /* get from cache */
+ id = as_utils_unique_id_build (AS_APP_SCOPE_SYSTEM,
+ AS_BUNDLE_KIND_UNKNOWN,
+ NULL, /* origin */
+ AS_APP_KIND_FIRMWARE,
+ fwupd_release_get_appstream_id (rel),
+ NULL);
+ app = gs_plugin_cache_lookup (plugin, id);
+ if (app == NULL) {
+ app = gs_app_new (id);
+ gs_plugin_cache_add (plugin, id, app);
+ }
+
+ /* default stuff */
+ gs_app_set_kind (app, AS_APP_KIND_FIRMWARE);
+ gs_app_set_bundle_kind (app, AS_BUNDLE_KIND_CABINET);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE);
+ gs_app_add_quirk (app, GS_APP_QUIRK_DO_NOT_AUTO_UPDATE);
+ gs_app_set_management_plugin (app, "fwupd");
+ gs_app_add_category (app, "System");
+ gs_fwupd_app_set_device_id (app, fwupd_device_get_id (dev));
+
+ /* create icon */
+ icon = as_icon_new ();
+ as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
+ as_icon_set_name (icon, "application-x-firmware");
+ gs_app_add_icon (app, icon);
+ gs_fwupd_app_set_from_device (app, dev);
+ gs_fwupd_app_set_from_release (app, rel);
+
+ if (fwupd_release_get_appstream_id (rel) != NULL)
+ gs_app_set_id (app, fwupd_release_get_appstream_id (rel));
+
+ /* the same as we have already */
+ if (g_strcmp0 (fwupd_device_get_version (dev),
+ fwupd_release_get_version (rel)) == 0) {
+ g_warning ("same firmware version as installed");
+ }
+
+ return app;
+}
+
+static gchar *
+gs_plugin_fwupd_build_device_id (FwupdDevice *dev)
+{
+ g_autofree gchar *tmp = g_strdup (fwupd_device_get_id (dev));
+ g_strdelimit (tmp, "/", '_');
+ return g_strdup_printf ("org.fwupd.%s.device", tmp);
+}
+
+static GsApp *
+gs_plugin_fwupd_new_app_from_device_raw (GsPlugin *plugin, FwupdDevice *device)
+{
+ GPtrArray *icons;
+ g_autofree gchar *id = NULL;
+ g_autoptr(GsApp) app = NULL;
+
+ /* create a GsApp based on the device, not the release */
+ id = gs_plugin_fwupd_build_device_id (device);
+ app = gs_app_new (id);
+ gs_app_set_kind (app, AS_APP_KIND_FIRMWARE);
+ gs_app_set_scope (app, AS_APP_SCOPE_SYSTEM);
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE);
+ gs_app_add_quirk (app, GS_APP_QUIRK_DO_NOT_AUTO_UPDATE);
+ gs_app_set_version (app, fwupd_device_get_version (device));
+ gs_app_set_name (app, GS_APP_QUALITY_LOWEST, fwupd_device_get_name (device));
+ gs_app_set_summary (app, GS_APP_QUALITY_LOWEST, fwupd_device_get_summary (device));
+ gs_app_set_description (app, GS_APP_QUALITY_LOWEST, fwupd_device_get_description (device));
+ gs_app_set_origin (app, fwupd_device_get_vendor (device));
+ gs_fwupd_app_set_device_id (app, fwupd_device_get_id (device));
+ gs_app_set_management_plugin (app, "fwupd");
+
+ /* create icon */
+ icons = fwupd_device_get_icons (device);
+ for (guint j = 0; j < icons->len; j++) {
+ const gchar *icon = g_ptr_array_index (icons, j);
+ g_autoptr(AsIcon) icon_tmp = as_icon_new ();
+ if (g_str_has_prefix (icon, "/")) {
+ as_icon_set_kind (icon_tmp, AS_ICON_KIND_LOCAL);
+ as_icon_set_filename (icon_tmp, icon);
+ } else {
+ as_icon_set_kind (icon_tmp, AS_ICON_KIND_STOCK);
+ as_icon_set_name (icon_tmp, icon);
+ }
+ gs_app_add_icon (app, icon_tmp);
+ }
+ return g_steal_pointer (&app);
+}
+
+static GsApp *
+gs_plugin_fwupd_new_app (GsPlugin *plugin, FwupdDevice *dev, GError **error)
+{
+ FwupdRelease *rel = fwupd_device_get_release_default (dev);
+ GPtrArray *checksums;
+ const gchar *update_uri;
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *filename_cache = NULL;
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GsApp) app = NULL;
+
+ /* update unsupported */
+ app = gs_plugin_fwupd_new_app_from_device (plugin, dev);
+ if (gs_app_get_state (app) != AS_APP_STATE_UPDATABLE_LIVE) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "%s [%s] cannot be updated",
+ gs_app_get_name (app), gs_app_get_id (app));
+ return NULL;
+ }
+
+ /* some missing */
+ if (gs_app_get_id (app) == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "fwupd: No id for firmware");
+ return NULL;
+ }
+ if (gs_app_get_version (app) == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "fwupd: No version! for %s!", gs_app_get_id (app));
+ return NULL;
+ }
+ if (gs_app_get_update_version (app) == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NOT_SUPPORTED,
+ "fwupd: No update-version! for %s!", gs_app_get_id (app));
+ return NULL;
+ }
+ checksums = fwupd_release_get_checksums (rel);
+ if (checksums->len == 0) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_NO_SECURITY,
+ "%s [%s] (%s) has no checksums, ignoring as unsafe",
+ gs_app_get_name (app),
+ gs_app_get_id (app),
+ gs_app_get_update_version (app));
+ return NULL;
+ }
+ update_uri = fwupd_release_get_uri (rel);
+ if (update_uri == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "no location available for %s [%s]",
+ gs_app_get_name (app), gs_app_get_id (app));
+ return NULL;
+ }
+
+ /* does the firmware already exist in the cache? */
+ basename = g_path_get_basename (update_uri);
+ filename_cache = gs_utils_get_cache_filename ("fwupd",
+ basename,
+ GS_UTILS_CACHE_FLAG_NONE,
+ error);
+ if (filename_cache == NULL)
+ return NULL;
+
+ /* delete the file if the checksum does not match */
+ if (g_file_test (filename_cache, G_FILE_TEST_EXISTS)) {
+ const gchar *checksum_tmp = NULL;
+ g_autofree gchar *checksum = NULL;
+
+ /* we can migrate to something better than SHA1 when the LVFS
+ * starts producing metadata with multiple hash types */
+ checksum_tmp = fwupd_checksum_get_by_kind (checksums,
+ G_CHECKSUM_SHA1);
+ if (checksum_tmp == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "No valid checksum for %s",
+ filename_cache);
+ }
+ checksum = gs_plugin_fwupd_get_file_checksum (filename_cache,
+ G_CHECKSUM_SHA1,
+ error);
+ if (checksum == NULL)
+ return NULL;
+ if (g_strcmp0 (checksum_tmp, checksum) != 0) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "%s does not match checksum, expected %s got %s",
+ filename_cache, checksum_tmp, checksum);
+ g_unlink (filename_cache);
+ return NULL;
+ }
+ }
+
+ /* already downloaded, so overwrite */
+ if (g_file_test (filename_cache, G_FILE_TEST_EXISTS))
+ gs_app_set_size_download (app, 0);
+
+ /* actually add the application */
+ file = g_file_new_for_path (filename_cache);
+ gs_app_set_local_file (app, file);
+ return g_steal_pointer (&app);
+}
+
+gboolean
+gs_plugin_add_updates_historical (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GsApp) app = NULL;
+ g_autoptr(FwupdDevice) dev = NULL;
+
+ /* get historical updates */
+ dev = fwupd_client_get_results (priv->client,
+ FWUPD_DEVICE_ID_ANY,
+ cancellable,
+ &error_local);
+ if (dev == NULL) {
+ if (g_error_matches (error_local,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOTHING_TO_DO))
+ return TRUE;
+ if (g_error_matches (error_local,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_FOUND))
+ return TRUE;
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ gs_plugin_fwupd_error_convert (error);
+ return FALSE;
+ }
+
+ /* parse */
+ app = gs_plugin_fwupd_new_app_from_device (plugin, dev);
+ if (app == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "failed to build result for %s",
+ fwupd_device_get_id (dev));
+ return FALSE;
+ }
+ gs_app_list_add (list, app);
+ return TRUE;
+}
+
+gboolean
+gs_plugin_add_updates (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GPtrArray) devices = NULL;
+
+ /* get current list of updates */
+ devices = fwupd_client_get_devices (priv->client, cancellable, &error_local);
+ if (devices == NULL) {
+ if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO) ||
+ g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) ||
+ g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) {
+ g_debug ("no devices (%s)", error_local->message);
+ return TRUE;
+ }
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ gs_plugin_fwupd_error_convert (error);
+ return FALSE;
+ }
+ for (guint i = 0; i < devices->len; i++) {
+ FwupdDevice *dev = g_ptr_array_index (devices, i);
+ FwupdRelease *rel_newest;
+ g_autoptr(GError) error_local2 = NULL;
+ g_autoptr(GPtrArray) rels = NULL;
+ g_autoptr(GsApp) app = NULL;
+
+ /* locked device that needs unlocking */
+ if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_LOCKED)) {
+ app = gs_plugin_fwupd_new_app_from_device_raw (plugin, dev);
+ gs_fwupd_app_set_is_locked (app, TRUE);
+ gs_app_list_add (list, app);
+ continue;
+ }
+
+ /* not going to have results, so save a D-Bus round-trip */
+ if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED))
+ continue;
+
+ /* get the releases for this device and filter for validity */
+ rels = fwupd_client_get_upgrades (priv->client,
+ fwupd_device_get_id (dev),
+ cancellable, &error_local2);
+ if (rels == NULL) {
+ if (g_error_matches (error_local2,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOTHING_TO_DO)) {
+ g_debug ("no updates for %s", fwupd_device_get_id (dev));
+ continue;
+ }
+ if (g_error_matches (error_local2,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED)) {
+ g_debug ("not supported for %s", fwupd_device_get_id (dev));
+ continue;
+ }
+ g_warning ("failed to get upgrades for %s: %s]",
+ fwupd_device_get_id (dev),
+ error_local2->message);
+ continue;
+ }
+
+ /* normal device update */
+ rel_newest = g_ptr_array_index (rels, 0);
+ fwupd_device_add_release (dev, rel_newest);
+ app = gs_plugin_fwupd_new_app (plugin, dev, &error_local2);
+ if (app == NULL) {
+ g_debug ("%s", error_local2->message);
+ continue;
+ }
+
+ /* add update descriptions for all releases inbetween */
+ if (rels->len > 1) {
+ g_autoptr(GString) update_desc = g_string_new (NULL);
+ for (guint j = 0; j < rels->len; j++) {
+ FwupdRelease *rel = g_ptr_array_index (rels, j);
+ g_autofree gchar *desc = NULL;
+ if (fwupd_release_get_description (rel) == NULL)
+ continue;
+ desc = as_markup_convert (fwupd_release_get_description (rel),
+ AS_MARKUP_CONVERT_FORMAT_SIMPLE,
+ NULL);
+ if (desc == NULL)
+ continue;
+ g_string_append_printf (update_desc,
+ "Version %s:\n%s\n\n",
+ fwupd_release_get_version (rel),
+ desc);
+ }
+ if (update_desc->len > 2) {
+ g_string_truncate (update_desc, update_desc->len - 2);
+ gs_app_set_update_details (app, update_desc->str);
+ }
+ }
+ gs_app_list_add (list, app);
+ }
+ return TRUE;
+}
+
+static gboolean
+gs_plugin_fwupd_refresh_remote (GsPlugin *plugin,
+ FwupdRemote *remote,
+ guint cache_age,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ GChecksumType checksum_kind;
+ const gchar *url_sig = NULL;
+ const gchar *url = NULL;
+ g_autoptr(GError) error_local = NULL;
+ g_autofree gchar *basename = NULL;
+ g_autofree gchar *basename_sig = NULL;
+ g_autofree gchar *cache_id = NULL;
+ g_autofree gchar *checksum = NULL;
+ g_autofree gchar *filename = NULL;
+ g_autofree gchar *filename_sig = NULL;
+ g_autoptr(GBytes) data = NULL;
+ g_autoptr(GsApp) app_dl = gs_app_new (gs_plugin_get_name (plugin));
+
+ /* sanity check */
+ if (fwupd_remote_get_filename_cache_sig (remote) == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "remote %s has no cache signature",
+ fwupd_remote_get_id (remote));
+ return FALSE;
+ }
+
+ /* check cache age */
+ if (cache_age > 0) {
+ guint64 age = fwupd_remote_get_age (remote);
+ guint tmp = age < G_MAXUINT ? (guint) age : G_MAXUINT;
+ if (tmp < cache_age) {
+ g_debug ("fwupd remote is only %u seconds old, so ignoring refresh",
+ tmp);
+ return TRUE;
+ }
+ }
+
+ /* download the signature first, it's smaller */
+ cache_id = g_strdup_printf ("fwupd/remotes.d/%s", fwupd_remote_get_id (remote));
+ basename_sig = g_path_get_basename (fwupd_remote_get_filename_cache_sig (remote));
+ filename_sig = gs_utils_get_cache_filename (cache_id, basename_sig,
+ GS_UTILS_CACHE_FLAG_WRITEABLE,
+ error);
+
+ /* download the signature first, it's smaller */
+ url_sig = fwupd_remote_get_metadata_uri_sig (remote);
+ gs_app_set_summary_missing (app_dl,
+ /* TRANSLATORS: status text when downloading */
+ _("Downloading firmware update signature…"));
+ data = gs_plugin_download_data (plugin, app_dl, url_sig, cancellable, error);
+ if (data == NULL) {
+ gs_utils_error_add_origin_id (error, priv->cached_origin);
+ return FALSE;
+ }
+
+ /* is the signature hash the same as we had before? */
+ checksum_kind = fwupd_checksum_guess_kind (fwupd_remote_get_checksum (remote));
+ checksum = g_compute_checksum_for_data (checksum_kind,
+ (const guchar *) g_bytes_get_data (data, NULL),
+ g_bytes_get_size (data));
+ if (g_strcmp0 (checksum, fwupd_remote_get_checksum (remote)) == 0) {
+ g_debug ("signature of %s is unchanged", url_sig);
+ return TRUE;
+ }
+
+ /* save to a file */
+ g_debug ("saving new remote signature to %s:", filename_sig);
+ if (!g_file_set_contents (filename_sig,
+ g_bytes_get_data (data, NULL),
+ (guint) g_bytes_get_size (data),
+ &error_local)) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_WRITE_FAILED,
+ "Failed to save firmware signature: %s",
+ error_local->message);
+ return FALSE;
+ }
+
+ /* download the payload and save to file */
+ basename = g_path_get_basename (fwupd_remote_get_filename_cache (remote));
+ filename = gs_utils_get_cache_filename (cache_id, basename,
+ GS_UTILS_CACHE_FLAG_WRITEABLE,
+ error);
+ if (filename == NULL)
+ return FALSE;
+ g_debug ("saving new firmware metadata to %s:", filename);
+ gs_app_set_summary_missing (app_dl,
+ /* TRANSLATORS: status text when downloading */
+ _("Downloading firmware update metadata…"));
+ url = fwupd_remote_get_metadata_uri (remote);
+ if (!gs_plugin_download_file (plugin, app_dl, url, filename,
+ cancellable, error)) {
+ gs_utils_error_add_origin_id (error, priv->cached_origin);
+ return FALSE;
+ }
+
+ /* phew, lets send all this to fwupd */
+ if (!fwupd_client_update_metadata (priv->client,
+ fwupd_remote_get_id (remote),
+ filename,
+ filename_sig,
+ cancellable,
+ error)) {
+ gs_plugin_fwupd_error_convert (error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean
+gs_plugin_refresh (GsPlugin *plugin,
+ guint cache_age,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GError) error_local = NULL;
+ g_autoptr(GPtrArray) remotes = NULL;
+
+ /* get the list of enabled remotes */
+ remotes = fwupd_client_get_remotes (priv->client, cancellable, &error_local);
+ if (remotes == NULL) {
+ g_debug ("No remotes found: %s", error_local ? error_local->message : "Unknown error");
+ if (g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO) ||
+ g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) ||
+ g_error_matches (error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND))
+ return TRUE;
+ g_propagate_error (error, g_steal_pointer (&error_local));
+ gs_plugin_fwupd_error_convert (error);
+ return FALSE;
+ }
+ for (guint i = 0; i < remotes->len; i++) {
+ FwupdRemote *remote = g_ptr_array_index (remotes, i);
+ if (!fwupd_remote_get_enabled (remote))
+ continue;
+ if (fwupd_remote_get_kind (remote) == FWUPD_REMOTE_KIND_LOCAL)
+ continue;
+ if (!gs_plugin_fwupd_refresh_remote (plugin, remote, cache_age,
+ cancellable, error))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean
+gs_plugin_fwupd_install (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ const gchar *device_id;
+ FwupdInstallFlags install_flags = 0;
+ GFile *local_file;
+ g_autofree gchar *filename = NULL;
+ gboolean downloaded_to_cache = FALSE;
+ g_autoptr(FwupdDevice) dev = NULL;
+ g_autoptr(GError) error_local = NULL;
+
+ /* not set */
+ local_file = gs_app_get_local_file (app);
+ if (local_file == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "not enough data for fwupd %s",
+ filename);
+ return FALSE;
+ }
+
+ /* file does not yet exist */
+ filename = g_file_get_path (local_file);
+ if (!g_file_query_exists (local_file, cancellable)) {
+ const gchar *uri = gs_fwupd_app_get_update_uri (app);
+ gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+ if (!gs_plugin_download_file (plugin, app, uri, filename,
+ cancellable, error))
+ return FALSE;
+ downloaded_to_cache = TRUE;
+ }
+
+ /* limit to single device? */
+ device_id = gs_fwupd_app_get_device_id (app);
+ if (device_id == NULL)
+ device_id = FWUPD_DEVICE_ID_ANY;
+
+ /* set the last object */
+ g_set_object (&priv->app_current, app);
+
+ /* only offline supported */
+ if (gs_app_get_metadata_item (app, "fwupd::OnlyOffline") != NULL)
+ install_flags |= FWUPD_INSTALL_FLAG_OFFLINE;
+
+ gs_app_set_state (app, AS_APP_STATE_INSTALLING);
+ if (!fwupd_client_install (priv->client, device_id,
+ filename, install_flags,
+ cancellable, error)) {
+ gs_plugin_fwupd_error_convert (error);
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+
+ /* delete the file from the cache */
+ gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+ if (downloaded_to_cache) {
+ if (!g_file_delete (local_file, cancellable, error))
+ return FALSE;
+ }
+
+ /* does the device have an update message */
+ dev = fwupd_client_get_device_by_id (priv->client, device_id,
+ cancellable, &error_local);
+ if (dev == NULL) {
+ /* NOTE: this is probably entirely fine; some devices do not
+ * re-enumerate until replugged manually or the machine is
+ * rebooted -- and the metadata to know that is only available
+ * in a too-new-to-depend-on fwupd version */
+ g_debug ("failed to find device after install: %s", error_local->message);
+ } else {
+ if (fwupd_device_get_update_message (dev) != NULL) {
+ g_autoptr(AsScreenshot) ss = as_screenshot_new ();
+
+#if FWUPD_CHECK_VERSION(1,4,5)
+ /* image is optional */
+ if (fwupd_device_get_update_image (dev) != NULL) {
+ g_autoptr(AsImage) im = as_image_new ();
+ as_image_set_kind (im, AS_IMAGE_KIND_SOURCE);
+ as_image_set_url (im, fwupd_device_get_update_image (dev));
+ as_screenshot_add_image (ss, im);
+ }
+#endif
+
+ /* caption is required */
+ as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_DEFAULT);
+ as_screenshot_set_caption (ss, NULL, fwupd_device_get_update_message (dev));
+ gs_app_set_action_screenshot (app, ss);
+
+ /* require the dialog */
+ gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_USER_ACTION);
+ }
+ }
+
+ /* success */
+ return TRUE;
+}
+
+static gboolean
+gs_plugin_fwupd_modify_source (GsPlugin *plugin, GsApp *app, gboolean enabled,
+ GCancellable *cancellable, GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ const gchar *remote_id = gs_app_get_metadata_item (app, "fwupd::remote-id");
+ if (remote_id == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "not enough data for fwupd %s",
+ gs_app_get_unique_id (app));
+ return FALSE;
+ }
+ gs_app_set_state (app, enabled ?
+ AS_APP_STATE_INSTALLING : AS_APP_STATE_REMOVING);
+ if (!fwupd_client_modify_remote (priv->client,
+ remote_id,
+ "Enabled",
+ enabled ? "true" : "false",
+ cancellable,
+ error)) {
+ gs_app_set_state_recover (app);
+ return FALSE;
+ }
+ gs_app_set_state (app, enabled ?
+ AS_APP_STATE_INSTALLED : AS_APP_STATE_AVAILABLE);
+ return TRUE;
+}
+
+gboolean
+gs_plugin_app_install (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),
+ gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* source -> remote */
+ if (gs_app_get_kind (app) == AS_APP_KIND_SOURCE) {
+ return gs_plugin_fwupd_modify_source (plugin, app, TRUE,
+ cancellable, error);
+ }
+
+ /* firmware */
+ return gs_plugin_fwupd_install (plugin, app, cancellable, error);
+}
+
+gboolean
+gs_plugin_app_remove (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),
+ gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* source -> remote */
+ return gs_plugin_fwupd_modify_source (plugin, app, FALSE, cancellable, error);
+}
+
+gboolean
+gs_plugin_download_app (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GFile *local_file;
+ g_autofree gchar *filename = NULL;
+
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app),
+ gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* not set */
+ local_file = gs_app_get_local_file (app);
+ if (local_file == NULL) {
+ g_set_error (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_FAILED,
+ "not enough data for fwupd %s",
+ filename);
+ return FALSE;
+ }
+
+ /* file does not yet exist */
+ filename = g_file_get_path (local_file);
+ if (!g_file_query_exists (local_file, cancellable)) {
+ const gchar *uri = gs_fwupd_app_get_update_uri (app);
+
+ if (!gs_plugin_has_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE)) {
+ g_autoptr(GError) error_local = NULL;
+
+ if (!gs_metered_block_app_on_download_scheduler (app, cancellable, &error_local)) {
+ g_warning ("Failed to block on download scheduler: %s",
+ error_local->message);
+ g_clear_error (&error_local);
+ }
+ }
+
+ if (!gs_plugin_download_file (plugin, app, uri, filename,
+ cancellable, error))
+ return FALSE;
+ }
+ gs_app_set_size_download (app, 0);
+ return TRUE;
+}
+
+gboolean
+gs_plugin_update_app (GsPlugin *plugin,
+ GsApp *app,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ /* only process this app if was created by this plugin */
+ if (g_strcmp0 (gs_app_get_management_plugin (app),
+ gs_plugin_get_name (plugin)) != 0)
+ return TRUE;
+
+ /* locked devices need unlocking, rather than installing */
+ if (gs_fwupd_app_get_is_locked (app)) {
+ const gchar *device_id;
+ device_id = gs_fwupd_app_get_device_id (app);
+ if (device_id == NULL) {
+ g_set_error_literal (error,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_INVALID_FORMAT,
+ "not enough data for fwupd unlock");
+ return FALSE;
+ }
+ if (!fwupd_client_unlock (priv->client, device_id,
+ cancellable, error)) {
+ gs_plugin_fwupd_error_convert (error);
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /* update means install */
+ if (!gs_plugin_fwupd_install (plugin, app, cancellable, error)) {
+ gs_plugin_fwupd_error_convert (error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean
+gs_plugin_file_to_app (GsPlugin *plugin,
+ GsAppList *list,
+ GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autofree gchar *content_type = NULL;
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GPtrArray) devices = NULL;
+ const gchar *mimetypes[] = {
+ "application/vnd.ms-cab-compressed",
+ NULL };
+
+ /* does this match any of the mimetypes we support */
+ content_type = gs_utils_get_content_type (file, cancellable, error);
+ if (content_type == NULL)
+ return FALSE;
+ if (!g_strv_contains (mimetypes, content_type))
+ return TRUE;
+
+ /* get results */
+ filename = g_file_get_path (file);
+ devices = fwupd_client_get_details (priv->client,
+ filename,
+ cancellable,
+ error);
+ if (devices == NULL) {
+ gs_plugin_fwupd_error_convert (error);
+ return FALSE;
+ }
+ for (guint i = 0; i < devices->len; i++) {
+ FwupdDevice *dev = g_ptr_array_index (devices, i);
+ g_autoptr(GsApp) app = NULL;
+
+ /* create each app */
+ app = gs_plugin_fwupd_new_app_from_device (plugin, dev);
+
+ /* we *might* have no update view for local files */
+ gs_app_set_version (app, gs_app_get_update_version (app));
+ gs_app_set_description (app, GS_APP_QUALITY_LOWEST,
+ gs_app_get_update_details (app));
+ gs_app_list_add (list, app);
+ }
+ return TRUE;
+}
+
+gboolean
+gs_plugin_add_sources (GsPlugin *plugin,
+ GsAppList *list,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GsPluginData *priv = gs_plugin_get_data (plugin);
+ g_autoptr(GPtrArray) remotes = NULL;
+
+ /* find all remotes */
+ remotes = fwupd_client_get_remotes (priv->client, cancellable, error);
+ if (remotes == NULL)
+ return FALSE;
+ for (guint i = 0; i < remotes->len; i++) {
+ FwupdRemote *remote = g_ptr_array_index (remotes, i);
+ g_autofree gchar *id = NULL;
+ g_autoptr(GsApp) app = NULL;
+
+ /* ignore these, they're built in */
+ if (fwupd_remote_get_kind (remote) != FWUPD_REMOTE_KIND_DOWNLOAD)
+ continue;
+
+ /* create something that we can use to enable/disable */
+ id = g_strdup_printf ("org.fwupd.%s.remote", fwupd_remote_get_id (remote));
+ app = gs_app_new (id);
+ gs_app_set_kind (app, AS_APP_KIND_SOURCE);
+ gs_app_set_scope (app, AS_APP_SCOPE_SYSTEM);
+ gs_app_set_state (app, fwupd_remote_get_enabled (remote) ?
+ AS_APP_STATE_INSTALLED : AS_APP_STATE_AVAILABLE);
+ gs_app_add_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE);
+ gs_app_set_name (app, GS_APP_QUALITY_LOWEST,
+ fwupd_remote_get_title (remote));
+#if FWUPD_CHECK_VERSION(1,0,7)
+ gs_app_set_agreement (app, fwupd_remote_get_agreement (remote));
+#endif
+ gs_app_set_url (app, AS_URL_KIND_HOMEPAGE,
+ fwupd_remote_get_metadata_uri (remote));
+ gs_app_set_metadata (app, "fwupd::remote-id",
+ fwupd_remote_get_id (remote));
+ gs_app_set_management_plugin (app, "fwupd");
+ gs_app_list_add (list, app);
+ }
+ return TRUE;
+}