2310 lines
80 KiB
C
2310 lines
80 KiB
C
/* -*- 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;
|
||
|
||
/* We’re 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 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);
|
||
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, it’s not worth tracking it.
|
||
* Don’t 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 can’t know
|
||
* which errors are understandable by users and which aren’t. */
|
||
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;
|
||
}
|