1
0
Fork 0
gnome-software/lib/gs-plugin.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

2128 lines
60 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-2016 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2014-2020 Kalev Lember <klember@redhat.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/**
* SECTION:gs-plugin
* @title: GsPlugin Helpers
* @include: gnome-software.h
* @stability: Unstable
* @short_description: Runtime-loaded modules providing functionality
*
* Plugins are modules that are loaded at runtime to provide information
* about requests and to service user actions like installing, removing
* and updating.
* This allows different distributions to pick and choose how the
* application installer gathers data.
*
* Plugins also have a priority system where the largest number gets
* run first. That means if one plugin requires some property or
* metadata set by another plugin then it **must** depend on the other
* plugin to be run in the correct order.
*
* As a general rule, try to make plugins as small and self-contained
* as possible and remember to cache as much data as possible for speed.
* Memory is cheap, time less so.
*/
#include "config.h"
#include <gio/gdesktopappinfo.h>
#include <gdk/gdk.h>
#include <string.h>
#include "gs-app-list-private.h"
#include "gs-download-utils.h"
#include "gs-enums.h"
#include "gs-os-release.h"
#include "gs-plugin-private.h"
#include "gs-plugin.h"
#include "gs-utils.h"
typedef struct
{
GHashTable *cache;
GMutex cache_mutex;
GModule *module;
GsPluginFlags flags;
GPtrArray *rules[GS_PLUGIN_RULE_LAST];
GHashTable *vfuncs; /* string:pointer */
GMutex vfuncs_mutex;
gboolean enabled;
guint interactive_cnt;
GMutex interactive_mutex;
gchar *language; /* allow-none */
gchar *name;
gchar *appstream_id;
guint scale;
guint order;
guint priority;
guint timer_id;
GMutex timer_mutex;
GNetworkMonitor *network_monitor;
GDBusConnection *session_bus_connection; /* (owned) (not nullable) */
GDBusConnection *system_bus_connection; /* (owned) (not nullable) */
} GsPluginPrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GsPlugin, gs_plugin, G_TYPE_OBJECT)
G_DEFINE_QUARK (gs-plugin-error-quark, gs_plugin_error)
typedef enum {
PROP_FLAGS = 1,
PROP_SCALE,
PROP_SESSION_BUS_CONNECTION,
PROP_SYSTEM_BUS_CONNECTION,
} GsPluginProperty;
static GParamSpec *obj_props[PROP_SYSTEM_BUS_CONNECTION + 1] = { NULL, };
enum {
SIGNAL_UPDATES_CHANGED,
SIGNAL_STATUS_CHANGED,
SIGNAL_RELOAD,
SIGNAL_REPORT_EVENT,
SIGNAL_ALLOW_UPDATES,
SIGNAL_BASIC_AUTH_START,
SIGNAL_REPOSITORY_CHANGED,
SIGNAL_ASK_UNTRUSTED,
SIGNAL_LAST
};
static guint signals [SIGNAL_LAST] = { 0 };
typedef const gchar **(*GsPluginGetDepsFunc) (GsPlugin *plugin);
/**
* gs_plugin_status_to_string:
* @status: a #GsPluginStatus, e.g. %GS_PLUGIN_STATUS_DOWNLOADING
*
* Converts the #GsPluginStatus enum to a string.
*
* Returns: the string representation, or "unknown"
*
* Since: 3.22
**/
const gchar *
gs_plugin_status_to_string (GsPluginStatus status)
{
if (status == GS_PLUGIN_STATUS_WAITING)
return "waiting";
if (status == GS_PLUGIN_STATUS_FINISHED)
return "finished";
if (status == GS_PLUGIN_STATUS_SETUP)
return "setup";
if (status == GS_PLUGIN_STATUS_DOWNLOADING)
return "downloading";
if (status == GS_PLUGIN_STATUS_QUERYING)
return "querying";
if (status == GS_PLUGIN_STATUS_INSTALLING)
return "installing";
if (status == GS_PLUGIN_STATUS_REMOVING)
return "removing";
return "unknown";
}
/**
* gs_plugin_set_name:
* @plugin: a #GsPlugin
* @name: a plugin name
*
* Sets the name of the plugin.
*
* Plugins are not required to set the plugin name as it is automatically set
* from the `.so` filename.
*
* Since: 3.26
**/
void
gs_plugin_set_name (GsPlugin *plugin, const gchar *name)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
if (priv->name != NULL)
g_free (priv->name);
priv->name = g_strdup (name);
}
/**
* gs_plugin_create:
* @filename: an absolute filename
* @session_bus_connection: (not nullable) (transfer none): a session bus
* connection to use
* @system_bus_connection: (not nullable) (transfer none): a system bus
* connection to use
* @error: a #GError, or %NULL
*
* Creates a new plugin from an external module.
*
* Returns: (transfer full): the #GsPlugin, or %NULL on error
*
* Since: 43
**/
GsPlugin *
gs_plugin_create (const gchar *filename,
GDBusConnection *session_bus_connection,
GDBusConnection *system_bus_connection,
GError **error)
{
GsPlugin *plugin = NULL;
GsPluginPrivate *priv;
g_autofree gchar *basename = NULL;
GModule *module = NULL;
GType (*query_type_function) (void) = NULL;
GType plugin_type;
/* get the plugin name from the basename */
basename = g_path_get_basename (filename);
if (!g_str_has_prefix (basename, "libgs_plugin_")) {
g_set_error (error,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_FAILED,
"plugin filename has wrong prefix: %s",
filename);
return NULL;
}
g_strdelimit (basename, ".", '\0');
/* create new plugin */
module = g_module_open (filename, 0);
if (module == NULL ||
!g_module_symbol (module, "gs_plugin_query_type", (gpointer *) &query_type_function)) {
g_set_error (error,
GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_FAILED,
"failed to open plugin %s: %s",
filename, g_module_error ());
if (module != NULL)
g_module_close (module);
return NULL;
}
/* Make the module resident so it cant be unloaded: without using a
* full #GTypePlugin implementation for the modules, its not safe to
* re-load a module and re-register its types with GObject, as that will
* confuse the GType system. */
g_module_make_resident (module);
plugin_type = query_type_function ();
g_assert (g_type_is_a (plugin_type, GS_TYPE_PLUGIN));
plugin = g_object_new (plugin_type,
"session-bus-connection", session_bus_connection,
"system-bus-connection", system_bus_connection,
NULL);
priv = gs_plugin_get_instance_private (plugin);
priv->module = g_steal_pointer (&module);
gs_plugin_set_name (plugin, basename + 13);
return plugin;
}
static void
gs_plugin_dispose (GObject *object)
{
GsPlugin *plugin = GS_PLUGIN (object);
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_clear_object (&priv->session_bus_connection);
g_clear_object (&priv->system_bus_connection);
G_OBJECT_CLASS (gs_plugin_parent_class)->dispose (object);
}
static void
gs_plugin_finalize (GObject *object)
{
GsPlugin *plugin = GS_PLUGIN (object);
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
guint i;
for (i = 0; i < GS_PLUGIN_RULE_LAST; i++)
g_ptr_array_unref (priv->rules[i]);
if (priv->timer_id > 0)
g_source_remove (priv->timer_id);
g_free (priv->name);
g_free (priv->appstream_id);
g_free (priv->language);
if (priv->network_monitor != NULL)
g_object_unref (priv->network_monitor);
g_hash_table_unref (priv->cache);
g_hash_table_unref (priv->vfuncs);
g_mutex_clear (&priv->cache_mutex);
g_mutex_clear (&priv->interactive_mutex);
g_mutex_clear (&priv->timer_mutex);
g_mutex_clear (&priv->vfuncs_mutex);
if (priv->module != NULL)
g_module_close (priv->module);
G_OBJECT_CLASS (gs_plugin_parent_class)->finalize (object);
}
/**
* gs_plugin_get_symbol: (skip)
* @plugin: a #GsPlugin
* @function_name: a symbol name
*
* Gets the symbol from the module that backs the plugin. If the plugin is not
* enabled then no symbol is returned.
*
* Returns: the pointer to the symbol, or %NULL
*
* Since: 3.22
**/
gpointer
gs_plugin_get_symbol (GsPlugin *plugin, const gchar *function_name)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
gpointer func = NULL;
g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->vfuncs_mutex);
g_return_val_if_fail (function_name != NULL, NULL);
/* disabled plugins shouldn't be checked */
if (!priv->enabled)
return NULL;
/* look up the symbol from the cache */
if (g_hash_table_lookup_extended (priv->vfuncs, function_name, NULL, &func))
return func;
/* look up the symbol using the elf headers */
g_module_symbol (priv->module, function_name, &func);
g_hash_table_insert (priv->vfuncs, g_strdup (function_name), func);
return func;
}
/**
* gs_plugin_get_enabled:
* @plugin: a #GsPlugin
*
* Gets if the plugin is enabled.
*
* Returns: %TRUE if enabled
*
* Since: 3.22
**/
gboolean
gs_plugin_get_enabled (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
return priv->enabled;
}
/**
* gs_plugin_set_enabled:
* @plugin: a #GsPlugin
* @enabled: the enabled state
*
* Enables or disables a plugin.
* This is normally only called from the init function for a #GsPlugin instance.
*
* Since: 3.22
**/
void
gs_plugin_set_enabled (GsPlugin *plugin, gboolean enabled)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
priv->enabled = enabled;
}
void
gs_plugin_interactive_inc (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->interactive_mutex);
priv->interactive_cnt++;
gs_plugin_add_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE);
}
void
gs_plugin_interactive_dec (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->interactive_mutex);
if (priv->interactive_cnt > 0)
priv->interactive_cnt--;
if (priv->interactive_cnt == 0)
gs_plugin_remove_flags (plugin, GS_PLUGIN_FLAGS_INTERACTIVE);
}
/**
* gs_plugin_get_name:
* @plugin: a #GsPlugin
*
* Gets the plugin name.
*
* Returns: a string, e.g. "fwupd"
*
* Since: 3.22
**/
const gchar *
gs_plugin_get_name (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
return priv->name;
}
/**
* gs_plugin_get_appstream_id:
* @plugin: a #GsPlugin
*
* Gets the plugin AppStream ID.
*
* Returns: a string, e.g. `org.gnome.Software.Plugin.Epiphany`
*
* Since: 3.24
**/
const gchar *
gs_plugin_get_appstream_id (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
return priv->appstream_id;
}
/**
* gs_plugin_set_appstream_id:
* @plugin: a #GsPlugin
* @appstream_id: an appstream ID, e.g. `org.gnome.Software.Plugin.Epiphany`
*
* Sets the plugin AppStream ID.
*
* Since: 3.24
**/
void
gs_plugin_set_appstream_id (GsPlugin *plugin, const gchar *appstream_id)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_free (priv->appstream_id);
priv->appstream_id = g_strdup (appstream_id);
}
/**
* gs_plugin_get_scale:
* @plugin: a #GsPlugin
*
* Gets the window scale factor.
*
* Returns: the factor, usually 1 for standard screens or 2 for HiDPI
*
* Since: 3.22
**/
guint
gs_plugin_get_scale (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
return priv->scale;
}
/**
* gs_plugin_set_scale:
* @plugin: a #GsPlugin
* @scale: the window scale factor, usually 1 for standard screens or 2 for HiDPI
*
* Sets the window scale factor.
*
* Since: 3.22
**/
void
gs_plugin_set_scale (GsPlugin *plugin, guint scale)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
if (priv->scale != scale) {
priv->scale = scale;
g_object_notify_by_pspec (G_OBJECT (plugin), obj_props[PROP_SCALE]);
}
}
/**
* gs_plugin_get_order:
* @plugin: a #GsPlugin
*
* Gets the plugin order, where higher numbers are run after lower
* numbers.
*
* Returns: the integer value
*
* Since: 3.22
**/
guint
gs_plugin_get_order (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
return priv->order;
}
/**
* gs_plugin_set_order:
* @plugin: a #GsPlugin
* @order: a integer value
*
* Sets the plugin order, where higher numbers are run after lower
* numbers.
*
* Since: 3.22
**/
void
gs_plugin_set_order (GsPlugin *plugin, guint order)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
priv->order = order;
}
/**
* gs_plugin_get_priority:
* @plugin: a #GsPlugin
*
* Gets the plugin priority, where higher values will be chosen where
* multiple #GsApp's match a specific rule.
*
* Returns: the integer value
*
* Since: 3.22
**/
guint
gs_plugin_get_priority (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
return priv->priority;
}
/**
* gs_plugin_set_priority:
* @plugin: a #GsPlugin
* @priority: a integer value
*
* Sets the plugin priority, where higher values will be chosen where
* multiple #GsApp's match a specific rule.
*
* Since: 3.22
**/
void
gs_plugin_set_priority (GsPlugin *plugin, guint priority)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
priv->priority = priority;
}
/**
* gs_plugin_get_language:
* @plugin: a #GsPlugin
*
* Gets the user language from the locale. This is the first component of the
* locale.
*
* Typically you should use the full locale rather than the language, as the
* same language can be used quite differently in different territories.
*
* Returns: the language string, e.g. `fr`
*
* Since: 3.22
**/
const gchar *
gs_plugin_get_language (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
return priv->language;
}
/**
* gs_plugin_set_language:
* @plugin: a #GsPlugin
* @language: a language string, e.g. "fr"
*
* Sets the plugin language.
*
* Since: 3.22
**/
void
gs_plugin_set_language (GsPlugin *plugin, const gchar *language)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_free (priv->language);
priv->language = g_strdup (language);
}
/**
* gs_plugin_set_network_monitor:
* @plugin: a #GsPlugin
* @monitor: a #GNetworkMonitor
*
* Sets the network monitor so that plugins can check the state of the network.
*
* Since: 3.28
**/
void
gs_plugin_set_network_monitor (GsPlugin *plugin, GNetworkMonitor *monitor)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_set_object (&priv->network_monitor, monitor);
}
/**
* gs_plugin_get_network_available:
* @plugin: a #GsPlugin
*
* Gets whether a network connectivity is available.
*
* Returns: %TRUE if a network is available.
*
* Since: 3.28
**/
gboolean
gs_plugin_get_network_available (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
if (priv->network_monitor == NULL) {
g_debug ("no network monitor, so returning network-available=TRUE");
return TRUE;
}
return g_network_monitor_get_network_available (priv->network_monitor);
}
/**
* gs_plugin_has_flags:
* @plugin: a #GsPlugin
* @flags: a #GsPluginFlags, e.g. %GS_PLUGIN_FLAGS_INTERACTIVE
*
* Finds out if a plugin has a specific flag set.
*
* Returns: TRUE if the flag is set
*
* Since: 3.22
**/
gboolean
gs_plugin_has_flags (GsPlugin *plugin, GsPluginFlags flags)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
return (priv->flags & flags) > 0;
}
/**
* gs_plugin_add_flags:
* @plugin: a #GsPlugin
* @flags: a #GsPluginFlags, e.g. %GS_PLUGIN_FLAGS_INTERACTIVE
*
* Adds specific flags to the plugin.
*
* Since: 3.22
**/
void
gs_plugin_add_flags (GsPlugin *plugin, GsPluginFlags flags)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
priv->flags |= flags;
g_object_notify_by_pspec (G_OBJECT (plugin), obj_props[PROP_FLAGS]);
}
/**
* gs_plugin_remove_flags:
* @plugin: a #GsPlugin
* @flags: a #GsPluginFlags, e.g. %GS_PLUGIN_FLAGS_INTERACTIVE
*
* Removes specific flags from the plugin.
*
* Since: 3.22
**/
void
gs_plugin_remove_flags (GsPlugin *plugin, GsPluginFlags flags)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
priv->flags &= ~flags;
g_object_notify_by_pspec (G_OBJECT (plugin), obj_props[PROP_FLAGS]);
}
/**
* gs_plugin_add_rule:
* @plugin: a #GsPlugin
* @rule: a #GsPluginRule, e.g. %GS_PLUGIN_RULE_CONFLICTS
* @name: a plugin name, e.g. "appstream"
*
* If the plugin name is found, the rule will be used to sort the plugin list,
* for example the plugin specified by @name will be ordered after this plugin
* when %GS_PLUGIN_RULE_RUN_AFTER is used.
*
* NOTE: The depsolver is iterative and may not solve overly-complicated rules;
* If depsolving fails then gnome-software will not start.
*
* Since: 3.22
**/
void
gs_plugin_add_rule (GsPlugin *plugin, GsPluginRule rule, const gchar *name)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_ptr_array_add (priv->rules[rule], g_strdup (name));
}
/**
* gs_plugin_get_rules:
* @plugin: a #GsPlugin
* @rule: a #GsPluginRule, e.g. %GS_PLUGIN_RULE_CONFLICTS
*
* Gets the plugin IDs that should be run after this plugin.
*
* Returns: (element-type utf8) (transfer none): the list of plugin names, e.g. ['appstream']
*
* Since: 3.22
**/
GPtrArray *
gs_plugin_get_rules (GsPlugin *plugin, GsPluginRule rule)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
return priv->rules[rule];
}
/**
* gs_plugin_check_distro_id:
* @plugin: a #GsPlugin
* @distro_id: a distro ID, e.g. "fedora"
*
* Checks if the distro is compatible.
*
* Returns: %TRUE if compatible
*
* Since: 3.22
**/
gboolean
gs_plugin_check_distro_id (GsPlugin *plugin, const gchar *distro_id)
{
g_autoptr(GError) error = NULL;
g_autoptr(GsOsRelease) os_release = NULL;
const gchar *id = NULL;
/* load /etc/os-release */
os_release = gs_os_release_new (&error);
if (os_release == NULL) {
g_debug ("could not parse os-release: %s", error->message);
return FALSE;
}
/* check that we are running on Fedora */
id = gs_os_release_get_id (os_release);
if (id == NULL) {
g_debug ("could not get distro ID");
return FALSE;
}
if (g_strcmp0 (id, distro_id) != 0)
return FALSE;
return TRUE;
}
typedef struct {
GWeakRef plugin_weak; /* (element-type GsPlugin) */
GsApp *app; /* (owned) */
GsPluginStatus status;
guint percentage;
} GsPluginStatusHelper;
static void
gs_plugin_status_helper_free (GsPluginStatusHelper *helper)
{
g_weak_ref_clear (&helper->plugin_weak);
g_clear_object (&helper->app);
g_slice_free (GsPluginStatusHelper, helper);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GsPluginStatusHelper, gs_plugin_status_helper_free)
static gboolean
gs_plugin_status_update_cb (gpointer user_data)
{
GsPluginStatusHelper *helper = (GsPluginStatusHelper *) user_data;
g_autoptr(GsPlugin) plugin = NULL;
/* Does the plugin still exist? */
plugin = g_weak_ref_get (&helper->plugin_weak);
if (plugin != NULL)
g_signal_emit (plugin,
signals[SIGNAL_STATUS_CHANGED], 0,
helper->app,
helper->status);
return G_SOURCE_REMOVE;
}
/**
* gs_plugin_status_update:
* @plugin: a #GsPlugin
* @app: a #GsApp, or %NULL
* @status: a #GsPluginStatus, e.g. %GS_PLUGIN_STATUS_DOWNLOADING
*
* Update the state of the plugin so any UI can be updated.
*
* Since: 3.22
**/
void
gs_plugin_status_update (GsPlugin *plugin, GsApp *app, GsPluginStatus status)
{
g_autoptr(GsPluginStatusHelper) helper = NULL;
g_autoptr(GSource) idle_source = NULL;
helper = g_slice_new0 (GsPluginStatusHelper);
g_weak_ref_init (&helper->plugin_weak, plugin);
helper->status = status;
if (app != NULL)
helper->app = g_object_ref (app);
idle_source = g_idle_source_new ();
g_source_set_callback (idle_source, gs_plugin_status_update_cb, g_steal_pointer (&helper), (GDestroyNotify) gs_plugin_status_helper_free);
g_source_attach (idle_source, NULL);
}
typedef struct {
GsPlugin *plugin;
gchar *remote;
gchar *realm;
GCallback callback;
gpointer user_data;
} GsPluginBasicAuthHelper;
static gboolean
gs_plugin_basic_auth_start_cb (gpointer user_data)
{
GsPluginBasicAuthHelper *helper = user_data;
g_signal_emit (helper->plugin,
signals[SIGNAL_BASIC_AUTH_START], 0,
helper->remote,
helper->realm,
helper->callback,
helper->user_data);
g_free (helper->remote);
g_free (helper->realm);
g_slice_free (GsPluginBasicAuthHelper, helper);
return FALSE;
}
/**
* gs_plugin_basic_auth_start:
* @plugin: a #GsPlugin
* @remote: a string
* @realm: a string
* @callback: callback to invoke to submit the user/password
* @user_data: callback data to pass to the callback
*
* Emit the basic-auth-start signal in the main thread.
*
* Since: 3.38
**/
void
gs_plugin_basic_auth_start (GsPlugin *plugin,
const gchar *remote,
const gchar *realm,
GCallback callback,
gpointer user_data)
{
GsPluginBasicAuthHelper *helper;
g_autoptr(GSource) idle_source = NULL;
helper = g_slice_new0 (GsPluginBasicAuthHelper);
helper->plugin = plugin;
helper->remote = g_strdup (remote);
helper->realm = g_strdup (realm);
helper->callback = callback;
helper->user_data = user_data;
idle_source = g_idle_source_new ();
g_source_set_callback (idle_source, gs_plugin_basic_auth_start_cb, helper, NULL);
g_source_attach (idle_source, NULL);
}
static const gchar *
get_desktop_id_to_launch (GsApp *app)
{
const gchar *desktop_id = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_DESKTOP_ID);
if (desktop_id == NULL)
desktop_id = gs_app_get_id (app);
return desktop_id;
}
static gboolean
launch_app_info (GAppInfo *appinfo,
GError **error)
{
GdkDisplay *display;
g_autoptr(GAppLaunchContext) context = NULL;
g_assert (appinfo != NULL);
display = gdk_display_get_default ();
context = G_APP_LAUNCH_CONTEXT (gdk_display_get_app_launch_context (display));
return g_app_info_launch (appinfo, NULL, context, error);
}
/**
* gs_plugin_app_launch_async:
* @plugin: a #GsPlugin
* @app: a #GsApp
* @flags: a bit-or of #GsPluginLaunchFlags
* @cancellable: a #GCancellable, or %NULL
* @callback: (not nullable): a #GAsyncReadyCallback to call when the request is satisfied
* @user_data: (closure callback) (scope async): data to pass to @callback
*
* Asynchronously launches the application using #GAppInfo.
* Finish the call with gs_plugin_app_launch_finish().
*
* The function also verifies whether the @plugin can handle the @app,
* in a sense of gs_app_has_management_plugin(), and if not then does
* nothing.
*
* Since: 47
**/
void
gs_plugin_app_launch_async (GsPlugin *plugin,
GsApp *app,
GsPluginLaunchFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
const gchar *desktop_id;
g_autoptr(GTask) task = NULL;
g_autoptr(GAppInfo) appinfo = NULL;
g_return_if_fail (GS_IS_PLUGIN (plugin));
g_return_if_fail (GS_IS_APP (app));
g_return_if_fail (callback != NULL);
task = g_task_new (plugin, cancellable, callback, user_data);
g_task_set_source_tag (task, gs_plugin_app_launch_async);
/* only process this app if was created by this plugin */
if (!gs_app_has_management_plugin (app, plugin)) {
g_task_return_pointer (task, NULL, NULL);
return;
}
desktop_id = get_desktop_id_to_launch (app);
if (desktop_id == NULL) {
g_task_return_new_error (task, GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_NOT_SUPPORTED,
"no desktop file for app: %s",
gs_app_get_name (app));
return;
}
appinfo = G_APP_INFO (gs_utils_get_desktop_app_info (desktop_id));
if (appinfo == NULL) {
g_task_return_new_error (task, GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_NOT_SUPPORTED,
"no such desktop file: %s",
desktop_id);
return;
}
/* the actual launch happens in the _finish() function,
which should be in the main thread */
g_task_return_pointer (task, g_steal_pointer (&appinfo), g_object_unref);
}
/**
* gs_plugin_app_launch_finish:
* @plugin: a #GsPlugin
* @result: an async result
* @error: a #GError or %NULL
*
* Finishes operation started by gs_plugin_app_launch_async().
* This function should be called from the main thread.
*
* Returns: whether succeeded
*
* Since: 47
**/
gboolean
gs_plugin_app_launch_finish (GsPlugin *plugin,
GAsyncResult *result,
GError **error)
{
g_autoptr(GAppInfo) appinfo = NULL;
g_return_val_if_fail (g_task_is_valid (G_TASK (result), plugin), FALSE);
g_return_val_if_fail (g_async_result_is_tagged (result, gs_plugin_app_launch_async), FALSE);
appinfo = g_task_propagate_pointer (G_TASK (result), error);
if (appinfo == NULL)
return TRUE;
return launch_app_info (appinfo, error);
}
static GDesktopAppInfo *
check_directory_for_desktop_file (GsPlugin *plugin,
GsApp *app,
GsPluginPickDesktopFileCallback cb,
gpointer user_data,
const gchar *desktop_id,
const gchar *data_dir)
{
g_autofree gchar *filename = NULL;
g_autoptr(GKeyFile) key_file = NULL;
gboolean found, any_found = FALSE;
filename = g_build_filename (data_dir, "applications", desktop_id, NULL);
key_file = g_key_file_new ();
found = g_key_file_load_from_file (key_file, filename, G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
if (found && cb (plugin, app, filename, key_file, user_data)) {
g_autoptr(GDesktopAppInfo) appinfo = NULL;
g_debug ("Found '%s' for app '%s' and picked it", filename, desktop_id);
/* use the filename, not the key_file, to enable bus activation from the .desktop file */
appinfo = g_desktop_app_info_new_from_filename (filename);
if (appinfo != NULL)
return g_steal_pointer (&appinfo);
g_debug ("Failed to load '%s' as a GDesktopAppInfo", filename);
return NULL;
} else if (found) {
g_debug ("Found '%s' for app '%s', but did not pick it", filename, desktop_id);
any_found = TRUE;
}
if (!g_str_has_suffix (desktop_id, ".desktop")) {
g_autofree gchar *desktop_filename = g_strconcat (filename, ".desktop", NULL);
found = g_key_file_load_from_file (key_file, desktop_filename, G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
if (found && cb (plugin, app, desktop_filename, key_file, user_data)) {
g_autoptr(GDesktopAppInfo) appinfo = NULL;
g_debug ("Found '%s' for app '%s' and picked it", desktop_filename, desktop_id);
/* use the filename, not the key_file, to enable bus activation from the .desktop file */
appinfo = g_desktop_app_info_new_from_filename (desktop_filename);
if (appinfo != NULL)
return g_steal_pointer (&appinfo);
g_debug ("Failed to load '%s' as a GDesktopAppInfo", desktop_filename);
return NULL;
} else if (found) {
g_debug ("Found '%s' for app '%s', but did not pick it", desktop_filename, desktop_id);
any_found = TRUE;
}
}
if (!any_found)
g_debug ("Did not find any appropriate .desktop file for '%s' in '%s/applications/'", desktop_id, data_dir);
return NULL;
}
typedef struct {
GsApp *app; /* (owned) */
GsPluginPickDesktopFileCallback cb;
gpointer cb_user_data;
GAppInfo *appinfo; /* (owned) (nullable) (out) */
} LaunchFilteredData;
static void
launch_filtered_data_free (LaunchFilteredData *data)
{
g_clear_object (&data->app);
g_clear_object (&data->appinfo);
g_free (data);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (LaunchFilteredData, launch_filtered_data_free)
static void
launch_filtered_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
g_autoptr(GDesktopAppInfo) appinfo = NULL;
GsPlugin *plugin = GS_PLUGIN (source_object);
LaunchFilteredData *data = task_data;
const gchar *desktop_id;
desktop_id = get_desktop_id_to_launch (data->app);
/* the caller verified it's set */
g_assert (desktop_id != NULL);
/* First, the configs. Highest priority: the user's ~/.config */
appinfo = check_directory_for_desktop_file (plugin, data->app, data->cb, data->cb_user_data, desktop_id, g_get_user_config_dir ());
if (appinfo == NULL) {
/* Next, the system configs (/etc/xdg, and so on). */
const gchar * const *dirs;
dirs = g_get_system_config_dirs ();
for (guint i = 0; dirs[i] && appinfo == NULL; i++) {
appinfo = check_directory_for_desktop_file (plugin, data->app, data->cb, data->cb_user_data, desktop_id, dirs[i]);
}
}
if (appinfo == NULL) {
/* Now the data. Highest priority: the user's ~/.local/share/applications */
appinfo = check_directory_for_desktop_file (plugin, data->app, data->cb, data->cb_user_data, desktop_id, g_get_user_data_dir ());
}
if (appinfo == NULL) {
/* Following that, XDG_DATA_DIRS/applications, in order */
const gchar * const *dirs;
dirs = g_get_system_data_dirs ();
for (guint i = 0; dirs[i] && appinfo == NULL; i++) {
appinfo = check_directory_for_desktop_file (plugin, data->app, data->cb, data->cb_user_data, desktop_id, dirs[i]);
}
}
if (appinfo == NULL) {
g_task_return_new_error (task, GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_NOT_SUPPORTED,
"no appropriate desktop file found: %s",
desktop_id);
return;
}
/* the actual launch happens in the _finish() function,
which should be in the main thread */
data->appinfo = (GAppInfo *) g_steal_pointer (&appinfo);
g_task_return_boolean (task, TRUE);
}
/**
* gs_plugin_app_launch_filtered_async:
* @plugin: a #GsPlugin
* @app: a #GsApp to launch
* @flags: a bit-or of #GsPluginLaunchFlags
* @cb: a callback to pick the correct .desktop file
* @cb_user_data: (closure cb) (scope async): user data for the @cb
* @cancellable: a #GCancellable or %NULL
* @async_callback: (not nullable): async call ready callback
* @async_user_data: (closure async_callback) (scope async): user data for the @async_callback
*
* Asynchronosuly launches @app, using the .desktop file picked by the @cb.
* This can help in case multiple versions of the @app are installed
* in the system (like a Flatpak and RPM versions).
* Finish the call with gs_plugin_app_launch_filtered_finish().
*
* The function also verifies whether the @plugin can handle the @app,
* in a sense of gs_app_has_management_plugin(), and if not then does
* nothing.
*
* Since: 47
**/
void
gs_plugin_app_launch_filtered_async (GsPlugin *plugin,
GsApp *app,
GsPluginLaunchFlags flags,
GsPluginPickDesktopFileCallback cb,
gpointer cb_user_data,
GCancellable *cancellable,
GAsyncReadyCallback async_callback,
gpointer async_user_data)
{
const gchar *desktop_id;
g_autoptr(GTask) task = NULL;
g_autoptr(LaunchFilteredData) data = NULL;
g_return_if_fail (GS_IS_PLUGIN (plugin));
g_return_if_fail (GS_IS_APP (app));
g_return_if_fail (cb != NULL);
g_return_if_fail (async_callback != NULL);
task = g_task_new (plugin, cancellable, async_callback, async_user_data);
g_task_set_source_tag (task, gs_plugin_app_launch_filtered_async);
/* only process this app if was created by this plugin */
if (!gs_app_has_management_plugin (app, plugin)) {
g_task_return_boolean (task, TRUE);
return;
}
desktop_id = get_desktop_id_to_launch (app);
if (desktop_id == NULL) {
g_task_return_new_error (task, GS_PLUGIN_ERROR,
GS_PLUGIN_ERROR_NOT_SUPPORTED,
"no desktop file for app: %s",
gs_app_get_name (app));
return;
}
data = g_new0 (LaunchFilteredData, 1);
data->app = g_object_ref (app);
data->cb = cb;
data->cb_user_data = cb_user_data;
g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_filtered_data_free);
g_task_run_in_thread (task, launch_filtered_thread);
}
/**
* gs_plugin_app_launch_filtered_finish:
* @plugin: a #GsPlugin
* @result: an async result
* @error: a #GError or %NULL
*
* Finishes operation started by gs_plugin_app_launch_finltered_async().
* This function should be called from the main thread.
*
* Returns: whether succeeded
*
* Since: 47
**/
gboolean
gs_plugin_app_launch_filtered_finish (GsPlugin *plugin,
GAsyncResult *result,
GError **error)
{
GTask *task = G_TASK (result);
LaunchFilteredData *data = NULL;
g_return_val_if_fail (g_task_is_valid (task, plugin), FALSE);
g_return_val_if_fail (g_async_result_is_tagged (result, gs_plugin_app_launch_filtered_async), FALSE);
if (!g_task_propagate_boolean (task, error))
return FALSE;
data = g_task_get_task_data (task);
/* the plugin does not manage the provided app */
if (data == NULL)
return TRUE;
return launch_app_info (data->appinfo, error);
}
static void
weak_ref_free (GWeakRef *weak)
{
g_weak_ref_clear (weak);
g_free (weak);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GWeakRef, weak_ref_free)
/* @obj is a gpointer rather than a GObject* to avoid the need for casts */
static GWeakRef *
weak_ref_new (gpointer obj)
{
g_autoptr(GWeakRef) weak = g_new0 (GWeakRef, 1);
g_weak_ref_init (weak, obj);
return g_steal_pointer (&weak);
}
static gboolean
gs_plugin_updates_changed_cb (gpointer user_data)
{
GWeakRef *plugin_weak = user_data;
g_autoptr(GsPlugin) plugin = NULL;
plugin = g_weak_ref_get (plugin_weak);
if (plugin != NULL)
g_signal_emit (plugin, signals[SIGNAL_UPDATES_CHANGED], 0);
return G_SOURCE_REMOVE;
}
/**
* gs_plugin_updates_changed:
* @plugin: a #GsPlugin
*
* Emit a signal that tells the plugin loader that the list of updates
* may have changed.
*
* Since: 3.22
**/
void
gs_plugin_updates_changed (GsPlugin *plugin)
{
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, gs_plugin_updates_changed_cb,
weak_ref_new (plugin), (GDestroyNotify) weak_ref_free);
}
static gboolean
gs_plugin_reload_cb (gpointer user_data)
{
GWeakRef *plugin_weak = user_data;
g_autoptr(GsPlugin) plugin = NULL;
plugin = g_weak_ref_get (plugin_weak);
if (plugin != NULL)
g_signal_emit (plugin, signals[SIGNAL_RELOAD], 0);
return G_SOURCE_REMOVE;
}
/**
* gs_plugin_reload:
* @plugin: a #GsPlugin
*
* Plugins that call this function should expect that all panels will
* reload after a small delay, causing mush flashing, wailing and
* gnashing of teeth.
*
* Plugins should not call this unless absolutely required.
*
* Since: 3.22
**/
void
gs_plugin_reload (GsPlugin *plugin)
{
g_debug ("emitting %s::reload in idle", gs_plugin_get_name (plugin));
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, gs_plugin_reload_cb,
weak_ref_new (plugin), (GDestroyNotify) weak_ref_free);
}
/**
* gs_plugin_cache_lookup:
* @plugin: a #GsPlugin
* @key: a string
*
* Looks up an application object from the per-plugin cache
*
* Returns: (transfer full) (nullable): the #GsApp, or %NULL
*
* Since: 3.22
**/
GsApp *
gs_plugin_cache_lookup (GsPlugin *plugin, const gchar *key)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
GsApp *app;
g_autoptr(GMutexLocker) locker = NULL;
g_return_val_if_fail (GS_IS_PLUGIN (plugin), NULL);
g_return_val_if_fail (key != NULL, NULL);
locker = g_mutex_locker_new (&priv->cache_mutex);
app = g_hash_table_lookup (priv->cache, key);
if (app == NULL)
return NULL;
return g_object_ref (app);
}
/**
* gs_plugin_cache_lookup_by_state:
* @plugin: a #GsPlugin
* @list: a #GsAppList to add applications to
* @state: a #GsAppState
*
* Adds each cached #GsApp with state @state into the @list.
* When the state is %GS_APP_STATE_UNKNOWN, then adds all
* cached applications.
*
* Since: 40
**/
void
gs_plugin_cache_lookup_by_state (GsPlugin *plugin,
GsAppList *list,
GsAppState state)
{
GsPluginPrivate *priv;
GHashTableIter iter;
gpointer value;
g_autoptr(GMutexLocker) locker = NULL;
g_return_if_fail (GS_IS_PLUGIN (plugin));
g_return_if_fail (GS_IS_APP_LIST (list));
priv = gs_plugin_get_instance_private (plugin);
locker = g_mutex_locker_new (&priv->cache_mutex);
g_hash_table_iter_init (&iter, priv->cache);
while (g_hash_table_iter_next (&iter, NULL, &value)) {
GsApp *app = value;
if (state == GS_APP_STATE_UNKNOWN ||
state == gs_app_get_state (app))
gs_app_list_add (list, app);
}
}
/**
* gs_plugin_cache_remove:
* @plugin: a #GsPlugin
* @key: a key which matches
*
* Removes an application from the per-plugin cache.
*
* Since: 3.22
**/
void
gs_plugin_cache_remove (GsPlugin *plugin, const gchar *key)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_autoptr(GMutexLocker) locker = NULL;
g_return_if_fail (GS_IS_PLUGIN (plugin));
g_return_if_fail (key != NULL);
locker = g_mutex_locker_new (&priv->cache_mutex);
g_hash_table_remove (priv->cache, key);
}
/**
* gs_plugin_cache_add:
* @plugin: a #GsPlugin
* @key: a string, or %NULL if the unique ID should be used
* @app: a #GsApp
*
* Adds an application to the per-plugin cache. This is optional,
* and the plugin can use the cache however it likes.
*
* Since: 3.22
**/
void
gs_plugin_cache_add (GsPlugin *plugin, const gchar *key, GsApp *app)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_autoptr(GMutexLocker) locker = NULL;
g_return_if_fail (GS_IS_PLUGIN (plugin));
g_return_if_fail (GS_IS_APP (app));
locker = g_mutex_locker_new (&priv->cache_mutex);
/* the user probably doesn't want to do this */
if (gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD)) {
g_warning ("adding wildcard app %s to plugin cache",
gs_app_get_unique_id (app));
}
/* default */
if (key == NULL)
key = gs_app_get_unique_id (app);
g_return_if_fail (key != NULL);
if (g_hash_table_lookup (priv->cache, key) == app)
return;
g_hash_table_insert (priv->cache, g_strdup (key), g_object_ref (app));
}
/**
* gs_plugin_cache_invalidate:
* @plugin: a #GsPlugin
*
* Invalidate the per-plugin cache by marking all entries as invalid.
* This is optional, and the plugin can evict the cache whenever it
* likes. Using this function may mean the front-end and the plugin
* may be operating on a different GsApp with the same cache ID.
*
* Most plugins do not need to call this function; if a suitable cache
* key is being used the old cache item can remain.
*
* Since: 3.22
**/
void
gs_plugin_cache_invalidate (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
g_autoptr(GMutexLocker) locker = NULL;
g_return_if_fail (GS_IS_PLUGIN (plugin));
locker = g_mutex_locker_new (&priv->cache_mutex);
g_hash_table_remove_all (priv->cache);
}
/**
* gs_plugin_list_cached:
* @plugin: a #GsPlugin
*
* Lists all apps cached by the @plugin.
*
* Returns: (transfer full): a #GsAppList with all currently cached apps
*
* Since: 46
**/
GsAppList *
gs_plugin_list_cached (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
GsAppList *list = NULL;
GHashTableIter iter;
gpointer value = NULL;
g_autoptr(GMutexLocker) locker = NULL;
g_return_val_if_fail (GS_IS_PLUGIN (plugin), NULL);
locker = g_mutex_locker_new (&priv->cache_mutex);
list = gs_app_list_new ();
g_hash_table_iter_init (&iter, priv->cache);
while (g_hash_table_iter_next (&iter, NULL, &value)) {
GsApp *app = value;
gs_app_list_add (list, app);
}
return list;
}
/**
* gs_plugin_report_event:
* @plugin: a #GsPlugin
* @event: a #GsPluginEvent
*
* Report a non-fatal event to the UI. Plugins should not assume that a
* specific event is actually shown to the user as it may be ignored
* automatically.
*
* Since: 3.24
**/
void
gs_plugin_report_event (GsPlugin *plugin, GsPluginEvent *event)
{
g_return_if_fail (GS_IS_PLUGIN (plugin));
g_return_if_fail (GS_IS_PLUGIN_EVENT (event));
g_signal_emit (plugin, signals[SIGNAL_REPORT_EVENT], 0, event);
}
/**
* gs_plugin_set_allow_updates:
* @plugin: a #GsPlugin
* @allow_updates: boolean
*
* This allows plugins to inhibit the showing of the updates panel.
* This will typically be used when the required permissions are not possible
* to obtain, or when a LiveUSB image is low on space.
*
* By default, the updates panel is shown so plugins do not need to call this
* function unless they called gs_plugin_set_allow_updates() with %FALSE.
*
* Since: 3.24
**/
void
gs_plugin_set_allow_updates (GsPlugin *plugin, gboolean allow_updates)
{
g_return_if_fail (GS_IS_PLUGIN (plugin));
g_signal_emit (plugin, signals[SIGNAL_ALLOW_UPDATES], 0, allow_updates);
}
/**
* gs_plugin_error_to_string:
* @error: a #GsPluginError, e.g. %GS_PLUGIN_ERROR_NO_NETWORK
*
* Converts the enumerated error to a string.
*
* Returns: a string, or %NULL for invalid
**/
const gchar *
gs_plugin_error_to_string (GsPluginError error)
{
if (error == GS_PLUGIN_ERROR_FAILED)
return "failed";
if (error == GS_PLUGIN_ERROR_NOT_SUPPORTED)
return "not-supported";
if (error == GS_PLUGIN_ERROR_CANCELLED)
return "cancelled";
if (error == GS_PLUGIN_ERROR_NO_NETWORK)
return "no-network";
if (error == GS_PLUGIN_ERROR_NO_SECURITY)
return "no-security";
if (error == GS_PLUGIN_ERROR_NO_SPACE)
return "no-space";
if (error == GS_PLUGIN_ERROR_AUTH_REQUIRED)
return "auth-required";
if (error == GS_PLUGIN_ERROR_AUTH_INVALID)
return "auth-invalid";
if (error == GS_PLUGIN_ERROR_PLUGIN_DEPSOLVE_FAILED)
return "plugin-depsolve-failed";
if (error == GS_PLUGIN_ERROR_DOWNLOAD_FAILED)
return "download-failed";
if (error == GS_PLUGIN_ERROR_WRITE_FAILED)
return "write-failed";
if (error == GS_PLUGIN_ERROR_INVALID_FORMAT)
return "invalid-format";
if (error == GS_PLUGIN_ERROR_DELETE_FAILED)
return "delete-failed";
if (error == GS_PLUGIN_ERROR_RESTART_REQUIRED)
return "restart-required";
if (error == GS_PLUGIN_ERROR_AC_POWER_REQUIRED)
return "ac-power-required";
if (error == GS_PLUGIN_ERROR_BATTERY_LEVEL_TOO_LOW)
return "battery-level-too-low";
if (error == GS_PLUGIN_ERROR_TIMED_OUT)
return "timed-out";
return NULL;
}
/**
* gs_plugin_action_to_function_name: (skip)
* @action: a #GsPluginAction
*
* Converts the enumerated action to the vfunc name.
*
* Returns: a string, or %NULL for invalid
**/
const gchar *
gs_plugin_action_to_function_name (GsPluginAction action)
{
if (action == GS_PLUGIN_ACTION_GET_LANGPACKS)
return "gs_plugin_add_langpacks";
return NULL;
}
/**
* gs_plugin_action_to_string:
* @action: a #GsPluginAction
*
* Converts the enumerated action to a string.
*
* Returns: a string, or %NULL for invalid
**/
const gchar *
gs_plugin_action_to_string (GsPluginAction action)
{
if (action == GS_PLUGIN_ACTION_UNKNOWN)
return "unknown";
if (action == GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD)
return "upgrade-download";
if (action == GS_PLUGIN_ACTION_LAUNCH)
return "launch";
if (action == GS_PLUGIN_ACTION_FILE_TO_APP)
return "file-to-app";
if (action == GS_PLUGIN_ACTION_URL_TO_APP)
return "url-to-app";
if (action == GS_PLUGIN_ACTION_GET_LANGPACKS)
return "get-langpacks";
if (action == GS_PLUGIN_ACTION_INSTALL_REPO)
return "repo-install";
if (action == GS_PLUGIN_ACTION_REMOVE_REPO)
return "repo-remove";
if (action == GS_PLUGIN_ACTION_ENABLE_REPO)
return "repo-enable";
if (action == GS_PLUGIN_ACTION_DISABLE_REPO)
return "repo-disable";
return NULL;
}
/**
* gs_plugin_action_from_string:
* @action: a #GsPluginAction, e.g. "install"
*
* Converts the string to an enumerated action.
*
* Returns: a #GsPluginAction.
*
* Since: 3.26
**/
GsPluginAction
gs_plugin_action_from_string (const gchar *action)
{
if (g_strcmp0 (action, "upgrade-download") == 0)
return GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD;
if (g_strcmp0 (action, "launch") == 0)
return GS_PLUGIN_ACTION_LAUNCH;
if (g_strcmp0 (action, "file-to-app") == 0)
return GS_PLUGIN_ACTION_FILE_TO_APP;
if (g_strcmp0 (action, "url-to-app") == 0)
return GS_PLUGIN_ACTION_URL_TO_APP;
if (g_strcmp0 (action, "get-langpacks") == 0)
return GS_PLUGIN_ACTION_GET_LANGPACKS;
if (g_strcmp0 (action, "repo-install") == 0)
return GS_PLUGIN_ACTION_INSTALL_REPO;
if (g_strcmp0 (action, "repo-remove") == 0)
return GS_PLUGIN_ACTION_REMOVE_REPO;
if (g_strcmp0 (action, "repo-enable") == 0)
return GS_PLUGIN_ACTION_ENABLE_REPO;
if (g_strcmp0 (action, "repo-disable") == 0)
return GS_PLUGIN_ACTION_DISABLE_REPO;
return GS_PLUGIN_ACTION_UNKNOWN;
}
/**
* gs_plugin_refine_flags_to_string:
* @refine_flags: some #GsPluginRefineFlags, e.g. %GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE
*
* Converts the flags to a string.
*
* Returns: a string
**/
gchar *
gs_plugin_refine_flags_to_string (GsPluginRefineFlags refine_flags)
{
g_autoptr(GPtrArray) cstrs = g_ptr_array_new ();
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ID)
g_ptr_array_add (cstrs, (gpointer) "require-id");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE)
g_ptr_array_add (cstrs, (gpointer) "require-license");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL)
g_ptr_array_add (cstrs, (gpointer) "require-url");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION)
g_ptr_array_add (cstrs, (gpointer) "require-description");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE)
g_ptr_array_add (cstrs, (gpointer) "require-size");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING)
g_ptr_array_add (cstrs, (gpointer) "require-rating");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION)
g_ptr_array_add (cstrs, (gpointer) "require-version");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY)
g_ptr_array_add (cstrs, (gpointer) "require-history");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION)
g_ptr_array_add (cstrs, (gpointer) "require-setup-action");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS)
g_ptr_array_add (cstrs, (gpointer) "require-update-details");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN)
g_ptr_array_add (cstrs, (gpointer) "require-origin");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED)
g_ptr_array_add (cstrs, (gpointer) "require-related");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS)
g_ptr_array_add (cstrs, (gpointer) "require-addons");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES)
g_ptr_array_add (cstrs, (gpointer) "require-allow-packages");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_SEVERITY)
g_ptr_array_add (cstrs, (gpointer) "require-update-severity");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPGRADE_REMOVED)
g_ptr_array_add (cstrs, (gpointer) "require-upgrade-removed");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE)
g_ptr_array_add (cstrs, (gpointer) "require-provenance");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS)
g_ptr_array_add (cstrs, (gpointer) "require-reviews");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS)
g_ptr_array_add (cstrs, (gpointer) "require-review-ratings");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON)
g_ptr_array_add (cstrs, (gpointer) "require-icon");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS)
g_ptr_array_add (cstrs, (gpointer) "require-permissions");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME)
g_ptr_array_add (cstrs, (gpointer) "require-origin-hostname");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_UI)
g_ptr_array_add (cstrs, (gpointer) "require-origin-ui");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME)
g_ptr_array_add (cstrs, (gpointer) "require-runtime");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS)
g_ptr_array_add (cstrs, (gpointer) "require-screenshots");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_CATEGORIES)
g_ptr_array_add (cstrs, (gpointer) "require-categories");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROJECT_GROUP)
g_ptr_array_add (cstrs, (gpointer) "require-project-group");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_DEVELOPER_NAME)
g_ptr_array_add (cstrs, (gpointer) "require-developer-name");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_KUDOS)
g_ptr_array_add (cstrs, (gpointer) "require-kudos");
if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_CONTENT_RATING)
g_ptr_array_add (cstrs, (gpointer) "content-rating");
if (cstrs->len == 0)
return g_strdup ("none");
g_ptr_array_add (cstrs, NULL);
return g_strjoinv (",", (gchar**) cstrs->pdata);
}
static void
gs_plugin_constructed (GObject *object)
{
GsPlugin *plugin = GS_PLUGIN (object);
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
G_OBJECT_CLASS (gs_plugin_parent_class)->constructed (object);
/* Check all required properties have been set. */
g_assert (priv->session_bus_connection != NULL);
g_assert (priv->system_bus_connection != NULL);
}
static void
gs_plugin_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
GsPlugin *plugin = GS_PLUGIN (object);
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
switch ((GsPluginProperty) prop_id) {
case PROP_FLAGS:
priv->flags = g_value_get_flags (value);
g_object_notify_by_pspec (G_OBJECT (plugin), obj_props[PROP_FLAGS]);
break;
case PROP_SCALE:
gs_plugin_set_scale (plugin, g_value_get_uint (value));
break;
case PROP_SESSION_BUS_CONNECTION:
/* Construct only */
g_assert (priv->session_bus_connection == NULL);
priv->session_bus_connection = g_value_dup_object (value);
break;
case PROP_SYSTEM_BUS_CONNECTION:
/* Construct only */
g_assert (priv->system_bus_connection == NULL);
priv->system_bus_connection = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gs_plugin_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
GsPlugin *plugin = GS_PLUGIN (object);
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
switch ((GsPluginProperty) prop_id) {
case PROP_FLAGS:
g_value_set_flags (value, priv->flags);
break;
case PROP_SCALE:
g_value_set_uint (value, gs_plugin_get_scale (plugin));
break;
case PROP_SESSION_BUS_CONNECTION:
g_value_set_object (value, priv->session_bus_connection);
break;
case PROP_SYSTEM_BUS_CONNECTION:
g_value_set_object (value, priv->system_bus_connection);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gs_plugin_class_init (GsPluginClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = gs_plugin_constructed;
object_class->set_property = gs_plugin_set_property;
object_class->get_property = gs_plugin_get_property;
object_class->dispose = gs_plugin_dispose;
object_class->finalize = gs_plugin_finalize;
/**
* GsPlugin:flags:
*
* Flags which indicate various boolean properties of the plugin.
*
* These may change during the plugins lifetime.
*/
obj_props[PROP_FLAGS] =
g_param_spec_flags ("flags", NULL, NULL,
GS_TYPE_PLUGIN_FLAGS, GS_PLUGIN_FLAGS_NONE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
/**
* GsPlugin:scale:
*
* The window scale factor.
*
* These may change during the plugins lifetime.
*
* Since: 48
*/
obj_props[PROP_SCALE] =
g_param_spec_uint ("scale", NULL, NULL,
1, G_MAXUINT, 1,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
/**
* GsPlugin:session-bus-connection: (not nullable)
*
* A connection to the D-Bus session bus.
*
* This must be set at construction time and will not be %NULL
* afterwards.
*
* Since: 43
*/
obj_props[PROP_SESSION_BUS_CONNECTION] =
g_param_spec_object ("session-bus-connection", NULL, NULL,
G_TYPE_DBUS_CONNECTION,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
/**
* GsPlugin:system-bus-connection: (not nullable)
*
* A connection to the D-Bus system bus.
*
* This must be set at construction time and will not be %NULL
* afterwards.
*
* Since: 43
*/
obj_props[PROP_SYSTEM_BUS_CONNECTION] =
g_param_spec_object ("system-bus-connection", NULL, NULL,
G_TYPE_DBUS_CONNECTION,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props);
signals [SIGNAL_UPDATES_CHANGED] =
g_signal_new ("updates-changed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsPluginClass, updates_changed),
NULL, NULL, g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals [SIGNAL_STATUS_CHANGED] =
g_signal_new ("status-changed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsPluginClass, status_changed),
NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 2, GS_TYPE_APP, G_TYPE_UINT);
signals [SIGNAL_RELOAD] =
g_signal_new ("reload",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsPluginClass, reload),
NULL, NULL, g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals [SIGNAL_REPORT_EVENT] =
g_signal_new ("report-event",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsPluginClass, report_event),
NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 1, GS_TYPE_PLUGIN_EVENT);
signals [SIGNAL_ALLOW_UPDATES] =
g_signal_new ("allow-updates",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsPluginClass, allow_updates),
NULL, NULL, g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
signals [SIGNAL_BASIC_AUTH_START] =
g_signal_new ("basic-auth-start",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsPluginClass, basic_auth_start),
NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER);
signals [SIGNAL_REPOSITORY_CHANGED] =
g_signal_new ("repository-changed",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsPluginClass, repository_changed),
NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_NONE, 1, GS_TYPE_APP);
signals [SIGNAL_ASK_UNTRUSTED] =
g_signal_new ("ask-untrusted",
G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsPluginClass, ask_untrusted),
NULL, NULL, g_cclosure_marshal_generic,
G_TYPE_BOOLEAN, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
}
static void
gs_plugin_init (GsPlugin *plugin)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin);
guint i;
for (i = 0; i < GS_PLUGIN_RULE_LAST; i++)
priv->rules[i] = g_ptr_array_new_with_free_func (g_free);
priv->enabled = TRUE;
priv->scale = 1;
priv->cache = g_hash_table_new_full ((GHashFunc) as_utils_data_id_hash,
(GEqualFunc) as_utils_data_id_equal,
g_free,
(GDestroyNotify) g_object_unref);
priv->vfuncs = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
g_mutex_init (&priv->cache_mutex);
g_mutex_init (&priv->interactive_mutex);
g_mutex_init (&priv->timer_mutex);
g_mutex_init (&priv->vfuncs_mutex);
}
/**
* gs_plugin_new:
* @session_bus_connection: (not nullable) (transfer none): a session bus
* connection to use
* @system_bus_connection: (not nullable) (transfer none): a system bus
* connection to use
*
* Creates a new plugin.
*
* Returns: a #GsPlugin
*
* Since: 43
**/
GsPlugin *
gs_plugin_new (GDBusConnection *session_bus_connection,
GDBusConnection *system_bus_connection)
{
g_return_val_if_fail (G_IS_DBUS_CONNECTION (session_bus_connection), NULL);
g_return_val_if_fail (G_IS_DBUS_CONNECTION (system_bus_connection), NULL);
return g_object_new (GS_TYPE_PLUGIN,
"session-bus-connection", session_bus_connection,
"system-bus-connection", system_bus_connection,
NULL);
}
typedef struct {
GWeakRef plugin_weak; /* (owned) (element-type GsPlugin) */
GsApp *repository; /* (owned) */
} GsPluginRepositoryChangedHelper;
static void
gs_plugin_repository_changed_helper_free (GsPluginRepositoryChangedHelper *helper)
{
g_clear_object (&helper->repository);
g_weak_ref_clear (&helper->plugin_weak);
g_slice_free (GsPluginRepositoryChangedHelper, helper);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GsPluginRepositoryChangedHelper, gs_plugin_repository_changed_helper_free)
static gboolean
gs_plugin_repository_changed_cb (gpointer user_data)
{
GsPluginRepositoryChangedHelper *helper = user_data;
g_autoptr(GsPlugin) plugin = NULL;
plugin = g_weak_ref_get (&helper->plugin_weak);
if (plugin != NULL)
g_signal_emit (plugin,
signals[SIGNAL_REPOSITORY_CHANGED], 0,
helper->repository);
return G_SOURCE_REMOVE;
}
/**
* gs_plugin_repository_changed:
* @plugin: a #GsPlugin
* @repository: a #GsApp representing the repository
*
* Emit the "repository-changed" signal in the main thread.
*
* Since: 40
**/
void
gs_plugin_repository_changed (GsPlugin *plugin,
GsApp *repository)
{
g_autoptr(GsPluginRepositoryChangedHelper) helper = NULL;
g_autoptr(GSource) idle_source = NULL;
g_return_if_fail (GS_IS_PLUGIN (plugin));
g_return_if_fail (GS_IS_APP (repository));
helper = g_slice_new0 (GsPluginRepositoryChangedHelper);
g_weak_ref_init (&helper->plugin_weak, plugin);
helper->repository = g_object_ref (repository);
idle_source = g_idle_source_new ();
g_source_set_callback (idle_source, gs_plugin_repository_changed_cb, g_steal_pointer (&helper), (GDestroyNotify) gs_plugin_repository_changed_helper_free);
g_source_attach (idle_source, NULL);
}
/**
* gs_plugin_update_cache_state_for_repository:
* @plugin: a #GsPlugin
* @repository: a #GsApp representing a repository, which changed
*
* Update state of the all cached #GsApp instances related
* to the @repository.
*
* Since: 40
**/
void
gs_plugin_update_cache_state_for_repository (GsPlugin *plugin,
GsApp *repository)
{
GsPluginPrivate *priv;
GHashTableIter iter;
g_autoptr(GMutexLocker) locker = NULL;
g_autoptr(GsPlugin) repo_plugin = NULL;
gpointer value;
const gchar *repo_id;
GsAppState repo_state;
g_return_if_fail (GS_IS_PLUGIN (plugin));
g_return_if_fail (GS_IS_APP (repository));
priv = gs_plugin_get_instance_private (plugin);
repo_id = gs_app_get_id (repository);
repo_state = gs_app_get_state (repository);
repo_plugin = gs_app_dup_management_plugin (repository);
locker = g_mutex_locker_new (&priv->cache_mutex);
g_hash_table_iter_init (&iter, priv->cache);
while (g_hash_table_iter_next (&iter, NULL, &value)) {
GsApp *app = value;
GsAppState app_state = gs_app_get_state (app);
g_autoptr(GsPlugin) app_plugin = gs_app_dup_management_plugin (app);
if (app_plugin != repo_plugin ||
gs_app_get_scope (app) != gs_app_get_scope (repository) ||
gs_app_get_bundle_kind (app) != gs_app_get_bundle_kind (repository))
continue;
if (((app_state == GS_APP_STATE_AVAILABLE &&
repo_state != GS_APP_STATE_INSTALLED) ||
(app_state == GS_APP_STATE_UNAVAILABLE &&
repo_state == GS_APP_STATE_INSTALLED)) &&
g_strcmp0 (gs_app_get_origin (app), repo_id) == 0) {
/* First reset the state, because move from 'available' to 'unavailable' is not correct */
gs_app_set_state (app, GS_APP_STATE_UNKNOWN);
gs_app_set_state (app, repo_state == GS_APP_STATE_INSTALLED ? GS_APP_STATE_AVAILABLE : GS_APP_STATE_UNAVAILABLE);
}
}
}
/**
* gs_plugin_ask_untrusted:
* @plugin: a #GsPlugin
* @title: the title for the question
* @msg: the message for the question
* @details: (nullable): the detailed error message, or %NULL for none
* @accept_label: (nullable): a label of the 'accept' button, or %NULL to use 'Accept'
*
* Asks the user whether he/she accepts an untrusted package install/download/update,
* as described by @title and @msg, eventually with the @details.
*
* Note: This is a blocking call and can be called only from the main/GUI thread.
*
* Returns: whether the user accepted the question
*
* Since: 42
**/
gboolean
gs_plugin_ask_untrusted (GsPlugin *plugin,
const gchar *title,
const gchar *msg,
const gchar *details,
const gchar *accept_label)
{
gboolean accepts = FALSE;
g_signal_emit (plugin,
signals[SIGNAL_ASK_UNTRUSTED], 0,
title,
msg,
details,
accept_label,
&accepts);
return accepts;
}
/**
* gs_plugin_get_session_bus_connection:
* @self: a #GsPlugin
*
* Get the D-Bus session bus connection in use by the plugin.
*
* Returns: (transfer none) (not nullable): a D-Bus connection
* Since: 43
*/
GDBusConnection *
gs_plugin_get_session_bus_connection (GsPlugin *self)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (self);
g_return_val_if_fail (GS_IS_PLUGIN (self), NULL);
return priv->session_bus_connection;
}
/**
* gs_plugin_get_system_bus_connection:
* @self: a #GsPlugin
*
* Get the D-Bus system bus connection in use by the plugin.
*
* Returns: (transfer none) (not nullable): a D-Bus connection
* Since: 43
*/
GDBusConnection *
gs_plugin_get_system_bus_connection (GsPlugin *self)
{
GsPluginPrivate *priv = gs_plugin_get_instance_private (self);
g_return_val_if_fail (GS_IS_PLUGIN (self), NULL);
return priv->system_bus_connection;
}