/* -*- 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) 2013 Matthias Clasen * Copyright (C) 2014-2018 Kalev Lember * * SPDX-License-Identifier: GPL-2.0+ */ /** * SECTION:gs-app * @title: GsApp * @include: gnome-software.h * @stability: Unstable * @short_description: An application that is either installed or that can be installed * * This object represents a 1:1 mapping to a .desktop file. The design is such * so you can't have different GsApp's for different versions or architectures * of a package. This rule really only applies to GsApps of kind %AS_APP_KIND_DESKTOP * and %AS_APP_KIND_GENERIC. We allow GsApps of kind %AS_APP_KIND_OS_UPDATE or * %AS_APP_KIND_GENERIC, which don't correspond to desktop files, but instead * represent a system update and its individual components. * * The #GsPluginLoader de-duplicates the GsApp instances that are produced by * plugins to ensure that there is a single instance of GsApp for each id, making * the id the primary key for this object. This ensures that actions triggered on * a #GsApp in different parts of gnome-software can be observed by connecting to * signals on the #GsApp. * * Information about other #GsApp objects can be stored in this object, for * instance in the gs_app_add_related() method or gs_app_get_history(). */ #include "config.h" #include #include #include #include "gs-app-collation.h" #include "gs-app-private.h" #include "gs-os-release.h" #include "gs-plugin.h" #include "gs-utils.h" typedef struct { GObject parent_instance; GMutex mutex; gchar *id; gchar *unique_id; gboolean unique_id_valid; gchar *branch; gchar *name; GsAppQuality name_quality; GPtrArray *icons; GPtrArray *sources; GPtrArray *source_ids; gchar *project_group; gchar *developer_name; gchar *agreement; gchar *version; gchar *version_ui; gchar *summary; GsAppQuality summary_quality; gchar *summary_missing; gchar *description; GsAppQuality description_quality; GPtrArray *screenshots; GPtrArray *categories; GPtrArray *key_colors; GHashTable *urls; GHashTable *launchables; gchar *license; GsAppQuality license_quality; gchar **menu_path; gchar *origin; gchar *origin_appstream; gchar *origin_hostname; gchar *update_version; gchar *update_version_ui; gchar *update_details; AsUrgencyKind update_urgency; GsAppPermissions update_permissions; gchar *management_plugin; guint match_value; guint priority; gint rating; GArray *review_ratings; GPtrArray *reviews; /* of AsReview */ GPtrArray *provides; /* of AsProvide */ guint64 size_installed; guint64 size_download; AsAppKind kind; AsAppState state; AsAppState state_recover; AsAppScope scope; AsBundleKind bundle_kind; guint progress; /* integer 0–100 (inclusive), or %GS_APP_PROGRESS_UNKNOWN */ gboolean allow_cancel; GHashTable *metadata; GsAppList *addons; GsAppList *related; GsAppList *history; guint64 install_date; guint64 kudos; gboolean to_be_installed; GsAppQuirk quirk; gboolean license_is_free; GsApp *runtime; GFile *local_file; AsContentRating *content_rating; GdkPixbuf *pixbuf; /* (nullable) (owned) */ AsScreenshot *action_screenshot; /* (nullable) (owned) */ GCancellable *cancellable; GsPluginAction pending_action; GsAppPermissions permissions; gboolean is_update_downloaded; } GsAppPrivate; enum { PROP_0, PROP_ID, PROP_NAME, PROP_VERSION, PROP_SUMMARY, PROP_DESCRIPTION, PROP_RATING, PROP_KIND, PROP_STATE, PROP_PROGRESS, PROP_CAN_CANCEL_INSTALLATION, PROP_INSTALL_DATE, PROP_QUIRK, PROP_PENDING_ACTION, PROP_KEY_COLORS, PROP_IS_UPDATE_DOWNLOADED, PROP_LAST }; static GParamSpec *obj_props[PROP_LAST] = { NULL, }; G_DEFINE_TYPE_WITH_PRIVATE (GsApp, gs_app, G_TYPE_OBJECT) static gboolean _g_set_str (gchar **str_ptr, const gchar *new_str) { if (*str_ptr == new_str || g_strcmp0 (*str_ptr, new_str) == 0) return FALSE; g_free (*str_ptr); *str_ptr = g_strdup (new_str); return TRUE; } static gboolean _g_set_strv (gchar ***strv_ptr, gchar **new_strv) { if (*strv_ptr == new_strv) return FALSE; g_strfreev (*strv_ptr); *strv_ptr = g_strdupv (new_strv); return TRUE; } static gboolean _g_set_ptr_array (GPtrArray **array_ptr, GPtrArray *new_array) { if (*array_ptr == new_array) return FALSE; if (*array_ptr != NULL) g_ptr_array_unref (*array_ptr); *array_ptr = g_ptr_array_ref (new_array); return TRUE; } static gboolean _g_set_array (GArray **array_ptr, GArray *new_array) { if (*array_ptr == new_array) return FALSE; if (*array_ptr != NULL) g_array_unref (*array_ptr); *array_ptr = g_array_ref (new_array); return TRUE; } static void gs_app_kv_lpad (GString *str, const gchar *key, const gchar *value) { gs_utils_append_key_value (str, 20, key, value); } static void gs_app_kv_size (GString *str, const gchar *key, guint64 value) { g_autofree gchar *tmp = NULL; if (value == GS_APP_SIZE_UNKNOWABLE) { gs_app_kv_lpad (str, key, "unknowable"); return; } tmp = g_format_size (value); gs_app_kv_lpad (str, key, tmp); } G_GNUC_PRINTF (3, 4) static void gs_app_kv_printf (GString *str, const gchar *key, const gchar *fmt, ...) { va_list args; g_autofree gchar *tmp = NULL; va_start (args, fmt); tmp = g_strdup_vprintf (fmt, args); va_end (args); gs_app_kv_lpad (str, key, tmp); } static const gchar * _as_app_quirk_flag_to_string (GsAppQuirk quirk) { switch (quirk) { case GS_APP_QUIRK_PROVENANCE: return "provenance"; case GS_APP_QUIRK_COMPULSORY: return "compulsory"; case GS_APP_QUIRK_HAS_SOURCE: return "has-source"; case GS_APP_QUIRK_IS_WILDCARD: return "is-wildcard"; case GS_APP_QUIRK_NEEDS_REBOOT: return "needs-reboot"; case GS_APP_QUIRK_NOT_REVIEWABLE: return "not-reviewable"; case GS_APP_QUIRK_HAS_SHORTCUT: return "has-shortcut"; case GS_APP_QUIRK_NOT_LAUNCHABLE: return "not-launchable"; case GS_APP_QUIRK_NEEDS_USER_ACTION: return "needs-user-action"; case GS_APP_QUIRK_IS_PROXY: return "is-proxy"; case GS_APP_QUIRK_REMOVABLE_HARDWARE: return "removable-hardware"; case GS_APP_QUIRK_DEVELOPER_VERIFIED: return "developer-verified"; case GS_APP_QUIRK_PARENTAL_FILTER: return "parental-filter"; case GS_APP_QUIRK_NEW_PERMISSIONS: return "new-permissions"; case GS_APP_QUIRK_PARENTAL_NOT_LAUNCHABLE: return "parental-not-launchable"; case GS_APP_QUIRK_HIDE_FROM_SEARCH: return "hide-from-search"; case GS_APP_QUIRK_HIDE_EVERYWHERE: return "hide-everywhere"; case GS_APP_QUIRK_DO_NOT_AUTO_UPDATE: return "do-not-auto-update"; default: return NULL; } } /* mutex must be held */ static const gchar * gs_app_get_unique_id_unlocked (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); /* invalid */ if (priv->id == NULL) return NULL; /* hmm, do what we can */ if (priv->unique_id == NULL || !priv->unique_id_valid) { g_free (priv->unique_id); priv->unique_id = as_utils_unique_id_build (priv->scope, priv->bundle_kind, priv->origin, priv->kind, priv->id, priv->branch); priv->unique_id_valid = TRUE; } return priv->unique_id; } /** * gs_app_compare_priority: * @app1: a #GsApp * @app2: a #GsApp * * Compares two applications using their priority. * * Use `gs_plugin_add_rule(plugin,GS_PLUGIN_RULE_BETTER_THAN,"plugin-name")` * to set the application priority values. * * Returns: a negative value if @app1 is less than @app2, a positive value if * @app1 is greater than @app2, and zero if @app1 is equal to @app2 **/ gint gs_app_compare_priority (GsApp *app1, GsApp *app2) { GsAppPrivate *priv1 = gs_app_get_instance_private (app1); GsAppPrivate *priv2 = gs_app_get_instance_private (app2); /* prefer prio */ if (priv1->priority > priv2->priority) return -1; if (priv1->priority < priv2->priority) return 1; /* fall back to bundle kind */ if (priv1->bundle_kind < priv2->bundle_kind) return -1; if (priv1->bundle_kind > priv2->bundle_kind) return 1; return 0; } /** * gs_app_quirk_to_string: * @quirk: a #GsAppQuirk * * Returns the quirk bitfield as a string. * * Returns: (transfer full): a string **/ static gchar * gs_app_quirk_to_string (GsAppQuirk quirk) { GString *str = g_string_new (""); guint64 i; /* nothing set */ if (quirk == GS_APP_QUIRK_NONE) { g_string_append (str, "none"); return g_string_free (str, FALSE); } /* get flags */ for (i = 1; i < GS_APP_QUIRK_LAST; i *= 2) { if ((quirk & i) == 0) continue; g_string_append_printf (str, "%s,", _as_app_quirk_flag_to_string (i)); } /* nothing recognised */ if (str->len == 0) { g_string_append (str, "unknown"); return g_string_free (str, FALSE); } /* remove trailing comma */ g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } static gchar * gs_app_kudos_to_string (guint64 kudos) { g_autoptr(GPtrArray) array = g_ptr_array_new (); if ((kudos & GS_APP_KUDO_MY_LANGUAGE) > 0) g_ptr_array_add (array, "my-language"); if ((kudos & GS_APP_KUDO_RECENT_RELEASE) > 0) g_ptr_array_add (array, "recent-release"); if ((kudos & GS_APP_KUDO_FEATURED_RECOMMENDED) > 0) g_ptr_array_add (array, "featured-recommended"); if ((kudos & GS_APP_KUDO_MODERN_TOOLKIT) > 0) g_ptr_array_add (array, "modern-toolkit"); if ((kudos & GS_APP_KUDO_SEARCH_PROVIDER) > 0) g_ptr_array_add (array, "search-provider"); if ((kudos & GS_APP_KUDO_INSTALLS_USER_DOCS) > 0) g_ptr_array_add (array, "installs-user-docs"); if ((kudos & GS_APP_KUDO_USES_NOTIFICATIONS) > 0) g_ptr_array_add (array, "uses-notifications"); if ((kudos & GS_APP_KUDO_HAS_KEYWORDS) > 0) g_ptr_array_add (array, "has-keywords"); if ((kudos & GS_APP_KUDO_HAS_SCREENSHOTS) > 0) g_ptr_array_add (array, "has-screenshots"); if ((kudos & GS_APP_KUDO_POPULAR) > 0) g_ptr_array_add (array, "popular"); if ((kudos & GS_APP_KUDO_HIGH_CONTRAST) > 0) g_ptr_array_add (array, "high-contrast"); if ((kudos & GS_APP_KUDO_HI_DPI_ICON) > 0) g_ptr_array_add (array, "hi-dpi-icon"); if ((kudos & GS_APP_KUDO_SANDBOXED) > 0) g_ptr_array_add (array, "sandboxed"); if ((kudos & GS_APP_KUDO_SANDBOXED_SECURE) > 0) g_ptr_array_add (array, "sandboxed-secure"); g_ptr_array_add (array, NULL); return g_strjoinv ("|", (gchar **) array->pdata); } /** * gs_app_to_string: * @app: a #GsApp * * Converts the application to a string. * This is not designed to serialize the object but to produce a string suitable * for debugging. * * Returns: A multi-line string * * Since: 3.22 **/ gchar * gs_app_to_string (GsApp *app) { GString *str = g_string_new ("GsApp:"); gs_app_to_string_append (app, str); if (str->len > 0) g_string_truncate (str, str->len - 1); return g_string_free (str, FALSE); } /** * gs_app_to_string_append: * @app: a #GsApp * @str: a #GString * * Appends the application to an existing string. * * Since: 3.26 **/ void gs_app_to_string_append (GsApp *app, GString *str) { GsAppClass *klass = GS_APP_GET_CLASS (app); GsAppPrivate *priv = gs_app_get_instance_private (app); AsImage *im; GList *keys; const gchar *tmp; guint i; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (str != NULL); g_string_append_printf (str, " [%p]\n", app); gs_app_kv_lpad (str, "kind", as_app_kind_to_string (priv->kind)); gs_app_kv_lpad (str, "state", as_app_state_to_string (priv->state)); if (priv->quirk > 0) { g_autofree gchar *qstr = gs_app_quirk_to_string (priv->quirk); gs_app_kv_lpad (str, "quirk", qstr); } if (priv->progress == GS_APP_PROGRESS_UNKNOWN) gs_app_kv_printf (str, "progress", "unknown"); else gs_app_kv_printf (str, "progress", "%u%%", priv->progress); if (priv->id != NULL) gs_app_kv_lpad (str, "id", priv->id); if (priv->unique_id != NULL) gs_app_kv_lpad (str, "unique-id", gs_app_get_unique_id (app)); if (priv->scope != AS_APP_SCOPE_UNKNOWN) gs_app_kv_lpad (str, "scope", as_app_scope_to_string (priv->scope)); if (priv->bundle_kind != AS_BUNDLE_KIND_UNKNOWN) { gs_app_kv_lpad (str, "bundle-kind", as_bundle_kind_to_string (priv->bundle_kind)); } if (priv->kudos > 0) { g_autofree gchar *kudo_str = NULL; kudo_str = gs_app_kudos_to_string (priv->kudos); gs_app_kv_lpad (str, "kudos", kudo_str); } gs_app_kv_printf (str, "kudo-percentage", "%u", gs_app_get_kudos_percentage (app)); if (priv->name != NULL) gs_app_kv_lpad (str, "name", priv->name); if (priv->pixbuf != NULL) gs_app_kv_printf (str, "pixbuf", "%p", priv->pixbuf); if (priv->action_screenshot != NULL) gs_app_kv_printf (str, "action-screenshot", "%p", priv->action_screenshot); for (i = 0; i < priv->icons->len; i++) { AsIcon *icon = g_ptr_array_index (priv->icons, i); gs_app_kv_lpad (str, "icon-kind", as_icon_kind_to_string (as_icon_get_kind (icon))); if (as_icon_get_pixbuf (icon) != NULL) { gs_app_kv_printf (str, "icon-pixbuf", "%p", as_icon_get_pixbuf (icon)); } if (as_icon_get_name (icon) != NULL) gs_app_kv_lpad (str, "icon-name", as_icon_get_name (icon)); if (as_icon_get_prefix (icon) != NULL) gs_app_kv_lpad (str, "icon-prefix", as_icon_get_prefix (icon)); if (as_icon_get_filename (icon) != NULL) gs_app_kv_lpad (str, "icon-filename", as_icon_get_filename (icon)); } if (priv->match_value != 0) gs_app_kv_printf (str, "match-value", "%05x", priv->match_value); if (priv->priority != 0) gs_app_kv_printf (str, "priority", "%u", priv->priority); if (priv->version != NULL) gs_app_kv_lpad (str, "version", priv->version); if (priv->version_ui != NULL) gs_app_kv_lpad (str, "version-ui", priv->version_ui); if (priv->update_version != NULL) gs_app_kv_lpad (str, "update-version", priv->update_version); if (priv->update_version_ui != NULL) gs_app_kv_lpad (str, "update-version-ui", priv->update_version_ui); if (priv->update_details != NULL) gs_app_kv_lpad (str, "update-details", priv->update_details); if (priv->update_urgency != AS_URGENCY_KIND_UNKNOWN) { gs_app_kv_printf (str, "update-urgency", "%u", priv->update_urgency); } if (priv->summary != NULL) gs_app_kv_lpad (str, "summary", priv->summary); if (priv->description != NULL) gs_app_kv_lpad (str, "description", priv->description); for (i = 0; i < priv->screenshots->len; i++) { AsScreenshot *ss = g_ptr_array_index (priv->screenshots, i); g_autofree gchar *key = NULL; tmp = as_screenshot_get_caption (ss, NULL); im = as_screenshot_get_image (ss, 0, 0); if (im == NULL) continue; key = g_strdup_printf ("screenshot-%02u", i); gs_app_kv_printf (str, key, "%s [%s]", as_image_get_url (im), tmp != NULL ? tmp : ""); } for (i = 0; i < priv->sources->len; i++) { g_autofree gchar *key = NULL; tmp = g_ptr_array_index (priv->sources, i); key = g_strdup_printf ("source-%02u", i); gs_app_kv_lpad (str, key, tmp); } for (i = 0; i < priv->source_ids->len; i++) { g_autofree gchar *key = NULL; tmp = g_ptr_array_index (priv->source_ids, i); key = g_strdup_printf ("source-id-%02u", i); gs_app_kv_lpad (str, key, tmp); } if (priv->local_file != NULL) { g_autofree gchar *fn = g_file_get_path (priv->local_file); gs_app_kv_lpad (str, "local-filename", fn); } if (priv->content_rating != NULL) { guint age = as_content_rating_get_minimum_age (priv->content_rating); if (age != G_MAXUINT) { g_autofree gchar *value = g_strdup_printf ("%u", age); gs_app_kv_lpad (str, "content-age", value); } gs_app_kv_lpad (str, "content-rating", as_content_rating_get_kind (priv->content_rating)); } tmp = g_hash_table_lookup (priv->urls, as_url_kind_to_string (AS_URL_KIND_HOMEPAGE)); if (tmp != NULL) gs_app_kv_lpad (str, "url{homepage}", tmp); keys = g_hash_table_get_keys (priv->launchables); for (GList *l = keys; l != NULL; l = l->next) { g_autofree gchar *key = NULL; key = g_strdup_printf ("launchable{%s}", (const gchar *) l->data); tmp = g_hash_table_lookup (priv->launchables, l->data); gs_app_kv_lpad (str, key, tmp); } g_list_free (keys); if (priv->license != NULL) { gs_app_kv_lpad (str, "license", priv->license); gs_app_kv_lpad (str, "license-is-free", gs_app_get_license_is_free (app) ? "yes" : "no"); } if (priv->management_plugin != NULL) gs_app_kv_lpad (str, "management-plugin", priv->management_plugin); if (priv->summary_missing != NULL) gs_app_kv_lpad (str, "summary-missing", priv->summary_missing); if (priv->menu_path != NULL && priv->menu_path[0] != NULL && priv->menu_path[0][0] != '\0') { g_autofree gchar *path = g_strjoinv (" → ", priv->menu_path); gs_app_kv_lpad (str, "menu-path", path); } if (priv->branch != NULL) gs_app_kv_lpad (str, "branch", priv->branch); if (priv->origin != NULL && priv->origin[0] != '\0') gs_app_kv_lpad (str, "origin", priv->origin); if (priv->origin_appstream != NULL && priv->origin_appstream[0] != '\0') gs_app_kv_lpad (str, "origin-appstream", priv->origin_appstream); if (priv->origin_hostname != NULL && priv->origin_hostname[0] != '\0') gs_app_kv_lpad (str, "origin-hostname", priv->origin_hostname); if (priv->rating != -1) gs_app_kv_printf (str, "rating", "%i", priv->rating); if (priv->review_ratings != NULL) { for (i = 0; i < priv->review_ratings->len; i++) { guint32 rat = g_array_index (priv->review_ratings, guint32, i); gs_app_kv_printf (str, "review-rating", "[%u:%u]", i, rat); } } if (priv->reviews != NULL) gs_app_kv_printf (str, "reviews", "%u", priv->reviews->len); if (priv->provides != NULL) gs_app_kv_printf (str, "provides", "%u", priv->provides->len); if (priv->install_date != 0) { gs_app_kv_printf (str, "install-date", "%" G_GUINT64_FORMAT "", priv->install_date); } if (priv->size_installed != 0) gs_app_kv_size (str, "size-installed", priv->size_installed); if (priv->size_download != 0) gs_app_kv_size (str, "size-download", gs_app_get_size_download (app)); for (i = 0; i < gs_app_list_length (priv->related); i++) { GsApp *app_tmp = gs_app_list_index (priv->related, i); const gchar *id = gs_app_get_unique_id (app_tmp); if (id == NULL) id = gs_app_get_source_default (app_tmp); gs_app_kv_lpad (str, "related", id); } for (i = 0; i < gs_app_list_length (priv->history); i++) { GsApp *app_tmp = gs_app_list_index (priv->history, i); gs_app_kv_lpad (str, "history", gs_app_get_unique_id (app_tmp)); } for (i = 0; i < priv->categories->len; i++) { tmp = g_ptr_array_index (priv->categories, i); gs_app_kv_lpad (str, "category", tmp); } for (i = 0; i < priv->key_colors->len; i++) { GdkRGBA *color = g_ptr_array_index (priv->key_colors, i); g_autofree gchar *key = NULL; key = g_strdup_printf ("key-color-%02u", i); gs_app_kv_printf (str, key, "%.0f,%.0f,%.0f", color->red * 255.f, color->green * 255.f, color->blue * 255.f); } keys = g_hash_table_get_keys (priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { GVariant *val; const GVariantType *val_type; g_autofree gchar *key = NULL; g_autofree gchar *val_str = NULL; key = g_strdup_printf ("{%s}", (const gchar *) l->data); val = g_hash_table_lookup (priv->metadata, l->data); val_type = g_variant_get_type (val); if (g_variant_type_equal (val_type, G_VARIANT_TYPE_STRING)) { val_str = g_variant_dup_string (val, NULL); } else if (g_variant_type_equal (val_type, G_VARIANT_TYPE_BOOLEAN)) { val_str = g_strdup (g_variant_get_boolean (val) ? "True" : "False"); } else if (g_variant_type_equal (val_type, G_VARIANT_TYPE_UINT32)) { val_str = g_strdup_printf ("%" G_GUINT32_FORMAT, g_variant_get_uint32 (val)); } else { val_str = g_strdup_printf ("unknown type of %s", g_variant_get_type_string (val)); } gs_app_kv_lpad (str, key, val_str); } g_list_free (keys); /* add subclassed info */ if (klass->to_string != NULL) klass->to_string (app, str); /* print runtime data too */ if (priv->runtime != NULL) { g_string_append (str, "\n\tRuntime:\n\t"); gs_app_to_string_append (priv->runtime, str); } g_string_append_printf (str, "\n"); } typedef struct { GsApp *app; GParamSpec *pspec; } AppNotifyData; static gboolean notify_idle_cb (gpointer data) { AppNotifyData *notify_data = data; g_object_notify_by_pspec (G_OBJECT (notify_data->app), notify_data->pspec); g_object_unref (notify_data->app); g_free (notify_data); return G_SOURCE_REMOVE; } static void gs_app_queue_notify (GsApp *app, GParamSpec *pspec) { AppNotifyData *notify_data; notify_data = g_new (AppNotifyData, 1); notify_data->app = g_object_ref (app); notify_data->pspec = pspec; g_idle_add (notify_idle_cb, notify_data); } /** * gs_app_get_id: * @app: a #GsApp * * Gets the application ID. * * Returns: The whole ID, e.g. "gimp.desktop" * * Since: 3.22 **/ const gchar * gs_app_get_id (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->id; } /** * gs_app_set_id: * @app: a #GsApp * @id: a application ID, e.g. "gimp.desktop" * * Sets the application ID. */ void gs_app_set_id (GsApp *app, const gchar *id) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); if (_g_set_str (&priv->id, id)) priv->unique_id_valid = FALSE; } /** * gs_app_get_scope: * @app: a #GsApp * * Gets the scope of the application. * * Returns: the #AsAppScope, e.g. %AS_APP_SCOPE_USER * * Since: 3.22 **/ AsAppScope gs_app_get_scope (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), AS_APP_SCOPE_UNKNOWN); return priv->scope; } /** * gs_app_set_scope: * @app: a #GsApp * @scope: a #AsAppScope, e.g. AS_APP_SCOPE_SYSTEM * * This sets the scope of the application. * * Since: 3.22 **/ void gs_app_set_scope (GsApp *app, AsAppScope scope) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); /* same */ if (scope == priv->scope) return; priv->scope = scope; /* no longer valid */ priv->unique_id_valid = FALSE; } /** * gs_app_get_bundle_kind: * @app: a #GsApp * * Gets the bundle kind of the application. * * Returns: the #AsAppScope, e.g. %AS_BUNDLE_KIND_FLATPAK * * Since: 3.22 **/ AsBundleKind gs_app_get_bundle_kind (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), AS_BUNDLE_KIND_UNKNOWN); return priv->bundle_kind; } /** * gs_app_set_bundle_kind: * @app: a #GsApp * @bundle_kind: a #AsAppScope, e.g. AS_BUNDLE_KIND_FLATPAK * * This sets the bundle kind of the application. * * Since: 3.22 **/ void gs_app_set_bundle_kind (GsApp *app, AsBundleKind bundle_kind) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); /* same */ if (bundle_kind == priv->bundle_kind) return; priv->bundle_kind = bundle_kind; /* no longer valid */ priv->unique_id_valid = FALSE; } /** * gs_app_get_state: * @app: a #GsApp * * Gets the state of the application. * * Returns: the #AsAppState, e.g. %AS_APP_STATE_INSTALLED * * Since: 3.22 **/ AsAppState gs_app_get_state (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), AS_APP_STATE_UNKNOWN); return priv->state; } /** * gs_app_get_progress: * @app: a #GsApp * * Gets the percentage completion. * * Returns: the percentage completion (0–100 inclusive), or %GS_APP_PROGRESS_UNKNOWN for unknown * * Since: 3.22 **/ guint gs_app_get_progress (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), GS_APP_PROGRESS_UNKNOWN); return priv->progress; } /** * gs_app_get_allow_cancel: * @app: a #GsApp * * Gets whether the app's installation or upgrade can be cancelled. * * Returns: TRUE if cancellation is possible, FALSE otherwise. * * Since: 3.26 **/ gboolean gs_app_get_allow_cancel (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), FALSE); return priv->allow_cancel; } /** * gs_app_set_state_recover: * @app: a #GsApp * * Sets the application state to the last status value that was not * transient. * * Since: 3.22 **/ void gs_app_set_state_recover (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); if (priv->state_recover == AS_APP_STATE_UNKNOWN) return; if (priv->state_recover == priv->state) return; g_debug ("recovering state on %s from %s to %s", priv->id, as_app_state_to_string (priv->state), as_app_state_to_string (priv->state_recover)); /* make sure progress gets reset when recovering state, to prevent * confusing initial states when going through more than one attempt */ gs_app_set_progress (app, GS_APP_PROGRESS_UNKNOWN); priv->state = priv->state_recover; gs_app_queue_notify (app, obj_props[PROP_STATE]); } /* mutex must be held */ static gboolean gs_app_set_state_internal (GsApp *app, AsAppState state) { GsAppPrivate *priv = gs_app_get_instance_private (app); gboolean state_change_ok = FALSE; /* same */ if (priv->state == state) return FALSE; /* check the state change is allowed */ switch (priv->state) { case AS_APP_STATE_UNKNOWN: /* unknown has to go into one of the stable states */ if (state == AS_APP_STATE_INSTALLED || state == AS_APP_STATE_QUEUED_FOR_INSTALL || state == AS_APP_STATE_AVAILABLE || state == AS_APP_STATE_AVAILABLE_LOCAL || state == AS_APP_STATE_UPDATABLE || state == AS_APP_STATE_UPDATABLE_LIVE || state == AS_APP_STATE_UNAVAILABLE) state_change_ok = TRUE; break; case AS_APP_STATE_INSTALLED: /* installed has to go into an action state */ if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_REMOVING || state == AS_APP_STATE_UNAVAILABLE || state == AS_APP_STATE_UPDATABLE || state == AS_APP_STATE_UPDATABLE_LIVE) state_change_ok = TRUE; break; case AS_APP_STATE_QUEUED_FOR_INSTALL: if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_INSTALLING || state == AS_APP_STATE_AVAILABLE) state_change_ok = TRUE; break; case AS_APP_STATE_AVAILABLE: /* available has to go into an action state */ if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_QUEUED_FOR_INSTALL || state == AS_APP_STATE_INSTALLING) state_change_ok = TRUE; break; case AS_APP_STATE_INSTALLING: /* installing has to go into an stable state */ if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_INSTALLED || state == AS_APP_STATE_UPDATABLE || state == AS_APP_STATE_UPDATABLE_LIVE || state == AS_APP_STATE_AVAILABLE) state_change_ok = TRUE; break; case AS_APP_STATE_REMOVING: /* removing has to go into an stable state */ if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_AVAILABLE || state == AS_APP_STATE_INSTALLED) state_change_ok = TRUE; break; case AS_APP_STATE_UPDATABLE: /* updatable has to go into an action state */ if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_REMOVING || state == AS_APP_STATE_INSTALLING) state_change_ok = TRUE; break; case AS_APP_STATE_UPDATABLE_LIVE: /* updatable-live has to go into an action state */ if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_REMOVING || state == AS_APP_STATE_INSTALLING) state_change_ok = TRUE; break; case AS_APP_STATE_UNAVAILABLE: /* updatable has to go into an action state */ if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_AVAILABLE) state_change_ok = TRUE; break; case AS_APP_STATE_AVAILABLE_LOCAL: /* local has to go into an action state */ if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_INSTALLING) state_change_ok = TRUE; break; default: g_warning ("state %s unhandled", as_app_state_to_string (priv->state)); g_assert_not_reached (); } /* this state change was unexpected */ if (!state_change_ok) { g_warning ("State change on %s from %s to %s is not OK", gs_app_get_unique_id_unlocked (app), as_app_state_to_string (priv->state), as_app_state_to_string (state)); } priv->state = state; if (state == AS_APP_STATE_UNKNOWN || state == AS_APP_STATE_AVAILABLE_LOCAL || state == AS_APP_STATE_AVAILABLE) priv->install_date = 0; /* save this to simplify error handling in the plugins */ switch (state) { case AS_APP_STATE_INSTALLING: case AS_APP_STATE_REMOVING: case AS_APP_STATE_QUEUED_FOR_INSTALL: /* transient, so ignore */ break; default: if (priv->state_recover != state) priv->state_recover = state; break; } return TRUE; } /** * gs_app_set_progress: * @app: a #GsApp * @percentage: a percentage progress (0–100 inclusive), or %GS_APP_PROGRESS_UNKNOWN * * This sets the progress completion of the application. Use * %GS_APP_PROGRESS_UNKNOWN if the progress is unknown or has a wide confidence * interval. * * If called more than once with the same value then subsequent calls * will be ignored. * * Since: 3.22 **/ void gs_app_set_progress (GsApp *app, guint percentage) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); if (priv->progress == percentage) return; if (percentage != GS_APP_PROGRESS_UNKNOWN && percentage > 100) { g_warning ("cannot set %u%% for %s, setting instead: 100%%", percentage, gs_app_get_unique_id_unlocked (app)); percentage = 100; } priv->progress = percentage; gs_app_queue_notify (app, obj_props[PROP_PROGRESS]); } /** * gs_app_set_allow_cancel: * @app: a #GsApp * @allow_cancel: if the installation or upgrade can be cancelled or not * * This sets a flag indicating whether the operation can be cancelled or not. * This is used by the UI to set the "Cancel" button insensitive as * appropriate. * * Since: 3.26 **/ void gs_app_set_allow_cancel (GsApp *app, gboolean allow_cancel) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); if (priv->allow_cancel == allow_cancel) return; priv->allow_cancel = allow_cancel; gs_app_queue_notify (app, obj_props[PROP_CAN_CANCEL_INSTALLATION]); } static void gs_app_set_pending_action_internal (GsApp *app, GsPluginAction action) { GsAppPrivate *priv = gs_app_get_instance_private (app); if (priv->pending_action == action) return; priv->pending_action = action; gs_app_queue_notify (app, obj_props[PROP_PENDING_ACTION]); } /** * gs_app_set_state: * @app: a #GsApp * @state: a #AsAppState, e.g. AS_APP_STATE_UPDATABLE_LIVE * * This sets the state of the application. * The following state diagram explains the typical states. * All applications start in state %AS_APP_STATE_UNKNOWN, * but the frontend is not supposed to see GsApps with this state. * * Plugins are responsible for changing the state to one of the other * states before the GsApp is passed to the frontend. * * |[ * UPDATABLE --> INSTALLING --> INSTALLED * UPDATABLE --> REMOVING --> AVAILABLE * INSTALLED --> REMOVING --> AVAILABLE * AVAILABLE --> INSTALLING --> INSTALLED * AVAILABLE <--> QUEUED --> INSTALLING --> INSTALLED * UNKNOWN --> UNAVAILABLE * ]| * * Since: 3.22 **/ void gs_app_set_state (GsApp *app, AsAppState state) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); if (gs_app_set_state_internal (app, state)) { /* since the state changed, and the pending-action refers to * actions that usually change the state, we assign it to the * appropriate action here */ GsPluginAction action = GS_PLUGIN_ACTION_UNKNOWN; if (priv->state == AS_APP_STATE_QUEUED_FOR_INSTALL) action = GS_PLUGIN_ACTION_INSTALL; gs_app_set_pending_action_internal (app, action); gs_app_queue_notify (app, obj_props[PROP_STATE]); } } /** * gs_app_get_kind: * @app: a #GsApp * * Gets the kind of the application. * * Returns: the #AsAppKind, e.g. %AS_APP_KIND_UNKNOWN * * Since: 3.22 **/ AsAppKind gs_app_get_kind (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), AS_APP_KIND_UNKNOWN); return priv->kind; } /** * gs_app_set_kind: * @app: a #GsApp * @kind: a #AsAppKind, e.g. #AS_APP_KIND_DESKTOP * * This sets the kind of the application. * The following state diagram explains the typical states. * All applications start with kind %AS_APP_KIND_UNKNOWN. * * |[ * PACKAGE --> NORMAL * PACKAGE --> SYSTEM * NORMAL --> SYSTEM * ]| * * Since: 3.22 **/ void gs_app_set_kind (GsApp *app, AsAppKind kind) { GsAppPrivate *priv = gs_app_get_instance_private (app); gboolean state_change_ok = FALSE; g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* same */ if (priv->kind == kind) return; /* trying to change */ if (priv->kind != AS_APP_KIND_UNKNOWN && kind == AS_APP_KIND_UNKNOWN) { g_warning ("automatically prevented from changing " "kind on %s from %s to %s!", gs_app_get_unique_id_unlocked (app), as_app_kind_to_string (priv->kind), as_app_kind_to_string (kind)); return; } /* check the state change is allowed */ switch (priv->kind) { case AS_APP_KIND_UNKNOWN: case AS_APP_KIND_GENERIC: /* all others derive from generic */ state_change_ok = TRUE; break; case AS_APP_KIND_DESKTOP: /* desktop has to be reset to override */ if (kind == AS_APP_KIND_UNKNOWN) state_change_ok = TRUE; break; default: /* this can never change state */ break; } /* this state change was unexpected */ if (!state_change_ok) { g_warning ("Kind change on %s from %s to %s is not OK", priv->id, as_app_kind_to_string (priv->kind), as_app_kind_to_string (kind)); return; } priv->kind = kind; gs_app_queue_notify (app, obj_props[PROP_KIND]); /* no longer valid */ priv->unique_id_valid = FALSE; } /** * gs_app_get_unique_id: * @app: a #GsApp * * Gets the unique application ID used for de-duplication. * If nothing has been set the value from gs_app_get_id() will be used. * * Returns: The unique ID, e.g. `system/package/fedora/desktop/gimp.desktop/i386/master`, or %NULL * * Since: 3.22 **/ const gchar * gs_app_get_unique_id (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_val_if_fail (GS_IS_APP (app), NULL); locker = g_mutex_locker_new (&priv->mutex); return gs_app_get_unique_id_unlocked (app); } /** * gs_app_set_unique_id: * @app: a #GsApp * @unique_id: a unique application ID, e.g. `system/package/fedora/desktop/gimp.desktop/i386/master` * * Sets the unique application ID. Any #GsApp using the same ID will be * deduplicated. This means that applications that can exist from more than * one plugin should use this method. */ void gs_app_set_unique_id (GsApp *app, const gchar *unique_id) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* check for sanity */ if (!as_utils_unique_id_valid (unique_id)) g_warning ("unique_id %s not valid", unique_id); g_free (priv->unique_id); priv->unique_id = g_strdup (unique_id); priv->unique_id_valid = TRUE; } /** * gs_app_get_name: * @app: a #GsApp * * Gets the application name. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_name (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->name; } /** * gs_app_set_name: * @app: a #GsApp * @quality: A #GsAppQuality, e.g. %GS_APP_QUALITY_LOWEST * @name: The short localized name, e.g. "Calculator" * * Sets the application name. * * Since: 3.22 **/ void gs_app_set_name (GsApp *app, GsAppQuality quality, const gchar *name) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* only save this if the data is sufficiently high quality */ if (quality < priv->name_quality) return; priv->name_quality = quality; if (_g_set_str (&priv->name, name)) g_object_notify_by_pspec (G_OBJECT (app), obj_props[PROP_NAME]); } /** * gs_app_get_branch: * @app: a #GsApp * * Gets the application branch. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_branch (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->branch; } /** * gs_app_set_branch: * @app: a #GsApp * @branch: The branch, e.g. "master" * * Sets the application branch. * * Since: 3.22 **/ void gs_app_set_branch (GsApp *app, const gchar *branch) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); if (_g_set_str (&priv->branch, branch)) priv->unique_id_valid = FALSE; } /** * gs_app_get_source_default: * @app: a #GsApp * * Gets the default source. * * Returns: a string, or %NULL * * Since: 3.22 **/ const gchar * gs_app_get_source_default (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); if (priv->sources->len == 0) return NULL; return g_ptr_array_index (priv->sources, 0); } /** * gs_app_add_source: * @app: a #GsApp * @source: a source name * * Adds a source name for the application. * * Since: 3.22 **/ void gs_app_add_source (GsApp *app, const gchar *source) { GsAppPrivate *priv = gs_app_get_instance_private (app); const gchar *tmp; guint i; g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (source != NULL); locker = g_mutex_locker_new (&priv->mutex); /* check source doesn't already exist */ for (i = 0; i < priv->sources->len; i++) { tmp = g_ptr_array_index (priv->sources, i); if (g_strcmp0 (tmp, source) == 0) return; } g_ptr_array_add (priv->sources, g_strdup (source)); } /** * gs_app_get_sources: * @app: a #GsApp * * Gets the list of sources for the application. * * Returns: (element-type utf8) (transfer none): a list * * Since: 3.22 **/ GPtrArray * gs_app_get_sources (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->sources; } /** * gs_app_set_sources: * @app: a #GsApp * @sources: The non-localized short names, e.g. ["gnome-calculator"] * * This name is used for the update page if the application is collected into * the 'OS Updates' group. * It is typically the package names, although this should not be relied upon. * * Since: 3.22 **/ void gs_app_set_sources (GsApp *app, GPtrArray *sources) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); _g_set_ptr_array (&priv->sources, sources); } /** * gs_app_get_source_id_default: * @app: a #GsApp * * Gets the default source ID. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_source_id_default (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); if (priv->source_ids->len == 0) return NULL; return g_ptr_array_index (priv->source_ids, 0); } /** * gs_app_get_source_ids: * @app: a #GsApp * * Gets the list of source IDs. * * Returns: (element-type utf8) (transfer none): a list * * Since: 3.22 **/ GPtrArray * gs_app_get_source_ids (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->source_ids; } /** * gs_app_clear_source_ids: * @app: a #GsApp * * Clear the list of source IDs. * * Since: 3.22 **/ void gs_app_clear_source_ids (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); g_ptr_array_set_size (priv->source_ids, 0); } /** * gs_app_set_source_ids: * @app: a #GsApp * @source_ids: The source-id, e.g. ["gnome-calculator;0.134;fedora"] * or ["/home/hughsie/.local/share/applications/0ad.desktop"] * * This ID is used internally to the controlling plugin. * * Since: 3.22 **/ void gs_app_set_source_ids (GsApp *app, GPtrArray *source_ids) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); _g_set_ptr_array (&priv->source_ids, source_ids); } /** * gs_app_add_source_id: * @app: a #GsApp * @source_id: a source ID, e.g. "gnome-calculator;0.134;fedora" * * Adds a source ID to the application. * * Since: 3.22 **/ void gs_app_add_source_id (GsApp *app, const gchar *source_id) { GsAppPrivate *priv = gs_app_get_instance_private (app); const gchar *tmp; guint i; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (source_id != NULL); /* only add if not already present */ for (i = 0; i < priv->source_ids->len; i++) { tmp = g_ptr_array_index (priv->source_ids, i); if (g_strcmp0 (tmp, source_id) == 0) return; } g_ptr_array_add (priv->source_ids, g_strdup (source_id)); } /** * gs_app_get_project_group: * @app: a #GsApp * * Gets a project group for the application. * Applications belonging to other project groups may not be shown in * this software center. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_project_group (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->project_group; } /** * gs_app_get_developer_name: * @app: a #GsApp * * Gets the developer name for the application. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_developer_name (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->developer_name; } /** * gs_app_set_project_group: * @app: a #GsApp * @project_group: The non-localized project group, e.g. "GNOME" or "KDE" * * Sets a project group for the application. * * Since: 3.22 **/ void gs_app_set_project_group (GsApp *app, const gchar *project_group) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); _g_set_str (&priv->project_group, project_group); } /** * gs_app_set_developer_name: * @app: a #GsApp * @developer_name: The developer name, e.g. "Richard Hughes" * * Sets a developer name for the application. * * Since: 3.22 **/ void gs_app_set_developer_name (GsApp *app, const gchar *developer_name) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); _g_set_str (&priv->developer_name, developer_name); } /** * gs_app_get_pixbuf: * @app: a #GsApp * * Gets a pixbuf to represent the application. * * Returns: (transfer none) (nullable): a #GdkPixbuf, or %NULL * * Since: 3.22 **/ GdkPixbuf * gs_app_get_pixbuf (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->pixbuf; } /** * gs_app_get_action_screenshot: * @app: a #GsApp * * Gets a screenshot for the pending user action. * * Returns: (transfer none) (nullable): a #AsScreenshot, or %NULL * * Since: 3.38 **/ AsScreenshot * gs_app_get_action_screenshot (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->action_screenshot; } /** * gs_app_get_icons: * @app: a #GsApp * * Gets the icons for the application. * * Returns: (transfer none) (element-type AsIcon): an array of icons * * Since: 3.22 **/ GPtrArray * gs_app_get_icons (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->icons; } /** * gs_app_add_icon: * @app: a #GsApp * @icon: a #AsIcon, or %NULL to remove all icons * * Adds an icon to use for the application. * If the first icon added cannot be loaded then the next one is tried. * * Since: 3.22 **/ void gs_app_add_icon (GsApp *app, AsIcon *icon) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); if (icon == NULL) { g_ptr_array_set_size (priv->icons, 0); return; } g_ptr_array_add (priv->icons, g_object_ref (icon)); } /** * gs_app_get_use_drop_shadow: * @app: a #GsApp * * Uses a heuristic to work out if the application pixbuf should have a drop * shadow applied. * * Returns: %TRUE if a drop shadow should be applied * * Since: 3.34 **/ gboolean gs_app_get_use_drop_shadow (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); AsIcon *ic; /* guess */ if (priv->icons->len == 0) return TRUE; /* stock, and symbolic */ ic = g_ptr_array_index (priv->icons, 0); return as_icon_get_kind (ic) != AS_ICON_KIND_STOCK || !g_str_has_suffix (as_icon_get_name (ic), "-symbolic"); } /** * gs_app_get_agreement: * @app: a #GsApp * * Gets the agreement text for the application. * * Returns: a string in AppStream description format, or %NULL for unset * * Since: 3.28 **/ const gchar * gs_app_get_agreement (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->agreement; } /** * gs_app_set_agreement: * @app: a #GsApp * @agreement: The agreement text, e.g. "

