/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2012-2024 Matthias Klumpp * * Licensed under the GNU Lesser General Public License Version 2.1 * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the license, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ #include "as-component.h" #include "as-component-private.h" #include #include #include "as-utils.h" #include "as-utils-private.h" #include "as-stemmer.h" #include "as-spdx.h" #include "as-context-private.h" #include "as-icon-private.h" #include "as-screenshot-private.h" #include "as-bundle-private.h" #include "as-release-list-private.h" #include "as-translation-private.h" #include "as-suggested-private.h" #include "as-content-rating-private.h" #include "as-launchable-private.h" #include "as-provided-private.h" #include "as-relation-private.h" #include "as-agreement-private.h" #include "as-review-private.h" #include "as-branding-private.h" #include "as-developer-private.h" #include "as-reference-private.h" #include "as-desktop-entry.h" #include "as-relation-check-result.h" /** * SECTION:as-component * @short_description: Object representing a software component * @include: appstream.h * * This object represents an Appstream software component which is associated * to a package in the distribution's repositories. * A component can be anything, ranging from an application to a font, a codec or * even a non-visual software project providing libraries and python-modules for * other applications to use. * * The type of the component is stored as #AsComponentKind and can be queried to * find out which kind of component we're dealing with. * * See also: #AsProvidesKind, #AsDatabase */ typedef struct { AsComponentKind kind; AsComponentScope scope; AsOriginKind origin_kind; AsContext *context; /* the document context associated with this component */ gchar *date_eol; gchar *id; gchar *data_id; gchar **pkgnames; gchar *source_pkgname; GRefString *origin; GRefString *branch; GHashTable *name; /* localized entry */ GHashTable *summary; /* localized entry */ GHashTable *description; /* localized entry */ GHashTable *keywords; /* localized entry, value:strv */ GRefString *metadata_license; GRefString *project_license; GRefString *project_group; AsReleaseList *releases; GPtrArray *launchables; /* of #AsLaunchable */ GPtrArray *categories; /* of utf8 */ GPtrArray *compulsory_for_desktops; /* of utf8 */ GPtrArray *extends; /* of utf8 */ GPtrArray *addons; /* of AsComponent */ GPtrArray *screenshots; /* of AsScreenshot elements */ GPtrArray *provided; /* of AsProvided */ GPtrArray *bundles; /* of AsBundle */ GPtrArray *suggestions; /* of AsSuggested elements */ GPtrArray *content_ratings; /* of AsContentRating */ GPtrArray *requires; /* of AsRelation */ GPtrArray *recommends; /* of AsRelation */ GPtrArray *supports; /* of AsRelation */ GPtrArray *replaces; /* of utf8 */ GPtrArray *agreements; /* of AsAgreement */ GHashTable *urls; /* of int:utf8 */ GHashTable *languages; /* of utf8:utf8 */ GPtrArray *translations; /* of AsTranslation */ GPtrArray *icons; /* of AsIcon elements */ GPtrArray *reviews; /* of AsReview */ AsDeveloper *developer; AsBranding *branding; GRefString *arch; /* the architecture this data was generated from */ gint priority; /* used internally */ AsMergeKind merge_kind; /* whether and how the component data should be merged */ guint sort_score; /* used to priorize components in listings */ gsize token_cache_valid; GHashTable *token_cache; /* of RefString:AsTokenType* */ gboolean ignored; /* whether we should ignore this component */ GPtrArray *tags; GPtrArray *references; GHashTable *name_variant_suffix; /* variant suffix for component name */ GHashTable *custom; /* of RefString:RefString, free-form user-defined custom data */ } AsComponentPrivate; G_DEFINE_TYPE_WITH_PRIVATE (AsComponent, as_component, G_TYPE_OBJECT) #define GET_PRIVATE(o) (as_component_get_instance_private (o)) enum { AS_COMPONENT_DUMMY_PROPERTY, AS_COMPONENT_KIND, AS_COMPONENT_PKGNAMES, AS_COMPONENT_ID, AS_COMPONENT_NAME, AS_COMPONENT_SUMMARY, AS_COMPONENT_DESCRIPTION, AS_COMPONENT_KEYWORDS, AS_COMPONENT_ICONS, AS_COMPONENT_URLS, AS_COMPONENT_CATEGORIES, AS_COMPONENT_PROJECT_LICENSE, AS_COMPONENT_PROJECT_GROUP, AS_COMPONENT_SCREENSHOTS }; /** * as_component_kind_to_string: * @kind: the #AsComponentKind. * * Converts the enumerated value to an text representation. * * Returns: string version of @kind **/ const gchar * as_component_kind_to_string (AsComponentKind kind) { if (kind == AS_COMPONENT_KIND_GENERIC) return "generic"; if (kind == AS_COMPONENT_KIND_DESKTOP_APP) return "desktop-application"; if (kind == AS_COMPONENT_KIND_CONSOLE_APP) return "console-application"; if (kind == AS_COMPONENT_KIND_WEB_APP) return "web-application"; if (kind == AS_COMPONENT_KIND_ADDON) return "addon"; if (kind == AS_COMPONENT_KIND_FONT) return "font"; if (kind == AS_COMPONENT_KIND_CODEC) return "codec"; if (kind == AS_COMPONENT_KIND_INPUT_METHOD) return "inputmethod"; if (kind == AS_COMPONENT_KIND_FIRMWARE) return "firmware"; if (kind == AS_COMPONENT_KIND_DRIVER) return "driver"; if (kind == AS_COMPONENT_KIND_LOCALIZATION) return "localization"; if (kind == AS_COMPONENT_KIND_SERVICE) return "service"; if (kind == AS_COMPONENT_KIND_REPOSITORY) return "repository"; if (kind == AS_COMPONENT_KIND_OPERATING_SYSTEM) return "operating-system"; if (kind == AS_COMPONENT_KIND_ICON_THEME) return "icon-theme"; if (kind == AS_COMPONENT_KIND_RUNTIME) return "runtime"; return "unknown"; } /** * as_component_kind_from_string: * @kind_str: the string. * * Converts the text representation to an enumerated value. * * Returns: a #AsComponentKind or %AS_COMPONENT_KIND_UNKNOWN for unknown **/ AsComponentKind as_component_kind_from_string (const gchar *kind_str) { if (kind_str == NULL) return AS_COMPONENT_KIND_GENERIC; if (g_strcmp0 (kind_str, "generic") == 0) return AS_COMPONENT_KIND_GENERIC; if (g_strcmp0 (kind_str, "desktop-application") == 0) return AS_COMPONENT_KIND_DESKTOP_APP; if (g_strcmp0 (kind_str, "console-application") == 0) return AS_COMPONENT_KIND_CONSOLE_APP; if (g_strcmp0 (kind_str, "web-application") == 0) return AS_COMPONENT_KIND_WEB_APP; if (g_strcmp0 (kind_str, "addon") == 0) return AS_COMPONENT_KIND_ADDON; if (g_strcmp0 (kind_str, "font") == 0) return AS_COMPONENT_KIND_FONT; if (g_strcmp0 (kind_str, "codec") == 0) return AS_COMPONENT_KIND_CODEC; if (g_strcmp0 (kind_str, "inputmethod") == 0) return AS_COMPONENT_KIND_INPUT_METHOD; if (g_strcmp0 (kind_str, "firmware") == 0) return AS_COMPONENT_KIND_FIRMWARE; if (g_strcmp0 (kind_str, "driver") == 0) return AS_COMPONENT_KIND_DRIVER; if (g_strcmp0 (kind_str, "localization") == 0) return AS_COMPONENT_KIND_LOCALIZATION; if (g_strcmp0 (kind_str, "service") == 0) return AS_COMPONENT_KIND_SERVICE; if (g_strcmp0 (kind_str, "repository") == 0) return AS_COMPONENT_KIND_REPOSITORY; if (g_strcmp0 (kind_str, "operating-system") == 0) return AS_COMPONENT_KIND_OPERATING_SYSTEM; if (g_strcmp0 (kind_str, "icon-theme") == 0) return AS_COMPONENT_KIND_ICON_THEME; if (g_strcmp0 (kind_str, "runtime") == 0) return AS_COMPONENT_KIND_RUNTIME; /* legacy compatibility */ if (g_strcmp0 (kind_str, "desktop") == 0) return AS_COMPONENT_KIND_DESKTOP_APP; return AS_COMPONENT_KIND_UNKNOWN; } /** * as_merge_kind_to_string: * @kind: the #AsMergeKind. * * Converts the enumerated value to an text representation. * * Returns: string version of @kind **/ const gchar * as_merge_kind_to_string (AsMergeKind kind) { if (kind == AS_MERGE_KIND_NONE) return "none"; if (kind == AS_MERGE_KIND_REPLACE) return "replace"; if (kind == AS_MERGE_KIND_APPEND) return "append"; if (kind == AS_MERGE_KIND_REMOVE_COMPONENT) return "remove-component"; return "unknown"; } /** * as_merge_kind_from_string: * @kind_str: the string. * * Converts the text representation to an enumerated value. * * Returns: a #AsMergeKind or %AS_MERGE_KIND_NONE for unknown **/ AsMergeKind as_merge_kind_from_string (const gchar *kind_str) { if (g_strcmp0 (kind_str, "replace") == 0) return AS_MERGE_KIND_REPLACE; if (g_strcmp0 (kind_str, "append") == 0) return AS_MERGE_KIND_APPEND; if (g_strcmp0 (kind_str, "remove-component") == 0) return AS_MERGE_KIND_REMOVE_COMPONENT; return AS_MERGE_KIND_NONE; } /** * as_component_scope_to_string: * @scope: the #AsComponentScope. * * Converts the enumerated value to an text representation. * * Returns: string version of @scope **/ const gchar * as_component_scope_to_string (AsComponentScope scope) { if (scope == AS_COMPONENT_SCOPE_SYSTEM) return "system"; if (scope == AS_COMPONENT_SCOPE_USER) return "user"; return "unknown"; } /** * as_component_scope_from_string: * @scope_str: the string. * * Converts the text representation to an enumerated value. * * Returns: a #AsComponentScope or %AS_COMPONENT_SCOPE_UNKNOWN for unknown **/ AsComponentScope as_component_scope_from_string (const gchar *scope_str) { if (g_strcmp0 (scope_str, "system") == 0) return AS_COMPONENT_SCOPE_SYSTEM; if (g_strcmp0 (scope_str, "user") == 0) return AS_COMPONENT_SCOPE_USER; return AS_COMPONENT_SCOPE_UNKNOWN; } /** * as_url_kind_to_string: * @url_kind: the %AsUrlKind. * * Converts the enumerated value to an text representation. * * Returns: string version of @url_kind **/ const gchar * as_url_kind_to_string (AsUrlKind url_kind) { if (url_kind == AS_URL_KIND_HOMEPAGE) return "homepage"; if (url_kind == AS_URL_KIND_BUGTRACKER) return "bugtracker"; if (url_kind == AS_URL_KIND_FAQ) return "faq"; if (url_kind == AS_URL_KIND_HELP) return "help"; if (url_kind == AS_URL_KIND_DONATION) return "donation"; if (url_kind == AS_URL_KIND_TRANSLATE) return "translate"; if (url_kind == AS_URL_KIND_CONTACT) return "contact"; if (url_kind == AS_URL_KIND_VCS_BROWSER) return "vcs-browser"; if (url_kind == AS_URL_KIND_CONTRIBUTE) return "contribute"; return "unknown"; } /** * as_url_kind_from_string: * @url_kind: the string. * * Converts the text representation to an enumerated value. * * Returns: a #AsUrlKind or %AS_URL_KIND_UNKNOWN for unknown **/ AsUrlKind as_url_kind_from_string (const gchar *url_kind) { if (g_strcmp0 (url_kind, "homepage") == 0) return AS_URL_KIND_HOMEPAGE; if (g_strcmp0 (url_kind, "bugtracker") == 0) return AS_URL_KIND_BUGTRACKER; if (g_strcmp0 (url_kind, "faq") == 0) return AS_URL_KIND_FAQ; if (g_strcmp0 (url_kind, "help") == 0) return AS_URL_KIND_HELP; if (g_strcmp0 (url_kind, "donation") == 0) return AS_URL_KIND_DONATION; if (g_strcmp0 (url_kind, "translate") == 0) return AS_URL_KIND_TRANSLATE; if (g_strcmp0 (url_kind, "contact") == 0) return AS_URL_KIND_CONTACT; if (g_strcmp0 (url_kind, "vcs-browser") == 0) return AS_URL_KIND_VCS_BROWSER; if (g_strcmp0 (url_kind, "contribute") == 0) return AS_URL_KIND_CONTRIBUTE; return AS_URL_KIND_UNKNOWN; } /** * as_component_init: **/ static void as_component_init (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); /* translatable entities */ priv->name = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) as_ref_string_release, g_free); priv->summary = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) as_ref_string_release, g_free); priv->description = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) as_ref_string_release, g_free); priv->keywords = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) as_ref_string_release, (GDestroyNotify) g_ptr_array_unref); /* lists */ priv->releases = as_release_list_new (); priv->launchables = g_ptr_array_new_with_free_func (g_object_unref); priv->categories = g_ptr_array_new_with_free_func (g_free); priv->compulsory_for_desktops = g_ptr_array_new_with_free_func (g_free); priv->screenshots = g_ptr_array_new_with_free_func (g_object_unref); priv->provided = g_ptr_array_new_with_free_func (g_object_unref); priv->bundles = g_ptr_array_new_with_free_func (g_object_unref); priv->extends = g_ptr_array_new_with_free_func (g_free); priv->addons = g_ptr_array_new_with_free_func (g_object_unref); priv->suggestions = g_ptr_array_new_with_free_func (g_object_unref); priv->icons = g_ptr_array_new_with_free_func (g_object_unref); priv->content_ratings = g_ptr_array_new_with_free_func (g_object_unref); priv->requires = g_ptr_array_new_with_free_func (g_object_unref); priv->recommends = g_ptr_array_new_with_free_func (g_object_unref); priv->supports = g_ptr_array_new_with_free_func (g_object_unref); priv->agreements = g_ptr_array_new_with_free_func (g_object_unref); priv->reviews = g_ptr_array_new_with_free_func (g_object_unref); /* others */ priv->tags = g_ptr_array_new_with_free_func (g_free); priv->references = g_ptr_array_new_with_free_func (g_object_unref); priv->urls = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); priv->languages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); priv->custom = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_ref_string_release, (GDestroyNotify) g_ref_string_release); priv->token_cache = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_ref_string_release, g_free); priv->priority = 0; } /** * as_component_finalize: */ static void as_component_finalize (GObject *object) { AsComponent *cpt = AS_COMPONENT (object); AsComponentPrivate *priv = GET_PRIVATE (cpt); g_free (priv->id); g_free (priv->data_id); g_free (priv->date_eol); g_free (priv->source_pkgname); g_strfreev (priv->pkgnames); as_ref_string_release (priv->metadata_license); as_ref_string_release (priv->project_license); as_ref_string_release (priv->project_group); as_ref_string_release (priv->arch); as_ref_string_release (priv->origin); as_ref_string_release (priv->branch); g_hash_table_unref (priv->name); g_hash_table_unref (priv->summary); g_hash_table_unref (priv->description); g_hash_table_unref (priv->keywords); g_object_unref (priv->releases); g_ptr_array_unref (priv->launchables); g_ptr_array_unref (priv->categories); g_ptr_array_unref (priv->compulsory_for_desktops); g_ptr_array_unref (priv->screenshots); g_ptr_array_unref (priv->provided); g_ptr_array_unref (priv->bundles); g_ptr_array_unref (priv->extends); g_ptr_array_unref (priv->addons); g_ptr_array_unref (priv->suggestions); g_hash_table_unref (priv->urls); g_hash_table_unref (priv->languages); g_hash_table_unref (priv->custom); g_ptr_array_unref (priv->content_ratings); g_ptr_array_unref (priv->icons); g_ptr_array_unref (priv->tags); g_ptr_array_unref (priv->references); g_ptr_array_unref (priv->requires); g_ptr_array_unref (priv->recommends); g_ptr_array_unref (priv->supports); g_ptr_array_unref (priv->agreements); g_ptr_array_unref (priv->reviews); if (priv->replaces != NULL) g_ptr_array_unref (priv->replaces); if (priv->developer != NULL) g_object_unref (priv->developer); if (priv->branding != NULL) g_object_unref (priv->branding); if (priv->translations != NULL) g_ptr_array_unref (priv->translations); if (priv->name_variant_suffix != NULL) g_hash_table_unref (priv->name_variant_suffix); g_hash_table_unref (priv->token_cache); if (priv->context != NULL) g_object_unref (priv->context); G_OBJECT_CLASS (as_component_parent_class)->finalize (object); } /** * as_component_invalidate_data_id: * * Internal method to mark the metadata-ID as outdated, so * it will be regenerated next time. */ static void as_component_invalidate_data_id (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->data_id == NULL) return; g_free (g_steal_pointer (&priv->data_id)); } /** * as_component_is_valid: * @cpt: a #AsComponent instance. * * Check if the essential properties of this Component are * populated with useful data. * * Returns: TRUE if the component data was validated successfully. */ gboolean as_component_is_valid (AsComponent *cpt) { const gchar *cname; const gchar *csummary; AsComponentKind ctype; AsComponentPrivate *priv = GET_PRIVATE (cpt); ctype = priv->kind; if (ctype == AS_COMPONENT_KIND_UNKNOWN) return FALSE; if (priv->merge_kind != AS_MERGE_KIND_NONE) { /* merge components only need an ID to be valid */ return !as_is_empty (priv->id); } cname = as_component_get_name (cpt); csummary = as_component_get_summary (cpt); if ((!as_is_empty (priv->id)) && (!as_is_empty (cname)) && (!as_is_empty (csummary))) { return TRUE; } return FALSE; } /** * as_component_to_string: * @cpt: a #AsComponent instance. * * Returns a string identifying this component. * (useful for debugging) * * Returns: (transfer full): A descriptive string **/ gchar * as_component_to_string (AsComponent *cpt) { gchar *res = NULL; g_autofree gchar *pkgs = NULL; AsComponentPrivate *priv = GET_PRIVATE (cpt); if (as_component_has_package (cpt)) pkgs = g_strjoinv (",", priv->pkgnames); else pkgs = g_strdup (""); res = g_strdup_printf ("[%s::%s]> name: %s | summary: %s | package: %s", as_component_kind_to_string (priv->kind), as_component_get_data_id (cpt), as_component_get_name (cpt), as_component_get_summary (cpt), pkgs); return res; } /** * as_component_add_screenshot: * @cpt: a #AsComponent instance. * @sshot: The #AsScreenshot to add * * Add an #AsScreenshot to this component. **/ void as_component_add_screenshot (AsComponent *cpt, AsScreenshot *sshot) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_return_if_fail (sshot != NULL); g_ptr_array_add (priv->screenshots, g_object_ref (sshot)); } /** * as_component_get_releases_plain: * @cpt: a #AsComponent instance. * * Get release information for this component, * without downloading or loading any data from external sources. * * Returns: (transfer none): Release information as #AsReleaseList **/ AsReleaseList * as_component_get_releases_plain (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->releases; } /** * as_component_load_releases: * @cpt: a #AsComponent instance. * * Get release information for this component, download it * if necessary. * * Returns: (nullable) (transfer none): Release information as #AsReleaseList, or %NULL if loading failed. **/ AsReleaseList * as_component_load_releases (AsComponent *cpt, gboolean allow_net, GError **error) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (!as_release_list_load (priv->releases, cpt, FALSE, allow_net, error)) return NULL; return priv->releases; } /** * as_component_set_releases: * @cpt: a #AsComponent instance. * @releases: the #AsReleaseList to use. * * Set a new set of releases for this component. * */ void as_component_set_releases (AsComponent *cpt, AsReleaseList *releases) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_object_unref (priv->releases); priv->releases = g_object_ref (releases); } /** * as_component_add_release: * @cpt: a #AsComponent instance. * @release: The #AsRelease to add * * Add an #AsRelease to this component. **/ void as_component_add_release (AsComponent *cpt, AsRelease *release) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_release_list_add (priv->releases, release); } /** * as_component_get_url: * @cpt: a #AsComponent instance. * @url_kind: the URL kind, e.g. %AS_URL_KIND_HOMEPAGE. * * Gets a URL. * * Returns: (nullable): string, or %NULL if unset * * Since: 0.6.2 **/ const gchar * as_component_get_url (AsComponent *cpt, AsUrlKind url_kind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return g_hash_table_lookup (priv->urls, GINT_TO_POINTER (url_kind)); } /** * as_component_add_url: * @cpt: a #AsComponent instance. * @url_kind: the URL kind, e.g. %AS_URL_KIND_HOMEPAGE * @url: the full URL. * * Adds some URL data to the component. * * Since: 0.6.2 **/ void as_component_add_url (AsComponent *cpt, AsUrlKind url_kind, const gchar *url) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_hash_table_insert (priv->urls, GINT_TO_POINTER (url_kind), g_strdup (url)); } /** * as_component_get_extends: * @cpt: an #AsComponent instance. * * Returns a string list of IDs of components which * are extended by this addon. * * See %as_component_get_extends() for the reverse. * * Returns: (element-type utf8) (transfer none) (nullable): A #GPtrArray or %NULL if not set. * * Since: 0.15.5 **/ GPtrArray * as_component_get_extends (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->extends; } /** * as_component_check_value_flags: * * Test for the presence of a value flag. */ static gboolean as_component_check_value_flags (AsComponent *cpt, AsValueFlags test_flags) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->context == NULL) return FALSE; return as_flags_contains (as_context_get_value_flags (priv->context), test_flags); } /** * as_component_add_extends: * @cpt: a #AsComponent instance. * @cpt_id: The id of a component which is extended by this component * * Add a reference to the extended component * * Since: 0.15.5 **/ void as_component_add_extends (AsComponent *cpt, const gchar *cpt_id) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (as_component_check_value_flags (cpt, AS_VALUE_FLAG_DUPLICATE_CHECK)) { /* check for duplicates */ if (as_ptr_array_find_string (priv->extends, cpt_id) != NULL) return; } g_ptr_array_add (priv->extends, g_strdup (cpt_id)); } /** * as_component_get_addons: * @cpt: an #AsComponent instance. * * Returns a list of #AsComponent objects which * are addons extending this component in functionality. * * This is the reverse of %as_component_get_extends() * * Returns: (transfer none) (element-type AsComponent): An array of #AsComponent. * * Since: 0.9.2 **/ GPtrArray * as_component_get_addons (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->addons; } /** * as_component_add_addon: * @cpt: a #AsComponent instance. * @addon: The #AsComponent that extends @cpt * * Add a reference to the addon that is enhancing this component. * * Since: 0.9.2 **/ void as_component_add_addon (AsComponent *cpt, AsComponent *addon) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_ptr_array_add (priv->addons, g_object_ref (addon)); } /** * as_component_get_bundles: * @cpt: a #AsComponent instance. * * Get a list of all software bundles associated with this component. * * Returns: (transfer none) (element-type AsBundle): A list of #AsBundle. * * Since: 0.10 **/ GPtrArray * as_component_get_bundles (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->bundles; } /** * as_component_set_bundles_array: * @cpt: a #AsComponent instance. * * Internal helper. **/ void as_component_set_bundles_array (AsComponent *cpt, GPtrArray *bundles) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_assign_ptr_array_safe (priv->bundles, bundles); as_component_invalidate_data_id (cpt); } /** * as_component_get_bundle: * @cpt: a #AsComponent instance. * @bundle_kind: the bundle kind, e.g. %AS_BUNDLE_KIND_LIMBA. * * Gets a bundle identifier string. * * Returns: (transfer none) (nullable): An #AsBundle, or %NULL if not set. * * Since: 0.8.0 **/ AsBundle * as_component_get_bundle (AsComponent *cpt, AsBundleKind bundle_kind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); guint i; for (i = 0; i < priv->bundles->len; i++) { AsBundle *bundle = AS_BUNDLE (g_ptr_array_index (priv->bundles, i)); if (as_bundle_get_kind (bundle) == bundle_kind) return bundle; } return NULL; } /** * as_component_add_bundle: * @cpt: a #AsComponent instance. * @bundle: The #AsBundle to add. * * Adds a bundle to the component. * * Since: 0.8.0 **/ void as_component_add_bundle (AsComponent *cpt, AsBundle *bundle) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_ptr_array_add (priv->bundles, g_object_ref (bundle)); as_component_invalidate_data_id (cpt); } /** * as_component_has_bundle: * @cpt: a #AsComponent instance. * * Returns: %TRUE if this component has a bundle associated. **/ gboolean as_component_has_bundle (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->bundles->len > 0; } /** * as_component_has_package: * @cpt: a #AsComponent instance. * * Internal function. **/ gboolean as_component_has_package (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return (priv->pkgnames != NULL) && (priv->pkgnames[0] != NULL); } /** * as_component_has_install_candidate: * @cpt: a #AsComponent instance. * * Returns: %TRUE if the component has an installable component (package, bundle, ...) set. **/ gboolean as_component_has_install_candidate (AsComponent *cpt) { return as_component_has_bundle (cpt) || as_component_has_package (cpt); } /** * as_component_get_kind: * @cpt: a #AsComponent instance. * * Returns the #AsComponentKind of this component. * * Returns: the kind of #this. */ AsComponentKind as_component_get_kind (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->kind; } /** * as_component_set_kind: * @cpt: a #AsComponent instance. * @value: the #AsComponentKind. * * Sets the #AsComponentKind of this component. */ void as_component_set_kind (AsComponent *cpt, AsComponentKind value) { AsComponentPrivate *priv = GET_PRIVATE (cpt); priv->kind = value; g_object_notify ((GObject *) cpt, "kind"); } /** * as_component_get_date_eol: * @cpt: a #AsComponent instance. * * Gets the end-of-life date for the entire component. * * Returns: The EOL date as string in ISO8601 format. * * Since: 0.15.2 **/ const gchar * as_component_get_date_eol (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->date_eol; } /** * as_component_set_date_eol: * @cpt: a #AsComponent instance. * @date: the EOL date in ISO8601 format. * * Sets an end-of-life date for this component. * * Since: 0.15.2 **/ void as_component_set_date_eol (AsComponent *cpt, const gchar *date) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_assign_string_safe (priv->date_eol, date); } /** * as_component_get_timestamp_eol: * @cpt: a #AsComponent instance. * * Gets the UNIX timestamp for the date when this component * is out of support (end-of-life) and will receive no more * updates, not even security fixes. * * Returns: UNIX timestamp, or 0 for unset or invalid. * * Since: 0.15.2 **/ guint64 as_component_get_timestamp_eol (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autoptr(GDateTime) time = NULL; if (priv->date_eol == NULL) return 0; time = as_iso8601_to_datetime (priv->date_eol); if (time != NULL) { return g_date_time_to_unix (time); } else { g_warning ("Unable to retrieve EOL timestamp from component EOL date: %s", priv->date_eol); return 0; } } /** * as_component_sanitize_date_eol: */ static gchar * as_component_sanitize_date_eol (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autoptr(GDateTime) time = as_iso8601_to_datetime (priv->date_eol); if (time != NULL) return g_date_time_format_iso8601 (time); /* error, not a valid date */ return NULL; } /** * as_component_get_pkgnames: * @cpt: a #AsComponent instance. * * Get a list of package names which this component consists of. * This usually is just one package name. * * Returns: (transfer none): String array of package names */ gchar ** as_component_get_pkgnames (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->pkgnames; } /** * as_component_get_pkgname: * @cpt: a #AsComponent instance. * * Get the first package name of the list of packages that need to be installed * for this component to be present on the system. * Since most components consist of only one package, this is safe to use for * about 90% of all cases. * * However, to support a component fully, please use %as_component_get_pkgnames() for * getting all packages that need to be installed, and use this method only to * e.g. get the main package to perform a quick "is it installed?" check. * * Returns: (transfer none): String array of package names */ gchar * as_component_get_pkgname (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if ((priv->pkgnames == NULL) || (priv->pkgnames[0] == NULL)) return NULL; return priv->pkgnames[0]; } /** * as_component_set_pkgnames: * @cpt: a #AsComponent instance. * @packages: (array zero-terminated=1): * * Set a list of package names this component consists of. * (This should usually be just one package name) */ void as_component_set_pkgnames (AsComponent *cpt, gchar **packages) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (G_UNLIKELY (priv->pkgnames == packages)) return; g_strfreev (priv->pkgnames); priv->pkgnames = g_strdupv (packages); g_object_notify ((GObject *) cpt, "pkgnames"); } /** * as_component_set_pkgname: * @cpt: a #AsComponent instance. * @pkgname: the package name * * Set the package name that provides this component. * * Since: 0.14.5 */ void as_component_set_pkgname (AsComponent *cpt, const gchar *pkgname) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_strfreev (priv->pkgnames); priv->pkgnames = g_new0 (gchar *, 2); priv->pkgnames[0] = g_strdup (pkgname); g_object_notify ((GObject *) cpt, "pkgnames"); } /** * as_component_get_source_pkgname: * @cpt: a #AsComponent instance. * * Returns: the source package name. */ const gchar * as_component_get_source_pkgname (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->source_pkgname; } /** * as_component_set_source_pkgname: * @cpt: a #AsComponent instance. * @spkgname: the source package name. */ void as_component_set_source_pkgname (AsComponent *cpt, const gchar *spkgname) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_assign_string_safe (priv->source_pkgname, spkgname); } /** * as_component_get_id: * @cpt: a #AsComponent instance. * * Get the unique AppStream identifier for this component. * This ID is unique for the described component, but does * not uniquely identify the metadata set. * * For a unique ID for this metadata set in the current * session, use %as_component_get_data_id() * * Returns: the unique AppStream identifier. */ const gchar * as_component_get_id (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->id; } /** * as_component_set_id: * @cpt: a #AsComponent instance. * @value: the unique identifier. * * Set the AppStream identifier for this component. */ void as_component_set_id (AsComponent *cpt, const gchar *value) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_assign_string_safe (priv->id, value); g_object_notify ((GObject *) cpt, "id"); as_component_invalidate_data_id (cpt); } /** * as_component_get_data_id: * @cpt: a #AsComponent instance. * * Get a unique identifier for this metadata set. * This unique ID is only valid for the current session, * as opposed to the AppStream ID which uniquely identifies * a software component. * * The format of the unique id usually is: * %{scope}/%{origin}/%{distribution_system}/%{appstream_id} * * For example: * system/os/package/org.example.FooBar * * Returns: the unique session-specific identifier. */ const gchar * as_component_get_data_id (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->data_id == NULL) priv->data_id = as_utils_build_data_id_for_cpt (cpt); return priv->data_id; } /** * as_component_set_data_id: * @cpt: a #AsComponent instance. * @value: the unique session-specific identifier. * * Set the session-specific unique metadata identifier for this * component. * If two components have a different data_id but the same ID, * they will be treated as independent sets of metadata describing * the same component type. */ void as_component_set_data_id (AsComponent *cpt, const gchar *value) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_assign_string_safe (priv->data_id, value); } /** * as_component_get_origin: * @cpt: a #AsComponent instance. */ const gchar * as_component_get_origin (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if ((priv->context != NULL) && (priv->origin == NULL)) return as_context_get_origin (priv->context); return priv->origin; } /** * as_component_set_origin: * @cpt: a #AsComponent instance. * @origin: the origin. */ void as_component_set_origin (AsComponent *cpt, const gchar *origin) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_ref_string_assign_safe (&priv->origin, origin); as_component_invalidate_data_id (cpt); } /** * as_component_get_branch: * @cpt: a #AsComponent instance. * * Gets the branch for the application. * * Returns: string, or %NULL if unset * * Since: 0.14.0 */ const gchar * as_component_get_branch (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->branch; } /** * as_component_set_branch: * @cpt: a #AsComponent instance. * @branch: the branch, e.g. "master" or "3-16". * * Set the branch that the component instance was sourced from. * * Since: 0.14.0 */ void as_component_set_branch (AsComponent *cpt, const gchar *branch) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_ref_string_assign_safe (&priv->branch, branch); as_component_invalidate_data_id (cpt); } /** * as_component_get_architecture: * @cpt: a #AsComponent instance. * * The architecture of the software component this data was generated from. */ const gchar * as_component_get_architecture (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if ((priv->context != NULL) && (priv->arch == NULL)) return as_context_get_architecture (priv->context); return priv->arch; } /** * as_component_set_architecture: * @cpt: a #AsComponent instance. * @arch: the architecture string. */ void as_component_set_architecture (AsComponent *cpt, const gchar *arch) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_ref_string_assign_safe (&priv->arch, arch); } /** * as_component_set_context_locale: * @cpt: a #AsComponent instance. * @locale: the new locale. * * Set the active locale on the context assoaiacted with this component, * creating a new context for the component if none exists yet. * * Please not that this will flip the locale of all other components and * entities that use the same context as well! * This function is just a convenience method, and does not replace * proper #AsContext management. */ void as_component_set_context_locale (AsComponent *cpt, const gchar *locale) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->context == NULL) { priv->context = as_context_new (); as_context_set_origin (priv->context, priv->origin); } as_context_set_locale (priv->context, locale); } /** * as_component_get_active_locale: * @cpt: a #AsComponent instance. * * Get the current active locale for this component, which * is used to get localized messages. * * Returns: the current active locale. */ static const gchar * as_component_get_active_locale (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); const GRefString *locale; /* create empty context if none exists yet */ if (priv->context == NULL) { priv->context = as_context_new (); as_context_set_origin (priv->context, priv->origin); } locale = as_context_get_locale (priv->context); if (locale == NULL) return "C"; else return locale; } /** * as_component_get_name: * @cpt: a #AsComponent instance. * * A human-readable name for this component. * * Returns: the name. */ const gchar * as_component_get_name (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return as_context_localized_ht_get (priv->context, priv->name, NULL /* locale override */); } /** * as_component_set_name: * @cpt: A valid #AsComponent * @value: The name * @locale: (nullable): The BCP47 locale for this value, or %NULL to use the current active one. * * Set a human-readable name for this component. */ void as_component_set_name (AsComponent *cpt, const gchar *value, const gchar *locale) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_context_localized_ht_set (priv->context, priv->name, value, locale); g_object_notify ((GObject *) cpt, "name"); } /** * as_component_get_summary: * @cpt: a #AsComponent instance. * * Get a short description of this component. * * Returns: the summary. */ const gchar * as_component_get_summary (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return as_context_localized_ht_get (priv->context, priv->summary, NULL /* locale override */); } /** * as_component_set_summary: * @cpt: A valid #AsComponent * @value: The summary * @locale: (nullable): The BCP47 locale for this value, or %NULL to use the current active one. * * Set a short description for this component. */ void as_component_set_summary (AsComponent *cpt, const gchar *value, const gchar *locale) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_context_localized_ht_set (priv->context, priv->summary, value, locale); g_object_notify ((GObject *) cpt, "summary"); } /** * as_component_get_description: * @cpt: a #AsComponent instance. * * Get the localized long description of this component. * * Returns: the description. */ const gchar * as_component_get_description (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return as_context_localized_ht_get (priv->context, priv->description, NULL /* locale override */); } /** * as_component_set_description: * @cpt: A valid #AsComponent * @value: The long description * @locale: (nullable): The BCP47 locale for this value, or %NULL to use the current active one. * * Set long description for this component. */ void as_component_set_description (AsComponent *cpt, const gchar *value, const gchar *locale) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_context_localized_ht_set (priv->context, priv->description, value, locale); g_object_notify ((GObject *) cpt, "description"); } /** * as_component_get_keywords: * @cpt: a #AsComponent instance. * * Returns: (transfer none) (element-type utf8): String array of keywords */ GPtrArray * as_component_get_keywords (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); GPtrArray *keywords; keywords = g_hash_table_lookup (priv->keywords, as_component_get_active_locale (cpt)); if (keywords == NULL) { /* fall back to untranslated */ keywords = g_hash_table_lookup (priv->keywords, "C"); } if (keywords == NULL) { /* add an empty list to return, so checking for "no keywords" is easier * for the callers of this method. */ keywords = g_ptr_array_new_with_free_func (g_free); g_hash_table_insert (priv->keywords, g_ref_string_new_intern (as_component_get_active_locale (cpt)), keywords); } return keywords; } /** * as_component_get_keywords_table: * @cpt: a #AsComponent instance. * * Retrieve the internal hash table mapping languages to * keword arrays. * * Returns: (transfer none): Hash table locale->keyword arrays */ GHashTable * as_component_get_keywords_table (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->keywords; } /** * as_component_set_keywords: * @cpt: a #AsComponent instance. * @new_keywords: (element-type utf8): Array of keywords * @locale: (nullable): BCP47 locale of the values, or %NULL to use current locale. * @deep_copy: Set to %TRUE if the keywords array should be copied, %FALSE to set by reference. * * Set keywords for this component, replacing all existing ones for the selected locale. */ void as_component_set_keywords (AsComponent *cpt, GPtrArray *new_keywords, const gchar *locale, gboolean deep_copy) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autoptr(GPtrArray) keywords = NULL; /* if no locale was specified, we assume the default locale */ if (locale == NULL) locale = as_component_get_active_locale (cpt); if (deep_copy) { keywords = g_ptr_array_new_with_free_func (g_free); for (guint i = 0; i < new_keywords->len; ++i) { const gchar *value = g_ptr_array_index (new_keywords, i); if (!as_is_empty (value)) g_ptr_array_add (keywords, g_strdup (value)); } } else { keywords = g_ptr_array_ref (new_keywords); } g_hash_table_insert (priv->keywords, g_ref_string_new_intern (locale), g_steal_pointer (&keywords)); g_object_notify ((GObject *) cpt, "keywords"); } /** * as_component_add_keyword: * @cpt: an #AsComponent instance. * @keyword: The new keyword to add. * @locale: (nullable): BCP47 locale of the values, or %NULL to use current locale. * * Add a new keyword to the keywords list for the given locale. This function does not * check for duplicate keywords. */ void as_component_add_keyword (AsComponent *cpt, const gchar *keyword, const gchar *locale) { AsComponentPrivate *priv = GET_PRIVATE (cpt); GPtrArray *keywords = NULL; if (as_is_empty (keyword)) return; /* if no locale was specified, we assume the default locale */ if (locale == NULL) locale = as_component_get_active_locale (cpt); keywords = g_hash_table_lookup (priv->keywords, locale); if (keywords == NULL) { keywords = g_ptr_array_new_with_free_func (g_free); g_hash_table_insert (priv->keywords, g_ref_string_new_intern (locale), keywords); } g_ptr_array_add (keywords, g_strdup (keyword)); } /** * as_component_clear_keywords: * @cpt: an #AsComponent instance. * @locale: (nullable): BCP47 locale of the values, or %NULL to use current locale. * * Remove all keywords for the given locale. */ void as_component_clear_keywords (AsComponent *cpt, const gchar *locale) { AsComponentPrivate *priv = GET_PRIVATE (cpt); /* if no locale was specified, we assume the default locale */ if (locale == NULL) locale = as_component_get_active_locale (cpt); g_hash_table_remove (priv->keywords, locale); g_object_notify ((GObject *) cpt, "keywords"); } /** * as_component_get_icons: * @cpt: an #AsComponent instance * * Returns: (element-type AsIcon) (transfer none): A #GPtrArray of all icons for this component. */ GPtrArray * as_component_get_icons (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->icons; } /** * as_component_get_icon_by_size: * @cpt: an #AsComponent instance * @width: The icon width in pixels. * @height: the icon height in pixels. * * Gets an icon matching the size constraints. * The icons are not filtered by type, and the first icon * which matches the size is returned. * If you want more control over which icons you use for displaying, * use the %as_component_get_icons() function to get a list of all icons. * * Note that this function is not HiDPI aware! It will never return an icon with * a scaling factor > 1. * * Returns: (transfer none) (nullable): An icon matching the given width/height, or %NULL if not found. */ AsIcon * as_component_get_icon_by_size (AsComponent *cpt, guint width, guint height) { AsComponentPrivate *priv = GET_PRIVATE (cpt); guint i; for (i = 0; i < priv->icons->len; i++) { AsIcon *icon = AS_ICON (g_ptr_array_index (priv->icons, i)); /* ignore scaled icons */ if (as_icon_get_scale (icon) > 1) continue; if ((as_icon_get_width (icon) == width) && (as_icon_get_height (icon) == height)) return icon; } return NULL; } /** * as_component_get_icon_stock: * @cpt: an #AsComponent instance * * Gets a stock icon for this component if one is associated with it. * Will return %NULL otherwise. * * Returns: (transfer none) (nullable): An stock icon, or %NULL if none found. */ AsIcon * as_component_get_icon_stock (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); for (guint i = 0; i < priv->icons->len; i++) { AsIcon *icon = AS_ICON (g_ptr_array_index (priv->icons, i)); if (as_icon_get_kind (icon) == AS_ICON_KIND_STOCK) { return icon; } } return NULL; } /** * as_component_add_icon: * @cpt: an #AsComponent instance * @icon: the valid #AsIcon instance to add. * * Add an icon to this component. */ void as_component_add_icon (AsComponent *cpt, AsIcon *icon) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_ptr_array_add (priv->icons, g_object_ref (icon)); } /** * as_component_get_categories: * @cpt: a #AsComponent instance. * * Returns: (transfer none) (element-type utf8): String array of categories */ GPtrArray * as_component_get_categories (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->categories; } /** * as_component_add_category: * @cpt: a #AsComponent instance. * @category: the categories name to add. * * Add a category. */ void as_component_add_category (AsComponent *cpt, const gchar *category) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (as_component_check_value_flags (cpt, AS_VALUE_FLAG_DUPLICATE_CHECK)) { /* check for duplicates */ if (as_ptr_array_find_string (priv->categories, category) != NULL) return; } g_ptr_array_add (priv->categories, g_strdup (category)); } /** * as_component_has_category: * @cpt: an #AsComponent object * @category: the specified category to check * * Check if component is in the specified category. * * Returns: %TRUE if the component is in the specified category. **/ gboolean as_component_has_category (AsComponent *cpt, const gchar *category) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return as_ptr_array_find_string (priv->categories, category) != NULL; } /** * as_component_get_metadata_license: * @cpt: a #AsComponent instance. * * The license the metadata iself is subjected to. * * Returns: the license. */ const gchar * as_component_get_metadata_license (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->metadata_license; } /** * as_component_set_metadata_license: * @cpt: a #AsComponent instance. * @value: the metadata license. * * Set the license this metadata is licensed under. */ void as_component_set_metadata_license (AsComponent *cpt, const gchar *value) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_ref_string_assign_safe (&priv->metadata_license, value); } /** * as_component_get_project_license: * @cpt: a #AsComponent instance. * * Get the license of the project this component belongs to. * * Returns: the license. */ const gchar * as_component_get_project_license (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->project_license; } /** * as_component_set_project_license: * @cpt: a #AsComponent instance. * @value: the project license. * * Set the project license. */ void as_component_set_project_license (AsComponent *cpt, const gchar *value) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_ref_string_assign_safe (&priv->project_license, value); g_object_notify ((GObject *) cpt, "project-license"); } /** * as_component_get_project_group: * @cpt: a #AsComponent instance. * * Get the component's project group. * * Returns: the project group. */ const gchar * as_component_get_project_group (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->project_group; } /** * as_component_set_project_group: * @cpt: a #AsComponent instance. * @value: the project group. * * Set the component's project group. */ void as_component_set_project_group (AsComponent *cpt, const gchar *value) { AsComponentPrivate *priv = GET_PRIVATE (cpt); as_ref_string_assign_safe (&priv->project_group, value); } /** * as_component_get_developer: * @cpt: a #AsComponent instance. * * Get information about the component's developer or development team. * The returned object may be empty if no developer information was * available. * * Returns: (transfer none): the developer as #AsDeveloper. */ AsDeveloper * as_component_get_developer (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->developer == NULL) priv->developer = as_developer_new_with_context (priv->context); return priv->developer; } /** * as_component_set_developer: * @cpt: a #AsComponent instance. * @developer: the new #AsDeveloper * * Set the the component's developer. */ void as_component_set_developer (AsComponent *cpt, AsDeveloper *developer) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->developer == developer) return; if (priv->developer != NULL) g_object_unref (priv->developer); priv->developer = g_object_ref (developer); as_developer_set_context (priv->developer, priv->context); } /** * as_component_is_floss: * @cpt: a #AsComponent instance. * * Returns %TRUE if this component is free and open source software. * To determine this status, this function will check if it comes * from a vetted free-software-only source or whether its licenses * are only free software licenses. * * Returns: %TRUE if this component is free software. * * Since: 0.15.5 */ gboolean as_component_is_floss (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); gboolean is_free = as_license_is_free_license (priv->project_license); if (is_free) return TRUE; /* The license check yielded a non-free license, which is also returned * if no license was set. We also need to check the repository origin * for packaged components. */ if (as_is_empty (priv->origin)) return FALSE; if (as_utils_get_component_bundle_kind (cpt) == AS_BUNDLE_KIND_PACKAGE) { if (priv->context == NULL) { priv->context = as_context_new (); as_context_set_origin (priv->context, priv->origin); } return as_context_os_origin_is_free (priv->context, priv->origin); } return is_free; } /** * as_component_clear_tags: * @cpt: a #AsComponent instance. * * Remove all tags associated with this component. * * Since: 0.15.0 */ void as_component_clear_tags (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_ptr_array_remove_range (priv->tags, 0, priv->tags->len); } /** * as_component_add_tag: * @cpt: a #AsComponent instance. * @ns: The namespace the tag belongs to * @tag: The tag name * * Add a tag to this component. * * Returns: %TRUE if the tag was added. * * Since: 0.15.0 */ gboolean as_component_add_tag (AsComponent *cpt, const gchar *ns, const gchar *tag) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autofree gchar *tag_full = as_make_usertag_key (ns, tag); /* sanity check */ if (g_strstr_len (tag, -1, "::") != NULL) return FALSE; for (guint i = 0; i < priv->tags->len; i++) { const gchar *tag_iter = g_ptr_array_index (priv->tags, i); if (g_strcmp0 (tag_iter, tag_full) == 0) return TRUE; } g_ptr_array_add (priv->tags, g_steal_pointer (&tag_full)); return TRUE; } /** * as_component_remove_tag: * @cpt: a #AsComponent instance. * @ns: The namespace the tag belongs to * @tag: The tag name * * Remove a tag from this component * * Returns: %TRUE if the tag was removed. * * Since: 0.15.0 */ gboolean as_component_remove_tag (AsComponent *cpt, const gchar *ns, const gchar *tag) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autofree gchar *tag_full = as_make_usertag_key (ns, tag); for (guint i = 0; i < priv->tags->len; i++) { const gchar *tag_iter = g_ptr_array_index (priv->tags, i); if (g_strcmp0 (tag_iter, tag_full) == 0) { g_ptr_array_remove_index_fast (priv->tags, i); return TRUE; } } return FALSE; } /** * as_component_has_tag: * @cpt: a #AsComponent instance. * @ns: The namespace the tag belongs to * @tag: The tag name * * Test if the component is tagged with the selected * tag. * * Returns: %TRUE if tag exists. * * Since: 0.15.0 */ gboolean as_component_has_tag (AsComponent *cpt, const gchar *ns, const gchar *tag) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autofree gchar *tag_full = as_make_usertag_key (ns, tag); for (guint i = 0; i < priv->tags->len; i++) { const gchar *tag_iter = g_ptr_array_index (priv->tags, i); if (g_strcmp0 (tag_iter, tag_full) == 0) return TRUE; } return FALSE; } /** * as_component_get_references: * @cpt: an #AsComponent instance. * * Get a list of external references and citation information for this component. * * Returns: (transfer none) (element-type AsReference): An array of #AsReference. * * Since: 1.0.0 **/ GPtrArray * as_component_get_references (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->references; } /** * as_component_add_reference: * @cpt: a #AsComponent instance. * @reference: an #AsReference instance. * * Adds an external reference to the software component. * * Since: 1.0.0 **/ void as_component_add_reference (AsComponent *cpt, AsReference *reference) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_ptr_array_add (priv->references, g_object_ref (reference)); } /** * as_component_get_name_variant_suffix: * @cpt: a #AsComponent instance. * * Get variant suffix for the component name * (only to be displayed if two components have the same name). * * Returns: the variant suffix * * Since: 0.12.10 */ const gchar * as_component_get_name_variant_suffix (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->name_variant_suffix == NULL) return NULL; return as_context_localized_ht_get (priv->context, priv->name_variant_suffix, NULL /* locale override */); } /** * as_component_set_name_variant_suffix: * @cpt: a #AsComponent instance. * @value: the developer or developer team name * @locale: (nullable): the BCP47 locale, or %NULL. e.g. "en-GB" * * Set a variant suffix for the component name * (only to be displayed if components have the same name). * * Since: 0.12.10 */ void as_component_set_name_variant_suffix (AsComponent *cpt, const gchar *value, const gchar *locale) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->name_variant_suffix == NULL) priv->name_variant_suffix = g_hash_table_new_full ( g_str_hash, g_str_equal, (GDestroyNotify) as_ref_string_release, g_free); as_context_localized_ht_set (priv->context, priv->name_variant_suffix, value, locale); } /** * as_component_get_screenshots_all: * @cpt: a #AsComponent instance. * * Get a list of all associated screenshots, for all environments. * * Returns: (element-type AsScreenshot) (transfer none): an array of #AsScreenshot instances */ GPtrArray * as_component_get_screenshots_all (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->screenshots; } typedef struct { const gchar *env_id; const gchar *style; const gchar *environment; gboolean prioritize_style; } AsScreenshotSortHelper; static gint as_screenshot_order_score_helper (AsScreenshot *scr, AsScreenshotSortHelper *helper) { const gchar *scr_env = as_screenshot_get_environment (scr); if (as_str_equal0 (scr_env, helper->env_id)) return -2 * as_screenshot_get_position (scr); if (helper->prioritize_style) { if (scr_env != NULL && g_str_has_suffix (scr_env, helper->style)) return -1 * as_screenshot_get_position (scr); } if (scr_env != NULL && g_str_has_prefix (scr_env, helper->environment)) return -1 * as_screenshot_get_position (scr); return as_screenshot_get_position (scr); } static gint as_screenshots_sort_cb (gconstpointer a, gconstpointer b, gpointer user_data) { AsScreenshotSortHelper *helper = user_data; AsScreenshot *scr1 = *((AsScreenshot **) a); AsScreenshot *scr2 = *((AsScreenshot **) b); return as_screenshot_order_score_helper (scr1, helper) - as_screenshot_order_score_helper (scr2, helper); } static gint as_screenshots_sort_defaults_cb (gconstpointer a, gconstpointer b) { AsScreenshot *scr1 = *((AsScreenshot **) a); AsScreenshot *scr2 = *((AsScreenshot **) b); return as_screenshot_get_position (scr1) - as_screenshot_get_position (scr2); } /** * as_component_sort_screenshots: * @cpt: a #AsComponent instance. * @environment: (nullable): a GUI environment string, e.g. "plasma" or "gnome" * @style: (nullable): and environment style string, e.g. "light" or "dark" * @prioritize_style: if %TRUE, order screenshots of the given style earlier than ones of the given environment. * * Reorder the screenshots to prioritize a certain environment or style, instead of using the default * screenshot order. * * If both "environment" and "style" are %NULL, the previous default order is restored. */ void as_component_sort_screenshots (AsComponent *cpt, const gchar *environment, const gchar *style, gboolean prioritize_style) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autofree gchar *env_id = NULL; AsScreenshotSortHelper helper; if (priv->screenshots == 0) return; /* restore defaults if both specifiers are NULL */ if (environment == NULL && style == NULL) { /* restore the default order, but only if we changed the order before */ if (as_screenshot_get_position ( AS_SCREENSHOT (g_ptr_array_index (priv->screenshots, 0))) < 0) return; g_ptr_array_sort (priv->screenshots, as_screenshots_sort_defaults_cb); return; } if (environment == NULL) environment = ""; /* ensure screenshot ordering positions are set */ for (guint i = 0; i < priv->screenshots->len; i++) { AsScreenshot *scr = AS_SCREENSHOT (g_ptr_array_index (priv->screenshots, i)); if (as_screenshot_get_position (scr) < 0) as_screenshot_set_position (scr, i); } /* construct the environment ID to sort by */ env_id = (style == NULL) ? g_strdup (environment) : g_strconcat (environment, ":", style, NULL); /* set helper */ helper.environment = environment; helper.style = style; helper.env_id = env_id; helper.prioritize_style = prioritize_style; /* sort screenshots */ g_ptr_array_sort_with_data (priv->screenshots, as_screenshots_sort_cb, &helper); } /** * as_component_get_compulsory_for_desktops: * @cpt: a #AsComponent instance. * * Return value: (transfer none) (element-type utf8): A list of desktops where this component is compulsory **/ GPtrArray * as_component_get_compulsory_for_desktops (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->compulsory_for_desktops; } /** * as_component_set_compulsory_for_desktop: * @cpt: a #AsComponent instance. * @desktop: The name of the desktop. * * Mark this component to be compulsory for the specified desktop environment. **/ void as_component_set_compulsory_for_desktop (AsComponent *cpt, const gchar *desktop) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_return_if_fail (desktop != NULL); if (as_component_check_value_flags (cpt, AS_VALUE_FLAG_DUPLICATE_CHECK)) { /* check for duplicates */ if (as_ptr_array_find_string (priv->compulsory_for_desktops, desktop) != NULL) return; } g_ptr_array_add (priv->compulsory_for_desktops, g_strdup (desktop)); } /** * as_component_is_compulsory_for_desktop: * @cpt: an #AsComponent object * @desktop: the desktop-id to test for * * Check if this component is compulsory for the given desktop. * * Returns: %TRUE if compulsory, %FALSE otherwise. **/ gboolean as_component_is_compulsory_for_desktop (AsComponent *cpt, const gchar *desktop) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return as_ptr_array_find_string (priv->compulsory_for_desktops, desktop) != NULL; } /** * as_component_get_provided_for_kind: * @cpt: a #AsComponent instance. * @kind: kind of the provided item, e.g. %AS_PROVIDED_KIND_MEDIATYPE * * Get an #AsProvided object for the given interface type, * containing information about the public interfaces (mimetypes, firmware, DBus services, ...) * this component provides. * * Returns: (transfer none) (nullable): #AsProvided containing the items this component provides, or %NULL. **/ AsProvided * as_component_get_provided_for_kind (AsComponent *cpt, AsProvidedKind kind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); for (guint i = 0; i < priv->provided->len; i++) { AsProvided *prov = AS_PROVIDED (g_ptr_array_index (priv->provided, i)); if (as_provided_get_kind (prov) == kind) return prov; } return NULL; } /** * as_component_get_provided: * @cpt: a #AsComponent instance. * * Get a list of #AsProvided objects associated with this component. * * Returns: (transfer none) (element-type AsProvided): A list of #AsProvided objects. **/ GPtrArray * as_component_get_provided (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->provided; } /** * as_component_add_provided: * @cpt: a #AsComponent instance. * @prov: a #AsProvided instance. * * Add a set of provided items to this component. * * Since: 0.6.2 **/ void as_component_add_provided (AsComponent *cpt, AsProvided *prov) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_return_if_fail (prov != NULL); if (as_component_check_value_flags (cpt, AS_VALUE_FLAG_DUPLICATE_CHECK)) { guint i; for (i = 0; i < priv->provided->len; i++) { AsProvided *eprov = AS_PROVIDED (g_ptr_array_index (priv->provided, i)); if (as_provided_get_kind (prov) == as_provided_get_kind (eprov)) { /* replace existing entry */ g_ptr_array_remove_index (priv->provided, i); g_ptr_array_add (priv->provided, g_object_ref (prov)); return; } } } g_ptr_array_add (priv->provided, g_object_ref (prov)); } /** * as_component_add_provided_item: * @cpt: a #AsComponent instance. * @kind: the kind of the provided item (e.g. %AS_PROVIDED_KIND_MEDIATYPE) * @item: the item to add. * * Adds a provided item to the component with the given @kind, creating a new * @AsProvided for this kind internally if necessary. **/ void as_component_add_provided_item (AsComponent *cpt, AsProvidedKind kind, const gchar *item) { AsProvided *prov; AsComponentPrivate *priv = GET_PRIVATE (cpt); /* we just skip empty items */ if (as_is_empty (item)) return; prov = as_component_get_provided_for_kind (cpt, kind); if (prov == NULL) { prov = as_provided_new (); as_provided_set_kind (prov, kind); g_ptr_array_add (priv->provided, prov); } as_provided_add_item (prov, item); } /** * as_component_add_suggested: * @cpt: a #AsComponent instance. * @suggested: The #AsSuggested * * Add an #AsSuggested to this component. **/ void as_component_add_suggested (AsComponent *cpt, AsSuggested *suggested) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_ptr_array_add (priv->suggestions, g_object_ref (suggested)); } /** * as_component_get_suggested: * @cpt: a #AsComponent instance. * * Get a list of associated suggestions. * * Returns: (transfer none) (element-type AsSuggested): an array of #AsSuggested instances */ GPtrArray * as_component_get_suggested (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->suggestions; } /** * as_component_get_merge_kind: * @cpt: a #AsComponent instance. * * Get the merge method which should apply to duplicate components * with this ID. * * Returns: the #AsMergeKind of this component. * * Since: 0.9.8 */ AsMergeKind as_component_get_merge_kind (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->merge_kind; } /** * as_component_set_merge_kind: * @cpt: a #AsComponent instance. * @kind: the #AsMergeKind. * * Sets the #AsMergeKind for this component. * * Since: 0.9.8 */ void as_component_set_merge_kind (AsComponent *cpt, AsMergeKind kind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); priv->merge_kind = kind; } /** * as_component_get_priority: * @cpt: a #AsComponent instance. * * Returns the priority of this component. * This method is used internally. * * Since: 0.6.1 */ gint as_component_get_priority (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->priority; } /** * as_component_set_priority: * @cpt: a #AsComponent instance. * @priority: the given priority * * Sets the priority of this component. * This method is used internally. * * Since: 0.6.1 */ void as_component_set_priority (AsComponent *cpt, gint priority) { AsComponentPrivate *priv = GET_PRIVATE (cpt); priv->priority = priority; } /** * as_component_get_sort_score: * @cpt: a #AsComponent instance. * * Returns the sorting priority of this component. * * This will only return a valid value if this component * was the result of or involved in a search operation which * returned sorted results. * In most cases you will not need to access this value explicitly, * as all results of search operations in AppStream are already sorted * from best match to worst. * * The returned value is an arbitrary integer value, valid only for * the search terms involved in the search operation that yielded * this component as a result. * * Since: 0.12.11 */ guint as_component_get_sort_score (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->sort_score; } /** * as_component_set_sort_score: * @cpt: a #AsComponent instance. * @score: the given sorting score * * Sets the sorting score of this component. * * Since: 0.9.8 */ void as_component_set_sort_score (AsComponent *cpt, guint score) { AsComponentPrivate *priv = GET_PRIVATE (cpt); priv->sort_score = score; } /** * as_component_add_language: * @cpt: an #AsComponent instance. * @locale: (nullable): the BCP47 locale, or %NULL. e.g. "en-GB" * @percentage: the percentage completion of the translation, 0 for locales with unknown amount of translation * * Adds a language to the component. * * Since: 0.7.0 **/ void as_component_add_language (AsComponent *cpt, const gchar *locale, gint percentage) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (locale == NULL) locale = "C"; g_hash_table_insert (priv->languages, g_strdup (locale), GINT_TO_POINTER (percentage)); } /** * as_component_get_language: * @cpt: an #AsComponent instance. * @locale: (nullable): the BCP47 locale, or %NULL. e.g. "en-GB" * * Gets the translation coverage in percent for a specific locale * * Returns: a percentage value, -1 if locale was not found * * Since: 0.7.0 **/ gint as_component_get_language (AsComponent *cpt, const gchar *locale) { gboolean ret; gpointer value = NULL; AsComponentPrivate *priv = GET_PRIVATE (cpt); if (locale == NULL) locale = "C"; ret = g_hash_table_lookup_extended (priv->languages, locale, NULL, &value); if (!ret) return -1; return GPOINTER_TO_INT (value); } /** * as_component_get_languages: * @cpt: an #AsComponent instance. * * Get a list of all languages. * * Returns: (transfer container) (element-type utf8): list of locales * * Since: 0.7.0 **/ GList * as_component_get_languages (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return g_hash_table_get_keys (priv->languages); } /** * as_component_clear_languages: * @cpt: an #AsComponent instance. * * Remove all registered language translation information. * * Since: 0.14.5 **/ void as_component_clear_languages (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_hash_table_remove_all (priv->languages); } /** * as_component_get_languages_table: * @cpt: an #AsComponent instance. * * Get a HashMap mapping languages to their completion percentage * * Returns: (transfer none): locales map * * Since: 0.7.0 **/ GHashTable * as_component_get_languages_table (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->languages; } /** * as_component_get_name_table: * @cpt: an #AsComponent instance. * * Get the internal locale to component name * mapping table. * * Returns: (transfer none): locale->names map **/ GHashTable * as_component_get_name_table (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->name; } /** * as_component_get_summary_table: * @cpt: an #AsComponent instance. * * Get the internal locale to component summary * mapping table. * * Returns: (transfer none): locale->summary map **/ GHashTable * as_component_get_summary_table (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->summary; } /** * as_component_get_translations: * @cpt: an #AsComponent instance. * * Get a #GPtrArray of #AsTranslation objects describing the * translation systems and translation-ids (e.g. Gettext domains) used * by this software component. * * Only set for metainfo files. * * Returns: (transfer none) (element-type AsTranslation): An array of #AsTranslation objects. * * Since: 0.9.2 */ GPtrArray * as_component_get_translations (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->translations == NULL) priv->translations = g_ptr_array_new_with_free_func (g_object_unref); return priv->translations; } /** * as_component_add_translation: * @cpt: an #AsComponent instance. * @tr: an #AsTranslation instance. * * Assign an #AsTranslation object describing the translation system used * by this component. * * Since: 0.9.2 */ void as_component_add_translation (AsComponent *cpt, AsTranslation *tr) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->translations == NULL) priv->translations = g_ptr_array_new_with_free_func (g_object_unref); g_ptr_array_add (priv->translations, g_object_ref (tr)); } /** * as_component_get_scope: * @cpt: a #AsComponent instance. * * Returns: the #AsComponentScope of this component. * * Since: 0.10.2 */ AsComponentScope as_component_get_scope (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->scope; } /** * as_component_set_scope: * @cpt: a #AsComponent instance. * @scope: the #AsComponentKind. * * Sets the #AsComponentScope of this component. */ void as_component_set_scope (AsComponent *cpt, AsComponentScope scope) { AsComponentPrivate *priv = GET_PRIVATE (cpt); priv->scope = scope; } /** * as_component_get_origin_kind: * @cpt: a #AsComponent instance. * * Returns: the #AsOriginKind of this component. * * Since: 0.10.2 */ AsOriginKind as_component_get_origin_kind (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->origin_kind; } /** * as_component_set_origin_kind: * @cpt: a #AsComponent instance. * @okind: the #AsOriginKind. * * Sets the #AsOriginKind of this component. */ void as_component_set_origin_kind (AsComponent *cpt, AsOriginKind okind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); priv->origin_kind = okind; } /** * as_component_add_icon_full: * * Internal helper function for as_component_refine_icons() */ static void as_component_add_icon_full (AsComponent *cpt, AsIconKind kind, const gchar *size_str, const gchar *fname) { g_autoptr(AsIcon) icon = NULL; icon = as_icon_new (); as_icon_set_kind (icon, kind); as_icon_set_filename (icon, fname); if (g_strcmp0 (size_str, "128x128") == 0) { as_icon_set_width (icon, 128); as_icon_set_height (icon, 128); } else { /* it's either "64x64", emptystring or NULL, in any case we assume 64x64 * This has to be adapted as soon as we support more than 2 icon sizes, but * we are lazy here to not hurt performance too much. */ as_icon_set_width (icon, 64); as_icon_set_height (icon, 64); } as_component_add_icon (cpt, icon); } /** * as_component_refine_icons: * @cpt: a #AsComponent instance. * * We use this method to ensure the "icon" and "icon_url" properties of * a component are properly set, by finding the icons in default directories. */ static void as_component_refine_icons (AsComponent *cpt, GPtrArray *icon_paths) { const gchar *extensions[] = { "png", "svg", "svgz", "gif", "ico", "xcf", NULL }; const gchar *sizes[] = { "", "64x64", "128x128", NULL }; const gchar *icon_fname = NULL; const gchar *origin; guint i, j, k, l; g_autoptr(GPtrArray) icons = NULL; AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->icons->len == 0) return; /* take control of the old icon list and rewrite it */ icons = priv->icons; priv->icons = g_ptr_array_new_with_free_func (g_object_unref); origin = as_component_get_origin (cpt); /* Process the icons we have and extract sizes */ for (i = 0; i < icons->len; i++) { AsIconKind ikind; AsIcon *icon = AS_ICON (g_ptr_array_index (icons, i)); ikind = as_icon_get_kind (icon); if (ikind == AS_ICON_KIND_REMOTE) { /* no processing / icon-search is needed (and possible) for remote icons */ as_component_add_icon (cpt, icon); continue; } else if (ikind == AS_ICON_KIND_STOCK) { /* since AppStream 0.9, we are not expected to find stock icon names in cache * directories anymore, so we can just add it without changes */ as_component_add_icon (cpt, icon); continue; } if ((ikind != AS_ICON_KIND_CACHED) && (ikind != AS_ICON_KIND_LOCAL)) { g_warning ("Found icon of unknown type '%s' in '%s', skipping it.", as_icon_kind_to_string (ikind), as_component_get_data_id (cpt)); continue; } /* get icon name we want to resolve * (it's "filename", because we should only get here if we have * a CACHED or LOCAL icon) */ icon_fname = as_icon_get_filename (icon); if (g_str_has_prefix (icon_fname, "/")) { /* looks like this component already has a full icon path */ as_component_add_icon (cpt, icon); /* assume 64x64px icon, if not defined otherwise */ if ((as_icon_get_width (icon) == 0) && (as_icon_get_height (icon) == 0)) { as_icon_set_width (icon, 64); as_icon_set_height (icon, 64); } continue; } /* skip the full cache search if we already have size information */ if ((ikind == AS_ICON_KIND_CACHED) && (as_icon_get_width (icon) > 0)) { for (l = 0; l < icon_paths->len; l++) { g_autofree gchar *tmp_icon_path_wh = NULL; const gchar *icon_path = (const gchar *) g_ptr_array_index ( icon_paths, l); if (as_icon_get_scale (icon) <= 1) { tmp_icon_path_wh = g_strdup_printf ( "%s/%s/%ix%i/%s", icon_path, origin, as_icon_get_width (icon), as_icon_get_height (icon), icon_fname); } else { tmp_icon_path_wh = g_strdup_printf ( "%s/%s/%ix%i@%i/%s", icon_path, origin, as_icon_get_width (icon), as_icon_get_height (icon), as_icon_get_scale (icon), icon_fname); } if (g_file_test (tmp_icon_path_wh, G_FILE_TEST_EXISTS)) { as_icon_set_filename (icon, tmp_icon_path_wh); as_component_add_icon (cpt, icon); break; } } /* we don't need a full search anymore - the icon having size information means that * it will be in the "origin" subdirectory with the appropriate size, so there is no * reason to start a big icon-hunt */ continue; } /* search local icon path */ for (l = 0; l < icon_paths->len; l++) { const gchar *icon_path = (const gchar *) g_ptr_array_index (icon_paths, l); for (j = 0; sizes[j] != NULL; j++) { g_autofree gchar *tmp_icon_path = NULL; /* sometimes, the file already has an extension */ tmp_icon_path = g_strdup_printf ("%s/%s/%s/%s", icon_path, origin, sizes[j], icon_fname); if (g_file_test (tmp_icon_path, G_FILE_TEST_EXISTS)) { /* we have an icon! */ if (g_strcmp0 (sizes[j], "") == 0) { /* old icon directory, so assume 64x64 */ as_component_add_icon_full (cpt, as_icon_get_kind (icon), "64x64", tmp_icon_path); } else { as_component_add_icon_full (cpt, as_icon_get_kind (icon), sizes[j], tmp_icon_path); } continue; } /* file not found, try extensions (we will not do this forever, better fix AppStream data!) */ for (k = 0; extensions[k] != NULL; k++) { g_autofree gchar *tmp_icon_path_ext = NULL; tmp_icon_path_ext = g_strdup_printf ("%s/%s/%s/%s.%s", icon_path, origin, sizes[j], icon_fname, extensions[k]); if (g_file_test (tmp_icon_path_ext, G_FILE_TEST_EXISTS)) { /* we have an icon! */ if (g_strcmp0 (sizes[j], "") == 0) { /* old icon directory, so assume 64x64 */ as_component_add_icon_full ( cpt, as_icon_get_kind (icon), "64x64", tmp_icon_path_ext); } else { as_component_add_icon_full ( cpt, as_icon_get_kind (icon), sizes[j], tmp_icon_path_ext); } } } } } } } /** * as_component_complete: * @cpt: a #AsComponent instance. * @icon_paths: String array of possible (cached) icon locations * * Private function to complete a AsComponent with * additional data found on the system. * * INTERNAL */ void as_component_complete (AsComponent *cpt, GPtrArray *icon_paths) { AsComponentPrivate *priv = GET_PRIVATE (cpt); /* improve icon paths */ if (icon_paths != NULL) as_component_refine_icons (cpt, icon_paths); /* "fake" a launchable entry for desktop-apps that failed to include one. This is used for legacy compatibility */ if ((priv->kind == AS_COMPONENT_KIND_DESKTOP_APP) && (priv->launchables->len <= 0)) { if (g_str_has_suffix (priv->id, ".desktop")) { g_autoptr(AsLaunchable) launchable = as_launchable_new (); as_launchable_set_kind (launchable, AS_LAUNCHABLE_KIND_DESKTOP_ID); as_launchable_add_entry (launchable, priv->id); as_component_add_launchable (cpt, launchable); } } } /** * as_component_add_token_helper: */ static void as_component_add_token_helper (AsComponent *cpt, const gchar *value, AsSearchTokenMatch match_flag, AsStemmer *stemmer, GPtrArray *tokens_out) { AsComponentPrivate *priv = GET_PRIVATE (cpt); AsTokenType *match_pval; g_autofree gchar *token_stemmed = NULL; /* invalid */ if (!as_utils_search_token_valid (value)) return; /* small tokens are invalid unless they are in the summary / name of the component */ if (match_flag < AS_SEARCH_TOKEN_MATCH_SUMMARY) { if (strlen (value) < 3) return; } /* create a stemmed version of our token */ token_stemmed = as_stemmer_stem (stemmer, value); /* ignore the token if it couldn't be stemmed (usually means we had a low quality token) */ if (token_stemmed == NULL) return; /* does the token already exist */ match_pval = g_hash_table_lookup (priv->token_cache, token_stemmed); if (match_pval != NULL) { /* add token to the output array only if it was more important than the cached one */ if (tokens_out != NULL && *match_pval < match_flag) g_ptr_array_add (tokens_out, g_steal_pointer (&token_stemmed)); *match_pval |= match_flag; return; } /* create and add */ match_pval = g_new0 (AsTokenType, 1); *match_pval = match_flag; g_hash_table_insert (priv->token_cache, g_ref_string_new_intern (token_stemmed), match_pval); /* add to the token output array, if we have one */ if (tokens_out != NULL) g_ptr_array_add (tokens_out, g_steal_pointer (&token_stemmed)); } /** * as_component_add_token: */ static void as_component_add_token (AsComponent *cpt, const gchar *value, gboolean allow_split, AsSearchTokenMatch match_flag, GPtrArray *tokens_out) { /* get global stemmer instance (it's threadsafe and should survive this invocation) */ AsStemmer *stemmer = as_stemmer_get (as_component_get_active_locale (cpt)); /* add extra tokens for names like x-plane or half-life */ if (allow_split && g_strstr_len (value, -1, "-") != NULL) { guint i; g_auto(GStrv) split = g_strsplit (value, "-", -1); for (i = 0; split[i] != NULL; i++) as_component_add_token_helper (cpt, split[i], match_flag, stemmer, tokens_out); } /* add the whole token always, even when we split on hyphen */ as_component_add_token_helper (cpt, value, match_flag, stemmer, tokens_out); } /** * as_component_value_tokenize: * * Split a component value string into tokens. */ static gboolean as_component_value_tokenize (AsComponent *cpt, const gchar *value, gchar ***tokens_utf8, gchar ***tokens_ascii) { /* tokenize with UTF-8 fallbacks */ if (g_strstr_len (value, -1, "+") == NULL && g_strstr_len (value, -1, "-") == NULL) { (*tokens_utf8) = g_str_tokenize_and_fold (value, as_component_get_active_locale (cpt), tokens_ascii); } /* we might still be able to extract tokens if g_str_tokenize_and_fold() can't do it or +/- were found */ if ((*tokens_utf8) == NULL) { g_autofree gchar *delim = NULL; delim = g_utf8_strdown (value, -1); g_strdelimit (delim, "/,.;:", ' '); (*tokens_utf8) = g_strsplit (delim, " ", -1); } if (tokens_ascii == NULL) return (*tokens_utf8) != NULL; else return ((*tokens_utf8) != NULL) || ((*tokens_ascii) != NULL); } /** * as_component_add_tokens: */ static void as_component_add_tokens (AsComponent *cpt, const gchar *value, gboolean allow_split, AsSearchTokenMatch match_flag, GPtrArray *tokens_out) { guint i; g_auto(GStrv) values_utf8 = NULL; g_auto(GStrv) values_ascii = NULL; /* sanity check */ if (value == NULL) { g_critical ("trying to add NULL search token to %s", as_component_get_id (cpt)); return; } /* create a set of tokens from the value string */ if (!as_component_value_tokenize (cpt, value, &values_utf8, &values_ascii)) return; /* add each token */ for (i = 0; values_utf8 != NULL && values_utf8[i] != NULL; i++) as_component_add_token (cpt, values_utf8[i], allow_split, match_flag, tokens_out); for (i = 0; values_ascii != NULL && values_ascii[i] != NULL; i++) as_component_add_token (cpt, values_ascii[i], allow_split, match_flag, tokens_out); } /** * as_component_create_token_cache_target: */ static void as_component_create_token_cache_target (AsComponent *cpt, AsComponent *donor, guint flags, GPtrArray *tokens_out) { AsComponentPrivate *priv = GET_PRIVATE (donor); const gchar *tmp; GPtrArray *keywords; AsProvided *prov; /* tokenize all the data we have */ if (priv->id != NULL && as_flags_contains (flags, AS_SEARCH_TOKEN_MATCH_ID)) { as_component_add_token (cpt, priv->id, FALSE, AS_SEARCH_TOKEN_MATCH_ID, tokens_out); } tmp = as_component_get_name (cpt); if (tmp != NULL && as_flags_contains (flags, AS_SEARCH_TOKEN_MATCH_NAME)) { const gchar *name_c = g_hash_table_lookup (priv->name, "C"); as_component_add_tokens (cpt, tmp, TRUE, AS_SEARCH_TOKEN_MATCH_NAME, tokens_out); if (name_c != NULL && g_strcmp0 (tmp, name_c) != 0) as_component_add_tokens (cpt, name_c, TRUE, AS_SEARCH_TOKEN_MATCH_NAME, tokens_out); } tmp = as_component_get_summary (cpt); if (tmp != NULL && as_flags_contains (flags, AS_SEARCH_TOKEN_MATCH_SUMMARY)) { as_component_add_tokens (cpt, tmp, TRUE, AS_SEARCH_TOKEN_MATCH_SUMMARY, tokens_out); } tmp = as_component_get_description (cpt); if (tmp != NULL && as_flags_contains (flags, AS_SEARCH_TOKEN_MATCH_DESCRIPTION)) { as_component_add_tokens (cpt, tmp, TRUE, AS_SEARCH_TOKEN_MATCH_DESCRIPTION, tokens_out); } keywords = as_component_get_keywords (cpt); if (keywords->len > 0 && as_flags_contains (flags, AS_SEARCH_TOKEN_MATCH_KEYWORD)) { for (guint i = 0; i < keywords->len; i++) as_component_add_tokens (cpt, (const gchar *) g_ptr_array_index (keywords, i), FALSE, AS_SEARCH_TOKEN_MATCH_KEYWORD, tokens_out); } prov = as_component_get_provided_for_kind (donor, AS_PROVIDED_KIND_MEDIATYPE); if (prov != NULL && as_flags_contains (flags, AS_SEARCH_TOKEN_MATCH_MEDIATYPE)) { GPtrArray *items = as_provided_get_items (prov); for (guint i = 0; i < items->len; i++) as_component_add_token (cpt, (const gchar *) g_ptr_array_index (items, i), FALSE, AS_SEARCH_TOKEN_MATCH_MEDIATYPE, tokens_out); } if (priv->pkgnames != NULL && as_flags_contains (flags, AS_SEARCH_TOKEN_MATCH_PKGNAME)) { for (guint i = 0; priv->pkgnames[i] != NULL; i++) as_component_add_token (cpt, priv->pkgnames[i], FALSE, AS_SEARCH_TOKEN_MATCH_PKGNAME, tokens_out); } } /** * as_component_create_token_cache: * @cpt: a #AsComponent instance. * * Internal API. */ static void as_component_create_token_cache (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); guint flags; if (!g_once_init_enter (&priv->token_cache_valid)) return; flags = AS_SEARCH_TOKEN_MATCH_MEDIATYPE | AS_SEARCH_TOKEN_MATCH_PKGNAME | AS_SEARCH_TOKEN_MATCH_ORIGIN | AS_SEARCH_TOKEN_MATCH_DESCRIPTION | AS_SEARCH_TOKEN_MATCH_SUMMARY | AS_SEARCH_TOKEN_MATCH_KEYWORD | AS_SEARCH_TOKEN_MATCH_NAME | AS_SEARCH_TOKEN_MATCH_ID; as_component_create_token_cache_target (cpt, cpt, flags, NULL); for (guint i = 0; i < priv->addons->len; i++) { AsComponent *donor = g_ptr_array_index (priv->addons, i); as_component_create_token_cache_target (cpt, donor, flags, NULL); } g_once_init_leave (&priv->token_cache_valid, TRUE); } /** * as_component_generate_tokens_for: * @cpt: a #AsComponent instance. * * Internal API to get tokens only for a specific aspect of this * component. */ GPtrArray * as_component_generate_tokens_for (AsComponent *cpt, AsSearchTokenMatch token_kind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autoptr(GPtrArray) tokens = g_ptr_array_new_with_free_func (g_free); as_component_create_token_cache_target (cpt, cpt, token_kind, tokens); for (guint i = 0; i < priv->addons->len; i++) { AsComponent *donor = g_ptr_array_index (priv->addons, i); as_component_create_token_cache_target (cpt, donor, token_kind, tokens); } return g_steal_pointer (&tokens); } /** * as_component_search_matches: * @cpt: a #AsComponent instance. * @term: the search term. * * Searches component data for a specific keyword. * * Returns: a match scrore, where 0 is no match and 100 is the best match. * * Since: 0.9.7 **/ guint as_component_search_matches (AsComponent *cpt, const gchar *term) { AsComponentPrivate *priv = GET_PRIVATE (cpt); AsTokenType *match_pval; GList *l; guint result = 0; g_autoptr(GList) keys = NULL; /* nothing to do */ if (term == NULL) return 0; /* ensure the token cache is created */ as_component_create_token_cache (cpt); /* find the exact match (which is more awesome than a partial match) */ match_pval = g_hash_table_lookup (priv->token_cache, term); if (match_pval != NULL) return *match_pval << 2; /* need to do partial match */ keys = g_hash_table_get_keys (priv->token_cache); for (l = keys; l != NULL; l = l->next) { const gchar *key = l->data; if (g_str_has_prefix (key, term)) { match_pval = g_hash_table_lookup (priv->token_cache, key); result |= *match_pval; } } return result; } /** * as_component_search_matches_all: * @cpt: a #AsComponent instance. * @terms: the search terms. * * Searches component data for all the specific keywords. * * Returns: a match score, where 0 is no match and larger numbers are better * matches. * * Since: 0.9.8 */ guint as_component_search_matches_all (AsComponent *cpt, gchar **terms) { AsComponentPrivate *priv = GET_PRIVATE (cpt); guint i; guint matches_sum = 0; guint tmp; priv->sort_score = 0; if (terms == NULL) { /* if the terms list is NULL, we usually had a too short search term when * tokenizing the search string. In any case, we treat NULL as match-all * value. * (users will see a full list of all entries that way, which they will * recognize as hint to make their search more narrow) */ priv->sort_score = 1; return priv->sort_score; } /* do *all* search keywords match */ for (i = 0; terms[i] != NULL; i++) { tmp = as_component_search_matches (cpt, terms[i]); if (tmp == 0) return 0; matches_sum |= tmp; } priv->sort_score = matches_sum; return priv->sort_score; } /** * as_component_get_search_tokens: * @cpt: a #AsComponent instance. * * Returns all search tokens for this component. * * Returns: (transfer container) (element-type utf8): The string search tokens * * Since: 0.9.7 */ GPtrArray * as_component_get_search_tokens (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); GList *l; GPtrArray *array; g_autoptr(GList) keys = NULL; /* ensure the token cache is created */ as_component_create_token_cache (cpt); /* return all the token cache */ keys = g_hash_table_get_keys (priv->token_cache); array = g_ptr_array_new_with_free_func (g_free); for (l = keys; l != NULL; l = l->next) g_ptr_array_add (array, g_strdup (l->data)); return array; } /** * as_component_has_desktop_group: * * Internal helper method for %as_component_is_member_of_category */ static gboolean as_component_has_desktop_group (AsComponent *cpt, const gchar *desktop_group) { guint i; g_auto(GStrv) split = g_strsplit (desktop_group, "::", -1); for (i = 0; split[i] != NULL; i++) { if (!as_component_has_category (cpt, split[i])) return FALSE; } return TRUE; } /** * as_component_is_member_of_category: * @cpt: a #AsComponent instance. * @category: The category to test. * * Test if the component @cpt is a member of category @category. */ gboolean as_component_is_member_of_category (AsComponent *cpt, AsCategory *category) { GPtrArray *cdesktop_groups; guint i; cdesktop_groups = as_category_get_desktop_groups (category); for (i = 0; i < cdesktop_groups->len; i++) { const gchar *cdg_name = (const gchar *) g_ptr_array_index (cdesktop_groups, i); if (as_component_has_desktop_group (cpt, cdg_name)) return TRUE; } return FALSE; } /** * as_component_set_ignored: * @cpt: a #AsComponent instance. * @ignore: %TRUE if the metadata in @cpt should be ignored. * * Since: 0.10.2 */ void as_component_set_ignored (AsComponent *cpt, gboolean ignore) { AsComponentPrivate *priv = GET_PRIVATE (cpt); priv->ignored = ignore; } /** * as_component_is_ignored: * @cpt: a #AsComponent instance. * * Returns: Whether this component's metadata should be ignored. * * Since: 0.10.2 */ gboolean as_component_is_ignored (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->ignored; } /** * as_component_get_custom: * @cpt: An #AsComponent. * * Returns: (transfer none): Hash table of custom user defined data fields. * * Since: 0.10.5 */ GHashTable * as_component_get_custom (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->custom; } /** * as_component_get_custom_value: * @cpt: An #AsComponent. * @key: Field name. * * Retrieve value for a custom data entry with the given key. * * Since: 0.10.5 */ const gchar * as_component_get_custom_value (AsComponent *cpt, const gchar *key) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (key == NULL) return NULL; return g_hash_table_lookup (priv->custom, key); } /** * as_component_insert_custom_value: * @cpt: An #AsComponent. * @key: Key name. * @value: A string value. * * Add a key and value pair to the custom data table. * * Returns: %TRUE if the key did not exist yet. * * Since: 0.10.5 */ gboolean as_component_insert_custom_value (AsComponent *cpt, const gchar *key, const gchar *value) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (key == NULL) return FALSE; return g_hash_table_insert (priv->custom, g_ref_string_new_intern (key), g_ref_string_new_intern (value)); } /** * as_component_get_reviews: * @cpt: a #AsComponent instance. * * Gets any reviews associated with the component. * * Returns: (element-type AsReview) (transfer none): an array * * Since: 0.14.0 **/ GPtrArray * as_component_get_reviews (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->reviews; } /** * as_component_add_review: * @cpt: a #AsComponent instance. * @review: a #AsReview instance. * * Adds a user review to a software component. * * Since: 0.14.0 **/ void as_component_add_review (AsComponent *cpt, AsReview *review) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_ptr_array_add (priv->reviews, g_object_ref (review)); } /** * as_component_get_content_ratings: * @cpt: a #AsComponent instance. * * Gets all content ratings defined for this software. * * Returns: (element-type AsContentRating) (transfer none): an array * * Since: 0.11.0 **/ GPtrArray * as_component_get_content_ratings (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->content_ratings; } /** * as_component_get_content_rating: * @cpt: a #AsComponent instance. * @kind: a ratings kind, e.g. "oars-1.0" * * Gets a content ratings of a specific type that are defined for this component. * * Returns: (transfer none) (nullable): a #AsContentRating or %NULL if not found * * Since: 0.11.0 **/ AsContentRating * as_component_get_content_rating (AsComponent *cpt, const gchar *kind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); guint i; for (i = 0; i < priv->content_ratings->len; i++) { AsContentRating *content_rating = AS_CONTENT_RATING ( g_ptr_array_index (priv->content_ratings, i)); if (g_strcmp0 (as_content_rating_get_kind (content_rating), kind) == 0) return content_rating; } return NULL; } /** * as_component_add_content_rating: * @cpt: a #AsComponent instance. * @content_rating: a #AsContentRating instance. * * Adds a content rating to this component. * * Since: 0.11.0 **/ void as_component_add_content_rating (AsComponent *cpt, AsContentRating *content_rating) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_ptr_array_add (priv->content_ratings, g_object_ref (content_rating)); } /** * as_component_get_launchable: * @cpt: a #AsComponent instance. * @kind: a launch kind, e.g. %AS_LAUNCHABLE_KIND_DESKTOP_ID * * Gets a #AsLaunchable of a specific type that contains launchable entries for * this component. * * Returns: (transfer none) (nullable): a #AsLaunchable or %NULL if not found * * Since: 0.11.0 **/ AsLaunchable * as_component_get_launchable (AsComponent *cpt, AsLaunchableKind kind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); guint i; for (i = 0; i < priv->launchables->len; i++) { AsLaunchable *launch = AS_LAUNCHABLE (g_ptr_array_index (priv->launchables, i)); if (as_launchable_get_kind (launch) == kind) return launch; } return NULL; } /** * as_component_get_launchables: * @cpt: a #AsComponent instance. * * Returns: (transfer none) (element-type AsLaunchable): an array * * Since: 0.11.0 **/ GPtrArray * as_component_get_launchables (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->launchables; } /** * as_component_add_launchable: * @cpt: a #AsComponent instance. * @launchable: a #AsLaunchable instance. * * Adds a #AsLaunchable containing launchables entries for this component. * * Since: 0.11.0 **/ void as_component_add_launchable (AsComponent *cpt, AsLaunchable *launchable) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_return_if_fail (launchable != NULL); g_ptr_array_add (priv->launchables, g_object_ref (launchable)); } /** * as_component_get_requires: * @cpt: a #AsComponent instance. * * Get an array of items that are required by this component. * * Returns: (transfer none) (element-type AsRelation): an array * * Since: 0.12.0 **/ GPtrArray * as_component_get_requires (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->requires; } /** * as_component_get_recommends: * @cpt: a #AsComponent instance. * * Get an array of items that are recommended by this component. * * Returns: (transfer none) (element-type AsRelation): an array * * Since: 0.12.0 **/ GPtrArray * as_component_get_recommends (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->recommends; } /** * as_component_get_supports: * @cpt: a #AsComponent instance. * * Get an array of items that are supported by this component, * e.g. to indicate support for a specific piece of hardware. * * Returns: (transfer none) (element-type AsRelation): an array * * Since: 0.15.0 **/ GPtrArray * as_component_get_supports (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->supports; } /** * as_component_add_relation: * @cpt: a #AsComponent instance. * @relation: a #AsRelation instance. * * Adds a #AsRelation to set a recommends or requires relation of * component @cpt on the item mentioned in the #AsRelation. * * Since: 0.12.0 **/ void as_component_add_relation (AsComponent *cpt, AsRelation *relation) { AsComponentPrivate *priv = GET_PRIVATE (cpt); AsRelationKind kind = as_relation_get_kind (relation); if (kind == AS_RELATION_KIND_REQUIRES) { g_ptr_array_add (priv->requires, g_object_ref (relation)); } else if (kind == AS_RELATION_KIND_RECOMMENDS) { g_ptr_array_add (priv->recommends, g_object_ref (relation)); } else if (kind == AS_RELATION_KIND_SUPPORTS) { g_ptr_array_add (priv->supports, g_object_ref (relation)); } else { g_warning ("Tried to add relation of unknown kind to component %s", priv->data_id); } } /** * as_component_make_implicit_relations_explicit: * * This is for compatibility with older metadata where we did not yet have * the concept of device-requirements. Therefore, we make implicit assumptions * that they are for desktop-style apps only. * * This function makes such assumptions explicit from being implicit before. */ static void as_component_make_implicit_relations_explicit (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autoptr(AsRelation) rel = NULL; /* if we have any requirements set, we assume device requirements will have been set explicitly */ if (priv->requires->len != 0 || priv->recommends->len != 0 || priv->supports->len != 0) return; rel = as_relation_new (); as_relation_set_kind (rel, AS_RELATION_KIND_REQUIRES); as_relation_set_item_kind (rel, AS_RELATION_ITEM_KIND_DISPLAY_LENGTH); as_relation_set_display_side_kind (rel, AS_DISPLAY_SIDE_KIND_SHORTEST); as_relation_set_value_px (rel, 768); as_relation_set_compare (rel, AS_RELATION_COMPARE_GE); as_component_add_relation (cpt, rel); g_clear_pointer (&rel, g_object_unref); rel = as_relation_new (); as_relation_set_kind (rel, AS_RELATION_KIND_REQUIRES); as_relation_set_item_kind (rel, AS_RELATION_ITEM_KIND_CONTROL); as_relation_set_value_control_kind (rel, AS_CONTROL_KIND_KEYBOARD); as_component_add_relation (cpt, rel); g_clear_pointer (&rel, g_object_unref); rel = as_relation_new (); as_relation_set_kind (rel, AS_RELATION_KIND_REQUIRES); as_relation_set_item_kind (rel, AS_RELATION_ITEM_KIND_CONTROL); as_relation_set_value_control_kind (rel, AS_CONTROL_KIND_POINTING); as_component_add_relation (cpt, rel); g_clear_pointer (&rel, g_object_unref); } /** * as_component_check_relations_internal: */ static void as_component_check_relations_internal (AsComponent *cpt, AsSystemInfo *sysinfo, AsPool *pool, GPtrArray *relations, gboolean is_template, GPtrArray *result) { for (guint i = 0; i < relations->len; i++) { AsRelation *relation = AS_RELATION (g_ptr_array_index (relations, i)); g_autoptr(AsRelationCheckResult) rcr = NULL; g_autoptr(GError) tmp_error = NULL; if (is_template) { /* we ignore anything that isn't in a template system info */ AsRelationItemKind item_kind = as_relation_get_item_kind (relation); if (item_kind == AS_RELATION_ITEM_KIND_ID || item_kind == AS_RELATION_ITEM_KIND_MODALIAS || item_kind == AS_RELATION_ITEM_KIND_KERNEL || item_kind == AS_RELATION_ITEM_KIND_FIRMWARE || item_kind == AS_RELATION_ITEM_KIND_HARDWARE || item_kind == AS_RELATION_ITEM_KIND_INTERNET) continue; } rcr = as_relation_is_satisfied (relation, sysinfo, pool, &tmp_error); if (rcr == NULL) { rcr = as_relation_check_result_new (); as_relation_check_result_set_status (rcr, AS_RELATION_STATUS_ERROR); as_relation_check_result_set_relation (rcr, relation); as_relation_check_result_set_error_code (rcr, tmp_error->code); as_relation_check_result_set_message (rcr, "%s", tmp_error->message); } g_ptr_array_add (result, g_steal_pointer (&rcr)); } } /** * as_component_check_relations: * @cpt: a #AsComponent instance. * @sysinfo: (nullable): an #AsSystemInfo to use for system information. * @pool: (nullable): an #AsPool to find component dependencies in. * @rel_kind: the kind of relations to check * * Verifies the respective relations and presents whether the system specified * in #AsSystemInfo @sysinfo and data from @pool supply the requested facilities. * * Returns: (transfer container) (element-type AsRelationCheckResult): An array of #AsRelationCheckResult * * Since: 1.0.0 */ GPtrArray * as_component_check_relations (AsComponent *cpt, AsSystemInfo *sysinfo, AsPool *pool, AsRelationKind rel_kind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); GPtrArray *result = g_ptr_array_new_with_free_func (g_object_unref); /* compatibility for older metadata */ as_component_make_implicit_relations_explicit (cpt); if (rel_kind == AS_RELATION_KIND_REQUIRES) as_component_check_relations_internal (cpt, sysinfo, pool, priv->requires, FALSE, result); else if (rel_kind == AS_RELATION_KIND_RECOMMENDS) as_component_check_relations_internal (cpt, sysinfo, pool, priv->recommends, FALSE, result); else if (rel_kind == AS_RELATION_KIND_SUPPORTS) as_component_check_relations_internal (cpt, sysinfo, pool, priv->supports, FALSE, result); return result; } /** * as_component_get_system_compatibility_score: * @cpt: a #AsComponent instance. * @sysinfo: an #AsSystemInfo to use for system information. * @is_template: if %TRUE, treat system info as neutral template, ignoring any peripheral devices or kernel relations. * @results: (out callee-allocates) (optional) (element-type AsRelationCheckResult) (transfer container): Receive the resulting check results * * Return a score between 0 and 100 determining how compatible the component * is with the system configuration provided as parameter. * * 0 means the componsnt will not work at all, while 100 is best compatibility. * * Returns: a compatibility score between 0 and 100 */ gint as_component_get_system_compatibility_score (AsComponent *cpt, AsSystemInfo *sysinfo, gboolean is_template, GPtrArray **results) { AsComponentPrivate *priv = GET_PRIVATE (cpt); gint score = 0; g_autoptr(GPtrArray) rc_results = g_ptr_array_new_with_free_func (g_object_unref); g_return_val_if_fail (sysinfo != NULL, 0); /* compatibility for older metadata */ as_component_make_implicit_relations_explicit (cpt); as_component_check_relations_internal (cpt, sysinfo, NULL, priv->requires, is_template, rc_results); as_component_check_relations_internal (cpt, sysinfo, NULL, priv->recommends, is_template, rc_results); as_component_check_relations_internal (cpt, sysinfo, NULL, priv->supports, is_template, rc_results); score = as_relation_check_results_get_compatibility_score (rc_results); if (results != NULL) *results = g_steal_pointer (&rc_results); return score; } /** * as_component_get_replaces: * @cpt: a #AsComponent instance. * * Get a list of component IDs of components that this software replaces entirely. * * Returns: (transfer none) (element-type utf8): an array of component-IDs */ GPtrArray * as_component_get_replaces (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->replaces == NULL) priv->replaces = g_ptr_array_new_with_free_func (g_free); return priv->replaces; } /** * as_component_add_replaces: * @cpt: a #AsComponent instance. * @cid: an AppStream component ID * * Add the component ID of a component that gets replaced by the current component. **/ void as_component_add_replaces (AsComponent *cpt, const gchar *cid) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_return_if_fail (cid != NULL); if (priv->replaces == NULL) priv->replaces = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (priv->replaces, g_strdup (cid)); } /** * as_component_get_agreements: * @cpt: an #AsComponent instance. * * Get a list of all agreements registered with this software component. * * Returns: (transfer none) (element-type AsAgreement): An array of #AsAgreement. * * Since: 0.15.0 **/ GPtrArray * as_component_get_agreements (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->agreements; } /** * as_component_add_agreement: * @cpt: a #AsComponent instance. * @agreement: an #AsAgreement instance. * * Adds an agreement to the software component. * * Since: 0.12.1 **/ void as_component_add_agreement (AsComponent *cpt, AsAgreement *agreement) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_ptr_array_add (priv->agreements, g_object_ref (agreement)); } /** * as_component_get_agreement_by_kind: * @cpt: a #AsComponent instance. * @kind: an agreement kind, e.g. %AS_AGREEMENT_KIND_EULA * * Gets an agreement the component has specified for the particular kind. * * Returns: (transfer none) (nullable): a #AsAgreement or %NULL for not found * * Since: 0.12.1 **/ AsAgreement * as_component_get_agreement_by_kind (AsComponent *cpt, AsAgreementKind kind) { AsComponentPrivate *priv = GET_PRIVATE (cpt); guint i; for (i = 0; i < priv->agreements->len; i++) { AsAgreement *agreement = AS_AGREEMENT (g_ptr_array_index (priv->agreements, i)); if (as_agreement_get_kind (agreement) == kind) return agreement; } return NULL; } /** * as_component_get_branding: * @cpt: an #AsComponent instance. * * Get the branding associated with this component, or %NULL * in case this component has no special branding. * * Returns: (transfer none) (nullable): An #AsBranding. * * Since: 0.15.2 **/ AsBranding * as_component_get_branding (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->branding; } /** * as_component_set_branding: * @cpt: a #AsComponent instance. * @branding: an #AsBranding instance. * * Set branding for this component. * * Since: 0.15.2 **/ void as_component_set_branding (AsComponent *cpt, AsBranding *branding) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (branding == priv->branding) return; if (priv->branding != NULL) g_object_unref (priv->branding); priv->branding = g_object_ref (branding); } /** * as_component_get_context: * @cpt: a #AsComponent instance. * * Get the #AsContext associated with this component. * This function may return %NULL if no context is set * (which will be the case if the component was not loaded from * a file or cache but constructed in memory). * * Returns: (transfer none) (nullable): the associated #AsContext or %NULL * * Since: 0.11.2 */ AsContext * as_component_get_context (AsComponent *cpt) { AsComponentPrivate *priv = GET_PRIVATE (cpt); return priv->context; } /** * as_component_set_context: * @cpt: a #AsComponent instance. * @context: the #AsContext. * * Sets the document context this component is associated * with. * * Since: 0.11.2 */ void as_component_set_context (AsComponent *cpt, AsContext *context) { AsComponentPrivate *priv = GET_PRIVATE (cpt); if (priv->context != NULL) g_object_unref (priv->context); if (context == NULL) { priv->context = NULL; return; } priv->context = g_object_ref (context); /* reset individual properties, so the new context overrides them */ as_ref_string_assign_safe (&priv->origin, NULL); g_free (g_steal_pointer (&priv->arch)); } /** * as_copy_l10n_hashtable_hfunc: */ static void as_copy_l10n_hashtable_hfunc (gpointer key, gpointer value, gpointer user_data) { GHashTable *dest = (GHashTable *) user_data; g_hash_table_insert (dest, g_ref_string_acquire (key), g_strdup (value)); } /** * as_copy_l10n_hashtable: * * Helper for as_component_merge_with_mode() */ static void as_copy_l10n_hashtable (GHashTable *src, GHashTable *dest) { /* don't copy if there is nothing to copy */ if (g_hash_table_size (src) <= 0) return; /* clear our destination table */ g_hash_table_remove_all (dest); /* copy */ g_hash_table_foreach (src, &as_copy_l10n_hashtable_hfunc, dest); } /** * as_copy_gobject_array: * * Helper for as_component_merge_with_mode() * * NOTE: Only the object references are copied. */ static void as_copy_gobject_array (GPtrArray *src, GPtrArray *dest) { guint i; /* don't copy if there is nothing to copy */ if (src->len <= 0) return; /* clear our destination table */ g_ptr_array_remove_range (dest, 0, dest->len); /* copy */ for (i = 0; i < src->len; i++) { GObject *obj = G_OBJECT (g_ptr_array_index (src, i)); g_ptr_array_add (dest, g_object_ref (obj)); } } /** * as_component_merge_with_mode: * @cpt: An #AsComponent. * @source: An #AsComponent to merge into @cpt. * @merge_kind: How * * Merge data from component @source into @cpt, respecting the * merge parameter @merge_kind. */ void as_component_merge_with_mode (AsComponent *cpt, AsComponent *source, AsMergeKind merge_kind) { guint i; AsComponent *dest_cpt = cpt; AsComponent *src_cpt = source; AsComponentPrivate *dest_priv = GET_PRIVATE (dest_cpt); AsComponentPrivate *src_priv = GET_PRIVATE (src_cpt); if (merge_kind == AS_MERGE_KIND_REMOVE_COMPONENT) { /* we can't remove ourselves from the pool, so this merge action is handled separately */ return; } /* FIXME/TODO: We need to merge more attributes */ /* merge stuff in append mode */ if (merge_kind == AS_MERGE_KIND_APPEND) { GPtrArray *suggestions; GPtrArray *cats; /* merge categories */ cats = as_component_get_categories (src_cpt); if (cats->len > 0) { g_autoptr(GHashTable) cat_table = NULL; GPtrArray *dest_categories; cat_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (i = 0; i < cats->len; i++) { const gchar *cat = (const gchar *) g_ptr_array_index (cats, i); g_hash_table_add (cat_table, g_strdup (cat)); } dest_categories = as_component_get_categories (dest_cpt); if (dest_categories->len > 0) { for (i = 0; i < dest_categories->len; i++) { const gchar *cat = (const gchar *) g_ptr_array_index ( dest_categories, i); g_hash_table_add (cat_table, g_strdup (cat)); } } g_ptr_array_set_size (dest_categories, 0); as_hash_table_string_keys_to_array (cat_table, dest_categories); } /* merge suggestions */ suggestions = as_component_get_suggested (src_cpt); if (suggestions != NULL) { for (i = 0; i < suggestions->len; i++) { as_component_add_suggested ( dest_cpt, AS_SUGGESTED (g_ptr_array_index (suggestions, i))); } } /* merge icons */ for (i = 0; i < src_priv->icons->len; i++) { AsIcon *icon = AS_ICON (g_ptr_array_index (src_priv->icons, i)); /* this function will not replace existing icons */ as_component_add_icon (dest_cpt, icon); } /* names */ if (g_hash_table_size (dest_priv->name) <= 0) as_copy_l10n_hashtable (src_priv->name, dest_priv->name); /* summary */ if (g_hash_table_size (dest_priv->summary) <= 0) as_copy_l10n_hashtable (src_priv->summary, dest_priv->summary); /* description */ if (g_hash_table_size (dest_priv->description) <= 0) as_copy_l10n_hashtable (src_priv->description, dest_priv->description); } /* merge stuff in replace mode */ if (merge_kind == AS_MERGE_KIND_REPLACE) { /* names */ as_copy_l10n_hashtable (src_priv->name, dest_priv->name); /* summary */ as_copy_l10n_hashtable (src_priv->summary, dest_priv->summary); /* description */ as_copy_l10n_hashtable (src_priv->description, dest_priv->description); /* merge package names */ if ((src_priv->pkgnames != NULL) && (src_priv->pkgnames[0] != NULL)) as_component_set_pkgnames (dest_cpt, src_priv->pkgnames); /* merge bundles */ if (as_component_has_bundle (src_cpt)) as_component_set_bundles_array (dest_cpt, as_component_get_bundles (src_cpt)); /* merge icons */ as_copy_gobject_array (src_priv->icons, dest_priv->icons); /* merge provided items */ as_copy_gobject_array (src_priv->provided, dest_priv->provided); } g_debug ("Merged data for '[%i] %s' <<- '[%i] %s'", as_component_get_origin_kind (dest_cpt), as_component_get_data_id (dest_cpt), as_component_get_origin_kind (src_cpt), as_component_get_data_id (src_cpt)); } /** * as_component_merge: * @cpt: An #AsComponent. * @source: An #AsComponent with a merge type. * * Merge data from component @source into @cpt, respecting the * merge mode of @source. * * Returns: %TRUE if the merge was successful. */ gboolean as_component_merge (AsComponent *cpt, AsComponent *source) { AsMergeKind merge_kind = as_component_get_merge_kind (source); g_return_val_if_fail (merge_kind != AS_MERGE_KIND_NONE, FALSE); as_component_merge_with_mode (cpt, source, merge_kind); return TRUE; } /** * as_component_set_kind_from_node: */ static void as_component_set_kind_from_node (AsComponent *cpt, xmlNode *node) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autofree gchar *cpttype = NULL; /* find out which kind of component we are dealing with */ cpttype = as_xml_get_prop_value (node, "type"); if ((cpttype == NULL) || (g_strcmp0 (cpttype, "generic") == 0)) { priv->kind = AS_COMPONENT_KIND_GENERIC; } else { priv->kind = as_component_kind_from_string (cpttype); if (priv->kind == AS_COMPONENT_KIND_UNKNOWN) g_debug ("Found unknown component type: %s", cpttype); } } /** * as_component_load_provides_from_xml: */ static void as_component_load_provides_from_xml (AsComponent *cpt, xmlNode *node) { xmlNode *iter; gchar *node_name; for (iter = node->children; iter != NULL; iter = iter->next) { g_autofree gchar *content = NULL; /* discard spaces */ if (iter->type != XML_ELEMENT_NODE) continue; node_name = (gchar *) iter->name; content = as_xml_get_node_value (iter); if (content == NULL) continue; if (g_strcmp0 (node_name, "id") == 0) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_ID, content); } else if (g_strcmp0 (node_name, "mediatype") == 0) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_MEDIATYPE, content); } else if (g_strcmp0 (node_name, "library") == 0) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_LIBRARY, content); } else if (g_strcmp0 (node_name, "binary") == 0) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_BINARY, content); } else if (g_strcmp0 (node_name, "font") == 0) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_FONT, content); } else if (g_strcmp0 (node_name, "modalias") == 0) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_MODALIAS, content); } else if (g_strcmp0 (node_name, "firmware") == 0) { g_autofree gchar *fw_type = NULL; fw_type = as_xml_get_prop_value (iter, "type"); if (g_strcmp0 (fw_type, "runtime") == 0) as_component_add_provided_item (cpt, AS_PROVIDED_KIND_FIRMWARE_RUNTIME, content); else if (g_strcmp0 (fw_type, "flashed") == 0) as_component_add_provided_item (cpt, AS_PROVIDED_KIND_FIRMWARE_FLASHED, content); } else if (g_strcmp0 (node_name, "python3") == 0) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_PYTHON, content); } else if (g_strcmp0 (node_name, "dbus") == 0) { g_autofree gchar *dbus_type = as_xml_get_prop_value (iter, "type"); if (g_strcmp0 (dbus_type, "system") == 0) as_component_add_provided_item (cpt, AS_PROVIDED_KIND_DBUS_SYSTEM, content); else if ((g_strcmp0 (dbus_type, "user") == 0) || (g_strcmp0 (dbus_type, "session") == 0)) as_component_add_provided_item (cpt, AS_PROVIDED_KIND_DBUS_USER, content); } } } /** * as_component_xml_parse_languages_node: */ static void as_component_xml_parse_languages_node (AsComponent *cpt, xmlNode *node) { xmlNode *iter; for (iter = node->children; iter != NULL; iter = iter->next) { /* discard spaces */ if (iter->type != XML_ELEMENT_NODE) continue; if (g_strcmp0 ((gchar *) iter->name, "lang") == 0) { guint64 percentage = 0; g_autofree gchar *locale = NULL; g_autofree gchar *prop = NULL; prop = as_xml_get_prop_value (iter, "percentage"); if (prop != NULL) percentage = g_ascii_strtoll (prop, NULL, 10); locale = as_xml_get_node_value (iter); as_component_add_language (cpt, locale, percentage); } } } /** * as_component_load_launchable_from_xml: * * Loads data from an XML node. **/ static void as_component_load_launchable_from_xml (AsComponent *cpt, xmlNode *node) { AsLaunchableKind lkind; AsLaunchable *launchable; g_autofree gchar *lstr = NULL; g_autofree gchar *value = NULL; lstr = as_xml_get_prop_value (node, "type"); lkind = as_launchable_kind_from_string (lstr); if (lkind == AS_LAUNCHABLE_KIND_UNKNOWN) return; launchable = as_component_get_launchable (cpt, lkind); if (launchable == NULL) { launchable = as_launchable_new (); as_launchable_set_kind (launchable, lkind); as_component_add_launchable (cpt, launchable); g_object_unref (launchable); } value = as_xml_get_node_value (node); if (value == NULL) return; as_launchable_add_entry (launchable, value); } /** * as_component_load_relations_from_xml: */ static void as_component_load_relations_from_xml (AsComponent *cpt, AsContext *ctx, xmlNode *node, AsRelationKind kind) { for (xmlNode *iter = node->children; iter != NULL; iter = iter->next) { g_autoptr(AsRelation) relation = NULL; /* discard spaces */ if (iter->type != XML_ELEMENT_NODE) continue; relation = as_relation_new (); as_relation_set_kind (relation, kind); if (as_relation_load_from_xml (relation, ctx, iter, NULL)) as_component_add_relation (cpt, relation); } } /** * as_component_load_keywords_from_xml: */ static void as_component_load_keywords_from_xml (AsComponent *cpt, AsContext *ctx, xmlNode *node) { if (as_context_get_style (ctx) == AS_FORMAT_STYLE_METAINFO) { for (xmlNode *iter = node->children; iter != NULL; iter = iter->next) { g_autofree gchar *lang = NULL; g_autofree gchar *keyword = NULL; if (iter->type != XML_ELEMENT_NODE) continue; lang = as_xml_get_node_locale_match (ctx, iter); if (lang == NULL) continue; keyword = as_xml_get_node_value (iter); as_component_add_keyword (cpt, keyword, lang); } } else { g_autofree gchar *lang = as_xml_get_node_locale_match (ctx, node); if (lang != NULL) { g_autoptr(GPtrArray) keywords = NULL; keywords = as_xml_get_children_as_string_list (node, "keyword"); as_component_set_keywords (cpt, keywords, lang, FALSE); } } } /** * as_component_load_from_xml: * @cpt: An #AsComponent. * @ctx: the AppStream document context. * @node: the XML node. * @error: a #GError. * * Loads data from an XML node. **/ gboolean as_component_load_from_xml (AsComponent *cpt, AsContext *ctx, xmlNode *node, GError **error) { AsComponentPrivate *priv = GET_PRIVATE (cpt); const gchar *node_name; g_autoptr(GPtrArray) pkgnames = NULL; g_autofree gchar *priority_str = NULL; g_autofree gchar *merge_str = NULL; g_autofree gchar *date_eol_str = NULL; /* sanity check */ if ((g_strcmp0 ((gchar *) node->name, "component") != 0) && (g_strcmp0 ((gchar *) node->name, "application") != 0)) { g_set_error (error, AS_METADATA_ERROR, AS_METADATA_ERROR_FAILED, "Expected 'component' tag, but got '%s' instead.", (gchar *) node->name); return FALSE; } pkgnames = g_ptr_array_new_with_free_func (g_free); /* set component kind */ as_component_set_kind_from_node (cpt, node); /* end-of-life date for this component */ date_eol_str = as_xml_get_prop_value (node, "date_eol"); if (date_eol_str != NULL) { g_free (priv->date_eol); priv->date_eol = g_steal_pointer (&date_eol_str); } /* set the priority for this component */ priority_str = as_xml_get_prop_value (node, "priority"); if (priority_str == NULL) { priv->priority = as_context_get_priority (ctx); } else { priv->priority = g_ascii_strtoll (priority_str, NULL, 10); } /* set the merge method for this component */ merge_str = as_xml_get_prop_value (node, "merge"); if (merge_str != NULL) { priv->merge_kind = as_merge_kind_from_string (merge_str); } /* set context for this component */ as_component_set_context (cpt, ctx); /* clear any existing descriptions */ g_hash_table_remove_all (priv->description); for (xmlNode *iter = node->children; iter != NULL; iter = iter->next) { AsTag tag_id; /* discard spaces */ if (iter->type != XML_ELEMENT_NODE) continue; node_name = (const gchar *) iter->name; tag_id = as_xml_tag_from_string (node_name); if (tag_id == AS_TAG_ID) { g_autofree gchar *content = as_xml_get_node_value (iter); as_component_set_id (cpt, content); if ((as_context_get_style (ctx) == AS_FORMAT_STYLE_METAINFO) && (priv->kind == AS_COMPONENT_KIND_GENERIC)) { /* parse legacy component type information */ as_component_set_kind_from_node (cpt, iter); } } else if (tag_id == AS_TAG_PKGNAME) { g_autofree gchar *content = as_xml_get_node_value (iter); if (content != NULL) g_ptr_array_add (pkgnames, g_strdup (content)); } else if (tag_id == AS_TAG_SOURCE_PKGNAME) { g_autofree gchar *content = as_xml_get_node_value (iter); as_component_set_source_pkgname (cpt, content); } else if (tag_id == AS_TAG_NAME) { g_autofree gchar *lang = NULL; g_autofree gchar *content = as_xml_get_node_value (iter); lang = as_xml_get_node_locale_match (ctx, iter); if (lang != NULL) as_component_set_name (cpt, content, lang); } else if (tag_id == AS_TAG_SUMMARY) { g_autofree gchar *lang = NULL; g_autofree gchar *content = as_xml_get_node_value (iter); lang = as_xml_get_node_locale_match (ctx, iter); if (lang != NULL) as_component_set_summary (cpt, content, lang); } else if (tag_id == AS_TAG_DESCRIPTION) { if (as_context_get_style (ctx) == AS_FORMAT_STYLE_CATALOG) { g_autofree gchar *lang = as_xml_get_node_locale_match (ctx, iter); /* for catalog XML, the "description" tag has a language property, so parsing it is simple */ if (lang != NULL) { gchar *desc; desc = as_xml_dump_node_children (iter); as_component_set_description (cpt, desc, lang); g_free (desc); } } else { as_xml_parse_metainfo_description_node (ctx, iter, priv->description); } } else if (tag_id == AS_TAG_ICON) { g_autoptr(AsIcon) icon = NULL; g_autofree gchar *content = as_xml_get_node_value (iter); if (content == NULL) continue; icon = as_icon_new (); if (as_icon_load_from_xml (icon, ctx, iter, NULL)) as_component_add_icon (cpt, icon); } else if (tag_id == AS_TAG_URL) { g_autofree gchar *content = as_xml_get_node_value (iter); if (content != NULL) { g_autofree gchar *urltype_str = NULL; AsUrlKind url_kind; urltype_str = as_xml_get_prop_value (iter, "type"); url_kind = as_url_kind_from_string (urltype_str); if (url_kind != AS_URL_KIND_UNKNOWN) as_component_add_url (cpt, url_kind, content); } } else if (tag_id == AS_TAG_CATEGORIES) { as_xml_add_children_values_to_array (iter, "category", priv->categories); } else if (tag_id == AS_TAG_KEYWORDS) { as_component_load_keywords_from_xml (cpt, ctx, iter); } else if (tag_id == AS_TAG_MIMETYPES) { g_autoptr(GPtrArray) mime_list = NULL; guint i; /* Mimetypes are essentially provided interfaces, that's why they belong into AsProvided. * However, due to historic reasons, the spec has an own toplevel tag for them, so we need * to parse them here. */ mime_list = as_xml_get_children_as_string_list (iter, "mimetype"); for (i = 0; i < mime_list->len; i++) { as_component_add_provided_item ( cpt, AS_PROVIDED_KIND_MEDIATYPE, (const gchar *) g_ptr_array_index (mime_list, i)); } } else if (tag_id == AS_TAG_PROVIDES) { as_component_load_provides_from_xml (cpt, iter); } else if (tag_id == AS_TAG_SCREENSHOTS) { xmlNode *iter2; for (iter2 = iter->children; iter2 != NULL; iter2 = iter2->next) { if (iter2->type != XML_ELEMENT_NODE) continue; if (g_strcmp0 ((const gchar *) iter2->name, "screenshot") == 0) { g_autoptr(AsScreenshot) screenshot = as_screenshot_new (); if (as_screenshot_load_from_xml (screenshot, ctx, iter2, NULL)) as_component_add_screenshot (cpt, screenshot); } } } else if (tag_id == AS_TAG_METADATA_LICENSE) { g_autofree gchar *content = as_xml_get_node_value (iter); as_component_set_metadata_license (cpt, content); } else if (tag_id == AS_TAG_PROJECT_LICENSE) { g_autofree gchar *content = as_xml_get_node_value (iter); as_component_set_project_license (cpt, content); } else if (tag_id == AS_TAG_PROJECT_GROUP) { g_autofree gchar *content = as_xml_get_node_value (iter); as_component_set_project_group (cpt, content); } else if (tag_id == AS_TAG_DEVELOPER) { g_autoptr(AsDeveloper) developer = as_developer_new (); if (as_developer_load_from_xml (developer, ctx, iter, NULL)) { if (priv->developer != NULL) g_object_unref (priv->developer); priv->developer = g_steal_pointer (&developer); } } else if (tag_id == AS_TAG_DEVELOPER_NAME) { /* DEPRECATED */ g_autofree gchar *lang = NULL; g_autofree gchar *content = as_xml_get_node_value (iter); lang = as_xml_get_node_locale_match (ctx, iter); if (lang != NULL) { if (priv->developer == NULL) priv->developer = as_developer_new_with_context (ctx); as_developer_set_name (priv->developer, content, lang); } } else if (tag_id == AS_TAG_COMPULSORY_FOR_DESKTOP) { g_autofree gchar *content = as_xml_get_node_value (iter); if (content != NULL) as_component_set_compulsory_for_desktop (cpt, content); } else if (tag_id == AS_TAG_RELEASES) { as_release_list_load_from_xml (priv->releases, ctx, iter, NULL); } else if (tag_id == AS_TAG_EXTENDS) { g_autofree gchar *content = as_xml_get_node_value (iter); as_component_add_extends (cpt, content); } else if (tag_id == AS_TAG_LANGUAGES) { as_component_xml_parse_languages_node (cpt, iter); } else if (tag_id == AS_TAG_LAUNCHABLE) { as_component_load_launchable_from_xml (cpt, iter); } else if (tag_id == AS_TAG_BUNDLE) { g_autoptr(AsBundle) bundle = as_bundle_new (); if (as_bundle_load_from_xml (bundle, ctx, iter, NULL)) as_component_add_bundle (cpt, bundle); } else if (tag_id == AS_TAG_TRANSLATION) { g_autofree gchar *content = as_xml_get_node_value (iter); if (content != NULL) { g_autoptr(AsTranslation) tr = as_translation_new (); if (as_translation_load_from_xml (tr, ctx, iter, NULL)) as_component_add_translation (cpt, tr); } } else if (tag_id == AS_TAG_SUGGESTS) { g_autoptr(AsSuggested) suggested = as_suggested_new (); if (as_suggested_load_from_xml (suggested, ctx, iter, NULL)) as_component_add_suggested (cpt, suggested); } else if (tag_id == AS_TAG_CUSTOM) { as_xml_parse_custom_node (iter, as_component_get_custom (cpt)); } else if (tag_id == AS_TAG_CONTENT_RATING) { g_autoptr(AsContentRating) ctrating = as_content_rating_new (); if (as_content_rating_load_from_xml (ctrating, ctx, iter, NULL)) as_component_add_content_rating (cpt, ctrating); } else if (tag_id == AS_TAG_REPLACES) { if (priv->replaces != NULL) g_ptr_array_unref (priv->replaces); priv->replaces = as_xml_get_children_as_string_list (iter, "id"); } else if (tag_id == AS_TAG_REQUIRES) { as_component_load_relations_from_xml (cpt, ctx, iter, AS_RELATION_KIND_REQUIRES); } else if (tag_id == AS_TAG_RECOMMENDS) { as_component_load_relations_from_xml (cpt, ctx, iter, AS_RELATION_KIND_RECOMMENDS); } else if (tag_id == AS_TAG_SUPPORTS) { as_component_load_relations_from_xml (cpt, ctx, iter, AS_RELATION_KIND_SUPPORTS); } else if (tag_id == AS_TAG_AGREEMENT) { g_autoptr(AsAgreement) agreement = as_agreement_new (); if (as_agreement_load_from_xml (agreement, ctx, iter, NULL)) as_component_add_agreement (cpt, agreement); } else if (tag_id == AS_TAG_REVIEWS) { for (xmlNode *sn = iter->children; sn != NULL; sn = sn->next) { if (sn->type != XML_ELEMENT_NODE) continue; if (g_strcmp0 ((const gchar *) sn->name, "review") == 0) { g_autoptr(AsReview) review = as_review_new (); if (as_review_load_from_xml (review, ctx, sn, NULL)) as_component_add_review (cpt, review); } } } else if (tag_id == AS_TAG_BRANDING) { g_autoptr(AsBranding) branding = as_branding_new (); if (as_branding_load_from_xml (branding, ctx, iter, NULL)) as_component_set_branding (cpt, branding); } else if (tag_id == AS_TAG_TAGS) { for (xmlNode *sn = iter->children; sn != NULL; sn = sn->next) { g_autofree gchar *ns = NULL; g_autofree gchar *value = NULL; if (sn->type != XML_ELEMENT_NODE) continue; ns = as_xml_get_prop_value (sn, "namespace"); value = as_xml_get_node_value (sn); as_component_add_tag (cpt, ns, value); } } else if (tag_id == AS_TAG_REFERENCES) { xmlNode *iter2; for (iter2 = iter->children; iter2 != NULL; iter2 = iter2->next) { g_autoptr(AsReference) reference = NULL; if (iter2->type != XML_ELEMENT_NODE) continue; reference = as_reference_new (); if (as_reference_load_from_xml (reference, ctx, iter2, NULL)) as_component_add_reference (cpt, reference); } } else if (as_context_get_internal_mode (ctx)) { g_autofree gchar *content = as_xml_get_node_value (iter); /* internal information */ if (tag_id == AS_TAG_INTERNAL_SCOPE) { priv->scope = as_component_scope_from_string (content); } else if (tag_id == AS_TAG_INTERNAL_ORIGIN) { as_component_set_origin (cpt, content); } else if (tag_id == AS_TAG_INTERNAL_BRANCH) { as_component_set_branch (cpt, content); } } else if (tag_id == AS_TAG_NAME_VARIANT_SUFFIX) { g_autofree gchar *lang = NULL; g_autofree gchar *content = as_xml_get_node_value (iter); lang = as_xml_get_node_locale_match (ctx, iter); if (lang != NULL) as_component_set_name_variant_suffix (cpt, content, lang); } } /* add package name information to component */ { g_auto(GStrv) strv = as_ptr_array_to_strv (pkgnames); as_component_set_pkgnames (cpt, strv); } /* sanity check */ if (as_is_empty (priv->id)) { g_set_error_literal (error, AS_METADATA_ERROR, AS_METADATA_ERROR_FAILED, "Component is invalid (essential tags are missing or empty)."); return FALSE; } return TRUE; } /** * as_component_xml_keywords_to_node: */ static void as_component_xml_keywords_to_node (AsComponent *cpt, AsContext *ctx, xmlNode *root) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autoptr(GList) keys = NULL; xmlNode *mi_kw_node = NULL; if (g_hash_table_size (priv->keywords) == 0) return; /* we need to sort the keys here to get reproductible output */ keys = g_hash_table_get_keys (priv->keywords); keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); if (as_context_get_style (ctx) == AS_FORMAT_STYLE_METAINFO) mi_kw_node = as_xml_add_node (root, "keywords"); for (GList *link = keys; link != NULL; link = link->next) { gboolean has_locale = FALSE; const gchar *locale = (const gchar *) link->data; GPtrArray *kws = g_hash_table_lookup (priv->keywords, locale); /* skip cruft */ if (as_is_cruft_locale (locale)) continue; has_locale = g_strcmp0 (locale, "C") != 0; if (as_context_get_style (ctx) == AS_FORMAT_STYLE_METAINFO) { for (guint i = 0; i < kws->len; i++) { xmlNode *kw_node = as_xml_add_text_node ( mi_kw_node, "keyword", (const gchar *) g_ptr_array_index (kws, i)); if (has_locale) as_xml_add_text_prop (kw_node, "xml:lang", locale); } } else { xmlNode *kws_node = as_xml_add_node_list (root, "keywords", "keyword", kws); if (kws_node == NULL) continue; if (has_locale) as_xml_add_text_prop (kws_node, "xml:lang", locale); } } } /** * as_component_xml_serialize_provides: */ static void as_component_xml_serialize_provides (AsComponent *cpt, xmlNode *cnode) { AsComponentPrivate *priv = GET_PRIVATE (cpt); xmlNode *node; GPtrArray *items; if (priv->provided->len == 0) return; node = as_xml_add_node (cnode, "provides"); for (guint i = 0; i < priv->provided->len; i++) { guint j; AsProvided *prov = AS_PROVIDED (g_ptr_array_index (priv->provided, i)); items = as_provided_get_items (prov); switch (as_provided_get_kind (prov)) { case AS_PROVIDED_KIND_ID: as_xml_add_node_list (node, NULL, "id", items); break; case AS_PROVIDED_KIND_MEDIATYPE: as_xml_add_node_list (node, NULL, "mediatype", items); break; case AS_PROVIDED_KIND_LIBRARY: as_xml_add_node_list (node, NULL, "library", items); break; case AS_PROVIDED_KIND_BINARY: as_xml_add_node_list (node, NULL, "binary", items); break; case AS_PROVIDED_KIND_MODALIAS: as_xml_add_node_list (node, NULL, "modalias", items); break; case AS_PROVIDED_KIND_PYTHON: as_xml_add_node_list (node, NULL, "python3", items); break; case AS_PROVIDED_KIND_FONT: as_xml_add_node_list (node, NULL, "font", items); break; case AS_PROVIDED_KIND_FIRMWARE_RUNTIME: for (j = 0; j < items->len; j++) { xmlNode *n; n = as_xml_add_text_node (node, "firmware", g_ptr_array_index (items, j)); as_xml_add_text_prop (n, "type", "runtime"); } break; case AS_PROVIDED_KIND_FIRMWARE_FLASHED: for (j = 0; j < items->len; j++) { xmlNode *n; n = as_xml_add_text_node (node, "firmware", g_ptr_array_index (items, j)); as_xml_add_text_prop (n, "type", "flashed"); } break; case AS_PROVIDED_KIND_DBUS_SYSTEM: for (j = 0; j < items->len; j++) { xmlNode *n; n = as_xml_add_text_node (node, "dbus", g_ptr_array_index (items, j)); as_xml_add_text_prop (n, "type", "system"); } break; case AS_PROVIDED_KIND_DBUS_USER: for (j = 0; j < items->len; j++) { xmlNode *n; n = as_xml_add_text_node (node, "dbus", g_ptr_array_index (items, j)); as_xml_add_text_prop (n, "type", "user"); } break; default: g_debug ("Couldn't serialize provided-item type '%s'", as_provided_kind_to_string (as_provided_get_kind (prov))); break; } } } /** * as_component_xml_serialize_languages: */ static void as_component_xml_serialize_languages (AsComponent *cpt, xmlNode *cptnode) { AsComponentPrivate *priv = GET_PRIVATE (cpt); xmlNode *node; g_autoptr(GList) keys = NULL; GList *link; if (g_hash_table_size (priv->languages) == 0) return; node = as_xml_add_node (cptnode, "languages"); keys = g_hash_table_get_keys (priv->languages); keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); for (link = keys; link != NULL; link = link->next) { guint percentage; const gchar *locale; xmlNode *l_node; g_autofree gchar *percentage_str = NULL; locale = (const gchar *) link->data; percentage = GPOINTER_TO_INT (g_hash_table_lookup (priv->languages, locale)); percentage_str = g_strdup_printf ("%i", percentage); l_node = as_xml_add_text_node (node, "lang", locale); as_xml_add_text_prop (l_node, "percentage", percentage_str); } } /** * as_component_to_xml_node: * @cpt: an #AsComponent. * @ctx: the AppStream document context. * @root: XML node to attach the new nodes to. * * Serializes the data to an XML node. **/ xmlNode * as_component_to_xml_node (AsComponent *cpt, AsContext *ctx, xmlNode *root) { AsComponentPrivate *priv = GET_PRIVATE (cpt); AsFormatStyle format_style; xmlNode *cnode; format_style = as_context_get_style (ctx); /* define component root node properties */ if (root == NULL) cnode = xmlNewNode (NULL, (xmlChar *) "component"); else cnode = as_xml_add_node (root, "component"); if ((priv->kind != AS_COMPONENT_KIND_GENERIC) && (priv->kind != AS_COMPONENT_KIND_UNKNOWN)) { as_xml_add_text_prop (cnode, "type", as_component_kind_to_string (priv->kind)); } /* set end-of-life date */ if (priv->date_eol != NULL) { g_autofree gchar *time_str = as_component_sanitize_date_eol (cpt); if (time_str == NULL) g_debug ("Invalid ISO-8601 date_eol for component %s", as_component_get_data_id (cpt)); else as_xml_add_text_prop (cnode, "date_eol", time_str); } if (format_style == AS_FORMAT_STYLE_CATALOG) { /* write some propties which only exist in catalog XML */ if (priv->merge_kind != AS_MERGE_KIND_NONE) { as_xml_add_text_prop (cnode, "merge", as_merge_kind_to_string (priv->merge_kind)); } if (priv->priority != 0) { g_autofree gchar *priority_str = g_strdup_printf ("%i", priv->priority); as_xml_add_text_prop (cnode, "priority", priority_str); } } /* component tags */ as_xml_add_text_node (cnode, "id", as_component_get_id (cpt)); /* name */ as_xml_add_localized_text_node (cnode, "name", priv->name); /* name variant suffix */ if (priv->name_variant_suffix != NULL) as_xml_add_localized_text_node (cnode, "name_variant_suffix", priv->name_variant_suffix); /* summary */ as_xml_add_localized_text_node (cnode, "summary", priv->summary); /* order license and project group after name/summary */ if (format_style == AS_FORMAT_STYLE_METAINFO) as_xml_add_text_node (cnode, "metadata_license", priv->metadata_license); /* project license */ as_xml_add_text_node (cnode, "project_license", priv->project_license); /* long description */ as_xml_add_description_node (ctx, cnode, priv->description, TRUE); /* project group */ as_xml_add_text_node (cnode, "project_group", priv->project_group); /* developer */ if (priv->developer != NULL) { as_developer_to_xml_node (priv->developer, ctx, cnode); if (format_style == AS_FORMAT_STYLE_CATALOG && as_context_get_format_version (ctx) <= AS_FORMAT_VERSION_V1_0) { /* Add the deprecated tag for now, for improved backwards compatibility */ as_xml_add_text_node ( cnode, "developer_name", g_hash_table_lookup (as_developer_get_name_table (priv->developer), "C")); } } /* extends nodes */ as_xml_add_node_list (cnode, NULL, "extends", priv->extends); /* replaces */ as_xml_add_node_list (cnode, "replaces", "id", priv->replaces); /* requires */ if (priv->requires->len > 0) { xmlNode *rqnode = as_xml_add_node (cnode, "requires"); for (guint i = 0; i < priv->requires->len; i++) { AsRelation *relation = AS_RELATION (g_ptr_array_index (priv->requires, i)); as_relation_to_xml_node (relation, ctx, rqnode); } } /* recommends */ if (priv->recommends->len > 0) { xmlNode *rcnode = as_xml_add_node (cnode, "recommends"); for (guint i = 0; i < priv->recommends->len; i++) { AsRelation *relation = AS_RELATION ( g_ptr_array_index (priv->recommends, i)); as_relation_to_xml_node (relation, ctx, rcnode); } } /* supports */ if (priv->supports->len > 0) { xmlNode *rcnode = as_xml_add_node (cnode, "supports"); for (guint i = 0; i < priv->supports->len; i++) { AsRelation *relation = AS_RELATION (g_ptr_array_index (priv->supports, i)); as_relation_to_xml_node (relation, ctx, rcnode); } } /* suggests nodes */ for (guint i = 0; i < priv->suggestions->len; i++) { AsSuggested *suggested = AS_SUGGESTED (g_ptr_array_index (priv->suggestions, i)); as_suggested_to_xml_node (suggested, ctx, cnode); } /* package name(s) (if any) */ as_xml_add_node_list_strv (cnode, NULL, "pkgname", priv->pkgnames); /* bundles */ for (guint i = 0; i < priv->bundles->len; i++) { AsBundle *bundle = AS_BUNDLE (g_ptr_array_index (priv->bundles, i)); as_bundle_to_xml_node (bundle, ctx, cnode); } /* launchables */ for (guint i = 0; i < priv->launchables->len; i++) { AsLaunchable *launchable = AS_LAUNCHABLE (g_ptr_array_index (priv->launchables, i)); as_launchable_to_xml_node (launchable, ctx, cnode); } /* compulsory for desktop(s) */ as_xml_add_node_list (cnode, NULL, "compulsory_for_desktop", priv->compulsory_for_desktops); /* icons */ for (guint i = 0; i < priv->icons->len; i++) { AsIcon *icon = AS_ICON (g_ptr_array_index (priv->icons, i)); as_icon_to_xml_node (icon, ctx, cnode); } /* urls */ for (guint i = AS_URL_KIND_UNKNOWN; i < AS_URL_KIND_LAST; i++) { xmlNode *n; const gchar *value; value = as_component_get_url (cpt, i); if (value == NULL) continue; n = as_xml_add_text_node (cnode, "url", value); as_xml_add_text_prop (n, "type", as_url_kind_to_string (i)); } /* software sorting catgories */ as_xml_add_node_list (cnode, "categories", "category", priv->categories); /* provides node */ as_component_xml_serialize_provides (cpt, cnode); /* translations */ if (priv->translations != NULL) { for (guint i = 0; i < priv->translations->len; i++) { AsTranslation *tr = AS_TRANSLATION ( g_ptr_array_index (priv->translations, i)); as_translation_to_xml_node (tr, ctx, cnode); } } /* languages node */ as_component_xml_serialize_languages (cpt, cnode); /* screenshots */ if (priv->screenshots->len > 0) { xmlNode *rnode = as_xml_add_node (cnode, "screenshots"); /* restore default screenshot order */ as_component_sort_screenshots (cpt, NULL, NULL, FALSE); for (guint i = 0; i < priv->screenshots->len; i++) { AsScreenshot *scr = AS_SCREENSHOT ( g_ptr_array_index (priv->screenshots, i)); as_screenshot_to_xml_node (scr, ctx, rnode); } } /* keywords */ as_component_xml_keywords_to_node (cpt, ctx, cnode); /* agreements */ for (guint i = 0; i < priv->agreements->len; i++) { AsAgreement *agreement = AS_AGREEMENT (g_ptr_array_index (priv->agreements, i)); as_agreement_to_xml_node (agreement, ctx, cnode); } /* branding */ if (priv->branding != NULL) as_branding_to_xml_node (priv->branding, ctx, cnode); /* releases */ as_release_list_to_xml_node (priv->releases, ctx, cnode); /* content_rating nodes */ for (guint i = 0; i < priv->content_ratings->len; i++) { AsContentRating *ctrating = AS_CONTENT_RATING ( g_ptr_array_index (priv->content_ratings, i)); as_content_rating_to_xml_node (ctrating, ctx, cnode); } /* tags */ if (priv->tags->len > 0) { xmlNode *tags_node = as_xml_add_node (cnode, "tags"); for (guint i = 0; i < priv->tags->len; i++) { xmlNode *tag_node = NULL; g_auto(GStrv) parts = g_strsplit (g_ptr_array_index (priv->tags, i), "::", 2); tag_node = as_xml_add_text_node (tags_node, "tag", parts[1]); if (!as_is_empty (parts[0])) as_xml_add_text_prop (tag_node, "namespace", parts[0]); } } /* references */ if (priv->references->len > 0) { xmlNode *refs_node = as_xml_add_node (cnode, "references"); for (guint i = 0; i < priv->references->len; i++) { AsReference *ref = AS_REFERENCE (g_ptr_array_index (priv->references, i)); as_reference_to_xml_node (ref, ctx, refs_node); } } /* custom node */ as_xml_add_custom_node (cnode, "custom", priv->custom); /* reviews */ if (priv->reviews->len > 0) { xmlNode *rnode = as_xml_add_node (cnode, "reviews"); for (guint i = 0; i < priv->reviews->len; i++) { AsReview *review = AS_REVIEW (g_ptr_array_index (priv->reviews, i)); as_review_to_xml_node (review, ctx, rnode); } } /* internal information */ if (as_context_get_internal_mode (ctx)) { const gchar *origin = as_component_get_origin (cpt); if (priv->scope != AS_COMPONENT_SCOPE_UNKNOWN) as_xml_add_text_node (cnode, "_asi_scope", as_component_scope_to_string (priv->scope)); if (origin != NULL) as_xml_add_text_node (cnode, "_asi_origin", origin); if (priv->branch != NULL) as_xml_add_text_node (cnode, "_asi_branch", priv->branch); } return cnode; } /** * as_component_yaml_parse_keywords: * * Process a keywords node and add the data to an #AsComponent */ static void as_component_yaml_parse_keywords (AsComponent *cpt, AsContext *ctx, GNode *node) { GNode *tnode; for (tnode = node->children; tnode != NULL; tnode = tnode->next) { const gchar *locale = as_yaml_get_node_locale (ctx, tnode); if (locale != NULL) { g_autoptr(GPtrArray) keywords = NULL; keywords = g_ptr_array_new_with_free_func (g_free); as_yaml_list_to_str_array (tnode, keywords); as_component_set_keywords (cpt, keywords, locale, FALSE); } } } /** * as_component_yaml_parse_urls: */ static void as_component_yaml_parse_urls (AsComponent *cpt, GNode *node) { AsUrlKind url_kind; for (GNode *n = node->children; n != NULL; n = n->next) { const gchar *key = as_yaml_node_get_key (n); const gchar *value = as_yaml_node_get_value (n); url_kind = as_url_kind_from_string (key); if ((url_kind != AS_URL_KIND_UNKNOWN) && (value != NULL)) as_component_add_url (cpt, url_kind, value); } } /** * as_component_yaml_parse_icon: */ static void as_component_yaml_parse_icon (AsComponent *cpt, AsContext *ctx, GNode *node, AsIconKind kind) { GNode *n; guint64 size; guint scale; g_autoptr(AsIcon) icon = NULL; icon = as_icon_new (); as_icon_set_kind (icon, kind); for (n = node->children; n != NULL; n = n->next) { const gchar *key = as_yaml_node_get_key (n); const gchar *value = as_yaml_node_get_value (n); if (g_strcmp0 (key, "width") == 0) { size = g_ascii_strtoull (value, NULL, 10); as_icon_set_width (icon, size); } else if (g_strcmp0 (key, "height") == 0) { size = g_ascii_strtoull (value, NULL, 10); as_icon_set_height (icon, size); } else if (g_strcmp0 (key, "scale") == 0) { scale = g_ascii_strtoull (value, NULL, 10); as_icon_set_scale (icon, scale); } else { if (kind == AS_ICON_KIND_REMOTE) { if (g_strcmp0 (key, "url") == 0) { if (as_context_has_media_baseurl (ctx)) { /* handle the media baseurl */ g_autofree gchar *url = NULL; url = g_build_filename ( as_context_get_media_baseurl (ctx), value, NULL); as_icon_set_url (icon, url); } else { /* no baseurl, we can just set the value as URL */ as_icon_set_url (icon, value); } } } else { if (g_strcmp0 (key, "name") == 0) { as_icon_set_filename (icon, value); } } } } as_component_add_icon (cpt, icon); } /** * as_component_yaml_parse_icons: */ static void as_component_yaml_parse_icons (AsComponent *cpt, AsContext *ctx, GNode *node) { GNode *n; for (n = node->children; n != NULL; n = n->next) { const gchar *key = as_yaml_node_get_key (n); const gchar *value = as_yaml_node_get_value (n); if (g_strcmp0 (key, "stock") == 0) { g_autoptr(AsIcon) icon = as_icon_new (); as_icon_set_kind (icon, AS_ICON_KIND_STOCK); as_icon_set_name (icon, value); as_component_add_icon (cpt, icon); } else if (g_strcmp0 (key, "cached") == 0) { if (value != NULL) { g_autoptr(AsIcon) icon = as_icon_new (); /* we have a legacy YAML file */ as_icon_set_kind (icon, AS_ICON_KIND_CACHED); as_icon_set_filename (icon, value); as_component_add_icon (cpt, icon); } else { GNode *sn; /* we have a recent YAML file */ for (sn = n->children; sn != NULL; sn = sn->next) as_component_yaml_parse_icon (cpt, ctx, sn, AS_ICON_KIND_CACHED); } } else if (g_strcmp0 (key, "local") == 0) { GNode *sn; for (sn = n->children; sn != NULL; sn = sn->next) as_component_yaml_parse_icon (cpt, ctx, sn, AS_ICON_KIND_LOCAL); } else if (g_strcmp0 (key, "remote") == 0) { GNode *sn; for (sn = n->children; sn != NULL; sn = sn->next) as_component_yaml_parse_icon (cpt, ctx, sn, AS_ICON_KIND_REMOTE); } } } /** * as_component_yaml_parse_provides: */ static void as_component_yaml_parse_provides (AsComponent *cpt, GNode *node) { GNode *n; GNode *sn; for (n = node->children; n != NULL; n = n->next) { const gchar *key = as_yaml_node_get_key (n); if (g_strcmp0 (key, "ids") == 0) { for (sn = n->children; sn != NULL; sn = sn->next) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_ID, (gchar *) sn->data); } } else if (g_strcmp0 (key, "libraries") == 0) { for (sn = n->children; sn != NULL; sn = sn->next) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_LIBRARY, (gchar *) sn->data); } } else if (g_strcmp0 (key, "binaries") == 0) { for (sn = n->children; sn != NULL; sn = sn->next) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_BINARY, (gchar *) sn->data); } } else if (g_strcmp0 (key, "fonts") == 0) { GNode *dn; for (sn = n->children; sn != NULL; sn = sn->next) { for (dn = sn->children; dn != NULL; dn = dn->next) { gchar *dvalue = NULL; const gchar *dkey = (const gchar *) dn->data; if (dn->children) dvalue = (gchar *) dn->children->data; if (dvalue == NULL) continue; if (g_strcmp0 (dkey, "name") == 0) as_component_add_provided_item ( cpt, AS_PROVIDED_KIND_FONT, dvalue); } } } else if (g_strcmp0 (key, "modaliases") == 0) { for (sn = n->children; sn != NULL; sn = sn->next) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_MODALIAS, (gchar *) sn->data); } } else if (g_strcmp0 (key, "firmware") == 0) { GNode *dn; for (sn = n->children; sn != NULL; sn = sn->next) { gchar *kind = NULL; gchar *fwdata = NULL; for (dn = sn->children; dn != NULL; dn = dn->next) { gchar *dvalue; const gchar *dkey = (const gchar *) dn->data; if (dn->children) dvalue = (gchar *) dn->children->data; else continue; if (g_strcmp0 (dkey, "type") == 0) { kind = dvalue; } else if ((g_strcmp0 (dkey, "guid") == 0) || (g_strcmp0 (dkey, "file") == 0)) { fwdata = dvalue; } } /* we don't add malformed provides types */ if ((kind == NULL) || (fwdata == NULL)) continue; if (g_strcmp0 (kind, "runtime") == 0) as_component_add_provided_item ( cpt, AS_PROVIDED_KIND_FIRMWARE_RUNTIME, fwdata); else if (g_strcmp0 (kind, "flashed") == 0) as_component_add_provided_item ( cpt, AS_PROVIDED_KIND_FIRMWARE_FLASHED, fwdata); } } else if (g_strcmp0 (key, "python3") == 0) { for (sn = n->children; sn != NULL; sn = sn->next) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_PYTHON, (gchar *) sn->data); } } else if (g_strcmp0 (key, "mediatypes") == 0) { for (sn = n->children; sn != NULL; sn = sn->next) { as_component_add_provided_item (cpt, AS_PROVIDED_KIND_MEDIATYPE, (gchar *) sn->data); } } else if (g_strcmp0 (key, "dbus") == 0) { GNode *dn; for (sn = n->children; sn != NULL; sn = sn->next) { gchar *kind = NULL; gchar *service = NULL; for (dn = sn->children; dn != NULL; dn = dn->next) { gchar *dvalue = NULL; const gchar *dkey = (const gchar *) dn->data; if (dn->children) dvalue = (gchar *) dn->children->data; if (g_strcmp0 (dkey, "type") == 0) { kind = dvalue; } else if (g_strcmp0 (dkey, "service") == 0) { service = dvalue; } } /* we don't add malformed provides types */ if ((kind == NULL) || (service == NULL)) continue; if (g_strcmp0 (kind, "system") == 0) as_component_add_provided_item ( cpt, AS_PROVIDED_KIND_DBUS_SYSTEM, service); else if ((g_strcmp0 (kind, "user") == 0) || (g_strcmp0 (kind, "session") == 0)) as_component_add_provided_item (cpt, AS_PROVIDED_KIND_DBUS_USER, service); } } } } /** * as_component_yaml_parse_languages: */ static void as_component_yaml_parse_languages (AsComponent *cpt, GNode *node) { GNode *sn; for (sn = node->children; sn != NULL; sn = sn->next) { GNode *n; g_autofree gchar *locale = NULL; g_autofree gchar *percentage_str = NULL; for (n = sn->children; n != NULL; n = n->next) { const gchar *key = as_yaml_node_get_key (n); const gchar *value = as_yaml_node_get_value (n); if (g_strcmp0 (key, "locale") == 0) { if (locale == NULL) locale = g_strdup (value); } else if (g_strcmp0 (key, "percentage") == 0) { if (percentage_str == NULL) percentage_str = g_strdup (value); } else { as_yaml_print_unknown ("Languages", key); } } if ((locale != NULL) && (percentage_str != NULL)) as_component_add_language (cpt, locale, g_ascii_strtoll (percentage_str, NULL, 10)); } } /** * as_component_yaml_parse_relations: */ static void as_component_yaml_parse_relations (AsComponent *cpt, AsContext *ctx, GNode *node, AsRelationKind kind) { for (GNode *n = node->children; n != NULL; n = n->next) { g_autoptr(AsRelation) relation = as_relation_new (); as_relation_set_kind (relation, kind); if (as_relation_load_from_yaml (relation, ctx, n, NULL)) as_component_add_relation (cpt, relation); } } /** * as_component_yaml_parse_custom: */ static void as_component_yaml_parse_custom (AsComponent *cpt, GNode *node) { AsComponentPrivate *priv = GET_PRIVATE (cpt); for (GNode *sn = node->children; sn != NULL; sn = sn->next) { g_hash_table_insert (priv->custom, as_yaml_node_get_key_refstr (sn), as_yaml_node_get_value_refstr (sn)); } } /** * as_component_load_from_yaml: * @cpt: an #AsComponent. * @ctx: the AppStream document context. * @root: the YAML node. * @error: a #GError. * * Loads data from a YAML field. **/ gboolean as_component_load_from_yaml (AsComponent *cpt, AsContext *ctx, GNode *root, GError **error) { AsComponentPrivate *priv = GET_PRIVATE (cpt); GNode *node; /* set context for this component */ as_component_set_context (cpt, ctx); /* set component default priority */ priv->priority = as_context_get_priority (ctx); for (node = root->children; node != NULL; node = node->next) { const gchar *key; const gchar *value; AsTag field_id; if (node->children == NULL) continue; key = as_yaml_node_get_key (node); value = as_yaml_node_get_value (node); field_id = as_yaml_tag_from_string (key); if (field_id == AS_TAG_TYPE) { if (g_strcmp0 (value, "generic") == 0) priv->kind = AS_COMPONENT_KIND_GENERIC; else priv->kind = as_component_kind_from_string (value); } else if (field_id == AS_TAG_ID) { as_component_set_id (cpt, value); } else if (field_id == AS_TAG_PRIORITY) { priv->priority = g_ascii_strtoll (value, NULL, 10); } else if (field_id == AS_TAG_MERGE) { priv->merge_kind = as_merge_kind_from_string (value); } else if (field_id == AS_TAG_DATE_EOL) { as_component_set_date_eol (cpt, value); } else if (field_id == AS_TAG_PKGNAME) { g_strfreev (priv->pkgnames); priv->pkgnames = g_new0 (gchar *, 1 + 1); priv->pkgnames[0] = g_strdup (value); priv->pkgnames[1] = NULL; g_object_notify ((GObject *) cpt, "pkgnames"); } else if (field_id == AS_TAG_SOURCE_PKGNAME) { as_component_set_source_pkgname (cpt, value); } else if (field_id == AS_TAG_NAME) { as_yaml_set_localized_table (ctx, node, priv->name); g_object_notify ((GObject *) cpt, "name"); } else if (field_id == AS_TAG_SUMMARY) { as_yaml_set_localized_table (ctx, node, priv->summary); g_object_notify ((GObject *) cpt, "summary"); } else if (field_id == AS_TAG_DESCRIPTION) { as_yaml_set_localized_table (ctx, node, priv->description); g_object_notify ((GObject *) cpt, "description"); } else if (field_id == AS_TAG_DEVELOPER) { g_autoptr(AsDeveloper) developer = as_developer_new (); if (as_developer_load_from_yaml (developer, ctx, node, NULL)) { if (priv->developer != NULL) g_object_unref (priv->developer); priv->developer = g_steal_pointer (&developer); } } else if (field_id == AS_TAG_DEVELOPER_NAME) { /* DEPRECATED */ if (priv->developer == NULL) priv->developer = as_developer_new_with_context (ctx); as_yaml_set_localized_table (ctx, node, as_developer_get_name_table (priv->developer)); } else if (field_id == AS_TAG_PROJECT_LICENSE) { as_component_set_project_license (cpt, value); } else if (field_id == AS_TAG_PROJECT_GROUP) { as_component_set_project_group (cpt, value); } else if (field_id == AS_TAG_CATEGORIES) { as_yaml_list_to_str_array (node, priv->categories); } else if (field_id == AS_TAG_COMPULSORY_FOR_DESKTOP) { as_yaml_list_to_str_array (node, priv->compulsory_for_desktops); } else if (field_id == AS_TAG_EXTENDS) { as_yaml_list_to_str_array (node, priv->extends); } else if (field_id == AS_TAG_KEYWORDS) { as_component_yaml_parse_keywords (cpt, ctx, node); } else if (field_id == AS_TAG_URL) { as_component_yaml_parse_urls (cpt, node); } else if (field_id == AS_TAG_ICON) { as_component_yaml_parse_icons (cpt, ctx, node); } else if (field_id == AS_TAG_BUNDLE) { for (GNode *n = node->children; n != NULL; n = n->next) { g_autoptr(AsBundle) bundle = as_bundle_new (); if (as_bundle_load_from_yaml (bundle, ctx, n, NULL)) as_component_add_bundle (cpt, bundle); } } else if (field_id == AS_TAG_LAUNCHABLE) { for (GNode *n = node->children; n != NULL; n = n->next) { g_autoptr(AsLaunchable) launch = as_launchable_new (); if (as_launchable_load_from_yaml (launch, ctx, n, NULL)) as_component_add_launchable (cpt, launch); } } else if (field_id == AS_TAG_PROVIDES) { as_component_yaml_parse_provides (cpt, node); } else if (field_id == AS_TAG_SCREENSHOTS) { for (GNode *n = node->children; n != NULL; n = n->next) { g_autoptr(AsScreenshot) scr = as_screenshot_new (); if (as_screenshot_load_from_yaml (scr, ctx, n, NULL)) as_component_add_screenshot (cpt, scr); } } else if (field_id == AS_TAG_LANGUAGES) { as_component_yaml_parse_languages (cpt, node); } else if (field_id == AS_TAG_RELEASES) { as_release_list_load_from_yaml (priv->releases, ctx, node, NULL); } else if (field_id == AS_TAG_SUGGESTS) { for (GNode *n = node->children; n != NULL; n = n->next) { g_autoptr(AsSuggested) suggested = as_suggested_new (); if (as_suggested_load_from_yaml (suggested, ctx, n, NULL)) as_component_add_suggested (cpt, suggested); } } else if (field_id == AS_TAG_CONTENT_RATING) { for (GNode *n = node->children; n != NULL; n = n->next) { g_autoptr(AsContentRating) rating = as_content_rating_new (); if (as_content_rating_load_from_yaml (rating, ctx, n, NULL)) as_component_add_content_rating (cpt, rating); } } else if (field_id == AS_TAG_REPLACES) { if (priv->replaces != NULL) g_ptr_array_unref (priv->replaces); priv->replaces = g_ptr_array_new_with_free_func (g_free); for (GNode *n = node->children; n != NULL; n = n->next) { for (GNode *sn = n->children; sn != NULL; sn = sn->next) { if (g_strcmp0 (as_yaml_node_get_key (sn), "id") != 0) continue; g_ptr_array_add (priv->replaces, g_strdup (as_yaml_node_get_value (sn))); } } } else if (field_id == AS_TAG_REQUIRES) { as_component_yaml_parse_relations (cpt, ctx, node, AS_RELATION_KIND_REQUIRES); } else if (field_id == AS_TAG_RECOMMENDS) { as_component_yaml_parse_relations (cpt, ctx, node, AS_RELATION_KIND_RECOMMENDS); } else if (field_id == AS_TAG_SUPPORTS) { as_component_yaml_parse_relations (cpt, ctx, node, AS_RELATION_KIND_SUPPORTS); } else if (field_id == AS_TAG_AGREEMENT) { for (GNode *n = node->children; n != NULL; n = n->next) { g_autoptr(AsAgreement) agreement = as_agreement_new (); if (as_agreement_load_from_yaml (agreement, ctx, n, NULL)) as_component_add_agreement (cpt, agreement); } } else if (field_id == AS_TAG_BRANDING) { g_autoptr(AsBranding) branding = as_branding_new (); if (as_branding_load_from_yaml (branding, ctx, node, NULL)) as_component_set_branding (cpt, branding); } else if (field_id == AS_TAG_NAME_VARIANT_SUFFIX) { if (priv->name_variant_suffix != NULL) g_hash_table_unref (priv->name_variant_suffix); priv->name_variant_suffix = g_hash_table_new_full ( g_str_hash, g_str_equal, (GDestroyNotify) as_ref_string_release, g_free); as_yaml_set_localized_table (ctx, node, priv->name_variant_suffix); } else if (field_id == AS_TAG_TAGS) { for (GNode *tags_n = node->children; tags_n != NULL; tags_n = tags_n->next) { const gchar *ns = NULL; const gchar *tag_value = NULL; for (GNode *tag_n = tags_n->children; tag_n != NULL; tag_n = tag_n->next) { const gchar *c_key = as_yaml_node_get_key (tag_n); const gchar *c_value = as_yaml_node_get_value (tag_n); if (g_strcmp0 (c_key, "namespace") == 0) ns = c_value; else if (g_strcmp0 (c_key, "tag") == 0) tag_value = c_value; } as_component_add_tag (cpt, ns, tag_value); } } else if (field_id == AS_TAG_REFERENCES) { for (GNode *n = node->children; n != NULL; n = n->next) { g_autoptr(AsReference) reference = as_reference_new (); if (as_reference_load_from_yaml (reference, ctx, n, NULL)) as_component_add_reference (cpt, reference); } } else if (field_id == AS_TAG_CUSTOM) { as_component_yaml_parse_custom (cpt, node); } else if (field_id == AS_TAG_REVIEWS) { for (GNode *n = node->children; n != NULL; n = n->next) { g_autoptr(AsReview) review = as_review_new (); if (as_review_load_from_yaml (review, ctx, n, NULL)) as_component_add_review (cpt, review); } } else { as_yaml_print_unknown ("root", key); } } /* sanity check */ if (as_is_empty (priv->id)) { g_set_error_literal (error, AS_METADATA_ERROR, AS_METADATA_ERROR_FAILED, "Component is invalid (essential tags are missing or empty)."); return FALSE; } return TRUE; } /** * as_component_yaml_emit_icons: */ static void as_component_yaml_emit_icons (AsComponent *cpt, yaml_emitter_t *emitter, GPtrArray *icons) { guint i; GHashTableIter iter; gpointer key, value; gboolean stock_icon_added = FALSE; g_autoptr(GHashTable) icons_table = NULL; /* we need to emit the icons in order, so we first need to sort them properly */ icons_table = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_ptr_array_unref); for (i = 0; i < icons->len; i++) { GPtrArray *ilist; AsIconKind ikind; AsIcon *icon = AS_ICON (g_ptr_array_index (icons, i)); ikind = as_icon_get_kind (icon); ilist = g_hash_table_lookup (icons_table, GINT_TO_POINTER (ikind)); if (ilist == NULL) { ilist = g_ptr_array_new (); g_hash_table_insert (icons_table, GINT_TO_POINTER (ikind), ilist); } g_ptr_array_add (ilist, icon); } g_hash_table_iter_init (&iter, icons_table); while (g_hash_table_iter_next (&iter, &key, &value)) { GPtrArray *ilist; AsIconKind ikind; ikind = (AsIconKind) GPOINTER_TO_INT (key); ilist = (GPtrArray *) value; if (ikind == AS_ICON_KIND_STOCK) { /* there can always be only one stock icon, so this is easy */ if (!stock_icon_added) as_yaml_emit_entry ( emitter, as_icon_kind_to_string (ikind), as_icon_get_name (AS_ICON (g_ptr_array_index (ilist, 0)))); stock_icon_added = TRUE; } else { as_yaml_emit_scalar (emitter, as_icon_kind_to_string (ikind)); as_yaml_sequence_start (emitter); for (i = 0; i < ilist->len; i++) { AsIcon *icon = AS_ICON (g_ptr_array_index (ilist, i)); as_yaml_mapping_start (emitter); if (ikind == AS_ICON_KIND_REMOTE) as_yaml_emit_entry (emitter, "url", as_icon_get_url (icon)); else if (ikind == AS_ICON_KIND_LOCAL) as_yaml_emit_entry (emitter, "name", as_icon_get_filename (icon)); else as_yaml_emit_entry (emitter, "name", as_icon_get_name (icon)); if (as_icon_get_width (icon) > 0) { as_yaml_emit_entry_uint64 (emitter, "width", as_icon_get_width (icon)); } if (as_icon_get_height (icon) > 0) { as_yaml_emit_entry_uint64 (emitter, "height", as_icon_get_height (icon)); } if (as_icon_get_scale (icon) > 1) { as_yaml_emit_entry_uint64 (emitter, "scale", as_icon_get_scale (icon)); } as_yaml_mapping_end (emitter); } as_yaml_sequence_end (emitter); } } } /** * as_component_yaml_emit_provides: */ static void as_component_yaml_emit_provides (AsComponent *cpt, yaml_emitter_t *emitter) { AsComponentPrivate *priv = GET_PRIVATE (cpt); guint i; g_autoptr(GPtrArray) dbus_system = NULL; g_autoptr(GPtrArray) dbus_user = NULL; g_autoptr(GPtrArray) fw_runtime = NULL; g_autoptr(GPtrArray) fw_flashed = NULL; if (priv->provided->len == 0) return; as_yaml_emit_scalar (emitter, "Provides"); as_yaml_mapping_start (emitter); for (i = 0; i < priv->provided->len; i++) { AsProvidedKind kind; GPtrArray *items; guint j; AsProvided *prov = AS_PROVIDED (g_ptr_array_index (priv->provided, i)); items = as_provided_get_items (prov); if (items->len == 0) continue; kind = as_provided_get_kind (prov); switch (kind) { case AS_PROVIDED_KIND_ID: as_yaml_emit_sequence (emitter, "ids", items); break; case AS_PROVIDED_KIND_LIBRARY: as_yaml_emit_sequence (emitter, "libraries", items); break; case AS_PROVIDED_KIND_BINARY: as_yaml_emit_sequence (emitter, "binaries", items); break; case AS_PROVIDED_KIND_MEDIATYPE: as_yaml_emit_sequence (emitter, "mediatypes", items); break; case AS_PROVIDED_KIND_PYTHON: as_yaml_emit_sequence (emitter, "python3", items); break; case AS_PROVIDED_KIND_MODALIAS: as_yaml_emit_sequence (emitter, "modaliases", items); break; case AS_PROVIDED_KIND_FONT: as_yaml_emit_scalar (emitter, "fonts"); as_yaml_sequence_start (emitter); for (j = 0; j < items->len; j++) { as_yaml_mapping_start (emitter); as_yaml_emit_entry (emitter, "name", (const gchar *) g_ptr_array_index (items, j)); as_yaml_mapping_end (emitter); } as_yaml_sequence_end (emitter); break; case AS_PROVIDED_KIND_DBUS_SYSTEM: if (dbus_system == NULL) { dbus_system = g_ptr_array_ref (items); } else { g_critical ("Hit dbus:system twice, this should never happen!"); } break; case AS_PROVIDED_KIND_DBUS_USER: if (dbus_user == NULL) { dbus_user = g_ptr_array_ref (items); } else { g_critical ("Hit dbus:user twice, this should never happen!"); } break; case AS_PROVIDED_KIND_FIRMWARE_RUNTIME: if (fw_runtime == NULL) { fw_runtime = g_ptr_array_ref (items); } else { g_critical ( "Hit firmware:runtime twice, this should never happen!"); } break; case AS_PROVIDED_KIND_FIRMWARE_FLASHED: if (fw_flashed == NULL) { fw_flashed = g_ptr_array_ref (items); } else { g_critical ("Hit dbus-user twice, this should never happen!"); } break; default: g_warning ("Ignoring unknown type of provided items: %s", as_provided_kind_to_string (kind)); break; } } /* dbus subsection */ if ((dbus_system != NULL) || (dbus_user != NULL)) { as_yaml_emit_scalar (emitter, "dbus"); as_yaml_sequence_start (emitter); if (dbus_system != NULL) { for (i = 0; i < dbus_system->len; i++) { const gchar *value = (const gchar *) g_ptr_array_index (dbus_system, i); as_yaml_mapping_start (emitter); as_yaml_emit_entry (emitter, "type", "system"); as_yaml_emit_entry (emitter, "service", value); as_yaml_mapping_end (emitter); } } if (dbus_user != NULL) { for (i = 0; i < dbus_user->len; i++) { const gchar *value = (const gchar *) g_ptr_array_index (dbus_user, i); as_yaml_mapping_start (emitter); as_yaml_emit_entry (emitter, "type", "user"); as_yaml_emit_entry (emitter, "service", value); as_yaml_mapping_end (emitter); } } as_yaml_sequence_end (emitter); } /* firmware subsection */ if ((fw_runtime != NULL) || (fw_flashed != NULL)) { as_yaml_emit_scalar (emitter, "firmware"); as_yaml_sequence_start (emitter); if (fw_runtime != NULL) { for (i = 0; i < fw_runtime->len; i++) { const gchar *value = g_ptr_array_index (fw_runtime, i); as_yaml_mapping_start (emitter); as_yaml_emit_entry (emitter, "type", "runtime"); as_yaml_emit_entry (emitter, "file", value); as_yaml_mapping_end (emitter); } } if (fw_flashed != NULL) { for (i = 0; i < fw_flashed->len; i++) { const gchar *value = g_ptr_array_index (fw_flashed, i); as_yaml_mapping_start (emitter); as_yaml_emit_entry (emitter, "type", "flashed"); as_yaml_emit_entry (emitter, "guid", value); as_yaml_mapping_end (emitter); } } as_yaml_sequence_end (emitter); } as_yaml_mapping_end (emitter); } /** * as_component_yaml_emit_languages: */ static void as_component_yaml_emit_languages (AsComponent *cpt, yaml_emitter_t *emitter) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autoptr(GList) keys = NULL; GList *link; if (g_hash_table_size (priv->languages) == 0) return; as_yaml_emit_scalar (emitter, "Languages"); as_yaml_sequence_start (emitter); keys = g_hash_table_get_keys (priv->languages); keys = g_list_sort (keys, (GCompareFunc) g_strcmp0); for (link = keys; link != NULL; link = link->next) { guint percentage; const gchar *locale; locale = (const gchar *) link->data; percentage = GPOINTER_TO_INT (g_hash_table_lookup (priv->languages, locale)); as_yaml_mapping_start (emitter); as_yaml_emit_entry (emitter, "locale", locale); as_yaml_emit_entry_uint64 (emitter, "percentage", percentage); as_yaml_mapping_end (emitter); } as_yaml_sequence_end (emitter); } /** * as_component_yaml_emit_custom: */ static void as_component_yaml_emit_custom (AsComponent *cpt, yaml_emitter_t *emitter) { AsComponentPrivate *priv = GET_PRIVATE (cpt); g_autoptr(GList) keys = NULL; GList *link; if (g_hash_table_size (priv->custom) == 0) return; as_yaml_emit_scalar (emitter, "Custom"); as_yaml_mapping_start (emitter); keys = g_hash_table_get_keys (priv->custom); keys = g_list_sort (keys, (GCompareFunc) g_ascii_strcasecmp); for (link = keys; link != NULL; link = link->next) { as_yaml_emit_entry (emitter, (const gchar *) link->data, (const gchar *) g_hash_table_lookup (priv->custom, link->data)); } as_yaml_mapping_end (emitter); } /** * as_component_emit_yaml: * @cpt: an #AsComponent. * @ctx: the AppStream document context. * @emitter: The YAML emitter to emit data on. * * Emit YAML data for this object. **/ void as_component_emit_yaml (AsComponent *cpt, AsContext *ctx, yaml_emitter_t *emitter) { AsComponentPrivate *priv = GET_PRIVATE (cpt); gint res; yaml_event_t event; /* new document for this component */ yaml_document_start_event_initialize (&event, NULL, NULL, NULL, FALSE); res = yaml_emitter_emit (emitter, &event); g_assert (res); /* open main mapping */ as_yaml_mapping_start (emitter); /* write component kind */ as_yaml_emit_entry (emitter, "Type", as_component_kind_to_string (priv->kind)); /* AppStream-ID */ as_yaml_emit_entry (emitter, "ID", priv->id); /* Priority */ if (priv->priority != 0) { g_autofree gchar *priority_str = g_strdup_printf ("%i", priv->priority); as_yaml_emit_entry (emitter, "Priority", priority_str); } /* merge strategy */ if (priv->merge_kind != AS_MERGE_KIND_NONE) { as_yaml_emit_entry (emitter, "Merge", as_merge_kind_to_string (priv->merge_kind)); } /* End-of-life date */ if (priv->date_eol != NULL) { g_autofree gchar *time_str = as_component_sanitize_date_eol (cpt); if (time_str == NULL) g_debug ("Invalid ISO-8601 date_eol for component %s", as_component_get_data_id (cpt)); else as_yaml_emit_entry (emitter, "DateEOL", time_str); } /* SourcePackage */ as_yaml_emit_entry (emitter, "SourcePackage", priv->source_pkgname); /* Package */ /* NOTE: a DEP-11 components do *not* support multiple packages per component */ if ((priv->pkgnames != NULL) && (priv->pkgnames[0] != NULL)) as_yaml_emit_entry (emitter, "Package", priv->pkgnames[0]); /* Extends */ as_yaml_emit_sequence (emitter, "Extends", priv->extends); /* ProjectLicense */ as_yaml_emit_entry (emitter, "ProjectLicense", priv->project_license); /* Name */ as_yaml_emit_localized_entry (emitter, "Name", priv->name); /* Summary */ as_yaml_emit_localized_entry (emitter, "Summary", priv->summary); /* Description */ as_yaml_emit_long_localized_entry (emitter, "Description", priv->description); /* NameVariantSuffix */ if (priv->name_variant_suffix != NULL) as_yaml_emit_localized_entry (emitter, "NameVariantSuffix", priv->name_variant_suffix); /* ProjectGroup */ as_yaml_emit_entry (emitter, "ProjectGroup", priv->project_group); /* Developer */ if (priv->developer != NULL) as_developer_emit_yaml (priv->developer, ctx, emitter); /* CompulsoryForDesktops */ as_yaml_emit_sequence_from_str_array (emitter, "CompulsoryForDesktops", priv->compulsory_for_desktops); /* Categories */ as_yaml_emit_sequence_from_str_array (emitter, "Categories", priv->categories); /* Keywords */ as_yaml_emit_localized_str_array (emitter, "Keywords", priv->keywords); /* Urls */ if (g_hash_table_size (priv->urls) > 0) { GHashTableIter iter; gpointer key, value; as_yaml_emit_scalar (emitter, "Url"); as_yaml_mapping_start (emitter); g_hash_table_iter_init (&iter, priv->urls); while (g_hash_table_iter_next (&iter, &key, &value)) { as_yaml_emit_entry (emitter, as_url_kind_to_string (GPOINTER_TO_INT (key)), (const gchar *) value); } as_yaml_mapping_end (emitter); } /* Icons */ if (priv->icons->len > 0) { as_yaml_emit_scalar (emitter, "Icon"); as_yaml_mapping_start (emitter); as_component_yaml_emit_icons (cpt, emitter, priv->icons); as_yaml_mapping_end (emitter); } /* Bundles */ if (priv->bundles->len > 0) { as_yaml_emit_scalar (emitter, "Bundles"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->bundles->len; i++) { AsBundle *bundle = AS_BUNDLE (g_ptr_array_index (priv->bundles, i)); as_bundle_emit_yaml (bundle, ctx, emitter); } as_yaml_sequence_end (emitter); } /* Launchable */ if (priv->launchables->len > 0) { as_yaml_emit_scalar (emitter, "Launchable"); as_yaml_mapping_start (emitter); for (guint i = 0; i < priv->launchables->len; i++) { AsLaunchable *launch = AS_LAUNCHABLE ( g_ptr_array_index (priv->launchables, i)); as_launchable_emit_yaml (launch, ctx, emitter); } as_yaml_mapping_end (emitter); } /* Replaces */ if (priv->replaces != NULL && priv->replaces->len > 0) { as_yaml_emit_scalar (emitter, "Replaces"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->replaces->len; i++) { as_yaml_mapping_start (emitter); as_yaml_emit_entry (emitter, "id", (const gchar *) g_ptr_array_index (priv->replaces, i)); as_yaml_mapping_end (emitter); } as_yaml_sequence_end (emitter); } /* Requires */ if (priv->requires->len > 0) { as_yaml_emit_scalar (emitter, "Requires"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->requires->len; i++) { AsRelation *relation = AS_RELATION (g_ptr_array_index (priv->requires, i)); as_relation_emit_yaml (relation, ctx, emitter); } as_yaml_sequence_end (emitter); } /* Recommends */ if (priv->recommends->len > 0) { as_yaml_emit_scalar (emitter, "Recommends"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->recommends->len; i++) { AsRelation *relation = AS_RELATION ( g_ptr_array_index (priv->recommends, i)); as_relation_emit_yaml (relation, ctx, emitter); } as_yaml_sequence_end (emitter); } /* Supports */ if (priv->supports->len > 0) { as_yaml_emit_scalar (emitter, "Supports"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->supports->len; i++) { AsRelation *relation = AS_RELATION (g_ptr_array_index (priv->supports, i)); as_relation_emit_yaml (relation, ctx, emitter); } as_yaml_sequence_end (emitter); } /* Provides */ as_component_yaml_emit_provides (cpt, emitter); /* Suggests */ if (priv->suggestions->len > 0) { as_yaml_emit_scalar (emitter, "Suggests"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->suggestions->len; i++) { AsSuggested *suggested = AS_SUGGESTED ( g_ptr_array_index (priv->suggestions, i)); as_suggested_emit_yaml (suggested, ctx, emitter); } as_yaml_sequence_end (emitter); } /* Screenshots */ if (priv->screenshots->len > 0) { /* restore default screenshot order */ as_component_sort_screenshots (cpt, NULL, NULL, FALSE); as_yaml_emit_scalar (emitter, "Screenshots"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->screenshots->len; i++) { AsScreenshot *screenshot = AS_SCREENSHOT ( g_ptr_array_index (priv->screenshots, i)); as_screenshot_emit_yaml (screenshot, ctx, emitter); } as_yaml_sequence_end (emitter); } /* Translation details */ as_component_yaml_emit_languages (cpt, emitter); /* Agreements */ if (priv->agreements->len > 0) { as_yaml_emit_scalar (emitter, "Agreements"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->agreements->len; i++) { AsAgreement *agreement = AS_AGREEMENT ( g_ptr_array_index (priv->agreements, i)); as_agreement_emit_yaml (agreement, ctx, emitter); } as_yaml_sequence_end (emitter); } /* Branding */ if (priv->branding != NULL) as_branding_emit_yaml (priv->branding, ctx, emitter); /* Releases */ as_release_list_emit_yaml (priv->releases, ctx, emitter); /* ContentRating */ if (priv->content_ratings->len > 0) { as_yaml_emit_scalar (emitter, "ContentRating"); as_yaml_mapping_start (emitter); for (guint i = 0; i < priv->content_ratings->len; i++) { AsContentRating *content_rating = AS_CONTENT_RATING ( g_ptr_array_index (priv->content_ratings, i)); as_content_rating_emit_yaml (content_rating, ctx, emitter); } as_yaml_mapping_end (emitter); } /* Tags */ if (priv->tags->len > 0) { as_yaml_emit_scalar (emitter, "Tags"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->tags->len; i++) { g_auto(GStrv) parts = g_strsplit (g_ptr_array_index (priv->tags, i), "::", 2); as_yaml_mapping_start (emitter); if (!as_is_empty (parts[0])) as_yaml_emit_entry (emitter, "namespace", parts[0]); as_yaml_emit_entry (emitter, "tag", parts[1]); as_yaml_mapping_end (emitter); } as_yaml_sequence_end (emitter); } /* References */ if (priv->references->len > 0) { as_yaml_emit_scalar (emitter, "References"); as_yaml_sequence_start (emitter); for (guint i = 0; i < priv->references->len; i++) { AsReference *ref = AS_REFERENCE (g_ptr_array_index (priv->references, i)); as_reference_emit_yaml (ref, ctx, emitter); } as_yaml_sequence_end (emitter); } /* Custom fields */ as_component_yaml_emit_custom (cpt, emitter); /* Reviews */ if (priv->reviews->len > 0) { as_yaml_emit_scalar (emitter, "Reviews"); as_yaml_mapping_start (emitter); for (guint i = 0; i < priv->reviews->len; i++) { AsReview *review = AS_REVIEW (g_ptr_array_index (priv->reviews, i)); as_review_emit_yaml (review, ctx, emitter); } as_yaml_mapping_end (emitter); } /* close main mapping */ as_yaml_mapping_end (emitter); /* finalize the document */ yaml_document_end_event_initialize (&event, 1); res = yaml_emitter_emit (emitter, &event); g_assert (res); } /** * as_component_load_from_bytes: * @cpt: an #AsComponent instance. * @context: an #AsContext instance. * @format: the format of the data to load, e.g. %AS_FORMAT_KIND_XML * @bytes: the data to load. * @error: a #GError. * * Load metadata for this component from an XML string. * You normally do not want to use this method directly and instead use the more * convenient API of #AsMetadata to create and update components. * * If this function returns %TRUE, a valid component is returned unless the selected * format was %AS_FORMAT_KIND_DESKTOP_ENTRY, in which case a component ID will have to * be set explicitly by the caller in order to make the component valid. * * Returns: %TRUE on success. * * Since: 0.14.0 */ gboolean as_component_load_from_bytes (AsComponent *cpt, AsContext *context, AsFormatKind format, GBytes *bytes, GError **error) { AsComponentPrivate *priv = GET_PRIVATE (cpt); gsize data_len; const gchar *data = g_bytes_get_data (bytes, &data_len); if (data_len == 0) { g_set_error_literal (error, AS_METADATA_ERROR, AS_METADATA_ERROR_FAILED, "No data submitted to load component from."); return FALSE; } if ((format == AS_FORMAT_KIND_XML) || (format == AS_FORMAT_KIND_UNKNOWN)) { xmlDoc *doc; xmlNode *root; gboolean ret; g_return_val_if_fail (context != NULL, FALSE); doc = as_xml_parse_document (data, data_len, FALSE, /* pedantic */ error); if (doc == NULL) return FALSE; root = xmlDocGetRootElement (doc); ret = as_component_load_from_xml (cpt, context, root, error); xmlFreeDoc (doc); return ret; } if (format == AS_FORMAT_KIND_DESKTOP_ENTRY) { GError *tmp_error = NULL; as_desktop_entry_parse_data (cpt, data, data_len, AS_FORMAT_VERSION_LATEST, TRUE, NULL, /* issues */ NULL, /* l10n function */ NULL, /* l10n func user data */ &tmp_error); if (tmp_error != NULL) { g_propagate_error (error, tmp_error); return FALSE; } /* update context */ priv->priority = as_context_get_priority (context); as_component_set_context (cpt, context); return TRUE; } g_set_error_literal ( error, AS_METADATA_ERROR, AS_METADATA_ERROR_FAILED, "Unable to load data: Unknown or unsupported format for this operation."); return FALSE; } /** * as_component_to_xml_data: * @cpt: an #AsComponent instance. * @context: an #AsContext instance. * @error: a #GError. * * Serialize this component into an XML string. * You normally do not want to use this method directly and instead use the more * convenient API of #AsMetadata to serialize components. * * Returns: %TRUE on success. * * Since: 0.12.10 */ gchar * as_component_to_xml_data (AsComponent *cpt, AsContext *context, GError **error) { xmlNode *node; g_return_val_if_fail (context != NULL, NULL); node = as_component_to_xml_node (cpt, context, NULL); return as_xml_node_free_to_str (node, error); } /** * as_component_get_property: */ static void as_component_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { AsComponent *cpt; AsComponentPrivate *priv; cpt = G_TYPE_CHECK_INSTANCE_CAST (object, AS_TYPE_COMPONENT, AsComponent); priv = GET_PRIVATE (cpt); switch (property_id) { case AS_COMPONENT_KIND: g_value_set_enum (value, as_component_get_kind (cpt)); break; case AS_COMPONENT_PKGNAMES: g_value_set_boxed (value, as_component_get_pkgnames (cpt)); break; case AS_COMPONENT_ID: g_value_set_string (value, as_component_get_id (cpt)); break; case AS_COMPONENT_NAME: g_value_set_string (value, as_component_get_name (cpt)); break; case AS_COMPONENT_SUMMARY: g_value_set_string (value, as_component_get_summary (cpt)); break; case AS_COMPONENT_DESCRIPTION: g_value_set_string (value, as_component_get_description (cpt)); break; case AS_COMPONENT_KEYWORDS: g_value_set_boxed (value, as_component_get_keywords (cpt)); break; case AS_COMPONENT_ICONS: g_value_set_pointer (value, as_component_get_icons (cpt)); break; case AS_COMPONENT_URLS: g_value_set_boxed (value, priv->urls); break; case AS_COMPONENT_CATEGORIES: g_value_set_boxed (value, as_component_get_categories (cpt)); break; case AS_COMPONENT_PROJECT_LICENSE: g_value_set_string (value, as_component_get_project_license (cpt)); break; case AS_COMPONENT_PROJECT_GROUP: g_value_set_string (value, as_component_get_project_group (cpt)); break; case AS_COMPONENT_SCREENSHOTS: g_value_set_boxed (value, as_component_get_screenshots_all (cpt)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /** * as_component_set_property: */ static void as_component_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { AsComponent *cpt; cpt = G_TYPE_CHECK_INSTANCE_CAST (object, AS_TYPE_COMPONENT, AsComponent); switch (property_id) { case AS_COMPONENT_KIND: as_component_set_kind (cpt, g_value_get_enum (value)); break; case AS_COMPONENT_PKGNAMES: as_component_set_pkgnames (cpt, g_value_get_boxed (value)); break; case AS_COMPONENT_ID: as_component_set_id (cpt, g_value_get_string (value)); break; case AS_COMPONENT_NAME: as_component_set_name (cpt, g_value_get_string (value), NULL); break; case AS_COMPONENT_SUMMARY: as_component_set_summary (cpt, g_value_get_string (value), NULL); break; case AS_COMPONENT_DESCRIPTION: as_component_set_description (cpt, g_value_get_string (value), NULL); break; case AS_COMPONENT_KEYWORDS: as_component_set_keywords (cpt, g_value_get_pointer (value), NULL, TRUE); break; case AS_COMPONENT_PROJECT_LICENSE: as_component_set_project_license (cpt, g_value_get_string (value)); break; case AS_COMPONENT_PROJECT_GROUP: as_component_set_project_group (cpt, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /** * as_component_class_init: */ static void as_component_class_init (AsComponentClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = as_component_finalize; object_class->get_property = as_component_get_property; object_class->set_property = as_component_set_property; /** * AsComponent:kind: * * the #AsComponentKind of this component */ g_object_class_install_property ( object_class, AS_COMPONENT_KIND, g_param_spec_enum ("kind", "kind", "kind", AS_TYPE_COMPONENT_KIND, 0, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE)); /** * AsComponent:pkgnames: * * string array of packages name */ g_object_class_install_property ( object_class, AS_COMPONENT_PKGNAMES, g_param_spec_boxed ("pkgnames", "pkgnames", "pkgnames", G_TYPE_STRV, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE)); /** * AsComponent:id: * * the unique identifier */ g_object_class_install_property ( object_class, AS_COMPONENT_ID, g_param_spec_string ("id", "id", "id", NULL, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE)); /** * AsComponent:name: * * the name */ g_object_class_install_property ( object_class, AS_COMPONENT_NAME, g_param_spec_string ("name", "name", "name", NULL, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE)); /** * AsComponent:summary: * * the summary */ g_object_class_install_property ( object_class, AS_COMPONENT_SUMMARY, g_param_spec_string ("summary", "summary", "summary", NULL, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE)); /** * AsComponent:description: * * the description */ g_object_class_install_property ( object_class, AS_COMPONENT_DESCRIPTION, g_param_spec_string ("description", "description", "description", NULL, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE)); /** * AsComponent:keywords: * * string array of keywords */ g_object_class_install_property ( object_class, AS_COMPONENT_KEYWORDS, g_param_spec_boxed ("keywords", "keywords", "keywords", G_TYPE_STRV, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE)); /** * AsComponent:icons: (type GList(AsIcon)) * * hash map of icon urls and sizes */ g_object_class_install_property ( object_class, AS_COMPONENT_ICONS, g_param_spec_pointer ("icons", "icons", "icons", G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE)); /** * AsComponent:urls: (type GHashTable(AsUrlKind,utf8)) * * the urls associated with this component */ g_object_class_install_property ( object_class, AS_COMPONENT_URLS, g_param_spec_boxed ("urls", "urls", "urls", G_TYPE_HASH_TABLE, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE)); /** * AsComponent:categories: * * string array of categories */ g_object_class_install_property ( object_class, AS_COMPONENT_CATEGORIES, g_param_spec_boxed ("categories", "categories", "categories", G_TYPE_PTR_ARRAY, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE)); /** * AsComponent:project-license: * * the project license */ g_object_class_install_property ( object_class, AS_COMPONENT_PROJECT_LICENSE, g_param_spec_string ("project-license", "project-license", "project-license", NULL, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE)); /** * AsComponent:project-group: * * the project group */ g_object_class_install_property ( object_class, AS_COMPONENT_PROJECT_GROUP, g_param_spec_string ("project-group", "project-group", "project-group", NULL, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE | G_PARAM_WRITABLE)); /** * AsComponent:screenshots: (type GPtrArray(AsScreenshot)): * * An array of #AsScreenshot instances */ g_object_class_install_property ( object_class, AS_COMPONENT_SCREENSHOTS, g_param_spec_boxed ("screenshots", "screenshots", "screenshots", G_TYPE_PTR_ARRAY, G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READABLE)); } /** * as_component_new: * * Creates a new #AsComponent. * * Returns: (transfer full): a new #AsComponent **/ AsComponent * as_component_new (void) { AsComponent *cpt; cpt = g_object_new (AS_TYPE_COMPONENT, NULL); return AS_COMPONENT (cpt); }