static gboolean
as_app_validate_has_first_word_capital (AsAppValidateHelper *helper, const gchar *text)
{
	g_autofree gchar *first_word = NULL;
	gchar *tmp;
	guint i;

	if (text == NULL || text[0] == '\0')
		return TRUE;

	/* text starts with a number */
	if (g_ascii_isdigit (text[0]))
		return TRUE;

	/* get the first word */
	first_word = g_strdup (text);
	tmp = g_strstr_len (first_word, -1, " ");
	if (tmp != NULL)
		*tmp = '\0';

	/* does the word have caps anywhere? */
	for (i = 0; first_word[i] != '\0'; i++) {
		if (first_word[i] >= 'A' && first_word[i] <= 'Z')
			return TRUE;
	}

	/* is the first word the project name */
	if (g_strcmp0 (first_word, as_app_get_name (helper->app, NULL)) == 0)
		return TRUE;

	return FALSE;
}
Exemple #2
0
static gboolean
gs_plugin_steam_update_description (AsApp *app,
				    const gchar *html,
				    GError **error)
{
	guint i = 0;
	g_autofree gchar *desc = NULL;
	g_autofree gchar *subsect = NULL;
	g_autoptr(GError) error_local = NULL;
	g_autoptr(GString) subsect_str = NULL;

	/* get the game description div section */
	subsect = gs_plugin_steam_capture (html,
			"<div id=\"game_area_description\" class=\"game_area_description\">",
			"</div>", &i);

	/* fall back gracefully */
	if (subsect == NULL) {
		subsect = gs_plugin_steam_capture (html,
				"<meta name=\"Description\" content=\"",
				"\">", &i);
	}
	if (subsect == NULL) {
		g_warning ("Failed to get description for %s [%s]",
			   as_app_get_name (app, NULL),
			   as_app_get_id (app));
		return TRUE;
	}
	subsect_str = g_string_new (subsect);
	as_utils_string_replace (subsect_str, "About This Game", "");
	desc = as_markup_import (subsect_str->str,
				 AS_MARKUP_CONVERT_FORMAT_HTML,
				 &error_local);
	if (desc == NULL) {
		g_warning ("Failed to parse description for %s [%s]: %s",
			   as_app_get_name (app, NULL),
			   as_app_get_id (app),
			   error_local->message);
		return TRUE;
	}
	as_app_set_description (app, NULL, desc);
	return TRUE;
}
Exemple #3
0
static void
gs_editor_button_remove_clicked_cb (GtkWidget *widget, GsEditor *self)
{
	const gchar *name;
	g_autofree gchar *msg = NULL;

	if (self->selected_item == NULL)
		return;

	/* send notification */
	name = as_app_get_name (self->selected_item, NULL);
	if (name == NULL) {
		AsApp *item_global = as_store_get_app_by_id (self->store_global,
							     as_app_get_id (self->selected_item));
		if (item_global != NULL)
			name = as_app_get_name (item_global, NULL);
	}
	if (name != NULL) {
		g_autofree gchar *name_markup = NULL;
		name_markup = g_strdup_printf ("<b>%s</b>", name);
		/* TRANSLATORS, the %s is the app name, e.g. 'Inkscape' */
		msg = g_strdup_printf (_("%s banner design deleted."), name_markup);
	} else {
		/* TRANSLATORS, this is a notification */
		msg = g_strdup (_("Banner design deleted."));
	}
	gs_editor_show_notification (self, msg);

	/* save this so we can undo */
	g_set_object (&self->deleted_item, self->selected_item);

	as_store_remove_app_by_id (self->store, as_app_get_id (self->selected_item));
	self->pending_changes = TRUE;
	gs_editor_refresh_choice (self);

	/* set the appropriate page */
	gs_editor_set_page (self, as_store_get_size (self->store) == 0 ? "none" : "choice");
}
Exemple #4
0
/**
 * asb_context_detect_missing_data:
 **/
static gboolean
asb_context_detect_missing_data (AsbContext *ctx, GError **error)
{
	AsApp *app;
	AsbContextPrivate *priv = GET_PRIVATE (ctx);
	GList *l;

	/* look for the thing that an addon extends */
	for (l = priv->apps; l != NULL; l = l->next) {
		app = AS_APP (l->data);
		if (as_app_get_name (AS_APP (app), "C") == NULL)
			as_app_add_veto (AS_APP (app), "No <name> in AppData");
		if (as_app_get_comment (AS_APP (app), "C") == NULL)
			as_app_add_veto (AS_APP (app), "No <summary> in AppData");
		if (as_app_get_id_kind (AS_APP (app)) != AS_ID_KIND_ADDON) {
			if (as_app_get_icon_default (AS_APP (app)) == NULL)
				as_app_add_veto (AS_APP (app), "Has no Icon");
		}
	}
	return TRUE;
}
/**
 * gs_appstream_refine_app:
 */
