Example #1
0
/**
 * 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;
}
Example #2
0
/**
 * 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;
}
Example #3
0
/**
 * 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));
}
Example #4
0
/**
 * 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;
}
Example #5
0
/**
 * 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;
}
Example #6
0
/**
 * 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;
}