/** * as_pool_load_metadata: * * Load fresh metadata from AppStream directories. */ static gboolean as_pool_load_metadata (AsPool *pool) { GPtrArray *cpts; g_autoptr(GPtrArray) merge_cpts = NULL; guint i; gboolean ret; g_autoptr(AsMetadata) metad = NULL; g_autoptr(GPtrArray) mdata_files = NULL; GError *error = NULL; AsPoolPrivate *priv = GET_PRIVATE (pool); /* prepare metadata parser */ metad = as_metadata_new (); as_metadata_set_format_style (metad, AS_FORMAT_STYLE_COLLECTION); as_metadata_set_locale (metad, priv->locale); /* find AppStream metadata */ ret = TRUE; mdata_files = g_ptr_array_new_with_free_func (g_free); /* find XML data */ for (i = 0; i < priv->xml_dirs->len; i++) { const gchar *xml_path = (const gchar *) g_ptr_array_index (priv->xml_dirs, i); guint j; if (g_file_test (xml_path, G_FILE_TEST_IS_DIR)) { g_autoptr(GPtrArray) xmls = NULL; g_debug ("Searching for data in: %s", xml_path); xmls = as_utils_find_files_matching (xml_path, "*.xml*", FALSE, NULL); if (xmls != NULL) { for (j = 0; j < xmls->len; j++) { const gchar *val; val = (const gchar *) g_ptr_array_index (xmls, j); g_ptr_array_add (mdata_files, g_strdup (val)); } } } } /* find YAML metadata */ for (i = 0; i < priv->yaml_dirs->len; i++) { const gchar *yaml_path = (const gchar *) g_ptr_array_index (priv->yaml_dirs, i); guint j; if (g_file_test (yaml_path, G_FILE_TEST_IS_DIR)) { g_autoptr(GPtrArray) yamls = NULL; g_debug ("Searching for data in: %s", yaml_path); yamls = as_utils_find_files_matching (yaml_path, "*.yml*", FALSE, NULL); if (yamls != NULL) { for (j = 0; j < yamls->len; j++) { const gchar *val; val = (const gchar *) g_ptr_array_index (yamls, j); g_ptr_array_add (mdata_files, g_strdup (val)); } } } } /* parse the found data */ for (i = 0; i < mdata_files->len; i++) { g_autoptr(GFile) infile = NULL; const gchar *fname; fname = (const gchar*) g_ptr_array_index (mdata_files, i); g_debug ("Reading: %s", fname); infile = g_file_new_for_path (fname); if (!g_file_query_exists (infile, NULL)) { g_warning ("Metadata file '%s' does not exist.", fname); continue; } as_metadata_parse_file (metad, infile, AS_FORMAT_KIND_UNKNOWN, &error); if (error != NULL) { g_debug ("WARNING: %s", error->message); g_error_free (error); error = NULL; ret = FALSE; } } /* add found components to the metadata pool */ cpts = as_metadata_get_components (metad); merge_cpts = g_ptr_array_new (); for (i = 0; i < cpts->len; i++) { AsComponent *cpt = AS_COMPONENT (g_ptr_array_index (cpts, i)); /* deal with merge-components later */ if (as_component_get_merge_kind (cpt) != AS_MERGE_KIND_NONE) { g_ptr_array_add (merge_cpts, cpt); continue; } as_pool_add_component (pool, cpt, &error); if (error != NULL) { g_debug ("Metadata ignored: %s", error->message); g_error_free (error); error = NULL; } } /* we need to merge the merge-components into the pool last, so the merge process can fetch * all components with matching IDs from the pool */ for (i = 0; i < merge_cpts->len; i++) { AsComponent *mcpt = AS_COMPONENT (g_ptr_array_index (merge_cpts, i)); as_pool_add_component (pool, mcpt, &error); if (error != NULL) { g_debug ("Merge component ignored: %s", error->message); g_error_free (error); error = NULL; } } return ret; }
/** * as_pool_add_component: * @pool: An instance of #AsPool * @cpt: The #AsComponent to add to the pool. * @error: A #GError or %NULL * * Register a new component in the AppStream metadata pool. * * Returns: %TRUE if the new component was successfully added to the pool. */ gboolean as_pool_add_component (AsPool *pool, AsComponent *cpt, GError **error) { const gchar *cdid = NULL; AsComponent *existing_cpt; gint pool_priority; AsPoolPrivate *priv = GET_PRIVATE (pool); cdid = as_component_get_data_id (cpt); existing_cpt = g_hash_table_lookup (priv->cpt_table, cdid); /* add additional data to the component, e.g. external screenshots. Also refines * the component's icon paths */ as_component_complete (cpt, priv->screenshot_service_url, priv->icon_dirs); if (existing_cpt == NULL) { g_hash_table_insert (priv->cpt_table, g_strdup (cdid), g_object_ref (cpt)); return TRUE; } /* perform metadata merges if necessary */ if (as_component_get_merge_kind (cpt) != AS_MERGE_KIND_NONE) { g_autoptr(GPtrArray) matches = NULL; guint i; /* we merge the data into all components with matching IDs at time */ matches = as_pool_get_components_by_id (pool, as_component_get_id (cpt)); for (i = 0; i < matches->len; i++) { AsComponent *match = AS_COMPONENT (g_ptr_array_index (matches, i)); as_merge_components (match, cpt); } return TRUE; } /* if we are here, we might have duplicates and no merges, so check if we should replace a component * with data of higher priority, or if we have an actual error in the metadata */ pool_priority = as_component_get_priority (existing_cpt); if (pool_priority < as_component_get_priority (cpt)) { g_hash_table_replace (priv->cpt_table, g_strdup (cdid), g_object_ref (cpt)); g_debug ("Replaced '%s' with data of higher priority.", cdid); } else { /* bundles are treated specially here */ if ((!as_component_has_bundle (existing_cpt)) && (as_component_has_bundle (cpt))) { GPtrArray *bundles; /* propagate bundle information to existing component */ bundles = as_component_get_bundles (cpt); as_component_set_bundles_array (existing_cpt, bundles); return TRUE; } /* experimental multiarch support */ if (as_component_get_architecture (cpt) != NULL) { if (as_arch_compatible (as_component_get_architecture (cpt), priv->current_arch)) { const gchar *earch; /* this component is compatible with our current architecture */ earch = as_component_get_architecture (existing_cpt); if (earch != NULL) { if (as_arch_compatible (earch, priv->current_arch)) { g_hash_table_replace (priv->cpt_table, g_strdup (cdid), g_object_ref (cpt)); g_debug ("Preferred component for native architecture for %s (was %s)", cdid, earch); return TRUE; } else { g_debug ("Ignored additional entry for '%s' on architecture %s.", cdid, earch); return FALSE; } } } } if (pool_priority == as_component_get_priority (cpt)) { g_set_error (error, AS_POOL_ERROR, AS_POOL_ERROR_COLLISION, "Detected colliding ids: %s was already added with the same priority.", cdid); return FALSE; } else { g_set_error (error, AS_POOL_ERROR, AS_POOL_ERROR_COLLISION, "Detected colliding ids: %s was already added with a higher priority.", cdid); return FALSE; } } return TRUE; }
/** * as_merge_components: * * Merge selected data from two components. */ static void as_merge_components (AsComponent *dest_cpt, AsComponent *src_cpt) { guint i; AsMergeKind merge_kind; merge_kind = as_component_get_merge_kind (src_cpt); g_return_if_fail (merge_kind != AS_MERGE_KIND_NONE); /* FIXME: We need to merge more attributes */ /* merge stuff in append mode */ if (merge_kind == AS_MERGE_KIND_APPEND) { GPtrArray *suggestions; GPtrArray *cats; /* merge categories */ cats = as_component_get_categories (src_cpt); if (cats->len > 0) { g_autoptr(GHashTable) cat_table = NULL; GPtrArray *dest_categories; cat_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); for (i = 0; i < cats->len; i++) { const gchar *cat = (const gchar*) g_ptr_array_index (cats, i); g_hash_table_add (cat_table, g_strdup (cat)); } dest_categories = as_component_get_categories (dest_cpt); if (dest_categories->len > 0) { for (i = 0; i < dest_categories->len; i++) { const gchar *cat = (const gchar*) g_ptr_array_index (dest_categories, i); g_hash_table_add (cat_table, g_strdup (cat)); } } g_ptr_array_set_size (dest_categories, 0); as_hash_table_string_keys_to_array (cat_table, dest_categories); } /* merge suggestions */ suggestions = as_component_get_suggested (src_cpt); if (suggestions != NULL) { for (i = 0; i < suggestions->len; i++) { as_component_add_suggested (dest_cpt, AS_SUGGESTED (g_ptr_array_index (suggestions, i))); } } } /* merge stuff in replace mode */ if (merge_kind == AS_MERGE_KIND_REPLACE) { gchar **pkgnames; /* merge names */ if (as_component_get_name (src_cpt) != NULL) as_component_set_name (dest_cpt, as_component_get_name (src_cpt), as_component_get_active_locale (src_cpt)); /* merge package names */ pkgnames = as_component_get_pkgnames (src_cpt); if ((pkgnames != NULL) && (pkgnames[0] != '\0')) as_component_set_pkgnames (dest_cpt, as_component_get_pkgnames (src_cpt)); /* merge bundles */ if (as_component_has_bundle (src_cpt)) as_component_set_bundles_array (dest_cpt, as_component_get_bundles (src_cpt)); } g_debug ("Merged data for '%s'", as_component_get_data_id (dest_cpt)); }
/** * as_validator_validate_component_node: **/ static AsComponent* as_validator_validate_component_node (AsValidator *validator, AsXMLData *xdt, xmlNode *root) { xmlNode *iter; AsComponent *cpt; g_autofree gchar *cpttype = NULL; g_autoptr(GHashTable) found_tags = NULL; AsFormatStyle mode; gboolean has_metadata_license = FALSE; found_tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); mode = as_xmldata_get_format_style (xdt); /* validate the resulting AsComponent for sanity */ cpt = as_component_new (); as_xmldata_parse_component_node (xdt, root, cpt, NULL); as_validator_set_current_cpt (validator, cpt); /* check if component type is valid */ cpttype = (gchar*) xmlGetProp (root, (xmlChar*) "type"); if (cpttype != NULL) { if (as_component_kind_from_string (cpttype) == AS_COMPONENT_KIND_UNKNOWN) { as_validator_add_issue (validator, root, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_VALUE_WRONG, "Invalid component type found: %s", cpttype); } } if ((as_component_get_priority (cpt) != 0) && (mode == AS_FORMAT_STYLE_METAINFO)) { as_validator_add_issue (validator, root, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_VALUE_WRONG, "The component has a priority value set. This is not allowed in metainfo files."); } if ((as_component_get_merge_kind (cpt) != AS_MERGE_KIND_NONE) && (mode == AS_FORMAT_STYLE_METAINFO)) { as_validator_add_issue (validator, root, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_VALUE_WRONG, "The component has a 'merge' method defined. This is not allowed in metainfo files."); } for (iter = root->children; iter != NULL; iter = iter->next) { const gchar *node_name; g_autofree gchar *node_content = NULL; gboolean tag_valid = TRUE; /* discard spaces */ if (iter->type != XML_ELEMENT_NODE) continue; node_name = (const gchar*) iter->name; node_content = (gchar*) xmlNodeGetContent (iter); if (g_strcmp0 (node_name, "id") == 0) { gchar *prop; prop = (gchar*) xmlGetProp (iter, (xmlChar*) "type"); if (prop != NULL) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_INFO, AS_ISSUE_KIND_PROPERTY_INVALID, "The id tag for \"%s\" still contains a 'type' property, probably from an old conversion.", node_content); } g_free (prop); if (as_component_get_kind (cpt) == AS_COMPONENT_KIND_DESKTOP_APP) { if (!g_str_has_suffix (node_content, ".desktop")) as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_WARNING, AS_ISSUE_KIND_VALUE_WRONG, "Component id belongs to a desktop-application, but does not resemble the .desktop file name: \"%s\"", node_content); } /* validate the AppStream ID */ as_validator_validate_component_id (validator, iter, cpt); } else if (g_strcmp0 (node_name, "metadata_license") == 0) { has_metadata_license = TRUE; as_validator_check_appear_once (validator, iter, found_tags, cpt); /* the license must allow easy mixing of metadata in metainfo files */ if (mode == AS_FORMAT_STYLE_METAINFO) { if (!as_license_is_metadata_license (node_content)) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_WARNING, AS_ISSUE_KIND_VALUE_WRONG, "The metadata itself does not seem to be licensed under a permissive license. Please license the data under a permissive license, like FSFAP, CC-0-1.0 or MIT " "to allow distributors to include it in mixed data collections without the risk of license violations due to mutually incompatible licenses."); } } } else if (g_strcmp0 (node_name, "pkgname") == 0) { if (g_hash_table_contains (found_tags, node_name)) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_PEDANTIC, AS_ISSUE_KIND_TAG_DUPLICATED, "The tag 'pkgname' appears multiple times. You should evaluate creating a metapackage containing the data in order to avoid defining multiple package names per component."); } } else if (g_strcmp0 (node_name, "source_pkgname") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); } else if (g_strcmp0 (node_name, "name") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); if (g_str_has_suffix (node_content, ".")) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_INFO, AS_ISSUE_KIND_VALUE_ISSUE, "The component name should not end with a \".\" [%s]", node_content); } } else if (g_strcmp0 (node_name, "summary") == 0) { const gchar *summary = node_content; as_validator_check_appear_once (validator, iter, found_tags, cpt); if (g_str_has_suffix (summary, ".")) as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_INFO, AS_ISSUE_KIND_VALUE_ISSUE, "The component summary should not end with a \".\" [%s]", summary); if ((summary != NULL) && ((strstr (summary, "\n") != NULL) || (strstr (summary, "\t") != NULL))) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_VALUE_WRONG, "The summary tag must not contain tabs or linebreaks."); } } else if (g_strcmp0 (node_name, "description") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); as_validator_check_description_tag (validator, iter, cpt, mode); } else if (g_strcmp0 (node_name, "icon") == 0) { gchar *prop; prop = as_validator_check_type_property (validator, cpt, iter); if ((g_strcmp0 (prop, "cached") == 0) || (g_strcmp0 (prop, "stock") == 0)) { if (g_strrstr (node_content, "/") != NULL) as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_VALUE_WRONG, "Icons of type 'stock' or 'cached' must not contain a full or relative path to the icon."); } g_free (prop); } else if (g_strcmp0 (node_name, "url") == 0) { gchar *prop; prop = as_validator_check_type_property (validator, cpt, iter); if (as_url_kind_from_string (prop) == AS_URL_KIND_UNKNOWN) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_PROPERTY_INVALID, "Invalid property for 'url' tag: \"%s\"", prop); } g_free (prop); } else if (g_strcmp0 (node_name, "categories") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); as_validator_check_children_quick (validator, iter, "category", cpt); } else if (g_strcmp0 (node_name, "keywords") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); as_validator_check_children_quick (validator, iter, "keyword", cpt); } else if (g_strcmp0 (node_name, "mimetypes") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); as_validator_check_children_quick (validator, iter, "mimetype", cpt); } else if (g_strcmp0 (node_name, "provides") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); } else if (g_strcmp0 (node_name, "screenshots") == 0) { as_validator_check_children_quick (validator, iter, "screenshot", cpt); } else if (g_strcmp0 (node_name, "project_license") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); } else if (g_strcmp0 (node_name, "project_group") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); } else if (g_strcmp0 (node_name, "developer_name") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); } else if (g_strcmp0 (node_name, "compulsory_for_desktop") == 0) { if (!as_utils_is_desktop_environment (node_content)) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_VALUE_WRONG, "Unknown desktop-id '%s'.", node_content); } } else if (g_strcmp0 (node_name, "releases") == 0) { as_validator_check_children_quick (validator, iter, "release", cpt); } else if (g_strcmp0 (node_name, "languages") == 0) { as_validator_check_appear_once (validator, iter, found_tags, cpt); as_validator_check_children_quick (validator, iter, "lang", cpt); } else if ((g_strcmp0 (node_name, "translation") == 0) && (mode == AS_FORMAT_STYLE_METAINFO)) { g_autofree gchar *prop = NULL; AsTranslationKind trkind; prop = as_validator_check_type_property (validator, cpt, iter); trkind = as_translation_kind_from_string (prop); if (prop != NULL && trkind == AS_TRANSLATION_KIND_UNKNOWN) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_VALUE_WRONG, "Unknown type '%s' for <translation/> tag.", prop); } } else if (g_strcmp0 (node_name, "extends") == 0) { } else if (g_strcmp0 (node_name, "bundle") == 0) { g_autofree gchar *prop = NULL; prop = as_validator_check_type_property (validator, cpt, iter); if (prop != NULL && as_bundle_kind_from_string (prop) == AS_BUNDLE_KIND_UNKNOWN) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_VALUE_WRONG, "Unknown type '%s' for <bundle/> tag.", prop); } } else if (g_strcmp0 (node_name, "update_contact") == 0) { if (mode == AS_FORMAT_STYLE_COLLECTION) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_WARNING, AS_ISSUE_KIND_TAG_NOT_ALLOWED, "The 'update_contact' tag should not be included in collection AppStream XML."); } else { as_validator_check_appear_once (validator, iter, found_tags, cpt); } } else if (g_strcmp0 (node_name, "suggests") == 0) { as_validator_check_children_quick (validator, iter, "id", cpt); } else if ((g_strcmp0 (node_name, "metadata") == 0) || (g_strcmp0 (node_name, "kudos") == 0)) { /* these tags are GNOME / Fedora specific extensions and are therefore quite common. They shouldn't make the validation fail, * especially if we might standardize at leat the <kudos/> tag one day, but we should still complain about those tags to make * it obvious that they are not supported by all implementations */ as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_INFO, AS_ISSUE_KIND_TAG_UNKNOWN, "Found invalid tag: '%s'. This tag is a GNOME extensions to AppStream and is not supported by all implementations.", node_name); tag_valid = FALSE; } else if (!g_str_has_prefix (node_name, "x-")) { as_validator_add_issue (validator, iter, AS_ISSUE_IMPORTANCE_WARNING, AS_ISSUE_KIND_TAG_UNKNOWN, "Found invalid tag: '%s'. Non-standard tags must be prefixed with \"x-\".", node_name); tag_valid = FALSE; } if (tag_valid) { as_validator_check_content_empty (validator, iter, node_name, AS_ISSUE_IMPORTANCE_WARNING, cpt); } } /* emit an error if we are missing the metadata license in metainfo files */ if ((!has_metadata_license) && (mode == AS_FORMAT_STYLE_METAINFO)) { as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_TAG_MISSING, "The essential tag 'metadata_license' is missing."); } /* check if we have a description */ if (as_str_empty (as_component_get_description (cpt))) { AsComponentKind cpt_kind; cpt_kind = as_component_get_kind (cpt); if ((cpt_kind == AS_COMPONENT_KIND_DESKTOP_APP) || (cpt_kind == AS_COMPONENT_KIND_CONSOLE_APP) || (cpt_kind == AS_COMPONENT_KIND_WEB_APP)) { as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_TAG_MISSING, "The component is missing a long description. Components of this type must have a long description."); } else if (cpt_kind == AS_COMPONENT_KIND_FONT) { as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_PEDANTIC, AS_ISSUE_KIND_TAG_MISSING, "It would be useful for add a long description to this font to present it better to users."); } else if (cpt_kind != AS_COMPONENT_KIND_GENERIC) { as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_INFO, AS_ISSUE_KIND_TAG_MISSING, "The component is missing a long description. It is recommended to add one."); } } /* validate console-app specific stuff */ if (as_component_get_kind (cpt) == AS_COMPONENT_KIND_CONSOLE_APP) { if (as_component_get_provided_for_kind (cpt, AS_PROVIDED_KIND_BINARY) == NULL) as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_WARNING, AS_ISSUE_KIND_TAG_MISSING, "Type 'console-application' component, but no information about binaries in $PATH was provided via a provides/binary tag."); } /* validate font specific stuff */ if (as_component_get_kind (cpt) == AS_COMPONENT_KIND_FONT) { if (as_component_get_provided_for_kind (cpt, AS_PROVIDED_KIND_FONT) == NULL) as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_WARNING, AS_ISSUE_KIND_TAG_MISSING, "Type 'font' component, but no font information was provided via a provides/font tag."); } /* validate driver specific stuff */ if (as_component_get_kind (cpt) == AS_COMPONENT_KIND_DRIVER) { if (as_component_get_provided_for_kind (cpt, AS_PROVIDED_KIND_MODALIAS) == NULL) as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_INFO, AS_ISSUE_KIND_TAG_MISSING, "Type 'driver' component, but no modalias information was provided via a provides/modalias tag."); } /* validate addon specific stuff */ if (as_component_get_extends (cpt)->len > 0) { AsComponentKind kind = as_component_get_kind (cpt); if ((kind != AS_COMPONENT_KIND_ADDON) && (kind != AS_COMPONENT_KIND_LOCALIZATION)) as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_TAG_NOT_ALLOWED, "An 'extends' tag is specified, but the component is not of type 'addon' or 'localization'."); } else { if (as_component_get_kind (cpt) == AS_COMPONENT_KIND_ADDON) as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_TAG_MISSING, "The component is an addon, but no 'extends' tag was specified."); } /* validate l10n specific stuff */ if (as_component_get_kind (cpt) == AS_COMPONENT_KIND_LOCALIZATION) { if (as_component_get_extends (cpt)->len == 0) { as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_WARNING, AS_ISSUE_KIND_TAG_MISSING, "This 'localization' component is missing an An 'extends' tag, to specify the components it adds localization to."); } if (g_hash_table_size (as_component_get_languages_table (cpt)) == 0) { as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_TAG_MISSING, "This 'localization' component does not define any languages this localization is for."); } } /* validate suggestions */ if (as_component_get_suggested (cpt)->len > 0) { guint j; GPtrArray *sug_array; sug_array = as_component_get_suggested (cpt); for (j = 0; j < sug_array->len; j++) { AsSuggested *prov = AS_SUGGESTED (g_ptr_array_index (sug_array, j)); if (mode == AS_FORMAT_STYLE_METAINFO) { if (as_suggested_get_kind (prov) != AS_SUGGESTED_KIND_UPSTREAM) as_validator_add_issue (validator, NULL, AS_ISSUE_IMPORTANCE_ERROR, AS_ISSUE_KIND_VALUE_WRONG, "Suggestions of any type other than 'upstream' are not allowed in metainfo files (type was '%s')", as_suggested_kind_to_string (as_suggested_get_kind (prov))); } } } as_validator_clear_current_cpt (validator); return cpt; }
/** * as_pool_load_appstream: * * Load fresh metadata from AppStream directories. */ static gboolean as_pool_load_appstream (AsPool *pool, GError **error) { GPtrArray *cpts; g_autoptr(GPtrArray) merge_cpts = NULL; guint i; gboolean ret; g_autoptr(AsMetadata) metad = NULL; g_autoptr(GPtrArray) mdata_files = NULL; GError *tmp_error = NULL; AsPoolPrivate *priv = GET_PRIVATE (pool); /* see if we can use the caches */ if (!as_pool_metadata_changed (pool)) { g_autofree gchar *fname = NULL; g_debug ("Caches are up to date."); if (as_flags_contains (priv->cache_flags, AS_CACHE_FLAG_USE_SYSTEM)) { g_debug ("Using cached data."); fname = g_strdup_printf ("%s/%s.gvz", priv->sys_cache_path, priv->locale); if (g_file_test (fname, G_FILE_TEST_EXISTS)) { return as_pool_load_cache_file (pool, fname, error); } else { g_debug ("Missing cache for language '%s', attempting to load fresh data.", priv->locale); } } else { g_debug ("Not using system cache."); } } /* prepare metadata parser */ metad = as_metadata_new (); as_metadata_set_format_style (metad, AS_FORMAT_STYLE_COLLECTION); as_metadata_set_locale (metad, priv->locale); /* find AppStream metadata */ ret = TRUE; mdata_files = g_ptr_array_new_with_free_func (g_free); /* find XML data */ for (i = 0; i < priv->xml_dirs->len; i++) { const gchar *xml_path = (const gchar *) g_ptr_array_index (priv->xml_dirs, i); guint j; if (g_file_test (xml_path, G_FILE_TEST_IS_DIR)) { g_autoptr(GPtrArray) xmls = NULL; g_debug ("Searching for data in: %s", xml_path); xmls = as_utils_find_files_matching (xml_path, "*.xml*", FALSE, NULL); if (xmls != NULL) { for (j = 0; j < xmls->len; j++) { const gchar *val; val = (const gchar *) g_ptr_array_index (xmls, j); g_ptr_array_add (mdata_files, g_strdup (val)); } } } } /* find YAML metadata */ for (i = 0; i < priv->yaml_dirs->len; i++) { const gchar *yaml_path = (const gchar *) g_ptr_array_index (priv->yaml_dirs, i); guint j; if (g_file_test (yaml_path, G_FILE_TEST_IS_DIR)) { g_autoptr(GPtrArray) yamls = NULL; g_debug ("Searching for data in: %s", yaml_path); yamls = as_utils_find_files_matching (yaml_path, "*.yml*", FALSE, NULL); if (yamls != NULL) { for (j = 0; j < yamls->len; j++) { const gchar *val; val = (const gchar *) g_ptr_array_index (yamls, j); g_ptr_array_add (mdata_files, g_strdup (val)); } } } } /* parse the found data */ for (i = 0; i < mdata_files->len; i++) { g_autoptr(GFile) infile = NULL; const gchar *fname; fname = (const gchar*) g_ptr_array_index (mdata_files, i); g_debug ("Reading: %s", fname); infile = g_file_new_for_path (fname); if (!g_file_query_exists (infile, NULL)) { g_warning ("Metadata file '%s' does not exist.", fname); continue; } as_metadata_parse_file (metad, infile, AS_FORMAT_KIND_UNKNOWN, &tmp_error); if (tmp_error != NULL) { g_debug ("WARNING: %s", tmp_error->message); g_error_free (tmp_error); tmp_error = NULL; ret = FALSE; } } /* add found components to the metadata pool */ cpts = as_metadata_get_components (metad); merge_cpts = g_ptr_array_new (); for (i = 0; i < cpts->len; i++) { AsComponent *cpt = AS_COMPONENT (g_ptr_array_index (cpts, i)); /* TODO: We support only system components at time */ as_component_set_scope (cpt, AS_COMPONENT_SCOPE_SYSTEM); /* deal with merge-components later */ if (as_component_get_merge_kind (cpt) != AS_MERGE_KIND_NONE) { g_ptr_array_add (merge_cpts, cpt); continue; } as_pool_add_component (pool, cpt, &tmp_error); if (tmp_error != NULL) { g_debug ("Metadata ignored: %s", tmp_error->message); g_error_free (tmp_error); tmp_error = NULL; } } /* we need to merge the merge-components into the pool last, so the merge process can fetch * all components with matching IDs from the pool */ for (i = 0; i < merge_cpts->len; i++) { AsComponent *mcpt = AS_COMPONENT (g_ptr_array_index (merge_cpts, i)); as_pool_add_component (pool, mcpt, &tmp_error); if (tmp_error != NULL) { g_debug ("Merge component ignored: %s", tmp_error->message); g_error_free (tmp_error); tmp_error = NULL; } } return ret; }
/** * as_pool_add_component_internal: * @pool: An instance of #AsPool * @cpt: The #AsComponent to add to the pool. * @pedantic_noadd: If %TRUE, always emit an error if component couldn't be added. * @error: A #GError or %NULL * * Internal. */ static gboolean as_pool_add_component_internal (AsPool *pool, AsComponent *cpt, gboolean pedantic_noadd, GError **error) { const gchar *cdid = NULL; AsComponent *existing_cpt; gint pool_priority; AsPoolPrivate *priv = GET_PRIVATE (pool); cdid = as_component_get_data_id (cpt); if (as_component_is_ignored (cpt)) { if (pedantic_noadd) g_set_error (error, AS_POOL_ERROR, AS_POOL_ERROR_FAILED, "Skipping '%s' from inclusion into the pool: Component is ignored.", cdid); return FALSE; } existing_cpt = g_hash_table_lookup (priv->cpt_table, cdid); if (as_component_get_origin_kind (cpt) == AS_ORIGIN_KIND_DESKTOP_ENTRY) { g_autofree gchar *tmp_cdid = NULL; /* .desktop entries might map to existing metadata data with or without .desktop suffix, we need to check for that. * (the .desktop suffix is optional for desktop-application metainfo files, and the desktop-entry parser will automatically * omit it if the desktop-entry-id is following the reverse DNS scheme) */ if (existing_cpt == NULL) { tmp_cdid = g_strdup_printf ("%s.desktop", cdid); existing_cpt = g_hash_table_lookup (priv->cpt_table, tmp_cdid); } } if (existing_cpt == NULL) { /* add additional data to the component, e.g. external screenshots. Also refines * the component's icon paths */ as_component_complete (cpt, priv->screenshot_service_url, priv->icon_dirs); g_hash_table_insert (priv->cpt_table, g_strdup (cdid), g_object_ref (cpt)); return TRUE; } /* perform metadata merges if necessary */ if (as_component_get_merge_kind (cpt) != AS_MERGE_KIND_NONE) { g_autoptr(GPtrArray) matches = NULL; guint i; /* we merge the data into all components with matching IDs at time */ matches = as_pool_get_components_by_id (pool, as_component_get_id (cpt)); for (i = 0; i < matches->len; i++) { AsComponent *match = AS_COMPONENT (g_ptr_array_index (matches, i)); as_merge_components (match, cpt); } return TRUE; } /* if we are here, we might have duplicates and no merges, so check if we should replace a component * with data of higher priority, or if we have an actual error in the metadata */ pool_priority = as_component_get_priority (existing_cpt); if (pool_priority < as_component_get_priority (cpt)) { g_hash_table_replace (priv->cpt_table, g_strdup (cdid), g_object_ref (cpt)); g_debug ("Replaced '%s' with data of higher priority.", cdid); } else { /* bundles are treated specially here */ if ((!as_component_has_bundle (existing_cpt)) && (as_component_has_bundle (cpt))) { GPtrArray *bundles; /* propagate bundle information to existing component */ bundles = as_component_get_bundles (cpt); as_component_set_bundles_array (existing_cpt, bundles); return TRUE; } /* experimental multiarch support */ if (as_component_get_architecture (cpt) != NULL) { if (as_arch_compatible (as_component_get_architecture (cpt), priv->current_arch)) { const gchar *earch; /* this component is compatible with our current architecture */ earch = as_component_get_architecture (existing_cpt); if (earch != NULL) { if (as_arch_compatible (earch, priv->current_arch)) { g_hash_table_replace (priv->cpt_table, g_strdup (cdid), g_object_ref (cpt)); g_debug ("Preferred component for native architecture for %s (was %s)", cdid, earch); return TRUE; } else { g_debug ("Ignored additional entry for '%s' on architecture %s.", cdid, earch); return FALSE; } } } } if (pool_priority == as_component_get_priority (cpt)) { g_set_error (error, AS_POOL_ERROR, AS_POOL_ERROR_COLLISION, "Detected colliding ids: %s was already added with the same priority.", cdid); return FALSE; } else { if (pedantic_noadd) g_set_error (error, AS_POOL_ERROR, AS_POOL_ERROR_COLLISION, "Detected colliding ids: %s was already added with a higher priority.", cdid); return FALSE; } } return TRUE; }