gboolean
gs_appstream_refine_app (GsPlugin *plugin,
			 GsApp *app,
			 AsApp *item,
			 GError **error)
{
	AsRelease *rel;
	GHashTable *urls;
	GPtrArray *pkgnames;
	GPtrArray *kudos;
	const gchar *tmp;
	guint i;

	/* set the kind to be more precise */
	if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN ||
	    gs_app_get_kind (app) == AS_APP_KIND_GENERIC) {
		gs_app_set_kind (app, as_app_get_kind (item));
	}

	/* is installed already */
	if (gs_app_get_state (app) == AS_APP_STATE_UNKNOWN) {
		switch (as_app_get_source_kind (item)) {
		case AS_APP_SOURCE_KIND_APPDATA:
		case AS_APP_SOURCE_KIND_DESKTOP:
			gs_app_set_kind (app, AS_APP_KIND_DESKTOP);
			gs_app_set_state (app, AS_APP_STATE_INSTALLED);
			break;
		case AS_APP_SOURCE_KIND_METAINFO:
			gs_app_set_state (app, AS_APP_STATE_INSTALLED);
			break;
		case AS_APP_SOURCE_KIND_APPSTREAM:
			gs_app_set_state (app, as_app_get_state (item));
			break;
		default:
			break;
		}
	}

	/* set management plugin automatically */
	gs_refine_item_management_plugin (app, item);

	/* set id */
	if (as_app_get_id (item) != NULL && gs_app_get_id (app) == NULL)
		gs_app_set_id (app, as_app_get_id (item));

	/* set name */
	tmp = as_app_get_name (item, NULL);
	if (tmp != NULL) {
		if (g_str_has_prefix (tmp, "(Nightly) ")) {
			tmp += 10;
			if (gs_app_get_metadata_item (app, "X-XdgApp-Tags") == NULL)
				gs_app_set_metadata (app, "X-XdgApp-Tags", "nightly");
		}
		gs_app_set_name (app, GS_APP_QUALITY_HIGHEST, tmp);
	}

	/* set summary */
	tmp = as_app_get_comment (item, NULL);
	if (tmp != NULL) {
		gs_app_set_summary (app, GS_APP_QUALITY_HIGHEST, tmp);
	}

	/* add urls */
	urls = as_app_get_urls (item);
	if (g_hash_table_size (urls) > 0 &&
	    gs_app_get_url (app, AS_URL_KIND_HOMEPAGE) == NULL) {
		GList *l;
		g_autoptr(GList) keys = NULL;
		keys = g_hash_table_get_keys (urls);
		for (l = keys; l != NULL; l = l->next) {
			gs_app_set_url (app,
					as_url_kind_from_string (l->data),
					g_hash_table_lookup (urls, l->data));
		}
	}

	/* set licence */
	if (as_app_get_project_license (item) != NULL && gs_app_get_license (app) == NULL)
		gs_app_set_license (app,
				    GS_APP_QUALITY_HIGHEST,
				    as_app_get_project_license (item));

	/* set keywords */
	if (as_app_get_keywords (item, NULL) != NULL &&
	    gs_app_get_keywords (app) == NULL) {
		gs_app_set_keywords (app, as_app_get_keywords (item, NULL));
		gs_app_add_kudo (app, GS_APP_KUDO_HAS_KEYWORDS);
	}

	/* set origin */
	if (as_app_get_origin (item) != NULL &&
	    gs_app_get_origin (app) == NULL) {
		gs_app_set_origin (app, as_app_get_origin (item));
	}

	/* set description */
	tmp = as_app_get_description (item, NULL);
	if (tmp != NULL) {
		g_autofree gchar *from_xml = NULL;
		from_xml = as_markup_convert_simple (tmp, error);
		if (from_xml == NULL) {
			g_prefix_error (error, "trying to parse '%s': ", tmp);
			return FALSE;
		}
		gs_app_set_description (app, GS_APP_QUALITY_HIGHEST, from_xml);
	}

	/* set icon */
	if (as_app_get_icon_default (item) != NULL && gs_app_get_pixbuf (app) == NULL)
		gs_refine_item_pixbuf (plugin, app, item);

	/* set categories */
	if (as_app_get_categories (item) != NULL &&
	    gs_app_get_categories (app)->len == 0)
		gs_app_set_categories (app, as_app_get_categories (item));

	/* set project group */
	if (as_app_get_project_group (item) != NULL &&
	    gs_app_get_project_group (app) == NULL)
		gs_app_set_project_group (app, as_app_get_project_group (item));

	/* this is a core application for the desktop and cannot be removed */
	if (_as_app_has_compulsory_for_desktop (item, "GNOME") &&
	    gs_app_get_kind (app) == AS_APP_KIND_DESKTOP)
		gs_app_add_quirk (app, AS_APP_QUIRK_COMPULSORY);

	/* set id kind */
	if (gs_app_get_kind (app) == AS_APP_KIND_UNKNOWN)
		gs_app_set_kind (app, as_app_get_kind (item));

	/* copy all the metadata */
	gs_appstream_copy_metadata (app, item);

	/* set package names */
	pkgnames = as_app_get_pkgnames (item);
	if (pkgnames->len > 0 && gs_app_get_sources(app)->len == 0)
		gs_app_set_sources (app, pkgnames);

	/* set addons */
	gs_appstream_refine_add_addons (plugin, app, item);

	/* set screenshots */
	gs_appstream_refine_add_screenshots (app, item);

	/* are the screenshots perfect */
	if (gs_appstream_are_screenshots_perfect (item))
		gs_app_add_kudo (app, GS_APP_KUDO_PERFECT_SCREENSHOTS);

	/* was this application released recently */
	if (gs_appstream_is_recent_release (item))
		gs_app_add_kudo (app, GS_APP_KUDO_RECENT_RELEASE);

	/* add kudos */
	if (as_app_get_language (item, plugin->locale) > 50)
		gs_app_add_kudo (app, GS_APP_KUDO_MY_LANGUAGE);

	/* add new-style kudos */
	kudos = as_app_get_kudos (item);
	for (i = 0; i < kudos->len; i++) {
		tmp = g_ptr_array_index (kudos, i);
		switch (as_kudo_kind_from_string (tmp)) {
		case AS_KUDO_KIND_SEARCH_PROVIDER:
			gs_app_add_kudo (app, GS_APP_KUDO_SEARCH_PROVIDER);
			break;
		case AS_KUDO_KIND_USER_DOCS:
			gs_app_add_kudo (app, GS_APP_KUDO_INSTALLS_USER_DOCS);
			break;
		case AS_KUDO_KIND_APP_MENU:
			gs_app_add_kudo (app, GS_APP_KUDO_USES_APP_MENU);
			break;
		case AS_KUDO_KIND_MODERN_TOOLKIT:
			gs_app_add_kudo (app, GS_APP_KUDO_MODERN_TOOLKIT);
			break;
		case AS_KUDO_KIND_NOTIFICATIONS:
			gs_app_add_kudo (app, GS_APP_KUDO_USES_NOTIFICATIONS);
			break;
		case AS_KUDO_KIND_HIGH_CONTRAST:
			gs_app_add_kudo (app, GS_APP_KUDO_HIGH_CONTRAST);
			break;
		case AS_KUDO_KIND_HI_DPI_ICON:
			gs_app_add_kudo (app, GS_APP_KUDO_HI_DPI_ICON);
			break;
		default:
			g_debug ("no idea how to handle kudo '%s'", tmp);
			break;
		}
	}

	/* is there any update information */
	rel = as_app_get_release_default (item);
	if (rel != NULL) {
		tmp = as_release_get_description (rel, NULL);
		if (tmp != NULL) {
			g_autofree gchar *desc = NULL;
			desc = as_markup_convert (tmp,
						  AS_MARKUP_CONVERT_FORMAT_MARKDOWN,
						  error);
			if (desc == NULL)
				return FALSE;
			gs_app_set_update_details (app, desc);
		}
		gs_app_set_update_urgency (app, as_release_get_urgency (rel));
		gs_app_set_update_version (app, as_release_get_version (rel));
	}

	return TRUE;
}
/**
 * as_app_validate:
 * @app: a #AsApp instance.
 * @flags: the #AsAppValidateFlags to use, e.g. %AS_APP_VALIDATE_FLAG_NONE
 * @error: A #GError or %NULL.
 *
 * Validates data in the instance for style and consistency.
 *
 * Returns: (transfer container) (element-type AsProblem): A list of problems, or %NULL
 *
 * Since: 0.1.4
 **/
