/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2016-2024 Matthias Klumpp * * Licensed under the GNU Lesser General Public License Version 2.1 * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the license, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ /** * SECTION:asc-globals * @short_description: Set and read a bunch of global settings used across appstream-compose * @include: appstream-compose.h */ #include "config.h" #include "asc-globals-private.h" #include "as-utils-private.h" #include "as-validator-issue-tag.h" #include "asc-resources.h" #define ASC_TYPE_GLOBALS (asc_globals_get_type ()) G_DECLARE_DERIVABLE_TYPE (AscGlobals, asc_globals, ASC, GLOBALS, GObject) struct _AscGlobalsClass { GObjectClass parent_class; }; typedef struct { gboolean use_optipng; gchar *optipng_bin; gchar *ffprobe_bin; gchar *tmp_dir; GMutex pangrams_mutex; GPtrArray *pangrams_en; GMutex hint_tags_mutex; GHashTable *hint_tags; } AscGlobalsPrivate; G_DEFINE_TYPE_WITH_PRIVATE (AscGlobals, asc_globals, G_TYPE_OBJECT) #define GET_PRIVATE(o) (asc_globals_get_instance_private (o)) static AscGlobals *g_globals = NULL; static GMutex g_globals_mutex; /** * asc_get_resource_safe: * * A threadsafe variant to obtain the buit-in #GResource. */ static GResource * asc_get_resource_safe (void) { static GResource *resource = NULL; if (g_once_init_enter (&resource)) { GResource *res = asc_get_resource (); g_once_init_leave (&resource, res); } return resource; } /** * asc_compose_error_quark: * * Return value: An error quark. **/ GQuark asc_compose_error_quark (void) { static GQuark quark = 0; if (!quark) quark = g_quark_from_static_string ("AscComposeError"); return quark; } static GObject * asc_globals_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&g_globals_mutex); if (g_globals != NULL) return G_OBJECT (g_globals); else return G_OBJECT_CLASS (asc_globals_parent_class) ->constructor (type, n_construct_properties, construct_properties); } static void asc_globals_finalize (GObject *object) { AscGlobals *globals = ASC_GLOBALS (object); AscGlobalsPrivate *priv = GET_PRIVATE (globals); g_free (priv->tmp_dir); g_free (priv->optipng_bin); g_free (priv->ffprobe_bin); g_mutex_clear (&priv->pangrams_mutex); if (priv->pangrams_en != NULL) g_ptr_array_unref (priv->pangrams_en); g_mutex_clear (&priv->hint_tags_mutex); if (priv->hint_tags != NULL) g_hash_table_unref (priv->hint_tags); G_OBJECT_CLASS (asc_globals_parent_class)->finalize (object); } static void asc_globals_init (AscGlobals *globals) { AscGlobalsPrivate *priv = GET_PRIVATE (globals); g_autofree gchar *tmp_str1 = NULL; g_autofree gchar *tmp_str2 = NULL; g_assert (g_globals == NULL); g_globals = globals; tmp_str1 = as_random_alnum_string (6); tmp_str2 = g_strconcat ("as-compose_", tmp_str1, NULL); priv->tmp_dir = g_build_filename (g_get_tmp_dir (), tmp_str2, NULL); priv->optipng_bin = g_find_program_in_path ("optipng"); if (priv->optipng_bin != NULL) priv->use_optipng = TRUE; priv->ffprobe_bin = g_find_program_in_path ("ffprobe"); g_mutex_init (&priv->hint_tags_mutex); g_mutex_init (&priv->pangrams_mutex); } static void asc_globals_class_init (AscGlobalsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructor = asc_globals_constructor; object_class->finalize = asc_globals_finalize; } static AscGlobalsPrivate * asc_globals_get_priv (void) { return GET_PRIVATE (g_object_new (ASC_TYPE_GLOBALS, NULL)); } /** * asc_globals_clear: * * Clear all global state and restore defaults. */ void asc_globals_clear (void) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&g_globals_mutex); if (g_globals == NULL) return; g_object_unref (g_globals); g_globals = NULL; } /** * asc_globals_get_tmp_dir: * * Get temporary directory used by appstream-compose. **/ const gchar * asc_globals_get_tmp_dir (void) { AscGlobalsPrivate *priv = asc_globals_get_priv (); return priv->tmp_dir; } /** * asc_globals_get_tmp_dir_create: * * Get temporary directory used by appstream-compose * and try to create it if it does not exist. **/ const gchar * asc_globals_get_tmp_dir_create (void) { AscGlobalsPrivate *priv = asc_globals_get_priv (); g_mkdir_with_parents (priv->tmp_dir, 0700); return priv->tmp_dir; } /** * asc_globals_set_tmp_dir: * * Set temporary directory used by appstream-compose. **/ void asc_globals_set_tmp_dir (const gchar *path) { AscGlobalsPrivate *priv = asc_globals_get_priv (); g_free (priv->tmp_dir); priv->tmp_dir = g_strdup (path); } /** * asc_globals_get_use_optipng: * * Get whether images should be optimized using optipng. **/ gboolean asc_globals_get_use_optipng (void) { AscGlobalsPrivate *priv = asc_globals_get_priv (); return priv->use_optipng; } /** * asc_globals_set_use_optipng: * * Set whether images should be optimized using optipng. **/ void asc_globals_set_use_optipng (gboolean enabled) { AscGlobalsPrivate *priv = asc_globals_get_priv (); if (enabled && priv->optipng_bin == NULL) { g_critical ("Refusing to enable optipng: not found in $PATH"); priv->use_optipng = FALSE; return; } priv->use_optipng = enabled; } /** * asc_globals_get_optipng_binary: * * Get path to the "optipng" binary we should use. **/ const gchar * asc_globals_get_optipng_binary (void) { AscGlobalsPrivate *priv = asc_globals_get_priv (); return priv->optipng_bin; } /** * asc_globals_set_optipng_binary: * * Set path to the "optipng" binary we should use. **/ void asc_globals_set_optipng_binary (const gchar *path) { AscGlobalsPrivate *priv = asc_globals_get_priv (); g_free (priv->optipng_bin); priv->optipng_bin = g_strdup (path); if (priv->optipng_bin == NULL) priv->use_optipng = FALSE; } /** * asc_globals_get_ffprobe_binary: * * Get path to the "ffprobe" binary we should use. **/ const gchar * asc_globals_get_ffprobe_binary (void) { AscGlobalsPrivate *priv = asc_globals_get_priv (); return priv->ffprobe_bin; } /** * asc_globals_set_ffprobe_binary: * * Set path to the "ffprobe" binary we should use. **/ void asc_globals_set_ffprobe_binary (const gchar *path) { AscGlobalsPrivate *priv = asc_globals_get_priv (); g_free (priv->ffprobe_bin); priv->ffprobe_bin = g_strdup (path); } /** * asc_globals_get_pangrams_for: * * Obtain a list of pangrams for the given language, currently * only "en" is supported. * * Returns: (transfer none) (element-type utf8): List of pangrams. */ GPtrArray * asc_globals_get_pangrams_for (const gchar *lang) { AscGlobalsPrivate *priv = asc_globals_get_priv (); if ((lang != NULL) && (g_strcmp0 (lang, "en") != 0)) return NULL; /* return cached value if possible */ if (priv->pangrams_en != NULL) return priv->pangrams_en; { g_autoptr(GBytes) data = NULL; g_auto(GStrv) strv = NULL; g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->pangrams_mutex); /* race protection, just to be sure */ if (priv->pangrams_en != NULL) return priv->pangrams_en; /* load array from resources */ data = g_resource_lookup_data (asc_get_resource_safe (), "/org/freedesktop/appstream-compose/pangrams/en.txt", G_RESOURCE_LOOKUP_FLAGS_NONE, NULL); if (data == NULL) return NULL; strv = g_strsplit (g_bytes_get_data (data, NULL), "\n", -1); if (strv == NULL) return NULL; priv->pangrams_en = g_ptr_array_new_full (g_strv_length (strv), g_free); for (guint i = 0; strv[i] != NULL; i++) g_ptr_array_add (priv->pangrams_en, g_strdup (strv[i])); } return priv->pangrams_en; } /** * asc_globals_create_hint_tag_table: * * Create hint tag cache. * IMPORTANT: This function may only be called while a lock is held * on the hint tag table mutex. */ static void asc_globals_create_hint_tag_table (void) { AscGlobalsPrivate *priv = asc_globals_get_priv (); g_return_if_fail (priv->hint_tags == NULL); priv->hint_tags = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) as_ref_string_release, (GDestroyNotify) asc_hint_tag_free); /* add compose issue hint tags */ for (guint i = 0; asc_hint_tag_list[i].tag != NULL; i++) { AscHintTag *htag; gboolean r; const AscHintTagStatic s = asc_hint_tag_list[i]; htag = asc_hint_tag_new (s.tag, s.severity, s.explanation); r = g_hash_table_insert (priv->hint_tags, g_ref_string_new_intern (asc_hint_tag_list[i].tag), htag); if (G_UNLIKELY (!r)) g_critical ("Duplicate compose-hint tag '%s' found in tag list. This is a " "bug in appstream-compose.", asc_hint_tag_list[i].tag); } /* add validator issue hint tags */ for (guint i = 0; as_validator_issue_tag_list[i].tag != NULL; i++) { AscHintTag *htag; gboolean r; AsIssueSeverity severity; g_autofree gchar *compose_tag = g_strconcat ("asv-", as_validator_issue_tag_list[i].tag, NULL); g_autofree gchar *explanation = g_markup_printf_escaped ( "{{location}} - {{hint}}
%s", as_validator_issue_tag_list[i].explanation); /* any validator issue can not be of type error in as-compose - if the validation issue * is so severe that it renders the compose process impossible, we will throw another issue * of type "error" which will immediately terminate the data genertaion. */ severity = as_validator_issue_tag_list[i].severity; if (severity == AS_ISSUE_SEVERITY_ERROR) severity = AS_ISSUE_SEVERITY_WARNING; htag = asc_hint_tag_new (compose_tag, severity, explanation); r = g_hash_table_insert (priv->hint_tags, g_ref_string_new_intern (compose_tag), htag); if (G_UNLIKELY (!r)) g_critical ("Duplicate issue-tag '%s' found in tag list. This is a bug in " "appstream-compose.", as_validator_issue_tag_list[i].tag); } } /** * asc_globals_add_hint_tag: * @tag: the tag-ID to add * @severity: the tag severity as #AsIssueSeverity * @explanation: the tag explanatory message * @overrideExisting: whether an existing tag should be replaced * * Register a new hint tag. If a previous tag with the given name * already existed, the existing tag will not be replaced unless * @overrideExisting is set to %TRUE. * Please be careful when overriding tags! Tag severities can not * be lowered by overriding a tag. * * Returns: %TRUE if the tag was registered and did not exist previously. */ gboolean asc_globals_add_hint_tag (const gchar *tag, AsIssueSeverity severity, const gchar *explanation, gboolean overrideExisting) { AscGlobalsPrivate *priv = asc_globals_get_priv (); AscHintTag *htag; AscHintTag *e_htag; g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->hint_tags_mutex); g_return_val_if_fail (tag != NULL, FALSE); if (priv->hint_tags == NULL) asc_globals_create_hint_tag_table (); e_htag = g_hash_table_lookup (priv->hint_tags, tag); if (e_htag != NULL) { if (overrideExisting) { /* make sure we don't permit lowering severities */ if (severity > e_htag->severity) severity = e_htag->severity; } else { /* don't allow the override */ return FALSE; } } htag = asc_hint_tag_new (tag, severity, explanation); g_hash_table_insert (priv->hint_tags, g_ref_string_new_intern (tag), htag); return TRUE; } /** * asc_globals_get_hint_info: * * Return details for a given hint tag. * * Returns: (transfer none): Hint tag details. */ AscHintTag * asc_globals_get_hint_tag_details (const gchar *tag) { AscGlobalsPrivate *priv = asc_globals_get_priv (); g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->hint_tags_mutex); g_return_val_if_fail (tag != NULL, NULL); if (priv->hint_tags == NULL) asc_globals_create_hint_tag_table (); return g_hash_table_lookup (priv->hint_tags, tag); } /** * asc_globals_get_hint_tags: * * Retrieve all hint tags that we know. * * Returns: (transfer full): A list of valid hint tags. Free with %g_strfreev */ gchar ** asc_globals_get_hint_tags (void) { AscGlobalsPrivate *priv = asc_globals_get_priv (); GHashTableIter iter; gpointer key; gchar **strv; guint i = 0; g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->hint_tags_mutex); if (priv->hint_tags == NULL) asc_globals_create_hint_tag_table (); /* deep-copy the table keys to a strv */ strv = g_new0 (gchar *, g_hash_table_size (priv->hint_tags) + 1); g_hash_table_iter_init (&iter, priv->hint_tags); while (g_hash_table_iter_next (&iter, &key, NULL)) strv[i++] = g_strdup ((const gchar *) key); return strv; } /** * asc_globals_hint_tag_severity: * * Retrieve the severity of the given hint tag. * * Returns: An #AsIssueSeverity or %AS_ISSUE_SEVERITY_UNKNOWN if the tag did not exist * or has an unknown severity. */ AsIssueSeverity asc_globals_hint_tag_severity (const gchar *tag) { AscHintTag *htag = asc_globals_get_hint_tag_details (tag); if (htag == NULL) return AS_ISSUE_SEVERITY_UNKNOWN; return htag->severity; } /** * asc_globals_hint_tag_explanation: * * Retrieve the explanation template of the given hint tag. * * Returns: An explanation template, or %NULL if the tag was not found. */ const gchar * asc_globals_hint_tag_explanation (const gchar *tag) { AscHintTag *htag = asc_globals_get_hint_tag_details (tag); if (htag == NULL) return NULL; return htag->explanation; }