diff options
Diffstat (limited to '')
-rw-r--r-- | plugins/fwupd/gs-fwupd-app.c | 309 | ||||
-rw-r--r-- | plugins/fwupd/gs-fwupd-app.h | 31 | ||||
-rw-r--r-- | plugins/fwupd/gs-plugin-fwupd.c | 1414 | ||||
-rw-r--r-- | plugins/fwupd/gs-plugin-fwupd.h | 22 | ||||
-rw-r--r-- | plugins/fwupd/gs-self-test.c | 111 | ||||
-rw-r--r-- | plugins/fwupd/meson.build | 54 | ||||
-rw-r--r-- | plugins/fwupd/org.gnome.Software.Plugin.Fwupd.metainfo.xml.in | 12 | ||||
-rwxr-xr-x | plugins/fwupd/tests/build-cab.sh | 4 | ||||
-rw-r--r-- | plugins/fwupd/tests/chiron-0.2.cab | bin | 0 -> 34376 bytes | |||
-rw-r--r-- | plugins/fwupd/tests/firmware.dfu | bin | 0 -> 32784 bytes | |||
-rw-r--r-- | plugins/fwupd/tests/firmware.dfu.asc | 11 | ||||
-rw-r--r-- | plugins/fwupd/tests/firmware.metainfo.xml | 30 |
12 files changed, 1998 insertions, 0 deletions
diff --git a/plugins/fwupd/gs-fwupd-app.c b/plugins/fwupd/gs-fwupd-app.c new file mode 100644 index 0000000..c27f5a5 --- /dev/null +++ b/plugins/fwupd/gs-fwupd-app.c @@ -0,0 +1,309 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2017 Richard Hughes <richard@hughsie.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include <string.h> +#include <glib/gi18n.h> + +#include "gs-fwupd-app.h" + +const gchar * +gs_fwupd_app_get_device_id (GsApp *app) +{ + return gs_app_get_metadata_item (app, "fwupd::DeviceID"); +} + +const gchar * +gs_fwupd_app_get_update_uri (GsApp *app) +{ + return gs_app_get_metadata_item (app, "fwupd::UpdateID"); +} + +gboolean +gs_fwupd_app_get_is_locked (GsApp *app) +{ + GVariant *tmp = gs_app_get_metadata_variant (app, "fwupd::IsLocked"); + if (tmp == NULL) + return FALSE; + return g_variant_get_boolean (tmp); +} + +void +gs_fwupd_app_set_device_id (GsApp *app, const gchar *device_id) +{ + gs_app_set_metadata (app, "fwupd::DeviceID", device_id); +} + +void +gs_fwupd_app_set_update_uri (GsApp *app, const gchar *update_uri) +{ + gs_app_set_metadata (app, "fwupd::UpdateID", update_uri); +} + +void +gs_fwupd_app_set_is_locked (GsApp *app, gboolean is_locked) +{ + g_autoptr(GVariant) tmp = g_variant_new_boolean (is_locked); + gs_app_set_metadata_variant (app, "fwupd::IsLocked", tmp); +} + +void +gs_fwupd_app_set_from_device (GsApp *app, FwupdDevice *dev) +{ + GPtrArray *guids; + + /* something can be done */ + if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE)) + gs_app_set_state (app, GS_APP_STATE_UPDATABLE_LIVE); + + /* only can be applied in systemd-offline */ + if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_ONLY_OFFLINE)) + gs_app_set_metadata (app, "fwupd::OnlyOffline", ""); + + + /* reboot required to apply update */ + if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT)) + gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_REBOOT); + + /* is removable */ + if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL)) + gs_app_add_quirk (app, GS_APP_QUIRK_REMOVABLE_HARDWARE); + + guids = fwupd_device_get_guids (dev); + if (guids->len > 0) { + g_autofree gchar *guid_str = NULL; + g_auto(GStrv) tmp = g_new0 (gchar *, guids->len + 1); + for (guint i = 0; i < guids->len; i++) + tmp[i] = g_strdup (g_ptr_array_index (guids, i)); + guid_str = g_strjoinv (",", tmp); + gs_app_set_metadata (app, "fwupd::Guid", guid_str); + } + if (fwupd_device_get_name (dev) != NULL) { + g_autofree gchar *vendor_name = NULL; + if (fwupd_device_get_vendor (dev) == NULL || + g_str_has_prefix (fwupd_device_get_name (dev), + fwupd_device_get_vendor (dev))) { + vendor_name = g_strdup (fwupd_device_get_name (dev)); + } else { + vendor_name = g_strdup_printf ("%s %s", + fwupd_device_get_vendor (dev), + fwupd_device_get_name (dev)); + } + gs_app_set_name (app, GS_APP_QUALITY_NORMAL, vendor_name); + } + if (fwupd_device_get_summary (dev) != NULL) { + gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, + fwupd_device_get_summary (dev)); + } + if (fwupd_device_get_version (dev) != NULL) { + gs_app_set_version (app, fwupd_device_get_version (dev)); + } + if (fwupd_device_get_created (dev) != 0) + gs_app_set_install_date (app, fwupd_device_get_created (dev)); + if (fwupd_device_get_description (dev) != NULL) { + g_autofree gchar *tmp = NULL; + tmp = as_markup_convert_simple (fwupd_device_get_description (dev), NULL); + if (tmp != NULL) + gs_app_set_description (app, GS_APP_QUALITY_NORMAL, tmp); + } + + /* needs action */ + if (fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER)) + gs_app_add_quirk (app, GS_APP_QUIRK_NEEDS_USER_ACTION); + else + gs_app_remove_quirk (app, GS_APP_QUIRK_NEEDS_USER_ACTION); +} + +static gchar * +gs_fwupd_release_get_name (FwupdRelease *release) +{ + const gchar *name = fwupd_release_get_name (release); + GPtrArray *cats = fwupd_release_get_categories (release); + + for (guint i = 0; i < cats->len; i++) { + const gchar *cat = g_ptr_array_index (cats, i); + if (g_strcmp0 (cat, "X-Device") == 0) { + /* TRANSLATORS: a specific part of hardware, + * the first %s is the device name, e.g. 'Unifying Receiver` */ + return g_strdup_printf (_("%s Device Update"), name); + } + if (g_strcmp0 (cat, "X-System") == 0) { + /* TRANSLATORS: the entire system, e.g. all internal devices, + * the first %s is the device name, e.g. 'ThinkPad P50` */ + return g_strdup_printf (_("%s System Update"), name); + } + if (g_strcmp0 (cat, "X-EmbeddedController") == 0) { + /* TRANSLATORS: the EC is typically the keyboard controller chip, + * the first %s is the device name, e.g. 'ThinkPad P50` */ + return g_strdup_printf (_("%s Embedded Controller Update"), name); + } + if (g_strcmp0 (cat, "X-ManagementEngine") == 0) { + /* TRANSLATORS: ME stands for Management Engine, the Intel AMT thing, + * the first %s is the device name, e.g. 'ThinkPad P50` */ + return g_strdup_printf (_("%s ME Update"), name); + } + if (g_strcmp0 (cat, "X-CorporateManagementEngine") == 0) { + /* TRANSLATORS: ME stands for Management Engine (with Intel AMT), + * where the first %s is the device name, e.g. 'ThinkPad P50` */ + return g_strdup_printf (_("%s Corporate ME Update"), name); + } + if (g_strcmp0 (cat, "X-ConsumerManagementEngine") == 0) { + /* TRANSLATORS: ME stands for Management Engine, where + * the first %s is the device name, e.g. 'ThinkPad P50` */ + return g_strdup_printf (_("%s Consumer ME Update"), name); + } + if (g_strcmp0 (cat, "X-Controller") == 0) { + /* TRANSLATORS: the controller is a device that has other devices + * plugged into it, for example ThunderBolt, FireWire or USB, + * the first %s is the device name, e.g. 'Intel ThunderBolt` */ + return g_strdup_printf (_("%s Controller Update"), name); + } + if (g_strcmp0 (cat, "X-ThunderboltController") == 0) { + /* TRANSLATORS: the Thunderbolt controller is a device that + * has other high speed Thunderbolt devices plugged into it; + * the first %s is the system name, e.g. 'ThinkPad P50` */ + return g_strdup_printf (_("%s Thunderbolt Controller Update"), name); + } + if (g_strcmp0 (cat, "X-CpuMicrocode") == 0) { + /* TRANSLATORS: the CPU microcode is firmware loaded onto the CPU + * at system bootup */ + return g_strdup_printf (_("%s CPU Microcode Update"), name); + } + if (g_strcmp0 (cat, "X-Configuration") == 0) { + /* TRANSLATORS: configuration refers to hardware state, + * e.g. a security database or a default power value */ + return g_strdup_printf (_("%s Configuration Update"), name); + } + if (g_strcmp0 (cat, "X-Battery") == 0) { + /* TRANSLATORS: battery refers to the system power source */ + return g_strdup_printf (_("%s Battery Update"), name); + } + if (g_strcmp0 (cat, "X-Camera") == 0) { + /* TRANSLATORS: camera can refer to the laptop internal + * camera in the bezel or external USB webcam */ + return g_strdup_printf (_("%s Camera Update"), name); + } + if (g_strcmp0 (cat, "X-TPM") == 0) { + /* TRANSLATORS: TPM refers to a Trusted Platform Module */ + return g_strdup_printf (_("%s TPM Update"), name); + } + if (g_strcmp0 (cat, "X-Touchpad") == 0) { + /* TRANSLATORS: TouchPad refers to a flat input device */ + return g_strdup_printf (_("%s Touchpad Update"), name); + } + if (g_strcmp0 (cat, "X-Mouse") == 0) { + /* TRANSLATORS: Mouse refers to a handheld input device */ + return g_strdup_printf (_("%s Mouse Update"), name); + } + if (g_strcmp0 (cat, "X-Keyboard") == 0) { + /* TRANSLATORS: Keyboard refers to an input device for typing */ + return g_strdup_printf (_("%s Keyboard Update"), name); + } + if (g_strcmp0 (cat, "X-StorageController") == 0) { + /* TRANSLATORS: Storage Controller is typically a RAID or SAS adapter */ + return g_strdup_printf (_("%s Storage Controller Update"), name); + } + if (g_strcmp0 (cat, "X-NetworkInterface") == 0) { + /* TRANSLATORS: Network Interface refers to the physical + * PCI card, not the logical wired connection */ + return g_strdup_printf (_("%s Network Interface Update"), name); + } + if (g_strcmp0 (cat, "X-VideoDisplay") == 0) { + /* TRANSLATORS: Video Display refers to the laptop internal display or + * external monitor */ + return g_strdup_printf (_("%s Display Update"), name); + } + if (g_strcmp0 (cat, "X-BaseboardManagementController") == 0) { + /* TRANSLATORS: BMC refers to baseboard management controller which + * is the device that updates all the other firmware on the system */ + return g_strdup_printf (_("%s BMC Update"), name); + } + if (g_strcmp0 (cat, "X-UsbReceiver") == 0) { + /* TRANSLATORS: Receiver refers to a radio device, e.g. a tiny Bluetooth + * device that stays in the USB port so the wireless peripheral works */ + return g_strdup_printf (_("%s USB Receiver Update"), name); + } + } + + /* default fallback */ + return g_strdup (name); +} + +static AsUrgencyKind +gs_fwupd_release_urgency_to_as_urgency_kind (FwupdReleaseUrgency urgency) +{ + switch (urgency) { + case FWUPD_RELEASE_URGENCY_LOW: + return AS_URGENCY_KIND_LOW; + case FWUPD_RELEASE_URGENCY_MEDIUM: + return AS_URGENCY_KIND_MEDIUM; + case FWUPD_RELEASE_URGENCY_HIGH: + return AS_URGENCY_KIND_HIGH; + case FWUPD_RELEASE_URGENCY_CRITICAL: + return AS_URGENCY_KIND_CRITICAL; + case FWUPD_RELEASE_URGENCY_UNKNOWN: + default: + return AS_URGENCY_KIND_UNKNOWN; + } +} + +void +gs_fwupd_app_set_from_release (GsApp *app, FwupdRelease *rel) +{ + GPtrArray *locations = fwupd_release_get_locations (rel); + + if (fwupd_release_get_name (rel) != NULL) { + g_autofree gchar *tmp = gs_fwupd_release_get_name (rel); + gs_app_set_name (app, GS_APP_QUALITY_NORMAL, tmp); + } + if (fwupd_release_get_summary (rel) != NULL) { + gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, + fwupd_release_get_summary (rel)); + } + if (fwupd_release_get_homepage (rel) != NULL) { + gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, + fwupd_release_get_homepage (rel)); + } + if (fwupd_release_get_size (rel) != 0) { + gs_app_set_size_installed (app, GS_SIZE_TYPE_VALID, 0); + gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, fwupd_release_get_size (rel)); + } + if (fwupd_release_get_version (rel) != NULL) + gs_app_set_update_version (app, fwupd_release_get_version (rel)); + if (fwupd_release_get_license (rel) != NULL) { + gs_app_set_license (app, GS_APP_QUALITY_NORMAL, + fwupd_release_get_license (rel)); + } + if (locations->len > 0) { + const gchar *uri = g_ptr_array_index (locations, 0); + /* typically the first URI will be the main HTTP mirror, and we + * don't have the capability to use an IPFS/IPNS URL anyway */ + gs_app_set_origin_hostname (app, uri); + gs_fwupd_app_set_update_uri (app, uri); + } + if (fwupd_release_get_description (rel) != NULL) { + g_autofree gchar *tmp = NULL; + tmp = as_markup_convert_simple (fwupd_release_get_description (rel), NULL); + if (tmp != NULL) + gs_app_set_update_details_text (app, tmp); + } + if (fwupd_release_get_detach_image (rel) != NULL) { + g_autoptr(AsScreenshot) ss = as_screenshot_new (); + g_autoptr(AsImage) im = as_image_new (); + as_image_set_kind (im, AS_IMAGE_KIND_SOURCE); + as_image_set_url (im, fwupd_release_get_detach_image (rel)); + as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_DEFAULT); + as_screenshot_add_image (ss, im); + if (fwupd_release_get_detach_caption (rel) != NULL) + as_screenshot_set_caption (ss, fwupd_release_get_detach_caption (rel), NULL); + gs_app_set_action_screenshot (app, ss); + } + + gs_app_set_update_urgency (app, gs_fwupd_release_urgency_to_as_urgency_kind (fwupd_release_get_urgency (rel))); +} diff --git a/plugins/fwupd/gs-fwupd-app.h b/plugins/fwupd/gs-fwupd-app.h new file mode 100644 index 0000000..5a3e9f2 --- /dev/null +++ b/plugins/fwupd/gs-fwupd-app.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2017 Richard Hughes <richard@hughsie.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#pragma once + +#include <gnome-software.h> +#include <fwupd.h> + +G_BEGIN_DECLS + +const gchar *gs_fwupd_app_get_device_id (GsApp *app); +const gchar *gs_fwupd_app_get_update_uri (GsApp *app); +gboolean gs_fwupd_app_get_is_locked (GsApp *app); + +void gs_fwupd_app_set_device_id (GsApp *app, + const gchar *device_id); +void gs_fwupd_app_set_update_uri (GsApp *app, + const gchar *update_uri); +void gs_fwupd_app_set_is_locked (GsApp *app, + gboolean is_locked); +void gs_fwupd_app_set_from_device (GsApp *app, + FwupdDevice *dev); +void gs_fwupd_app_set_from_release (GsApp *app, + FwupdRelease *rel); + +G_END_DECLS diff --git a/plugins/fwupd/gs-plugin-fwupd.c b/plugins/fwupd/gs-plugin-fwupd.c new file mode 100644 index 0000000..b5d99b7 --- /dev/null +++ b/plugins/fwupd/gs-plugin-fwupd.c @@ -0,0 +1,1414 @@ +/* -*- 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" + +#include "gs-plugin-fwupd.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. + * + * Since fwupd is a daemon accessible over D-Bus, this plugin basically + * translates every job into one or more D-Bus calls, and all the real work is + * done in the fwupd daemon. FIXME: This means the plugin can therefore execute + * entirely in the main thread, making asynchronous D-Bus calls, once all the + * vfuncs have been ported. + */ + +struct _GsPluginFwupd { + GsPlugin parent; + + FwupdClient *client; + GsApp *app_current; + GsApp *cached_origin; +}; + +G_DEFINE_TYPE (GsPluginFwupd, gs_plugin_fwupd, GS_TYPE_PLUGIN) + +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; + case FWUPD_ERROR_BATTERY_LEVEL_TOO_LOW: + error->code = GS_PLUGIN_ERROR_BATTERY_LEVEL_TOO_LOW; + break; + 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; +} + +static void +gs_plugin_fwupd_init (GsPluginFwupd *self) +{ + self->client = fwupd_client_new (); + + /* set name of MetaInfo file */ + gs_plugin_set_appstream_id (GS_PLUGIN (self), "org.gnome.Software.Plugin.Fwupd"); +} + +static void +gs_plugin_fwupd_dispose (GObject *object) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (object); + + g_clear_object (&self->cached_origin); + g_clear_object (&self->client); + + G_OBJECT_CLASS (gs_plugin_fwupd_parent_class)->dispose (object); +} + +void +gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app) +{ + if (gs_app_get_kind (app) == AS_COMPONENT_KIND_FIRMWARE) + gs_app_set_management_plugin (app, 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, + gpointer user_data) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (user_data); + + /* nothing in progress */ + if (self->app_current == NULL) { + g_debug ("fwupd percentage: %u%%", + fwupd_client_get_percentage (self->client)); + return; + } + g_debug ("fwupd percentage for %s: %u%%", + gs_app_get_unique_id (self->app_current), + fwupd_client_get_percentage (self->client)); + gs_app_set_progress (self->app_current, + fwupd_client_get_percentage (self->client)); +} + +static void +gs_plugin_fwupd_notify_status_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (user_data); + + /* nothing in progress */ + if (self->app_current == NULL) { + g_debug ("fwupd status: %s", + fwupd_status_to_string (fwupd_client_get_status (self->client))); + return; + } + + g_debug ("fwupd status for %s: %s", + gs_app_get_unique_id (self->app_current), + fwupd_status_to_string (fwupd_client_get_status (self->client))); + switch (fwupd_client_get_status (self->client)) { + case FWUPD_STATUS_DECOMPRESSING: + case FWUPD_STATUS_DEVICE_RESTART: + case FWUPD_STATUS_DEVICE_WRITE: + case FWUPD_STATUS_DEVICE_VERIFY: + gs_app_set_state (self->app_current, GS_APP_STATE_INSTALLING); + break; + case FWUPD_STATUS_IDLE: + g_clear_object (&self->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); +} + +static void setup_connect_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); +static void setup_features_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); + +static void +gs_plugin_fwupd_setup_async (GsPlugin *plugin, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + g_autoptr(GTask) task = NULL; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_fwupd_setup_async); + + /* connect a proxy */ + fwupd_client_connect_async (self->client, cancellable, setup_connect_cb, + g_steal_pointer (&task)); +} + +static void +setup_connect_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = g_steal_pointer (&user_data); + GsPluginFwupd *self = g_task_get_source_object (task); + GCancellable *cancellable = g_task_get_cancellable (task); + g_autoptr(GError) local_error = NULL; + + if (!fwupd_client_connect_finish (self->client, result, &local_error)) { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } + + /* send our implemented feature set */ + fwupd_client_set_feature_flags_async (self->client, + FWUPD_FEATURE_FLAG_UPDATE_ACTION | + FWUPD_FEATURE_FLAG_DETACH_ACTION, + cancellable, setup_features_cb, + g_steal_pointer (&task)); +} + +static void +setup_features_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = g_steal_pointer (&user_data); + GsPluginFwupd *self = g_task_get_source_object (task); + GsPlugin *plugin = GS_PLUGIN (self); + g_autoptr(GError) local_error = NULL; + + if (!fwupd_client_set_feature_flags_finish (self->client, result, &local_error)) + g_debug ("Failed to set front-end features: %s", local_error->message); + g_clear_error (&local_error); + + /* we know the runtime daemon version now */ + fwupd_client_set_user_agent_for_package (self->client, PACKAGE_NAME, PACKAGE_VERSION); + if (!fwupd_client_ensure_networking (self->client, &local_error)) { + gs_plugin_fwupd_error_convert (&local_error); + g_prefix_error (&local_error, "Failed to setup networking: "); + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } + + /* add source */ + self->cached_origin = gs_app_new (gs_plugin_get_name (plugin)); + gs_app_set_kind (self->cached_origin, AS_COMPONENT_KIND_REPOSITORY); + gs_app_set_bundle_kind (self->cached_origin, AS_BUNDLE_KIND_CABINET); + gs_app_set_management_plugin (self->cached_origin, plugin); + + /* 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 (self->cached_origin), + self->cached_origin); + + /* register D-Bus errors */ + fwupd_error_quark (); + g_signal_connect (self->client, "changed", + G_CALLBACK (gs_plugin_fwupd_changed_cb), plugin); + g_signal_connect (self->client, "device-added", + G_CALLBACK (gs_plugin_fwupd_device_changed_cb), plugin); + g_signal_connect (self->client, "device-removed", + G_CALLBACK (gs_plugin_fwupd_device_changed_cb), plugin); + g_signal_connect (self->client, "device-changed", + G_CALLBACK (gs_plugin_fwupd_device_changed_cb), plugin); + g_signal_connect (self->client, "notify::percentage", + G_CALLBACK (gs_plugin_fwupd_notify_percentage_cb), self); + g_signal_connect (self->client, "notify::status", + G_CALLBACK (gs_plugin_fwupd_notify_status_cb), self); + + g_task_return_boolean (task, TRUE); +} + +static gboolean +gs_plugin_fwupd_setup_finish (GsPlugin *plugin, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +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(GIcon) 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 = gs_utils_build_unique_id (AS_COMPONENT_SCOPE_SYSTEM, + AS_BUNDLE_KIND_UNKNOWN, + NULL, /* origin */ + 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_COMPONENT_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, plugin); + gs_app_add_category (app, "System"); + gs_fwupd_app_set_device_id (app, fwupd_device_get_id (dev)); + + /* create icon */ + icon = g_themed_icon_new ("system-component-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_COMPONENT_KIND_FIRMWARE); + gs_app_set_scope (app, AS_COMPONENT_SCOPE_SYSTEM); + gs_app_set_state (app, GS_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, plugin); + + /* create icon */ + icons = fwupd_device_get_icons (device); + for (guint j = 0; j < icons->len; j++) { + const gchar *icon_str = g_ptr_array_index (icons, j); + g_autoptr(GIcon) icon = NULL; + if (g_str_has_prefix (icon_str, "/")) { + g_autoptr(GFile) icon_file = g_file_new_for_path (icon_str); + icon = g_file_icon_new (icon_file); + } else { + icon = g_themed_icon_new (icon_str); + } + gs_app_add_icon (app, icon); + } + 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; + GPtrArray *locations = fwupd_release_get_locations (rel); + const gchar *update_uri = NULL; + 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) != GS_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; + } + + /* typically the first URI will be the main HTTP mirror, and we + * don't have the capability to use an IPFS/IPNS URL anyway */ + if (locations->len > 0) + update_uri = g_ptr_array_index (locations, 0); + + 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_CREATE_DIRECTORY, + 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, GS_SIZE_TYPE_VALID, 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) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (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 (self->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) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + g_autoptr(GError) error_local = NULL; + g_autoptr(GPtrArray) devices = NULL; + + /* get current list of updates */ + devices = fwupd_client_get_devices (self->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_debug ("Failed to get devices: %s", error_local->message); + return TRUE; + } + 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 (self->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_simple (fwupd_release_get_description (rel), 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_text (app, update_desc->str); + } + } + gs_app_list_add (list, app); + } + return TRUE; +} + +static gboolean +remote_cache_is_expired (FwupdRemote *remote, + guint64 cache_age_secs) +{ + /* check cache age */ + if (cache_age_secs > 0) { + guint64 age = fwupd_remote_get_age (remote); + if (age < cache_age_secs) { + g_debug ("fwupd remote is only %" G_GUINT64_FORMAT " seconds old, so ignoring refresh", age); + return FALSE; + } + } + + return TRUE; +} + +typedef struct { + /* Input data. */ + guint64 cache_age_secs; + + /* In-progress state. */ + guint n_operations_pending; + GError *error; /* (owned) (nullable) */ +} RefreshMetadataData; + +static void +refresh_metadata_data_free (RefreshMetadataData *data) +{ + g_clear_error (&data->error); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (RefreshMetadataData, refresh_metadata_data_free) + +static void get_remotes_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); +static void refresh_remote_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data); +static void finish_refresh_metadata_op (GTask *task); + +static void +gs_plugin_fwupd_refresh_metadata_async (GsPlugin *plugin, + guint64 cache_age_secs, + GsPluginRefreshMetadataFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + g_autoptr(GTask) task = NULL; + g_autoptr(RefreshMetadataData) data = NULL; + + task = g_task_new (plugin, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_fwupd_refresh_metadata_async); + + data = g_new0 (RefreshMetadataData, 1); + data->cache_age_secs = cache_age_secs; + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) refresh_metadata_data_free); + + /* get the list of enabled remotes */ + fwupd_client_get_remotes_async (self->client, cancellable, get_remotes_cb, g_steal_pointer (&task)); +} + +static void +get_remotes_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + FwupdClient *client = FWUPD_CLIENT (source_object); + g_autoptr(GTask) task = g_steal_pointer (&user_data); + RefreshMetadataData *data = g_task_get_task_data (task); + GCancellable *cancellable = g_task_get_cancellable (task); + g_autoptr(GError) error_local = NULL; + g_autoptr(GPtrArray) remotes = NULL; + + remotes = fwupd_client_get_remotes_finish (client, result, &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)) { + g_task_return_boolean (task, TRUE); + return; + } + + gs_plugin_fwupd_error_convert (&error_local); + g_task_return_error (task, g_steal_pointer (&error_local)); + return; + } + + /* Refresh each of the remotes in parallel. Keep the pending operation + * count incremented until all operations have been started, so that + * the overall operation doesn’t complete too early. */ + data->n_operations_pending = 1; + + 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_DOWNLOAD) + continue; + if (!remote_cache_is_expired (remote, data->cache_age_secs)) + continue; + + data->n_operations_pending++; + fwupd_client_refresh_remote_async (client, remote, cancellable, + refresh_remote_cb, g_object_ref (task)); + } + + finish_refresh_metadata_op (task); +} + +static void +refresh_remote_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + FwupdClient *client = FWUPD_CLIENT (source_object); + g_autoptr(GTask) task = g_steal_pointer (&user_data); + RefreshMetadataData *data = g_task_get_task_data (task); + g_autoptr(GError) local_error = NULL; + + if (!fwupd_client_refresh_remote_finish (client, result, &local_error)) { + gs_plugin_fwupd_error_convert (&local_error); + if (data->error == NULL) + data->error = g_steal_pointer (&local_error); + else + g_debug ("Another remote refresh error: %s", local_error->message); + } + + finish_refresh_metadata_op (task); +} + +static void +finish_refresh_metadata_op (GTask *task) +{ + RefreshMetadataData *data = g_task_get_task_data (task); + + g_assert (data->n_operations_pending > 0); + data->n_operations_pending--; + + if (data->n_operations_pending == 0) { + if (data->error != NULL) + g_task_return_error (task, g_steal_pointer (&data->error)); + else + g_task_return_boolean (task, TRUE); + } +} + +static gboolean +gs_plugin_fwupd_refresh_metadata_finish (GsPlugin *plugin, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static gboolean +gs_plugin_fwupd_install (GsPluginFwupd *self, + GsApp *app, + GCancellable *cancellable, + GError **error) +{ + 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); + g_autoptr(GFile) file = g_file_new_for_path (filename); + gs_app_set_state (app, GS_APP_STATE_INSTALLING); + if (!fwupd_client_download_file (self->client, + uri, file, + FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, + cancellable, + error)) { + gs_plugin_fwupd_error_convert (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 (&self->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, GS_APP_STATE_INSTALLING); + if (!fwupd_client_install (self->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, GS_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 (self->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 (); + + /* 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); + } + + /* caption is required */ + as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_DEFAULT); + as_screenshot_set_caption (ss, fwupd_device_get_update_message (dev), NULL); + 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 void +gs_plugin_fwupd_modify_source_ready_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GError) local_error = NULL; + g_autoptr(GTask) task = user_data; + GsPluginFwupd *self = g_task_get_source_object (task); + GsApp *repository = g_task_get_task_data (task); + + if (!fwupd_client_modify_remote_finish (FWUPD_CLIENT (source_object), result, &local_error)) { + gs_app_set_state_recover (repository); + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } + + if (gs_app_get_state (repository) == GS_APP_STATE_INSTALLING) + gs_app_set_state (repository, GS_APP_STATE_INSTALLED); + else if (gs_app_get_state (repository) == GS_APP_STATE_REMOVING) + gs_app_set_state (repository, GS_APP_STATE_AVAILABLE); + + gs_plugin_repository_changed (GS_PLUGIN (self), repository); + + g_task_return_boolean (task, TRUE); +} + +static void +gs_plugin_fwupd_modify_source_async (GsPluginFwupd *self, + GsApp *repository, + gboolean enabled, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + const gchar *remote_id; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, g_object_ref (repository), g_object_unref); + g_task_set_source_tag (task, gs_plugin_fwupd_modify_source_async); + + if (!gs_app_has_management_plugin (repository, GS_PLUGIN (self))) { + g_task_return_boolean (task, TRUE); + return; + } + + /* source -> remote */ + g_assert (gs_app_get_kind (repository) == AS_COMPONENT_KIND_REPOSITORY); + + remote_id = gs_app_get_metadata_item (repository, "fwupd::remote-id"); + if (remote_id == NULL) { + g_task_return_new_error (task, + GS_PLUGIN_ERROR, + GS_PLUGIN_ERROR_FAILED, + "not enough data for fwupd %s", + gs_app_get_unique_id (repository)); + return; + } + gs_app_set_state (repository, enabled ? + GS_APP_STATE_INSTALLING : GS_APP_STATE_REMOVING); + fwupd_client_modify_remote_async (self->client, + remote_id, + "Enabled", + enabled ? "true" : "false", + cancellable, + gs_plugin_fwupd_modify_source_ready_cb, + g_steal_pointer (&task)); +} + +static gboolean +gs_plugin_fwupd_modify_source_finish (GsPluginFwupd *self, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +gboolean +gs_plugin_app_install (GsPlugin *plugin, + GsApp *app, + GCancellable *cancellable, + GError **error) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + /* only process this app if was created by this plugin */ + if (!gs_app_has_management_plugin (app, plugin)) + return TRUE; + + /* source -> remote, handled by dedicated function */ + g_return_val_if_fail (gs_app_get_kind (app) != AS_COMPONENT_KIND_REPOSITORY, FALSE); + + /* firmware */ + return gs_plugin_fwupd_install (self, app, cancellable, error); +} + +gboolean +gs_plugin_download_app (GsPlugin *plugin, + GsApp *app, + GCancellable *cancellable, + GError **error) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + GFile *local_file; + g_autofree gchar *filename = NULL; + gpointer schedule_entry_handle = NULL; + g_autoptr(GError) error_local = NULL; + + /* only process this app if was created by this plugin */ + if (!gs_app_has_management_plugin (app, plugin)) + 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); + g_autoptr(GFile) file = g_file_new_for_path (filename); + gboolean download_success; + + if (!gs_plugin_has_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE)) { + if (!gs_metered_block_app_on_download_scheduler (app, &schedule_entry_handle, cancellable, &error_local)) { + g_warning ("Failed to block on download scheduler: %s", + error_local->message); + g_clear_error (&error_local); + } + } + + download_success = fwupd_client_download_file (self->client, + uri, file, + FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, + cancellable, + error); + if (!download_success) + gs_plugin_fwupd_error_convert (error); + + if (!gs_metered_remove_from_download_scheduler (schedule_entry_handle, NULL, &error_local)) + g_warning ("Failed to remove schedule entry: %s", error_local->message); + + if (!download_success) + return FALSE; + } + gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, 0); + return TRUE; +} + +gboolean +gs_plugin_update_app (GsPlugin *plugin, + GsApp *app, + GCancellable *cancellable, + GError **error) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + /* only process this app if was created by this plugin */ + if (!gs_app_has_management_plugin (app, plugin)) + 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 (self->client, device_id, + cancellable, error)) { + gs_plugin_fwupd_error_convert (error); + return FALSE; + } + return TRUE; + } + + /* update means install */ + if (!gs_plugin_fwupd_install (self, 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) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (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 (self->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_markup (app)); + gs_app_list_add (list, app); + } + return TRUE; +} + +gboolean +gs_plugin_add_sources (GsPlugin *plugin, + GsAppList *list, + GCancellable *cancellable, + GError **error) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + g_autoptr(GPtrArray) remotes = NULL; + + /* find all remotes */ + remotes = fwupd_client_get_remotes (self->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_COMPONENT_KIND_REPOSITORY); + gs_app_set_scope (app, AS_COMPONENT_SCOPE_SYSTEM); + gs_app_set_state (app, fwupd_remote_get_enabled (remote) ? + GS_APP_STATE_INSTALLED : GS_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)); + gs_app_set_agreement (app, fwupd_remote_get_agreement (remote)); + 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, plugin); + gs_app_set_metadata (app, "GnomeSoftware::PackagingFormat", "fwupd"); + gs_app_set_metadata (app, "GnomeSoftware::SortKey", "800"); + gs_app_set_origin_ui (app, _("Firmware")); + gs_app_list_add (list, app); + } + return TRUE; +} + +static void +gs_plugin_fwupd_enable_repository_remote_refresh_ready_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = user_data; + g_autoptr(GError) local_error = NULL; + + if (!fwupd_client_refresh_remote_finish (FWUPD_CLIENT (source_object), result, &local_error)) + g_debug ("Failed to refresh remote after enable: %s", local_error ? local_error->message : "Unknown error"); + + /* Silently ignore refresh errors */ + g_task_return_boolean (task, TRUE); +} + +static void +gs_plugin_fwupd_enable_repository_get_remotes_ready_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = user_data; + g_autoptr(GError) local_error = NULL; + g_autoptr(GPtrArray) remotes = NULL; + GsPluginFwupd *self = GS_PLUGIN_FWUPD (g_task_get_source_object (task)); + GsApp *repository = g_task_get_task_data (task); + const gchar *remote_id; + guint cache_age = 1; + + remotes = fwupd_client_get_remotes_finish (FWUPD_CLIENT (source_object), result, &local_error); + if (remotes == NULL) { + g_debug ("No remotes found after remote enable: %s", local_error ? local_error->message : "Unknown error"); + /* Silently ignore refresh errors */ + g_task_return_boolean (task, TRUE); + return; + } + + remote_id = gs_app_get_metadata_item (repository, "fwupd::remote-id"); + g_assert (remote_id != NULL); + + for (guint i = 0; i < remotes->len; i++) { + FwupdRemote *remote = g_ptr_array_index (remotes, i); + if (g_strcmp0 (remote_id, fwupd_remote_get_id (remote)) == 0) { + if (fwupd_remote_get_enabled (remote) && + fwupd_remote_get_kind (remote) != FWUPD_REMOTE_KIND_LOCAL && + !remote_cache_is_expired (remote, cache_age)) { + GCancellable *cancellable = g_task_get_cancellable (task); + fwupd_client_refresh_remote_async (self->client, remote, cancellable, + gs_plugin_fwupd_enable_repository_remote_refresh_ready_cb, + g_steal_pointer (&task)); + return; + } + break; + } + } + + g_task_return_boolean (task, TRUE); +} + +static void +gs_plugin_fwupd_enable_repository_ready_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = user_data; + g_autoptr(GError) local_error = NULL; + GsPluginFwupd *self = GS_PLUGIN_FWUPD (g_task_get_source_object (task)); + GCancellable *cancellable = g_task_get_cancellable (task); + + if (!gs_plugin_fwupd_modify_source_finish (self, result, &local_error)) { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } + + /* This can fail silently, it's only to update necessary caches, to provide + * up-to-date information after the successful repository enable/install. */ + fwupd_client_get_remotes_async (self->client, + cancellable, + gs_plugin_fwupd_enable_repository_get_remotes_ready_cb, + g_steal_pointer (&task)); +} + +static void +gs_plugin_fwupd_enable_repository_async (GsPlugin *plugin, + GsApp *repository, + GsPluginManageRepositoryFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + g_autoptr(GTask) task = NULL; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, g_object_ref (repository), g_object_unref); + g_task_set_source_tag (task, gs_plugin_fwupd_enable_repository_async); + + /* only process this app if was created by this plugin */ + if (!gs_app_has_management_plugin (repository, plugin)) { + g_task_return_boolean (task, TRUE); + return; + } + + gs_plugin_fwupd_modify_source_async (self, repository, TRUE, cancellable, + gs_plugin_fwupd_enable_repository_ready_cb, g_steal_pointer (&task)); +} + +static gboolean +gs_plugin_fwupd_enable_repository_finish (GsPlugin *plugin, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +gs_plugin_fwupd_disable_repository_async (GsPlugin *plugin, + GsApp *repository, + GsPluginManageRepositoryFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + + /* only process this app if was created by this plugin */ + if (!gs_app_has_management_plugin (repository, plugin)) { + g_autoptr(GTask) task = NULL; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, gs_plugin_fwupd_disable_repository_async); + g_task_return_boolean (task, TRUE); + return; + } + + gs_plugin_fwupd_modify_source_async (self, repository, FALSE, cancellable, callback, user_data); +} + +static gboolean +gs_plugin_fwupd_disable_repository_finish (GsPlugin *plugin, + GAsyncResult *result, + GError **error) +{ + GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin); + return gs_plugin_fwupd_modify_source_finish (self, result, error); +} + +static void +gs_plugin_fwupd_class_init (GsPluginFwupdClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GsPluginClass *plugin_class = GS_PLUGIN_CLASS (klass); + + object_class->dispose = gs_plugin_fwupd_dispose; + + plugin_class->setup_async = gs_plugin_fwupd_setup_async; + plugin_class->setup_finish = gs_plugin_fwupd_setup_finish; + plugin_class->refresh_metadata_async = gs_plugin_fwupd_refresh_metadata_async; + plugin_class->refresh_metadata_finish = gs_plugin_fwupd_refresh_metadata_finish; + plugin_class->enable_repository_async = gs_plugin_fwupd_enable_repository_async; + plugin_class->enable_repository_finish = gs_plugin_fwupd_enable_repository_finish; + plugin_class->disable_repository_async = gs_plugin_fwupd_disable_repository_async; + plugin_class->disable_repository_finish = gs_plugin_fwupd_disable_repository_finish; +} + +GType +gs_plugin_query_type (void) +{ + return GS_TYPE_PLUGIN_FWUPD; +} diff --git a/plugins/fwupd/gs-plugin-fwupd.h b/plugins/fwupd/gs-plugin-fwupd.h new file mode 100644 index 0000000..6114814 --- /dev/null +++ b/plugins/fwupd/gs-plugin-fwupd.h @@ -0,0 +1,22 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2021 Endless OS Foundation LLC + * + * Author: Philip Withnall <pwithnall@endlessos.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#pragma once + +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GS_TYPE_PLUGIN_FWUPD (gs_plugin_fwupd_get_type ()) + +G_DECLARE_FINAL_TYPE (GsPluginFwupd, gs_plugin_fwupd, GS, PLUGIN_FWUPD, GsPlugin) + +G_END_DECLS diff --git a/plugins/fwupd/gs-self-test.c b/plugins/fwupd/gs-self-test.c new file mode 100644 index 0000000..f02a9f0 --- /dev/null +++ b/plugins/fwupd/gs-self-test.c @@ -0,0 +1,111 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2013-2017 Richard Hughes <richard@hughsie.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include <fwupd.h> + +#include "gnome-software-private.h" + +#include "gs-test.h" + +static void +gs_plugins_fwupd_func (GsPluginLoader *plugin_loader) +{ + g_autofree gchar *fn = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GFile) file = NULL; + g_autoptr(GsApp) app = NULL; + g_autoptr(GsPluginJob) plugin_job = NULL; + GsSizeType size_download_type; + guint64 size_download_bytes; + + /* no fwupd, abort */ + if (!gs_plugin_loader_get_enabled (plugin_loader, "fwupd")) { + g_test_skip ("not enabled"); + return; + } + + /* load local file */ + fn = gs_test_get_filename (TESTDATADIR, "chiron-0.2.cab"); + g_assert_nonnull (fn); + file = g_file_new_for_path (fn); + plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_FILE_TO_APP, + "file", file, + NULL); + app = gs_plugin_loader_job_process_app (plugin_loader, plugin_job, NULL, &error); + gs_test_flush_main_context (); + g_assert_no_error (error); + g_assert_nonnull (app); + g_assert_cmpint (gs_app_get_kind (app), ==, AS_COMPONENT_KIND_FIRMWARE); + g_assert_nonnull (gs_app_get_license (app)); + g_assert_true (gs_app_has_category (app, "System")); + g_assert_cmpstr (gs_app_get_id (app), ==, "com.test.chiron.firmware"); + 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_summary (app), ==, "Single line synopsis"); + g_assert_cmpstr (gs_app_get_version (app), ==, "0.2"); + size_download_type = gs_app_get_size_download (app, &size_download_bytes); + g_assert_cmpint (size_download_type, ==, GS_SIZE_TYPE_VALID); + g_assert_cmpuint (size_download_bytes, ==, 32784); + g_assert_cmpstr (gs_app_get_description (app), ==, + "This is the first paragraph in the example " + "cab file.\n\nThis is the second paragraph."); +#if FWUPD_CHECK_VERSION(1, 7, 1) && !FWUPD_CHECK_VERSION(1, 8, 0) + /* Changes introduced in fwupd commit d3706e0e0b0fc210796da839b84ac391f7a251f8 and + removed for 1.8.0 with https://github.com/fwupd/fwupd/commit/0eeaad76ec79562ea3790bb377d847d5be02182f */ + g_assert_cmpstr (gs_app_get_update_details_markup (app), ==, + "Some of the platform secrets may be invalidated when " + "updating this firmware. Please ensure you have the " + "volume recovery key before continuing.\n\nLatest " + "firmware release."); +#else + g_assert_cmpstr (gs_app_get_update_details_markup (app), ==, + "Latest firmware release."); +#endif + + /* seems wrong, but this is only set if the update is available */ + g_assert_cmpint (gs_app_get_state (app), ==, GS_APP_STATE_UNKNOWN); +} + +int +main (int argc, char **argv) +{ + gboolean ret; + g_autoptr(GError) error = NULL; + g_autoptr(GsPluginLoader) plugin_loader = NULL; + const gchar * const allowlist[] = { + "fwupd", + NULL + }; + + /* While we use %G_TEST_OPTION_ISOLATE_DIRS to create temporary directories + * for each of the tests, we want to use the system MIME registry, assuming + * that it exists and correctly has shared-mime-info installed. */ + g_content_type_set_mime_dirs (NULL); + + gs_test_init (&argc, &argv); + + /* we can only load this once per process */ + plugin_loader = gs_plugin_loader_new (NULL, NULL); + gs_plugin_loader_add_location (plugin_loader, LOCALPLUGINDIR); + ret = gs_plugin_loader_setup (plugin_loader, + allowlist, + NULL, + NULL, + &error); + g_assert_no_error (error); + g_assert_true (ret); + + /* plugin tests go here */ + g_test_add_data_func ("/gnome-software/plugins/fwupd", + plugin_loader, + (GTestDataFunc) gs_plugins_fwupd_func); + + return g_test_run (); +} diff --git a/plugins/fwupd/meson.build b/plugins/fwupd/meson.build new file mode 100644 index 0000000..8ed030b --- /dev/null +++ b/plugins/fwupd/meson.build @@ -0,0 +1,54 @@ +cargs = ['-DG_LOG_DOMAIN="GsPluginFwupd"'] +cargs += ['-DLOCALPLUGINDIR="' + meson.current_build_dir() + '"'] +deps = [ + plugin_libs, + fwupd, +] + +if get_option('mogwai') + deps += [mogwai_schedule_client] +endif + +shared_module( + 'gs_plugin_fwupd', + sources : [ + 'gs-fwupd-app.c', + 'gs-plugin-fwupd.c', + ], + include_directories : [ + include_directories('../..'), + include_directories('../../lib'), + ], + install : true, + install_dir: plugin_dir, + c_args : cargs, + dependencies : deps, +) +metainfo = 'org.gnome.Software.Plugin.Fwupd.metainfo.xml' + +i18n.merge_file( + input: metainfo + '.in', + output: metainfo, + type: 'xml', + po_dir: join_paths(meson.project_source_root(), 'po'), + install: true, + install_dir: join_paths(get_option('datadir'), 'metainfo') +) + +if get_option('tests') + cargs += ['-DTESTDATADIR="' + join_paths(meson.current_source_dir(), 'tests') + '"'] + e = executable( + 'gs-self-test-fwupd', + compiled_schemas, + sources : [ + 'gs-self-test.c' + ], + include_directories : [ + include_directories('../..'), + include_directories('../../lib'), + ], + dependencies : deps, + c_args : cargs, + ) + test('gs-self-test-fwupd', e, suite: ['plugins', 'fwupd'], env: test_env) +endif diff --git a/plugins/fwupd/org.gnome.Software.Plugin.Fwupd.metainfo.xml.in b/plugins/fwupd/org.gnome.Software.Plugin.Fwupd.metainfo.xml.in new file mode 100644 index 0000000..3f2067d --- /dev/null +++ b/plugins/fwupd/org.gnome.Software.Plugin.Fwupd.metainfo.xml.in @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright 2013-2016 Richard Hughes <richard@hughsie.com> --> +<component type="addon"> + <id>org.gnome.Software.Plugin.Fwupd</id> + <extends>org.gnome.Software.desktop</extends> + <name>Firmware Upgrade Support</name> + <summary>Provides support for firmware upgrades</summary> + <url type="homepage">http://www.fwupd.org</url> + <metadata_license>CC0-1.0</metadata_license> + <project_license>GPL-2.0+</project_license> + <update_contact>richard_at_hughsie.com</update_contact> +</component> diff --git a/plugins/fwupd/tests/build-cab.sh b/plugins/fwupd/tests/build-cab.sh new file mode 100755 index 0000000..ea7bed6 --- /dev/null +++ b/plugins/fwupd/tests/build-cab.sh @@ -0,0 +1,4 @@ +gcab --create chiron-0.2.cab \ + firmware.dfu \ + firmware.dfu.asc \ + firmware.metainfo.xml diff --git a/plugins/fwupd/tests/chiron-0.2.cab b/plugins/fwupd/tests/chiron-0.2.cab Binary files differnew file mode 100644 index 0000000..6618361 --- /dev/null +++ b/plugins/fwupd/tests/chiron-0.2.cab diff --git a/plugins/fwupd/tests/firmware.dfu b/plugins/fwupd/tests/firmware.dfu Binary files differnew file mode 100644 index 0000000..50f00c0 --- /dev/null +++ b/plugins/fwupd/tests/firmware.dfu diff --git a/plugins/fwupd/tests/firmware.dfu.asc b/plugins/fwupd/tests/firmware.dfu.asc new file mode 100644 index 0000000..0ea79a7 --- /dev/null +++ b/plugins/fwupd/tests/firmware.dfu.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.14 (GNU/Linux) + +iQEcBAABAgAGBQJWUy6EAAoJEEim2A5FOLrCxokIAJJtLVkuXZHgEu2C2Eq9jGrR +FZ9/z9XtsMgs33teLdmYUAQwvnNIbtIb6z7JViP8llCREP8y2fH+1OjrOOdtuS/A +bIJ0r40c9wYeH97ZcXBdHZiYVEFO+etbMBUg5ifuRO5VPjD9H1NqL05Wx9kUg/1T +a1fwgHopXR0T4jYcg5aijp3mdgfmg4boIklDaRV/g2c93W+0VhDZ2h5sKwBxxlFS +TrptclTMCvRYmVvL1CDOsBtgzu3jGo03wV9rcnSKzeBWvINcvlRLdS0ejlPaRYDK +MUY4MBVz3fDW1vFsqLpU80XMOYk0bxtQqQ2MsrlXWp9qazB+A6mC7kOnJQfx0yI= +=A3W8 +-----END PGP SIGNATURE----- diff --git a/plugins/fwupd/tests/firmware.metainfo.xml b/plugins/fwupd/tests/firmware.metainfo.xml new file mode 100644 index 0000000..d942fd8 --- /dev/null +++ b/plugins/fwupd/tests/firmware.metainfo.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright 2016 Richard Hughes <richard@hughsie.com> --> +<component type="firmware"> + <id>com.test.chiron.firmware</id> + <name>Chiron</name> + <summary>Single line synopsis</summary> + <description> + <p> + This is the first paragraph in the example cab file. + </p> + <p> + This is the second paragraph. + </p> + </description> + <provides> + <firmware type="flashed">fd9f37b4-36fb-5245-86a8-4d5993bb153b</firmware> + </provides> + <url type="homepage">http://127.0.0.1/</url> + <metadata_license>CC0-1.0</metadata_license> + <project_license>GPL-2.0+</project_license> + <developer_name>ACME Corp</developer_name> + <releases> + <release urgency="medium" version="0.2" timestamp="1447353015"> + <checksum target="content" filename="firmware.dfu"/> + <description> + <p>Latest firmware release.</p> + </description> + </release> + </releases> +</component> |