GPtrArray *
as_app_validate (AsApp *app, guint32 flags, GError **error)
{
	AsAppProblems problems;
	AsFormat *format;
	GError *error_local = NULL;
	GHashTable *urls;
	GList *l;
	const gchar *description;
	const gchar *key;
	const gchar *license;
	const gchar *name;
	const gchar *summary;
	const gchar *tmp;
	const gchar *update_contact;
	gboolean deprecated_failure = FALSE;
	gboolean require_appstream_spec_only = FALSE;
	gboolean require_contactdetails = FALSE;
	gboolean require_copyright = FALSE;
	gboolean require_description = FALSE;
	gboolean require_project_license = FALSE;
	gboolean require_sentence_case = FALSE;
	gboolean require_translations = FALSE;
	gboolean require_url = TRUE;
	gboolean require_content_license = TRUE;
	gboolean require_name = TRUE;
	gboolean require_translation = FALSE;
	gboolean require_content_rating = FALSE;
	gboolean require_name_shorter_than_summary = FALSE;
	gboolean validate_license = TRUE;
	gboolean ret;
	guint length_name_max = 60;
	guint length_name_min = 3;
	guint length_summary_max = 200;
	guint length_summary_min = 8;
	guint number_para_max = 10;
	guint number_para_min = 1;
	guint str_len;
	g_autoptr(GList) keys = NULL;
	g_autoptr(AsAppValidateHelper) helper = g_new0 (AsAppValidateHelper, 1);

	/* has to be set */
	format = as_app_get_format_default (app);
	if (format == NULL) {
		g_set_error_literal (error,
				     AS_APP_ERROR,
				     AS_APP_ERROR_FAILED,
				     "cannot validate without at least one format");
		return NULL;
	}

	/* only for desktop and console apps */
	if (as_app_get_kind (app) == AS_APP_KIND_DESKTOP ||
	    as_app_get_kind (app) == AS_APP_KIND_CONSOLE) {
		require_content_rating = TRUE;
		require_description = TRUE;
	}

	/* relax the requirements a bit */
	if ((flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) {
		length_name_max = 100;
		length_summary_max = 200;
		require_content_license = FALSE;
		validate_license = FALSE;
		require_url = FALSE;
		number_para_max = 20;
		number_para_min = 1;
		require_sentence_case = FALSE;
		require_content_rating = FALSE;
		switch (as_format_get_kind (format)) {
		case AS_FORMAT_KIND_METAINFO:
		case AS_FORMAT_KIND_APPDATA:
			require_name = FALSE;
			break;
		default:
			break;
		}
	}

	/* make the requirements more strict */
	if ((flags & AS_APP_VALIDATE_FLAG_STRICT) > 0) {
		deprecated_failure = TRUE;
		require_copyright = TRUE;
		require_translations = TRUE;
		require_project_license = TRUE;
		require_content_license = TRUE;
		require_appstream_spec_only = TRUE;
		require_sentence_case = TRUE;
		require_name_shorter_than_summary = TRUE;
		require_contactdetails = TRUE;
		require_translation = TRUE;
		number_para_min = 2;
		number_para_max = 4;
	}

	/* addons don't need such a long description */
	switch (as_format_get_kind (format)) {
	case AS_FORMAT_KIND_METAINFO:
	case AS_FORMAT_KIND_APPDATA:
		number_para_min = 1;
		break;
	default:
		break;
	}

	/* set up networking */
	helper->app = app;
	helper->probs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
	helper->screenshot_urls = g_ptr_array_new_with_free_func (g_free);
	helper->flags = flags;
	if (!as_app_validate_setup_networking (helper, error))
		return NULL;

	/* invalid component type */
	if (as_app_get_kind (app) == AS_APP_KIND_UNKNOWN) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_ATTRIBUTE_INVALID,
				     "<component> has invalid type attribute");

	}
	as_app_validate_check_id (helper, as_app_get_id (app));

	/* metadata_license */
	license = as_app_get_metadata_license (app);
	if (license != NULL) {
		if (require_content_license &&
		    !as_app_validate_is_content_license (license)) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     "<metadata_license> is not valid [%s]",
					     license);
		} else if (validate_license) {
			if (!as_app_validate_license (license, &error_local)) {
				g_prefix_error (&error_local,
						"<metadata_license> is not valid [%s]",
						license);
				ai_app_validate_add (helper,
						     AS_PROBLEM_KIND_TAG_INVALID,
						     "%s", error_local->message);
				g_clear_error (&error_local);
			}
		}
	}
	if (license == NULL) {
		switch (as_format_get_kind (format)) {
		case AS_FORMAT_KIND_APPDATA:
		case AS_FORMAT_KIND_METAINFO:
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<metadata_license> is not present");
			break;
		default:
			break;
		}
	}

	/* project_license */
	license = as_app_get_project_license (app);
	if (license != NULL && validate_license) {
		if (!as_app_validate_license (license, &error_local)) {
			g_prefix_error (&error_local,
					"<project_license> is not valid [%s]",
					license);
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     "%s", error_local->message);
			g_clear_error (&error_local);
		}
	}
	if (require_project_license && license == NULL) {
		switch (as_format_get_kind (format)) {
		case AS_FORMAT_KIND_APPDATA:
		case AS_FORMAT_KIND_METAINFO:
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<project_license> is not present");
			break;
		default:
			break;
		}
	}

	/* categories */
	if (as_format_get_kind (format) == AS_FORMAT_KIND_APPSTREAM &&
	    as_app_get_kind (app) == AS_APP_KIND_DESKTOP) {
		GPtrArray *categories = as_app_get_categories (app);
		guint nr_toplevel_cats = 0;
		const gchar *cats[] = { "AudioVideo",
					"Development",
					"Education",
					"Game",
					"Graphics",
					"Network",
					"Office",
					"Science",
					"Settings",
					"System",
					"Utility",
					NULL };
		for (guint i = 0; i < categories->len; i++) {
			const gchar *cat = g_ptr_array_index (categories, i);
			for (guint j = 0; cats[j] != NULL; j++) {
				if (g_strcmp0 (cats[j], cat) == 0)
					nr_toplevel_cats++;
			}
		}
		if (nr_toplevel_cats == 0) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<category> must include main categories "
					     "from the desktop entry spec");
		} else if (nr_toplevel_cats > 3) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "too many main <category> types: %u",
					     nr_toplevel_cats);
		}
	}

	/* translation */
	if (require_translation &&
	    as_format_get_kind (format) == AS_FORMAT_KIND_APPDATA &&
	    as_app_get_translations (app)->len == 0) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_MISSING,
				     "<translation> not specified");
	}

	/* pkgname */
	if (as_app_get_pkgname_default (app) != NULL &&
	    as_format_get_kind (format) == AS_FORMAT_KIND_METAINFO) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "<pkgname> not allowed in metainfo");
	}

	/* appdata */
	if (as_format_get_kind (format) == AS_FORMAT_KIND_APPDATA &&
	    as_app_get_kind (app) == AS_APP_KIND_DESKTOP) {
		AsIcon *icon = as_app_get_icon_default (app);
		if (icon != NULL &&
		    as_icon_get_kind (icon) != AS_ICON_KIND_REMOTE) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     "<icon> not allowed in desktop appdata");
		}
	}

	/* extends */
	if (as_app_get_extends(app)->len == 0 &&
	    as_app_get_kind (app) == AS_APP_KIND_ADDON &&
	    as_format_get_kind (format) == AS_FORMAT_KIND_METAINFO) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_MISSING,
				     "<extends> is not present");
	}

	/* update_contact */
	update_contact = as_app_get_update_contact (app);
	if (g_strcmp0 (update_contact,
		       "someone_who_cares@upstream_project.org") == 0) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "<update_contact> is still set to a dummy value");
	}
	if (update_contact != NULL && strlen (update_contact) < 6) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_STYLE_INCORRECT,
				     "<update_contact> is too short [%s]",
				     update_contact);
	}
	if (require_contactdetails && update_contact == NULL) {
		switch (as_format_get_kind (format)) {
		case AS_FORMAT_KIND_APPDATA:
		case AS_FORMAT_KIND_METAINFO:
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<update_contact> is not present");
			break;
		default:
			break;
		}
	}

	/* only found for files */
	problems = as_app_get_problems (app);
	if (as_format_get_kind (format) == AS_FORMAT_KIND_APPDATA ||
	    as_format_get_kind (format) == AS_FORMAT_KIND_METAINFO) {
		if ((problems & AS_APP_PROBLEM_NO_XML_HEADER) > 0) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_MARKUP_INVALID,
					     "<?xml> header not found");
		}
		if (require_copyright &&
		    (problems & AS_APP_PROBLEM_NO_COPYRIGHT_INFO) > 0) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_VALUE_MISSING,
					     "<!-- Copyright [year] [name] --> is not present");
		}
		if (deprecated_failure &&
		    (problems & AS_APP_PROBLEM_UPDATECONTACT_FALLBACK) > 0) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     "<updatecontact> should be <update_contact>");
		}
	}

	/* check invalid values */
	if ((problems & AS_APP_PROBLEM_INVALID_PROJECT_GROUP) > 0) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "<project_group> is not valid");
	}

	/* only allow XML in the specification */
	if (require_appstream_spec_only &&
	    (problems & AS_APP_PROBLEM_INVALID_XML_TAG) > 0) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "XML data contains unknown tag");
	}

	/* only allow XML in the specification */
	if (problems & AS_APP_PROBLEM_EXPECTED_CHILDREN) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "Expected children for tag");
	}

	/* only allow XML in the specification */
	if (problems & AS_APP_PROBLEM_INVALID_KEYWORDS) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "<keyword> invalid contents");
	}

	/* releases all have to have unique versions */
	if (problems & AS_APP_PROBLEM_DUPLICATE_RELEASE) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "<release> version was duplicated");
	}
	if (problems & AS_APP_PROBLEM_DUPLICATE_SCREENSHOT) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "<screenshot> content was duplicated");
	}
	if (problems & AS_APP_PROBLEM_DUPLICATE_CONTENT_RATING) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "<content_rating> was duplicated");
	}

	/* check for things that have to exist */
	if (as_app_get_id (app) == NULL) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_MISSING,
				     "<id> is not present");
	}

	/* games require a content rating */
	if (require_content_rating) {
		GPtrArray *ratings = as_app_get_content_ratings (app);
		if (ratings->len == 0) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<content_rating> required "
					     "[use https://odrs.gnome.org/oars]");
		}
	}

	/* url */
	urls = as_app_get_urls (app);
	keys = g_hash_table_get_keys (urls);
	for (l = keys; l != NULL; l = l->next) {
		key = l->data;
		if (g_strcmp0 (key, "unknown") == 0) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     "<url> type invalid [%s]", key);
		}
		tmp = g_hash_table_lookup (urls, key);
		if (tmp == NULL || tmp[0] == '\0')
			continue;
		if (!g_str_has_prefix (tmp, "http://") &&
		    !g_str_has_prefix (tmp, "https://")) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     "<url> does not start with 'http://' [%s]",
					     tmp);
		}
	}

	/* screenshots */
	as_app_validate_screenshots (app, helper);

	/* icons */
	as_app_validate_icons (app, helper);

	/* releases */
	if (!as_app_validate_releases (app, helper, error))
		return NULL;

	/* kudos */
	if (!as_app_validate_kudos (app, helper, error))
		return NULL;

	/* name */
	name = as_app_get_name (app, "C");
	if (name != NULL) {
		str_len = (guint) strlen (name);
		if (str_len < length_name_min) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<name> is too short [%s] minimum is %u chars",
					     name, length_name_min);
		}
		if (str_len > length_name_max) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<name> is too long [%s] maximum is %u chars",
					     name, length_name_max);
		}
		if (ai_app_validate_fullstop_ending (name)) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<name> cannot end in '.' [%s]",
					     name);
		}
		if (as_app_validate_has_hyperlink (name)) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<name> cannot contain a hyperlink [%s]",
					     name);
		}
		if (require_sentence_case &&
		    !as_app_validate_has_first_word_capital (helper, name)) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<name> requires sentence case [%s]",
					     name);
		}
	} else if (require_name) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_MISSING,
				     "<name> is not present");
	}

	/* comment */
	summary = as_app_get_comment (app, "C");
	if (summary != NULL) {
		str_len = (guint) strlen (summary);
		if (str_len < length_summary_min) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<summary> is too short [%s] minimum is %u chars",
					     summary, length_summary_min);
		}
		if (str_len > length_summary_max) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<summary> is too long [%s] maximum is %u chars",
					     summary, length_summary_max);
		}
		if (require_sentence_case &&
		    ai_app_validate_fullstop_ending (summary)) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<summary> cannot end in '.' [%s]",
					     summary);
		}
		if (as_app_validate_has_hyperlink (summary)) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<summary> cannot contain a hyperlink [%s]",
					     summary);
		}
		if (require_sentence_case &&
		    !as_app_validate_has_first_word_capital (helper, summary)) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<summary> requires sentence case [%s]",
					     summary);
		}
	} else if (require_name) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_TAG_MISSING,
				     "<summary> is not present");
	}
	if (require_name_shorter_than_summary &&
	    summary != NULL && name != NULL &&
	    strlen (summary) < strlen (name)) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_STYLE_INCORRECT,
				     "<summary> is shorter than <name>");
	}
	description = as_app_get_description (app, "C");
	if (description == NULL) {
		if (require_description) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<description> required");
		}
	} else {
		ret = as_app_validate_description (description,
						   helper,
						   number_para_min,
						   number_para_max,
						   FALSE,
						   &error_local);
		if (!ret) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_MARKUP_INVALID,
					     "%s", error_local->message);
			g_error_free (error_local);
		}
	}
	if (require_translations) {
		if (name != NULL &&
		    as_app_get_name_size (app) == 1 &&
		    (problems & AS_APP_PROBLEM_INTLTOOL_NAME) == 0) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TRANSLATIONS_REQUIRED,
					     "<name> has no translations");
		}
		if (summary != NULL &&
		    as_app_get_comment_size (app) == 1 &&
		    (problems & AS_APP_PROBLEM_INTLTOOL_SUMMARY) == 0) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TRANSLATIONS_REQUIRED,
					     "<summary> has no translations");
		}
		if (description != NULL &&
		    as_app_get_description_size (app) == 1 &&
		    (problems & AS_APP_PROBLEM_INTLTOOL_DESCRIPTION) == 0) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TRANSLATIONS_REQUIRED,
					     "<description> has no translations");
		}
	}

	/* developer_name */
	name = as_app_get_developer_name (app, NULL);
	if (name != NULL) {
		str_len = (guint) strlen (name);
		if (str_len < length_name_min) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<developer_name> is too short [%s] minimum is %u chars",
					     name, length_name_min);
		}
		if (str_len > length_name_max) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<developer_name> is too long [%s] maximum is %u chars",
					     name, length_name_max);
		}
		if (as_app_validate_has_hyperlink (name)) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<developer_name> cannot contain a hyperlink [%s]",
					     name);
		}
		if (as_app_validate_has_email (name)) {
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<developer_name> cannot contain an email address [%s]",
					     name);
		}
	}

	/* using deprecated names */
	if (deprecated_failure && (problems & AS_APP_PROBLEM_DEPRECATED_LICENCE) > 0) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_ATTRIBUTE_INVALID,
				     "<licence> is deprecated, use "
				     "<metadata_license> instead");
	}
	if ((problems & AS_APP_PROBLEM_MULTIPLE_ENTRIES) > 0) {
		ai_app_validate_add (helper,
				     AS_PROBLEM_KIND_MARKUP_INVALID,
				     "<application> used more than once");
	}

	/* require homepage */
	if (require_url && as_app_get_url_item (app, AS_URL_KIND_HOMEPAGE) == NULL) {
		switch (as_format_get_kind (format)) {
		case AS_FORMAT_KIND_APPDATA:
		case AS_FORMAT_KIND_METAINFO:
			ai_app_validate_add (helper,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<url> is not present");
			break;
		default:
			break;
		}
	}
	return helper->probs;
}
Exemple #7
0
static void
gs_editor_refresh_details (GsEditor *self)
{
	AsAppKind app_kind = AS_APP_KIND_UNKNOWN;
	GtkWidget *widget;
	const gchar *css = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GsApp) app = NULL;

	/* ignore changed events */
	self->is_in_refresh = TRUE;

	/* create a GsApp for the AsApp */
	if (self->selected_item != NULL) {
		app = gs_editor_convert_app (self, self->selected_item);
		g_debug ("refreshing details for %s", gs_app_get_id (app));
	}

	/* get kind */
	if (self->selected_item != NULL)
		app_kind = as_app_get_kind (self->selected_item);

	/* feature tiles */
	if (app_kind != AS_APP_KIND_OS_UPGRADE) {
		if (self->selected_item != NULL) {
			gs_app_tile_set_app (GS_APP_TILE (self->featured_tile1), app);
			gtk_widget_set_sensitive (self->featured_tile1, TRUE);
		} else {
			gtk_widget_set_sensitive (self->featured_tile1, FALSE);
		}
		gtk_widget_set_visible (self->featured_tile1, TRUE);
	} else {
		gtk_widget_set_visible (self->featured_tile1, FALSE);
	}

	/* upgrade banner */
	if (app_kind == AS_APP_KIND_OS_UPGRADE) {
		if (self->selected_item != NULL) {
			gs_upgrade_banner_set_app (GS_UPGRADE_BANNER (self->upgrade_banner), app);
			gtk_widget_set_sensitive (self->upgrade_banner, TRUE);
		} else {
			gtk_widget_set_sensitive (self->upgrade_banner, FALSE);
		}
		gtk_widget_set_visible (self->upgrade_banner, TRUE);
	} else {
		gtk_widget_set_visible (self->upgrade_banner, FALSE);
	}

	/* name */
	widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_name"));
	if (self->selected_item != NULL) {
		const gchar *tmp;
		gtk_widget_set_visible (widget, app_kind == AS_APP_KIND_OS_UPGRADE);
		widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_name"));
		tmp = as_app_get_name (self->selected_item, NULL);
		if (tmp != NULL)
			gtk_entry_set_text (GTK_ENTRY (widget), tmp);
	} else {
		gtk_widget_set_visible (widget, FALSE);
	}

	/* summary */
	widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_summary"));
	if (self->selected_item != NULL) {
		const gchar *tmp;
		gtk_widget_set_visible (widget, app_kind == AS_APP_KIND_OS_UPGRADE);
		widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_summary"));
		tmp = as_app_get_comment (self->selected_item, NULL);
		if (tmp != NULL)
			gtk_entry_set_text (GTK_ENTRY (widget), tmp);
	} else {
		gtk_widget_set_visible (widget, FALSE);
	}

	/* kudos */
	widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_kudos"));
	if (self->selected_item != NULL) {
		gtk_widget_set_visible (widget, app_kind != AS_APP_KIND_OS_UPGRADE);
	} else {
		gtk_widget_set_visible (widget, TRUE);
	}

	/* category featured */
	widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "checkbutton_category_featured"));
	if (self->selected_item != NULL) {
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
					      as_app_has_category (self->selected_item,
								   "Featured"));
		gtk_widget_set_sensitive (widget, TRUE);
	} else {
		gtk_widget_set_sensitive (widget, FALSE);
	}

	/* kudo popular */
	widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "checkbutton_editors_pick"));
	if (self->selected_item != NULL) {
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
					      as_app_has_kudo (self->selected_item,
							       "GnomeSoftware::popular"));
		gtk_widget_set_sensitive (widget, TRUE);
	} else {
		gtk_widget_set_sensitive (widget, FALSE);
	}

	/* featured */
	widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "textview_css"));
	if (self->selected_item != NULL) {
		GtkTextBuffer *buffer;
		GtkTextIter iter_end;
		GtkTextIter iter_start;
		g_autofree gchar *css_existing = NULL;

		if (app_kind == AS_APP_KIND_OS_UPGRADE) {
			css = as_app_get_metadata_item (self->selected_item,
							"GnomeSoftware::UpgradeBanner-css");
		} else {
			css = as_app_get_metadata_item (self->selected_item,
							"GnomeSoftware::FeatureTile-css");
		}
		if (css == NULL)
			css = "";
		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
		gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
		css_existing = gtk_text_buffer_get_text (buffer, &iter_start, &iter_end, FALSE);
		if (g_strcmp0 (css_existing, css) != 0)
			gtk_text_buffer_set_text (buffer, css, -1);
		gtk_widget_set_sensitive (widget, TRUE);
	} else {
		gtk_widget_set_sensitive (widget, FALSE);
	}

	/* desktop ID */
	widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_desktop_id"));
	if (self->selected_item != NULL) {
		const gchar *id = as_app_get_id (self->selected_item);
		if (id == NULL)
			id = "";
		gtk_entry_set_text (GTK_ENTRY (widget), id);
		gtk_widget_set_sensitive (widget, TRUE);
	} else {
		gtk_entry_set_text (GTK_ENTRY (widget), "");
		gtk_widget_set_sensitive (widget, FALSE);
	}

	/* validate CSS */
	if (css == NULL) {
		widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "label_infobar_css"));
		gtk_label_set_label (GTK_LABEL (widget), "");
		widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "infobar_css"));
		gtk_info_bar_set_message_type (GTK_INFO_BAR (widget), GTK_MESSAGE_OTHER);
	} else if (!gs_design_validate_css (self, css, &error)) {
		g_autofree gchar *msg = g_strdup (error->message);
		g_strdelimit (msg, "\n\r<>", '\0');
		widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "label_infobar_css"));
		gtk_label_set_label (GTK_LABEL (widget), msg);
		widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "infobar_css"));
		gtk_info_bar_set_message_type (GTK_INFO_BAR (widget), GTK_MESSAGE_WARNING);
	} else {
		widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "label_infobar_css"));
		gtk_label_set_label (GTK_LABEL (widget), _("CSS validated OK!"));
		widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "infobar_css"));
		gtk_info_bar_set_message_type (GTK_INFO_BAR (widget), GTK_MESSAGE_OTHER);
	}

	/* do not ignore changed events */
	self->is_in_refresh = FALSE;
}
Exemple #8
0
static GsApp *
gs_editor_convert_app (GsEditor *self, AsApp *item)
{
	AsApp *item_global;
	AsAppState item_state;
	GsApp *app;
	const gchar *keys[] = {
		"GnomeSoftware::AppTile-css",
		"GnomeSoftware::FeatureTile-css",
		"GnomeSoftware::UpgradeBanner-css",
		NULL };

	/* copy name, summary and description */
	app = gs_app_new (as_app_get_id (item));
	item_global = as_store_get_app_by_id (self->store_global, as_app_get_id (item));
	if (item_global == NULL) {
		const gchar *tmp;
		g_autoptr(AsIcon) ic = NULL;
		g_debug ("no app found for %s, using fallback", as_app_get_id (item));

		/* copy from AsApp, falling back to something sane */
		tmp = as_app_get_name (item, NULL);
		if (tmp == NULL)
			tmp = "Application";
		gs_app_set_name (app, GS_APP_QUALITY_NORMAL, tmp);
		tmp = as_app_get_comment (item, NULL);
		if (tmp == NULL)
			tmp = "Description";
		gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, tmp);
		tmp = as_app_get_description (item, NULL);
		if (tmp == NULL)
			tmp = "A multiline description";
		gs_app_set_description (app, GS_APP_QUALITY_NORMAL, tmp);
		ic = as_icon_new ();
		as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
		as_icon_set_name (ic, "application-x-executable");
		gs_app_add_icon (app, ic);
		item_state = as_app_get_state (item);
	} else {
		GPtrArray *icons;
		g_debug ("found global app for %s", as_app_get_id (item));
		gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
				 as_app_get_name (item_global, NULL));
		gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
				    as_app_get_comment (item_global, NULL));
		gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
					as_app_get_description (item_global, NULL));
		icons = as_app_get_icons (item_global);
		for (guint i = 0; i < icons->len; i++) {
			AsIcon *icon = g_ptr_array_index (icons, i);
			gs_app_add_icon (app, icon);
		}
		item_state = as_app_get_state (item_global);
	}

	/* copy state */
	if (item_state == AS_APP_STATE_UNKNOWN)
		item_state = AS_APP_STATE_AVAILABLE;
	gs_app_set_state (app, item_state);

	/* copy version */
	gs_app_set_version (app, "3.28");

	/* load pixbuf */
	gs_editor_refine_app_pixbuf (app);

	/* copy metadata */
	for (guint i = 0; keys[i] != NULL; i++) {
		g_autoptr(GError) error = NULL;
		const gchar *markup = as_app_get_metadata_item (item, keys[i]);
		if (markup != NULL) {
			g_autofree gchar *css_new = NULL;
			css_new = gs_editor_css_download_resources (self, markup, &error);
			if (css_new == NULL) {
				g_warning ("%s", error->message);
				gs_app_set_metadata (app, keys[i], markup);
			} else {
				gs_app_set_metadata (app, keys[i], css_new);
			}
		} else {
			gs_app_set_metadata (app, keys[i], NULL);
		}
	}
	return app;
}
/**
 * _as_app_composite:
 */