Foobar

" * * Sets the application end-user agreement (e.g. a EULA) in AppStream * description format. * * Since: 3.28 **/ void gs_app_set_agreement (GsApp *app, const gchar *agreement) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); _g_set_str (&priv->agreement, agreement); } /** * gs_app_get_local_file: * @app: a #GsApp * * Gets the file that backs this application, for instance this might * be a local file in ~/Downloads that we are installing. * * Returns: (transfer none): a #GFile, or %NULL * * Since: 3.22 **/ GFile * gs_app_get_local_file (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->local_file; } /** * gs_app_set_local_file: * @app: a #GsApp * @local_file: a #GFile, or %NULL * * Sets the file that backs this application, for instance this might * be a local file in ~/Downloads that we are installing. * * Since: 3.22 **/ void gs_app_set_local_file (GsApp *app, GFile *local_file) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); g_set_object (&priv->local_file, local_file); } /** * gs_app_get_content_rating: * @app: a #GsApp * * Gets the content rating for this application. * * Returns: (transfer none): a #AsContentRating, or %NULL * * Since: 3.24 **/ AsContentRating * gs_app_get_content_rating (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->content_rating; } /** * gs_app_set_content_rating: * @app: a #GsApp * @content_rating: a #AsContentRating, or %NULL * * Sets the content rating for this application. * * Since: 3.24 **/ void gs_app_set_content_rating (GsApp *app, AsContentRating *content_rating) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); g_set_object (&priv->content_rating, content_rating); } /** * gs_app_get_runtime: * @app: a #GsApp * * Gets the runtime for the installed application. * * Returns: (transfer none): a #GsApp, or %NULL for unset * * Since: 3.22 **/ GsApp * gs_app_get_runtime (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->runtime; } /** * gs_app_set_runtime: * @app: a #GsApp * @runtime: a #GsApp * * Sets the runtime that the installed application requires. * * Since: 3.22 **/ void gs_app_set_runtime (GsApp *app, GsApp *runtime) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (app != runtime); locker = g_mutex_locker_new (&priv->mutex); g_set_object (&priv->runtime, runtime); } /** * gs_app_set_pixbuf: * @app: a #GsApp * @pixbuf: (transfer none) (nullable): a #GdkPixbuf, or %NULL * * Sets a pixbuf used to represent the application. * * Since: 3.22 **/ void gs_app_set_pixbuf (GsApp *app, GdkPixbuf *pixbuf) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); g_set_object (&priv->pixbuf, pixbuf); } /** * gs_app_set_action_screenshot: * @app: a #GsApp * @action_screenshot: (transfer none) (nullable): a #AsScreenshot, or %NULL * * Sets a screenshot used to represent the action. * * Since: 3.38 **/ void gs_app_set_action_screenshot (GsApp *app, AsScreenshot *action_screenshot) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); g_set_object (&priv->action_screenshot, action_screenshot); } typedef enum { GS_APP_VERSION_FIXUP_RELEASE = 1, GS_APP_VERSION_FIXUP_DISTRO_SUFFIX = 2, GS_APP_VERSION_FIXUP_GIT_SUFFIX = 4, GS_APP_VERSION_FIXUP_LAST, } GsAppVersionFixup; /** * gs_app_get_ui_version: * * convert 1:1.6.2-7.fc17 into "Version 1.6.2" **/ static gchar * gs_app_get_ui_version (const gchar *version, guint64 flags) { guint i; gchar *new; gchar *f; /* nothing set */ if (version == NULL) return NULL; /* first remove any epoch */ for (i = 0; version[i] != '\0'; i++) { if (version[i] == ':') { version = &version[i+1]; break; } if (!g_ascii_isdigit (version[i])) break; } /* then remove any distro suffix */ new = g_strdup (version); if ((flags & GS_APP_VERSION_FIXUP_DISTRO_SUFFIX) > 0) { f = g_strstr_len (new, -1, ".fc"); if (f != NULL) *f= '\0'; f = g_strstr_len (new, -1, ".el"); if (f != NULL) *f= '\0'; } /* then remove any release */ if ((flags & GS_APP_VERSION_FIXUP_RELEASE) > 0) { f = g_strrstr_len (new, -1, "-"); if (f != NULL) *f= '\0'; } /* then remove any git suffix */ if ((flags & GS_APP_VERSION_FIXUP_GIT_SUFFIX) > 0) { f = g_strrstr_len (new, -1, ".2012"); if (f != NULL) *f= '\0'; f = g_strrstr_len (new, -1, ".2013"); if (f != NULL) *f= '\0'; } return new; } static void gs_app_ui_versions_invalidate (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_free (priv->version_ui); g_free (priv->update_version_ui); priv->version_ui = NULL; priv->update_version_ui = NULL; } static void gs_app_ui_versions_populate (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); guint i; guint64 flags[] = { GS_APP_VERSION_FIXUP_RELEASE | GS_APP_VERSION_FIXUP_DISTRO_SUFFIX | GS_APP_VERSION_FIXUP_GIT_SUFFIX, GS_APP_VERSION_FIXUP_DISTRO_SUFFIX | GS_APP_VERSION_FIXUP_GIT_SUFFIX, GS_APP_VERSION_FIXUP_DISTRO_SUFFIX, 0 }; /* try each set of bitfields in order */ for (i = 0; flags[i] != 0; i++) { priv->version_ui = gs_app_get_ui_version (priv->version, flags[i]); priv->update_version_ui = gs_app_get_ui_version (priv->update_version, flags[i]); if (g_strcmp0 (priv->version_ui, priv->update_version_ui) != 0) { gs_app_queue_notify (app, obj_props[PROP_VERSION]); return; } gs_app_ui_versions_invalidate (app); } /* we tried, but failed */ priv->version_ui = g_strdup (priv->version); priv->update_version_ui = g_strdup (priv->update_version); } /** * gs_app_get_version: * @app: a #GsApp * * Gets the exact version for the application. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_version (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->version; } /** * gs_app_get_version_ui: * @app: a #GsApp * * Gets a version string that can be displayed in a UI. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_version_ui (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); /* work out the two version numbers */ if (priv->version != NULL && priv->version_ui == NULL) { gs_app_ui_versions_populate (app); } return priv->version_ui; } /** * gs_app_set_version: * @app: a #GsApp * @version: The version, e.g. "2:1.2.3.fc19" * * This saves the version after stripping out any non-friendly parts, such as * distro tags, git revisions and that kind of thing. * * Since: 3.22 **/ void gs_app_set_version (GsApp *app, const gchar *version) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); if (_g_set_str (&priv->version, version)) { gs_app_ui_versions_invalidate (app); gs_app_queue_notify (app, obj_props[PROP_VERSION]); } } /** * gs_app_get_summary: * @app: a #GsApp * * Gets the single-line description of the application. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_summary (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->summary; } /** * gs_app_set_summary: * @app: a #GsApp * @quality: a #GsAppQuality, e.g. %GS_APP_QUALITY_LOWEST * @summary: a string, e.g. "A graphical calculator for GNOME" * * The medium length one-line localized name. * * Since: 3.22 **/ void gs_app_set_summary (GsApp *app, GsAppQuality quality, const gchar *summary) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* only save this if the data is sufficiently high quality */ if (quality < priv->summary_quality) return; priv->summary_quality = quality; if (_g_set_str (&priv->summary, summary)) g_object_notify_by_pspec (G_OBJECT (app), obj_props[PROP_SUMMARY]); } /** * gs_app_get_description: * @app: a #GsApp * * Gets the long multi-line description of the application. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_description (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->description; } /** * gs_app_set_description: * @app: a #GsApp * @quality: a #GsAppQuality, e.g. %GS_APP_QUALITY_LOWEST * @description: a string, e.g. "GNOME Calculator is a graphical calculator for GNOME..." * * Sets the long multi-line description of the application. * * Since: 3.22 **/ void gs_app_set_description (GsApp *app, GsAppQuality quality, const gchar *description) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* only save this if the data is sufficiently high quality */ if (quality < priv->description_quality) return; priv->description_quality = quality; _g_set_str (&priv->description, description); } /** * gs_app_get_url: * @app: a #GsApp * @kind: a #AsUrlKind, e.g. %AS_URL_KIND_HOMEPAGE * * Gets a web address of a specific type. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_url (GsApp *app, AsUrlKind kind) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_val_if_fail (GS_IS_APP (app), NULL); locker = g_mutex_locker_new (&priv->mutex); return g_hash_table_lookup (priv->urls, as_url_kind_to_string (kind)); } /** * gs_app_set_url: * @app: a #GsApp * @kind: a #AsUrlKind, e.g. %AS_URL_KIND_HOMEPAGE * @url: a web URL, e.g. "http://www.hughsie.com/" * * Sets a web address of a specific type. * * Since: 3.22 **/ void gs_app_set_url (GsApp *app, AsUrlKind kind, const gchar *url) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); g_hash_table_insert (priv->urls, g_strdup (as_url_kind_to_string (kind)), g_strdup (url)); } /** * gs_app_get_launchable: * @app: a #GsApp * @kind: a #AsLaunchableKind, e.g. %AS_LAUNCHABLE_KIND_DESKTOP_ID * * Gets a launchable of a specific type. * * Returns: a string, or %NULL for unset * * Since: 3.28 **/ const gchar * gs_app_get_launchable (GsApp *app, AsLaunchableKind kind) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return g_hash_table_lookup (priv->launchables, as_launchable_kind_to_string (kind)); } /** * gs_app_set_launchable: * @app: a #GsApp * @kind: a #AsLaunchableKind, e.g. %AS_LAUNCHABLE_KIND_DESKTOP_ID * @launchable: a way to launch, e.g. "org.gnome.Sysprof2.desktop" * * Sets a launchable of a specific type. * * Since: 3.28 **/ void gs_app_set_launchable (GsApp *app, AsLaunchableKind kind, const gchar *launchable) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); g_hash_table_insert (priv->launchables, g_strdup (as_launchable_kind_to_string (kind)), g_strdup (launchable)); } /** * gs_app_get_license: * @app: a #GsApp * * Gets the project license of the application. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_license (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->license; } /** * gs_app_get_license_is_free: * @app: a #GsApp * * Returns if the application is free software. * * Returns: %TRUE if the application is free software * * Since: 3.22 **/ gboolean gs_app_get_license_is_free (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), FALSE); return priv->license_is_free; } static gboolean gs_app_get_license_token_is_nonfree (const gchar *token) { /* grammar */ if (g_strcmp0 (token, "(") == 0) return FALSE; if (g_strcmp0 (token, ")") == 0) return FALSE; /* a token, but still nonfree */ if (g_str_has_prefix (token, "@LicenseRef-proprietary")) return TRUE; /* if it has a prefix, assume it is free */ return token[0] != '@'; } /** * gs_app_set_license: * @app: a #GsApp * @quality: a #GsAppQuality, e.g. %GS_APP_QUALITY_NORMAL * @license: a SPDX license string, e.g. "GPL-3.0 AND LGPL-2.0+" * * Sets the project licenses used in the application. * * Since: 3.22 **/ void gs_app_set_license (GsApp *app, GsAppQuality quality, const gchar *license) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; guint i; g_auto(GStrv) tokens = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* only save this if the data is sufficiently high quality */ if (quality <= priv->license_quality) return; if (license == NULL) return; priv->license_quality = quality; /* assume free software until we find a nonfree SPDX token */ priv->license_is_free = TRUE; tokens = as_utils_spdx_license_tokenize (license); for (i = 0; tokens[i] != NULL; i++) { if (g_strcmp0 (tokens[i], "&") == 0 || g_strcmp0 (tokens[i], "+") == 0 || g_strcmp0 (tokens[i], "|") == 0) continue; if (gs_app_get_license_token_is_nonfree (tokens[i])) { priv->license_is_free = FALSE; break; } } _g_set_str (&priv->license, license); } /** * gs_app_get_summary_missing: * @app: a #GsApp * * Gets the one-line summary to use when this application is missing. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_summary_missing (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->summary_missing; } /** * gs_app_set_summary_missing: * @app: a #GsApp * @summary_missing: a string, or %NULL * * Sets the one-line summary to use when this application is missing. * * Since: 3.22 **/ void gs_app_set_summary_missing (GsApp *app, const gchar *summary_missing) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); _g_set_str (&priv->summary_missing, summary_missing); } /** * gs_app_get_menu_path: * @app: a #GsApp * * Returns the menu path which is an array of path elements. * The resulting array is an internal structure and must not be * modified or freed. * * Returns: a %NULL-terminated array of strings * * Since: 3.22 **/ gchar ** gs_app_get_menu_path (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->menu_path; } /** * gs_app_set_menu_path: * @app: a #GsApp * @menu_path: a %NULL-terminated array of strings * * Sets the new menu path. The menu path is an array of path elements. * This function creates a deep copy of the path. * * Since: 3.22 **/ void gs_app_set_menu_path (GsApp *app, gchar **menu_path) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); _g_set_strv (&priv->menu_path, menu_path); } /** * gs_app_get_origin: * @app: a #GsApp * * Gets the origin for the application, e.g. "fedora". * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_origin (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->origin; } /** * gs_app_set_origin: * @app: a #GsApp * @origin: a string, or %NULL * * The origin is the original source of the application e.g. "fedora-updates" * * Since: 3.22 **/ void gs_app_set_origin (GsApp *app, const gchar *origin) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* same */ if (g_strcmp0 (origin, priv->origin) == 0) return; /* trying to change */ if (priv->origin != NULL && origin != NULL) { g_warning ("automatically prevented from changing " "origin on %s from %s to %s!", gs_app_get_unique_id_unlocked (app), priv->origin, origin); return; } g_free (priv->origin); priv->origin = g_strdup (origin); /* no longer valid */ priv->unique_id_valid = FALSE; } /** * gs_app_get_origin_appstream: * @app: a #GsApp * * Gets the appstream origin for the application, e.g. "fedora". * * Returns: a string, or %NULL for unset * * Since: 3.28 **/ const gchar * gs_app_get_origin_appstream (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->origin_appstream; } /** * gs_app_set_origin_appstream: * @app: a #GsApp * @origin_appstream: a string, or %NULL * * The appstream origin is the appstream source of the application e.g. "fedora" * * Since: 3.28 **/ void gs_app_set_origin_appstream (GsApp *app, const gchar *origin_appstream) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* same */ if (g_strcmp0 (origin_appstream, priv->origin_appstream) == 0) return; g_free (priv->origin_appstream); priv->origin_appstream = g_strdup (origin_appstream); } /** * gs_app_get_origin_hostname: * @app: a #GsApp * * Gets the hostname of the origin used to install the application, e.g. * "fedoraproject.org" or "sdk.gnome.org". * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_origin_hostname (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->origin_hostname; } /** * gs_app_set_origin_hostname: * @app: a #GsApp * @origin_hostname: a string, or %NULL * * The origin is the hostname of the source used to install the application * e.g. "fedoraproject.org" * * You can also use a full URL as @origin_hostname and this will be parsed and * the hostname extracted. This process will also remove any unnecessary DNS * prefixes like "download" or "mirrors". * * Since: 3.22 **/ void gs_app_set_origin_hostname (GsApp *app, const gchar *origin_hostname) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_autoptr(SoupURI) uri = NULL; guint i; const gchar *prefixes[] = { "download.", "mirrors.", NULL }; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* same */ if (g_strcmp0 (origin_hostname, priv->origin_hostname) == 0) return; g_free (priv->origin_hostname); /* use libsoup to convert a URL */ uri = soup_uri_new (origin_hostname); if (uri != NULL) origin_hostname = soup_uri_get_host (uri); /* remove some common prefixes */ for (i = 0; prefixes[i] != NULL; i++) { if (g_str_has_prefix (origin_hostname, prefixes[i])) origin_hostname += strlen (prefixes[i]); } /* fallback for localhost */ if (g_strcmp0 (origin_hostname, "") == 0) origin_hostname = "localhost"; /* success */ priv->origin_hostname = g_strdup (origin_hostname); } /** * gs_app_add_screenshot: * @app: a #GsApp * @screenshot: a #AsScreenshot * * Adds a screenshot to the application. * * Since: 3.22 **/ void gs_app_add_screenshot (GsApp *app, AsScreenshot *screenshot) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (AS_IS_SCREENSHOT (screenshot)); locker = g_mutex_locker_new (&priv->mutex); g_ptr_array_add (priv->screenshots, g_object_ref (screenshot)); } /** * gs_app_get_screenshots: * @app: a #GsApp * * Gets the list of screenshots. * * Returns: (element-type AsScreenshot) (transfer none): a list * * Since: 3.22 **/ GPtrArray * gs_app_get_screenshots (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->screenshots; } /** * gs_app_get_update_version: * @app: a #GsApp * * Gets the newest update version. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_update_version (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->update_version; } /** * gs_app_get_update_version_ui: * @app: a #GsApp * * Gets the update version for the UI. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_update_version_ui (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); /* work out the two version numbers */ if (priv->update_version != NULL && priv->update_version_ui == NULL) { gs_app_ui_versions_populate (app); } return priv->update_version_ui; } static void gs_app_set_update_version_internal (GsApp *app, const gchar *update_version) { GsAppPrivate *priv = gs_app_get_instance_private (app); if (_g_set_str (&priv->update_version, update_version)) gs_app_ui_versions_invalidate (app); } /** * gs_app_set_update_version: * @app: a #GsApp * @update_version: a string, e.g. "0.1.2.3" * * Sets the new version number of the update. * * Since: 3.22 **/ void gs_app_set_update_version (GsApp *app, const gchar *update_version) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); gs_app_set_update_version_internal (app, update_version); gs_app_queue_notify (app, obj_props[PROP_VERSION]); } /** * gs_app_get_update_details: * @app: a #GsApp * * Gets the multi-line description for the update. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_update_details (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->update_details; } /** * gs_app_set_update_details: * @app: a #GsApp * @update_details: a string * * Sets the multi-line description for the update. * * Since: 3.22 **/ void gs_app_set_update_details (GsApp *app, const gchar *update_details) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); _g_set_str (&priv->update_details, update_details); } /** * gs_app_get_update_urgency: * @app: a #GsApp * * Gets the update urgency. * * Returns: a #AsUrgencyKind, or %AS_URGENCY_KIND_UNKNOWN for unset * * Since: 3.22 **/ AsUrgencyKind gs_app_get_update_urgency (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), AS_URGENCY_KIND_UNKNOWN); return priv->update_urgency; } /** * gs_app_set_update_urgency: * @app: a #GsApp * @update_urgency: a #AsUrgencyKind * * Sets the update urgency. * * Since: 3.22 **/ void gs_app_set_update_urgency (GsApp *app, AsUrgencyKind update_urgency) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); if (update_urgency == priv->update_urgency) return; priv->update_urgency = update_urgency; } /** * gs_app_get_management_plugin: * @app: a #GsApp * * Gets the management plugin. * This is some metadata about the application which is used to work out * which plugin should handle the install, remove or upgrade actions. * * Typically plugins will just set this to the plugin name using * gs_plugin_get_name(). * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_management_plugin (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->management_plugin; } /** * gs_app_set_management_plugin: * @app: a #GsApp * @management_plugin: a string, or %NULL, e.g. "fwupd" * * The management plugin is the plugin that can handle doing install and remove * operations on the #GsApp. * Typical values include "packagekit" and "flatpak" * * It is an error to attempt to change the management plugin once it has been * previously set or to try to use this function on a wildcard application. * * Since: 3.22 **/ void gs_app_set_management_plugin (GsApp *app, const gchar *management_plugin) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* plugins cannot adopt wildcard packages */ if (gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD)) { g_warning ("plugins should not set the management plugin on " "%s to %s -- create a new GsApp in refine()!", gs_app_get_unique_id_unlocked (app), management_plugin); return; } /* same */ if (g_strcmp0 (priv->management_plugin, management_plugin) == 0) return; /* trying to change */ if (priv->management_plugin != NULL && management_plugin != NULL) { g_warning ("automatically prevented from changing " "management plugin on %s from %s to %s!", gs_app_get_unique_id_unlocked (app), priv->management_plugin, management_plugin); return; } g_free (priv->management_plugin); priv->management_plugin = g_strdup (management_plugin); } /** * gs_app_get_rating: * @app: a #GsApp * * Gets the percentage rating of the application, where 100 is 5 stars. * * Returns: a percentage, or -1 for unset * * Since: 3.22 **/ gint gs_app_get_rating (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), -1); return priv->rating; } /** * gs_app_set_rating: * @app: a #GsApp * @rating: a percentage, or -1 for invalid * * Gets the percentage rating of the application. * * Since: 3.22 **/ void gs_app_set_rating (GsApp *app, gint rating) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); if (rating == priv->rating) return; priv->rating = rating; gs_app_queue_notify (app, obj_props[PROP_RATING]); } /** * gs_app_get_review_ratings: * @app: a #GsApp * * Gets the review ratings. * * Returns: (element-type guint32) (transfer none): a list * * Since: 3.22 **/ GArray * gs_app_get_review_ratings (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->review_ratings; } /** * gs_app_set_review_ratings: * @app: a #GsApp * @review_ratings: (element-type guint32): a list * * Sets the review ratings. * * Since: 3.22 **/ void gs_app_set_review_ratings (GsApp *app, GArray *review_ratings) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); _g_set_array (&priv->review_ratings, review_ratings); } /** * gs_app_get_reviews: * @app: a #GsApp * * Gets all the user-submitted reviews for the application. * * Returns: (element-type AsReview) (transfer none): the list of reviews * * Since: 3.22 **/ GPtrArray * gs_app_get_reviews (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->reviews; } /** * gs_app_add_review: * @app: a #GsApp * @review: a #AsReview * * Adds a user-submitted review to the application. * * Since: 3.22 **/ void gs_app_add_review (GsApp *app, AsReview *review) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (AS_IS_REVIEW (review)); locker = g_mutex_locker_new (&priv->mutex); g_ptr_array_add (priv->reviews, g_object_ref (review)); } /** * gs_app_remove_review: * @app: a #GsApp * @review: a #AsReview * * Removes a user-submitted review to the application. * * Since: 3.22 **/ void gs_app_remove_review (GsApp *app, AsReview *review) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); g_ptr_array_remove (priv->reviews, review); } /** * gs_app_get_provides: * @app: a #GsApp * * Gets all the provides for the application. * * Returns: (element-type AsProvide) (transfer none): the list of provides * * Since: 3.22 **/ GPtrArray * gs_app_get_provides (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->provides; } /** * gs_app_add_provide: * @app: a #GsApp * @provide: a #AsProvide * * Adds a provide to the application. * * Since: 3.22 **/ void gs_app_add_provide (GsApp *app, AsProvide *provide) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (AS_IS_PROVIDE (provide)); locker = g_mutex_locker_new (&priv->mutex); g_ptr_array_add (priv->provides, g_object_ref (provide)); } /** * gs_app_get_size_download: * @app: A #GsApp * * Gets the size of the total download needed to either install an available * application, or update an already installed one. * * If there is a runtime not yet installed then this is also added. * * Returns: number of bytes, 0 for unknown, or %GS_APP_SIZE_UNKNOWABLE for invalid * * Since: 3.22 **/ guint64 gs_app_get_size_download (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); guint64 sz; g_return_val_if_fail (GS_IS_APP (app), G_MAXUINT64); /* this app */ sz = priv->size_download; /* add the runtime if this is not installed */ if (priv->runtime != NULL) { if (gs_app_get_state (priv->runtime) == AS_APP_STATE_AVAILABLE) sz += gs_app_get_size_installed (priv->runtime); } /* add related apps */ for (guint i = 0; i < gs_app_list_length (priv->related); i++) { GsApp *app_related = gs_app_list_index (priv->related, i); sz += gs_app_get_size_download (app_related); } return sz; } /** * gs_app_set_size_download: * @app: a #GsApp * @size_download: size in bytes, or %GS_APP_SIZE_UNKNOWABLE for invalid * * Sets the download size of the application, not including any * required runtime. * * Since: 3.22 **/ void gs_app_set_size_download (GsApp *app, guint64 size_download) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); if (size_download == priv->size_download) return; priv->size_download = size_download; } /** * gs_app_get_size_installed: * @app: a #GsApp * * Gets the size on disk, either for an existing application of one that could * be installed. * * Returns: size in bytes, 0 for unknown, or %GS_APP_SIZE_UNKNOWABLE for invalid. * * Since: 3.22 **/ guint64 gs_app_get_size_installed (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); guint64 sz; g_return_val_if_fail (GS_IS_APP (app), G_MAXUINT64); /* this app */ sz = priv->size_installed; /* add related apps */ for (guint i = 0; i < gs_app_list_length (priv->related); i++) { GsApp *app_related = gs_app_list_index (priv->related, i); sz += gs_app_get_size_installed (app_related); } return sz; } /** * gs_app_set_size_installed: * @app: a #GsApp * @size_installed: size in bytes, or %GS_APP_SIZE_UNKNOWABLE for invalid * * Sets the installed size of the application. * * Since: 3.22 **/ void gs_app_set_size_installed (GsApp *app, guint64 size_installed) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); if (size_installed == priv->size_installed) return; priv->size_installed = size_installed; } /** * gs_app_get_metadata_item: * @app: a #GsApp * @key: a string, e.g. "fwupd::device-id" * * Gets some metadata for the application. * Is is expected that plugins namespace any plugin-specific metadata, * for example `fwupd::device-id`. * * Returns: a string, or %NULL for unset * * Since: 3.22 **/ const gchar * gs_app_get_metadata_item (GsApp *app, const gchar *key) { GVariant *tmp; g_return_val_if_fail (GS_IS_APP (app), NULL); g_return_val_if_fail (key != NULL, NULL); tmp = gs_app_get_metadata_variant (app, key); if (tmp == NULL) return NULL; return g_variant_get_string (tmp, NULL); } /** * gs_app_set_metadata: * @app: a #GsApp * @key: a string, e.g. "fwupd::DeviceID" * @value: a string, e.g. "fubar" * * Sets some metadata for the application. * Is is expected that plugins namespace any plugin-specific metadata. * * Since: 3.22 **/ void gs_app_set_metadata (GsApp *app, const gchar *key, const gchar *value) { g_autoptr(GVariant) tmp = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (key != NULL); if (value != NULL) tmp = g_variant_new_string (value); gs_app_set_metadata_variant (app, key, tmp); } /** * gs_app_get_metadata_variant: * @app: a #GsApp * @key: a string, e.g. "fwupd::device-id" * * Gets some metadata for the application. * Is is expected that plugins namespace any plugin-specific metadata. * * Returns: a string, or %NULL for unset * * Since: 3.26 **/ GVariant * gs_app_get_metadata_variant (GsApp *app, const gchar *key) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); g_return_val_if_fail (key != NULL, NULL); return g_hash_table_lookup (priv->metadata, key); } /** * gs_app_set_metadata_variant: * @app: a #GsApp * @key: a string, e.g. "fwupd::DeviceID" * @value: a #GVariant * * Sets some metadata for the application. * Is is expected that plugins namespace any plugin-specific metadata, * for example `fwupd::device-id`. * * Since: 3.26 **/ void gs_app_set_metadata_variant (GsApp *app, const gchar *key, GVariant *value) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; GVariant *found; g_return_if_fail (GS_IS_APP (app)); locker = g_mutex_locker_new (&priv->mutex); /* if no value, then remove the key */ if (value == NULL) { g_hash_table_remove (priv->metadata, key); return; } /* check we're not overwriting */ found = g_hash_table_lookup (priv->metadata, key); if (found != NULL) { if (g_variant_equal (found, value)) return; if (g_variant_type_equal (g_variant_get_type (value), G_VARIANT_TYPE_STRING) && g_variant_type_equal (g_variant_get_type (found), G_VARIANT_TYPE_STRING)) { g_debug ("tried overwriting %s key %s from %s to %s", priv->id, key, g_variant_get_string (found, NULL), g_variant_get_string (value, NULL)); } else { g_debug ("tried overwriting %s key %s (%s->%s)", priv->id, key, g_variant_get_type_string (found), g_variant_get_type_string (value)); } return; } g_hash_table_insert (priv->metadata, g_strdup (key), g_variant_ref (value)); } /** * gs_app_get_addons: * @app: a #GsApp * * Gets the list of addons for the application. * * Returns: (transfer none): a list of addons * * Since: 3.22 **/ GsAppList * gs_app_get_addons (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->addons; } /** * gs_app_add_addon: * @app: a #GsApp * @addon: a #GsApp * * Adds an addon to the list of application addons. * * Since: 3.22 **/ void gs_app_add_addon (GsApp *app, GsApp *addon) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (GS_IS_APP (addon)); locker = g_mutex_locker_new (&priv->mutex); gs_app_list_add (priv->addons, addon); } /** * gs_app_remove_addon: * @app: a #GsApp * @addon: a #GsApp * * Removes an addon from the list of application addons. * * Since: 3.22 **/ void gs_app_remove_addon (GsApp *app, GsApp *addon) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (GS_IS_APP (addon)); locker = g_mutex_locker_new (&priv->mutex); gs_app_list_remove (priv->addons, addon); } /** * gs_app_get_related: * @app: a #GsApp * * Gets any related applications. * * Returns: (transfer none): a list of applications * * Since: 3.22 **/ GsAppList * gs_app_get_related (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->related; } /** * gs_app_add_related: * @app: a #GsApp * @app2: a #GsApp * * Adds a related application. * * Since: 3.22 **/ void gs_app_add_related (GsApp *app, GsApp *app2) { GsAppPrivate *priv = gs_app_get_instance_private (app); GsAppPrivate *priv2 = gs_app_get_instance_private (app2); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (GS_IS_APP (app2)); locker = g_mutex_locker_new (&priv->mutex); /* if the app is updatable-live and any related app is not then * degrade to the offline state */ if (priv->state == AS_APP_STATE_UPDATABLE_LIVE && priv2->state == AS_APP_STATE_UPDATABLE) priv->state = priv2->state; gs_app_list_add (priv->related, app2); } /** * gs_app_get_history: * @app: a #GsApp * * Gets the history of this application. * * Returns: (transfer none): a list * * Since: 3.22 **/ GsAppList * gs_app_get_history (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->history; } /** * gs_app_add_history: * @app: a #GsApp * @app2: a #GsApp * * Adds a history item for this package. * * Since: 3.22 **/ void gs_app_add_history (GsApp *app, GsApp *app2) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (GS_IS_APP (app2)); locker = g_mutex_locker_new (&priv->mutex); gs_app_list_add (priv->history, app2); } /** * gs_app_get_install_date: * @app: a #GsApp * * Gets the date that an application was installed. * * Returns: A UNIX epoch, or 0 for unset * * Since: 3.22 **/ guint64 gs_app_get_install_date (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), 0); return priv->install_date; } /** * gs_app_set_install_date: * @app: a #GsApp * @install_date: an epoch, or %GS_APP_INSTALL_DATE_UNKNOWN * * Sets the date that an application was installed. * * Since: 3.22 **/ void gs_app_set_install_date (GsApp *app, guint64 install_date) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); if (install_date == priv->install_date) return; priv->install_date = install_date; } /** * gs_app_is_installed: * @app: a #GsApp * * Gets whether the app is installed or not. * * Returns: %TRUE if the app is installed, %FALSE otherwise. * * Since: 3.22 **/ gboolean gs_app_is_installed (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), FALSE); return (priv->state == AS_APP_STATE_INSTALLED) || (priv->state == AS_APP_STATE_UPDATABLE) || (priv->state == AS_APP_STATE_UPDATABLE_LIVE) || (priv->state == AS_APP_STATE_REMOVING); } /** * gs_app_is_updatable: * @app: a #GsApp * * Gets whether the app is updatable or not. * * Returns: %TRUE if the app is updatable, %FALSE otherwise. * * Since: 3.22 **/ gboolean gs_app_is_updatable (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), FALSE); if (priv->kind == AS_APP_KIND_OS_UPGRADE) return TRUE; return (priv->state == AS_APP_STATE_UPDATABLE) || (priv->state == AS_APP_STATE_UPDATABLE_LIVE); } /** * gs_app_get_categories: * @app: a #GsApp * * Gets the list of categories for an application. * * Returns: (element-type utf8) (transfer none): a list * * Since: 3.22 **/ GPtrArray * gs_app_get_categories (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->categories; } /** * gs_app_has_category: * @app: a #GsApp * @category: a category ID, e.g. "AudioVideo" * * Checks if the application is in a specific category. * * Returns: %TRUE for success * * Since: 3.22 **/ gboolean gs_app_has_category (GsApp *app, const gchar *category) { GsAppPrivate *priv = gs_app_get_instance_private (app); const gchar *tmp; guint i; g_return_val_if_fail (GS_IS_APP (app), FALSE); /* find the category */ for (i = 0; i < priv->categories->len; i++) { tmp = g_ptr_array_index (priv->categories, i); if (g_strcmp0 (tmp, category) == 0) return TRUE; } return FALSE; } /** * gs_app_set_categories: * @app: a #GsApp * @categories: a set of categories * * Set the list of categories for an application. * * Since: 3.22 **/ void gs_app_set_categories (GsApp *app, GPtrArray *categories) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (categories != NULL); locker = g_mutex_locker_new (&priv->mutex); _g_set_ptr_array (&priv->categories, categories); } /** * gs_app_add_category: * @app: a #GsApp * @category: a category ID, e.g. "AudioVideo" * * Adds a category ID to an application. * * Since: 3.22 **/ void gs_app_add_category (GsApp *app, const gchar *category) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (category != NULL); locker = g_mutex_locker_new (&priv->mutex); if (gs_app_has_category (app, category)) return; g_ptr_array_add (priv->categories, g_strdup (category)); } /** * gs_app_remove_category: * @app: a #GsApp * @category: a category ID, e.g. "AudioVideo" * * Removes an category ID from an application, it exists. * * Returns: %TRUE for success * * Since: 3.24 **/ gboolean gs_app_remove_category (GsApp *app, const gchar *category) { GsAppPrivate *priv = gs_app_get_instance_private (app); const gchar *tmp; guint i; g_autoptr(GMutexLocker) locker = NULL; g_return_val_if_fail (GS_IS_APP (app), FALSE); locker = g_mutex_locker_new (&priv->mutex); for (i = 0; i < priv->categories->len; i++) { tmp = g_ptr_array_index (priv->categories, i); if (g_strcmp0 (tmp, category) != 0) continue; g_ptr_array_remove_index_fast (priv->categories, i); return TRUE; } return FALSE; } /** * gs_app_set_is_update_downloaded: * @app: a #GsApp * @is_update_downloaded: Whether a new update is already downloaded locally * * Sets if the new update is already downloaded for the app. * * Since: 3.36 **/ void gs_app_set_is_update_downloaded (GsApp *app, gboolean is_update_downloaded) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); priv->is_update_downloaded = is_update_downloaded; } /** * gs_app_get_is_update_downloaded: * @app: a #GsApp * * Gets if the new update is already downloaded for the app and * is locally available. * * Returns: (element-type gboolean): Whether a new update for the #GsApp is already downloaded. * * Since: 3.36 **/ gboolean gs_app_get_is_update_downloaded (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), FALSE); return priv->is_update_downloaded; } /** * gs_app_get_key_colors: * @app: a #GsApp * * Gets the key colors used in the application icon. * * Returns: (element-type GdkRGBA) (transfer none): a list * * Since: 3.22 **/ GPtrArray * gs_app_get_key_colors (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), NULL); return priv->key_colors; } /** * gs_app_set_key_colors: * @app: a #GsApp * @key_colors: (element-type GdkRGBA): a set of key colors * * Sets the key colors used in the application icon. * * Since: 3.22 **/ void gs_app_set_key_colors (GsApp *app, GPtrArray *key_colors) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (key_colors != NULL); locker = g_mutex_locker_new (&priv->mutex); if (_g_set_ptr_array (&priv->key_colors, key_colors)) gs_app_queue_notify (app, obj_props[PROP_KEY_COLORS]); } /** * gs_app_add_key_color: * @app: a #GsApp * @key_color: a #GdkRGBA * * Adds a key colors used in the application icon. * * Since: 3.22 **/ void gs_app_add_key_color (GsApp *app, GdkRGBA *key_color) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (key_color != NULL); g_ptr_array_add (priv->key_colors, gdk_rgba_copy (key_color)); gs_app_queue_notify (app, obj_props[PROP_KEY_COLORS]); } /** * gs_app_add_kudo: * @app: a #GsApp * @kudo: a #GsAppKudo, e.g. %GS_APP_KUDO_MY_LANGUAGE * * Adds a kudo to the application. * * Since: 3.22 **/ void gs_app_add_kudo (GsApp *app, GsAppKudo kudo) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); if (kudo & GS_APP_KUDO_SANDBOXED_SECURE) kudo |= GS_APP_KUDO_SANDBOXED; priv->kudos |= kudo; } /** * gs_app_remove_kudo: * @app: a #GsApp * @kudo: a #GsAppKudo, e.g. %GS_APP_KUDO_MY_LANGUAGE * * Removes a kudo from the application. * * Since: 3.30 **/ void gs_app_remove_kudo (GsApp *app, GsAppKudo kudo) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); priv->kudos &= ~kudo; } /** * gs_app_has_kudo: * @app: a #GsApp * @kudo: a #GsAppKudo, e.g. %GS_APP_KUDO_MY_LANGUAGE * * Finds out if a kudo has been awarded by the application. * * Returns: %TRUE if the app has the specified kudo * * Since: 3.22 **/ gboolean gs_app_has_kudo (GsApp *app, GsAppKudo kudo) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), FALSE); return (priv->kudos & kudo) > 0; } /** * gs_app_get_kudos: * @app: a #GsApp * * Gets all the kudos the application has been awarded. * * Returns: the kudos, as a bitfield * * Since: 3.22 **/ guint64 gs_app_get_kudos (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), 0); return priv->kudos; } /** * gs_app_get_kudos_percentage: * @app: a #GsApp * * Gets the kudos, as a percentage value. * * Returns: a percentage, with 0 for no kudos and a maximum of 100. * * Since: 3.22 **/ guint gs_app_get_kudos_percentage (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); guint percentage = 0; g_return_val_if_fail (GS_IS_APP (app), 0); if ((priv->kudos & GS_APP_KUDO_MY_LANGUAGE) > 0) percentage += 20; if ((priv->kudos & GS_APP_KUDO_RECENT_RELEASE) > 0) percentage += 20; if ((priv->kudos & GS_APP_KUDO_FEATURED_RECOMMENDED) > 0) percentage += 20; if ((priv->kudos & GS_APP_KUDO_MODERN_TOOLKIT) > 0) percentage += 20; if ((priv->kudos & GS_APP_KUDO_SEARCH_PROVIDER) > 0) percentage += 10; if ((priv->kudos & GS_APP_KUDO_INSTALLS_USER_DOCS) > 0) percentage += 10; if ((priv->kudos & GS_APP_KUDO_USES_NOTIFICATIONS) > 0) percentage += 20; if ((priv->kudos & GS_APP_KUDO_HAS_KEYWORDS) > 0) percentage += 5; if ((priv->kudos & GS_APP_KUDO_HAS_SCREENSHOTS) > 0) percentage += 20; if ((priv->kudos & GS_APP_KUDO_HIGH_CONTRAST) > 0) percentage += 20; if ((priv->kudos & GS_APP_KUDO_HI_DPI_ICON) > 0) percentage += 20; if ((priv->kudos & GS_APP_KUDO_SANDBOXED) > 0) percentage += 20; if ((priv->kudos & GS_APP_KUDO_SANDBOXED_SECURE) > 0) percentage += 20; /* popular apps should be at *least* 50% */ if ((priv->kudos & GS_APP_KUDO_POPULAR) > 0) percentage = MAX (percentage, 50); return MIN (percentage, 100); } /** * gs_app_get_to_be_installed: * @app: a #GsApp * * Gets if the application is queued for installation. * * This is only set for addons when the user has selected some addons to be * installed before installing the main application. * Plugins should check all the addons for this property when installing * main applications so that the chosen set of addons is also installed at the * same time. This is never set when applications do not have addons. * * Returns: %TRUE for success * * Since: 3.22 **/ gboolean gs_app_get_to_be_installed (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), FALSE); return priv->to_be_installed; } /** * gs_app_set_to_be_installed: * @app: a #GsApp * @to_be_installed: if the app is due to be installed * * Sets if the application is queued for installation. * * Since: 3.22 **/ void gs_app_set_to_be_installed (GsApp *app, gboolean to_be_installed) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); priv->to_be_installed = to_be_installed; } /** * gs_app_has_quirk: * @app: a #GsApp * @quirk: a #GsAppQuirk, e.g. %GS_APP_QUIRK_COMPULSORY * * Finds out if an application has a specific quirk. * * Returns: %TRUE for success * * Since: 3.22 **/ gboolean gs_app_has_quirk (GsApp *app, GsAppQuirk quirk) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), FALSE); return (priv->quirk & quirk) > 0; } /** * gs_app_add_quirk: * @app: a #GsApp * @quirk: a #GsAppQuirk, e.g. %GS_APP_QUIRK_COMPULSORY * * Adds a quirk to an application. * * Since: 3.22 **/ void gs_app_add_quirk (GsApp *app, GsAppQuirk quirk) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); /* same */ if ((priv->quirk & quirk) > 0) return; locker = g_mutex_locker_new (&priv->mutex); priv->quirk |= quirk; gs_app_queue_notify (app, obj_props[PROP_QUIRK]); } /** * gs_app_remove_quirk: * @app: a #GsApp * @quirk: a #GsAppQuirk, e.g. %GS_APP_QUIRK_COMPULSORY * * Removes a quirk from an application. * * Since: 3.22 **/ void gs_app_remove_quirk (GsApp *app, GsAppQuirk quirk) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = NULL; g_return_if_fail (GS_IS_APP (app)); /* same */ if ((priv->quirk & quirk) == 0) return; locker = g_mutex_locker_new (&priv->mutex); priv->quirk &= ~quirk; gs_app_queue_notify (app, obj_props[PROP_QUIRK]); } /** * gs_app_set_match_value: * @app: a #GsApp * @match_value: a value * * Set a match quality value, where higher values correspond to a * "better" search match, and should be shown above lower results. * * Since: 3.22 **/ void gs_app_set_match_value (GsApp *app, guint match_value) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); priv->match_value = match_value; } /** * gs_app_get_match_value: * @app: a #GsApp * * Get a match quality value, where higher values correspond to a * "better" search match, and should be shown above lower results. * * Note: This value is only valid when processing the result set * and may be overwritten on subsequent searches if the plugin is using * a cache. * * Returns: a value, where higher is better * * Since: 3.22 **/ guint gs_app_get_match_value (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), 0); return priv->match_value; } /** * gs_app_set_priority: * @app: a #GsApp * @priority: a value * * Set a priority value. * * Since: 3.22 **/ void gs_app_set_priority (GsApp *app, guint priority) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_if_fail (GS_IS_APP (app)); priv->priority = priority; } /** * gs_app_get_priority: * @app: a #GsApp * * Get a priority value, where higher values will be chosen where * multiple #GsApp's match a specific rule. * * Returns: a value, where higher is better * * Since: 3.22 **/ guint gs_app_get_priority (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_return_val_if_fail (GS_IS_APP (app), 0); return priv->priority; } /** * gs_app_get_cancellable: * @app: a #GsApp * * Get a cancellable to be used with operations related to the #GsApp. This is a * way for views to be able to cancel an on-going operation. If the #GCancellable * is canceled, it will be unreferenced and renewed before returning it, i.e. the * cancellable object will always be ready to use for new operations. So be sure * to keep a reference to it if you do more than just passing the cancellable to * a process. * * Returns: a #GCancellable * * Since: 3.28 **/ GCancellable * gs_app_get_cancellable (GsApp *app) { g_autoptr(GCancellable) cancellable = NULL; GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex); if (priv->cancellable == NULL || g_cancellable_is_cancelled (priv->cancellable)) { cancellable = g_cancellable_new (); g_set_object (&priv->cancellable, cancellable); } return priv->cancellable; } /** * gs_app_get_pending_action: * @app: a #GsApp * * Get the pending action for this #GsApp, or %NULL if no action is pending. * * Returns: the #GsAppAction of the @app. **/ GsPluginAction gs_app_get_pending_action (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex); return priv->pending_action; } /** * gs_app_set_pending_action: * @app: a #GsApp * @action: a #GsPluginAction * * Set an action that is pending on this #GsApp. **/ void gs_app_set_pending_action (GsApp *app, GsPluginAction action) { GsAppPrivate *priv = gs_app_get_instance_private (app); g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex); gs_app_set_pending_action_internal (app, action); } static void gs_app_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GsApp *app = GS_APP (object); GsAppPrivate *priv = gs_app_get_instance_private (app); switch (prop_id) { case PROP_ID: g_value_set_string (value, priv->id); break; case PROP_NAME: g_value_set_string (value, priv->name); break; case PROP_VERSION: g_value_set_string (value, priv->version); break; case PROP_SUMMARY: g_value_set_string (value, priv->summary); break; case PROP_DESCRIPTION: g_value_set_string (value, priv->description); break; case PROP_RATING: g_value_set_int (value, priv->rating); break; case PROP_KIND: g_value_set_uint (value, priv->kind); break; case PROP_STATE: g_value_set_uint (value, priv->state); break; case PROP_PROGRESS: g_value_set_uint (value, priv->progress); break; case PROP_CAN_CANCEL_INSTALLATION: g_value_set_boolean (value, priv->allow_cancel); break; case PROP_INSTALL_DATE: g_value_set_uint64 (value, priv->install_date); break; case PROP_QUIRK: g_value_set_uint64 (value, priv->quirk); break; case PROP_KEY_COLORS: g_value_set_boxed (value, priv->key_colors); break; case PROP_IS_UPDATE_DOWNLOADED: g_value_set_boolean (value, priv->is_update_downloaded); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gs_app_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GsApp *app = GS_APP (object); GsAppPrivate *priv = gs_app_get_instance_private (app); switch (prop_id) { case PROP_ID: gs_app_set_id (app, g_value_get_string (value)); break; case PROP_NAME: gs_app_set_name (app, GS_APP_QUALITY_UNKNOWN, g_value_get_string (value)); break; case PROP_VERSION: gs_app_set_version (app, g_value_get_string (value)); break; case PROP_SUMMARY: gs_app_set_summary (app, GS_APP_QUALITY_UNKNOWN, g_value_get_string (value)); break; case PROP_DESCRIPTION: gs_app_set_description (app, GS_APP_QUALITY_UNKNOWN, g_value_get_string (value)); break; case PROP_RATING: gs_app_set_rating (app, g_value_get_int (value)); break; case PROP_KIND: gs_app_set_kind (app, g_value_get_uint (value)); break; case PROP_STATE: gs_app_set_state_internal (app, g_value_get_uint (value)); break; case PROP_PROGRESS: gs_app_set_progress (app, g_value_get_uint (value)); break; case PROP_CAN_CANCEL_INSTALLATION: priv->allow_cancel = g_value_get_boolean (value); break; case PROP_INSTALL_DATE: gs_app_set_install_date (app, g_value_get_uint64 (value)); break; case PROP_QUIRK: priv->quirk = g_value_get_uint64 (value); break; case PROP_KEY_COLORS: gs_app_set_key_colors (app, g_value_get_boxed (value)); break; case PROP_IS_UPDATE_DOWNLOADED: gs_app_set_is_update_downloaded (app, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gs_app_dispose (GObject *object) { GsApp *app = GS_APP (object); GsAppPrivate *priv = gs_app_get_instance_private (app); g_clear_object (&priv->runtime); g_clear_pointer (&priv->addons, g_object_unref); g_clear_pointer (&priv->history, g_object_unref); g_clear_pointer (&priv->related, g_object_unref); g_clear_pointer (&priv->screenshots, g_ptr_array_unref); g_clear_pointer (&priv->review_ratings, g_array_unref); g_clear_pointer (&priv->reviews, g_ptr_array_unref); g_clear_pointer (&priv->provides, g_ptr_array_unref); g_clear_pointer (&priv->icons, g_ptr_array_unref); G_OBJECT_CLASS (gs_app_parent_class)->dispose (object); } static void gs_app_finalize (GObject *object) { GsApp *app = GS_APP (object); GsAppPrivate *priv = gs_app_get_instance_private (app); g_mutex_clear (&priv->mutex); g_free (priv->id); g_free (priv->unique_id); g_free (priv->branch); g_free (priv->name); g_hash_table_unref (priv->urls); g_hash_table_unref (priv->launchables); g_free (priv->license); g_strfreev (priv->menu_path); g_free (priv->origin); g_free (priv->origin_appstream); g_free (priv->origin_hostname); g_ptr_array_unref (priv->sources); g_ptr_array_unref (priv->source_ids); g_free (priv->project_group); g_free (priv->developer_name); g_free (priv->agreement); g_free (priv->version); g_free (priv->version_ui); g_free (priv->summary); g_free (priv->summary_missing); g_free (priv->description); g_free (priv->update_version); g_free (priv->update_version_ui); g_free (priv->update_details); g_free (priv->management_plugin); g_hash_table_unref (priv->metadata); g_ptr_array_unref (priv->categories); g_ptr_array_unref (priv->key_colors); g_clear_object (&priv->cancellable); if (priv->local_file != NULL) g_object_unref (priv->local_file); if (priv->content_rating != NULL) g_object_unref (priv->content_rating); if (priv->pixbuf != NULL) g_object_unref (priv->pixbuf); if (priv->action_screenshot != NULL) g_object_unref (priv->action_screenshot); G_OBJECT_CLASS (gs_app_parent_class)->finalize (object); } static void gs_app_class_init (GsAppClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = gs_app_dispose; object_class->finalize = gs_app_finalize; object_class->get_property = gs_app_get_property; object_class->set_property = gs_app_set_property; /** * GsApp:id: */ obj_props[PROP_ID] = g_param_spec_string ("id", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:name: */ obj_props[PROP_NAME] = g_param_spec_string ("name", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:version: */ obj_props[PROP_VERSION] = g_param_spec_string ("version", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:summary: */ obj_props[PROP_SUMMARY] = g_param_spec_string ("summary", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:description: */ obj_props[PROP_DESCRIPTION] = g_param_spec_string ("description", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:rating: */ obj_props[PROP_RATING] = g_param_spec_int ("rating", NULL, NULL, -1, 100, -1, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:kind: */ obj_props[PROP_KIND] = g_param_spec_uint ("kind", NULL, NULL, AS_APP_KIND_UNKNOWN, AS_APP_KIND_LAST, AS_APP_KIND_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:state: */ obj_props[PROP_STATE] = g_param_spec_uint ("state", NULL, NULL, AS_APP_STATE_UNKNOWN, AS_APP_STATE_LAST, AS_APP_STATE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:progress: * * A percentage (0–100, inclusive) indicating the progress through the * current task on this app. The value may otherwise be * %GS_APP_PROGRESS_UNKNOWN if the progress is unknown or has a wide * confidence interval. */ obj_props[PROP_PROGRESS] = g_param_spec_uint ("progress", NULL, NULL, 0, GS_APP_PROGRESS_UNKNOWN, GS_APP_PROGRESS_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:allow-cancel: */ obj_props[PROP_CAN_CANCEL_INSTALLATION] = g_param_spec_boolean ("allow-cancel", NULL, NULL, TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:install-date: */ obj_props[PROP_INSTALL_DATE] = g_param_spec_uint64 ("install-date", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:quirk: */ obj_props[PROP_QUIRK] = g_param_spec_uint64 ("quirk", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); /** * GsApp:pending-action: */ obj_props[PROP_PENDING_ACTION] = g_param_spec_uint64 ("pending-action", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READABLE); /** * GsApp:key-colors: */ obj_props[PROP_KEY_COLORS] = g_param_spec_boxed ("key-colors", NULL, NULL, G_TYPE_PTR_ARRAY, G_PARAM_READWRITE); /** * GsApp:is-update-downloaded: */ obj_props[PROP_IS_UPDATE_DOWNLOADED] = g_param_spec_boolean ("is-update-downloaded", NULL, NULL, FALSE, G_PARAM_READWRITE); g_object_class_install_properties (object_class, PROP_LAST, obj_props); } static void gs_app_init (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); priv->rating = -1; priv->sources = g_ptr_array_new_with_free_func (g_free); priv->source_ids = g_ptr_array_new_with_free_func (g_free); priv->categories = g_ptr_array_new_with_free_func (g_free); priv->key_colors = g_ptr_array_new_with_free_func ((GDestroyNotify) gdk_rgba_free); priv->addons = gs_app_list_new (); priv->related = gs_app_list_new (); priv->history = gs_app_list_new (); priv->screenshots = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->reviews = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->provides = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->icons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); priv->urls = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); priv->launchables = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); priv->allow_cancel = TRUE; g_mutex_init (&priv->mutex); } /** * gs_app_new: * @id: an application ID, or %NULL, e.g. "org.gnome.Software.desktop" * * Creates a new application object. * * The ID should only be set when the application ID (with optional prefix) is * known; it is perfectly valid to use gs_app_new() with an @id of %NULL, and * then relying on another plugin to set the @id using gs_app_set_id() based on * some other information. * * For instance, a #GsApp is created with no ID when returning results from the * packagekit plugin, but with the default source name set as the package name. * The source name is read by the appstream plugin, and if matched in the * AppStream XML the correct ID is set, along with other higher quality data * like the application icon and long description. * * Returns: a new #GsApp * * Since: 3.22 **/ GsApp * gs_app_new (const gchar *id) { GsApp *app; app = g_object_new (GS_TYPE_APP, "id", id, NULL); return GS_APP (app); } /** * gs_app_set_from_unique_id: * @app: a #GsApp * @unique_id: an application unique ID, e.g. * `system/flatpak/gnome/desktop/org.gnome.Software.desktop/master` * * Sets details on an application object. * * The unique ID will be parsed to set some information in the application such * as the scope, bundle kind, id, etc. * * Since: 3.26 **/ void gs_app_set_from_unique_id (GsApp *app, const gchar *unique_id) { g_auto(GStrv) split = NULL; g_return_if_fail (GS_IS_APP (app)); g_return_if_fail (unique_id != NULL); split = g_strsplit (unique_id, "/", -1); if (g_strv_length (split) != 6) return; if (g_strcmp0 (split[0], "*") != 0) gs_app_set_scope (app, as_app_scope_from_string (split[0])); if (g_strcmp0 (split[1], "*") != 0) gs_app_set_bundle_kind (app, as_bundle_kind_from_string (split[1])); if (g_strcmp0 (split[2], "*") != 0) gs_app_set_origin (app, split[2]); if (g_strcmp0 (split[3], "*") != 0) gs_app_set_kind (app, as_app_kind_from_string (split[3])); if (g_strcmp0 (split[4], "*") != 0) gs_app_set_id (app, split[4]); if (g_strcmp0 (split[5], "*") != 0) gs_app_set_branch (app, split[5]); } /** * gs_app_new_from_unique_id: * @unique_id: an application unique ID, e.g. * `system/flatpak/gnome/desktop/org.gnome.Software.desktop/master` * * Creates a new application object. * * The unique ID will be parsed to set some information in the application such * as the scope, bundle kind, id, etc. Unlike gs_app_new(), it cannot take a * %NULL argument. * * Returns: a new #GsApp * * Since: 3.22 **/ GsApp * gs_app_new_from_unique_id (const gchar *unique_id) { GsApp *app; g_return_val_if_fail (unique_id != NULL, NULL); app = gs_app_new (NULL); gs_app_set_from_unique_id (app, unique_id); return app; } /** * gs_app_get_origin_ui: * @app: a #GsApp * * Gets the package origin that's suitable for UI use. * * Returns: The package origin for UI use * * Since: 3.32 **/ gchar * gs_app_get_origin_ui (GsApp *app) { /* use the distro name for official packages */ if (gs_app_has_quirk (app, GS_APP_QUIRK_PROVENANCE)) { g_autoptr(GsOsRelease) os_release = gs_os_release_new (NULL); if (os_release != NULL) return g_strdup (gs_os_release_get_name (os_release)); } /* use "Local file" rather than the filename for local files */ if (gs_app_get_state (app) == AS_APP_STATE_AVAILABLE_LOCAL) { /* TRANSLATORS: this is a locally downloaded package */ return g_strdup (_("Local file")); } /* capitalize "Flathub" and "Flathub Beta" */ if (g_strcmp0 (gs_app_get_origin (app), "flathub") == 0) { return g_strdup ("Flathub"); } else if (g_strcmp0 (gs_app_get_origin (app), "flathub-beta") == 0) { return g_strdup ("Flathub Beta"); } /* fall back to origin */ return g_strdup (gs_app_get_origin (app)); } /** * gs_app_get_packaging_format: * @app: a #GsApp * * Gets the packaging format, e.g. 'RPM' or 'Flatpak'. * * Returns: The packaging format * * Since: 3.32 **/ gchar * gs_app_get_packaging_format (GsApp *app) { AsBundleKind bundle_kind; const gchar *bundle_kind_ui; const gchar *packaging_format; /* does the app have packaging format set? */ packaging_format = gs_app_get_metadata_item (app, "GnomeSoftware::PackagingFormat"); if (packaging_format != NULL) return g_strdup (packaging_format); /* fall back to bundle kind */ bundle_kind = gs_app_get_bundle_kind (app); switch (bundle_kind) { case AS_BUNDLE_KIND_UNKNOWN: bundle_kind_ui = NULL; break; case AS_BUNDLE_KIND_LIMBA: bundle_kind_ui = "Limba"; break; case AS_BUNDLE_KIND_FLATPAK: bundle_kind_ui = "Flatpak"; break; case AS_BUNDLE_KIND_SNAP: bundle_kind_ui = "Snap"; break; case AS_BUNDLE_KIND_PACKAGE: bundle_kind_ui = _("Package"); break; case AS_BUNDLE_KIND_CABINET: bundle_kind_ui = "Cabinet"; break; case AS_BUNDLE_KIND_APPIMAGE: bundle_kind_ui = "AppImage"; break; default: g_warning ("unhandled bundle kind %s", as_bundle_kind_to_string (bundle_kind)); bundle_kind_ui = as_bundle_kind_to_string (bundle_kind); } return g_strdup (bundle_kind_ui); } /** * gs_app_subsume_metadata: * @app: a #GsApp * @donor: another #GsApp * * Copies any metadata from @donor to @app. * * Since: 3.32 **/ void gs_app_subsume_metadata (GsApp *app, GsApp *donor) { GsAppPrivate *priv = gs_app_get_instance_private (donor); g_autoptr(GList) keys = g_hash_table_get_keys (priv->metadata); for (GList *l = keys; l != NULL; l = l->next) { const gchar *key = l->data; GVariant *tmp = gs_app_get_metadata_variant (donor, key); if (gs_app_get_metadata_variant (app, key) != NULL) continue; gs_app_set_metadata_variant (app, key, tmp); } } GsAppPermissions gs_app_get_permissions (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); return priv->permissions; } void gs_app_set_permissions (GsApp *app, GsAppPermissions permissions) { GsAppPrivate *priv = gs_app_get_instance_private (app); priv->permissions = permissions; } GsAppPermissions gs_app_get_update_permissions (GsApp *app) { GsAppPrivate *priv = gs_app_get_instance_private (app); return priv->update_permissions; } void gs_app_set_update_permissions (GsApp *app, GsAppPermissions update_permissions) { GsAppPrivate *priv = gs_app_get_instance_private (app); priv->update_permissions = update_permissions; }