/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2014-2015 Richard Hughes * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * SECTION:as-app * @short_description: An object for an AppStream application or add-on * @include: appstream-glib.h * @stability: Stable * * This object represents the base object of all AppStream, the application. * Although called #AsApp, this object also represents components like fonts, * codecs and input methods. * * See also: #AsScreenshot, #AsRelease */ #include "config.h" #include #include "as-app-private.h" #include "as-bundle-private.h" #include "as-enums.h" #include "as-icon-private.h" #include "as-node-private.h" #include "as-provide-private.h" #include "as-release-private.h" #include "as-screenshot-private.h" #include "as-tag.h" #include "as-utils-private.h" #include "as-yaml.h" typedef struct { AsAppProblems problems; AsIconKind icon_kind; AsIdKind id_kind; GHashTable *comments; /* of locale:string */ GHashTable *developer_names; /* of locale:string */ GHashTable *descriptions; /* of locale:string */ GHashTable *keywords; /* of locale:GPtrArray */ GHashTable *languages; /* of locale:string */ GHashTable *metadata; /* of key:value */ GHashTable *names; /* of locale:string */ GHashTable *urls; /* of key:string */ GPtrArray *addons; /* of AsApp */ GPtrArray *categories; /* of string */ GPtrArray *compulsory_for_desktops; /* of string */ GPtrArray *extends; /* of string */ GPtrArray *kudos; /* of string */ GPtrArray *permissions; /* of string */ GPtrArray *mimetypes; /* of string */ GPtrArray *pkgnames; /* of string */ GPtrArray *architectures; /* of string */ GPtrArray *releases; /* of AsRelease */ GPtrArray *provides; /* of AsProvide */ GPtrArray *screenshots; /* of AsScreenshot */ GPtrArray *icons; /* of AsIcon */ GPtrArray *bundles; /* of AsBundle */ GPtrArray *vetos; /* of string */ AsAppSourceKind source_kind; AsAppState state; AsAppTrustFlags trust_flags; gchar *icon_path; gchar *id_filename; gchar *id; gchar *origin; gchar *project_group; gchar *project_license; gchar *metadata_license; gchar *source_pkgname; gchar *update_contact; gchar *source_file; gint priority; gsize token_cache_valid; GPtrArray *token_cache; /* of AsAppTokenItem */ } AsAppPrivate; G_DEFINE_TYPE_WITH_PRIVATE (AsApp, as_app, G_TYPE_OBJECT) #define GET_PRIVATE(o) (as_app_get_instance_private (o)) typedef struct { gchar **values_ascii; gchar **values_utf8; guint score; } AsAppTokenItem; /** * as_app_error_quark: * * Return value: An error quark. * * Since: 0.1.2 **/ GQuark as_app_error_quark (void) { static GQuark quark = 0; if (!quark) quark = g_quark_from_static_string ("AsAppError"); return quark; } /** * as_app_source_kind_from_string: * @source_kind: a source kind string * * Converts the text representation to an enumerated value. * * Return value: A #AsAppSourceKind, e.g. %AS_APP_SOURCE_KIND_APPSTREAM. * * Since: 0.2.2 **/ AsAppSourceKind as_app_source_kind_from_string (const gchar *source_kind) { if (g_strcmp0 (source_kind, "appstream") == 0) return AS_APP_SOURCE_KIND_APPSTREAM; if (g_strcmp0 (source_kind, "appdata") == 0) return AS_APP_SOURCE_KIND_APPDATA; if (g_strcmp0 (source_kind, "metainfo") == 0) return AS_APP_SOURCE_KIND_METAINFO; if (g_strcmp0 (source_kind, "desktop") == 0) return AS_APP_SOURCE_KIND_DESKTOP; if (g_strcmp0 (source_kind, "inf") == 0) return AS_APP_SOURCE_KIND_INF; return AS_APP_SOURCE_KIND_UNKNOWN; } /** * as_app_source_kind_to_string: * @source_kind: the #AsAppSourceKind. * * Converts the enumerated value to an text representation. * * Returns: string version of @source_kind, or %NULL for unknown * * Since: 0.2.2 **/ const gchar * as_app_source_kind_to_string (AsAppSourceKind source_kind) { if (source_kind == AS_APP_SOURCE_KIND_APPSTREAM) return "appstream"; if (source_kind == AS_APP_SOURCE_KIND_APPDATA) return "appdata"; if (source_kind == AS_APP_SOURCE_KIND_METAINFO) return "metainfo"; if (source_kind == AS_APP_SOURCE_KIND_DESKTOP) return "desktop"; if (source_kind == AS_APP_SOURCE_KIND_INF) return "inf"; return NULL; } /** * as_app_state_to_string: * @state: the #AsAppState. * * Converts the enumerated value to an text representation. * * Returns: string version of @state, or %NULL for unknown * * Since: 0.2.2 **/ const gchar * as_app_state_to_string (AsAppState state) { if (state == AS_APP_STATE_UNKNOWN) return "unknown"; if (state == AS_APP_STATE_INSTALLED) return "installed"; if (state == AS_APP_STATE_AVAILABLE) return "available"; if (state == AS_APP_STATE_AVAILABLE_LOCAL) return "local"; if (state == AS_APP_STATE_QUEUED_FOR_INSTALL) return "queued"; if (state == AS_APP_STATE_INSTALLING) return "installing"; if (state == AS_APP_STATE_REMOVING) return "removing"; if (state == AS_APP_STATE_UPDATABLE) return "updatable"; if (state == AS_APP_STATE_UNAVAILABLE) return "unavailable"; return NULL; } /** * as_app_guess_source_kind: * @filename: a file name * * Guesses the source kind based from the filename. * * Return value: A #AsAppSourceKind, e.g. %AS_APP_SOURCE_KIND_APPSTREAM. * * Since: 0.1.8 **/ AsAppSourceKind as_app_guess_source_kind (const gchar *filename) { if (g_str_has_suffix (filename, ".xml.gz")) return AS_APP_SOURCE_KIND_APPSTREAM; if (g_str_has_suffix (filename, ".yml")) return AS_APP_SOURCE_KIND_APPSTREAM; if (g_str_has_suffix (filename, ".yml.gz")) return AS_APP_SOURCE_KIND_APPSTREAM; if (g_str_has_suffix (filename, ".desktop")) return AS_APP_SOURCE_KIND_DESKTOP; if (g_str_has_suffix (filename, ".desktop.in")) return AS_APP_SOURCE_KIND_DESKTOP; if (g_str_has_suffix (filename, ".appdata.xml")) return AS_APP_SOURCE_KIND_APPDATA; if (g_str_has_suffix (filename, ".appdata.xml.in")) return AS_APP_SOURCE_KIND_APPDATA; if (g_str_has_suffix (filename, ".metainfo.xml")) return AS_APP_SOURCE_KIND_METAINFO; if (g_str_has_suffix (filename, ".metainfo.xml.in")) return AS_APP_SOURCE_KIND_METAINFO; if (g_str_has_suffix (filename, ".xml")) return AS_APP_SOURCE_KIND_APPSTREAM; if (g_str_has_suffix (filename, ".inf")) return AS_APP_SOURCE_KIND_INF; return AS_APP_SOURCE_KIND_UNKNOWN; } /** * as_app_finalize: **/ static void as_app_finalize (GObject *object) { AsApp *app = AS_APP (object); AsAppPrivate *priv = GET_PRIVATE (app); g_free (priv->icon_path); g_free (priv->id_filename); g_free (priv->id); g_free (priv->project_group); g_free (priv->project_license); g_free (priv->metadata_license); g_free (priv->origin); g_free (priv->source_pkgname); g_free (priv->update_contact); g_free (priv->source_file); g_hash_table_unref (priv->comments); g_hash_table_unref (priv->developer_names); g_hash_table_unref (priv->descriptions); g_hash_table_unref (priv->keywords); g_hash_table_unref (priv->languages); g_hash_table_unref (priv->metadata); g_hash_table_unref (priv->names); g_hash_table_unref (priv->urls); g_ptr_array_unref (priv->addons); g_ptr_array_unref (priv->categories); g_ptr_array_unref (priv->compulsory_for_desktops); g_ptr_array_unref (priv->extends); g_ptr_array_unref (priv->kudos); g_ptr_array_unref (priv->permissions); g_ptr_array_unref (priv->mimetypes); g_ptr_array_unref (priv->pkgnames); g_ptr_array_unref (priv->architectures); g_ptr_array_unref (priv->releases); g_ptr_array_unref (priv->provides); g_ptr_array_unref (priv->screenshots); g_ptr_array_unref (priv->icons); g_ptr_array_unref (priv->bundles); g_ptr_array_unref (priv->token_cache); g_ptr_array_unref (priv->vetos); G_OBJECT_CLASS (as_app_parent_class)->finalize (object); } /** * as_app_token_item_free: **/ static void as_app_token_item_free (AsAppTokenItem *token_item) { g_strfreev (token_item->values_ascii); g_strfreev (token_item->values_utf8); g_slice_free (AsAppTokenItem, token_item); } /** * as_app_init: **/ static void as_app_init (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); 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->extends = g_ptr_array_new_with_free_func (g_free); priv->keywords = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); priv->kudos = g_ptr_array_new_with_free_func (g_free); priv->permissions = g_ptr_array_new_with_free_func (g_free); priv->mimetypes = g_ptr_array_new_with_free_func (g_free); priv->pkgnames = g_ptr_array_new_with_free_func (g_free); priv->architectures = g_ptr_array_new_with_free_func (g_free); priv->addons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->provides = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->screenshots = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->icons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->bundles = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); priv->token_cache = g_ptr_array_new_with_free_func ((GDestroyNotify) as_app_token_item_free); priv->vetos = g_ptr_array_new_with_free_func (g_free); priv->comments = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); priv->developer_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); priv->descriptions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); priv->languages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); priv->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); priv->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); priv->urls = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } /** * as_app_class_init: **/ static void as_app_class_init (AsAppClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = as_app_finalize; } /******************************************************************************/ /** * as_app_get_id: * @app: a #AsApp instance. * * Gets the full ID value. * * Returns: the ID, e.g. "org.gnome.Software.desktop" * * Since: 0.1.0 **/ const gchar * as_app_get_id (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->id; } /** * as_app_get_id_filename: * @app: a #AsApp instance. * * Returns a filename which represents the applications ID, e.g. "gimp.desktop" * becomes "gimp" and is used for cache directories. * * Returns: A utf8 filename * * Since: 0.3.0 **/ const gchar * as_app_get_id_filename (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->id_filename; } /** * as_app_get_categories: * @app: a #AsApp instance. * * Get the application categories. * * Returns: (element-type utf8) (transfer none): an array * * Since: 0.1.0 **/ GPtrArray * as_app_get_categories (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->categories; } /** * as_app_has_category: * @app: a #AsApp instance. * @category: a category string, e.g. "DesktopSettings" * * Searches the category list for a specific item. * * Returns: %TRUE if the application has got the specified category * * Since: 0.1.5 */ gboolean as_app_has_category (AsApp *app, const gchar *category) { AsAppPrivate *priv = GET_PRIVATE (app); const gchar *tmp; guint i; for (i = 0; i < priv->categories->len; i++) { tmp = g_ptr_array_index (priv->categories, i); if (g_strcmp0 (tmp, category) == 0) return TRUE; } return FALSE; } /** * as_app_has_kudo: * @app: a #AsApp instance. * @kudo: a kudo string, e.g. "SearchProvider" * * Searches the kudo list for a specific item. * * Returns: %TRUE if the application has got the specified kudo * * Since: 0.2.2 */ gboolean as_app_has_kudo (AsApp *app, const gchar *kudo) { AsAppPrivate *priv = GET_PRIVATE (app); const gchar *tmp; guint i; for (i = 0; i < priv->kudos->len; i++) { tmp = g_ptr_array_index (priv->kudos, i); if (g_strcmp0 (tmp, kudo) == 0) return TRUE; } return FALSE; } /** * as_app_has_kudo_kind: * @app: a #AsApp instance. * @kudo: a #AsKudoKind, e.g. %AS_KUDO_KIND_SEARCH_PROVIDER * * Searches the kudo list for a specific item. * * Returns: %TRUE if the application has got the specified kudo * * Since: 0.2.2 */ gboolean as_app_has_kudo_kind (AsApp *app, AsKudoKind kudo) { AsAppPrivate *priv = GET_PRIVATE (app); const gchar *tmp; guint i; for (i = 0; i < priv->kudos->len; i++) { tmp = g_ptr_array_index (priv->kudos, i); if (as_kudo_kind_from_string (tmp) == kudo) return TRUE; } return FALSE; } /** * as_app_get_compulsory_for_desktops: * @app: a #AsApp instance. * * Returns the desktops where this application is compulsory. * * Returns: (element-type utf8) (transfer none): an array * * Since: 0.1.0 **/ GPtrArray * as_app_get_compulsory_for_desktops (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->compulsory_for_desktops; } /** * as_app_has_permission: * @app: a #AsApp instance. * @permission: a permission string, e.g. "Network" * * Searches the permission list for a specific item. * * Returns: %TRUE if the application has got the specified permission * * Since: 0.3.5 */ gboolean as_app_has_permission (AsApp *app, const gchar *permission) { AsAppPrivate *priv = GET_PRIVATE (app); const gchar *tmp; guint i; for (i = 0; i < priv->permissions->len; i++) { tmp = g_ptr_array_index (priv->permissions, i); if (g_strcmp0 (tmp, permission) == 0) return TRUE; } return FALSE; } /** * as_app_get_keywords: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * * Gets any keywords the application should match against. * * Returns: (element-type utf8) (transfer none): an array, or %NULL * * Since: 0.3.0 **/ GPtrArray * as_app_get_keywords (AsApp *app, const gchar *locale) { AsAppPrivate *priv = GET_PRIVATE (app); if (locale == NULL) locale = "C"; return g_hash_table_lookup (priv->keywords, locale); } /** * as_app_get_kudos: * @app: a #AsApp instance. * * Gets any kudos the application has obtained. * * Returns: (element-type utf8) (transfer none): an array * * Since: 0.2.2 **/ GPtrArray * as_app_get_kudos (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->kudos; } /** * as_app_get_permissions: * @app: a #AsApp instance. * * Gets any permissions the application has obtained. * * Returns: (element-type utf8) (transfer none): an array * * Since: 0.3.5 **/ GPtrArray * as_app_get_permissions (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->permissions; } /** * as_app_get_mimetypes: * @app: a #AsApp instance. * * Gets any mimetypes the application will register. * * Returns: (transfer none) (element-type utf8): an array * * Since: 0.2.0 **/ GPtrArray * as_app_get_mimetypes (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->mimetypes; } /** * as_app_get_releases: * @app: a #AsApp instance. * * Gets all the releases the application has had. * * Returns: (element-type AsRelease) (transfer none): an array * * Since: 0.1.0 **/ GPtrArray * as_app_get_releases (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->releases; } /** * as_app_get_release: * @app: a #AsApp instance. * @version: a version string * * Gets a specific release from the application. * * Returns: (transfer none): a release, or %NULL * * Since: 0.3.5 **/ AsRelease * as_app_get_release (AsApp *app, const gchar *version) { AsAppPrivate *priv = GET_PRIVATE (app); AsRelease *release; guint i; for (i = 0; i < priv->releases->len; i++) { release = g_ptr_array_index (priv->releases, i); if (g_strcmp0 (as_release_get_version (release), version) == 0) return release; } return NULL; } /** * as_app_get_release_default: * @app: a #AsApp instance. * * Gets the default (newest) release from the application. * * Returns: (transfer none): a release, or %NULL * * Since: 0.3.5 **/ AsRelease * as_app_get_release_default (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); AsRelease *release_newest = NULL; AsRelease *release_tmp = NULL; guint i; for (i = 0; i < priv->releases->len; i++) { release_tmp = g_ptr_array_index (priv->releases, i); if (release_newest == NULL || as_release_vercmp (release_tmp, release_newest) < 1) release_newest = release_tmp; } return release_newest; } /** * as_app_get_provides: * @app: a #AsApp instance. * * Gets all the provides the application has. * * Returns: (element-type AsProvide) (transfer none): an array * * Since: 0.1.6 **/ GPtrArray * as_app_get_provides (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->provides; } /** * as_app_get_screenshots: * @app: a #AsApp instance. * * Gets any screenshots the application has defined. * * Returns: (element-type AsScreenshot) (transfer none): an array * * Since: 0.1.0 **/ GPtrArray * as_app_get_screenshots (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->screenshots; } /** * as_app_get_icons: * @app: a #AsApp instance. * * Gets any icons the application has defined. * * Returns: (element-type AsIcon) (transfer none): an array * * Since: 0.3.1 **/ GPtrArray * as_app_get_icons (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->icons; } /** * as_app_get_bundles: * @app: a #AsApp instance. * * Gets any bundles the application has defined. * * Returns: (element-type AsBundle) (transfer none): an array * * Since: 0.3.5 **/ GPtrArray * as_app_get_bundles (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->bundles; } /** * as_app_get_names: * @app: a #AsApp instance. * * Gets the names set for the application. * * Returns: (transfer none): hash table of names * * Since: 0.1.6 **/ GHashTable * as_app_get_names (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->names; } /** * as_app_get_comments: * @app: a #AsApp instance. * * Gets the comments set for the application. * * Returns: (transfer none): hash table of comments * * Since: 0.1.6 **/ GHashTable * as_app_get_comments (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->comments; } /** * as_app_get_developer_names: * @app: a #AsApp instance. * * Gets the developer_names set for the application. * * Returns: (transfer none): hash table of developer_names * * Since: 0.1.8 **/ GHashTable * as_app_get_developer_names (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->developer_names; } /** * as_app_get_metadata: * @app: a #AsApp instance. * * Gets the metadata set for the application. * * Returns: (transfer none): hash table of metadata * * Since: 0.1.6 **/ GHashTable * as_app_get_metadata (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->metadata; } /** * as_app_get_descriptions: * @app: a #AsApp instance. * * Gets the descriptions set for the application. * * Returns: (transfer none): hash table of descriptions * * Since: 0.1.6 **/ GHashTable * as_app_get_descriptions (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->descriptions; } /** * as_app_get_urls: * @app: a #AsApp instance. * * Gets the URLs set for the application. * * Returns: (transfer none): hash table of URLs * * Since: 0.1.0 **/ GHashTable * as_app_get_urls (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->urls; } /** * as_app_get_pkgnames: * @app: a #AsApp instance. * * Gets the package names (if any) for the application. * * Returns: (element-type utf8) (transfer none): an array * * Since: 0.1.0 **/ GPtrArray * as_app_get_pkgnames (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->pkgnames; } /** * as_app_get_architectures: * @app: a #AsApp instance. * * Gets the supported architectures for the application, or an empty list * if all architectures are supported. * * Returns: (element-type utf8) (transfer none): an array * * Since: 0.1.1 **/ GPtrArray * as_app_get_architectures (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->architectures; } /** * as_app_get_extends: * @app: a #AsApp instance. * * Gets the IDs that are extended from the addon. * * Returns: (element-type utf8) (transfer none): an array * * Since: 0.1.7 **/ GPtrArray * as_app_get_extends (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->extends; } /** * as_app_get_addons: * @app: a #AsApp instance. * * Gets all the addons the application has. * * Returns: (element-type AsApp) (transfer none): an array * * Since: 0.1.7 **/ GPtrArray * as_app_get_addons (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->addons; } /** * as_app_get_id_kind: * @app: a #AsApp instance. * * Gets the ID kind. * * Returns: enumerated value * * Since: 0.1.0 **/ AsIdKind as_app_get_id_kind (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->id_kind; } /** * as_app_get_name_size: (skip) * @app: a #AsApp instance. * * Gets the number of names. * * Returns: integer * * Since: 0.1.4 **/ guint as_app_get_name_size (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return g_hash_table_size (priv->names); } /** * as_app_get_comment_size: (skip) * @app: a #AsApp instance. * * Gets the number of comments. * * Returns: integer * * Since: 0.1.4 **/ guint as_app_get_comment_size (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return g_hash_table_size (priv->comments); } /** * as_app_get_description_size: (skip) * @app: a #AsApp instance. * * Gets the number of descriptions. * * Returns: integer * * Since: 0.1.4 **/ guint as_app_get_description_size (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return g_hash_table_size (priv->descriptions); } /** * as_app_get_source_kind: * @app: a #AsApp instance. * * Gets the source kind, i.e. where the AsApp came from. * * Returns: enumerated value * * Since: 0.1.4 **/ AsAppSourceKind as_app_get_source_kind (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->source_kind; } /** * as_app_get_state: * @app: a #AsApp instance. * * Gets the application state. * * Returns: enumerated value * * Since: 0.2.2 **/ AsAppState as_app_get_state (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->state; } /** * as_app_get_trust_flags: * @app: a #AsApp instance. * * Gets the trust flags, i.e. how trusted the incoming data is. * * Returns: bitfield * * Since: 0.2.2 **/ AsAppTrustFlags as_app_get_trust_flags (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->trust_flags; } /** * as_app_get_problems: (skip) * @app: a #AsApp instance. * * Gets the bitfield of problems. * * Returns: problems encountered during parsing the application * * Since: 0.1.4 **/ AsAppProblems as_app_get_problems (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->problems; } /** * as_app_get_pkgname_default: * @app: a #AsApp instance. * * Gets the default package name. * * Returns: string, or %NULL if unset * * Since: 0.2.0 **/ const gchar * as_app_get_pkgname_default (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); if (priv->pkgnames->len < 1) return NULL; return g_ptr_array_index (priv->pkgnames, 0); } /** * as_app_get_source_pkgname: * @app: a #AsApp instance. * * Gets the source package name that produced the binary package. * Only source packages producing more than one binary package will have this * entry set. * * Returns: string, or %NULL if unset * * Since: 0.2.4 **/ const gchar * as_app_get_source_pkgname (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->source_pkgname; } /** * as_app_get_icon_path: * @app: a #AsApp instance. * * Gets the application icon path. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * as_app_get_icon_path (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->icon_path; } /** * as_app_get_name: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * * Gets the application name for a specific locale. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * as_app_get_name (AsApp *app, const gchar *locale) { AsAppPrivate *priv = GET_PRIVATE (app); return as_hash_lookup_by_locale (priv->names, locale); } /** * as_app_get_comment: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * * Gets the application summary for a specific locale. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * as_app_get_comment (AsApp *app, const gchar *locale) { AsAppPrivate *priv = GET_PRIVATE (app); return as_hash_lookup_by_locale (priv->comments, locale); } /** * as_app_get_developer_name: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * * Gets the application developer name for a specific locale. * * Returns: string, or %NULL if unset * * Since: 0.1.8 **/ const gchar * as_app_get_developer_name (AsApp *app, const gchar *locale) { AsAppPrivate *priv = GET_PRIVATE (app); return as_hash_lookup_by_locale (priv->developer_names, locale); } /** * as_app_get_description: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * * Gets the application description markup for a specific locale. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * as_app_get_description (AsApp *app, const gchar *locale) { AsAppPrivate *priv = GET_PRIVATE (app); return as_hash_lookup_by_locale (priv->descriptions, locale); } /** * as_app_get_language: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * * Gets the language coverage for the specific language. * * Returns: a percentage value where 0 is unspecified, or -1 for not found * * Since: 0.1.0 **/ gint as_app_get_language (AsApp *app, const gchar *locale) { AsAppPrivate *priv = GET_PRIVATE (app); gboolean ret; gpointer value = NULL; 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_app_get_priority: * @app: a #AsApp instance. * * Gets the application priority. Larger values trump smaller values. * * Returns: priority value * * Since: 0.1.0 **/ gint as_app_get_priority (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->priority; } /** * as_app_get_languages: * @app: a #AsApp instance. * * Get a list of all languages. * * Returns: (transfer container) (element-type utf8): list of language values * * Since: 0.1.0 **/ GList * as_app_get_languages (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return g_hash_table_get_keys (priv->languages); } /** * as_app_get_url_item: * @app: a #AsApp instance. * @url_kind: the URL kind, e.g. %AS_URL_KIND_HOMEPAGE. * * Gets a URL. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * as_app_get_url_item (AsApp *app, AsUrlKind url_kind) { AsAppPrivate *priv = GET_PRIVATE (app); return g_hash_table_lookup (priv->urls, as_url_kind_to_string (url_kind)); } /** * as_app_get_metadata_item: * @app: a #AsApp instance. * @key: the metadata key. * * Gets some metadata item. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * as_app_get_metadata_item (AsApp *app, const gchar *key) { AsAppPrivate *priv = GET_PRIVATE (app); return g_hash_table_lookup (priv->metadata, key); } /** * as_app_get_project_group: * @app: a #AsApp instance. * * Gets an application project group. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * as_app_get_project_group (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->project_group; } /** * as_app_get_project_license: * @app: a #AsApp instance. * * Gets the application project license. * * Returns: string, or %NULL if unset * * Since: 0.1.0 **/ const gchar * as_app_get_project_license (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->project_license; } /** * as_app_get_metadata_license: * @app: a #AsApp instance. * * Gets the application project license. * * Returns: string, or %NULL if unset * * Since: 0.1.4 **/ const gchar * as_app_get_metadata_license (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->metadata_license; } /** * as_app_get_update_contact: * @app: a #AsApp instance. * * Gets the application upstream update contact email. * * Returns: string, or %NULL if unset * * Since: 0.1.4 **/ const gchar * as_app_get_update_contact (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->update_contact; } /** * as_app_get_origin: * @app: a #AsApp instance. * * Gets the application origin. * * Returns: the origin string, or %NULL if unset * * Since: 0.3.2 **/ const gchar * as_app_get_origin (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->origin; } /** * as_app_get_source_file: * @app: a #AsApp instance. * * Gets the source filename the instance was populated from. * * NOTE: this is not set for %AS_APP_SOURCE_KIND_APPSTREAM entries. * * Returns: string, or %NULL if unset * * Since: 0.2.2 **/ const gchar * as_app_get_source_file (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); return priv->source_file; } /** * as_app_validate_utf8: **/ static gboolean as_app_validate_utf8 (const gchar *text) { gboolean is_whitespace = TRUE; guint i; /* nothing */ if (text == NULL) return TRUE; if (text[0] == '\0') return FALSE; /* is just whitespace */ for (i = 0; text[i] != '\0' && is_whitespace; i++) is_whitespace = g_ascii_isspace (text[i]); if (is_whitespace) return FALSE; /* standard UTF-8 checks */ if (!g_utf8_validate (text, -1, NULL)) return FALSE; /* additional check for xmllint */ for (i = 0; text[i] != '\0'; i++) { if (text[i] == 0x1f) return FALSE; } return TRUE; } /******************************************************************************/ /** * as_app_set_id: * @app: a #AsApp instance. * @id: the new _full_ application ID, e.g. "org.gnome.Software.desktop". * * Sets a new application ID. Any invalid characters will be automatically replaced. * * Since: 0.1.0 **/ void as_app_set_id (AsApp *app, const gchar *id) { AsAppPrivate *priv = GET_PRIVATE (app); gchar *tmp; /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (id)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } g_free (priv->id); g_free (priv->id_filename); priv->id = g_strdup (id); priv->id_filename = g_strdup (priv->id); g_strdelimit (priv->id_filename, "&<>", '-'); tmp = g_strrstr_len (priv->id_filename, -1, "."); if (tmp != NULL) *tmp = '\0'; } /** * as_app_set_source_kind: * @app: a #AsApp instance. * @source_kind: the #AsAppSourceKind. * * Sets the source kind. * * Since: 0.1.4 **/ void as_app_set_source_kind (AsApp *app, AsAppSourceKind source_kind) { AsAppPrivate *priv = GET_PRIVATE (app); priv->source_kind = source_kind; } /** * as_app_set_state: * @app: a #AsApp instance. * @state: the #AsAppState. * * Sets the application state. * * Since: 0.2.2 **/ void as_app_set_state (AsApp *app, AsAppState state) { AsAppPrivate *priv = GET_PRIVATE (app); priv->state = state; } /** * as_app_set_trust_flags: * @app: a #AsApp instance. * @trust_flags: the #AsAppSourceKind. * * Sets the check flags, where %AS_APP_TRUST_FLAG_COMPLETE is completely * trusted input. * * Since: 0.2.2 **/ void as_app_set_trust_flags (AsApp *app, AsAppTrustFlags trust_flags) { AsAppPrivate *priv = GET_PRIVATE (app); priv->trust_flags = trust_flags; } /** * as_app_set_id_kind: * @app: a #AsApp instance. * @id_kind: the #AsIdKind. * * Sets the application kind. * * Since: 0.1.0 **/ void as_app_set_id_kind (AsApp *app, AsIdKind id_kind) { AsAppPrivate *priv = GET_PRIVATE (app); priv->id_kind = id_kind; } /** * as_app_set_project_group: * @app: a #AsApp instance. * @project_group: the project group, e.g. "GNOME". * * Set any project affiliation. * * Since: 0.1.0 **/ void as_app_set_project_group (AsApp *app, const gchar *project_group) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (project_group)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } g_free (priv->project_group); priv->project_group = g_strdup (project_group); } /** * as_app_set_project_license: * @app: a #AsApp instance. * @project_license: the project license string. * * Set the project license. * * Since: 0.1.0 **/ void as_app_set_project_license (AsApp *app, const gchar *project_license) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (project_license)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } g_free (priv->project_license); priv->project_license = g_strdup (project_license); } /** * as_app_set_metadata_license: * @app: a #AsApp instance. * @metadata_license: the project license string. * * Set the project license. * * Since: 0.1.4 **/ void as_app_set_metadata_license (AsApp *app, const gchar *metadata_license) { AsAppPrivate *priv = GET_PRIVATE (app); g_auto(GStrv) tokens = NULL; /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (metadata_license)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } /* automatically replace deprecated license names */ g_free (priv->metadata_license); tokens = as_utils_spdx_license_tokenize (metadata_license); priv->metadata_license = as_utils_spdx_license_detokenize (tokens); } /** * as_app_set_source_pkgname: * @app: a #AsApp instance. * @source_pkgname: the project license string. * * Set the project license. * * Since: 0.2.4 **/ void as_app_set_source_pkgname (AsApp *app, const gchar *source_pkgname) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (source_pkgname)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } g_free (priv->source_pkgname); priv->source_pkgname = g_strdup (source_pkgname); } /** * as_app_set_source_file: * @app: a #AsApp instance. * @source_file: the filename. * * Set the file that the instance was sourced from. * * Since: 0.2.2 **/ void as_app_set_source_file (AsApp *app, const gchar *source_file) { AsAppPrivate *priv = GET_PRIVATE (app); g_free (priv->source_file); priv->source_file = g_strdup (source_file); } /** * as_app_set_update_contact: * @app: a #AsApp instance. * @update_contact: the project license string. * * Set the project license. * * Since: 0.1.4 **/ void as_app_set_update_contact (AsApp *app, const gchar *update_contact) { AsAppPrivate *priv = GET_PRIVATE (app); gboolean done_replacement = TRUE; gchar *tmp; gsize len; guint i; struct { const gchar *search; const gchar replace; } replacements[] = { { "(@)", '@' }, { " _at_ ", '@' }, { "_at_", '@' }, { "(at)", '@' }, { " AT ", '@' }, { "_dot_", '.' }, { " DOT ", '.' }, { NULL, '\0' } }; /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (update_contact)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } /* copy as-is */ g_free (priv->update_contact); priv->update_contact = g_strdup (update_contact); if (priv->update_contact == NULL) return; /* keep going until we have no more matches */ len = strlen (priv->update_contact); while (done_replacement) { done_replacement = FALSE; for (i = 0; replacements[i].search != NULL; i++) { tmp = g_strstr_len (priv->update_contact, -1, replacements[i].search); if (tmp != NULL) { *tmp = replacements[i].replace; g_strlcpy (tmp + 1, tmp + strlen (replacements[i].search), len); done_replacement = TRUE; } } } } /** * as_app_set_origin: * @app: a #AsApp instance. * @origin: the origin, e.g. "fedora-21" * * Sets the application origin. * * Since: 0.3.2 **/ void as_app_set_origin (AsApp *app, const gchar *origin) { AsAppPrivate *priv = GET_PRIVATE (app); g_free (priv->origin); priv->origin = g_strdup (origin); } /** * as_app_set_icon_path: * @app: a #AsApp instance. * @icon_path: the local path. * * Sets the icon path, where local icons would be found. * * Since: 0.1.0 **/ void as_app_set_icon_path (AsApp *app, const gchar *icon_path) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (icon_path)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } g_free (priv->icon_path); priv->icon_path = g_strdup (icon_path); } /** * as_app_set_name: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * @name: the application name. * * Sets the application name for a specific locale. * * Since: 0.1.0 **/ void as_app_set_name (AsApp *app, const gchar *locale, const gchar *name) { AsAppPrivate *priv = GET_PRIVATE (app); gchar *tmp_locale; /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (name)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } /* get fixed locale */ tmp_locale = as_node_fix_locale (locale); if (tmp_locale == NULL) return; g_hash_table_insert (priv->names, tmp_locale, g_strdup (name)); } /** * as_app_set_comment: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * @comment: the application summary. * * Sets the application summary for a specific locale. * * Since: 0.1.0 **/ void as_app_set_comment (AsApp *app, const gchar *locale, const gchar *comment) { AsAppPrivate *priv = GET_PRIVATE (app); gchar *tmp_locale; g_return_if_fail (comment != NULL); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (comment)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } /* get fixed locale */ tmp_locale = as_node_fix_locale (locale); if (tmp_locale == NULL) return; g_hash_table_insert (priv->comments, tmp_locale, g_strdup (comment)); } /** * as_app_set_developer_name: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * @developer_name: the application developer name. * * Sets the application developer name for a specific locale. * * Since: 0.1.0 **/ void as_app_set_developer_name (AsApp *app, const gchar *locale, const gchar *developer_name) { AsAppPrivate *priv = GET_PRIVATE (app); gchar *tmp_locale; g_return_if_fail (developer_name != NULL); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (developer_name)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } /* get fixed locale */ tmp_locale = as_node_fix_locale (locale); if (tmp_locale == NULL) return; g_hash_table_insert (priv->developer_names, tmp_locale, g_strdup (developer_name)); } /** * as_app_set_description: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * @description: the application description. * * Sets the application descrption markup for a specific locale. * * Since: 0.1.0 **/ void as_app_set_description (AsApp *app, const gchar *locale, const gchar *description) { AsAppPrivate *priv = GET_PRIVATE (app); gchar *tmp_locale; g_return_if_fail (description != NULL); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (description)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } /* get fixed locale */ tmp_locale = as_node_fix_locale (locale); if (tmp_locale == NULL) return; g_hash_table_insert (priv->descriptions, tmp_locale, g_strdup (description)); } /** * as_app_set_priority: * @app: a #AsApp instance. * @priority: the priority. * * Sets the application priority, where 0 is default and positive numbers * are better than negative numbers. * * Since: 0.1.0 **/ void as_app_set_priority (AsApp *app, gint priority) { AsAppPrivate *priv = GET_PRIVATE (app); priv->priority = priority; } /** * as_app_add_category: * @app: a #AsApp instance. * @category: the category. * * Adds a menu category to the application. * * Since: 0.1.0 **/ void as_app_add_category (AsApp *app, const gchar *category) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (category)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 && as_ptr_array_find_string (priv->categories, category)) { return; } /* simple substitution */ if (g_strcmp0 (category, "Feed") == 0) category = "News"; g_ptr_array_add (priv->categories, g_strdup (category)); } /** * as_app_add_compulsory_for_desktop: * @app: a #AsApp instance. * @compulsory_for_desktop: the desktop string, e.g. "GNOME". * * Adds a desktop that requires this application to be installed. * * Since: 0.1.0 **/ void as_app_add_compulsory_for_desktop (AsApp *app, const gchar *compulsory_for_desktop) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (compulsory_for_desktop)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 && as_ptr_array_find_string (priv->compulsory_for_desktops, compulsory_for_desktop)) { return; } g_ptr_array_add (priv->compulsory_for_desktops, g_strdup (compulsory_for_desktop)); } /** * as_app_add_keyword: * @app: a #AsApp instance. * @locale: the locale, or %NULL. e.g. "en_GB" * @keyword: the keyword. * * Add a keyword the application should match against. * * Since: 0.3.0 **/ void as_app_add_keyword (AsApp *app, const gchar *locale, const gchar *keyword) { AsAppPrivate *priv = GET_PRIVATE (app); GPtrArray *tmp; g_autofree gchar *tmp_locale = NULL; /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (keyword)) { return; } /* get fixed locale */ tmp_locale = as_node_fix_locale (locale); if (tmp_locale == NULL) return; /* create an array if required */ tmp = g_hash_table_lookup (priv->keywords, tmp_locale); if (tmp == NULL) { tmp = g_ptr_array_new_with_free_func (g_free); g_hash_table_insert (priv->keywords, g_strdup (tmp_locale), tmp); } else if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) { if (as_ptr_array_find_string (tmp, keyword)) return; } g_ptr_array_add (tmp, g_strdup (keyword)); } /** * as_app_add_kudo: * @app: a #AsApp instance. * @kudo: the kudo. * * Add a kudo the application has obtained. * * Since: 0.2.2 **/ void as_app_add_kudo (AsApp *app, const gchar *kudo) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (kudo)) { return; } if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 && as_ptr_array_find_string (priv->kudos, kudo)) { return; } g_ptr_array_add (priv->kudos, g_strdup (kudo)); } /** * as_app_add_permission: * @app: a #AsApp instance. * @permission: the permission. * * Add a permission the application has obtained. * * Since: 0.3.5 **/ void as_app_add_permission (AsApp *app, const gchar *permission) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (permission)) { return; } if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 && as_ptr_array_find_string (priv->permissions, permission)) { return; } g_ptr_array_add (priv->permissions, g_strdup (permission)); } /** * as_app_add_kudo_kind: * @app: a #AsApp instance. * @kudo_kind: the #AsKudoKind. * * Add a kudo the application has obtained. * * Since: 0.2.2 **/ void as_app_add_kudo_kind (AsApp *app, AsKudoKind kudo_kind) { as_app_add_kudo (app, as_kudo_kind_to_string (kudo_kind)); } /** * as_app_add_mimetype: * @app: a #AsApp instance. * @mimetype: the mimetype. * * Adds a mimetype the application can process. * * Since: 0.1.0 **/ void as_app_add_mimetype (AsApp *app, const gchar *mimetype) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (mimetype)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 && as_ptr_array_find_string (priv->mimetypes, mimetype)) { return; } g_ptr_array_add (priv->mimetypes, g_strdup (mimetype)); } /** * as_app_subsume_release: **/ static void as_app_subsume_release (AsRelease *release, AsRelease *donor) { AsChecksum *csum; AsChecksum *csum_tmp; GPtrArray *locations; GPtrArray *checksums; const gchar *tmp; guint i; /* this is high quality metadata */ tmp = as_release_get_description (donor, NULL); if (tmp != NULL) as_release_set_description (release, NULL, tmp); /* overwrite the timestamp if the metadata is high quality, * or if no timestamp has already been set */ if (tmp != NULL || as_release_get_timestamp (release) == 0) as_release_set_timestamp (release, as_release_get_timestamp (donor)); /* overwrite the version */ tmp = as_release_get_version (donor); if (tmp != NULL && as_release_get_version (release) == NULL) as_release_set_version (release, tmp); /* copy all locations */ locations = as_release_get_locations (donor); for (i = 0; i < locations->len; i++) { tmp = g_ptr_array_index (locations, i); as_release_add_location (release, tmp); } /* copy checksums if set */ checksums = as_release_get_checksums (donor); for (i = 0; i < checksums->len; i++) { csum = g_ptr_array_index (checksums, i); tmp = as_checksum_get_filename (csum); csum_tmp = as_release_get_checksum_by_fn (release, tmp); if (csum_tmp != NULL) continue; as_release_add_checksum (release, csum); } } /** * as_app_add_release: * @app: a #AsApp instance. * @release: a #AsRelease instance. * * Adds a release to an application. * * Since: 0.1.0 **/ void as_app_add_release (AsApp *app, AsRelease *release) { AsAppPrivate *priv = GET_PRIVATE (app); AsRelease *release_old; /* if already exists them update */ release_old = as_app_get_release (app, as_release_get_version (release)); if (release_old == NULL) release_old = as_app_get_release (app, NULL); if (release_old == release) return; if (release_old != NULL) { as_app_subsume_release (release_old, release); return; } g_ptr_array_add (priv->releases, g_object_ref (release)); } /** * as_app_add_provide: * @app: a #AsApp instance. * @provide: a #AsProvide instance. * * Adds a provide to an application. * * Since: 0.1.6 **/ void as_app_add_provide (AsApp *app, AsProvide *provide) { AsAppPrivate *priv = GET_PRIVATE (app); AsProvide *tmp; guint i; /* check for duplicates */ if (priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) { for (i = 0; i < priv->provides->len; i++) { tmp = g_ptr_array_index (priv->provides, i); if (as_provide_get_kind (tmp) == as_provide_get_kind (provide) && g_strcmp0 (as_provide_get_value (tmp), as_provide_get_value (provide)) == 0) return; } } g_ptr_array_add (priv->provides, g_object_ref (provide)); } /** * as_app_sort_screenshots: **/ static gint as_app_sort_screenshots (gconstpointer a, gconstpointer b) { AsScreenshot *ss1 = *((AsScreenshot **) a); AsScreenshot *ss2 = *((AsScreenshot **) b); if (as_screenshot_get_priority (ss1) < as_screenshot_get_priority (ss2)) return 1; if (as_screenshot_get_priority (ss1) > as_screenshot_get_priority (ss2)) return -1; return g_strcmp0 (as_screenshot_get_caption (ss1, NULL), as_screenshot_get_caption (ss2, NULL)); } /** * as_app_add_screenshot: * @app: a #AsApp instance. * @screenshot: a #AsScreenshot instance. * * Adds a screenshot to an application. * * Since: 0.1.0 **/ void as_app_add_screenshot (AsApp *app, AsScreenshot *screenshot) { AsAppPrivate *priv = GET_PRIVATE (app); AsScreenshot *ss; guint i; /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) { for (i = 0; i < priv->screenshots->len; i++) { ss = g_ptr_array_index (priv->screenshots, i); if (ss == screenshot) return; } } /* add then resort */ g_ptr_array_add (priv->screenshots, g_object_ref (screenshot)); g_ptr_array_sort (priv->screenshots, as_app_sort_screenshots); /* make only the first screenshot default */ for (i = 0; i < priv->screenshots->len; i++) { ss = g_ptr_array_index (priv->screenshots, i); as_screenshot_set_kind (ss, i == 0 ? AS_SCREENSHOT_KIND_DEFAULT : AS_SCREENSHOT_KIND_NORMAL); } } /** * as_app_check_icon_duplicate: **/ static gboolean as_app_check_icon_duplicate (AsIcon *icon1, AsIcon *icon2) { if (as_icon_get_width (icon1) != as_icon_get_width (icon2)) return FALSE; if (as_icon_get_height (icon1) != as_icon_get_height (icon2)) return FALSE; if (g_strcmp0 (as_icon_get_name (icon1), as_icon_get_name (icon2)) != 0) return FALSE; return TRUE; } /** * as_app_check_bundle_duplicate: **/ static gboolean as_app_check_bundle_duplicate (AsBundle *bundle1, AsBundle *bundle2) { if (as_bundle_get_kind (bundle1) != as_bundle_get_kind (bundle2)) return FALSE; if (g_strcmp0 (as_bundle_get_id (bundle1), as_bundle_get_id (bundle2)) != 0) return FALSE; return TRUE; } /** * as_app_add_icon: * @app: a #AsApp instance. * @icon: a #AsIcon instance. * * Adds a icon to an application. * * Since: 0.3.1 **/ void as_app_add_icon (AsApp *app, AsIcon *icon) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) { AsIcon *ic_tmp; guint i; for (i = 0; i < priv->icons->len; i++) { ic_tmp = g_ptr_array_index (priv->icons, i); if (as_app_check_icon_duplicate (icon, ic_tmp)) return; } } /* assume that desktop stock icons are available in HiDPI sizes */ if (as_icon_get_kind (icon) == AS_ICON_KIND_STOCK) { switch (priv->id_kind) { case AS_ID_KIND_DESKTOP: as_app_add_kudo_kind (app, AS_KUDO_KIND_HI_DPI_ICON); break; default: break; } } g_ptr_array_add (priv->icons, g_object_ref (icon)); } /** * as_app_add_bundle: * @app: a #AsApp instance. * @bundle: a #AsBundle instance. * * Adds a bundle to an application. * * Since: 0.3.5 **/ void as_app_add_bundle (AsApp *app, AsBundle *bundle) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) { AsBundle *bu_tmp; guint i; for (i = 0; i < priv->bundles->len; i++) { bu_tmp = g_ptr_array_index (priv->bundles, i); if (as_app_check_bundle_duplicate (bundle, bu_tmp)) return; } } g_ptr_array_add (priv->bundles, g_object_ref (bundle)); } /** * as_app_add_pkgname: * @app: a #AsApp instance. * @pkgname: the package name. * * Adds a package name to an application. * * Since: 0.1.0 **/ void as_app_add_pkgname (AsApp *app, const gchar *pkgname) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (pkgname)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 && as_ptr_array_find_string (priv->pkgnames, pkgname)) { return; } g_ptr_array_add (priv->pkgnames, g_strdup (pkgname)); } /** * as_app_add_arch: * @app: a #AsApp instance. * @arch: the package name. * * Adds a package name to an application. * * Since: 0.1.1 **/ void as_app_add_arch (AsApp *app, const gchar *arch) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (arch)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 && as_ptr_array_find_string (priv->architectures, arch)) { return; } g_ptr_array_add (priv->architectures, g_strdup (arch)); } /** * as_app_add_language: * @app: a #AsApp instance. * @percentage: the percentage completion of the translation, or 0 for unknown * @locale: the locale, or %NULL. e.g. "en_GB" * * Adds a language to the application. * * Since: 0.1.0 **/ void as_app_add_language (AsApp *app, gint percentage, const gchar *locale) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (locale)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } if (locale == NULL) locale = "C"; g_hash_table_insert (priv->languages, g_strdup (locale), GINT_TO_POINTER (percentage)); } /** * as_app_add_url: * @app: a #AsApp instance. * @url_kind: the URL kind, e.g. %AS_URL_KIND_HOMEPAGE * @url: the full URL. * * Adds some URL data to the application. * * Since: 0.1.0 **/ void as_app_add_url (AsApp *app, AsUrlKind url_kind, const gchar *url) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (url)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } g_hash_table_insert (priv->urls, g_strdup (as_url_kind_to_string (url_kind)), g_strdup (url)); } /** * as_app_add_metadata: * @app: a #AsApp instance. * @key: the metadata key. * @value: the value to store. * * Adds a metadata entry to the application. * * Since: 0.1.0 **/ void as_app_add_metadata (AsApp *app, const gchar *key, const gchar *value) { AsAppPrivate *priv = GET_PRIVATE (app); g_return_if_fail (key != NULL); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (value)) { return; } if (value == NULL) value = ""; g_hash_table_insert (priv->metadata, g_strdup (key), g_strdup (value)); } /** * as_app_remove_metadata: * @app: a #AsApp instance. * @key: the metadata key. * * Removes a metadata item from the application. * * Since: 0.1.0 **/ void as_app_remove_metadata (AsApp *app, const gchar *key) { AsAppPrivate *priv = GET_PRIVATE (app); g_hash_table_remove (priv->metadata, key); } /** * as_app_add_extends: * @app: a #AsApp instance. * @extends: the full ID, e.g. "eclipse.desktop". * * Adds a parent ID to the application. * * Since: 0.1.7 **/ void as_app_add_extends (AsApp *app, const gchar *extends) { AsAppPrivate *priv = GET_PRIVATE (app); /* handle untrusted */ if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 && !as_app_validate_utf8 (extends)) { priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8; return; } if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 && as_ptr_array_find_string (priv->extends, extends)) { return; } /* we can never extend ourself */ if (g_strcmp0 (priv->id, extends) == 0) return; g_ptr_array_add (priv->extends, g_strdup (extends)); } /** * as_app_add_addon: * @app: a #AsApp instance. * @addon: a #AsApp instance. * * Adds a addon to an application. * * Since: 0.1.7 **/ void as_app_add_addon (AsApp *app, AsApp *addon) { AsAppPrivate *priv = GET_PRIVATE (app); g_ptr_array_add (priv->addons, g_object_ref (addon)); } /******************************************************************************/ /** * as_app_subsume_dict: **/ static void as_app_subsume_dict (GHashTable *dest, GHashTable *src, gboolean overwrite) { GList *l; const gchar *tmp; const gchar *key; const gchar *value; g_autoptr(GList) keys = NULL; keys = g_hash_table_get_keys (src); for (l = keys; l != NULL; l = l->next) { key = l->data; if (!overwrite) { tmp = g_hash_table_lookup (dest, key); if (tmp != NULL) continue; } value = g_hash_table_lookup (src, key); g_hash_table_insert (dest, g_strdup (key), g_strdup (value)); } } /** * as_app_subsume_keywords: **/ static void as_app_subsume_keywords (AsApp *app, AsApp *donor, gboolean overwrite) { AsAppPrivate *priv = GET_PRIVATE (donor); GList *l; GPtrArray *array; const gchar *key; const gchar *tmp; guint i; g_autoptr(GList) keys = NULL; /* get all locales in the keywords dict */ keys = g_hash_table_get_keys (priv->keywords); for (l = keys; l != NULL; l = l->next) { key = l->data; if (!overwrite) { array = as_app_get_keywords (app, key); if (array != NULL) continue; } /* get the array of keywords for the specific locale */ array = g_hash_table_lookup (priv->keywords, key); if (array == NULL) continue; for (i = 0; i < array->len; i++) { tmp = g_ptr_array_index (array, i); as_app_add_keyword (app, key, tmp); } } } /** * as_app_subsume_icon: **/ static void as_app_subsume_icon (AsApp *app, AsIcon *icon) { AsAppPrivate *priv = GET_PRIVATE (app); AsIcon *ic_tmp; guint i; /* don't add a rubbish icon */ if (as_icon_get_kind (icon) == AS_ICON_KIND_UNKNOWN) return; /* does application already have this icon in this size */ for (i = 0; i < priv->icons->len; i++) { ic_tmp = AS_ICON (g_ptr_array_index (priv->icons, i)); if (as_icon_get_height (ic_tmp) != as_icon_get_height (icon)) continue; if (as_icon_get_width (ic_tmp) != as_icon_get_width (icon)) continue; if (g_strcmp0 (as_icon_get_name (ic_tmp), as_icon_get_name (icon)) != 0) continue; return; } as_app_add_icon (app, icon); } /** * as_app_subsume_private: **/ static void as_app_subsume_private (AsApp *app, AsApp *donor, AsAppSubsumeFlags flags) { AsAppPrivate *priv = GET_PRIVATE (donor); AsAppPrivate *papp = GET_PRIVATE (app); AsBundle *bundle; AsScreenshot *ss; AsProvide *pr; const gchar *tmp; const gchar *key; gboolean overwrite; guint i; gint percentage; GList *l; g_autoptr(GList) keys = NULL; /* stop us shooting ourselves in the foot */ papp->trust_flags |= AS_APP_TRUST_FLAG_CHECK_DUPLICATES; overwrite = (flags & AS_APP_SUBSUME_FLAG_NO_OVERWRITE) == 0; /* id-kind */ if (papp->id_kind == AS_ID_KIND_UNKNOWN) as_app_set_id_kind (app, priv->id_kind); /* AppData or AppStream can overwrite the id-kind of desktop files */ if ((priv->source_kind == AS_APP_SOURCE_KIND_APPDATA || priv->source_kind == AS_APP_SOURCE_KIND_APPSTREAM) && papp->source_kind == AS_APP_SOURCE_KIND_DESKTOP) as_app_set_id_kind (app, priv->id_kind); /* state */ if (papp->state == AS_APP_STATE_UNKNOWN) as_app_set_state (app, priv->state); /* pkgnames */ for (i = 0; i < priv->pkgnames->len; i++) { tmp = g_ptr_array_index (priv->pkgnames, i); as_app_add_pkgname (app, tmp); } /* bundles */ for (i = 0; i < priv->bundles->len; i++) { bundle = g_ptr_array_index (priv->bundles, i); as_app_add_bundle (app, bundle); } /* releases */ for (i = 0; i < priv->releases->len; i++) { AsRelease *rel= g_ptr_array_index (priv->releases, i); as_app_add_release (app, rel); } /* kudos */ for (i = 0; i < priv->kudos->len; i++) { tmp = g_ptr_array_index (priv->kudos, i); as_app_add_kudo (app, tmp); } /* categories */ for (i = 0; i < priv->categories->len; i++) { tmp = g_ptr_array_index (priv->categories, i); as_app_add_category (app, tmp); } /* permissions */ for (i = 0; i < priv->permissions->len; i++) { tmp = g_ptr_array_index (priv->permissions, i); as_app_add_permission (app, tmp); } /* extends */ for (i = 0; i < priv->extends->len; i++) { tmp = g_ptr_array_index (priv->extends, i); as_app_add_extends (app, tmp); } /* compulsory_for_desktops */ for (i = 0; i < priv->compulsory_for_desktops->len; i++) { tmp = g_ptr_array_index (priv->compulsory_for_desktops, i); as_app_add_compulsory_for_desktop (app, tmp); } /* screenshots */ for (i = 0; i < priv->screenshots->len; i++) { ss = g_ptr_array_index (priv->screenshots, i); as_app_add_screenshot (app, ss); } /* provides */ for (i = 0; i < priv->provides->len; i++) { pr = g_ptr_array_index (priv->provides, i); as_app_add_provide (app, pr); } /* icons */ for (i = 0; i < priv->icons->len; i++) { AsIcon *ic = g_ptr_array_index (priv->icons, i); as_app_subsume_icon (app, ic); } /* mimetypes */ for (i = 0; i < priv->mimetypes->len; i++) { tmp = g_ptr_array_index (priv->mimetypes, i); as_app_add_mimetype (app, tmp); } /* do not subsume all properties */ if ((flags & AS_APP_SUBSUME_FLAG_PARTIAL) > 0) return; /* vetos */ for (i = 0; i < priv->vetos->len; i++) { tmp = g_ptr_array_index (priv->vetos, i); as_app_add_veto (app, "%s", tmp); } /* languages */ keys = g_hash_table_get_keys (priv->languages); for (l = keys; l != NULL; l = l->next) { key = l->data; if (flags & AS_APP_SUBSUME_FLAG_NO_OVERWRITE) { percentage = as_app_get_language (app, key); if (percentage >= 0) continue; } percentage = GPOINTER_TO_INT (g_hash_table_lookup (priv->languages, key)); as_app_add_language (app, percentage, key); } /* dictionaries */ as_app_subsume_dict (papp->names, priv->names, overwrite); as_app_subsume_dict (papp->comments, priv->comments, overwrite); as_app_subsume_dict (papp->developer_names, priv->developer_names, overwrite); as_app_subsume_dict (papp->descriptions, priv->descriptions, overwrite); as_app_subsume_dict (papp->metadata, priv->metadata, overwrite); as_app_subsume_dict (papp->urls, priv->urls, overwrite); as_app_subsume_keywords (app, donor, overwrite); /* source */ if (priv->source_file != NULL) as_app_set_source_file (app, priv->source_file); /* source_pkgname */ if (priv->source_pkgname != NULL) as_app_set_source_pkgname (app, priv->source_pkgname); /* origin */ if (priv->origin != NULL) as_app_set_origin (app, priv->origin); /* licenses */ if (priv->project_license != NULL) as_app_set_project_license (app, priv->project_license); if (priv->metadata_license != NULL) as_app_set_metadata_license (app, priv->metadata_license); /* project_group */ if (priv->project_group != NULL) as_app_set_project_group (app, priv->project_group); } /** * as_app_subsume_full: * @app: a #AsApp instance. * @donor: the donor. * @flags: any optional flags, e.g. %AS_APP_SUBSUME_FLAG_NO_OVERWRITE * * Copies information from the donor to the application object. * * Since: 0.1.4 **/ void as_app_subsume_full (AsApp *app, AsApp *donor, AsAppSubsumeFlags flags) { g_assert (app != donor); /* two way sync implies no overwriting */ if ((flags & AS_APP_SUBSUME_FLAG_BOTH_WAYS) > 0) flags |= AS_APP_SUBSUME_FLAG_NO_OVERWRITE; /* one way sync */ as_app_subsume_private (app, donor, flags); /* and back again */ if ((flags & AS_APP_SUBSUME_FLAG_BOTH_WAYS) > 0) as_app_subsume_private (donor, app, flags); } /** * as_app_subsume: * @app: a #AsApp instance. * @donor: the donor. * * Copies information from the donor to the application object. * * Since: 0.1.0 **/ void as_app_subsume (AsApp *app, AsApp *donor) { as_app_subsume_full (app, donor, AS_APP_SUBSUME_FLAG_NONE); } /** * gs_app_node_language_sort_cb: **/ static gint gs_app_node_language_sort_cb (gconstpointer a, gconstpointer b) { return g_strcmp0 ((const gchar *) a, (const gchar *) b); } /** * as_app_node_insert_languages: **/ static void as_app_node_insert_languages (AsApp *app, GNode *parent) { GNode *node_tmp; GList *l; const gchar *locale; gchar tmp[4]; gint percentage; g_autoptr(GList) langs = NULL; node_tmp = as_node_insert (parent, "languages", NULL, 0, NULL); langs = as_app_get_languages (app); langs = g_list_sort (langs, gs_app_node_language_sort_cb); for (l = langs; l != NULL; l = l->next) { locale = l->data; percentage = as_app_get_language (app, locale); if (percentage == 0) { as_node_insert (node_tmp, "lang", locale, 0, NULL); } else { g_snprintf (tmp, sizeof (tmp), "%i", percentage); as_node_insert (node_tmp, "lang", locale, 0, "percentage", tmp, NULL); } } } /** * as_app_ptr_array_sort_cb: **/ static gint as_app_ptr_array_sort_cb (gconstpointer a, gconstpointer b) { return g_strcmp0 (*((const gchar **) a), *((const gchar **) b)); } /** * as_app_releases_sort_cb: **/ static gint as_app_releases_sort_cb (gconstpointer a, gconstpointer b) { AsRelease **rel1 = (AsRelease **) a; AsRelease **rel2 = (AsRelease **) b; return as_release_vercmp (*rel1, *rel2); } /** * as_app_icons_sort_cb: **/ static gint as_app_icons_sort_cb (gconstpointer a, gconstpointer b) { AsIcon **ic1 = (AsIcon **) a; AsIcon **ic2 = (AsIcon **) b; return g_strcmp0 (as_icon_get_name (*ic1), as_icon_get_name (*ic2)); } /** * as_app_list_sort_cb: **/ static gint as_app_list_sort_cb (gconstpointer a, gconstpointer b) { return g_strcmp0 ((const gchar *) a, (const gchar *) b); } /** * as_app_node_insert_keywords: **/ static void as_app_node_insert_keywords (AsApp *app, GNode *parent, AsNodeContext *ctx) { AsAppPrivate *priv = GET_PRIVATE (app); GList *keys; GList *l; GNode *node_tmp; GPtrArray *keywords; const gchar *lang; const gchar *tmp; guint i; g_autoptr(GHashTable) already_in_c = NULL; /* don't add localized keywords that already exist in C, e.g. * there's no point adding "c++" in 14 different languages */ already_in_c = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); keywords = g_hash_table_lookup (priv->keywords, "C"); if (keywords != NULL) { for (i = 0; i < keywords->len; i++) { tmp = g_ptr_array_index (keywords, i); g_hash_table_insert (already_in_c, g_strdup (tmp), GINT_TO_POINTER (1)); } } keys = g_hash_table_get_keys (priv->keywords); keys = g_list_sort (keys, as_app_list_sort_cb); for (l = keys; l != NULL; l = l->next) { lang = l->data; keywords = g_hash_table_lookup (priv->keywords, lang); g_ptr_array_sort (keywords, as_app_ptr_array_sort_cb); for (i = 0; i < keywords->len; i++) { tmp = g_ptr_array_index (keywords, i); if (tmp == NULL) continue; if (g_strcmp0 (lang, "C") != 0 && g_hash_table_lookup (already_in_c, tmp) != NULL) continue; node_tmp = as_node_insert (parent, "keyword", tmp, 0, NULL); if (g_strcmp0 (lang, "C") != 0) { as_node_add_attribute (node_tmp, "xml:lang", lang); } } } g_list_free (keys); } /** * as_app_node_insert: (skip) * @app: a #AsApp instance. * @parent: the parent #GNode to use.. * @ctx: the #AsNodeContext * * Inserts the application into the DOM tree. * * Returns: (transfer none): A populated #GNode, or %NULL * * Since: 0.1.0 **/ GNode * as_app_node_insert (AsApp *app, GNode *parent, AsNodeContext *ctx) { AsAppPrivate *priv = GET_PRIVATE (app); AsRelease *rel; AsScreenshot *ss; GNode *node_app; GNode *node_tmp; const gchar *tmp; guint i; /* or */ node_app = as_node_insert (parent, "component", NULL, 0, NULL); if (priv->id_kind != AS_ID_KIND_UNKNOWN) { as_node_add_attribute (node_app, "type", as_id_kind_to_string (priv->id_kind)); } /* */ as_node_insert (node_app, "id", priv->id, 0, NULL); /* */ if (priv->priority != 0) as_node_add_attribute_as_int (node_app, "priority", priv->priority); /* */ g_ptr_array_sort (priv->pkgnames, as_app_ptr_array_sort_cb); for (i = 0; i < priv->pkgnames->len; i++) { tmp = g_ptr_array_index (priv->pkgnames, i); as_node_insert (node_app, "pkgname", tmp, 0, NULL); } /* */ if (priv->source_pkgname != NULL) { as_node_insert (node_app, "source_pkgname", priv->source_pkgname, 0, NULL); } /* */ for (i = 0; i < priv->bundles->len; i++) { AsBundle *bu = g_ptr_array_index (priv->bundles, i); as_bundle_node_insert (bu, node_app, ctx); } /* */ as_node_insert_localized (node_app, "name", priv->names, AS_NODE_INSERT_FLAG_DEDUPE_LANG); /* */ as_node_insert_localized (node_app, "summary", priv->comments, AS_NODE_INSERT_FLAG_DEDUPE_LANG); /* */ as_node_insert_localized (node_app, "developer_name", priv->developer_names, AS_NODE_INSERT_FLAG_DEDUPE_LANG); /* */ as_node_insert_localized (node_app, "description", priv->descriptions, AS_NODE_INSERT_FLAG_PRE_ESCAPED | AS_NODE_INSERT_FLAG_DEDUPE_LANG); /* */ g_ptr_array_sort (priv->icons, as_app_icons_sort_cb); for (i = 0; i < priv->icons->len; i++) { AsIcon *ic = g_ptr_array_index (priv->icons, i); as_icon_node_insert (ic, node_app, ctx); } /* */ if (priv->categories->len > 0) { g_ptr_array_sort (priv->categories, as_app_ptr_array_sort_cb); node_tmp = as_node_insert (node_app, "categories", NULL, 0, NULL); for (i = 0; i < priv->categories->len; i++) { tmp = g_ptr_array_index (priv->categories, i); as_node_insert (node_tmp, "category", tmp, 0, NULL); } } /* */ if (priv->architectures->len > 0) { g_ptr_array_sort (priv->architectures, as_app_ptr_array_sort_cb); node_tmp = as_node_insert (node_app, "architectures", NULL, 0, NULL); for (i = 0; i < priv->architectures->len; i++) { tmp = g_ptr_array_index (priv->architectures, i); as_node_insert (node_tmp, "arch", tmp, 0, NULL); } } /* */ if (g_hash_table_size (priv->keywords) > 0) { node_tmp = as_node_insert (node_app, "keywords", NULL, 0, NULL); as_app_node_insert_keywords (app, node_tmp, ctx); } /* */ if (priv->kudos->len > 0) { g_ptr_array_sort (priv->kudos, as_app_ptr_array_sort_cb); node_tmp = as_node_insert (node_app, "kudos", NULL, 0, NULL); for (i = 0; i < priv->kudos->len; i++) { tmp = g_ptr_array_index (priv->kudos, i); as_node_insert (node_tmp, "kudo", tmp, 0, NULL); } } /* */ if (priv->permissions->len > 0) { g_ptr_array_sort (priv->permissions, as_app_ptr_array_sort_cb); node_tmp = as_node_insert (node_app, "permissions", NULL, 0, NULL); for (i = 0; i < priv->permissions->len; i++) { tmp = g_ptr_array_index (priv->permissions, i); as_node_insert (node_tmp, "permission", tmp, 0, NULL); } } /* */ if (priv->vetos->len > 0) { g_ptr_array_sort (priv->vetos, as_app_ptr_array_sort_cb); node_tmp = as_node_insert (node_app, "vetos", NULL, 0, NULL); for (i = 0; i < priv->vetos->len; i++) { tmp = g_ptr_array_index (priv->vetos, i); as_node_insert (node_tmp, "veto", tmp, 0, NULL); } } /* */ if (priv->mimetypes->len > 0) { g_ptr_array_sort (priv->mimetypes, as_app_ptr_array_sort_cb); node_tmp = as_node_insert (node_app, "mimetypes", NULL, 0, NULL); for (i = 0; i < priv->mimetypes->len; i++) { tmp = g_ptr_array_index (priv->mimetypes, i); as_node_insert (node_tmp, "mimetype", tmp, 0, NULL); } } /* */ if (as_node_context_get_output (ctx) == AS_APP_SOURCE_KIND_APPDATA || as_node_context_get_output (ctx) == AS_APP_SOURCE_KIND_METAINFO) { if (priv->metadata_license != NULL) { as_node_insert (node_app, "metadata_license", priv->metadata_license, 0, NULL); } } /* */ if (priv->project_license != NULL) { as_node_insert (node_app, "project_license", priv->project_license, 0, NULL); } /* */ as_node_insert_hash (node_app, "url", "type", priv->urls, 0); /* */ if (priv->project_group != NULL) { as_node_insert (node_app, "project_group", priv->project_group, 0, NULL); } /* */ if (priv->compulsory_for_desktops != NULL) { g_ptr_array_sort (priv->compulsory_for_desktops, as_app_ptr_array_sort_cb); for (i = 0; i < priv->compulsory_for_desktops->len; i++) { tmp = g_ptr_array_index (priv->compulsory_for_desktops, i); as_node_insert (node_app, "compulsory_for_desktop", tmp, 0, NULL); } } /* */ if (priv->extends->len > 0) { g_ptr_array_sort (priv->extends, as_app_ptr_array_sort_cb); for (i = 0; i < priv->extends->len; i++) { tmp = g_ptr_array_index (priv->extends, i); as_node_insert (node_app, "extends", tmp, 0, NULL); } } /* */ if (priv->screenshots->len > 0) { node_tmp = as_node_insert (node_app, "screenshots", NULL, 0, NULL); for (i = 0; i < priv->screenshots->len; i++) { ss = g_ptr_array_index (priv->screenshots, i); as_screenshot_node_insert (ss, node_tmp, ctx); } } /* */ if (priv->releases->len > 0) { g_ptr_array_sort (priv->releases, as_app_releases_sort_cb); node_tmp = as_node_insert (node_app, "releases", NULL, 0, NULL); for (i = 0; i < priv->releases->len && i < 3; i++) { rel = g_ptr_array_index (priv->releases, i); as_release_node_insert (rel, node_tmp, ctx); } } /* */ if (priv->provides->len > 0) { AsProvide *provide; node_tmp = as_node_insert (node_app, "provides", NULL, 0, NULL); for (i = 0; i < priv->provides->len; i++) { provide = g_ptr_array_index (priv->provides, i); as_provide_node_insert (provide, node_tmp, ctx); } } /* */ if (g_hash_table_size (priv->languages) > 0) as_app_node_insert_languages (app, node_app); /* */ if (as_node_context_get_output (ctx) == AS_APP_SOURCE_KIND_APPDATA || as_node_context_get_output (ctx) == AS_APP_SOURCE_KIND_METAINFO) { if (priv->update_contact != NULL) { as_node_insert (node_app, "update_contact", priv->update_contact, 0, NULL); } } /* */ if (g_hash_table_size (priv->metadata) > 0) { node_tmp = as_node_insert (node_app, "metadata", NULL, 0, NULL); as_node_insert_hash (node_tmp, "value", "key", priv->metadata, FALSE); } return node_app; } /** * as_app_node_parse_child: **/ static gboolean as_app_node_parse_child (AsApp *app, GNode *n, AsAppParseFlags flags, AsNodeContext *ctx, GError **error) { AsAppPrivate *priv = GET_PRIVATE (app); GNode *c; const gchar *tmp; gchar *taken; switch (as_node_get_tag (n)) { /* */ case AS_TAG_ID: if (as_node_get_attribute (n, "xml:lang") != NULL) { priv->problems |= AS_APP_PROBLEM_TRANSLATED_ID; break; } tmp = as_node_get_attribute (n, "type"); if (tmp != NULL) as_app_set_id_kind (app, as_id_kind_from_string (tmp)); as_app_set_id (app, as_node_get_data (n)); break; /* */ case AS_TAG_PRIORITY: as_app_set_priority (app, g_ascii_strtoll (as_node_get_data (n), NULL, 10)); break; /* */ case AS_TAG_PKGNAME: g_ptr_array_add (priv->pkgnames, as_node_take_data (n)); break; /* */ case AS_TAG_BUNDLE: { g_autoptr(AsBundle) ic = NULL; ic = as_bundle_new (); if (!as_bundle_node_parse (ic, n, ctx, error)) return FALSE; as_app_add_bundle (app, ic); break; } /* */ case AS_TAG_NAME: taken = as_node_fix_locale (as_node_get_attribute (n, "xml:lang")); if (taken == NULL) break; g_hash_table_insert (priv->names, taken, as_node_take_data (n)); break; /* */ case AS_TAG_SUMMARY: taken = as_node_fix_locale (as_node_get_attribute (n, "xml:lang")); if (taken == NULL) break; g_hash_table_insert (priv->comments, taken, as_node_take_data (n)); break; /* */ case AS_TAG_DEVELOPER_NAME: taken = as_node_fix_locale (as_node_get_attribute (n, "xml:lang")); if (taken == NULL) break; g_hash_table_insert (priv->developer_names, taken, as_node_take_data (n)); break; /* */ case AS_TAG_DESCRIPTION: /* unwrap appdata inline */ if (priv->source_kind == AS_APP_SOURCE_KIND_APPDATA) { GError *error_local = NULL; g_autoptr(GHashTable) unwrapped = NULL; unwrapped = as_node_get_localized_unwrap (n, &error_local); if (unwrapped == NULL) { if (g_error_matches (error_local, AS_NODE_ERROR, AS_NODE_ERROR_INVALID_MARKUP)) { g_autoptr(GString) debug = NULL; debug = as_node_to_xml (n, AS_NODE_TO_XML_FLAG_NONE); g_warning ("ignoring description '%s' from %s: %s", debug->str, as_app_get_source_file (app), error_local->message); g_error_free (error_local); break; } g_propagate_error (error, error_local); return FALSE; } as_app_subsume_dict (priv->descriptions, unwrapped, FALSE); break; } if (n->children == NULL) { /* pre-formatted */ priv->problems |= AS_APP_PROBLEM_PREFORMATTED_DESCRIPTION; as_app_set_description (app, as_node_get_attribute (n, "xml:lang"), as_node_get_data (n)); } else { g_autoptr(GString) xml = NULL; xml = as_node_to_xml (n->children, AS_NODE_TO_XML_FLAG_INCLUDE_SIBLINGS); as_app_set_description (app, as_node_get_attribute (n, "xml:lang"), xml->str); } break; /* */ case AS_TAG_ICON: { g_autoptr(AsIcon) ic = NULL; ic = as_icon_new (); as_icon_set_prefix (ic, priv->icon_path); if (!as_icon_node_parse (ic, n, ctx, error)) return FALSE; as_app_add_icon (app, ic); break; } /* */ case AS_TAG_CATEGORIES: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_ptr_array_set_size (priv->categories, 0); for (c = n->children; c != NULL; c = c->next) { if (as_node_get_tag (c) != AS_TAG_CATEGORY) continue; taken = as_node_take_data (c); if (taken == NULL) continue; g_ptr_array_add (priv->categories, taken); } break; /* */ case AS_TAG_ARCHITECTURES: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_ptr_array_set_size (priv->architectures, 0); for (c = n->children; c != NULL; c = c->next) { if (as_node_get_tag (c) != AS_TAG_ARCH) continue; taken = as_node_take_data (c); if (taken == NULL) continue; g_ptr_array_add (priv->architectures, taken); } break; /* */ case AS_TAG_KEYWORDS: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_hash_table_remove_all (priv->keywords); for (c = n->children; c != NULL; c = c->next) { if (as_node_get_tag (c) != AS_TAG_KEYWORD) continue; tmp = as_node_get_data (c); if (tmp == NULL) continue; taken = as_node_fix_locale (as_node_get_attribute (c, "xml:lang")); if (taken == NULL) continue; as_app_add_keyword (app, taken, tmp); g_free (taken); } break; /* */ case AS_TAG_KUDOS: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_ptr_array_set_size (priv->kudos, 0); for (c = n->children; c != NULL; c = c->next) { if (as_node_get_tag (c) != AS_TAG_KUDO) continue; taken = as_node_take_data (c); if (taken == NULL) continue; g_ptr_array_add (priv->kudos, taken); } break; /* */ case AS_TAG_PERMISSIONS: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_ptr_array_set_size (priv->permissions, 0); for (c = n->children; c != NULL; c = c->next) { if (as_node_get_tag (c) != AS_TAG_PERMISSION) continue; taken = as_node_take_data (c); if (taken == NULL) continue; g_ptr_array_add (priv->permissions, taken); } break; /* */ case AS_TAG_VETOS: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_ptr_array_set_size (priv->vetos, 0); for (c = n->children; c != NULL; c = c->next) { if (as_node_get_tag (c) != AS_TAG_VETO) continue; taken = as_node_take_data (c); if (taken == NULL) continue; g_ptr_array_add (priv->vetos, taken); } break; /* */ case AS_TAG_MIMETYPES: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_ptr_array_set_size (priv->mimetypes, 0); for (c = n->children; c != NULL; c = c->next) { if (as_node_get_tag (c) != AS_TAG_MIMETYPE) continue; taken = as_node_take_data (c); if (taken == NULL) continue; g_ptr_array_add (priv->mimetypes, taken); } break; /* */ case AS_TAG_PROJECT_LICENSE: if (as_node_get_attribute (n, "xml:lang") != NULL) { priv->problems |= AS_APP_PROBLEM_TRANSLATED_LICENSE; break; } g_free (priv->project_license); priv->project_license = as_node_take_data (n); break; /* */ case AS_TAG_METADATA_LICENSE: if (as_node_get_attribute (n, "xml:lang") != NULL) { priv->problems |= AS_APP_PROBLEM_TRANSLATED_LICENSE; break; } as_app_set_metadata_license (app, as_node_get_data (n)); break; /* */ case AS_TAG_SOURCE_PKGNAME: as_app_set_source_pkgname (app, as_node_get_data (n)); break; /* */ case AS_TAG_UPDATE_CONTACT: as_app_set_update_contact (app, as_node_get_data (n)); break; /* */ case AS_TAG_URL: tmp = as_node_get_attribute (n, "type"); as_app_add_url (app, as_url_kind_from_string (tmp), as_node_get_data (n)); break; /* */ case AS_TAG_PROJECT_GROUP: if (as_node_get_attribute (n, "xml:lang") != NULL) { priv->problems |= AS_APP_PROBLEM_TRANSLATED_PROJECT_GROUP; break; } g_free (priv->project_group); priv->project_group = as_node_take_data (n); break; /* */ case AS_TAG_COMPULSORY_FOR_DESKTOP: g_ptr_array_add (priv->compulsory_for_desktops, as_node_take_data (n)); break; /* */ case AS_TAG_EXTENDS: g_ptr_array_add (priv->extends, as_node_take_data (n)); break; /* */ case AS_TAG_SCREENSHOTS: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_ptr_array_set_size (priv->screenshots, 0); for (c = n->children; c != NULL; c = c->next) { g_autoptr(AsScreenshot) ss = NULL; if (as_node_get_tag (c) != AS_TAG_SCREENSHOT) continue; /* we don't yet support localised screenshots */ if (as_node_get_attribute (c, "xml:lang") != NULL) continue; ss = as_screenshot_new (); if (!as_screenshot_node_parse (ss, c, ctx, error)) return FALSE; as_app_add_screenshot (app, ss); } break; /* */ case AS_TAG_RELEASES: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_ptr_array_set_size (priv->releases, 0); for (c = n->children; c != NULL; c = c->next) { g_autoptr(AsRelease) r = NULL; if (as_node_get_tag (c) != AS_TAG_RELEASE) continue; r = as_release_new (); if (!as_release_node_parse (r, c, ctx, error)) return FALSE; as_app_add_release (app, r); } break; /* */ case AS_TAG_PROVIDES: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_ptr_array_set_size (priv->provides, 0); for (c = n->children; c != NULL; c = c->next) { g_autoptr(AsProvide) p = NULL; p = as_provide_new (); if (!as_provide_node_parse (p, c, ctx, error)) return FALSE; as_app_add_provide (app, p); } break; /* */ case AS_TAG_LANGUAGES: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_hash_table_remove_all (priv->languages); for (c = n->children; c != NULL; c = c->next) { guint percent; if (as_node_get_tag (c) != AS_TAG_LANG) continue; percent = as_node_get_attribute_as_int (c, "percentage"); if (percent == G_MAXINT) percent = 0; as_app_add_language (app, percent, as_node_get_data (c)); } break; /* */ case AS_TAG_METADATA: if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) g_hash_table_remove_all (priv->metadata); for (c = n->children; c != NULL; c = c->next) { gchar *key; if (as_node_get_tag (c) != AS_TAG_VALUE) continue; key = as_node_take_attribute (c, "key"); taken = as_node_take_data (c); if (taken == NULL) taken = g_strdup (""); g_hash_table_insert (priv->metadata, key, taken); } break; default: break; } return TRUE; } /** * as_app_check_for_hidpi_icons: **/ static void as_app_check_for_hidpi_icons (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); AsIcon *icon_tmp; g_autofree gchar *fn_size = NULL; g_autoptr(AsIcon) icon_hidpi = NULL; /* does the file exist */ icon_tmp = as_app_get_icon_default (app); fn_size = g_build_filename (priv->icon_path, "128x128", as_icon_get_name (icon_tmp), NULL); if (!g_file_test (fn_size, G_FILE_TEST_EXISTS)) return; /* create the HiDPI version */ icon_hidpi = as_icon_new (); as_icon_set_prefix (icon_hidpi, priv->icon_path); as_icon_set_name (icon_hidpi, as_icon_get_name (icon_tmp)); as_icon_set_width (icon_hidpi, 128); as_icon_set_height (icon_hidpi, 128); as_app_add_icon (app, icon_hidpi); } /** * as_app_node_parse_full: **/ static gboolean as_app_node_parse_full (AsApp *app, GNode *node, AsAppParseFlags flags, AsNodeContext *ctx, GError **error) { AsAppPrivate *priv = GET_PRIVATE (app); GNode *n; const gchar *tmp; guint prio; /* new style */ if (g_strcmp0 (as_node_get_name (node), "component") == 0) { tmp = as_node_get_attribute (node, "type"); if (tmp != NULL) as_app_set_id_kind (app, as_id_kind_from_string (tmp)); prio = as_node_get_attribute_as_int (node, "priority"); if (prio != G_MAXINT && prio != 0) as_app_set_priority (app, prio); } /* parse each node */ if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) { g_ptr_array_set_size (priv->compulsory_for_desktops, 0); g_ptr_array_set_size (priv->pkgnames, 0); g_ptr_array_set_size (priv->architectures, 0); g_ptr_array_set_size (priv->extends, 0); g_ptr_array_set_size (priv->icons, 0); g_ptr_array_set_size (priv->bundles, 0); g_hash_table_remove_all (priv->keywords); } for (n = node->children; n != NULL; n = n->next) { if (!as_app_node_parse_child (app, n, flags, ctx, error)) return FALSE; } /* if only one icon is listed, look for HiDPI versions too */ if (as_app_get_icons(app)->len == 1) as_app_check_for_hidpi_icons (app); return TRUE; } /** * as_app_node_parse: * @app: a #AsApp instance. * @node: a #GNode. * @ctx: a #AsNodeContext. * @error: A #GError or %NULL. * * Populates the object from a DOM node. * * Returns: %TRUE for success * * Since: 0.1.0 **/ gboolean as_app_node_parse (AsApp *app, GNode *node, AsNodeContext *ctx, GError **error) { return as_app_node_parse_full (app, node, AS_APP_PARSE_FLAG_NONE, ctx, error); } /** * as_app_node_parse_dep11_icons: **/ static gboolean as_app_node_parse_dep11_icons (AsApp *app, GNode *node, AsNodeContext *ctx, GError **error) { AsAppPrivate *priv = GET_PRIVATE (app); const gchar *sizes[] = { "128x128", "64x64", "", NULL }; guint i; guint size; g_autoptr(AsIcon) ic_tmp = NULL; /* YAML files only specify one icon for various sizes */ ic_tmp = as_icon_new (); if (!as_icon_node_parse_dep11 (ic_tmp, node, ctx, error)) return FALSE; /* find each size */ for (i = 0; sizes[i] != NULL; i++) { g_autofree gchar *path = NULL; g_autofree gchar *size_name = NULL; g_autoptr(AsIcon) ic = NULL; size_name = g_build_filename (sizes[i], as_icon_get_name (ic_tmp), NULL); path = g_build_filename (priv->icon_path, size_name, NULL); if (!g_file_test (path, G_FILE_TEST_EXISTS)) continue; /* only the first try is a HiDPI icon, assume 64px otherwise */ size = (i == 0) ? 128 : 64; ic = as_icon_new (); as_icon_set_kind (ic, AS_ICON_KIND_CACHED); as_icon_set_prefix (ic, priv->icon_path); as_icon_set_name (ic, size_name); as_icon_set_width (ic, size); as_icon_set_height (ic, size); as_app_add_icon (app, ic); } return TRUE; } /** * as_app_node_parse_dep11: * @app: a #AsApp instance. * @node: a #GNode. * @ctx: a #AsNodeContext. * @error: A #GError or %NULL. * * Populates the object from a DEP-11 node. * * Returns: %TRUE for success * * Since: 0.3.0 **/ gboolean as_app_node_parse_dep11 (AsApp *app, GNode *node, AsNodeContext *ctx, GError **error) { GNode *c; GNode *c2; GNode *n; const gchar *tmp; for (n = node->children; n != NULL; n = n->next) { tmp = as_yaml_node_get_key (n); if (g_strcmp0 (tmp, "ID") == 0) { as_app_set_id (app, as_yaml_node_get_value (n)); continue; } if (g_strcmp0 (tmp, "Type") == 0) { if (g_strcmp0 (as_yaml_node_get_value (n), "desktop-app") == 0) { as_app_set_id_kind (app, AS_ID_KIND_DESKTOP); continue; } continue; } if (g_strcmp0 (tmp, "Packages") == 0) { for (c = n->children; c != NULL; c = c->next) as_app_add_pkgname (app, as_yaml_node_get_key (c)); continue; } if (g_strcmp0 (tmp, "Name") == 0) { for (c = n->children; c != NULL; c = c->next) { as_app_set_name (app, as_yaml_node_get_key (c), as_yaml_node_get_value (c)); } continue; } if (g_strcmp0 (tmp, "Summary") == 0) { for (c = n->children; c != NULL; c = c->next) { as_app_set_comment (app, as_yaml_node_get_key (c), as_yaml_node_get_value (c)); } continue; } if (g_strcmp0 (tmp, "Description") == 0) { for (c = n->children; c != NULL; c = c->next) { as_app_set_description (app, as_yaml_node_get_key (c), as_yaml_node_get_value (c)); } continue; } if (g_strcmp0 (tmp, "Keywords") == 0) { for (c = n->children; c != NULL; c = c->next) { for (c2 = c->children; c2 != NULL; c2 = c2->next) { if (as_yaml_node_get_key (c2) == NULL) continue; as_app_add_keyword (app, as_yaml_node_get_key (c), as_yaml_node_get_key (c2)); } } continue; } if (g_strcmp0 (tmp, "Categories") == 0) { for (c = n->children; c != NULL; c = c->next) as_app_add_category (app, as_yaml_node_get_key (c)); continue; } if (g_strcmp0 (tmp, "Icon") == 0) { for (c = n->children; c != NULL; c = c->next) { if (!as_app_node_parse_dep11_icons (app, c, ctx, error)) return FALSE; } continue; } if (g_strcmp0 (tmp, "Bundle") == 0) { for (c = n->children; c != NULL; c = c->next) { g_autoptr(AsBundle) bu = NULL; bu = as_bundle_new (); if (!as_bundle_node_parse_dep11 (bu, c, ctx, error)) return FALSE; as_app_add_bundle (app, bu); } continue; } if (g_strcmp0 (tmp, "Url") == 0) { for (c = n->children; c != NULL; c = c->next) { if (g_strcmp0 (as_yaml_node_get_key (c), "homepage") == 0) { as_app_add_url (app, AS_URL_KIND_HOMEPAGE, as_yaml_node_get_value (c)); continue; } } continue; } if (g_strcmp0 (tmp, "Provides") == 0) { for (c = n->children; c != NULL; c = c->next) { if (g_strcmp0 (as_yaml_node_get_key (c), "mimetypes") == 0) { for (c2 = c->children; c2 != NULL; c2 = c2->next) { as_app_add_mimetype (app, as_yaml_node_get_key (c2)); } continue; } else { g_autoptr(AsProvide) pr = NULL; pr = as_provide_new (); if (!as_provide_node_parse_dep11 (pr, c, ctx, error)) return FALSE; as_app_add_provide (app, pr); } } continue; } if (g_strcmp0 (tmp, "Screenshots") == 0) { for (c = n->children; c != NULL; c = c->next) { g_autoptr(AsScreenshot) ss = NULL; ss = as_screenshot_new (); if (!as_screenshot_node_parse_dep11 (ss, c, ctx, error)) return FALSE; as_app_add_screenshot (app, ss); } continue; } } return TRUE; } /** * as_app_value_tokenize: **/ static gchar ** as_app_value_tokenize (const gchar *value) { gchar **values; guint i; g_autofree gchar *delim = NULL; g_auto(GStrv) tmp = NULL; delim = g_strdup (value); g_strdelimit (delim, "/,.;:", ' '); tmp = g_strsplit (delim, " ", -1); values = g_new0 (gchar *, g_strv_length (tmp) + 1); for (i = 0; tmp[i] != NULL; i++) values[i] = g_utf8_strdown (tmp[i], -1); return values; } /** * as_app_remove_invalid_tokens: **/ static void as_app_remove_invalid_tokens (gchar **tokens) { guint i; guint idx = 0; guint len; if (tokens == NULL) return; /* remove any tokens that are invalid and maintain the order */ len = g_strv_length (tokens); for (i = 0; i < len; i++) { if (!as_utils_search_token_valid (tokens[i])) { g_free (tokens[i]); tokens[i] = NULL; continue; } tokens[idx++] = tokens[i]; } /* unused token space */ for (i = idx; i < len; i++) tokens[i] = NULL; } /** * as_app_add_tokens: **/ static void as_app_add_tokens (AsApp *app, const gchar *value, const gchar *locale, guint score) { AsAppPrivate *priv = GET_PRIVATE (app); AsAppTokenItem *token_item; /* sanity check */ if (value == NULL) { g_critical ("trying to add NULL search token to %s", as_app_get_id (app)); return; } token_item = g_slice_new0 (AsAppTokenItem); #if GLIB_CHECK_VERSION(2,39,1) if (g_strstr_len (value, -1, "+") == NULL && g_strstr_len (value, -1, "-") == NULL) { token_item->values_utf8 = g_str_tokenize_and_fold (value, locale, &token_item->values_ascii); } #endif if (token_item->values_utf8 == NULL) token_item->values_utf8 = as_app_value_tokenize (value); /* remove any tokens that are invalid */ as_app_remove_invalid_tokens (token_item->values_utf8); as_app_remove_invalid_tokens (token_item->values_ascii); token_item->score = score; g_ptr_array_add (priv->token_cache, token_item); } /** * as_app_create_token_cache_target: **/ static void as_app_create_token_cache_target (AsApp *app, AsApp *donor) { AsAppPrivate *priv = GET_PRIVATE (donor); GPtrArray *array; const gchar * const *locales; const gchar *tmp; guint i; guint j; /* add all the data we have */ if (priv->id_filename != NULL) as_app_add_tokens (app, priv->id_filename, "C", 100); locales = g_get_language_names (); for (i = 0; locales[i] != NULL; i++) { if (g_str_has_suffix (locales[i], ".UTF-8")) continue; tmp = as_app_get_name (app, locales[i]); if (tmp != NULL) as_app_add_tokens (app, tmp, locales[i], 80); tmp = as_app_get_comment (app, locales[i]); if (tmp != NULL) as_app_add_tokens (app, tmp, locales[i], 60); tmp = as_app_get_description (app, locales[i]); if (tmp != NULL) as_app_add_tokens (app, tmp, locales[i], 20); array = as_app_get_keywords (app, locales[i]); if (array != NULL) { for (j = 0; j < array->len; j++) { tmp = g_ptr_array_index (array, j); as_app_add_tokens (app, tmp, locales[i], 90); } } } for (i = 0; i < priv->mimetypes->len; i++) { tmp = g_ptr_array_index (priv->mimetypes, i); as_app_add_tokens (app, tmp, "C", 1); } } /** * as_app_create_token_cache: **/ static void as_app_create_token_cache (AsApp *app) { AsApp *donor; AsAppPrivate *priv = GET_PRIVATE (app); guint i; as_app_create_token_cache_target (app, app); for (i = 0; i < priv->addons->len; i++) { donor = g_ptr_array_index (priv->addons, i); as_app_create_token_cache_target (app, donor); } } /** * as_app_search_matches: * @app: a #AsApp instance. * @search: the search term. * * Searches application data for a specific keyword. * * Returns: a match scrore, where 0 is no match and 100 is the best match. * * Since: 0.1.0 **/ guint as_app_search_matches (AsApp *app, const gchar *search) { AsAppPrivate *priv = GET_PRIVATE (app); AsAppTokenItem *item; guint i, j; /* nothing to do */ if (search == NULL) return 0; /* ensure the token cache is created */ if (g_once_init_enter (&priv->token_cache_valid)) { as_app_create_token_cache (app); g_once_init_leave (&priv->token_cache_valid, TRUE); } /* find the search term */ for (i = 0; i < priv->token_cache->len; i++) { item = g_ptr_array_index (priv->token_cache, i); /* prefer UTF-8 matches */ if (item->values_utf8 != NULL) { for (j = 0; item->values_utf8[j] != NULL; j++) { if (g_str_has_prefix (item->values_utf8[j], search)) return item->score; } } /* fall back to ASCII matches */ if (item->values_ascii != NULL) { for (j = 0; item->values_ascii[j] != NULL; j++) { if (g_str_has_prefix (item->values_ascii[j], search)) return item->score / 2; } } } return 0; } /** * as_app_get_search_tokens: * @app: a #AsApp instance. * * Returns all the search tokens for the application. These are unsorted. * * Returns: (transfer full): The string search tokens * * Since: 0.3.4 */ GPtrArray * as_app_get_search_tokens (AsApp *app) { AsAppPrivate *priv = GET_PRIVATE (app); AsAppTokenItem *item; GPtrArray *array; guint i, j; /* ensure the token cache is created */ if (g_once_init_enter (&priv->token_cache_valid)) { as_app_create_token_cache (app); g_once_init_leave (&priv->token_cache_valid, TRUE); } /* return all the toek cache */ array = g_ptr_array_new_with_free_func (g_free); for (i = 0; i < priv->token_cache->len; i++) { item = g_ptr_array_index (priv->token_cache, i); if (item->values_utf8 != NULL) { for (j = 0; item->values_utf8[j] != NULL; j++) g_ptr_array_add (array, g_strdup (item->values_utf8[j])); } if (item->values_ascii != NULL) { for (j = 0; item->values_ascii[j] != NULL; j++) g_ptr_array_add (array, g_strdup (item->values_ascii[j])); } } return array; } /** * as_app_search_matches_all: * @app: a #AsApp instance. * @search: the search terms. * * Searches application data for all the specific keywords. * * Returns: a match scrore, where 0 is no match and larger numbers are better * matches. * * It's probably a good idea to use as_utils_search_tokenize() to populate * search as very short or common keywords will return a lot of matches. * * Since: 0.1.3 */ guint as_app_search_matches_all (AsApp *app, gchar **search) { guint i; guint matches_sum = 0; guint tmp; /* do *all* search keywords match */ for (i = 0; search[i] != NULL; i++) { tmp = as_app_search_matches (app, search[i]); if (tmp == 0) return 0; matches_sum += tmp; } return matches_sum; } /** * as_app_parse_appdata_unintltoolize_cb: **/ static gboolean as_app_parse_appdata_unintltoolize_cb (GNode *node, gpointer data) { AsAppPrivate *priv = GET_PRIVATE (AS_APP (data)); const gchar *name; name = as_node_get_name (node); if (g_strcmp0 (name, "_name") == 0) { as_node_set_name (node, "name"); priv->problems |= AS_APP_PROBLEM_INTLTOOL_NAME; return FALSE; } if (g_strcmp0 (name, "_summary") == 0) { as_node_set_name (node, "summary"); priv->problems |= AS_APP_PROBLEM_INTLTOOL_SUMMARY; return FALSE; } if (g_strcmp0 (name, "_caption") == 0) { as_node_set_name (node, "caption"); return FALSE; } if (g_strcmp0 (name, "_p") == 0) { as_node_set_name (node, "p"); priv->problems |= AS_APP_PROBLEM_INTLTOOL_DESCRIPTION; return FALSE; } if (g_strcmp0 (name, "_li") == 0) { as_node_set_name (node, "li"); priv->problems |= AS_APP_PROBLEM_INTLTOOL_DESCRIPTION; return FALSE; } if (g_strcmp0 (name, "_ul") == 0) { as_node_set_name (node, "ul"); priv->problems |= AS_APP_PROBLEM_INTLTOOL_DESCRIPTION; return FALSE; } if (g_strcmp0 (name, "_ol") == 0) { as_node_set_name (node, "ol"); priv->problems |= AS_APP_PROBLEM_INTLTOOL_DESCRIPTION; return FALSE; } return FALSE; } /** * as_app_parse_appdata_file: **/ static gboolean as_app_parse_appdata_file (AsApp *app, const gchar *filename, AsAppParseFlags flags, GError **error) { AsAppPrivate *priv = GET_PRIVATE (app); AsNodeFromXmlFlags from_xml_flags = AS_NODE_FROM_XML_FLAG_NONE; GNode *l; GNode *node; gboolean seen_application = FALSE; gchar *tmp; gsize len; g_autoptr(GError) error_local = NULL; g_autofree AsNodeContext *ctx = NULL; g_autofree gchar *data = NULL; g_autoptr(AsNode) root = NULL; /* open file */ if (!g_file_get_contents (filename, &data, &len, &error_local)) { g_set_error (error, AS_APP_ERROR, AS_APP_ERROR_INVALID_TYPE, "%s could not be read: %s", filename, error_local->message); return FALSE; } /* validate */ tmp = g_strstr_len (data, len, ""); if (tmp == NULL) tmp = g_strstr_len (data, len, ""); if (tmp == NULL) tmp = g_strstr_len (data, len, ""); if (tmp == NULL) priv->problems |= AS_APP_PROBLEM_NO_XML_HEADER; /* check for copyright */ tmp = g_strstr_len (data, len, "