static gboolean
_as_app_composite (AsApp *app, AsApp *donor, GError **error)
{
	AsApp *tmp;
	gint rc;
	_cleanup_free_ gchar *id = NULL;

	/* check this makes sense */
	if (as_app_get_id_kind (app) != as_app_get_id_kind (donor)) {
		g_set_error (error,
			     AS_APP_ERROR,
			     AS_APP_ERROR_INVALID_TYPE,
			     "Cannot composite %s:%s of different id kind",
			     as_app_get_id (app),
			     as_app_get_id (donor));
		return FALSE;
	}

	/* the ID, name with the shortest length wins */
	rc = strlen (as_app_get_id (app)) - strlen (as_app_get_id (donor));
	if (rc == 0) {
		rc = strlen (as_app_get_name (app, "C")) -
		     strlen (as_app_get_name (donor, "C"));
	}
	if (rc > 0) {
		tmp = app;
		app = donor;
		donor = tmp;
	}

	/* set the new composite string */
	id = as_utils_get_string_overlap (as_app_get_id (app), as_app_get_id (donor));
	if (id == NULL || !_as_app_is_id_valid (id)) {
		g_set_error (error,
			     AS_APP_ERROR,
			     AS_APP_ERROR_INVALID_TYPE,
			     "Cannot composite %s:%s as no ID overlap",
			     as_app_get_id (app),
			     as_app_get_id (donor));
		return FALSE;
	}


	/* log */
	if (ASB_IS_APP (app) && g_strcmp0 (as_app_get_id (app), id) != 0) {
		asb_package_log (asb_app_get_package (ASB_APP (app)),
				 ASB_PACKAGE_LOG_LEVEL_INFO,
				 "Renamed %s into %s so it could be "
				 "composited with %s",
				 as_app_get_id (app), id,
				 as_app_get_id (donor));
	}
	if (ASB_IS_APP (donor)) {
		asb_package_log (asb_app_get_package (ASB_APP (donor)),
				 ASB_PACKAGE_LOG_LEVEL_INFO,
				 "Composited %s into %s",
				 as_app_get_id (donor), id);
	}

	/* set the new ID (on both apps?) */
	as_app_set_id (app, id, -1);

	/* add some easily merged properties */
	as_app_subsume_full (app, donor, AS_APP_SUBSUME_FLAG_PARTIAL);
	as_app_add_veto (donor, "absorbed into %s", as_app_get_id (app));
	return TRUE;
}
/**
 * as_app_validate:
 * @app: a #AsApp instance.
 * @flags: the #AsAppValidateFlags to use, e.g. %AS_APP_VALIDATE_FLAG_NONE
 * @error: A #GError or %NULL.
 *
 * Validates data in the instance for style and consitency.
 *
 * Returns: (transfer container) (element-type AsProblem): A list of problems, or %NULL
 *
 * Since: 0.1.4
 **/
