1
0
Fork 0
gnome-software/plugins/fwupd/gs-plugin-fwupd.c
Daniel Baumann 68ee05b3fd
Adding upstream version 48.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 21:00:23 +02:00

2310 lines
80 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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-or-later
*/
#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;
GHashTable *cached_sources; /* (nullable) (owned) (element-type utf8 GsApp); sources by id, each value is weak reffed */
GMutex cached_sources_mutex;
};
G_DEFINE_TYPE (GsPluginFwupd, gs_plugin_fwupd, GS_TYPE_PLUGIN)
static void
cached_sources_weak_ref_cb (gpointer user_data,
GObject *object)
{
GsPluginFwupd *self = user_data;
GHashTableIter iter;
gpointer key, value;
g_autoptr(GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&self->cached_sources_mutex);
g_assert (self->cached_sources != NULL);
g_hash_table_iter_init (&iter, self->cached_sources);
while (g_hash_table_iter_next (&iter, &key, &value)) {
GObject *repo_object = value;
if (repo_object == object) {
g_hash_table_iter_remove (&iter);
if (!g_hash_table_size (self->cached_sources))
g_clear_pointer (&self->cached_sources, g_hash_table_unref);
break;
}
}
}
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 ();
g_mutex_init (&self->cached_sources_mutex);
/* 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);
if (self->cached_sources != NULL) {
GHashTableIter iter;
gpointer value;
g_hash_table_iter_init (&iter, self->cached_sources);
while (g_hash_table_iter_next (&iter, NULL, &value)) {
GObject *app_repo = value;
g_object_weak_unref (app_repo, cached_sources_weak_ref_cb, self);
}
g_clear_pointer (&self->cached_sources, g_hash_table_unref);
}
G_OBJECT_CLASS (gs_plugin_fwupd_parent_class)->dispose (object);
}
static void
gs_plugin_fwupd_finalize (GObject *object)
{
GsPluginFwupd *self = GS_PLUGIN_FWUPD (object);
g_mutex_clear (&self->cached_sources_mutex);
G_OBJECT_CLASS (gs_plugin_fwupd_parent_class)->finalize (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,
#if FWUPD_CHECK_VERSION(1, 8, 1)
FWUPD_FEATURE_FLAG_SHOW_PROBLEMS |
#endif
FWUPD_FEATURE_FLAG_REQUESTS |
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,
gboolean can_cached)
{
FwupdRelease *rel = fwupd_device_get_release_default (dev);
GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin);
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);
if (can_cached) {
app = gs_plugin_cache_lookup (plugin, id);
if (app == NULL) {
app = gs_app_new (id);
gs_plugin_cache_add (plugin, id, app);
}
} else {
app = gs_app_new (id);
}
/* 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, self->client, 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_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, TRUE);
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);
}
typedef struct {
guint n_pending_ops;
gboolean get_historical_updates;
GsAppList *list; /* (owned) */
GError *saved_error; /* (nullable) (owned) */
} ListUpdatesData;
static void
list_updates_data_free (ListUpdatesData *data)
{
g_assert (data->n_pending_ops == 0);
g_clear_object (&data->list);
g_clear_error (&data->saved_error);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ListUpdatesData, list_updates_data_free)
typedef struct {
GTask *task; /* (owned) */
FwupdDevice *device; /* (not nullable) (owned) */
} ListUpdatesDevData;
static void
list_updates_dev_data_free (ListUpdatesDevData *data)
{
g_clear_object (&data->task);
g_clear_object (&data->device);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ListUpdatesDevData, list_updates_dev_data_free)
/* @error is (nullable) (transfer full) */
static void
gs_plugin_fwupd_list_updates_finish_op (GTask *task,
GsApp *app,
GError *error)
{
g_autoptr(GError) error_owned = g_steal_pointer (&error);
ListUpdatesData *data = g_task_get_task_data (task);
if (error_owned != NULL && data->saved_error == NULL)
data->saved_error = g_steal_pointer (&error_owned);
else if (error_owned != NULL)
g_debug ("Additional error while listing apps for update: %s", error_owned->message);
else if (app != NULL)
gs_app_list_add (data->list, app);
g_assert (data->n_pending_ops > 0);
data->n_pending_ops--;
if (data->n_pending_ops > 0)
return;
if (data->saved_error != NULL)
g_task_return_error (task, g_steal_pointer (&data->saved_error));
else if (data->list == NULL)
g_task_return_pointer (task, gs_app_list_new (), g_object_unref);
else
g_task_return_pointer (task, g_steal_pointer (&data->list), g_object_unref);
}
static void
gs_plugin_fwupd_list_historical_updates_got_dev_results_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(ListUpdatesDevData) dev_data = g_steal_pointer (&user_data);
GsPlugin *plugin = GS_PLUGIN (g_task_get_source_object (dev_data->task));
g_autoptr(FwupdDevice) dev = NULL;
g_autoptr(GsApp) app = NULL;
g_autoptr(GError) local_error = NULL;
gboolean success = TRUE;
dev = fwupd_client_get_results_finish (FWUPD_CLIENT (source_object), result, &local_error);
if (dev == NULL) {
if (g_error_matches (local_error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO) ||
g_error_matches (local_error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) {
g_clear_error (&local_error);
} else {
gs_plugin_fwupd_error_convert (&local_error);
}
success = FALSE;
} else {
/* do not reuse cached GsApp for historical updates,
to not overwrite updateID of a newer version */
app = gs_plugin_fwupd_new_app_from_device (plugin, dev, FALSE);
if (app == NULL) {
g_debug ("updates historical: failed to build result for '%s' (%s)",
fwupd_device_get_name (dev),
fwupd_device_get_id (dev));
}
}
gs_plugin_fwupd_list_updates_finish_op (dev_data->task, app, success ? NULL : g_steal_pointer (&local_error));
}
static void
gs_plugin_fwupd_list_updates_got_dev_upgrades_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(ListUpdatesDevData) dev_data = g_steal_pointer (&user_data);
g_autoptr(GPtrArray) rels = NULL;
g_autoptr(GsApp) app = NULL;
g_autoptr(GError) local_error = NULL;
FwupdRelease *rel_newest;
gboolean success = TRUE;
rels = fwupd_client_get_upgrades_finish (FWUPD_CLIENT (source_object), result, &local_error);
if (rels == NULL) {
if (g_error_matches (local_error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO)) {
g_debug ("no updates for %s", fwupd_device_get_id (dev_data->device));
} else if (g_error_matches (local_error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) {
g_debug ("not supported for %s", fwupd_device_get_id (dev_data->device));
} else {
g_warning ("failed to get upgrades for %s: %s]",
fwupd_device_get_id (dev_data->device),
local_error->message);
}
success = FALSE;
/* ignore error for this device */
g_clear_error (&local_error);
}
if (success) {
GsPlugin *plugin = GS_PLUGIN (g_task_get_source_object (dev_data->task));
/* normal device update */
rel_newest = g_ptr_array_index (rels, 0);
fwupd_device_add_release (dev_data->device, rel_newest);
app = gs_plugin_fwupd_new_app (plugin, dev_data->device, &local_error);
if (app == NULL) {
g_debug ("Failed to create app for list for-update: %s", local_error->message);
success = FALSE;
/* ignore error for this device */
g_clear_error (&local_error);
}
}
/* add update descriptions for all releases inbetween */
if (success && 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;
#if AS_CHECK_VERSION(1, 0, 0)
desc = as_markup_convert (fwupd_release_get_description (rel), AS_MARKUP_KIND_TEXT, NULL);
#else
desc = as_markup_convert_simple (fwupd_release_get_description (rel), NULL);
#endif
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_plugin_fwupd_list_updates_finish_op (dev_data->task, app, success ? NULL : g_steal_pointer (&local_error));
}
static void
gs_plugin_fwupd_list_updates_got_devices_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(GTask) task = g_steal_pointer (&user_data);
g_autoptr(GPtrArray) devices = NULL;
g_autoptr(GError) local_error = NULL;
FwupdClient *client = FWUPD_CLIENT (source_object);
GCancellable *cancellable = g_task_get_cancellable (task);
GsPlugin *plugin = GS_PLUGIN (g_task_get_source_object (task));
ListUpdatesData *list_updates_data = g_task_get_task_data (task);
/* this operation is decremented at the end of the function */
g_assert (list_updates_data->n_pending_ops == 1);
/* get current list of updates */
devices = fwupd_client_get_devices_finish (client, result, &local_error);
if (devices == NULL) {
if (g_error_matches (local_error, FWUPD_ERROR, FWUPD_ERROR_NOTHING_TO_DO) ||
g_error_matches (local_error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED) ||
g_error_matches (local_error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND)) {
g_debug ("no devices (%s)", local_error->message);
} else {
g_debug ("Failed to get devices: %s", local_error->message);
}
/* ignore error */
gs_plugin_fwupd_list_updates_finish_op (task, NULL, NULL);
return;
}
for (guint i = 0; i < devices->len; i++) {
FwupdDevice *dev = g_ptr_array_index (devices, i);
g_autoptr(ListUpdatesDevData) dev_data = NULL;
g_autoptr(GsApp) app = NULL;
/* not going to have results, so save a D-Bus round-trip */
if (!fwupd_device_has_flag (dev, FWUPD_DEVICE_FLAG_SUPPORTED))
continue;
/* Were interested in historical updates rather than pending updates */
if (list_updates_data->get_historical_updates) {
list_updates_data->n_pending_ops++;
dev_data = g_new0 (ListUpdatesDevData, 1);
dev_data->task = g_object_ref (task);
dev_data->device = g_object_ref (dev);
fwupd_client_get_results_async (client,
fwupd_device_get_id (dev),
cancellable,
gs_plugin_fwupd_list_historical_updates_got_dev_results_cb,
g_steal_pointer (&dev_data));
continue;
}
/* 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_updates_data->list, app);
continue;
}
list_updates_data->n_pending_ops++;
dev_data = g_new0 (ListUpdatesDevData, 1);
dev_data->task = g_object_ref (task);
dev_data->device = g_object_ref (dev);
/* get the releases for this device and filter for validity */
fwupd_client_get_upgrades_async (client,
fwupd_device_get_id (dev),
cancellable,
gs_plugin_fwupd_list_updates_got_dev_upgrades_cb,
g_steal_pointer (&dev_data));
}
gs_plugin_fwupd_list_updates_finish_op (task, NULL, NULL);
}
static void
gs_plugin_fwupd_list_sources_got_remotes_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(GTask) task = g_steal_pointer (&user_data);
g_autoptr(GPtrArray) remotes = NULL;
g_autoptr(GsAppList) list = NULL;
g_autoptr(GMutexLocker) locker = NULL;
g_autoptr(GError) local_error = NULL;
GsPluginFwupd *self = GS_PLUGIN_FWUPD (g_task_get_source_object (task));
GsPlugin *plugin = GS_PLUGIN (self);
/* find all remotes */
remotes = fwupd_client_get_remotes_finish (FWUPD_CLIENT (source_object), result, &local_error);
if (remotes == NULL) {
gs_plugin_fwupd_error_convert (&local_error);
g_task_return_error (task, g_steal_pointer (&local_error));
return;
}
locker = g_mutex_locker_new (&self->cached_sources_mutex);
list = gs_app_list_new ();
if (self->cached_sources == NULL)
self->cached_sources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
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 = g_hash_table_lookup (self->cached_sources, id);
if (app == NULL) {
gboolean is_enabled;
#if FWUPD_CHECK_VERSION(1, 9, 4)
is_enabled = fwupd_remote_has_flag (remote, FWUPD_REMOTE_FLAG_ENABLED);
#else
is_enabled = fwupd_remote_get_enabled (remote);
#endif
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, is_enabled ?
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"));
g_hash_table_insert (self->cached_sources, g_strdup (id), app);
g_object_weak_ref (G_OBJECT (app), cached_sources_weak_ref_cb, self);
} else {
g_object_ref (app);
/* The repo-related apps are those installed; due to re-using
cached app, make sure the list is populated from fresh data. */
gs_app_list_remove_all (gs_app_get_related (app));
}
gs_app_list_add (list, app);
}
g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref);
}
static void
gs_plugin_fwupd_list_apps_async (GsPlugin *plugin,
GsAppQuery *query,
GsPluginListAppsFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin);
GsAppQueryTristate is_for_update = GS_APP_QUERY_TRISTATE_UNSET;
GsAppQueryTristate is_historical_update = GS_APP_QUERY_TRISTATE_UNSET;
GsAppQueryTristate is_source = GS_APP_QUERY_TRISTATE_UNSET;
g_autoptr(GTask) task = NULL;
task = g_task_new (plugin, cancellable, callback, user_data);
g_task_set_source_tag (task, gs_plugin_fwupd_list_apps_async);
if (query != NULL) {
is_for_update = gs_app_query_get_is_for_update (query);
is_historical_update = gs_app_query_get_is_historical_update (query);
is_source = gs_app_query_get_is_source (query);
}
/* Currently only support a subset of query properties, and only one set at once. */
if ((is_for_update == GS_APP_QUERY_TRISTATE_UNSET &&
is_historical_update == GS_APP_QUERY_TRISTATE_UNSET &&
is_source == GS_APP_QUERY_TRISTATE_UNSET) ||
is_for_update == GS_APP_QUERY_TRISTATE_FALSE ||
is_historical_update == GS_APP_QUERY_TRISTATE_FALSE ||
is_source == GS_APP_QUERY_TRISTATE_FALSE ||
gs_app_query_get_n_properties_set (query) != 1) {
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"Unsupported query");
return;
}
if (is_for_update == GS_APP_QUERY_TRISTATE_TRUE ||
is_historical_update == GS_APP_QUERY_TRISTATE_TRUE) {
g_autoptr(ListUpdatesData) data = g_new0 (ListUpdatesData, 1);
data->n_pending_ops = 1;
data->get_historical_updates = (is_historical_update == GS_APP_QUERY_TRISTATE_TRUE);
data->list = gs_app_list_new ();
g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) list_updates_data_free);
fwupd_client_get_devices_async (self->client, cancellable,
gs_plugin_fwupd_list_updates_got_devices_cb, g_steal_pointer (&task));
} else if (is_source == GS_APP_QUERY_TRISTATE_TRUE) {
fwupd_client_get_remotes_async (self->client, cancellable,
gs_plugin_fwupd_list_sources_got_remotes_cb, g_steal_pointer (&task));
} else {
g_assert_not_reached ();
}
}
static GsAppList *
gs_plugin_fwupd_list_apps_finish (GsPlugin *plugin,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_pointer (G_TASK (result), error);
}
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 doesnt complete too early. */
data->n_operations_pending = 1;
for (guint i = 0; i < remotes->len; i++) {
FwupdRemote *remote = g_ptr_array_index (remotes, i);
gboolean is_enabled;
#if FWUPD_CHECK_VERSION(1, 9, 4)
is_enabled = fwupd_remote_has_flag (remote, FWUPD_REMOTE_FLAG_ENABLED);
#else
is_enabled = fwupd_remote_get_enabled (remote);
#endif
if (!is_enabled)
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++;
#if FWUPD_CHECK_VERSION(2, 0, 0)
fwupd_client_refresh_remote_async (client, remote, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable,
refresh_remote_cb, g_object_ref (task));
#elif FWUPD_CHECK_VERSION(1, 9, 4)
fwupd_client_refresh_remote2_async (client, remote, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable,
refresh_remote_cb, g_object_ref (task));
#else
fwupd_client_refresh_remote_async (client, remote, cancellable,
refresh_remote_cb, g_object_ref (task));
#endif
}
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);
}
typedef struct {
GsApp *app; /* (owned) (not nullable) */
GFile *local_file; /* (owned) (not nullable) */
gpointer schedule_entry_handle; /* (nullable) (owned) */
} DownloadData;
static void
download_data_free (DownloadData *data)
{
/* Should have been explicitly removed from the scheduler by now. */
g_assert (data->schedule_entry_handle == NULL);
g_clear_object (&data->app);
g_clear_object (&data->local_file);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (DownloadData, download_data_free)
static void download_schedule_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void download_download_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void download_replace_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void
gs_plugin_fwupd_download_async (GsPluginFwupd *self,
GsApp *app,
gboolean interactive,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GFile *local_file;
g_autoptr(GTask) task = NULL;
DownloadData *data;
g_autoptr(DownloadData) data_owned = NULL;
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, gs_plugin_fwupd_download_async);
/* not set */
local_file = gs_app_get_local_file (app);
if (local_file == NULL) {
g_task_return_new_error (task,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_FAILED,
"not enough data for fwupd");
return;
}
data = data_owned = g_new0 (DownloadData, 1);
data->app = g_object_ref (app);
data->local_file = g_object_ref (local_file);
g_task_set_task_data (task, g_steal_pointer (&data_owned), (GDestroyNotify) download_data_free);
/* Check the cancellable, since the error return for
* g_file_query_exists() is the same as file-not-exists. */
if (g_task_return_error_if_cancelled (task))
return;
/* If the file exists already, return early */
if (g_file_query_exists (local_file, cancellable)) {
gs_app_set_size_download (app, GS_SIZE_TYPE_VALID, 0);
g_task_return_boolean (task, TRUE);
return;
}
gs_app_set_state (app, GS_APP_STATE_DOWNLOADING);
if (!interactive) {
gs_metered_block_on_download_scheduler_async (gs_metered_build_scheduler_parameters_for_app (app),
cancellable, download_schedule_cb, g_steal_pointer (&task));
} else {
download_schedule_cb (NULL, NULL, g_steal_pointer (&task));
}
}
static void
download_schedule_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);
DownloadData *data = g_task_get_task_data (task);
GCancellable *cancellable = g_task_get_cancellable (task);
const gchar *uri = gs_fwupd_app_get_update_uri (data->app);
g_autoptr(GError) local_error = NULL;
if (result != NULL &&
!gs_metered_block_on_download_scheduler_finish (result, &data->schedule_entry_handle, &local_error)) {
g_warning ("Failed to block on download scheduler: %s",
local_error->message);
g_clear_error (&local_error);
}
/* Download the firmware contents. */
fwupd_client_download_bytes_async (self->client,
uri,
FWUPD_CLIENT_DOWNLOAD_FLAG_NONE,
cancellable,
download_download_cb,
g_steal_pointer (&task));
}
static void
download_download_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
FwupdClient *client = FWUPD_CLIENT (source_object);
g_autoptr(GTask) task = g_steal_pointer (&user_data);
DownloadData *data = g_task_get_task_data (task);
GCancellable *cancellable = g_task_get_cancellable (task);
g_autoptr(GBytes) bytes = NULL;
g_autoptr(GError) local_error = NULL;
bytes = fwupd_client_download_bytes_finish (client, result, &local_error);
if (bytes == NULL) {
gs_app_set_state_recover (data->app);
gs_plugin_fwupd_error_convert (&local_error);
g_task_return_error (task, g_steal_pointer (&local_error));
return;
}
/* Now write to the file. */
g_file_replace_contents_bytes_async (data->local_file, bytes, NULL, FALSE,
G_FILE_CREATE_NONE,
cancellable,
download_replace_cb,
g_steal_pointer (&task));
}
static void
download_replace_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GFile *local_file = G_FILE (source_object);
g_autoptr(GTask) task = g_steal_pointer (&user_data);
DownloadData *data = g_task_get_task_data (task);
gboolean download_success;
g_autoptr(GError) local_error = NULL;
download_success = g_file_replace_contents_finish (local_file, result, NULL, &local_error);
/* Fire this call off into the void, its not worth tracking it.
* Dont pass a cancellable in, as the download may have been cancelled. */
if (data->schedule_entry_handle != NULL)
gs_metered_remove_from_download_scheduler_async (data->schedule_entry_handle, NULL, NULL, NULL);
gs_app_set_state_recover (data->app);
if (!download_success) {
gs_plugin_fwupd_error_convert (&local_error);
g_task_return_error (task, g_steal_pointer (&local_error));
return;
}
gs_app_set_size_download (data->app, GS_SIZE_TYPE_VALID, 0);
g_task_return_boolean (task, TRUE);
}
static gboolean
gs_plugin_fwupd_download_finish (GsPluginFwupd *self,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}
typedef struct {
GsPluginAppNeedsUserActionCallback app_needs_user_action_callback;
gpointer app_needs_user_action_data;
GsApp *app; /* (owned) (not nullable) */
gboolean interactive;
GFile *local_file; /* (owned) (not nullable) */
const gchar *device_id; /* (not nullable) */
} InstallData;
static void
install_data_free (InstallData *data)
{
g_clear_object (&data->app);
g_clear_object (&data->local_file);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (InstallData, install_data_free)
static void install_install_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void install_delete_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void install_get_device_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void install_device_request_cb (FwupdClient *client,
FwupdRequest *request,
GTask *task);
static void
gs_plugin_fwupd_install_async (GsPluginFwupd *self,
GsApp *app,
gboolean interactive,
GsPluginAppNeedsUserActionCallback app_needs_user_action_callback,
gpointer app_needs_user_action_data,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
FwupdInstallFlags install_flags = 0;
GFile *local_file;
g_autoptr(GTask) task = NULL;
InstallData *data;
g_autoptr(InstallData) data_owned = NULL;
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, gs_plugin_fwupd_install_async);
/* This function assumes that the file has already been downloaded and
* cached at @local_file. */
local_file = gs_app_get_local_file (app);
if (local_file == NULL) {
g_task_return_new_error (task,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_FAILED,
"not enough data for fwupd");
return;
}
data = data_owned = g_new0 (InstallData, 1);
data->app_needs_user_action_callback = app_needs_user_action_callback;
data->app_needs_user_action_data = app_needs_user_action_data;
data->app = g_object_ref (app);
data->interactive = interactive;
data->local_file = g_object_ref (local_file);
g_task_set_task_data (task, g_steal_pointer (&data_owned), (GDestroyNotify) install_data_free);
/* limit to single device? */
data->device_id = gs_fwupd_app_get_device_id (app);
if (data->device_id == NULL)
data->device_id = FWUPD_DEVICE_ID_ANY;
/* watch for FwupdRequest */
g_signal_connect (self->client, "device-request", G_CALLBACK (install_device_request_cb), task);
/* Store the app pointer for getting status and progress updates from
* the daemon.
*
* FIXME: This only supports one operation in parallel, so progress
* reporting with gs_app_set_progress() will get a little confused if
* there are multiple firmware updates being applied. We need more API
* from libfwupd to improve on this; see
* https://github.com/fwupd/fwupd/issues/5522. */
g_set_object (&self->app_current, app);
gs_app_set_state (app, GS_APP_STATE_INSTALLING);
fwupd_client_install_async (self->client, data->device_id,
g_file_peek_path (local_file), install_flags,
cancellable,
install_install_cb, g_steal_pointer (&task));
}
static void
install_device_request_cb (FwupdClient *client, FwupdRequest *request, GTask *task)
{
GsPluginFwupd *self = g_task_get_source_object (task);
InstallData *data = g_task_get_task_data (task);
g_autoptr(AsScreenshot) ss = as_screenshot_new ();
/* check the device ID is correct */
g_debug ("got FwupdRequest: %s for %s",
fwupd_request_get_id (request),
fwupd_request_get_device_id (request));
if (g_strcmp0 (data->device_id, FWUPD_DEVICE_ID_ANY) != 0 &&
g_strcmp0 (data->device_id, fwupd_request_get_device_id (request)) != 0) {
g_warning ("received request for %s, but updating %s",
fwupd_request_get_device_id (request),
data->device_id);
return;
}
/* image is optional, caption is required */
if (fwupd_request_get_image (request) != NULL) {
g_autoptr(AsImage) im = as_image_new ();
as_image_set_kind (im, AS_IMAGE_KIND_SOURCE);
as_image_set_url (im, fwupd_request_get_image (request));
as_screenshot_add_image (ss, im);
}
as_screenshot_set_kind (ss, AS_SCREENSHOT_KIND_DEFAULT);
as_screenshot_set_caption (ss, fwupd_request_get_message (request), NULL);
/* require the dialog */
if (fwupd_request_get_kind (request) == FWUPD_REQUEST_KIND_POST) {
gs_app_add_quirk (data->app, GS_APP_QUIRK_NEEDS_USER_ACTION);
gs_app_set_action_screenshot (data->app, ss);
} else if (data->app_needs_user_action_callback != NULL) {
data->app_needs_user_action_callback (GS_PLUGIN (self),
data->app,
ss,
data->app_needs_user_action_data);
}
}
static void
install_install_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
FwupdClient *client = FWUPD_CLIENT (source_object);
g_autoptr(GTask) task = g_steal_pointer (&user_data);
GsPluginFwupd *self = g_task_get_source_object (task);
InstallData *data = g_task_get_task_data (task);
GCancellable *cancellable = g_task_get_cancellable (task);
g_autoptr(GError) local_error = NULL;
/* no longer handling requests */
g_signal_handlers_disconnect_by_func (client, G_CALLBACK (install_device_request_cb), task);
if (!fwupd_client_install_finish (client, result, &local_error)) {
g_autoptr(GsPluginEvent) event = NULL;
/* show the user this failed */
gs_plugin_fwupd_error_convert (&local_error);
event = gs_plugin_event_new ("app", self->app_current,
"error", local_error,
NULL);
gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING);
if (data->interactive)
gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_INTERACTIVE);
gs_plugin_report_event (GS_PLUGIN (self), event);
gs_app_set_state_recover (data->app);
/* this error code *has* to be cancelled to *not* show the reboot dialog */
g_task_return_new_error (task,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_CANCELLED,
"%s", local_error->message);
return;
}
gs_app_set_state (data->app, GS_APP_STATE_INSTALLED);
/* delete the file from the cache */
g_file_delete_async (data->local_file, G_PRIORITY_DEFAULT, cancellable,
install_delete_cb, g_steal_pointer (&task));
}
static void
install_delete_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GFile *local_file = G_FILE (source_object);
g_autoptr(GTask) task = g_steal_pointer (&user_data);
GsPluginFwupd *self = g_task_get_source_object (task);
InstallData *data = g_task_get_task_data (task);
GCancellable *cancellable = g_task_get_cancellable (task);
g_autoptr(GError) local_error = NULL;
if (!g_file_delete_finish (local_file, result, &local_error) &&
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
g_task_return_error (task, g_steal_pointer (&local_error));
return;
}
g_clear_error (&local_error);
/* does the device have an update message? */
fwupd_client_get_device_by_id_async (self->client,
data->device_id,
cancellable,
install_get_device_cb,
g_steal_pointer (&task));
}
static void
install_get_device_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
FwupdClient *client = FWUPD_CLIENT (source_object);
g_autoptr(GTask) task = g_steal_pointer (&user_data);
g_autoptr(FwupdDevice) dev = NULL;
g_autoptr(GError) local_error = NULL;
dev = fwupd_client_get_device_by_id_finish (client, result, &local_error);
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", local_error->message);
}
/* success */
g_task_return_boolean (task, TRUE);
}
static gboolean
gs_plugin_fwupd_install_finish (GsPluginFwupd *self,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}
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);
}
typedef struct {
/* Input data. */
guint n_apps;
GsPluginInstallAppsFlags install_flags; /* mutually exclusive with @update_flags */
GsPluginUpdateAppsFlags update_flags; /* mutually exclusive with @install_flags */
GsPluginProgressCallback progress_callback;
gpointer progress_user_data;
GsPluginAppNeedsUserActionCallback app_needs_user_action_callback;
gpointer app_needs_user_action_data;
/* In-progress data. */
guint n_pending_ops;
GError *saved_error; /* (owned) (nullable) */
} InstallOrUpdateAppsData;
static void
install_or_update_apps_data_free (InstallOrUpdateAppsData *data)
{
/* Error should have been propagated by now, and all pending ops completed. */
g_assert (data->saved_error == NULL);
g_assert (data->n_pending_ops == 0);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (InstallOrUpdateAppsData, install_or_update_apps_data_free)
typedef struct {
GTask *task; /* (owned) */
GsApp *app; /* (owned) */
guint index; /* zero-based */
} InstallOrUpdateSingleAppData;
static void
install_or_update_single_app_data_free (InstallOrUpdateSingleAppData *data)
{
g_clear_object (&data->app);
g_clear_object (&data->task);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (InstallOrUpdateSingleAppData, install_or_update_single_app_data_free)
static gboolean
is_install_or_update_install_flag_set (GsPluginInstallAppsFlags install_flags,
GsPluginInstallAppsFlags check_flag)
{
if (((int) install_flags) == -1)
return FALSE;
return (install_flags & check_flag) != 0;
}
static gboolean
is_install_or_update_update_flag_set (GsPluginUpdateAppsFlags update_flags,
GsPluginUpdateAppsFlags check_flag)
{
if (((int) update_flags) == -1)
return FALSE;
return (update_flags & check_flag) != 0;
}
static void install_or_update_app_download_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void install_or_update_app_unlock_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void install_or_update_app_install_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void finish_install_or_update_apps_op (GTask *task,
GError *error);
static void
install_or_update_apps_impl (GsPluginFwupd *self,
GsAppList *apps,
GsPluginInstallAppsFlags install_flags,
GsPluginUpdateAppsFlags update_flags,
GsPluginProgressCallback progress_callback,
gpointer progress_user_data,
GsPluginAppNeedsUserActionCallback app_needs_user_action_callback,
gpointer app_needs_user_action_data,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
gboolean interactive = is_install_or_update_install_flag_set (install_flags, GS_PLUGIN_INSTALL_APPS_FLAGS_INTERACTIVE) ||
is_install_or_update_update_flag_set (update_flags, GS_PLUGIN_UPDATE_APPS_FLAGS_INTERACTIVE);
InstallOrUpdateAppsData *data;
g_autoptr(InstallOrUpdateAppsData) data_owned = NULL;
g_autoptr(GError) local_error = NULL;
/* Exactly one must be set */
g_assert ((int) install_flags == -1 || (int) update_flags == -1);
g_assert (!((int) install_flags == -1 && (int) update_flags == -1));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, install_or_update_apps_impl);
data = data_owned = g_new0 (InstallOrUpdateAppsData, 1);
data->install_flags = install_flags;
data->update_flags = update_flags;
data->progress_callback = progress_callback;
data->progress_user_data = progress_user_data;
data->app_needs_user_action_callback = app_needs_user_action_callback;
data->app_needs_user_action_data = app_needs_user_action_data;
data->n_apps = gs_app_list_length (apps);
g_task_set_task_data (task, g_steal_pointer (&data_owned), (GDestroyNotify) install_or_update_apps_data_free);
/* Start a load of operations in parallel to download the firmware
* files for all the apps. When each download is complete, start the
* install process for it in parallel with whatever downloads and
* installs are going on for the other apps.
*
* When all installs are finished for all apps, finish_install_or_update_apps_op()
* will return success/error for the overall #GTask. */
data->n_pending_ops = 1;
for (guint i = 0; i < gs_app_list_length (apps); i++) {
GsApp *app = gs_app_list_index (apps, i);
g_autoptr(InstallOrUpdateSingleAppData) app_data = NULL;
/* source -> remote, handled by dedicated function */
g_assert (gs_app_get_kind (app) != AS_COMPONENT_KIND_REPOSITORY);
/* only process this app if was created by this plugin */
if (!gs_app_has_management_plugin (app, GS_PLUGIN (self)))
continue;
app_data = g_new0 (InstallOrUpdateSingleAppData, 1);
app_data->index = i;
app_data->task = g_object_ref (task);
app_data->app = g_object_ref (app);
data->n_pending_ops++;
if (!is_install_or_update_install_flag_set (install_flags, GS_PLUGIN_INSTALL_APPS_FLAGS_NO_DOWNLOAD) &&
!is_install_or_update_update_flag_set (update_flags, GS_PLUGIN_UPDATE_APPS_FLAGS_NO_DOWNLOAD)) {
gs_plugin_fwupd_download_async (self, app, interactive, cancellable, install_or_update_app_download_cb, g_steal_pointer (&app_data));
} else {
install_or_update_app_download_cb (G_OBJECT (self), NULL, g_steal_pointer (&app_data));
}
}
finish_install_or_update_apps_op (task, NULL);
}
static void
install_or_update_app_download_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GsPluginFwupd *self = GS_PLUGIN_FWUPD (source_object);
g_autoptr(InstallOrUpdateSingleAppData) app_data = g_steal_pointer (&user_data);
GTask *task = app_data->task;
InstallOrUpdateAppsData *data = g_task_get_task_data (task);
GCancellable *cancellable = g_task_get_cancellable (task);
g_autoptr(GError) local_error = NULL;
if (result != NULL &&
!gs_plugin_fwupd_download_finish (self, result, &local_error)) {
finish_install_or_update_apps_op (task, g_steal_pointer (&local_error));
return;
}
if (!is_install_or_update_install_flag_set (data->install_flags, GS_PLUGIN_INSTALL_APPS_FLAGS_NO_APPLY) &&
!is_install_or_update_update_flag_set (data->update_flags, GS_PLUGIN_UPDATE_APPS_FLAGS_NO_APPLY)) {
/* locked devices need unlocking, rather than installing */
if (gs_fwupd_app_get_is_locked (app_data->app)) {
const gchar *device_id = gs_fwupd_app_get_device_id (app_data->app);
if (device_id == NULL) {
finish_install_or_update_apps_op (task, g_error_new (GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_INVALID_FORMAT,
"not enough data for fwupd unlock"));
return;
}
fwupd_client_unlock_async (self->client, device_id,
cancellable,
install_or_update_app_unlock_cb,
g_steal_pointer (&app_data));
} else {
install_or_update_app_unlock_cb (G_OBJECT (self->client), NULL, g_steal_pointer (&app_data));
}
} else {
/* Not installing the firmware or applying the update, so finish the operation now. */
finish_install_or_update_apps_op (task, NULL);
}
}
static void
install_or_update_app_unlock_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
FwupdClient *client = FWUPD_CLIENT (source_object);
g_autoptr(InstallOrUpdateSingleAppData) app_data = g_steal_pointer (&user_data);
GTask *task = app_data->task;
InstallOrUpdateAppsData *data = g_task_get_task_data (task);
gboolean interactive = is_install_or_update_install_flag_set (data->install_flags, GS_PLUGIN_INSTALL_APPS_FLAGS_INTERACTIVE) ||
is_install_or_update_update_flag_set (data->update_flags, GS_PLUGIN_UPDATE_APPS_FLAGS_INTERACTIVE);
GCancellable *cancellable = g_task_get_cancellable (task);
GsPluginFwupd *self = g_task_get_source_object (task);
GsApp *app = app_data->app;
g_autoptr(GError) local_error = NULL;
if (result != NULL &&
!fwupd_client_unlock_finish (client, result, &local_error)) {
gs_plugin_fwupd_error_convert (&local_error);
finish_install_or_update_apps_op (task, g_steal_pointer (&local_error));
return;
}
/* gs_plugin_fwupd_install_async() will install new firmware from
* scratch, or apply an update to existing firmware. */
gs_plugin_fwupd_install_async (self, app,
interactive,
data->app_needs_user_action_callback,
data->app_needs_user_action_data,
cancellable,
install_or_update_app_install_cb,
g_steal_pointer (&app_data));
}
static void
install_or_update_app_install_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GsPluginFwupd *self = GS_PLUGIN_FWUPD (source_object);
g_autoptr(InstallOrUpdateSingleAppData) app_data = g_steal_pointer (&user_data);
GTask *task = app_data->task;
InstallOrUpdateAppsData *data = g_task_get_task_data (task);
g_autoptr(GError) local_error = NULL;
if (!gs_plugin_fwupd_install_finish (self, result, &local_error)) {
gs_plugin_fwupd_error_convert (&local_error);
finish_install_or_update_apps_op (task, g_steal_pointer (&local_error));
return;
}
/* Simple progress reporting. */
if (data->progress_callback != NULL) {
data->progress_callback (GS_PLUGIN (self),
100 * ((gdouble) (app_data->index + 1) / data->n_apps),
data->progress_user_data);
}
/* App successfully installed/updated. */
finish_install_or_update_apps_op (task, NULL);
}
/* @error is (transfer full) if non-%NULL */
static void
finish_install_or_update_apps_op (GTask *task,
GError *error)
{
GsPluginFwupd *self = g_task_get_source_object (task);
InstallOrUpdateAppsData *data = g_task_get_task_data (task);
gboolean interactive = is_install_or_update_install_flag_set (data->install_flags, GS_PLUGIN_INSTALL_APPS_FLAGS_INTERACTIVE) ||
is_install_or_update_update_flag_set (data->update_flags, GS_PLUGIN_UPDATE_APPS_FLAGS_INTERACTIVE);
g_autoptr(GError) error_owned = g_steal_pointer (&error);
/* Report certain errors to the user directly. Any errors which we
* return from the `update_apps_async()` vfunc are logged but not
* displayed in the UI as the #GsPluginJobUpdateApps code cant know
* which errors are understandable by users and which arent. */
if (g_error_matches (error_owned, FWUPD_ERROR, FWUPD_ERROR_NEEDS_USER_ACTION)) {
g_autoptr(GError) event_error = NULL;
g_autoptr(GsPluginEvent) event = NULL;
event_error = g_error_copy (error_owned);
g_prefix_error_literal (&event_error, _("Firmware update could not be applied: "));
gs_plugin_fwupd_error_convert (&event_error);
event = gs_plugin_event_new ("app", self->app_current,
"error", event_error,
NULL);
gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_WARNING);
if (interactive)
gs_plugin_event_add_flag (event, GS_PLUGIN_EVENT_FLAG_INTERACTIVE);
gs_plugin_report_event (GS_PLUGIN (self), event);
}
if (error_owned != NULL && data->saved_error == NULL)
data->saved_error = g_steal_pointer (&error_owned);
else if (error_owned != NULL)
g_debug ("Additional error while installing/updating apps: %s", error_owned->message);
g_assert (data->n_pending_ops > 0);
data->n_pending_ops--;
if (data->n_pending_ops > 0)
return;
/* Get the results of the parallel ops. */
if (data->saved_error != NULL)
g_task_return_error (task, g_steal_pointer (&data->saved_error));
else
g_task_return_boolean (task, TRUE);
}
static void
gs_plugin_fwupd_update_apps_async (GsPlugin *plugin,
GsAppList *apps,
GsPluginUpdateAppsFlags flags,
GsPluginProgressCallback progress_callback,
gpointer progress_user_data,
GsPluginAppNeedsUserActionCallback app_needs_user_action_callback,
gpointer app_needs_user_action_data,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin);
install_or_update_apps_impl (self, apps, -1, flags,
progress_callback, progress_user_data,
app_needs_user_action_callback, app_needs_user_action_data,
cancellable, callback, user_data);
}
static gboolean
gs_plugin_fwupd_update_apps_finish (GsPlugin *plugin,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
gs_plugin_fwupd_install_apps_async (GsPlugin *plugin,
GsAppList *apps,
GsPluginInstallAppsFlags flags,
GsPluginProgressCallback progress_callback,
gpointer progress_user_data,
GsPluginAppNeedsUserActionCallback app_needs_user_action_callback,
gpointer app_needs_user_action_data,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GsPluginFwupd *self = GS_PLUGIN_FWUPD (plugin);
install_or_update_apps_impl (self, apps, flags, -1,
progress_callback, progress_user_data,
app_needs_user_action_callback, app_needs_user_action_data,
cancellable, callback, user_data);
}
static gboolean
gs_plugin_fwupd_install_apps_finish (GsPlugin *plugin,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}
static void gs_plugin_fwupd_file_to_app_got_content_type_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void
gs_plugin_fwupd_file_to_app_async (GsPlugin *plugin,
GFile *file,
GsPluginFileToAppFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
task = gs_plugin_file_to_app_data_new_task (plugin, file, flags, cancellable, callback, user_data);
g_task_set_source_tag (task, gs_plugin_fwupd_file_to_app_async);
gs_utils_get_content_type_async (file, cancellable,
gs_plugin_fwupd_file_to_app_got_content_type_cb,
g_steal_pointer (&task));
}
static void
gs_plugin_fwupd_file_to_app_got_content_type_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(GTask) task = g_steal_pointer (&user_data);
g_autoptr(GPtrArray) devices = NULL;
g_autoptr(GsAppList) list = NULL;
g_autoptr(GError) local_error = NULL;
g_autofree gchar *content_type = NULL;
g_autofree gchar *filename = NULL;
GsPluginFileToAppData *data = g_task_get_task_data (task);
GsPluginFwupd *self = GS_PLUGIN_FWUPD (g_task_get_source_object (task));
GsPlugin *plugin = GS_PLUGIN (self);
const gchar *mimetypes[] = {
"application/vnd.ms-cab-compressed",
NULL };
content_type = gs_utils_get_content_type_finish (G_FILE (source_object), result, &local_error);
if (content_type == NULL) {
g_task_return_error (task, g_steal_pointer (&local_error));
return;
}
if (!g_strv_contains (mimetypes, content_type)) {
g_task_return_pointer (task, gs_app_list_new (), g_object_unref);
return;
}
filename = g_file_get_path (data->file);
devices = fwupd_client_get_details (self->client, filename, g_task_get_cancellable (task), &local_error);
if (devices == NULL) {
gs_plugin_fwupd_error_convert (&local_error);
g_task_return_error (task, g_steal_pointer (&local_error));
return;
}
list = gs_app_list_new ();
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, TRUE);
/* 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_set_local_file (app, data->file);
gs_app_list_add (list, app);
}
g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref);
}
static GsAppList *
gs_plugin_fwupd_file_to_app_finish (GsPlugin *plugin,
GAsyncResult *result,
GError **error)
{
return g_task_propagate_pointer (G_TASK (result), error);
}
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) {
gboolean is_enabled;
#if FWUPD_CHECK_VERSION(1, 9, 4)
is_enabled = fwupd_remote_has_flag (remote, FWUPD_REMOTE_FLAG_ENABLED);
#else
is_enabled = fwupd_remote_get_enabled (remote);
#endif
if (is_enabled &&
fwupd_remote_get_kind (remote) != FWUPD_REMOTE_KIND_LOCAL &&
!remote_cache_is_expired (remote, cache_age)) {
GCancellable *cancellable = g_task_get_cancellable (task);
#if FWUPD_CHECK_VERSION(2, 0, 0)
fwupd_client_refresh_remote_async (self->client, remote, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable,
gs_plugin_fwupd_enable_repository_remote_refresh_ready_cb,
g_steal_pointer (&task));
#elif FWUPD_CHECK_VERSION(1, 9, 4)
fwupd_client_refresh_remote2_async (self->client, remote, FWUPD_CLIENT_DOWNLOAD_FLAG_NONE, cancellable,
gs_plugin_fwupd_enable_repository_remote_refresh_ready_cb,
g_steal_pointer (&task));
#else
fwupd_client_refresh_remote_async (self->client, remote, cancellable,
gs_plugin_fwupd_enable_repository_remote_refresh_ready_cb,
g_steal_pointer (&task));
#endif
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;
object_class->finalize = gs_plugin_fwupd_finalize;
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;
plugin_class->install_apps_async = gs_plugin_fwupd_install_apps_async;
plugin_class->install_apps_finish = gs_plugin_fwupd_install_apps_finish;
plugin_class->update_apps_async = gs_plugin_fwupd_update_apps_async;
plugin_class->update_apps_finish = gs_plugin_fwupd_update_apps_finish;
plugin_class->list_apps_async = gs_plugin_fwupd_list_apps_async;
plugin_class->list_apps_finish = gs_plugin_fwupd_list_apps_finish;
plugin_class->file_to_app_async = gs_plugin_fwupd_file_to_app_async;
plugin_class->file_to_app_finish = gs_plugin_fwupd_file_to_app_finish;
}
GType
gs_plugin_query_type (void)
{
return GS_TYPE_PLUGIN_FWUPD;
}