/* -*- 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 * Copyright (C) 2014-2020 Kalev Lember * * SPDX-License-Identifier: GPL-2.0+ */ /** * 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 #include #include #ifdef USE_VALGRIND #include #endif #include "gs-app-list-private.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; GsPluginData *data; /* for gs-plugin-{name}.c */ GsPluginFlags flags; SoupSession *soup_session; GPtrArray *rules[GS_PLUGIN_RULE_LAST]; GHashTable *vfuncs; /* string:pointer */ GMutex vfuncs_mutex; gboolean enabled; guint interactive_cnt; GMutex interactive_mutex; gchar *locale; /* allow-none */ gchar *language; /* allow-none */ gchar *name; gchar *appstream_id; guint scale; guint order; guint priority; guint timer_id; GMutex timer_mutex; GNetworkMonitor *network_monitor; } GsPluginPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GsPlugin, gs_plugin, G_TYPE_OBJECT) G_DEFINE_QUARK (gs-plugin-error-quark, gs_plugin_error) enum { PROP_0, PROP_FLAGS, PROP_LAST }; enum { SIGNAL_UPDATES_CHANGED, SIGNAL_STATUS_CHANGED, SIGNAL_RELOAD, SIGNAL_REPORT_EVENT, SIGNAL_ALLOW_UPDATES, SIGNAL_BASIC_AUTH_START, 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 * @error: a #GError, or %NULL * * Creates a new plugin from an external module. * * Returns: the #GsPlugin or %NULL * * Since: 3.22 **/ GsPlugin * gs_plugin_create (const gchar *filename, GError **error) { GsPlugin *plugin = NULL; GsPluginPrivate *priv; g_autofree gchar *basename = NULL; /* 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 */ plugin = gs_plugin_new (); priv = gs_plugin_get_instance_private (plugin); priv->module = g_module_open (filename, 0); if (priv->module == NULL) { g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED, "failed to open plugin %s: %s", filename, g_module_error ()); return NULL; } gs_plugin_set_name (plugin, basename + 13); return plugin; } 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->data); g_free (priv->locale); g_free (priv->language); if (priv->soup_session != NULL) g_object_unref (priv->soup_session); 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); #ifndef RUNNING_ON_VALGRIND if (priv->module != NULL) g_module_close (priv->module); #endif G_OBJECT_CLASS (gs_plugin_parent_class)->finalize (object); } /** * gs_plugin_get_data: * @plugin: a #GsPlugin * * Gets the private data for the plugin if gs_plugin_alloc_data() has * been called. * * Returns: the #GsPluginData, or %NULL * * Since: 3.22 **/ GsPluginData * gs_plugin_get_data (GsPlugin *plugin) { GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin); g_assert (priv->data != NULL); return priv->data; } /** * gs_plugin_alloc_data: * @plugin: a #GsPlugin * @sz: the size of data to allocate, e.g. `sizeof(FooPluginPrivate)` * * Allocates a private data area for the plugin which can be retrieved * using gs_plugin_get_data(). * This is normally called in gs_plugin_initialize() and the data should * not be manually freed. * * Returns: the #GsPluginData, cleared to NUL bytes * * Since: 3.22 **/ GsPluginData * gs_plugin_alloc_data (GsPlugin *plugin, gsize sz) { GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin); g_assert (priv->data == NULL); priv->data = g_malloc0 (sz); return priv->data; } /** * gs_plugin_clear_data: * @plugin: a #GsPlugin * * Clears and resets the private data. Only run this from the self tests. **/ void gs_plugin_clear_data (GsPlugin *plugin) { GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin); if (priv->data == NULL) return; g_clear_pointer (&priv->data, g_free); } /** * 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 gs_plugin_initialize(). * * 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); priv->scale = 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_locale: * @plugin: a #GsPlugin * * Gets the user locale. This is in the form documented in `man 3 setlocale`: * ``` * language[_territory][.codeset][@modifier] * ``` * where `language` is an * [ISO 639 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), * `territory` is an * [ISO 3166 country code](https://en.wikipedia.org/wiki/ISO_3166-1), and * `codeset` is a character set or encoding identifier like `ISO-8859-1` or * `UTF-8`. For a list of all supported locales, run `locale -a`. * * Returns: the locale string, e.g. `en_GB` or `uz_UZ.utf8@cyrillic` * * Since: 3.22 **/ const gchar * gs_plugin_get_locale (GsPlugin *plugin) { GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin); return priv->locale; } /** * 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_locale: * @plugin: a #GsPlugin * @locale: a locale string, e.g. "en_GB" * * Sets the plugin locale. * * Since: 3.22 **/ void gs_plugin_set_locale (GsPlugin *plugin, const gchar *locale) { GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin); g_free (priv->locale); priv->locale = g_strdup (locale); } /** * 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_get_soup_session: * @plugin: a #GsPlugin * * Gets the soup session that this plugin can use when downloading. * * Returns: the #SoupSession * * Since: 3.22 **/ SoupSession * gs_plugin_get_soup_session (GsPlugin *plugin) { GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin); return priv->soup_session; } /** * gs_plugin_set_soup_session: * @plugin: a #GsPlugin * @soup_session: a #SoupSession * * Sets the soup session that this plugin will use when downloading. * * Since: 3.22 **/ void gs_plugin_set_soup_session (GsPlugin *plugin, SoupSession *soup_session) { GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin); g_set_object (&priv->soup_session, soup_session); } /** * 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_RUNNING_SELF * * 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_RUNNING_SELF * * 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; } /** * gs_plugin_remove_flags: * @plugin: a #GsPlugin * @flags: a #GsPluginFlags, e.g. %GS_PLUGIN_FLAGS_RUNNING_SELF * * 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; } /** * 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 { GsPlugin *plugin; GsApp *app; GsPluginStatus status; guint percentage; } GsPluginStatusHelper; static gboolean gs_plugin_status_update_cb (gpointer user_data) { GsPluginStatusHelper *helper = (GsPluginStatusHelper *) user_data; g_signal_emit (helper->plugin, signals[SIGNAL_STATUS_CHANGED], 0, helper->app, helper->status); if (helper->app != NULL) g_object_unref (helper->app); g_slice_free (GsPluginStatusHelper, helper); return FALSE; } /** * 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) { GsPluginStatusHelper *helper; g_autoptr(GSource) idle_source = NULL; helper = g_slice_new0 (GsPluginStatusHelper); helper->plugin = 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, helper, NULL); 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 gboolean gs_plugin_app_launch_cb (gpointer user_data) { GAppInfo *appinfo = (GAppInfo *) user_data; GdkDisplay *display; g_autoptr(GAppLaunchContext) context = NULL; g_autoptr(GError) error = NULL; display = gdk_display_get_default (); context = G_APP_LAUNCH_CONTEXT (gdk_display_get_app_launch_context (display)); if (!g_app_info_launch (appinfo, NULL, context, &error)) g_warning ("Failed to launch: %s", error->message); return FALSE; } /** * gs_plugin_app_launch: * @plugin: a #GsPlugin * @app: a #GsApp * @error: a #GError, or %NULL * * Launches the application using #GAppInfo. * * Returns: %TRUE for success * * Since: 3.22 **/ gboolean gs_plugin_app_launch (GsPlugin *plugin, GsApp *app, GError **error) { const gchar *desktop_id; g_autoptr(GAppInfo) appinfo = NULL; desktop_id = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_DESKTOP_ID); if (desktop_id == NULL) desktop_id = gs_app_get_id (app); if (desktop_id == NULL) { g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED, "no such desktop file: %s", desktop_id); return FALSE; } appinfo = G_APP_INFO (gs_utils_get_desktop_app_info (desktop_id)); if (appinfo == NULL) { g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED, "no such desktop file: %s", desktop_id); return FALSE; } g_idle_add_full (G_PRIORITY_DEFAULT, gs_plugin_app_launch_cb, g_object_ref (appinfo), (GDestroyNotify) g_object_unref); return TRUE; } static gboolean gs_plugin_updates_changed_cb (gpointer user_data) { GsPlugin *plugin = GS_PLUGIN (user_data); g_signal_emit (plugin, signals[SIGNAL_UPDATES_CHANGED], 0); return FALSE; } /** * 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 (gs_plugin_updates_changed_cb, plugin); } static gboolean gs_plugin_reload_cb (gpointer user_data) { GsPlugin *plugin = GS_PLUGIN (user_data); g_signal_emit (plugin, signals[SIGNAL_RELOAD], 0); return FALSE; } /** * 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 ::reload in idle"); g_idle_add (gs_plugin_reload_cb, plugin); } typedef struct { GsPlugin *plugin; GsApp *app; GCancellable *cancellable; } GsPluginDownloadHelper; static void gs_plugin_download_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, GsPluginDownloadHelper *helper) { GsPluginPrivate *priv = gs_plugin_get_instance_private (helper->plugin); guint percentage; goffset header_size; goffset body_length; /* cancelled? */ if (g_cancellable_is_cancelled (helper->cancellable)) { g_debug ("cancelling download of %s", gs_app_get_id (helper->app)); soup_session_cancel_message (priv->soup_session, msg, SOUP_STATUS_CANCELLED); return; } /* if it's returning "Found" or an error, ignore the percentage */ if (msg->status_code != SOUP_STATUS_OK) { g_debug ("ignoring status code %u (%s)", msg->status_code, msg->reason_phrase); return; } /* get data */ body_length = msg->response_body->length; header_size = soup_message_headers_get_content_length (msg->response_headers); /* size is not known */ if (header_size < body_length) return; /* calculate percentage */ percentage = (guint) ((100 * body_length) / header_size); g_debug ("%s progress: %u%%", gs_app_get_id (helper->app), percentage); gs_app_set_progress (helper->app, percentage); gs_plugin_status_update (helper->plugin, helper->app, GS_PLUGIN_STATUS_DOWNLOADING); } /** * gs_plugin_download_data: * @plugin: a #GsPlugin * @app: a #GsApp, or %NULL * @uri: a remote URI * @cancellable: a #GCancellable, or %NULL * @error: a #GError, or %NULL * * Downloads data. * * Returns: the downloaded data, or %NULL * * Since: 3.22 **/ GBytes * gs_plugin_download_data (GsPlugin *plugin, GsApp *app, const gchar *uri, GCancellable *cancellable, GError **error) { GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin); GsPluginDownloadHelper helper; guint status_code; g_autoptr(SoupMessage) msg = NULL; g_return_val_if_fail (GS_IS_PLUGIN (plugin), NULL); g_return_val_if_fail (uri != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* local */ if (g_str_has_prefix (uri, "file://")) { gsize length = 0; g_autofree gchar *contents = NULL; g_autoptr(GError) error_local = NULL; g_debug ("copying %s from plugin %s", uri, priv->name); if (!g_file_get_contents (uri + 7, &contents, &length, &error_local)) { g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_DOWNLOAD_FAILED, "failed to copy %s: %s", uri, error_local->message); return NULL; } return g_bytes_new (contents, length); } /* remote */ g_debug ("downloading %s from plugin %s", uri, priv->name); msg = soup_message_new (SOUP_METHOD_GET, uri); if (app != NULL) { helper.plugin = plugin; helper.app = app; helper.cancellable = cancellable; g_signal_connect (msg, "got-chunk", G_CALLBACK (gs_plugin_download_chunk_cb), &helper); } status_code = soup_session_send_message (priv->soup_session, msg); if (status_code != SOUP_STATUS_OK) { g_autoptr(GString) str = g_string_new (NULL); g_string_append (str, soup_status_get_phrase (status_code)); if (msg->response_body->data != NULL) { g_string_append (str, ": "); g_string_append (str, msg->response_body->data); } g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_DOWNLOAD_FAILED, "failed to download %s: %s", uri, str->str); return NULL; } return g_bytes_new (msg->response_body->data, (gsize) msg->response_body->length); } /** * gs_plugin_download_file: * @plugin: a #GsPlugin * @app: a #GsApp, or %NULL * @uri: a remote URI * @filename: a local filename * @cancellable: a #GCancellable, or %NULL * @error: a #GError, or %NULL * * Downloads data and saves it to a file. * * Returns: %TRUE for success * * Since: 3.22 **/ gboolean gs_plugin_download_file (GsPlugin *plugin, GsApp *app, const gchar *uri, const gchar *filename, GCancellable *cancellable, GError **error) { GsPluginPrivate *priv = gs_plugin_get_instance_private (plugin); GsPluginDownloadHelper helper; guint status_code; g_autoptr(GError) error_local = NULL; g_autoptr(SoupMessage) msg = NULL; g_return_val_if_fail (GS_IS_PLUGIN (plugin), FALSE); g_return_val_if_fail (uri != NULL, FALSE); g_return_val_if_fail (filename != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* local */ if (g_str_has_prefix (uri, "file://")) { gsize length = 0; g_autofree gchar *contents = NULL; g_debug ("copying %s from plugin %s", uri, priv->name); if (!g_file_get_contents (uri + 7, &contents, &length, &error_local)) { g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_DOWNLOAD_FAILED, "failed to copy %s: %s", uri, error_local->message); return FALSE; } if (!g_file_set_contents (filename, contents, length, &error_local)) { g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_WRITE_FAILED, "Failed to save file: %s", error_local->message); return FALSE; } return TRUE; } /* remote */ g_debug ("downloading %s to %s from plugin %s", uri, filename, priv->name); msg = soup_message_new (SOUP_METHOD_GET, uri); if (msg == NULL) { g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_DOWNLOAD_FAILED, "failed to parse URI %s", uri); return FALSE; } if (app != NULL) { helper.plugin = plugin; helper.app = app; helper.cancellable = cancellable; g_signal_connect (msg, "got-chunk", G_CALLBACK (gs_plugin_download_chunk_cb), &helper); } status_code = soup_session_send_message (priv->soup_session, msg); if (status_code != SOUP_STATUS_OK) { g_autoptr(GString) str = g_string_new (NULL); g_string_append (str, soup_status_get_phrase (status_code)); if (msg->response_body->data != NULL) { g_string_append (str, ": "); g_string_append (str, msg->response_body->data); } g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_DOWNLOAD_FAILED, "failed to download %s: %s", uri, str->str); return FALSE; } if (!gs_mkdir_parent (filename, error)) return FALSE; if (!g_file_set_contents (filename, msg->response_body->data, msg->response_body->length, &error_local)) { g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_WRITE_FAILED, "Failed to save file: %s", error_local->message); return FALSE; } return TRUE; } static gchar * gs_plugin_download_rewrite_resource_uri (GsPlugin *plugin, GsApp *app, const gchar *uri, GCancellable *cancellable, GError **error) { g_autofree gchar *cachefn = NULL; /* local files */ if (g_str_has_prefix (uri, "file://")) uri += 7; if (g_str_has_prefix (uri, "/")) { if (!g_file_test (uri, G_FILE_TEST_EXISTS)) { g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED, "Failed to find file: %s", uri); return NULL; } return g_strdup (uri); } /* get cache location */ cachefn = gs_utils_get_cache_filename ("cssresource", uri, GS_UTILS_CACHE_FLAG_WRITEABLE | GS_UTILS_CACHE_FLAG_USE_HASH, error); if (cachefn == NULL) return NULL; /* already exists */ if (g_file_test (cachefn, G_FILE_TEST_EXISTS)) return g_steal_pointer (&cachefn); /* download */ if (!gs_plugin_download_file (plugin, app, uri, cachefn, cancellable, error)) { return NULL; } return g_steal_pointer (&cachefn); } /** * gs_plugin_download_rewrite_resource: * @plugin: a #GsPlugin * @app: a #GsApp, or %NULL * @resource: the CSS resource * @cancellable: a #GCancellable, or %NULL * @error: a #GError, or %NULL * * Downloads remote assets and rewrites a CSS resource to use cached local URIs. * * Returns: %TRUE for success * * Since: 3.26 **/ gchar * gs_plugin_download_rewrite_resource (GsPlugin *plugin, GsApp *app, const gchar *resource, GCancellable *cancellable, GError **error) { guint start = 0; g_autoptr(GString) resource_str = g_string_new (resource); g_autoptr(GString) str = g_string_new (NULL); g_return_val_if_fail (GS_IS_PLUGIN (plugin), NULL); g_return_val_if_fail (resource != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* replace datadir */ as_utils_string_replace (resource_str, "@datadir@", DATADIR); resource = resource_str->str; /* look in string for any url() links */ for (guint i = 0; resource[i] != '\0'; i++) { if (i > 4 && strncmp (resource + i - 4, "url(", 4) == 0) { start = i; continue; } if (start == 0) { g_string_append_c (str, resource[i]); continue; } if (resource[i] == ')') { guint len; g_autofree gchar *cachefn = NULL; g_autofree gchar *uri = NULL; /* remove optional single quotes */ if (resource[start] == '\'' || resource[start] == '"') start++; len = i - start; if (i > 0 && (resource[i - 1] == '\'' || resource[i - 1] == '"')) len--; uri = g_strndup (resource + start, len); /* download them to per-user cache */ cachefn = gs_plugin_download_rewrite_resource_uri (plugin, app, uri, cancellable, error); if (cachefn == NULL) return NULL; g_string_append_printf (str, "'%s'", cachefn); g_string_append_c (str, resource[i]); start = 0; } } return g_strdup (str->str); } /** * 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_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_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, e.g. %GS_PLUGIN_ERROR_NO_NETWORK * * 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_REFRESH) return "gs_plugin_refresh"; if (action == GS_PLUGIN_ACTION_REVIEW_SUBMIT) return "gs_plugin_review_submit"; if (action == GS_PLUGIN_ACTION_REVIEW_UPVOTE) return "gs_plugin_review_upvote"; if (action == GS_PLUGIN_ACTION_REVIEW_DOWNVOTE) return "gs_plugin_review_downvote"; if (action == GS_PLUGIN_ACTION_REVIEW_REPORT) return "gs_plugin_review_report"; if (action == GS_PLUGIN_ACTION_REVIEW_REMOVE) return "gs_plugin_review_remove"; if (action == GS_PLUGIN_ACTION_REVIEW_DISMISS) return "gs_plugin_review_dismiss"; if (action == GS_PLUGIN_ACTION_INSTALL) return "gs_plugin_app_install"; if (action == GS_PLUGIN_ACTION_REMOVE) return "gs_plugin_app_remove"; if (action == GS_PLUGIN_ACTION_SET_RATING) return "gs_plugin_app_set_rating"; if (action == GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD) return "gs_plugin_app_upgrade_download"; if (action == GS_PLUGIN_ACTION_UPGRADE_TRIGGER) return "gs_plugin_app_upgrade_trigger"; if (action == GS_PLUGIN_ACTION_LAUNCH) return "gs_plugin_launch"; if (action == GS_PLUGIN_ACTION_UPDATE_CANCEL) return "gs_plugin_update_cancel"; if (action == GS_PLUGIN_ACTION_ADD_SHORTCUT) return "gs_plugin_add_shortcut"; if (action == GS_PLUGIN_ACTION_REMOVE_SHORTCUT) return "gs_plugin_remove_shortcut"; if (action == GS_PLUGIN_ACTION_REFINE) return "gs_plugin_refine"; if (action == GS_PLUGIN_ACTION_UPDATE) return "gs_plugin_update"; if (action == GS_PLUGIN_ACTION_DOWNLOAD) return "gs_plugin_download"; if (action == GS_PLUGIN_ACTION_FILE_TO_APP) return "gs_plugin_file_to_app"; if (action == GS_PLUGIN_ACTION_URL_TO_APP) return "gs_plugin_url_to_app"; if (action == GS_PLUGIN_ACTION_GET_DISTRO_UPDATES) return "gs_plugin_add_distro_upgrades"; if (action == GS_PLUGIN_ACTION_GET_SOURCES) return "gs_plugin_add_sources"; if (action == GS_PLUGIN_ACTION_GET_UNVOTED_REVIEWS) return "gs_plugin_add_unvoted_reviews"; if (action == GS_PLUGIN_ACTION_GET_INSTALLED) return "gs_plugin_add_installed"; if (action == GS_PLUGIN_ACTION_GET_FEATURED) return "gs_plugin_add_featured"; if (action == GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL) return "gs_plugin_add_updates_historical"; if (action == GS_PLUGIN_ACTION_GET_UPDATES) return "gs_plugin_add_updates"; if (action == GS_PLUGIN_ACTION_GET_POPULAR) return "gs_plugin_add_popular"; if (action == GS_PLUGIN_ACTION_GET_RECENT) return "gs_plugin_add_recent"; if (action == GS_PLUGIN_ACTION_SEARCH) return "gs_plugin_add_search"; if (action == GS_PLUGIN_ACTION_SEARCH_FILES) return "gs_plugin_add_search_files"; if (action == GS_PLUGIN_ACTION_SEARCH_PROVIDES) return "gs_plugin_add_search_what_provides"; if (action == GS_PLUGIN_ACTION_GET_CATEGORY_APPS) return "gs_plugin_add_category_apps"; if (action == GS_PLUGIN_ACTION_GET_CATEGORIES) return "gs_plugin_add_categories"; if (action == GS_PLUGIN_ACTION_SETUP) return "gs_plugin_setup"; if (action == GS_PLUGIN_ACTION_INITIALIZE) return "gs_plugin_initialize"; if (action == GS_PLUGIN_ACTION_DESTROY) return "gs_plugin_destroy"; if (action == GS_PLUGIN_ACTION_GET_ALTERNATES) return "gs_plugin_add_alternates"; if (action == GS_PLUGIN_ACTION_GET_LANGPACKS) return "gs_plugin_add_langpacks"; return NULL; } /** * gs_plugin_action_to_string: * @action: a #GsPluginAction, e.g. %GS_PLUGIN_ERROR_NO_NETWORK * * 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_SETUP) return "setup"; if (action == GS_PLUGIN_ACTION_INSTALL) return "install"; if (action == GS_PLUGIN_ACTION_DOWNLOAD) return "download"; if (action == GS_PLUGIN_ACTION_REMOVE) return "remove"; if (action == GS_PLUGIN_ACTION_UPDATE) return "update"; if (action == GS_PLUGIN_ACTION_SET_RATING) return "set-rating"; if (action == GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD) return "upgrade-download"; if (action == GS_PLUGIN_ACTION_UPGRADE_TRIGGER) return "upgrade-trigger"; if (action == GS_PLUGIN_ACTION_LAUNCH) return "launch"; if (action == GS_PLUGIN_ACTION_UPDATE_CANCEL) return "update-cancel"; if (action == GS_PLUGIN_ACTION_ADD_SHORTCUT) return "add-shortcut"; if (action == GS_PLUGIN_ACTION_REMOVE_SHORTCUT) return "remove-shortcut"; if (action == GS_PLUGIN_ACTION_REVIEW_SUBMIT) return "review-submit"; if (action == GS_PLUGIN_ACTION_REVIEW_UPVOTE) return "review-upvote"; if (action == GS_PLUGIN_ACTION_REVIEW_DOWNVOTE) return "review-downvote"; if (action == GS_PLUGIN_ACTION_REVIEW_REPORT) return "review-report"; if (action == GS_PLUGIN_ACTION_REVIEW_REMOVE) return "review-remove"; if (action == GS_PLUGIN_ACTION_REVIEW_DISMISS) return "review-dismiss"; if (action == GS_PLUGIN_ACTION_GET_UPDATES) return "get-updates"; if (action == GS_PLUGIN_ACTION_GET_DISTRO_UPDATES) return "get-distro-updates"; if (action == GS_PLUGIN_ACTION_GET_UNVOTED_REVIEWS) return "get-unvoted-reviews"; if (action == GS_PLUGIN_ACTION_GET_SOURCES) return "get-sources"; if (action == GS_PLUGIN_ACTION_GET_INSTALLED) return "get-installed"; if (action == GS_PLUGIN_ACTION_GET_POPULAR) return "get-popular"; if (action == GS_PLUGIN_ACTION_GET_FEATURED) return "get-featured"; if (action == GS_PLUGIN_ACTION_SEARCH) return "search"; if (action == GS_PLUGIN_ACTION_SEARCH_FILES) return "search-files"; if (action == GS_PLUGIN_ACTION_SEARCH_PROVIDES) return "search-provides"; if (action == GS_PLUGIN_ACTION_GET_CATEGORIES) return "get-categories"; if (action == GS_PLUGIN_ACTION_GET_CATEGORY_APPS) return "get-category-apps"; if (action == GS_PLUGIN_ACTION_REFINE) return "refine"; if (action == GS_PLUGIN_ACTION_REFRESH) return "refresh"; 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_RECENT) return "get-recent"; if (action == GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL) return "get-updates-historical"; if (action == GS_PLUGIN_ACTION_INITIALIZE) return "initialize"; if (action == GS_PLUGIN_ACTION_DESTROY) return "destroy"; if (action == GS_PLUGIN_ACTION_GET_ALTERNATES) return "get-alternates"; if (action == GS_PLUGIN_ACTION_GET_LANGPACKS) return "get-langpacks"; return NULL; } /** * gs_plugin_action_from_string: * @action: a #GsPluginAction, e.g. "install" * * Converts the string to an enumerated action. * * Returns: a GsPluginAction, e.g. %GS_PLUGIN_ACTION_INSTALL * * Since: 3.26 **/ GsPluginAction gs_plugin_action_from_string (const gchar *action) { if (g_strcmp0 (action, "setup") == 0) return GS_PLUGIN_ACTION_SETUP; if (g_strcmp0 (action, "install") == 0) return GS_PLUGIN_ACTION_INSTALL; if (g_strcmp0 (action, "download") == 0) return GS_PLUGIN_ACTION_DOWNLOAD; if (g_strcmp0 (action, "remove") == 0) return GS_PLUGIN_ACTION_REMOVE; if (g_strcmp0 (action, "update") == 0) return GS_PLUGIN_ACTION_UPDATE; if (g_strcmp0 (action, "set-rating") == 0) return GS_PLUGIN_ACTION_SET_RATING; if (g_strcmp0 (action, "upgrade-download") == 0) return GS_PLUGIN_ACTION_UPGRADE_DOWNLOAD; if (g_strcmp0 (action, "upgrade-trigger") == 0) return GS_PLUGIN_ACTION_UPGRADE_TRIGGER; if (g_strcmp0 (action, "launch") == 0) return GS_PLUGIN_ACTION_LAUNCH; if (g_strcmp0 (action, "update-cancel") == 0) return GS_PLUGIN_ACTION_UPDATE_CANCEL; if (g_strcmp0 (action, "add-shortcut") == 0) return GS_PLUGIN_ACTION_ADD_SHORTCUT; if (g_strcmp0 (action, "remove-shortcut") == 0) return GS_PLUGIN_ACTION_REMOVE_SHORTCUT; if (g_strcmp0 (action, "review-submit") == 0) return GS_PLUGIN_ACTION_REVIEW_SUBMIT; if (g_strcmp0 (action, "review-upvote") == 0) return GS_PLUGIN_ACTION_REVIEW_UPVOTE; if (g_strcmp0 (action, "review-downvote") == 0) return GS_PLUGIN_ACTION_REVIEW_DOWNVOTE; if (g_strcmp0 (action, "review-report") == 0) return GS_PLUGIN_ACTION_REVIEW_REPORT; if (g_strcmp0 (action, "review-remove") == 0) return GS_PLUGIN_ACTION_REVIEW_REMOVE; if (g_strcmp0 (action, "review-dismiss") == 0) return GS_PLUGIN_ACTION_REVIEW_DISMISS; if (g_strcmp0 (action, "get-updates") == 0) return GS_PLUGIN_ACTION_GET_UPDATES; if (g_strcmp0 (action, "get-distro-updates") == 0) return GS_PLUGIN_ACTION_GET_DISTRO_UPDATES; if (g_strcmp0 (action, "get-unvoted-reviews") == 0) return GS_PLUGIN_ACTION_GET_UNVOTED_REVIEWS; if (g_strcmp0 (action, "get-sources") == 0) return GS_PLUGIN_ACTION_GET_SOURCES; if (g_strcmp0 (action, "get-installed") == 0) return GS_PLUGIN_ACTION_GET_INSTALLED; if (g_strcmp0 (action, "get-popular") == 0) return GS_PLUGIN_ACTION_GET_POPULAR; if (g_strcmp0 (action, "get-featured") == 0) return GS_PLUGIN_ACTION_GET_FEATURED; if (g_strcmp0 (action, "search") == 0) return GS_PLUGIN_ACTION_SEARCH; if (g_strcmp0 (action, "search-files") == 0) return GS_PLUGIN_ACTION_SEARCH_FILES; if (g_strcmp0 (action, "search-provides") == 0) return GS_PLUGIN_ACTION_SEARCH_PROVIDES; if (g_strcmp0 (action, "get-categories") == 0) return GS_PLUGIN_ACTION_GET_CATEGORIES; if (g_strcmp0 (action, "get-category-apps") == 0) return GS_PLUGIN_ACTION_GET_CATEGORY_APPS; if (g_strcmp0 (action, "refine") == 0) return GS_PLUGIN_ACTION_REFINE; if (g_strcmp0 (action, "refresh") == 0) return GS_PLUGIN_ACTION_REFRESH; 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-recent") == 0) return GS_PLUGIN_ACTION_GET_RECENT; if (g_strcmp0 (action, "get-updates-historical") == 0) return GS_PLUGIN_ACTION_GET_UPDATES_HISTORICAL; if (g_strcmp0 (action, "initialize") == 0) return GS_PLUGIN_ACTION_INITIALIZE; if (g_strcmp0 (action, "destroy") == 0) return GS_PLUGIN_ACTION_DESTROY; if (g_strcmp0 (action, "get-alternates") == 0) return GS_PLUGIN_ACTION_GET_ALTERNATES; if (g_strcmp0 (action, "get-langpacks") == 0) return GS_PLUGIN_ACTION_GET_LANGPACKS; 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_USE_HISTORY) g_ptr_array_add (cstrs, "use-history"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_LICENSE) g_ptr_array_add (cstrs, "require-license"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL) g_ptr_array_add (cstrs, "require-url"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_DESCRIPTION) g_ptr_array_add (cstrs, "require-description"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE) g_ptr_array_add (cstrs, "require-size"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RATING) g_ptr_array_add (cstrs, "require-rating"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION) g_ptr_array_add (cstrs, "require-version"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_HISTORY) g_ptr_array_add (cstrs, "require-history"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION) g_ptr_array_add (cstrs, "require-setup-action"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_DETAILS) g_ptr_array_add (cstrs, "require-update-details"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN) g_ptr_array_add (cstrs, "require-origin"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED) g_ptr_array_add (cstrs, "require-related"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH) g_ptr_array_add (cstrs, "require-menu-path"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS) g_ptr_array_add (cstrs, "require-addons"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_ALLOW_PACKAGES) g_ptr_array_add (cstrs, "require-allow-packages"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPDATE_SEVERITY) g_ptr_array_add (cstrs, "require-update-severity"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_UPGRADE_REMOVED) g_ptr_array_add (cstrs, "require-upgrade-removed"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROVENANCE) g_ptr_array_add (cstrs, "require-provenance"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS) g_ptr_array_add (cstrs, "require-reviews"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEW_RATINGS) g_ptr_array_add (cstrs, "require-review-ratings"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_KEY_COLORS) g_ptr_array_add (cstrs, "require-key-colors"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON) g_ptr_array_add (cstrs, "require-icon"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PERMISSIONS) g_ptr_array_add (cstrs, "require-permissions"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_HOSTNAME) g_ptr_array_add (cstrs, "require-origin-hostname"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN_UI) g_ptr_array_add (cstrs, "require-origin-ui"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RUNTIME) g_ptr_array_add (cstrs, "require-runtime"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SCREENSHOTS) g_ptr_array_add (cstrs, "require-screenshots"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_CATEGORIES) g_ptr_array_add (cstrs, "require-categories"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_PROJECT_GROUP) g_ptr_array_add (cstrs, "require-project-group"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_DEVELOPER_NAME) g_ptr_array_add (cstrs, "require-developer-name"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_KUDOS) g_ptr_array_add (cstrs, "require-kudos"); if (refine_flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_CONTENT_RATING) g_ptr_array_add (cstrs, "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_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 (prop_id) { case PROP_FLAGS: priv->flags = g_value_get_uint64 (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 (prop_id) { case PROP_FLAGS: g_value_set_uint64 (value, priv->flags); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gs_plugin_class_init (GsPluginClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = gs_plugin_set_property; object_class->get_property = gs_plugin_get_property; object_class->finalize = gs_plugin_finalize; pspec = g_param_spec_uint64 ("flags", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_FLAGS, pspec); 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); } 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_unique_id_hash, (GEqualFunc) as_utils_unique_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: * * Creates a new plugin. * * Returns: a #GsPlugin * * Since: 3.22 **/ GsPlugin * gs_plugin_new (void) { GsPlugin *plugin; plugin = g_object_new (GS_TYPE_PLUGIN, NULL); return plugin; }