GPtrArray *
as_app_validate (AsApp *app, AsAppValidateFlags flags, GError **error)
{
	AsAppProblems problems;
	AsAppValidateHelper helper;
	GError *error_local = NULL;
	GHashTable *urls;
	GList *l;
	GPtrArray *probs = NULL;
	const gchar *description;
	const gchar *id_full;
	const gchar *key;
	const gchar *license;
	const gchar *name;
	const gchar *summary;
	const gchar *tmp;
	const gchar *update_contact;
	gboolean deprectated_failure = FALSE;
	gboolean require_contactdetails = TRUE;
	gboolean require_copyright = FALSE;
	gboolean require_project_license = FALSE;
	gboolean require_translations = FALSE;
	gboolean require_url = TRUE;
	gboolean ret;
	guint length_name_max = 30;
	guint length_name_min = 3;
	guint length_summary_max = 100;
	guint length_summary_min = 8;
	guint number_para_max = 4;
	guint number_para_min = 2;
	guint str_len;
	_cleanup_list_free_ GList *keys = NULL;

	/* relax the requirements a bit */
	if ((flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) {
		length_name_max = 100;
		length_summary_max = 200;
		require_contactdetails = FALSE;
		require_url = FALSE;
		number_para_max = 10;
		number_para_min = 1;
	}

	/* make the requirements more strict */
	if ((flags & AS_APP_VALIDATE_FLAG_STRICT) > 0) {
		deprectated_failure = TRUE;
		require_copyright = TRUE;
		require_translations = TRUE;
		require_project_license = TRUE;
	}

	/* set up networking */
	helper.probs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
	helper.screenshot_urls = g_ptr_array_new_with_free_func (g_free);
	helper.flags = flags;
	helper.previous_para_was_short = FALSE;
	helper.para_chars_before_list = 0;
	helper.number_paragraphs = 0;
	ret = as_app_validate_setup_networking (&helper, error);
	if (!ret)
		goto out;

	/* success, enough */
	probs = helper.probs;

	/* id */
	ret = FALSE;
	id_full = as_app_get_id_full (app);
	switch (as_app_get_id_kind (app)) {
	case AS_ID_KIND_DESKTOP:
		if (g_str_has_suffix (id_full, ".desktop"))
			ret = TRUE;
		break;
	case AS_ID_KIND_FONT:
		if (g_str_has_suffix (id_full, ".ttf"))
			ret = TRUE;
		else if (g_str_has_suffix (id_full, ".otf"))
			ret = TRUE;
		break;
	case AS_ID_KIND_INPUT_METHOD:
		if (g_str_has_suffix (id_full, ".xml"))
			ret = TRUE;
		else if (g_str_has_suffix (id_full, ".db"))
			ret = TRUE;
		break;
	case AS_ID_KIND_CODEC:
		if (g_str_has_prefix (id_full, "gstreamer"))
			ret = TRUE;
		break;
	case AS_ID_KIND_UNKNOWN:
		ai_app_validate_add (probs,
				     AS_PROBLEM_KIND_ATTRIBUTE_INVALID,
				     "<id> has invalid type attribute");

		break;
	case AS_ID_KIND_ADDON:
		/* anything goes */
		ret = TRUE;
	default:
		break;
	}
	if (!ret) {
		ai_app_validate_add (probs,
				     AS_PROBLEM_KIND_MARKUP_INVALID,
				     "<id> does not have correct extension for kind");
	}

	/* metadata_license */
	license = as_app_get_metadata_license (app);
	if (license != NULL) {
		if (g_strcmp0 (license, "CC0-1.0") != 0 &&
		    g_strcmp0 (license, "CC-BY-3.0") != 0 &&
		    g_strcmp0 (license, "CC-BY-SA-3.0") != 0 &&
		    g_strcmp0 (license, "GFDL-1.3") != 0) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     "<metadata_license> is not valid");
		}
	}
	if (license == NULL) {
		switch (as_app_get_source_kind (app)) {
		case AS_APP_SOURCE_KIND_APPDATA:
		case AS_APP_SOURCE_KIND_METAINFO:
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<metadata_license> is not present");
			break;
		default:
			break;
		}
	}

	/* project_license */
	license = as_app_get_project_license (app);
	if (license != NULL) {
		ret = as_app_validate_license (license, &error_local);
		if (!ret) {
			g_prefix_error (&error_local,
					"<project_license> is not valid: ");
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     error_local->message);
			g_clear_error (&error_local);
		}
	}
	if (require_project_license && license == NULL) {
		switch (as_app_get_source_kind (app)) {
		case AS_APP_SOURCE_KIND_APPDATA:
		case AS_APP_SOURCE_KIND_METAINFO:
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<project_license> is not present");
			break;
		default:
			break;
		}
	}

	/* updatecontact */
	update_contact = as_app_get_update_contact (app);
	if (g_strcmp0 (update_contact,
		       "someone_who_cares@upstream_project.org") == 0) {
		ai_app_validate_add (probs,
				     AS_PROBLEM_KIND_TAG_INVALID,
				     "<update_contact> is still set to a dummy value");
	}
	if (update_contact != NULL && strlen (update_contact) < 6) {
		ai_app_validate_add (probs,
				     AS_PROBLEM_KIND_STYLE_INCORRECT,
				     "<update_contact> is too short");
	}
	if (require_contactdetails && update_contact == NULL) {
		switch (as_app_get_source_kind (app)) {
		case AS_APP_SOURCE_KIND_APPDATA:
		case AS_APP_SOURCE_KIND_METAINFO:
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<updatecontact> is not present");
			break;
		default:
			break;
		}
	}

	/* only found for files */
	problems = as_app_get_problems (app);
	if (as_app_get_source_kind (app) == AS_APP_SOURCE_KIND_APPDATA ||
	    as_app_get_source_kind (app) == AS_APP_SOURCE_KIND_METAINFO) {
		if ((problems & AS_APP_PROBLEM_NO_XML_HEADER) > 0) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_MARKUP_INVALID,
					     "<?xml> header not found");
		}
		if (require_copyright &&
		    (problems & AS_APP_PROBLEM_NO_COPYRIGHT_INFO) > 0) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_VALUE_MISSING,
					     "<!-- Copyright [year] [name] --> is not present");
		}
	}

	/* check for things that have to exist */
	if (as_app_get_id_full (app) == NULL) {
		ai_app_validate_add (probs,
				     AS_PROBLEM_KIND_TAG_MISSING,
				     "<id> is not present");
	}

	/* url */
	urls = as_app_get_urls (app);
	keys = g_hash_table_get_keys (urls);
	for (l = keys; l != NULL; l = l->next) {
		key = l->data;
		if (g_strcmp0 (key, "unknown") == 0) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     "<url> type invalid");
		}
		tmp = g_hash_table_lookup (urls, key);
		if (!g_str_has_prefix (tmp, "http://") &&
		    !g_str_has_prefix (tmp, "https://")) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TAG_INVALID,
					     "<url> does not start with 'http://'");
		}
	}

	/* screenshots */
	as_app_validate_screenshots (app, &helper);

	/* releases */
	ret = as_app_validate_releases (app, &helper, error);
	if (!ret)
		goto out;

	/* name */
	name = as_app_get_name (app, "C");
	if (name != NULL) {
		str_len = strlen (name);
		if (str_len < length_name_min) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<name> is too short");
		}
		if (str_len > length_name_max) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<name> is too long");
		}
		if (ai_app_validate_fullstop_ending (name)) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<name> cannot end in '.'");
		}
		if (as_app_validate_has_hyperlink (name)) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<name> cannot contain a hyperlink");
		}
	}

	/* comment */
	summary = as_app_get_comment (app, "C");
	if (summary != NULL) {
		str_len = strlen (summary);
		if (str_len < length_summary_min) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<summary> is too short");
		}
		if (str_len > length_summary_max) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<summary> is too long");
		}
		if (ai_app_validate_fullstop_ending (summary)) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<summary> cannot end in '.'");
		}
		if (as_app_validate_has_hyperlink (summary)) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_STYLE_INCORRECT,
					     "<summary> cannot contain a hyperlink");
		}
	}
	if (summary != NULL && name != NULL &&
	    strlen (summary) < strlen (name)) {
		ai_app_validate_add (probs,
				     AS_PROBLEM_KIND_STYLE_INCORRECT,
				     "<summary> is shorter than <name>");
	}
	description = as_app_get_description (app, "C");
	if (description != NULL) {
		ret = as_app_validate_description (description,
						   &helper,
						   number_para_min,
						   number_para_max,
						   &error_local);
		if (!ret) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_MARKUP_INVALID,
					     error_local->message);
			g_error_free (error_local);
		}
	}
	if (require_translations) {
		if (name != NULL &&
		    as_app_get_name_size (app) == 1 &&
		    (problems & AS_APP_PROBLEM_INTLTOOL_NAME) == 0) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TRANSLATIONS_REQUIRED,
					     "<name> has no translations");
		}
		if (summary != NULL &&
		    as_app_get_comment_size (app) == 1 &&
		    (problems & AS_APP_PROBLEM_INTLTOOL_SUMMARY) == 0) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TRANSLATIONS_REQUIRED,
					     "<summary> has no translations");
		}
		if (description != NULL &&
		    as_app_get_description_size (app) == 1 &&
		    (problems & AS_APP_PROBLEM_INTLTOOL_DESCRIPTION) == 0) {
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TRANSLATIONS_REQUIRED,
					     "<description> has no translations");
		}
	}

	/* using deprecated names */
	if (deprectated_failure && (problems & AS_APP_PROBLEM_DEPRECATED_LICENCE) > 0) {
		ai_app_validate_add (probs,
				     AS_PROBLEM_KIND_ATTRIBUTE_INVALID,
				     "<licence> is deprecated, use "
				     "<metadata_license> instead");
	}
	if ((problems & AS_APP_PROBLEM_MULTIPLE_ENTRIES) > 0) {
		ai_app_validate_add (probs,
				     AS_PROBLEM_KIND_MARKUP_INVALID,
				     "<application> used more than once");
	}

	/* require homepage */
	if (require_url && as_app_get_url_item (app, AS_URL_KIND_HOMEPAGE) == NULL) {
		switch (as_app_get_source_kind (app)) {
		case AS_APP_SOURCE_KIND_APPDATA:
		case AS_APP_SOURCE_KIND_METAINFO:
			ai_app_validate_add (probs,
					     AS_PROBLEM_KIND_TAG_MISSING,
					     "<url> is not present");
			break;
		default:
			break;
		}
	}
out:
	g_ptr_array_unref (helper.screenshot_urls);
	if (helper.session != NULL)
		g_object_unref (helper.session);
	return probs;
}