1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2014-2018 Richard Hughes <richard@hughsie.com>
4  *
5  * SPDX-License-Identifier: LGPL-2.1+
6  */
7 
8 /**
9  * SECTION:as-app
10  * @short_description: An object for an AppStream application or add-on
11  * @include: appstream-glib.h
12  * @stability: Stable
13  *
14  * This object represents the base object of all AppStream, the application.
15  * Although called #AsApp, this object also represents components like fonts,
16  * codecs and input methods.
17  *
18  * See also: #AsScreenshot, #AsRelease
19  */
20 
21 #include "config.h"
22 
23 #include <string.h>
24 
25 #include "as-app-private.h"
26 #include "as-bundle-private.h"
27 #include "as-content-rating-private.h"
28 #include "as-agreement-private.h"
29 #include "as-enums.h"
30 #include "as-icon-private.h"
31 #include "as-node-private.h"
32 #include "as-launchable-private.h"
33 #include "as-provide-private.h"
34 #include "as-release-private.h"
35 #include "as-ref-string.h"
36 #include "as-require-private.h"
37 #include "as-review-private.h"
38 #include "as-screenshot-private.h"
39 #include "as-stemmer.h"
40 #include "as-tag.h"
41 #include "as-translation-private.h"
42 #include "as-suggest-private.h"
43 #include "as-utils-private.h"
44 #include "as-yaml.h"
45 
46 #ifndef _WIN32
47 #include <fnmatch.h>
48 #endif
49 
50 typedef struct
51 {
52 	AsAppProblems	 problems;
53 	AsIconKind	 icon_kind;
54 	AsAppKind	 kind;
55 	AsStemmer	*stemmer;
56 	GHashTable	*comments;			/* of AsRefString:AsRefString */
57 	GHashTable	*developer_names;		/* of AsRefString:AsRefString */
58 	GHashTable	*descriptions;			/* of AsRefString:AsRefString */
59 	GHashTable	*keywords;			/* of AsRefString:GPtrArray */
60 	GHashTable	*languages;			/* of AsRefString:AsRefString */
61 	GHashTable	*metadata;			/* of AsRefString:AsRefString */
62 	GHashTable	*names;				/* of AsRefString:AsRefString */
63 	GHashTable	*urls;				/* of AsRefString:AsRefString */
64 	GPtrArray	*addons;			/* of AsApp */
65 	GPtrArray	*categories;			/* of AsRefString */
66 	GPtrArray	*compulsory_for_desktops;	/* of AsRefString */
67 	GPtrArray	*extends;			/* of AsRefString */
68 	GPtrArray	*kudos;				/* of AsRefString */
69 	GPtrArray	*permissions;			/* of AsRefString */
70 	GPtrArray	*mimetypes;			/* of AsRefString */
71 	GPtrArray	*pkgnames;			/* of AsRefString */
72 	GPtrArray	*architectures;			/* of AsRefString */
73 	GPtrArray	*formats;			/* of AsFormat */
74 	GPtrArray	*releases;			/* of AsRelease */
75 	GPtrArray	*provides;			/* of AsProvide */
76 	GPtrArray	*launchables;			/* of AsLaunchable */
77 	GPtrArray	*screenshots;			/* of AsScreenshot */
78 	GPtrArray	*reviews;			/* of AsReview */
79 	GPtrArray	*content_ratings;		/* of AsContentRating */
80 	GPtrArray	*agreements;			/* of AsAgreement */
81 	GPtrArray	*icons;				/* of AsIcon */
82 	GPtrArray	*bundles;			/* of AsBundle */
83 	GPtrArray	*translations;			/* of AsTranslation */
84 	GPtrArray	*suggests;			/* of AsSuggest */
85 	GPtrArray	*requires;			/* of AsRequire */
86 	GPtrArray	*vetos;				/* of AsRefString */
87 	AsAppScope	 scope;
88 	AsAppMergeKind	 merge_kind;
89 	AsAppState	 state;
90 	guint32		 trust_flags;
91 	AsAppQuirk	 quirk;
92 	guint16		 search_match;
93 	AsRefString	*icon_path;
94 	AsRefString	*id_filename;
95 	AsRefString	*id;
96 	AsRefString	*origin;
97 	AsRefString	*project_group;
98 	AsRefString	*project_license;
99 	AsRefString	*metadata_license;
100 	AsRefString	*source_pkgname;
101 	AsRefString	*update_contact;
102 	gchar		*unique_id;
103 	gboolean	 unique_id_valid;
104 	GMutex		 unique_id_mutex;
105 	AsRefString	*branch;
106 	gint		 priority;
107 	gsize		 token_cache_valid;
108 	GHashTable	*token_cache;			/* of AsRefString:AsAppTokenType* */
109 	GHashTable	*search_blacklist;		/* of AsRefString:1 */
110 } AsAppPrivate;
111 
112 G_DEFINE_TYPE_WITH_PRIVATE (AsApp, as_app, G_TYPE_OBJECT)
113 
114 #define GET_PRIVATE(o) (as_app_get_instance_private (o))
115 
116 typedef guint16	AsAppTokenType;	/* big enough for both bitshifts */
117 
118 /**
119  * as_app_error_quark:
120  *
121  * Return value: An error quark.
122  *
123  * Since: 0.1.2
124  **/
125 G_DEFINE_QUARK (as-app-error-quark, as_app_error)
126 
127 /**
128  * as_app_kind_to_string:
129  * @kind: the #AsAppKind.
130  *
131  * Converts the enumerated value to an text representation.
132  *
133  * Returns: string version of @kind
134  *
135  * Since: 0.5.10
136  **/
137 const gchar *
as_app_kind_to_string(AsAppKind kind)138 as_app_kind_to_string (AsAppKind kind)
139 {
140 	if (kind == AS_APP_KIND_DESKTOP)
141 		return "desktop";
142 	if (kind == AS_APP_KIND_CODEC)
143 		return "codec";
144 	if (kind == AS_APP_KIND_FONT)
145 		return "font";
146 	if (kind == AS_APP_KIND_INPUT_METHOD)
147 		return "inputmethod";
148 	if (kind == AS_APP_KIND_WEB_APP)
149 		return "webapp";
150 	if (kind == AS_APP_KIND_SOURCE)
151 		return "source";
152 	if (kind == AS_APP_KIND_ADDON)
153 		return "addon";
154 	if (kind == AS_APP_KIND_FIRMWARE)
155 		return "firmware";
156 	if (kind == AS_APP_KIND_RUNTIME)
157 		return "runtime";
158 	if (kind == AS_APP_KIND_GENERIC)
159 		return "generic";
160 	if (kind == AS_APP_KIND_OS_UPDATE)
161 		return "os-update";
162 	if (kind == AS_APP_KIND_OS_UPGRADE)
163 		return "os-upgrade";
164 	if (kind == AS_APP_KIND_SHELL_EXTENSION)
165 		return "shell-extension";
166 	if (kind == AS_APP_KIND_LOCALIZATION)
167 		return "localization";
168 	if (kind == AS_APP_KIND_CONSOLE)
169 		return "console-application";
170 	if (kind == AS_APP_KIND_DRIVER)
171 		return "driver";
172 	return "unknown";
173 }
174 
175 /**
176  * as_app_kind_from_string:
177  * @kind: the string.
178  *
179  * Converts the text representation to an enumerated value.
180  *
181  * Returns: a #AsAppKind or %AS_APP_KIND_UNKNOWN for unknown
182  *
183  * Since: 0.5.10
184  **/
185 AsAppKind
as_app_kind_from_string(const gchar * kind)186 as_app_kind_from_string (const gchar *kind)
187 {
188 	if (g_strcmp0 (kind, "desktop-application") == 0)
189 		return AS_APP_KIND_DESKTOP;
190 	if (g_strcmp0 (kind, "codec") == 0)
191 		return AS_APP_KIND_CODEC;
192 	if (g_strcmp0 (kind, "font") == 0)
193 		return AS_APP_KIND_FONT;
194 	if (g_strcmp0 (kind, "inputmethod") == 0)
195 		return AS_APP_KIND_INPUT_METHOD;
196 	if (g_strcmp0 (kind, "web-application") == 0)
197 		return AS_APP_KIND_WEB_APP;
198 	if (g_strcmp0 (kind, "source") == 0)
199 		return AS_APP_KIND_SOURCE;
200 	if (g_strcmp0 (kind, "addon") == 0)
201 		return AS_APP_KIND_ADDON;
202 	if (g_strcmp0 (kind, "firmware") == 0)
203 		return AS_APP_KIND_FIRMWARE;
204 	if (g_strcmp0 (kind, "runtime") == 0)
205 		return AS_APP_KIND_RUNTIME;
206 	if (g_strcmp0 (kind, "generic") == 0)
207 		return AS_APP_KIND_GENERIC;
208 	if (g_strcmp0 (kind, "os-update") == 0)
209 		return AS_APP_KIND_OS_UPDATE;
210 	if (g_strcmp0 (kind, "os-upgrade") == 0)
211 		return AS_APP_KIND_OS_UPGRADE;
212 	if (g_strcmp0 (kind, "shell-extension") == 0)
213 		return AS_APP_KIND_SHELL_EXTENSION;
214 	if (g_strcmp0 (kind, "localization") == 0)
215 		return AS_APP_KIND_LOCALIZATION;
216 	if (g_strcmp0 (kind, "console-application") == 0)
217 		return AS_APP_KIND_CONSOLE;
218 	if (g_strcmp0 (kind, "driver") == 0)
219 		return AS_APP_KIND_DRIVER;
220 	if (g_strcmp0 (kind, "icon-theme") == 0)
221 		return AS_APP_KIND_ICON_THEME;
222 
223 	/* legacy */
224 	if (g_strcmp0 (kind, "desktop") == 0)
225 		return AS_APP_KIND_DESKTOP;
226 	if (g_strcmp0 (kind, "desktop-app") == 0)
227 		return AS_APP_KIND_DESKTOP;
228 	if (g_strcmp0 (kind, "webapp") == 0)
229 		return AS_APP_KIND_WEB_APP;
230 
231 	return AS_APP_KIND_UNKNOWN;
232 }
233 
234 /**
235  * as_app_source_kind_from_string:
236  * @source_kind: a source kind string
237  *
238  * Converts the text representation to an enumerated value.
239  *
240  * Return value: A #AsFormatKind, e.g. %AS_FORMAT_KIND_APPSTREAM.
241  *
242  * Since: 0.2.2
243  **/
244 AsFormatKind
as_app_source_kind_from_string(const gchar * source_kind)245 as_app_source_kind_from_string (const gchar *source_kind)
246 {
247 	return as_format_kind_from_string (source_kind);
248 }
249 
250 /**
251  * as_app_source_kind_to_string:
252  * @source_kind: the #AsFormatKind.
253  *
254  * Converts the enumerated value to an text representation.
255  *
256  * Returns: string version of @source_kind, or %NULL for unknown
257  *
258  * Since: 0.2.2
259  **/
260 const gchar *
as_app_source_kind_to_string(AsFormatKind source_kind)261 as_app_source_kind_to_string (AsFormatKind source_kind)
262 {
263 	return as_format_kind_to_string (source_kind);
264 }
265 
266 /**
267  * as_app_state_to_string:
268  * @state: the #AsAppState.
269  *
270  * Converts the enumerated value to an text representation.
271  *
272  * Returns: string version of @state, or %NULL for unknown
273  *
274  * Since: 0.2.2
275  **/
276 const gchar *
as_app_state_to_string(AsAppState state)277 as_app_state_to_string (AsAppState state)
278 {
279 	if (state == AS_APP_STATE_UNKNOWN)
280 		return "unknown";
281 	if (state == AS_APP_STATE_INSTALLED)
282 		return "installed";
283 	if (state == AS_APP_STATE_AVAILABLE)
284 		return "available";
285 	if (state == AS_APP_STATE_PURCHASABLE)
286 		return "purchasable";
287 	if (state == AS_APP_STATE_PURCHASING)
288 		return "purchasing";
289 	if (state == AS_APP_STATE_AVAILABLE_LOCAL)
290 		return "local";
291 	if (state == AS_APP_STATE_QUEUED_FOR_INSTALL)
292 		return "queued";
293 	if (state == AS_APP_STATE_INSTALLING)
294 		return "installing";
295 	if (state == AS_APP_STATE_REMOVING)
296 		return "removing";
297 	if (state == AS_APP_STATE_UPDATABLE)
298 		return "updatable";
299 	if (state == AS_APP_STATE_UPDATABLE_LIVE)
300 		return "updatable-live";
301 	if (state == AS_APP_STATE_UNAVAILABLE)
302 		return "unavailable";
303 	return NULL;
304 }
305 
306 /**
307  * as_app_scope_from_string:
308  * @scope: a source kind string
309  *
310  * Converts the text representation to an enumerated value.
311  *
312  * Return value: A #AsAppScope, e.g. %AS_APP_SCOPE_SYSTEM.
313  *
314  * Since: 0.6.1
315  **/
316 AsAppScope
as_app_scope_from_string(const gchar * scope)317 as_app_scope_from_string (const gchar *scope)
318 {
319 	if (g_strcmp0 (scope, "user") == 0)
320 		return AS_APP_SCOPE_USER;
321 	if (g_strcmp0 (scope, "system") == 0)
322 		return AS_APP_SCOPE_SYSTEM;
323 	return AS_APP_SCOPE_UNKNOWN;
324 }
325 
326 /**
327  * as_app_scope_to_string:
328  * @scope: the #AsAppScope, e.g. %AS_APP_SCOPE_SYSTEM
329  *
330  * Converts the enumerated value to an text representation.
331  *
332  * Returns: string version of @scope, or %NULL for unknown
333  *
334  * Since: 0.6.1
335  **/
336 const gchar *
as_app_scope_to_string(AsAppScope scope)337 as_app_scope_to_string (AsAppScope scope)
338 {
339 	if (scope == AS_APP_SCOPE_USER)
340 		return "user";
341 	if (scope == AS_APP_SCOPE_SYSTEM)
342 		return "system";
343 	return NULL;
344 }
345 
346 /**
347  * as_app_merge_kind_from_string:
348  * @merge_kind: a source kind string
349  *
350  * Converts the text representation to an enumerated value.
351  *
352  * Return value: A #AsAppMergeKind, e.g. %AS_APP_MERGE_KIND_REPLACE.
353  *
354  * Since: 0.6.1
355  **/
356 AsAppMergeKind
as_app_merge_kind_from_string(const gchar * merge_kind)357 as_app_merge_kind_from_string (const gchar *merge_kind)
358 {
359 	if (g_strcmp0 (merge_kind, "none") == 0)
360 		return AS_APP_MERGE_KIND_NONE;
361 	if (g_strcmp0 (merge_kind, "replace") == 0)
362 		return AS_APP_MERGE_KIND_REPLACE;
363 	if (g_strcmp0 (merge_kind, "append") == 0)
364 		return AS_APP_MERGE_KIND_APPEND;
365 	return AS_APP_MERGE_KIND_NONE;
366 }
367 
368 /**
369  * as_app_merge_kind_to_string:
370  * @merge_kind: the #AsAppMergeKind, e.g. %AS_APP_MERGE_KIND_REPLACE
371  *
372  * Converts the enumerated value to an text representation.
373  *
374  * Returns: string version of @merge_kind, or %NULL for unknown
375  *
376  * Since: 0.6.1
377  **/
378 const gchar *
as_app_merge_kind_to_string(AsAppMergeKind merge_kind)379 as_app_merge_kind_to_string (AsAppMergeKind merge_kind)
380 {
381 	if (merge_kind == AS_APP_MERGE_KIND_NONE)
382 		return "none";
383 	if (merge_kind == AS_APP_MERGE_KIND_REPLACE)
384 		return "replace";
385 	if (merge_kind == AS_APP_MERGE_KIND_APPEND)
386 		return "append";
387 	return NULL;
388 }
389 
390 /**
391  * as_app_guess_source_kind:
392  * @filename: a file name
393  *
394  * Guesses the source kind based from the filename.
395  *
396  * Return value: A #AsFormatKind, e.g. %AS_FORMAT_KIND_APPSTREAM.
397  *
398  * Since: 0.1.8
399  **/
400 AsFormatKind
as_app_guess_source_kind(const gchar * filename)401 as_app_guess_source_kind (const gchar *filename)
402 {
403 	return as_format_guess_kind (filename);
404 }
405 
406 static void
as_app_finalize(GObject * object)407 as_app_finalize (GObject *object)
408 {
409 	AsApp *app = AS_APP (object);
410 	AsAppPrivate *priv = GET_PRIVATE (app);
411 
412 	if (priv->stemmer != NULL)
413 		g_object_unref (priv->stemmer);
414 	if (priv->search_blacklist != NULL)
415 		g_hash_table_unref (priv->search_blacklist);
416 
417 	if (priv->icon_path != NULL)
418 		as_ref_string_unref (priv->icon_path);
419 	if (priv->id_filename != NULL)
420 		as_ref_string_unref (priv->id_filename);
421 	if (priv->id != NULL)
422 		as_ref_string_unref (priv->id);
423 	if (priv->project_group != NULL)
424 		as_ref_string_unref (priv->project_group);
425 	if (priv->project_license != NULL)
426 		as_ref_string_unref (priv->project_license);
427 	if (priv->metadata_license != NULL)
428 		as_ref_string_unref (priv->metadata_license);
429 	if (priv->origin != NULL)
430 		as_ref_string_unref (priv->origin);
431 	if (priv->source_pkgname != NULL)
432 		as_ref_string_unref (priv->source_pkgname);
433 	if (priv->update_contact != NULL)
434 		as_ref_string_unref (priv->update_contact);
435 	g_free (priv->unique_id);
436 	g_mutex_clear (&priv->unique_id_mutex);
437 	if (priv->branch != NULL)
438 		as_ref_string_unref (priv->branch);
439 	g_hash_table_unref (priv->comments);
440 	g_hash_table_unref (priv->developer_names);
441 	g_hash_table_unref (priv->descriptions);
442 	g_hash_table_unref (priv->keywords);
443 	g_hash_table_unref (priv->languages);
444 	g_hash_table_unref (priv->metadata);
445 	g_hash_table_unref (priv->names);
446 	g_hash_table_unref (priv->urls);
447 	g_hash_table_unref (priv->token_cache);
448 	g_ptr_array_unref (priv->addons);
449 	g_ptr_array_unref (priv->categories);
450 	g_ptr_array_unref (priv->compulsory_for_desktops);
451 	g_ptr_array_unref (priv->content_ratings);
452 	g_ptr_array_unref (priv->agreements);
453 	g_ptr_array_unref (priv->extends);
454 	g_ptr_array_unref (priv->kudos);
455 	g_ptr_array_unref (priv->permissions);
456 	g_ptr_array_unref (priv->formats);
457 	g_ptr_array_unref (priv->mimetypes);
458 	g_ptr_array_unref (priv->pkgnames);
459 	g_ptr_array_unref (priv->architectures);
460 	g_ptr_array_unref (priv->releases);
461 	g_ptr_array_unref (priv->provides);
462 	g_ptr_array_unref (priv->launchables);
463 	g_ptr_array_unref (priv->screenshots);
464 	g_ptr_array_unref (priv->reviews);
465 	g_ptr_array_unref (priv->icons);
466 	g_ptr_array_unref (priv->bundles);
467 	g_ptr_array_unref (priv->translations);
468 	g_ptr_array_unref (priv->suggests);
469 	g_ptr_array_unref (priv->requires);
470 	g_ptr_array_unref (priv->vetos);
471 
472 	G_OBJECT_CLASS (as_app_parent_class)->finalize (object);
473 }
474 
475 static void
as_app_init(AsApp * app)476 as_app_init (AsApp *app)
477 {
478 	AsAppPrivate *priv = GET_PRIVATE (app);
479 	g_mutex_init (&priv->unique_id_mutex);
480 	priv->categories = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
481 	priv->compulsory_for_desktops = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
482 	priv->content_ratings = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
483 	priv->agreements = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
484 	priv->extends = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
485 	priv->keywords = g_hash_table_new_full (g_str_hash, g_str_equal,
486 						(GDestroyNotify) as_ref_string_unref,
487 						(GDestroyNotify) g_ptr_array_unref);
488 	priv->kudos = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
489 	priv->permissions = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
490 	priv->formats = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
491 	priv->mimetypes = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
492 	priv->pkgnames = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
493 	priv->architectures = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
494 	priv->addons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
495 	priv->releases = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
496 	priv->provides = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
497 	priv->launchables = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
498 	priv->screenshots = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
499 	priv->reviews = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
500 	priv->icons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
501 	priv->bundles = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
502 	priv->translations = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
503 	priv->suggests = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
504 	priv->requires = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
505 	priv->vetos = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
506 
507 	priv->comments = g_hash_table_new_full (g_str_hash, g_str_equal,
508 						(GDestroyNotify) as_ref_string_unref,
509 						(GDestroyNotify) as_ref_string_unref);
510 	priv->developer_names = g_hash_table_new_full (g_str_hash, g_str_equal,
511 						       (GDestroyNotify) as_ref_string_unref,
512 						       (GDestroyNotify) as_ref_string_unref);
513 	priv->descriptions = g_hash_table_new_full (g_str_hash, g_str_equal,
514 						    (GDestroyNotify) as_ref_string_unref,
515 						    (GDestroyNotify) as_ref_string_unref);
516 	priv->languages = g_hash_table_new_full (g_str_hash, g_str_equal,
517 						 (GDestroyNotify) as_ref_string_unref, NULL);
518 	priv->metadata = g_hash_table_new_full (g_str_hash,
519 						g_str_equal,
520 						(GDestroyNotify) as_ref_string_unref,
521 						(GDestroyNotify) as_ref_string_unref);
522 	priv->names = g_hash_table_new_full (g_str_hash, g_str_equal,
523 					     (GDestroyNotify) as_ref_string_unref,
524 					     (GDestroyNotify) as_ref_string_unref);
525 	priv->urls = g_hash_table_new_full (g_str_hash, g_str_equal,
526 					    (GDestroyNotify) as_ref_string_unref,
527 					    (GDestroyNotify) as_ref_string_unref);
528 	priv->token_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
529 						   (GDestroyNotify) as_ref_string_unref,
530 						   g_free);
531 	priv->search_match = AS_APP_SEARCH_MATCH_LAST;
532 }
533 
534 static void
as_app_class_init(AsAppClass * klass)535 as_app_class_init (AsAppClass *klass)
536 {
537 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
538 	object_class->finalize = as_app_finalize;
539 }
540 
541 /******************************************************************************/
542 
543 static gboolean
as_app_equal_int(guint v1,guint v2)544 as_app_equal_int (guint v1, guint v2)
545 {
546 	if (v1 == 0 || v2 == 0)
547 		return TRUE;
548 	return v1 == v2;
549 }
550 
551 static gboolean
as_app_equal_str(const gchar * v1,const gchar * v2)552 as_app_equal_str (const gchar *v1, const gchar *v2)
553 {
554 	if (v1 == NULL || v2 == NULL)
555 		return TRUE;
556 	return g_strcmp0 (v1, v2) == 0;
557 }
558 
559 static gboolean
as_app_equal_array_str(GPtrArray * v1,GPtrArray * v2)560 as_app_equal_array_str (GPtrArray *v1, GPtrArray *v2)
561 {
562 	if (v1->len == 0 || v2->len == 0)
563 		return TRUE;
564 	return g_strcmp0 (g_ptr_array_index (v1, 0),
565 			  g_ptr_array_index (v2, 0)) == 0;
566 }
567 
568 AsBundleKind
as_app_get_bundle_kind(AsApp * app)569 as_app_get_bundle_kind (AsApp *app)
570 {
571 	AsAppPrivate *priv = GET_PRIVATE (app);
572 
573 	/* prefer bundle */
574 	if (priv->bundles->len > 0) {
575 		AsBundle *bundle = g_ptr_array_index (priv->bundles, 0);
576 		if (as_bundle_get_kind (bundle) != AS_BUNDLE_KIND_UNKNOWN)
577 			return as_bundle_get_kind (bundle);
578 	}
579 
580 	/* fallback to packages */
581 	if (priv->pkgnames->len > 0)
582 		return AS_BUNDLE_KIND_PACKAGE;
583 
584 	/* nothing */
585 	return AS_BUNDLE_KIND_UNKNOWN;
586 }
587 
588 /**
589  * as_app_equal:
590  * @app1: a #AsApp instance.
591  * @app2: another #AsApp instance.
592  *
593  * Compare one application with another for equality using the following
594  * properties:
595  *
596  *  1. scope, e.g. `system` or `user`
597  *  2. bundle kind, e.g. `package` or `flatpak`
598  *  3. origin, e.g. `fedora` or `gnome-apps-nightly`
599  *  4. kind, e.g. `app` or `runtime`
600  *  5. AppStream ID, e.g. `gimp.desktop`
601  *  6. branch, e.g. `stable` or `master`
602  *
603  * Returns: %TRUE if the applications are equal
604  *
605  * Since: 0.6.1
606  **/
607 gboolean
as_app_equal(AsApp * app1,AsApp * app2)608 as_app_equal (AsApp *app1, AsApp *app2)
609 {
610 	AsAppPrivate *priv1 = GET_PRIVATE (app1);
611 	AsAppPrivate *priv2 = GET_PRIVATE (app2);
612 
613 	g_return_val_if_fail (AS_IS_APP (app1), FALSE);
614 	g_return_val_if_fail (AS_IS_APP (app2), FALSE);
615 
616 	if (app1 == app2)
617 		return TRUE;
618 	if (!as_app_equal_int (priv1->scope, priv2->scope))
619 		return FALSE;
620 	if (!as_app_equal_int (priv1->kind, priv2->kind))
621 		return FALSE;
622 	if (!as_app_equal_str (priv1->id_filename, priv2->id_filename))
623 		return FALSE;
624 	if (!as_app_equal_str (priv1->origin, priv2->origin))
625 		return FALSE;
626 	if (!as_app_equal_str (priv1->branch, priv2->branch))
627 		return FALSE;
628 	if (!as_app_equal_array_str (priv1->architectures,
629 				     priv2->architectures))
630 		return FALSE;
631 	if (!as_app_equal_int (as_app_get_bundle_kind (app1),
632 			       as_app_get_bundle_kind (app2)))
633 		return FALSE;
634 	return TRUE;
635 }
636 
637 /**
638  * as_app_get_unique_id:
639  * @app: a #AsApp instance.
640  *
641  * Gets the unique ID value to represent the component.
642  *
643  * Returns: the unique ID, e.g. `system/package/fedora/desktop/gimp.desktop/master`
644  *
645  * Since: 0.6.1
646  **/
647 const gchar *
as_app_get_unique_id(AsApp * app)648 as_app_get_unique_id (AsApp *app)
649 {
650 	AsAppPrivate *priv = GET_PRIVATE (app);
651 	g_autoptr(GMutexLocker) locker = NULL;
652 
653 	g_return_val_if_fail (AS_IS_APP (app), NULL);
654 
655 	locker = g_mutex_locker_new (&priv->unique_id_mutex);
656 	if (priv->unique_id == NULL || !priv->unique_id_valid) {
657 		g_free (priv->unique_id);
658 		if (as_app_has_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX)) {
659 			priv->unique_id = as_utils_unique_id_build (AS_APP_SCOPE_UNKNOWN,
660 								    AS_BUNDLE_KIND_UNKNOWN,
661 								    NULL,
662 								    priv->kind,
663 								    as_app_get_id_no_prefix (app),
664 								    NULL);
665 		} else {
666 			priv->unique_id = as_utils_unique_id_build (priv->scope,
667 								    as_app_get_bundle_kind (app),
668 								    priv->origin,
669 								    priv->kind,
670 								    as_app_get_id_no_prefix (app),
671 								    priv->branch);
672 		}
673 		priv->unique_id_valid = TRUE;
674 	}
675 	return priv->unique_id;
676 }
677 
678 /**
679  * as_app_get_id:
680  * @app: a #AsApp instance.
681  *
682  * Gets the full ID value.
683  *
684  * Returns: the ID, e.g. "org.gnome.Software.desktop"
685  *
686  * Since: 0.1.0
687  **/
688 const gchar *
as_app_get_id(AsApp * app)689 as_app_get_id (AsApp *app)
690 {
691 	AsAppPrivate *priv = GET_PRIVATE (app);
692 	return priv->id;
693 }
694 
695 /**
696  * as_app_get_id_no_prefix:
697  * @app: a #AsApp instance.
698  *
699  * Gets the full ID value, stripping any prefix.
700  *
701  * Returns: the ID, e.g. "org.gnome.Software.desktop"
702  *
703  * Since: 0.5.12
704  **/
705 const gchar *
as_app_get_id_no_prefix(AsApp * app)706 as_app_get_id_no_prefix (AsApp *app)
707 {
708 	AsAppPrivate *priv = GET_PRIVATE (app);
709 	gchar *tmp;
710 	if (priv->id == NULL)
711 		return NULL;
712 	tmp = g_strrstr (priv->id, ":");
713 	if (tmp != NULL)
714 		return tmp + 1;
715 	return priv->id;
716 }
717 
718 /**
719  * as_app_get_id_filename:
720  * @app: a #AsApp instance.
721  *
722  * Returns a filename which represents the applications ID, e.g. "gimp.desktop"
723  * becomes "gimp" and is used for cache directories.
724  *
725  * Returns: A utf8 filename
726  *
727  * Since: 0.3.0
728  **/
729 const gchar *
as_app_get_id_filename(AsApp * app)730 as_app_get_id_filename (AsApp *app)
731 {
732 	AsAppPrivate *priv = GET_PRIVATE (app);
733 	return priv->id_filename;
734 }
735 
736 /**
737  * as_app_get_categories:
738  * @app: a #AsApp instance.
739  *
740  * Get the application categories.
741  *
742  * Returns: (element-type utf8) (transfer none): an array
743  *
744  * Since: 0.1.0
745  **/
746 GPtrArray *
as_app_get_categories(AsApp * app)747 as_app_get_categories (AsApp *app)
748 {
749 	AsAppPrivate *priv = GET_PRIVATE (app);
750 	return priv->categories;
751 }
752 
753 /**
754  * as_app_has_category:
755  * @app: a #AsApp instance.
756  * @category: a category string, e.g. "DesktopSettings"
757  *
758  * Searches the category list for a specific item.
759  *
760  * Returns: %TRUE if the application has got the specified category
761  *
762  * Since: 0.1.5
763  */
764 gboolean
as_app_has_category(AsApp * app,const gchar * category)765 as_app_has_category (AsApp *app, const gchar *category)
766 {
767 	AsAppPrivate *priv = GET_PRIVATE (app);
768 	const gchar *tmp;
769 	guint i;
770 
771 	for (i = 0; i < priv->categories->len; i++) {
772 		tmp = g_ptr_array_index (priv->categories, i);
773 		if (g_strcmp0 (tmp, category) == 0)
774 			return TRUE;
775 	}
776 	return FALSE;
777 }
778 
779 /**
780  * as_app_has_kudo:
781  * @app: a #AsApp instance.
782  * @kudo: a kudo string, e.g. "SearchProvider"
783  *
784  * Searches the kudo list for a specific item.
785  *
786  * Returns: %TRUE if the application has got the specified kudo
787  *
788  * Since: 0.2.2
789  */
790 gboolean
as_app_has_kudo(AsApp * app,const gchar * kudo)791 as_app_has_kudo (AsApp *app, const gchar *kudo)
792 {
793 	AsAppPrivate *priv = GET_PRIVATE (app);
794 	const gchar *tmp;
795 	guint i;
796 
797 	for (i = 0; i < priv->kudos->len; i++) {
798 		tmp = g_ptr_array_index (priv->kudos, i);
799 		if (g_strcmp0 (tmp, kudo) == 0)
800 			return TRUE;
801 	}
802 	return FALSE;
803 }
804 
805 /**
806  * as_app_has_kudo_kind:
807  * @app: a #AsApp instance.
808  * @kudo: a #AsKudoKind, e.g. %AS_KUDO_KIND_SEARCH_PROVIDER
809  *
810  * Searches the kudo list for a specific item.
811  *
812  * Returns: %TRUE if the application has got the specified kudo
813  *
814  * Since: 0.2.2
815  */
816 gboolean
as_app_has_kudo_kind(AsApp * app,AsKudoKind kudo)817 as_app_has_kudo_kind (AsApp *app, AsKudoKind kudo)
818 {
819 	AsAppPrivate *priv = GET_PRIVATE (app);
820 	const gchar *tmp;
821 	guint i;
822 
823 	for (i = 0; i < priv->kudos->len; i++) {
824 		tmp = g_ptr_array_index (priv->kudos, i);
825 		if (as_kudo_kind_from_string (tmp) == kudo)
826 			return TRUE;
827 	}
828 	return FALSE;
829 }
830 
831 /**
832  * as_app_get_compulsory_for_desktops:
833  * @app: a #AsApp instance.
834  *
835  * Returns the desktops where this application is compulsory.
836  *
837  * Returns: (element-type utf8) (transfer none): an array
838  *
839  * Since: 0.1.0
840  **/
841 GPtrArray *
as_app_get_compulsory_for_desktops(AsApp * app)842 as_app_get_compulsory_for_desktops (AsApp *app)
843 {
844 	AsAppPrivate *priv = GET_PRIVATE (app);
845 	return priv->compulsory_for_desktops;
846 }
847 
848 /**
849  * as_app_has_compulsory_for_desktop:
850  * @app: a #AsApp instance.
851  * @desktop: a desktop string, e.g. "GNOME"
852  *
853  * Searches the compulsory for desktop list for a specific item.
854  *
855  * Returns: %TRUE if the application is compulsory for a specific desktop
856  *
857  * Since: 0.5.12
858  */
859 gboolean
as_app_has_compulsory_for_desktop(AsApp * app,const gchar * desktop)860 as_app_has_compulsory_for_desktop (AsApp *app, const gchar *desktop)
861 {
862 	AsAppPrivate *priv = GET_PRIVATE (app);
863 	const gchar *tmp;
864 	guint i;
865 
866 	for (i = 0; i < priv->compulsory_for_desktops->len; i++) {
867 		tmp = g_ptr_array_index (priv->compulsory_for_desktops, i);
868 		if (g_strcmp0 (tmp, desktop) == 0)
869 			return TRUE;
870 	}
871 	return FALSE;
872 }
873 
874 /**
875  * as_app_has_permission:
876  * @app: a #AsApp instance.
877  * @permission: a permission string, e.g. "Network"
878  *
879  * Searches the permission list for a specific item.
880  *
881  * Returns: %TRUE if the application has got the specified permission
882  *
883  * Since: 0.3.5
884  */
885 gboolean
as_app_has_permission(AsApp * app,const gchar * permission)886 as_app_has_permission (AsApp *app, const gchar *permission)
887 {
888 	AsAppPrivate *priv = GET_PRIVATE (app);
889 	const gchar *tmp;
890 	guint i;
891 
892 	for (i = 0; i < priv->permissions->len; i++) {
893 		tmp = g_ptr_array_index (priv->permissions, i);
894 		if (g_strcmp0 (tmp, permission) == 0)
895 			return TRUE;
896 	}
897 	return FALSE;
898 }
899 
900 /**
901  * as_app_get_format_default:
902  * @app: a #AsApp instance.
903  *
904  * Returns the default format.
905  *
906  * Returns: (transfer none): A #AsFormat, or %NULL if not found
907  *
908  * Since: 0.6.9
909  */
910 AsFormat *
as_app_get_format_default(AsApp * app)911 as_app_get_format_default (AsApp *app)
912 {
913 	AsAppPrivate *priv = GET_PRIVATE (app);
914 	if (priv->formats->len > 0) {
915 		AsFormat *format = g_ptr_array_index (priv->formats, 0);
916 		return format;
917 	}
918 	return NULL;
919 }
920 
921 /**
922  * as_app_get_format_by_filename:
923  * @app: a #AsApp instance.
924  * @filename: a filename, e.g. "/home/hughsie/dave.desktop"
925  *
926  * Searches the list of formats for a specific filename.
927  *
928  * Returns: (transfer none): A #AsFormat, or %NULL if not found
929  *
930  * Since: 0.6.9
931  */
932 AsFormat *
as_app_get_format_by_filename(AsApp * app,const gchar * filename)933 as_app_get_format_by_filename (AsApp *app, const gchar *filename)
934 {
935 	AsAppPrivate *priv = GET_PRIVATE (app);
936 	for (guint i = 0; i < priv->formats->len; i++) {
937 		AsFormat *format = g_ptr_array_index (priv->formats, i);
938 		if (g_strcmp0 (as_format_get_filename (format), filename) == 0)
939 			return format;
940 	}
941 	return NULL;
942 }
943 
944 /**
945  * as_app_get_format_by_kind:
946  * @app: a #AsApp instance.
947  * @kind: a #AsFormatKind, e.g. %AS_FORMAT_KIND_APPDATA
948  *
949  * Searches the list of formats for a specific format kind.
950  *
951  * Returns: (transfer none): A #AsFormat, or %NULL if not found
952  *
953  * Since: 0.6.9
954  */
955 AsFormat *
as_app_get_format_by_kind(AsApp * app,AsFormatKind kind)956 as_app_get_format_by_kind (AsApp *app, AsFormatKind kind)
957 {
958 	AsAppPrivate *priv = GET_PRIVATE (app);
959 	for (guint i = 0; i < priv->formats->len; i++) {
960 		AsFormat *format = g_ptr_array_index (priv->formats, i);
961 		if (as_format_get_kind (format) == kind)
962 			return format;
963 	}
964 	return NULL;
965 }
966 
967 /**
968  * as_app_get_keywords:
969  * @app: a #AsApp instance.
970  * @locale: (nullable): the locale. e.g. "en_GB"
971  *
972  * Gets any keywords the application should match against.
973  *
974  * Returns: (element-type utf8) (transfer none): an array, or %NULL
975  *
976  * Since: 0.3.0
977  **/
978 GPtrArray *
as_app_get_keywords(AsApp * app,const gchar * locale)979 as_app_get_keywords (AsApp *app, const gchar *locale)
980 {
981 	AsAppPrivate *priv = GET_PRIVATE (app);
982 	if (locale == NULL)
983 		locale = "C";
984 	return g_hash_table_lookup (priv->keywords, locale);
985 }
986 
987 /**
988  * as_app_get_kudos:
989  * @app: a #AsApp instance.
990  *
991  * Gets any kudos the application has obtained.
992  *
993  * Returns: (element-type utf8) (transfer none): an array
994  *
995  * Since: 0.2.2
996  **/
997 GPtrArray *
as_app_get_kudos(AsApp * app)998 as_app_get_kudos (AsApp *app)
999 {
1000 	AsAppPrivate *priv = GET_PRIVATE (app);
1001 	return priv->kudos;
1002 }
1003 
1004 /**
1005  * as_app_get_permissions:
1006  * @app: a #AsApp instance.
1007  *
1008  * Gets any permissions the application has obtained.
1009  *
1010  * Returns: (element-type utf8) (transfer none): an array
1011  *
1012  * Since: 0.3.5
1013  **/
1014 GPtrArray *
as_app_get_permissions(AsApp * app)1015 as_app_get_permissions (AsApp *app)
1016 {
1017 	AsAppPrivate *priv = GET_PRIVATE (app);
1018 	return priv->permissions;
1019 }
1020 
1021 /**
1022  * as_app_get_formats:
1023  * @app: a #AsApp instance.
1024  *
1025  * Gets any formats that make up the application.
1026  *
1027  * Returns: (element-type utf8) (transfer none): an array
1028  *
1029  * Since: 0.6.9
1030  **/
1031 GPtrArray *
as_app_get_formats(AsApp * app)1032 as_app_get_formats (AsApp *app)
1033 {
1034 	AsAppPrivate *priv = GET_PRIVATE (app);
1035 	return priv->formats;
1036 }
1037 
1038 /**
1039  * as_app_get_mimetypes:
1040  * @app: a #AsApp instance.
1041  *
1042  * Gets any mimetypes the application will register.
1043  *
1044  * Returns: (transfer none) (element-type utf8): an array
1045  *
1046  * Since: 0.2.0
1047  **/
1048 GPtrArray *
as_app_get_mimetypes(AsApp * app)1049 as_app_get_mimetypes (AsApp *app)
1050 {
1051 	AsAppPrivate *priv = GET_PRIVATE (app);
1052 	return priv->mimetypes;
1053 }
1054 
1055 /**
1056  * as_app_get_releases:
1057  * @app: a #AsApp instance.
1058  *
1059  * Gets all the releases the application has had.
1060  *
1061  * Returns: (element-type AsRelease) (transfer none): an array
1062  *
1063  * Since: 0.1.0
1064  **/
1065 GPtrArray *
as_app_get_releases(AsApp * app)1066 as_app_get_releases (AsApp *app)
1067 {
1068 	AsAppPrivate *priv = GET_PRIVATE (app);
1069 	return priv->releases;
1070 }
1071 
1072 /**
1073  * as_app_get_release:
1074  * @app: a #AsApp instance.
1075  * @version: a version string
1076  *
1077  * Gets a specific release from the application.
1078  *
1079  * Returns: (transfer none): a release, or %NULL
1080  *
1081  * Since: 0.3.5
1082  **/
1083 AsRelease *
as_app_get_release(AsApp * app,const gchar * version)1084 as_app_get_release (AsApp *app, const gchar *version)
1085 {
1086 	AsAppPrivate *priv = GET_PRIVATE (app);
1087 	AsRelease *release;
1088 	guint i;
1089 
1090 	for (i = 0; i < priv->releases->len; i++) {
1091 		release = g_ptr_array_index (priv->releases, i);
1092 		if (g_strcmp0 (as_release_get_version (release), version) == 0)
1093 			return release;
1094 	}
1095 	return NULL;
1096 }
1097 
1098 /**
1099  * as_app_get_release_default:
1100  * @app: a #AsApp instance.
1101  *
1102  * Gets the default (newest) release from the application.
1103  *
1104  * Returns: (transfer none): a release, or %NULL
1105  *
1106  * Since: 0.3.5
1107  **/
1108 AsRelease *
as_app_get_release_default(AsApp * app)1109 as_app_get_release_default (AsApp *app)
1110 {
1111 	AsAppPrivate *priv = GET_PRIVATE (app);
1112 	AsRelease *release_newest = NULL;
1113 	AsRelease *release_tmp = NULL;
1114 	guint i;
1115 
1116 	for (i = 0; i < priv->releases->len; i++) {
1117 		release_tmp = g_ptr_array_index (priv->releases, i);
1118 		if (release_newest == NULL ||
1119 		    as_release_vercmp (release_tmp, release_newest) < 1)
1120 			release_newest = release_tmp;
1121 	}
1122 	return release_newest;
1123 }
1124 
1125 /**
1126  * as_app_get_release_by_version:
1127  * @app: a #AsApp instance.
1128  * @version: a release version number, e.g. "1.2.3"
1129  *
1130  * Gets a specific release from the application.
1131  *
1132  * Returns: (transfer none): a release, or %NULL
1133  *
1134  * Since: 0.7.3
1135  **/
1136 AsRelease *
as_app_get_release_by_version(AsApp * app,const gchar * version)1137 as_app_get_release_by_version (AsApp *app, const gchar *version)
1138 {
1139 	AsAppPrivate *priv = GET_PRIVATE (app);
1140 	for (guint i = 0; i < priv->releases->len; i++) {
1141 		AsRelease *release_tmp = g_ptr_array_index (priv->releases, i);
1142 		if (g_strcmp0 (version, as_release_get_version (release_tmp)) == 0)
1143 			return release_tmp;
1144 	}
1145 	return NULL;
1146 }
1147 
1148 /**
1149  * as_app_get_provides:
1150  * @app: a #AsApp instance.
1151  *
1152  * Gets all the provides the application has.
1153  *
1154  * Returns: (element-type AsProvide) (transfer none): an array
1155  *
1156  * Since: 0.1.6
1157  **/
1158 GPtrArray *
as_app_get_provides(AsApp * app)1159 as_app_get_provides (AsApp *app)
1160 {
1161 	AsAppPrivate *priv = GET_PRIVATE (app);
1162 	return priv->provides;
1163 }
1164 
1165 /**
1166  * as_app_get_launchables:
1167  * @app: a #AsApp instance.
1168  *
1169  * Gets all the launchables the application has.
1170  *
1171  * Returns: (element-type AsLaunchable) (transfer none): an array
1172  *
1173  * Since: 0.6.13
1174  **/
1175 GPtrArray *
as_app_get_launchables(AsApp * app)1176 as_app_get_launchables (AsApp *app)
1177 {
1178 	AsAppPrivate *priv = GET_PRIVATE (app);
1179 	return priv->launchables;
1180 }
1181 
1182 /**
1183  * as_app_get_launchable_by_kind:
1184  * @app: a #AsApp instance.
1185  * @kind: a #AsLaunchableKind, e.g. %AS_FORMAT_KIND_APPDATA
1186  *
1187  * Searches the list of launchables for a specific launchable kind.
1188  *
1189  * Returns: (transfer none): A #AsLaunchable, or %NULL if not found
1190  *
1191  * Since: 0.6.13
1192  */
1193 AsLaunchable *
as_app_get_launchable_by_kind(AsApp * app,AsLaunchableKind kind)1194 as_app_get_launchable_by_kind (AsApp *app, AsLaunchableKind kind)
1195 {
1196 	AsAppPrivate *priv = GET_PRIVATE (app);
1197 	for (guint i = 0; i < priv->launchables->len; i++) {
1198 		AsLaunchable *launchable = g_ptr_array_index (priv->launchables, i);
1199 		if (as_launchable_get_kind (launchable) == kind)
1200 			return launchable;
1201 	}
1202 	return NULL;
1203 }
1204 
1205 /**
1206  * as_app_get_launchable_default:
1207  * @app: a #AsApp instance.
1208  *
1209  * Returns the default launchable.
1210  *
1211  * Returns: (transfer none): A #AsLaunchable, or %NULL if not found
1212  *
1213  * Since: 0.6.13
1214  */
1215 AsLaunchable *
as_app_get_launchable_default(AsApp * app)1216 as_app_get_launchable_default (AsApp *app)
1217 {
1218 	AsAppPrivate *priv = GET_PRIVATE (app);
1219 	if (priv->launchables->len > 0) {
1220 		AsLaunchable *launchable = g_ptr_array_index (priv->launchables, 0);
1221 		return launchable;
1222 	}
1223 	return NULL;
1224 }
1225 
1226 /**
1227  * as_app_get_screenshots:
1228  * @app: a #AsApp instance.
1229  *
1230  * Gets any screenshots the application has defined.
1231  *
1232  * Returns: (element-type AsScreenshot) (transfer none): an array
1233  *
1234  * Since: 0.1.0
1235  **/
1236 GPtrArray *
as_app_get_screenshots(AsApp * app)1237 as_app_get_screenshots (AsApp *app)
1238 {
1239 	AsAppPrivate *priv = GET_PRIVATE (app);
1240 	return priv->screenshots;
1241 }
1242 
1243 /**
1244  * as_app_get_screenshot_default:
1245  * @app: a #AsApp instance.
1246  *
1247  * Gets the default screenshot for the component.
1248  *
1249  * Returns: (transfer none): a screenshot or %NULL
1250  *
1251  * Since: 0.7.3
1252  **/
1253 AsScreenshot *
as_app_get_screenshot_default(AsApp * app)1254 as_app_get_screenshot_default (AsApp *app)
1255 {
1256 	AsAppPrivate *priv = GET_PRIVATE (app);
1257 	if (priv->screenshots->len == 0)
1258 		return NULL;
1259 	return AS_SCREENSHOT (g_ptr_array_index (priv->screenshots, 0));
1260 }
1261 
1262 /**
1263  * as_app_get_reviews:
1264  * @app: a #AsApp instance.
1265  *
1266  * Gets any reviews the application has defined.
1267  *
1268  * Returns: (element-type AsScreenshot) (transfer none): an array
1269  *
1270  * Since: 0.6.1
1271  **/
1272 GPtrArray *
as_app_get_reviews(AsApp * app)1273 as_app_get_reviews (AsApp *app)
1274 {
1275 	AsAppPrivate *priv = GET_PRIVATE (app);
1276 	return priv->reviews;
1277 }
1278 
1279 /**
1280  * as_app_get_content_ratings:
1281  * @app: a #AsApp instance.
1282  *
1283  * Gets any content_ratings the application has defined.
1284  *
1285  * Returns: (element-type AsContentRating) (transfer none): an array
1286  *
1287  * Since: 0.5.12
1288  **/
1289 GPtrArray *
as_app_get_content_ratings(AsApp * app)1290 as_app_get_content_ratings (AsApp *app)
1291 {
1292 	AsAppPrivate *priv = GET_PRIVATE (app);
1293 	return priv->content_ratings;
1294 }
1295 
1296 /**
1297  * as_app_get_content_rating:
1298  * @app: a #AsApp instance.
1299  * @kind: a ratings kind, e.g. "oars-1.0"
1300  *
1301  * Gets a content ratings the application has defined of a specific type.
1302  *
1303  * Returns: (transfer none): a #AsContentRating or NULL for not found
1304  *
1305  * Since: 0.5.12
1306  **/
1307 AsContentRating *
as_app_get_content_rating(AsApp * app,const gchar * kind)1308 as_app_get_content_rating (AsApp *app, const gchar *kind)
1309 {
1310 	AsAppPrivate *priv = GET_PRIVATE (app);
1311 	guint i;
1312 
1313 	for (i = 0; i < priv->content_ratings->len; i++) {
1314 		AsContentRating *content_rating;
1315 		content_rating = g_ptr_array_index (priv->content_ratings, i);
1316 		if (g_strcmp0 (as_content_rating_get_kind (content_rating), kind) == 0)
1317 			return content_rating;
1318 	}
1319 	return NULL;
1320 }
1321 
1322 /**
1323  * as_app_get_agreements:
1324  * @app: a #AsApp instance.
1325  *
1326  * Gets any agreements the application has defined.
1327  *
1328  * Returns: (element-type AsAgreement) (transfer none): an array
1329  *
1330  * Since: 0.7.8
1331  **/
1332 GPtrArray *
as_app_get_agreements(AsApp * app)1333 as_app_get_agreements (AsApp *app)
1334 {
1335 	AsAppPrivate *priv = GET_PRIVATE (app);
1336 	return priv->agreements;
1337 }
1338 
1339 /**
1340  * as_app_get_agreement_by_kind:
1341  * @app: a #AsApp instance.
1342  * @kind: an agreement kind, e.g. %AS_AGREEMENT_KIND_EULA
1343  *
1344  * Gets a agreement the application has defined of a specific type.
1345  *
1346  * Returns: (transfer none): a #AsAgreement or NULL for not found
1347  *
1348  * Since: 0.7.8
1349  **/
1350 AsAgreement *
as_app_get_agreement_by_kind(AsApp * app,AsAgreementKind kind)1351 as_app_get_agreement_by_kind (AsApp *app, AsAgreementKind kind)
1352 {
1353 	AsAppPrivate *priv = GET_PRIVATE (app);
1354 	guint i;
1355 
1356 	for (i = 0; i < priv->agreements->len; i++) {
1357 		AsAgreement *agreement;
1358 		agreement = g_ptr_array_index (priv->agreements, i);
1359 		if (as_agreement_get_kind (agreement) == kind)
1360 			return agreement;
1361 	}
1362 	return NULL;
1363 }
1364 
1365 /**
1366  * as_app_get_agreement_default:
1367  * @app: a #AsApp instance.
1368  *
1369  * Gets a privacy policys the application has defined of a specific type.
1370  *
1371  * Returns: (transfer none): a #AsAgreement or NULL for not found
1372  *
1373  * Since: 0.7.8
1374  **/
1375 AsAgreement *
as_app_get_agreement_default(AsApp * app)1376 as_app_get_agreement_default (AsApp *app)
1377 {
1378 	AsAppPrivate *priv = GET_PRIVATE (app);
1379 	if (priv->agreements->len < 1)
1380 		return NULL;
1381 	return g_ptr_array_index (priv->agreements, 0);
1382 }
1383 
1384 /**
1385  * as_app_get_icons:
1386  * @app: a #AsApp instance.
1387  *
1388  * Gets any icons the application has defined.
1389  *
1390  * Returns: (element-type AsIcon) (transfer none): an array
1391  *
1392  * Since: 0.3.1
1393  **/
1394 GPtrArray *
as_app_get_icons(AsApp * app)1395 as_app_get_icons (AsApp *app)
1396 {
1397 	AsAppPrivate *priv = GET_PRIVATE (app);
1398 	return priv->icons;
1399 }
1400 
1401 /**
1402  * as_app_get_bundles:
1403  * @app: a #AsApp instance.
1404  *
1405  * Gets any bundles the application has defined.
1406  *
1407  * Returns: (element-type AsBundle) (transfer none): an array
1408  *
1409  * Since: 0.3.5
1410  **/
1411 GPtrArray *
as_app_get_bundles(AsApp * app)1412 as_app_get_bundles (AsApp *app)
1413 {
1414 	AsAppPrivate *priv = GET_PRIVATE (app);
1415 	return priv->bundles;
1416 }
1417 
1418 /**
1419  * as_app_get_translations:
1420  * @app: a #AsApp instance.
1421  *
1422  * Gets any translations the application has defined.
1423  *
1424  * Returns: (element-type AsTranslation) (transfer none): an array
1425  *
1426  * Since: 0.5.8
1427  **/
1428 GPtrArray *
as_app_get_translations(AsApp * app)1429 as_app_get_translations (AsApp *app)
1430 {
1431 	AsAppPrivate *priv = GET_PRIVATE (app);
1432 	return priv->translations;
1433 }
1434 
1435 /**
1436  * as_app_get_suggests:
1437  * @app: a #AsApp instance.
1438  *
1439  * Gets any suggests the application has defined.
1440  *
1441  * Returns: (element-type AsSuggest) (transfer none): an array
1442  *
1443  * Since: 0.6.1
1444  **/
1445 GPtrArray *
as_app_get_suggests(AsApp * app)1446 as_app_get_suggests (AsApp *app)
1447 {
1448 	AsAppPrivate *priv = GET_PRIVATE (app);
1449 	return priv->suggests;
1450 }
1451 
1452 /**
1453  * as_app_get_requires:
1454  * @app: a #AsApp instance.
1455  *
1456  * Gets any requires the application has defined. A rquirement could be that
1457  * a firmware version has to be below a defined version or that another
1458  * application is required to be installed.
1459  *
1460  * Returns: (element-type AsRequire) (transfer none): an array
1461  *
1462  * Since: 0.6.7
1463  **/
1464 GPtrArray *
as_app_get_requires(AsApp * app)1465 as_app_get_requires (AsApp *app)
1466 {
1467 	AsAppPrivate *priv = GET_PRIVATE (app);
1468 	return priv->requires;
1469 }
1470 
1471 /**
1472  * as_app_get_require_by_value:
1473  * @app: a #AsApp instance.
1474  * @kind: a #AsRequireKind, e.g. %AS_REQUIRE_KIND_FIRMWARE
1475  * @value: a string, or NULL, e.g. `bootloader`
1476  *
1477  * Gets a specific requirement for the application.
1478  *
1479  * Returns: (transfer none): A #AsRequire, or %NULL for not found
1480  *
1481  * Since: 0.6.7
1482  **/
1483 AsRequire *
as_app_get_require_by_value(AsApp * app,AsRequireKind kind,const gchar * value)1484 as_app_get_require_by_value (AsApp *app, AsRequireKind kind, const gchar *value)
1485 {
1486 	AsAppPrivate *priv = GET_PRIVATE (app);
1487 	for (guint i = 0; i < priv->requires->len; i++) {
1488 		AsRequire *req = g_ptr_array_index (priv->requires, i);
1489 		if (as_require_get_kind (req) == kind &&
1490 		    g_strcmp0 (as_require_get_value (req), value) == 0)
1491 			return req;
1492 	}
1493 	return NULL;
1494 }
1495 
1496 /**
1497  * as_app_get_names:
1498  * @app: a #AsApp instance.
1499  *
1500  * Gets the names set for the application.
1501  *
1502  * Returns: (transfer none): hash table of names
1503  *
1504  * Since: 0.1.6
1505  **/
1506 GHashTable *
as_app_get_names(AsApp * app)1507 as_app_get_names (AsApp *app)
1508 {
1509 	AsAppPrivate *priv = GET_PRIVATE (app);
1510 	return priv->names;
1511 }
1512 
1513 /**
1514  * as_app_get_comments:
1515  * @app: a #AsApp instance.
1516  *
1517  * Gets the comments set for the application.
1518  *
1519  * Returns: (transfer none): hash table of comments
1520  *
1521  * Since: 0.1.6
1522  **/
1523 GHashTable *
as_app_get_comments(AsApp * app)1524 as_app_get_comments (AsApp *app)
1525 {
1526 	AsAppPrivate *priv = GET_PRIVATE (app);
1527 	return priv->comments;
1528 }
1529 
1530 /**
1531  * as_app_get_developer_names:
1532  * @app: a #AsApp instance.
1533  *
1534  * Gets the developer_names set for the application.
1535  *
1536  * Returns: (transfer none): hash table of developer_names
1537  *
1538  * Since: 0.1.8
1539  **/
1540 GHashTable *
as_app_get_developer_names(AsApp * app)1541 as_app_get_developer_names (AsApp *app)
1542 {
1543 	AsAppPrivate *priv = GET_PRIVATE (app);
1544 	return priv->developer_names;
1545 }
1546 
1547 /**
1548  * as_app_get_metadata:
1549  * @app: a #AsApp instance.
1550  *
1551  * Gets the metadata set for the application.
1552  *
1553  * Returns: (transfer none) (element-type utf8 utf8): hash table of metadata
1554  *
1555  * Since: 0.1.6
1556  **/
1557 GHashTable *
as_app_get_metadata(AsApp * app)1558 as_app_get_metadata (AsApp *app)
1559 {
1560 	AsAppPrivate *priv = GET_PRIVATE (app);
1561 	return priv->metadata;
1562 }
1563 
1564 /**
1565  * as_app_get_descriptions:
1566  * @app: a #AsApp instance.
1567  *
1568  * Gets the descriptions set for the application.
1569  *
1570  * Returns: (transfer none): hash table of descriptions
1571  *
1572  * Since: 0.1.6
1573  **/
1574 GHashTable *
as_app_get_descriptions(AsApp * app)1575 as_app_get_descriptions (AsApp *app)
1576 {
1577 	AsAppPrivate *priv = GET_PRIVATE (app);
1578 	return priv->descriptions;
1579 }
1580 
1581 /**
1582  * as_app_get_urls:
1583  * @app: a #AsApp instance.
1584  *
1585  * Gets the URLs set for the application.
1586  *
1587  * Returns: (transfer none): hash table of URLs
1588  *
1589  * Since: 0.1.0
1590  **/
1591 GHashTable *
as_app_get_urls(AsApp * app)1592 as_app_get_urls (AsApp *app)
1593 {
1594 	AsAppPrivate *priv = GET_PRIVATE (app);
1595 	return priv->urls;
1596 }
1597 
1598 /**
1599  * as_app_get_pkgnames:
1600  * @app: a #AsApp instance.
1601  *
1602  * Gets the package names (if any) for the application.
1603  *
1604  * Returns: (element-type utf8) (transfer none): an array
1605  *
1606  * Since: 0.1.0
1607  **/
1608 GPtrArray *
as_app_get_pkgnames(AsApp * app)1609 as_app_get_pkgnames (AsApp *app)
1610 {
1611 	AsAppPrivate *priv = GET_PRIVATE (app);
1612 	return priv->pkgnames;
1613 }
1614 
1615 /**
1616  * as_app_get_architectures:
1617  * @app: a #AsApp instance.
1618  *
1619  * Gets the supported architectures for the application, or an empty list
1620  * if all architectures are supported.
1621  *
1622  * Returns: (element-type utf8) (transfer none): an array
1623  *
1624  * Since: 0.1.1
1625  **/
1626 GPtrArray *
as_app_get_architectures(AsApp * app)1627 as_app_get_architectures (AsApp *app)
1628 {
1629 	AsAppPrivate *priv = GET_PRIVATE (app);
1630 	return priv->architectures;
1631 }
1632 
1633 /**
1634  * as_app_get_extends:
1635  * @app: a #AsApp instance.
1636  *
1637  * Gets the IDs that are extended from the addon.
1638  *
1639  * Returns: (element-type utf8) (transfer none): an array
1640  *
1641  * Since: 0.1.7
1642  **/
1643 GPtrArray *
as_app_get_extends(AsApp * app)1644 as_app_get_extends (AsApp *app)
1645 {
1646 	AsAppPrivate *priv = GET_PRIVATE (app);
1647 	return priv->extends;
1648 }
1649 
1650 /**
1651  * as_app_get_addons:
1652  * @app: a #AsApp instance.
1653  *
1654  * Gets all the addons the application has.
1655  *
1656  * Returns: (element-type AsApp) (transfer none): an array
1657  *
1658  * Since: 0.1.7
1659  **/
1660 GPtrArray *
as_app_get_addons(AsApp * app)1661 as_app_get_addons (AsApp *app)
1662 {
1663 	AsAppPrivate *priv = GET_PRIVATE (app);
1664 	return priv->addons;
1665 }
1666 
1667 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1668 /**
1669  * as_app_get_id_kind:
1670  * @app: a #AsApp instance.
1671  *
1672  * Gets the ID kind.
1673  *
1674  * Returns: enumerated value
1675  *
1676  * Since: 0.1.0
1677  **/
1678 AsIdKind
as_app_get_id_kind(AsApp * app)1679 as_app_get_id_kind (AsApp *app)
1680 {
1681 	AsAppPrivate *priv = GET_PRIVATE (app);
1682 	return priv->kind;
1683 }
1684 G_GNUC_END_IGNORE_DEPRECATIONS
1685 
1686 /**
1687  * as_app_get_kind:
1688  * @app: a #AsApp instance.
1689  *
1690  * Gets the ID kind.
1691  *
1692  * Returns: enumerated value
1693  *
1694  * Since: 0.5.10
1695  **/
1696 AsAppKind
as_app_get_kind(AsApp * app)1697 as_app_get_kind (AsApp *app)
1698 {
1699 	AsAppPrivate *priv = GET_PRIVATE (app);
1700 	return priv->kind;
1701 }
1702 
1703 /**
1704  * as_app_get_name_size: (skip)
1705  * @app: a #AsApp instance.
1706  *
1707  * Gets the number of names.
1708  *
1709  * Returns: integer
1710  *
1711  * Since: 0.1.4
1712  **/
1713 guint
as_app_get_name_size(AsApp * app)1714 as_app_get_name_size (AsApp *app)
1715 {
1716 	AsAppPrivate *priv = GET_PRIVATE (app);
1717 	return g_hash_table_size (priv->names);
1718 }
1719 
1720 /**
1721  * as_app_get_comment_size: (skip)
1722  * @app: a #AsApp instance.
1723  *
1724  * Gets the number of comments.
1725  *
1726  * Returns: integer
1727  *
1728  * Since: 0.1.4
1729  **/
1730 guint
as_app_get_comment_size(AsApp * app)1731 as_app_get_comment_size (AsApp *app)
1732 {
1733 	AsAppPrivate *priv = GET_PRIVATE (app);
1734 	return g_hash_table_size (priv->comments);
1735 }
1736 
1737 /**
1738  * as_app_get_description_size: (skip)
1739  * @app: a #AsApp instance.
1740  *
1741  * Gets the number of descriptions.
1742  *
1743  * Returns: integer
1744  *
1745  * Since: 0.1.4
1746  **/
1747 guint
as_app_get_description_size(AsApp * app)1748 as_app_get_description_size (AsApp *app)
1749 {
1750 	AsAppPrivate *priv = GET_PRIVATE (app);
1751 	return g_hash_table_size (priv->descriptions);
1752 }
1753 
1754 /**
1755  * as_app_get_source_kind:
1756  * @app: a #AsApp instance.
1757  *
1758  * Gets the source kind, i.e. where the AsApp came from.
1759  *
1760  * Returns: enumerated value
1761  *
1762  * Since: 0.1.4
1763  **/
1764 AsFormatKind
as_app_get_source_kind(AsApp * app)1765 as_app_get_source_kind (AsApp *app)
1766 {
1767 	AsAppPrivate *priv = GET_PRIVATE (app);
1768 	if (priv->formats->len > 0) {
1769 		AsFormat *format = g_ptr_array_index (priv->formats, 0);
1770 		return as_format_get_kind (format);
1771 	}
1772 	return AS_FORMAT_KIND_UNKNOWN;
1773 }
1774 
1775 /**
1776  * as_app_get_scope:
1777  * @app: a #AsApp instance.
1778  *
1779  * Gets the scope of the application.
1780  *
1781  * Returns: enumerated value
1782  *
1783  * Since: 0.6.1
1784  **/
1785 AsAppScope
as_app_get_scope(AsApp * app)1786 as_app_get_scope (AsApp *app)
1787 {
1788 	AsAppPrivate *priv = GET_PRIVATE (app);
1789 	return priv->scope;
1790 }
1791 
1792 /**
1793  * as_app_get_merge_kind:
1794  * @app: a #AsApp instance.
1795  *
1796  * Gets the merge_kind of the application.
1797  *
1798  * Returns: enumerated value
1799  *
1800  * Since: 0.6.1
1801  **/
1802 AsAppMergeKind
as_app_get_merge_kind(AsApp * app)1803 as_app_get_merge_kind (AsApp *app)
1804 {
1805 	AsAppPrivate *priv = GET_PRIVATE (app);
1806 	return priv->merge_kind;
1807 }
1808 
1809 /**
1810  * as_app_get_state:
1811  * @app: a #AsApp instance.
1812  *
1813  * Gets the application state.
1814  *
1815  * Returns: enumerated value
1816  *
1817  * Since: 0.2.2
1818  **/
1819 AsAppState
as_app_get_state(AsApp * app)1820 as_app_get_state (AsApp *app)
1821 {
1822 	AsAppPrivate *priv = GET_PRIVATE (app);
1823 	return priv->state;
1824 }
1825 
1826 /**
1827  * as_app_get_trust_flags:
1828  * @app: a #AsApp instance.
1829  *
1830  * Gets the trust flags, i.e. how trusted the incoming data is.
1831  *
1832  * Returns: bitfield
1833  *
1834  * Since: 0.2.2
1835  **/
1836 guint32
as_app_get_trust_flags(AsApp * app)1837 as_app_get_trust_flags (AsApp *app)
1838 {
1839 	AsAppPrivate *priv = GET_PRIVATE (app);
1840 	return priv->trust_flags;
1841 }
1842 
1843 /**
1844  * as_app_get_problems: (skip)
1845  * @app: a #AsApp instance.
1846  *
1847  * Gets the bitfield of problems.
1848  *
1849  * Returns: problems encountered during parsing the application
1850  *
1851  * Since: 0.1.4
1852  **/
1853 AsAppProblems
as_app_get_problems(AsApp * app)1854 as_app_get_problems (AsApp *app)
1855 {
1856 	AsAppPrivate *priv = GET_PRIVATE (app);
1857 	return priv->problems;
1858 }
1859 
1860 /**
1861  * as_app_get_pkgname_default:
1862  * @app: a #AsApp instance.
1863  *
1864  * Gets the default package name.
1865  *
1866  * Returns: string, or %NULL if unset
1867  *
1868  * Since: 0.2.0
1869  **/
1870 const gchar *
as_app_get_pkgname_default(AsApp * app)1871 as_app_get_pkgname_default (AsApp *app)
1872 {
1873 	AsAppPrivate *priv = GET_PRIVATE (app);
1874 	if (priv->pkgnames->len < 1)
1875 		return NULL;
1876 	return g_ptr_array_index (priv->pkgnames, 0);
1877 }
1878 
1879 /**
1880  * as_app_get_source_pkgname:
1881  * @app: a #AsApp instance.
1882  *
1883  * Gets the source package name that produced the binary package.
1884  * Only source packages producing more than one binary package will have this
1885  * entry set.
1886  *
1887  * Returns: string, or %NULL if unset
1888  *
1889  * Since: 0.2.4
1890  **/
1891 const gchar *
as_app_get_source_pkgname(AsApp * app)1892 as_app_get_source_pkgname (AsApp *app)
1893 {
1894 	AsAppPrivate *priv = GET_PRIVATE (app);
1895 	return priv->source_pkgname;
1896 }
1897 
1898 /**
1899  * as_app_get_icon_path:
1900  * @app: a #AsApp instance.
1901  *
1902  * Gets the application icon path.
1903  *
1904  * Returns: string, or %NULL if unset
1905  *
1906  * Since: 0.1.0
1907  **/
1908 const gchar *
as_app_get_icon_path(AsApp * app)1909 as_app_get_icon_path (AsApp *app)
1910 {
1911 	AsAppPrivate *priv = GET_PRIVATE (app);
1912 	return priv->icon_path;
1913 }
1914 
1915 /**
1916  * as_app_get_name:
1917  * @app: a #AsApp instance.
1918  * @locale: (nullable): the locale. e.g. "en_GB"
1919  *
1920  * Gets the application name for a specific locale.
1921  *
1922  * Returns: string, or %NULL if unset
1923  *
1924  * Since: 0.1.0
1925  **/
1926 const gchar *
as_app_get_name(AsApp * app,const gchar * locale)1927 as_app_get_name (AsApp *app, const gchar *locale)
1928 {
1929 	AsAppPrivate *priv = GET_PRIVATE (app);
1930 	return as_hash_lookup_by_locale (priv->names, locale);
1931 }
1932 
1933 /**
1934  * as_app_get_comment:
1935  * @app: a #AsApp instance.
1936  * @locale: (nullable): the locale. e.g. "en_GB"
1937  *
1938  * Gets the application summary for a specific locale.
1939  *
1940  * Returns: string, or %NULL if unset
1941  *
1942  * Since: 0.1.0
1943  **/
1944 const gchar *
as_app_get_comment(AsApp * app,const gchar * locale)1945 as_app_get_comment (AsApp *app, const gchar *locale)
1946 {
1947 	AsAppPrivate *priv = GET_PRIVATE (app);
1948 	return as_hash_lookup_by_locale (priv->comments, locale);
1949 }
1950 
1951 /**
1952  * as_app_get_developer_name:
1953  * @app: a #AsApp instance.
1954  * @locale: (nullable): the locale. e.g. "en_GB"
1955  *
1956  * Gets the application developer name for a specific locale.
1957  *
1958  * Returns: string, or %NULL if unset
1959  *
1960  * Since: 0.1.8
1961  **/
1962 const gchar *
as_app_get_developer_name(AsApp * app,const gchar * locale)1963 as_app_get_developer_name (AsApp *app, const gchar *locale)
1964 {
1965 	AsAppPrivate *priv = GET_PRIVATE (app);
1966 	return as_hash_lookup_by_locale (priv->developer_names, locale);
1967 }
1968 
1969 /**
1970  * as_app_get_description:
1971  * @app: a #AsApp instance.
1972  * @locale: (nullable): the locale. e.g. "en_GB"
1973  *
1974  * Gets the application description markup for a specific locale.
1975  *
1976  * Returns: string, or %NULL if unset
1977  *
1978  * Since: 0.1.0
1979  **/
1980 const gchar *
as_app_get_description(AsApp * app,const gchar * locale)1981 as_app_get_description (AsApp *app, const gchar *locale)
1982 {
1983 	AsAppPrivate *priv = GET_PRIVATE (app);
1984 	return as_hash_lookup_by_locale (priv->descriptions, locale);
1985 }
1986 
1987 /**
1988  * as_app_get_language:
1989  * @app: a #AsApp instance.
1990  * @locale: (nullable): the locale. e.g. "en_GB"
1991  *
1992  * Gets the language coverage for the specific language.
1993  *
1994  * Returns: a percentage value where 0 is unspecified, or -1 for not found
1995  *
1996  * Since: 0.1.0
1997  **/
1998 gint
as_app_get_language(AsApp * app,const gchar * locale)1999 as_app_get_language (AsApp *app, const gchar *locale)
2000 {
2001 	AsAppPrivate *priv = GET_PRIVATE (app);
2002 	gpointer value = NULL;
2003 	g_auto(GStrv) lang = NULL;
2004 
2005 	/* fallback */
2006 	if (locale == NULL)
2007 		locale = "C";
2008 
2009 	/* look up full locale */
2010 	if (g_hash_table_lookup_extended (priv->languages,
2011 					  locale, NULL, &value)) {
2012 		return GPOINTER_TO_INT (value);
2013 	}
2014 
2015 	/* look up just country code */
2016 	lang = g_strsplit (locale, "_", 2);
2017 	if (g_strv_length (lang) == 2 &&
2018 	    g_hash_table_lookup_extended (priv->languages,
2019 					  lang[0], NULL, &value)) {
2020 		return GPOINTER_TO_INT (value);
2021 	}
2022 
2023 	/* failed */
2024 	return -1;
2025 }
2026 
2027 /**
2028  * as_app_get_priority:
2029  * @app: a #AsApp instance.
2030  *
2031  * Gets the application priority. Larger values trump smaller values.
2032  *
2033  * Returns: priority value
2034  *
2035  * Since: 0.1.0
2036  **/
2037 gint
as_app_get_priority(AsApp * app)2038 as_app_get_priority (AsApp *app)
2039 {
2040 	AsAppPrivate *priv = GET_PRIVATE (app);
2041 	return priv->priority;
2042 }
2043 
2044 /**
2045  * as_app_get_languages:
2046  * @app: a #AsApp instance.
2047  *
2048  * Get a list of all languages.
2049  *
2050  * Returns: (transfer container) (element-type utf8): list of language values
2051  *
2052  * Since: 0.1.0
2053  **/
2054 GList *
as_app_get_languages(AsApp * app)2055 as_app_get_languages (AsApp *app)
2056 {
2057 	AsAppPrivate *priv = GET_PRIVATE (app);
2058 	return g_hash_table_get_keys (priv->languages);
2059 }
2060 
2061 /**
2062  * as_app_get_url_item:
2063  * @app: a #AsApp instance.
2064  * @url_kind: the URL kind, e.g. %AS_URL_KIND_HOMEPAGE.
2065  *
2066  * Gets a URL.
2067  *
2068  * Returns: string, or %NULL if unset
2069  *
2070  * Since: 0.1.0
2071  **/
2072 const gchar *
as_app_get_url_item(AsApp * app,AsUrlKind url_kind)2073 as_app_get_url_item (AsApp *app, AsUrlKind url_kind)
2074 {
2075 	AsAppPrivate *priv = GET_PRIVATE (app);
2076 	return g_hash_table_lookup (priv->urls,
2077 				    as_url_kind_to_string (url_kind));
2078 }
2079 
2080 /**
2081  * as_app_get_metadata_item:
2082  * @app: a #AsApp instance.
2083  * @key: the metadata key.
2084  *
2085  * Gets some metadata item.
2086  *
2087  * Returns: string, or %NULL if unset
2088  *
2089  * Since: 0.1.0
2090  **/
2091 const gchar *
as_app_get_metadata_item(AsApp * app,const gchar * key)2092 as_app_get_metadata_item (AsApp *app, const gchar *key)
2093 {
2094 	AsAppPrivate *priv = GET_PRIVATE (app);
2095 	return g_hash_table_lookup (priv->metadata, key);
2096 }
2097 
2098 /**
2099  * as_app_get_project_group:
2100  * @app: a #AsApp instance.
2101  *
2102  * Gets an application project group.
2103  *
2104  * Returns: string, or %NULL if unset
2105  *
2106  * Since: 0.1.0
2107  **/
2108 const gchar *
as_app_get_project_group(AsApp * app)2109 as_app_get_project_group (AsApp *app)
2110 {
2111 	AsAppPrivate *priv = GET_PRIVATE (app);
2112 	return priv->project_group;
2113 }
2114 
2115 /**
2116  * as_app_get_project_license:
2117  * @app: a #AsApp instance.
2118  *
2119  * Gets the application project license.
2120  *
2121  * Returns: string, or %NULL if unset
2122  *
2123  * Since: 0.1.0
2124  **/
2125 const gchar *
as_app_get_project_license(AsApp * app)2126 as_app_get_project_license (AsApp *app)
2127 {
2128 	AsAppPrivate *priv = GET_PRIVATE (app);
2129 	return priv->project_license;
2130 }
2131 
2132 /**
2133  * as_app_get_metadata_license:
2134  * @app: a #AsApp instance.
2135  *
2136  * Gets the application project license.
2137  *
2138  * Returns: string, or %NULL if unset
2139  *
2140  * Since: 0.1.4
2141  **/
2142 const gchar *
as_app_get_metadata_license(AsApp * app)2143 as_app_get_metadata_license (AsApp *app)
2144 {
2145 	AsAppPrivate *priv = GET_PRIVATE (app);
2146 	return priv->metadata_license;
2147 }
2148 
2149 /**
2150  * as_app_get_update_contact:
2151  * @app: a #AsApp instance.
2152  *
2153  * Gets the application upstream update contact email.
2154  *
2155  * Returns: string, or %NULL if unset
2156  *
2157  * Since: 0.1.4
2158  **/
2159 const gchar *
as_app_get_update_contact(AsApp * app)2160 as_app_get_update_contact (AsApp *app)
2161 {
2162 	AsAppPrivate *priv = GET_PRIVATE (app);
2163 	return priv->update_contact;
2164 }
2165 
2166 /**
2167  * as_app_get_origin:
2168  * @app: a #AsApp instance.
2169  *
2170  * Gets the application origin.
2171  *
2172  * Returns: the origin string, or %NULL if unset
2173  *
2174  * Since: 0.3.2
2175  **/
2176 const gchar *
as_app_get_origin(AsApp * app)2177 as_app_get_origin (AsApp *app)
2178 {
2179 	AsAppPrivate *priv = GET_PRIVATE (app);
2180 	return priv->origin;
2181 }
2182 
2183 /**
2184  * as_app_get_source_file:
2185  * @app: a #AsApp instance.
2186  *
2187  * Gets the default source filename the instance was populated from.
2188  *
2189  * Returns: string, or %NULL if unset
2190  *
2191  * Since: 0.2.2
2192  **/
2193 const gchar *
as_app_get_source_file(AsApp * app)2194 as_app_get_source_file (AsApp *app)
2195 {
2196 	AsAppPrivate *priv = GET_PRIVATE (app);
2197 	if (priv->formats->len > 0) {
2198 		AsFormat *format = g_ptr_array_index (priv->formats, 0);
2199 		return as_format_get_filename (format);
2200 	}
2201 	return NULL;
2202 }
2203 
2204 /**
2205  * as_app_get_branch:
2206  * @app: a #AsApp instance.
2207  *
2208  * Gets the branch for the application.
2209  *
2210  * Returns: string, or %NULL if unset
2211  *
2212  * Since: 0.6.1
2213  **/
2214 const gchar *
as_app_get_branch(AsApp * app)2215 as_app_get_branch (AsApp *app)
2216 {
2217 	AsAppPrivate *priv = GET_PRIVATE (app);
2218 	return priv->branch;
2219 }
2220 
2221 static gboolean
as_app_validate_utf8(const gchar * text)2222 as_app_validate_utf8 (const gchar *text)
2223 {
2224 	gboolean is_whitespace = TRUE;
2225 	guint i;
2226 
2227 	/* nothing */
2228 	if (text == NULL)
2229 		return TRUE;
2230 	if (text[0] == '\0')
2231 		return FALSE;
2232 
2233 	/* is just whitespace */
2234 	for (i = 0; text[i] != '\0' && is_whitespace; i++)
2235 		is_whitespace = g_ascii_isspace (text[i]);
2236 	if (is_whitespace)
2237 		return FALSE;
2238 
2239 	/* standard UTF-8 checks */
2240 	if (!g_utf8_validate (text, -1, NULL))
2241 		return FALSE;
2242 
2243 	/* additional check for xmllint */
2244 	for (i = 0; text[i] != '\0'; i++) {
2245 		if (text[i] == 0x1f)
2246 			return FALSE;
2247 	}
2248 	return TRUE;
2249 }
2250 
2251 /******************************************************************************/
2252 
2253 /**
2254  * as_app_set_id:
2255  * @app: a #AsApp instance.
2256  * @id: the new _full_ application ID, e.g. "org.gnome.Software.desktop".
2257  *
2258  * Sets a new application ID. Any invalid characters will be automatically replaced.
2259  *
2260  * Since: 0.1.0
2261  **/
2262 void
as_app_set_id(AsApp * app,const gchar * id)2263 as_app_set_id (AsApp *app, const gchar *id)
2264 {
2265 	AsAppPrivate *priv = GET_PRIVATE (app);
2266 	gchar *tmp;
2267 	guint i;
2268 	const gchar *suffixes[] = {
2269 		".desktop",
2270 		".addon",
2271 		".firmware",
2272 		".shell-extension",
2273 		NULL };
2274 
2275 	g_return_if_fail (AS_IS_APP (app));
2276 	g_return_if_fail (id != NULL);
2277 
2278 	/* handle untrusted */
2279 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2280 	    !as_app_validate_utf8 (id)) {
2281 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2282 		return;
2283 	}
2284 
2285 	/* save full ID */
2286 	as_ref_string_assign_safe (&priv->id, id);
2287 
2288 	/* save filename */
2289 	if (priv->id_filename != NULL)
2290 		as_ref_string_unref (priv->id_filename);
2291 	priv->id_filename = as_ref_string_new (as_app_get_id_no_prefix (app));
2292 	g_strdelimit (priv->id_filename, "&<>", '-');
2293 	for (i = 0; suffixes[i] != NULL; i++) {
2294 		tmp = g_strrstr_len (priv->id_filename, -1, suffixes[i]);
2295 		if (tmp != NULL)
2296 			*tmp = '\0';
2297 	}
2298 
2299 	/* no longer valid */
2300 	priv->unique_id_valid = FALSE;
2301 }
2302 
2303 /**
2304  * as_app_set_source_kind:
2305  * @app: a #AsApp instance.
2306  * @source_kind: the #AsFormatKind.
2307  *
2308  * Sets the source kind.
2309  *
2310  * Since: 0.1.4
2311  **/
2312 void
as_app_set_source_kind(AsApp * app,AsFormatKind source_kind)2313 as_app_set_source_kind (AsApp *app, AsFormatKind source_kind)
2314 {
2315 	AsAppPrivate *priv = GET_PRIVATE (app);
2316 	g_autoptr(AsFormat) format = NULL;
2317 
2318 	/* already exists */
2319 	if (priv->formats->len > 0) {
2320 		AsFormat *format_tmp = g_ptr_array_index (priv->formats, 0);
2321 		as_format_set_kind (format_tmp, source_kind);
2322 		return;
2323 	}
2324 
2325 	/* create something */
2326 	format = as_format_new ();
2327 	as_format_set_kind (format, source_kind);
2328 	as_app_add_format (app, format);
2329 }
2330 
2331 /**
2332  * as_app_set_scope:
2333  * @app: a #AsApp instance.
2334  * @scope: the #AsAppScope.
2335  *
2336  * Sets the scope of the application.
2337  *
2338  * Since: 0.6.1
2339  **/
2340 void
as_app_set_scope(AsApp * app,AsAppScope scope)2341 as_app_set_scope (AsApp *app, AsAppScope scope)
2342 {
2343 	AsAppPrivate *priv = GET_PRIVATE (app);
2344 	priv->scope = scope;
2345 
2346 	/* no longer valid */
2347 	priv->unique_id_valid = FALSE;
2348 }
2349 
2350 /**
2351  * as_app_set_merge_kind:
2352  * @app: a #AsApp instance.
2353  * @merge_kind: the #AsAppMergeKind.
2354  *
2355  * Sets the merge kind of the application.
2356  *
2357  * Since: 0.6.1
2358  **/
2359 void
as_app_set_merge_kind(AsApp * app,AsAppMergeKind merge_kind)2360 as_app_set_merge_kind (AsApp *app, AsAppMergeKind merge_kind)
2361 {
2362 	AsAppPrivate *priv = GET_PRIVATE (app);
2363 	priv->merge_kind = merge_kind;
2364 }
2365 
2366 /**
2367  * as_app_set_state:
2368  * @app: a #AsApp instance.
2369  * @state: the #AsAppState.
2370  *
2371  * Sets the application state.
2372  *
2373  * Since: 0.2.2
2374  **/
2375 void
as_app_set_state(AsApp * app,AsAppState state)2376 as_app_set_state (AsApp *app, AsAppState state)
2377 {
2378 	AsAppPrivate *priv = GET_PRIVATE (app);
2379 	priv->state = state;
2380 }
2381 
2382 /**
2383  * as_app_set_trust_flags:
2384  * @app: a #AsApp instance.
2385  * @trust_flags: the #AsAppTrustFlags.
2386  *
2387  * Sets the check flags, where %AS_APP_TRUST_FLAG_COMPLETE is completely
2388  * trusted input.
2389  *
2390  * Since: 0.2.2
2391  **/
2392 void
as_app_set_trust_flags(AsApp * app,guint32 trust_flags)2393 as_app_set_trust_flags (AsApp *app, guint32 trust_flags)
2394 {
2395 	AsAppPrivate *priv = GET_PRIVATE (app);
2396 	priv->trust_flags = trust_flags;
2397 }
2398 
2399 /**
2400  * as_app_has_quirk:
2401  * @app: a #AsApp instance.
2402  * @quirk: the #AsAppQuirk, e.g. %AS_APP_QUIRK_PROVENANCE
2403  *
2404  * Queries to see if an application has a specific attribute.
2405  *
2406  * Returns: %TRUE if the application has the attribute
2407  *
2408  * Since: 0.5.10
2409  **/
2410 gboolean
as_app_has_quirk(AsApp * app,AsAppQuirk quirk)2411 as_app_has_quirk (AsApp *app, AsAppQuirk quirk)
2412 {
2413 	AsAppPrivate *priv = GET_PRIVATE (app);
2414 	return (priv->quirk & quirk) > 0;
2415 }
2416 
2417 /**
2418  * as_app_add_quirk:
2419  * @app: a #AsApp instance.
2420  * @quirk: the #AsAppQuirk, e.g. %AS_APP_QUIRK_PROVENANCE
2421  *
2422  * Adds a specific attribute to an application.
2423  *
2424  * Since: 0.5.10
2425  **/
2426 void
as_app_add_quirk(AsApp * app,AsAppQuirk quirk)2427 as_app_add_quirk (AsApp *app, AsAppQuirk quirk)
2428 {
2429 	AsAppPrivate *priv = GET_PRIVATE (app);
2430 	priv->quirk |= quirk;
2431 }
2432 
2433 /**
2434  * as_app_set_kind:
2435  * @app: a #AsApp instance.
2436  * @kind: the #AsAppKind.
2437  *
2438  * Sets the application kind.
2439  *
2440  * Since: 0.5.10
2441  **/
2442 void
as_app_set_kind(AsApp * app,AsAppKind kind)2443 as_app_set_kind (AsApp *app, AsAppKind kind)
2444 {
2445 	AsAppPrivate *priv = GET_PRIVATE (app);
2446 	priv->kind = kind;
2447 
2448 	/* no longer valid */
2449 	priv->unique_id_valid = FALSE;
2450 }
2451 
2452 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
2453 /**
2454  * as_app_set_id_kind:
2455  * @app: a #AsApp instance.
2456  * @id_kind: the #AsAppKind.
2457  *
2458  * Sets the application kind.
2459  *
2460  * Since: 0.1.0
2461  **/
2462 void
as_app_set_id_kind(AsApp * app,AsIdKind id_kind)2463 as_app_set_id_kind (AsApp *app, AsIdKind id_kind)
2464 {
2465 	AsAppPrivate *priv = GET_PRIVATE (app);
2466 	priv->kind = id_kind;
2467 }
2468 G_GNUC_END_IGNORE_DEPRECATIONS
2469 
2470 /**
2471  * as_app_set_project_group:
2472  * @app: a #AsApp instance.
2473  * @project_group: the project group, e.g. "GNOME".
2474  *
2475  * Set any project affiliation.
2476  *
2477  * Since: 0.1.0
2478  **/
2479 void
as_app_set_project_group(AsApp * app,const gchar * project_group)2480 as_app_set_project_group (AsApp *app, const gchar *project_group)
2481 {
2482 	AsAppPrivate *priv = GET_PRIVATE (app);
2483 
2484 	/* handle untrusted */
2485 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2486 	    !as_app_validate_utf8 (project_group)) {
2487 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2488 		return;
2489 	}
2490 
2491 	/* check value */
2492 	if (priv->trust_flags != AS_APP_TRUST_FLAG_COMPLETE) {
2493 		if (g_strcmp0 (project_group, "") == 0) {
2494 			priv->problems |= AS_APP_PROBLEM_INVALID_PROJECT_GROUP;
2495 			return;
2496 		}
2497 	}
2498 
2499 	as_ref_string_assign_safe (&priv->project_group, project_group);
2500 }
2501 
2502 /**
2503  * as_app_set_project_license:
2504  * @app: a #AsApp instance.
2505  * @project_license: the project license string.
2506  *
2507  * Set the project license.
2508  *
2509  * Since: 0.1.0
2510  **/
2511 void
as_app_set_project_license(AsApp * app,const gchar * project_license)2512 as_app_set_project_license (AsApp *app, const gchar *project_license)
2513 {
2514 	AsAppPrivate *priv = GET_PRIVATE (app);
2515 
2516 	/* handle untrusted */
2517 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2518 	    !as_app_validate_utf8 (project_license)) {
2519 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2520 		return;
2521 	}
2522 
2523 	as_ref_string_assign_safe (&priv->project_license, project_license);
2524 }
2525 
2526 /**
2527  * as_app_set_metadata_license:
2528  * @app: a #AsApp instance.
2529  * @metadata_license: the project license string.
2530  *
2531  * Set the project license.
2532  *
2533  * Since: 0.1.4
2534  **/
2535 void
as_app_set_metadata_license(AsApp * app,const gchar * metadata_license)2536 as_app_set_metadata_license (AsApp *app, const gchar *metadata_license)
2537 {
2538 	AsAppPrivate *priv = GET_PRIVATE (app);
2539 	g_autofree gchar *tmp = NULL;
2540 	g_auto(GStrv) tokens = NULL;
2541 
2542 	/* handle untrusted */
2543 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2544 	    !as_app_validate_utf8 (metadata_license)) {
2545 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2546 		return;
2547 	}
2548 
2549 	/* automatically replace deprecated license names */
2550 	tokens = as_utils_spdx_license_tokenize (metadata_license);
2551 	tmp = as_utils_spdx_license_detokenize (tokens);
2552 	as_ref_string_assign_safe (&priv->metadata_license, tmp);
2553 }
2554 
2555 /**
2556  * as_app_set_source_pkgname:
2557  * @app: a #AsApp instance.
2558  * @source_pkgname: the project license string.
2559  *
2560  * Set the project license.
2561  *
2562  * Since: 0.2.4
2563  **/
2564 void
as_app_set_source_pkgname(AsApp * app,const gchar * source_pkgname)2565 as_app_set_source_pkgname (AsApp *app,
2566 			     const gchar *source_pkgname)
2567 {
2568 	AsAppPrivate *priv = GET_PRIVATE (app);
2569 
2570 	/* handle untrusted */
2571 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2572 	    !as_app_validate_utf8 (source_pkgname)) {
2573 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2574 		return;
2575 	}
2576 	as_ref_string_assign_safe (&priv->source_pkgname, source_pkgname);
2577 }
2578 
2579 /**
2580  * as_app_set_source_file:
2581  * @app: a #AsApp instance.
2582  * @source_file: the filename.
2583  *
2584  * Set the file that the instance was sourced from.
2585  *
2586  * Since: 0.2.2
2587  **/
2588 void
as_app_set_source_file(AsApp * app,const gchar * source_file)2589 as_app_set_source_file (AsApp *app, const gchar *source_file)
2590 {
2591 	g_autoptr(AsFormat) format = as_format_new ();
2592 	as_format_set_filename (format, source_file);
2593 	as_app_add_format (app, format);
2594 }
2595 
2596 /**
2597  * as_app_set_branch:
2598  * @app: a #AsApp instance.
2599  * @branch: the branch, e.g. "master" or "3-16".
2600  *
2601  * Set the branch that the instance was sourced from.
2602  *
2603  * Since: 0.6.1
2604  **/
2605 void
as_app_set_branch(AsApp * app,const gchar * branch)2606 as_app_set_branch (AsApp *app, const gchar *branch)
2607 {
2608 	AsAppPrivate *priv = GET_PRIVATE (app);
2609 	as_ref_string_assign_safe (&priv->branch, branch);
2610 
2611 	/* no longer valid */
2612 	priv->unique_id_valid = FALSE;
2613 }
2614 
2615 /**
2616  * as_app_set_update_contact:
2617  * @app: a #AsApp instance.
2618  * @update_contact: the project license string.
2619  *
2620  * Set the project license.
2621  *
2622  * Since: 0.1.4
2623  **/
2624 void
as_app_set_update_contact(AsApp * app,const gchar * update_contact)2625 as_app_set_update_contact (AsApp *app, const gchar *update_contact)
2626 {
2627 	AsAppPrivate *priv = GET_PRIVATE (app);
2628 	gboolean done_replacement = TRUE;
2629 	gchar *tmp;
2630 	gsize len;
2631 	guint i;
2632 	struct {
2633 		const gchar	*search;
2634 		const gchar	 replace;
2635 	} replacements[] =  {
2636 		{ "(@)",	'@' },
2637 		{ " _at_ ",	'@' },
2638 		{ "_at_",	'@' },
2639 		{ "(at)",	'@' },
2640 		{ " AT ",	'@' },
2641 		{ "_dot_",	'.' },
2642 		{ " DOT ",	'.' },
2643 		{ NULL,		'\0' } };
2644 
2645 	/* handle untrusted */
2646 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2647 	    !as_app_validate_utf8 (update_contact)) {
2648 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2649 		return;
2650 	}
2651 
2652 	/* copy as-is */
2653 	as_ref_string_assign_safe (&priv->update_contact, update_contact);
2654 	if (priv->update_contact == NULL)
2655 		return;
2656 
2657 	/* keep going until we have no more matches */
2658 	len = (guint) strlen (priv->update_contact);
2659 	while (done_replacement) {
2660 		done_replacement = FALSE;
2661 		for (i = 0; replacements[i].search != NULL; i++) {
2662 			tmp = g_strstr_len (priv->update_contact, -1,
2663 					    replacements[i].search);
2664 			if (tmp != NULL) {
2665 				*tmp = replacements[i].replace;
2666 				g_strlcpy (tmp + 1,
2667 					   tmp + strlen (replacements[i].search),
2668 					   len);
2669 				done_replacement = TRUE;
2670 			}
2671 		}
2672 	}
2673 }
2674 
2675 /**
2676  * as_app_set_origin:
2677  * @app: a #AsApp instance.
2678  * @origin: the origin, e.g. "fedora-21"
2679  *
2680  * Sets the application origin.
2681  *
2682  * Since: 0.3.2
2683  **/
2684 void
as_app_set_origin(AsApp * app,const gchar * origin)2685 as_app_set_origin (AsApp *app, const gchar *origin)
2686 {
2687 	AsAppPrivate *priv = GET_PRIVATE (app);
2688 	as_ref_string_assign_safe (&priv->origin, origin);
2689 	priv->unique_id_valid = FALSE;
2690 }
2691 
2692 void
as_app_set_icon_path_rstr(AsApp * app,AsRefString * rstr)2693 as_app_set_icon_path_rstr (AsApp *app, AsRefString *rstr)
2694 {
2695 	AsAppPrivate *priv = GET_PRIVATE (app);
2696 	as_ref_string_assign (&priv->icon_path, rstr);
2697 }
2698 
2699 void
as_app_set_origin_rstr(AsApp * app,AsRefString * rstr)2700 as_app_set_origin_rstr (AsApp *app, AsRefString *rstr)
2701 {
2702 	AsAppPrivate *priv = GET_PRIVATE (app);
2703 	as_ref_string_assign (&priv->origin, rstr);
2704 }
2705 
2706 /**
2707  * as_app_set_icon_path:
2708  * @app: a #AsApp instance.
2709  * @icon_path: the local path.
2710  *
2711  * Sets the icon path, where local icons would be found.
2712  *
2713  * Since: 0.1.0
2714  **/
2715 void
as_app_set_icon_path(AsApp * app,const gchar * icon_path)2716 as_app_set_icon_path (AsApp *app, const gchar *icon_path)
2717 {
2718 	AsAppPrivate *priv = GET_PRIVATE (app);
2719 
2720 	/* handle untrusted */
2721 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2722 	    !as_app_validate_utf8 (icon_path)) {
2723 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2724 		return;
2725 	}
2726 
2727 	as_ref_string_assign_safe (&priv->icon_path, icon_path);
2728 }
2729 
2730 /**
2731  * as_app_set_name:
2732  * @app: a #AsApp instance.
2733  * @locale: (nullable): the locale. e.g. "en_GB"
2734  * @name: the application name.
2735  *
2736  * Sets the application name for a specific locale.
2737  *
2738  * Since: 0.1.0
2739  **/
2740 void
as_app_set_name(AsApp * app,const gchar * locale,const gchar * name)2741 as_app_set_name (AsApp *app,
2742 		 const gchar *locale,
2743 		 const gchar *name)
2744 {
2745 	AsAppPrivate *priv = GET_PRIVATE (app);
2746 	g_autoptr(AsRefString) locale_fixed = NULL;
2747 
2748 	/* handle untrusted */
2749 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2750 	    !as_app_validate_utf8 (name)) {
2751 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2752 		return;
2753 	}
2754 
2755 	/* get fixed locale */
2756 	locale_fixed = as_node_fix_locale (locale);
2757 	if (locale_fixed == NULL)
2758 		return;
2759 	g_hash_table_insert (priv->names,
2760 			     as_ref_string_ref (locale_fixed),
2761 			     as_ref_string_new (name));
2762 }
2763 
2764 /**
2765  * as_app_set_comment:
2766  * @app: a #AsApp instance.
2767  * @locale: (nullable): the locale. e.g. "en_GB"
2768  * @comment: the application summary.
2769  *
2770  * Sets the application summary for a specific locale.
2771  *
2772  * Since: 0.1.0
2773  **/
2774 void
as_app_set_comment(AsApp * app,const gchar * locale,const gchar * comment)2775 as_app_set_comment (AsApp *app,
2776 		    const gchar *locale,
2777 		    const gchar *comment)
2778 {
2779 	AsAppPrivate *priv = GET_PRIVATE (app);
2780 	g_autoptr(AsRefString) locale_fixed = NULL;
2781 
2782 	g_return_if_fail (comment != NULL);
2783 
2784 	/* handle untrusted */
2785 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2786 	    !as_app_validate_utf8 (comment)) {
2787 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2788 		return;
2789 	}
2790 
2791 	/* get fixed locale */
2792 	locale_fixed = as_node_fix_locale (locale);
2793 	if (locale_fixed == NULL)
2794 		return;
2795 	g_hash_table_insert (priv->comments,
2796 			     as_ref_string_ref (locale_fixed),
2797 			     as_ref_string_new (comment));
2798 }
2799 
2800 /**
2801  * as_app_set_developer_name:
2802  * @app: a #AsApp instance.
2803  * @locale: (nullable): the locale. e.g. "en_GB"
2804  * @developer_name: the application developer name.
2805  *
2806  * Sets the application developer name for a specific locale.
2807  *
2808  * Since: 0.1.0
2809  **/
2810 void
as_app_set_developer_name(AsApp * app,const gchar * locale,const gchar * developer_name)2811 as_app_set_developer_name (AsApp *app,
2812 			   const gchar *locale,
2813 			   const gchar *developer_name)
2814 {
2815 	AsAppPrivate *priv = GET_PRIVATE (app);
2816 	g_autoptr(AsRefString) locale_fixed = NULL;
2817 
2818 	g_return_if_fail (developer_name != NULL);
2819 
2820 	/* handle untrusted */
2821 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2822 	    !as_app_validate_utf8 (developer_name)) {
2823 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2824 		return;
2825 	}
2826 
2827 	/* get fixed locale */
2828 	locale_fixed = as_node_fix_locale (locale);
2829 	if (locale_fixed == NULL)
2830 		return;
2831 	g_hash_table_insert (priv->developer_names,
2832 			     as_ref_string_ref (locale_fixed),
2833 			     as_ref_string_new (developer_name));
2834 }
2835 
2836 /**
2837  * as_app_set_description:
2838  * @app: a #AsApp instance.
2839  * @locale: (nullable): the locale. e.g. "en_GB"
2840  * @description: the application description.
2841  *
2842  * Sets the application descrption markup for a specific locale.
2843  *
2844  * Since: 0.1.0
2845  **/
2846 void
as_app_set_description(AsApp * app,const gchar * locale,const gchar * description)2847 as_app_set_description (AsApp *app,
2848 			const gchar *locale,
2849 			const gchar *description)
2850 {
2851 	AsAppPrivate *priv = GET_PRIVATE (app);
2852 	g_autoptr(AsRefString) locale_fixed = NULL;
2853 
2854 	g_return_if_fail (description != NULL);
2855 
2856 	/* handle untrusted */
2857 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2858 	    !as_app_validate_utf8 (description)) {
2859 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2860 		return;
2861 	}
2862 
2863 	/* get fixed locale */
2864 	locale_fixed = as_node_fix_locale (locale);
2865 	if (locale_fixed == NULL)
2866 		return;
2867 	g_hash_table_insert (priv->descriptions,
2868 			     as_ref_string_ref (locale_fixed),
2869 			     as_ref_string_new (description));
2870 }
2871 
2872 /**
2873  * as_app_set_priority:
2874  * @app: a #AsApp instance.
2875  * @priority: the priority.
2876  *
2877  * Sets the application priority, where 0 is default and positive numbers
2878  * are better than negative numbers.
2879  *
2880  * Since: 0.1.0
2881  **/
2882 void
as_app_set_priority(AsApp * app,gint priority)2883 as_app_set_priority (AsApp *app, gint priority)
2884 {
2885 	AsAppPrivate *priv = GET_PRIVATE (app);
2886 	priv->priority = priority;
2887 }
2888 
2889 /**
2890  * as_app_add_category:
2891  * @app: a #AsApp instance.
2892  * @category: the category.
2893  *
2894  * Adds a menu category to the application.
2895  *
2896  * Since: 0.1.0
2897  **/
2898 void
as_app_add_category(AsApp * app,const gchar * category)2899 as_app_add_category (AsApp *app, const gchar *category)
2900 {
2901 	AsAppPrivate *priv = GET_PRIVATE (app);
2902 
2903 	g_return_if_fail (category != NULL);
2904 
2905 	/* handle untrusted */
2906 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2907 	    !as_app_validate_utf8 (category)) {
2908 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2909 		return;
2910 	}
2911 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 &&
2912 	    as_ptr_array_find_string (priv->categories, category)) {
2913 		return;
2914 	}
2915 
2916 	g_ptr_array_add (priv->categories, as_ref_string_new (category));
2917 }
2918 
2919 /**
2920  * as_app_remove_category:
2921  * @app: a #AsApp instance.
2922  * @category: the category.
2923  *
2924  * Removed a menu category from the application.
2925  *
2926  * Since: 0.6.13
2927  **/
2928 void
as_app_remove_category(AsApp * app,const gchar * category)2929 as_app_remove_category (AsApp *app, const gchar *category)
2930 {
2931 	AsAppPrivate *priv = GET_PRIVATE (app);
2932 	for (guint i = 0; i < priv->categories->len; i++) {
2933 		const gchar *tmp = g_ptr_array_index (priv->categories, i);
2934 		if (g_strcmp0 (tmp, category) == 0) {
2935 			g_ptr_array_remove (priv->categories, (gpointer) tmp);
2936 			break;
2937 		}
2938 	}
2939 }
2940 
2941 /**
2942  * as_app_add_compulsory_for_desktop:
2943  * @app: a #AsApp instance.
2944  * @compulsory_for_desktop: the desktop string, e.g. "GNOME".
2945  *
2946  * Adds a desktop that requires this application to be installed.
2947  *
2948  * Since: 0.1.0
2949  **/
2950 void
as_app_add_compulsory_for_desktop(AsApp * app,const gchar * compulsory_for_desktop)2951 as_app_add_compulsory_for_desktop (AsApp *app, const gchar *compulsory_for_desktop)
2952 {
2953 	AsAppPrivate *priv = GET_PRIVATE (app);
2954 
2955 	g_return_if_fail (compulsory_for_desktop != NULL);
2956 
2957 	/* handle untrusted */
2958 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
2959 	    !as_app_validate_utf8 (compulsory_for_desktop)) {
2960 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
2961 		return;
2962 	}
2963 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 &&
2964 	    as_ptr_array_find_string (priv->compulsory_for_desktops,
2965 				      compulsory_for_desktop)) {
2966 		return;
2967 	}
2968 
2969 	g_ptr_array_add (priv->compulsory_for_desktops,
2970 			 as_ref_string_new (compulsory_for_desktop));
2971 }
2972 
2973 static void
as_app_add_keyword_rstr(AsApp * app,AsRefString * locale,AsRefString * keyword)2974 as_app_add_keyword_rstr (AsApp *app, AsRefString *locale, AsRefString *keyword)
2975 {
2976 	AsAppPrivate *priv = GET_PRIVATE (app);
2977 	GPtrArray *tmp;
2978 
2979 	/* create an array if required */
2980 	tmp = g_hash_table_lookup (priv->keywords, locale);
2981 	if (tmp == NULL) {
2982 		tmp = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
2983 		g_hash_table_insert (priv->keywords, as_ref_string_ref (locale), tmp);
2984 	} else if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) {
2985 		if (as_ptr_array_find_string (tmp, keyword))
2986 			return;
2987 	}
2988 	g_ptr_array_add (tmp, as_ref_string_ref (keyword));
2989 
2990 	/* cache already populated */
2991 	if (priv->token_cache_valid) {
2992 		g_warning ("%s has token cache, invaliding as %s was added",
2993 			   as_app_get_unique_id (app), keyword);
2994 		g_hash_table_remove_all (priv->token_cache);
2995 		priv->token_cache_valid = FALSE;
2996 	}
2997 }
2998 
2999 /**
3000  * as_app_add_keyword:
3001  * @app: a #AsApp instance.
3002  * @locale: (nullable): the locale. e.g. "en_GB"
3003  * @keyword: the keyword.
3004  *
3005  * Add a keyword the application should match against.
3006  *
3007  * Since: 0.3.0
3008  **/
3009 void
as_app_add_keyword(AsApp * app,const gchar * locale,const gchar * keyword)3010 as_app_add_keyword (AsApp *app,
3011 		    const gchar *locale,
3012 		    const gchar *keyword)
3013 {
3014 	AsAppPrivate *priv = GET_PRIVATE (app);
3015 	g_autoptr(AsRefString) locale_fixed = NULL;
3016 	g_autoptr(AsRefString) keyword_rstr = NULL;
3017 
3018 	g_return_if_fail (keyword != NULL);
3019 
3020 	/* handle untrusted */
3021 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3022 	    !as_app_validate_utf8 (keyword)) {
3023 		return;
3024 	}
3025 
3026 	/* get fixed locale */
3027 	locale_fixed = as_node_fix_locale (locale);
3028 	if (locale_fixed == NULL)
3029 		return;
3030 
3031 	/* add */
3032 	keyword_rstr = as_ref_string_new (keyword);
3033 	as_app_add_keyword_rstr (app, locale_fixed, keyword_rstr);
3034 }
3035 
3036 /**
3037  * as_app_add_kudo:
3038  * @app: a #AsApp instance.
3039  * @kudo: the kudo.
3040  *
3041  * Add a kudo the application has obtained.
3042  *
3043  * Since: 0.2.2
3044  **/
3045 void
as_app_add_kudo(AsApp * app,const gchar * kudo)3046 as_app_add_kudo (AsApp *app, const gchar *kudo)
3047 {
3048 	AsAppPrivate *priv = GET_PRIVATE (app);
3049 
3050 	g_return_if_fail (kudo != NULL);
3051 
3052 	/* handle untrusted */
3053 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3054 	    !as_app_validate_utf8 (kudo)) {
3055 		return;
3056 	}
3057 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 &&
3058 	    as_ptr_array_find_string (priv->kudos, kudo)) {
3059 		return;
3060 	}
3061 	g_ptr_array_add (priv->kudos, as_ref_string_new (kudo));
3062 }
3063 
3064 /**
3065  * as_app_remove_kudo:
3066  * @app: a #AsApp instance.
3067  * @kudo: the kudo.
3068  *
3069  * Remove a kudo the application has obtained.
3070  *
3071  * Since: 0.6.13
3072  **/
3073 void
as_app_remove_kudo(AsApp * app,const gchar * kudo)3074 as_app_remove_kudo (AsApp *app, const gchar *kudo)
3075 {
3076 	AsAppPrivate *priv = GET_PRIVATE (app);
3077 	for (guint i = 0; i < priv->kudos->len; i++) {
3078 		const gchar *tmp = g_ptr_array_index (priv->kudos, i);
3079 		if (g_strcmp0 (tmp, kudo) == 0) {
3080 			g_ptr_array_remove (priv->kudos, (gpointer) tmp);
3081 			break;
3082 		}
3083 	}
3084 }
3085 
3086 /**
3087  * as_app_add_permission:
3088  * @app: a #AsApp instance.
3089  * @permission: the permission.
3090  *
3091  * Add a permission the application has obtained.
3092  *
3093  * Since: 0.3.5
3094  **/
3095 void
as_app_add_permission(AsApp * app,const gchar * permission)3096 as_app_add_permission (AsApp *app, const gchar *permission)
3097 {
3098 	AsAppPrivate *priv = GET_PRIVATE (app);
3099 
3100 	g_return_if_fail (permission != NULL);
3101 
3102 	/* handle untrusted */
3103 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3104 	    !as_app_validate_utf8 (permission)) {
3105 		return;
3106 	}
3107 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 &&
3108 	    as_ptr_array_find_string (priv->permissions, permission)) {
3109 		return;
3110 	}
3111 	g_ptr_array_add (priv->permissions, as_ref_string_new (permission));
3112 }
3113 
3114 static void
as_app_recalculate_state(AsApp * app)3115 as_app_recalculate_state (AsApp *app)
3116 {
3117 	AsAppPrivate *priv = GET_PRIVATE (app);
3118 	gboolean is_installed = FALSE;
3119 	gboolean is_available = FALSE;
3120 	for (guint i = 0; i < priv->formats->len; i++) {
3121 		AsFormat *format = g_ptr_array_index (priv->formats, i);
3122 		switch (as_format_get_kind (format)) {
3123 		case AS_FORMAT_KIND_APPDATA:
3124 		case AS_FORMAT_KIND_DESKTOP:
3125 		case AS_FORMAT_KIND_METAINFO:
3126 			is_installed = TRUE;
3127 			break;
3128 		case AS_FORMAT_KIND_APPSTREAM:
3129 			is_available = TRUE;
3130 			break;
3131 		default:
3132 			break;
3133 		}
3134 	}
3135 	if (is_installed) {
3136 		as_app_set_state (app, AS_APP_STATE_INSTALLED);
3137 		return;
3138 	}
3139 	if (is_available) {
3140 		as_app_set_state (app, AS_APP_STATE_AVAILABLE);
3141 		return;
3142 	}
3143 	as_app_set_state (app, AS_APP_STATE_UNKNOWN);
3144 }
3145 
3146 /**
3147  * as_app_add_format:
3148  * @app: a #AsApp instance.
3149  * @format: the #AsFormat.
3150  *
3151  * Add a format the application has been built from.
3152  *
3153  * Since: 0.6.9
3154  **/
3155 void
as_app_add_format(AsApp * app,AsFormat * format)3156 as_app_add_format (AsApp *app, AsFormat *format)
3157 {
3158 	AsAppPrivate *priv = GET_PRIVATE (app);
3159 	g_return_if_fail (AS_IS_APP (app));
3160 	g_return_if_fail (AS_IS_FORMAT (format));
3161 
3162 	/* check for duplicates */
3163 	for (guint i = 0; i < priv->formats->len; i++) {
3164 		AsFormat *fmt = g_ptr_array_index (priv->formats, i);
3165 		if (as_format_equal (fmt, format))
3166 			return;
3167 	}
3168 
3169 	/* add */
3170 	g_ptr_array_add (priv->formats, g_object_ref (format));
3171 	as_app_recalculate_state (app);
3172 }
3173 
3174 /**
3175  * as_app_remove_format:
3176  * @app: a #AsApp instance.
3177  * @format: the #AsFormat.
3178  *
3179  * Removes a format the application has been built from.
3180  *
3181  * Since: 0.6.9
3182  **/
3183 void
as_app_remove_format(AsApp * app,AsFormat * format)3184 as_app_remove_format (AsApp *app, AsFormat *format)
3185 {
3186 	AsAppPrivate *priv = GET_PRIVATE (app);
3187 	g_return_if_fail (AS_IS_APP (app));
3188 	g_return_if_fail (AS_IS_FORMAT (format));
3189 	g_ptr_array_remove (priv->formats, format);
3190 	as_app_recalculate_state (app);
3191 }
3192 
3193 /**
3194  * as_app_add_kudo_kind:
3195  * @app: a #AsApp instance.
3196  * @kudo_kind: the #AsKudoKind.
3197  *
3198  * Add a kudo the application has obtained.
3199  *
3200  * Since: 0.2.2
3201  **/
3202 void
as_app_add_kudo_kind(AsApp * app,AsKudoKind kudo_kind)3203 as_app_add_kudo_kind (AsApp *app, AsKudoKind kudo_kind)
3204 {
3205 	AsAppPrivate *priv = GET_PRIVATE (app);
3206 	g_ptr_array_add (priv->kudos, (AsRefString *) as_kudo_kind_to_string (kudo_kind));
3207 }
3208 
3209 /**
3210  * as_app_add_mimetype:
3211  * @app: a #AsApp instance.
3212  * @mimetype: the mimetype.
3213  *
3214  * Adds a mimetype the application can process.
3215  *
3216  * Since: 0.1.0
3217  **/
3218 void
as_app_add_mimetype(AsApp * app,const gchar * mimetype)3219 as_app_add_mimetype (AsApp *app, const gchar *mimetype)
3220 {
3221 	AsAppPrivate *priv = GET_PRIVATE (app);
3222 
3223 	g_return_if_fail (mimetype != NULL);
3224 
3225 	/* handle untrusted */
3226 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3227 	    !as_app_validate_utf8 (mimetype)) {
3228 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
3229 		return;
3230 	}
3231 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 &&
3232 	    as_ptr_array_find_string (priv->mimetypes, mimetype)) {
3233 		return;
3234 	}
3235 
3236 	g_ptr_array_add (priv->mimetypes, as_ref_string_new (mimetype));
3237 }
3238 
3239 static void
as_app_subsume_release(AsRelease * release,AsRelease * donor)3240 as_app_subsume_release (AsRelease *release, AsRelease *donor)
3241 {
3242 	AsChecksum *csum;
3243 	AsChecksum *csum_tmp;
3244 	GPtrArray *locations;
3245 	GPtrArray *checksums;
3246 	const gchar *tmp;
3247 	guint i;
3248 
3249 	/* this is high quality metadata */
3250 	tmp = as_release_get_description (donor, NULL);
3251 	if (tmp != NULL)
3252 		as_release_set_description (release, NULL, tmp);
3253 
3254 	/* only installed is useful */
3255 	if (as_release_get_state (donor) == AS_RELEASE_STATE_INSTALLED)
3256 		as_release_set_state (release, AS_RELEASE_STATE_INSTALLED);
3257 
3258 	/* overwrite the timestamp if the metadata is high quality,
3259 	 * or if no timestamp has already been set */
3260 	if (tmp != NULL || as_release_get_timestamp (release) == 0)
3261 		as_release_set_timestamp (release, as_release_get_timestamp (donor));
3262 
3263 	/* overwrite the version */
3264 	tmp = as_release_get_version (donor);
3265 	if (tmp != NULL && as_release_get_version (release) == NULL)
3266 		as_release_set_version (release, tmp);
3267 
3268 	/* copy all locations */
3269 	locations = as_release_get_locations (donor);
3270 	for (i = 0; i < locations->len; i++) {
3271 		tmp = g_ptr_array_index (locations, i);
3272 		as_release_add_location (release, tmp);
3273 	}
3274 
3275 	/* copy checksums if set */
3276 	checksums = as_release_get_checksums (donor);
3277 	for (i = 0; i < checksums->len; i++) {
3278 		csum = g_ptr_array_index (checksums, i);
3279 		tmp = as_checksum_get_filename (csum);
3280 		csum_tmp = as_release_get_checksum_by_fn (release, tmp);
3281 		if (csum_tmp != NULL)
3282 			continue;
3283 		as_release_add_checksum (release, csum);
3284 	}
3285 }
3286 
3287 /**
3288  * as_app_add_release:
3289  * @app: a #AsApp instance.
3290  * @release: a #AsRelease instance.
3291  *
3292  * Adds a release to an application.
3293  *
3294  * Since: 0.1.0
3295  **/
3296 void
as_app_add_release(AsApp * app,AsRelease * release)3297 as_app_add_release (AsApp *app, AsRelease *release)
3298 {
3299 	AsAppPrivate *priv = GET_PRIVATE (app);
3300 	AsRelease *release_old;
3301 
3302 	/* if already exists them update */
3303 	release_old = as_app_get_release (app, as_release_get_version (release));
3304 	if (release_old == NULL)
3305 		release_old = as_app_get_release (app, NULL);
3306 	if (release_old == release)
3307 		return;
3308 	if (release_old != NULL) {
3309 		priv->problems |= AS_APP_PROBLEM_DUPLICATE_RELEASE;
3310 		as_app_subsume_release (release_old, release);
3311 		return;
3312 	}
3313 
3314 	g_ptr_array_add (priv->releases, g_object_ref (release));
3315 }
3316 
3317 /**
3318  * as_app_add_provide:
3319  * @app: a #AsApp instance.
3320  * @provide: a #AsProvide instance.
3321  *
3322  * Adds a provide to an application.
3323  *
3324  * Since: 0.1.6
3325  **/
3326 void
as_app_add_provide(AsApp * app,AsProvide * provide)3327 as_app_add_provide (AsApp *app, AsProvide *provide)
3328 {
3329 	AsAppPrivate *priv = GET_PRIVATE (app);
3330 	AsProvide *tmp;
3331 	guint i;
3332 
3333 	/* check for duplicates */
3334 	if (priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) {
3335 		for (i = 0; i < priv->provides->len; i++) {
3336 			tmp = g_ptr_array_index (priv->provides, i);
3337 			if (as_provide_get_kind (tmp) == as_provide_get_kind (provide) &&
3338 			    g_strcmp0 (as_provide_get_value (tmp),
3339 				       as_provide_get_value (provide)) == 0)
3340 				return;
3341 		}
3342 	}
3343 
3344 	g_ptr_array_add (priv->provides, g_object_ref (provide));
3345 }
3346 
3347 /**
3348  * as_app_add_launchable:
3349  * @app: a #AsApp instance.
3350  * @launchable: a #AsLaunchable instance.
3351  *
3352  * Adds a launchable to an application.
3353  *
3354  * Since: 0.6.13
3355  **/
3356 void
as_app_add_launchable(AsApp * app,AsLaunchable * launchable)3357 as_app_add_launchable (AsApp *app, AsLaunchable *launchable)
3358 {
3359 	AsAppPrivate *priv = GET_PRIVATE (app);
3360 
3361 	/* check for duplicates */
3362 	if (priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) {
3363 		for (guint i = 0; i < priv->launchables->len; i++) {
3364 			AsLaunchable *lau = g_ptr_array_index (priv->launchables, i);
3365 			if (as_launchable_get_kind (lau) == as_launchable_get_kind (launchable) &&
3366 			    g_strcmp0 (as_launchable_get_value (lau),
3367 				       as_launchable_get_value (launchable)) == 0)
3368 				return;
3369 		}
3370 	}
3371 
3372 	g_ptr_array_add (priv->launchables, g_object_ref (launchable));
3373 }
3374 
3375 static gint
as_app_sort_screenshots(gconstpointer a,gconstpointer b)3376 as_app_sort_screenshots (gconstpointer a, gconstpointer b)
3377 {
3378 	AsScreenshot *ss1 = *((AsScreenshot **) a);
3379 	AsScreenshot *ss2 = *((AsScreenshot **) b);
3380 	if (as_screenshot_get_kind (ss1) < as_screenshot_get_kind (ss2))
3381 		return 1;
3382 	if (as_screenshot_get_kind (ss1) > as_screenshot_get_kind (ss2))
3383 		return -1;
3384 	if (as_screenshot_get_priority (ss1) < as_screenshot_get_priority (ss2))
3385 		return 1;
3386 	if (as_screenshot_get_priority (ss1) > as_screenshot_get_priority (ss2))
3387 		return -1;
3388 	return g_strcmp0 (as_screenshot_get_caption (ss1, NULL),
3389 			  as_screenshot_get_caption (ss2, NULL));
3390 }
3391 
3392 /**
3393  * as_app_add_screenshot:
3394  * @app: a #AsApp instance.
3395  * @screenshot: a #AsScreenshot instance.
3396  *
3397  * Adds a screenshot to an application.
3398  *
3399  * Since: 0.1.0
3400  **/
3401 void
as_app_add_screenshot(AsApp * app,AsScreenshot * screenshot)3402 as_app_add_screenshot (AsApp *app, AsScreenshot *screenshot)
3403 {
3404 	AsAppPrivate *priv = GET_PRIVATE (app);
3405 	AsScreenshot *ss;
3406 	guint i;
3407 
3408 	/* handle untrusted */
3409 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) {
3410 		for (i = 0; i < priv->screenshots->len; i++) {
3411 			ss = g_ptr_array_index (priv->screenshots, i);
3412 			if (as_screenshot_equal (ss, screenshot)) {
3413 				priv->problems |= AS_APP_PROBLEM_DUPLICATE_SCREENSHOT;
3414 				return;
3415 			}
3416 		}
3417 	}
3418 
3419 	/* add then resort */
3420 	g_ptr_array_add (priv->screenshots, g_object_ref (screenshot));
3421 	g_ptr_array_sort (priv->screenshots, as_app_sort_screenshots);
3422 
3423 	/* make only the first screenshot default */
3424 	for (i = 0; i < priv->screenshots->len; i++) {
3425 		ss = g_ptr_array_index (priv->screenshots, i);
3426 		as_screenshot_set_kind (ss, i == 0 ? AS_SCREENSHOT_KIND_DEFAULT :
3427 						     AS_SCREENSHOT_KIND_NORMAL);
3428 	}
3429 }
3430 
3431 /**
3432  * as_app_add_review:
3433  * @app: a #AsApp instance.
3434  * @review: a #AsReview instance.
3435  *
3436  * Adds a review to an application.
3437  *
3438  * Since: 0.6.1
3439  **/
3440 void
as_app_add_review(AsApp * app,AsReview * review)3441 as_app_add_review (AsApp *app, AsReview *review)
3442 {
3443 	AsAppPrivate *priv = GET_PRIVATE (app);
3444 	AsReview *review_tmp;
3445 	guint i;
3446 
3447 	/* handle untrusted */
3448 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) {
3449 		for (i = 0; i < priv->reviews->len; i++) {
3450 			review_tmp = g_ptr_array_index (priv->reviews, i);
3451 			if (as_review_equal (review_tmp, review))
3452 				return;
3453 		}
3454 	}
3455 	g_ptr_array_add (priv->reviews, g_object_ref (review));
3456 }
3457 
3458 /**
3459  * as_app_add_content_rating:
3460  * @app: a #AsApp instance.
3461  * @content_rating: a #AsContentRating instance.
3462  *
3463  * Adds a content_rating to an application.
3464  *
3465  * Since: 0.5.12
3466  **/
3467 void
as_app_add_content_rating(AsApp * app,AsContentRating * content_rating)3468 as_app_add_content_rating (AsApp *app, AsContentRating *content_rating)
3469 {
3470 	AsAppPrivate *priv = GET_PRIVATE (app);
3471 
3472 	/* handle untrusted */
3473 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) {
3474 		for (guint i = 0; i < priv->content_ratings->len; i++) {
3475 			AsContentRating *cr_tmp = g_ptr_array_index (priv->content_ratings, i);
3476 			if (g_strcmp0 (as_content_rating_get_kind (cr_tmp),
3477 				       as_content_rating_get_kind (content_rating)) == 0) {
3478 				priv->problems |= AS_APP_PROBLEM_DUPLICATE_CONTENT_RATING;
3479 				return;
3480 			}
3481 		}
3482 	}
3483 	g_ptr_array_add (priv->content_ratings, g_object_ref (content_rating));
3484 }
3485 
3486 /**
3487  * as_app_add_agreement:
3488  * @app: a #AsApp instance.
3489  * @agreement: a #AsAgreement instance.
3490  *
3491  * Adds a agreement to an application.
3492  *
3493  * Since: 0.7.8
3494  **/
3495 void
as_app_add_agreement(AsApp * app,AsAgreement * agreement)3496 as_app_add_agreement (AsApp *app, AsAgreement *agreement)
3497 {
3498 	AsAppPrivate *priv = GET_PRIVATE (app);
3499 
3500 	/* handle untrusted */
3501 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) {
3502 		for (guint i = 0; i < priv->agreements->len; i++) {
3503 			AsAgreement *cr_tmp = g_ptr_array_index (priv->agreements, i);
3504 			if (as_agreement_get_kind (cr_tmp) == as_agreement_get_kind (agreement)) {
3505 				priv->problems |= AS_APP_PROBLEM_DUPLICATE_AGREEMENT;
3506 				return;
3507 			}
3508 		}
3509 	}
3510 	g_ptr_array_add (priv->agreements, g_object_ref (agreement));
3511 }
3512 
3513 static gboolean
as_app_check_icon_duplicate(AsIcon * icon1,AsIcon * icon2)3514 as_app_check_icon_duplicate (AsIcon *icon1, AsIcon *icon2)
3515 {
3516 	if (as_icon_get_width (icon1) != as_icon_get_width (icon2))
3517 		return FALSE;
3518 	if (as_icon_get_height (icon1) != as_icon_get_height (icon2))
3519 		return FALSE;
3520 	if (g_strcmp0 (as_icon_get_name (icon1),
3521 		       as_icon_get_name (icon2)) != 0)
3522 		return FALSE;
3523 	return TRUE;
3524 }
3525 
3526 static gboolean
as_app_check_bundle_duplicate(AsBundle * bundle1,AsBundle * bundle2)3527 as_app_check_bundle_duplicate (AsBundle *bundle1, AsBundle *bundle2)
3528 {
3529 	if (as_bundle_get_kind (bundle1) != as_bundle_get_kind (bundle2))
3530 		return FALSE;
3531 	if (g_strcmp0 (as_bundle_get_id (bundle1),
3532 		       as_bundle_get_id (bundle2)) != 0)
3533 		return FALSE;
3534 	return TRUE;
3535 }
3536 
3537 static gboolean
as_app_check_translation_duplicate(AsTranslation * translation1,AsTranslation * translation2)3538 as_app_check_translation_duplicate (AsTranslation *translation1, AsTranslation *translation2)
3539 {
3540 	if (as_translation_get_kind (translation1) != as_translation_get_kind (translation2))
3541 		return FALSE;
3542 	if (g_strcmp0 (as_translation_get_id (translation1),
3543 		       as_translation_get_id (translation2)) != 0)
3544 		return FALSE;
3545 	return TRUE;
3546 }
3547 
3548 /**
3549  * as_app_add_icon:
3550  * @app: a #AsApp instance.
3551  * @icon: a #AsIcon instance.
3552  *
3553  * Adds a icon to an application.
3554  *
3555  * Since: 0.3.1
3556  **/
3557 void
as_app_add_icon(AsApp * app,AsIcon * icon)3558 as_app_add_icon (AsApp *app, AsIcon *icon)
3559 {
3560 	AsAppPrivate *priv = GET_PRIVATE (app);
3561 
3562 	/* handle untrusted */
3563 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) {
3564 		AsIcon *ic_tmp;
3565 		guint i;
3566 		for (i = 0; i < priv->icons->len; i++) {
3567 			ic_tmp = g_ptr_array_index (priv->icons, i);
3568 			if (as_app_check_icon_duplicate (icon, ic_tmp))
3569 				return;
3570 		}
3571 	}
3572 
3573 	/* assume that desktop stock icons are available in HiDPI sizes */
3574 	if (as_icon_get_kind (icon) == AS_ICON_KIND_STOCK) {
3575 		switch (priv->kind) {
3576 		case AS_APP_KIND_DESKTOP:
3577 			as_app_add_kudo_kind (app, AS_KUDO_KIND_HI_DPI_ICON);
3578 			break;
3579 		default:
3580 			break;
3581 		}
3582 	}
3583 	g_ptr_array_add (priv->icons, g_object_ref (icon));
3584 }
3585 
3586 static void
as_app_parse_flatpak_id(AsApp * app,const gchar * bundle_id)3587 as_app_parse_flatpak_id (AsApp *app, const gchar *bundle_id)
3588 {
3589 	AsAppPrivate *priv = GET_PRIVATE (app);
3590 	g_auto(GStrv) split = NULL;
3591 
3592 	/* not set */
3593 	if (bundle_id == NULL)
3594 		return;
3595 
3596 	/* split into type/id/arch/branch */
3597 	split = g_strsplit (bundle_id, "/", -1);
3598 	if (g_strv_length (split) != 4) {
3599 		g_warning ("invalid flatpak bundle ID: %s", bundle_id);
3600 		return;
3601 	}
3602 
3603 	/* only set if not already set */
3604 	if (priv->architectures->len == 0)
3605 		as_app_add_arch (app, split[2]);
3606 	if (priv->branch == NULL)
3607 		as_app_set_branch (app, split[3]);
3608 }
3609 
3610 /**
3611  * as_app_add_bundle:
3612  * @app: a #AsApp instance.
3613  * @bundle: a #AsBundle instance.
3614  *
3615  * Adds a bundle to an application.
3616  *
3617  * Since: 0.3.5
3618  **/
3619 void
as_app_add_bundle(AsApp * app,AsBundle * bundle)3620 as_app_add_bundle (AsApp *app, AsBundle *bundle)
3621 {
3622 	AsAppPrivate *priv = GET_PRIVATE (app);
3623 
3624 	/* handle untrusted */
3625 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) {
3626 		AsBundle *bu_tmp;
3627 		guint i;
3628 		for (i = 0; i < priv->bundles->len; i++) {
3629 			bu_tmp = g_ptr_array_index (priv->bundles, i);
3630 			if (as_app_check_bundle_duplicate (bundle, bu_tmp))
3631 				return;
3632 		}
3633 	}
3634 
3635 	/* set the architecture and branch */
3636 	switch (as_bundle_get_kind (bundle)) {
3637 	case AS_BUNDLE_KIND_FLATPAK:
3638 		as_app_parse_flatpak_id (app, as_bundle_get_id (bundle));
3639 		break;
3640 	default:
3641 		break;
3642 	}
3643 
3644 	g_ptr_array_add (priv->bundles, g_object_ref (bundle));
3645 
3646 	/* no longer valid */
3647 	priv->unique_id_valid = FALSE;
3648 }
3649 
3650 /**
3651  * as_app_add_translation:
3652  * @app: a #AsApp instance.
3653  * @translation: a #AsTranslation instance.
3654  *
3655  * Adds a translation to an application.
3656  *
3657  * Since: 0.5.8
3658  **/
3659 void
as_app_add_translation(AsApp * app,AsTranslation * translation)3660 as_app_add_translation (AsApp *app, AsTranslation *translation)
3661 {
3662 	AsAppPrivate *priv = GET_PRIVATE (app);
3663 
3664 	/* handle untrusted */
3665 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) {
3666 		AsTranslation *bu_tmp;
3667 		guint i;
3668 		for (i = 0; i < priv->translations->len; i++) {
3669 			bu_tmp = g_ptr_array_index (priv->translations, i);
3670 			if (as_app_check_translation_duplicate (translation, bu_tmp))
3671 				return;
3672 		}
3673 	}
3674 	g_ptr_array_add (priv->translations, g_object_ref (translation));
3675 }
3676 
3677 /**
3678  * as_app_add_suggest:
3679  * @app: a #AsApp instance.
3680  * @suggest: a #AsSuggest instance.
3681  *
3682  * Adds a suggest to an application.
3683  *
3684  * Since: 0.6.1
3685  **/
3686 void
as_app_add_suggest(AsApp * app,AsSuggest * suggest)3687 as_app_add_suggest (AsApp *app, AsSuggest *suggest)
3688 {
3689 	AsAppPrivate *priv = GET_PRIVATE (app);
3690 	g_ptr_array_add (priv->suggests, g_object_ref (suggest));
3691 }
3692 
3693 /**
3694  * as_app_add_require:
3695  * @app: a #AsApp instance.
3696  * @require: a #AsRequire instance.
3697  *
3698  * Adds a require to an application.
3699  *
3700  * Since: 0.6.7
3701  **/
3702 void
as_app_add_require(AsApp * app,AsRequire * require)3703 as_app_add_require (AsApp *app, AsRequire *require)
3704 {
3705 	AsAppPrivate *priv = GET_PRIVATE (app);
3706 
3707 	/* handle untrusted */
3708 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0) {
3709 		for (guint i = 0; i < priv->requires->len; i++) {
3710 			AsRequire *req_tmp = g_ptr_array_index (priv->requires, i);
3711 			if (as_require_equal (require, req_tmp))
3712 				return;
3713 		}
3714 	}
3715 	g_ptr_array_add (priv->requires, g_object_ref (require));
3716 }
3717 
3718 /**
3719  * as_app_add_pkgname:
3720  * @app: a #AsApp instance.
3721  * @pkgname: the package name.
3722  *
3723  * Adds a package name to an application.
3724  *
3725  * Since: 0.1.0
3726  **/
3727 void
as_app_add_pkgname(AsApp * app,const gchar * pkgname)3728 as_app_add_pkgname (AsApp *app, const gchar *pkgname)
3729 {
3730 	AsAppPrivate *priv = GET_PRIVATE (app);
3731 
3732 	g_return_if_fail (pkgname != NULL);
3733 
3734 	/* handle untrusted */
3735 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3736 	    !as_app_validate_utf8 (pkgname)) {
3737 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
3738 		return;
3739 	}
3740 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 &&
3741 	    as_ptr_array_find_string (priv->pkgnames, pkgname)) {
3742 		return;
3743 	}
3744 
3745 	g_ptr_array_add (priv->pkgnames, as_ref_string_new (pkgname));
3746 
3747 	/* no longer valid */
3748 	priv->unique_id_valid = FALSE;
3749 }
3750 
3751 /**
3752  * as_app_add_arch:
3753  * @app: a #AsApp instance.
3754  * @arch: the package name.
3755  *
3756  * Adds a package name to an application.
3757  *
3758  * Since: 0.1.1
3759  **/
3760 void
as_app_add_arch(AsApp * app,const gchar * arch)3761 as_app_add_arch (AsApp *app, const gchar *arch)
3762 {
3763 	AsAppPrivate *priv = GET_PRIVATE (app);
3764 
3765 	g_return_if_fail (arch != NULL);
3766 
3767 	/* handle untrusted */
3768 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3769 	    !as_app_validate_utf8 (arch)) {
3770 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
3771 		return;
3772 	}
3773 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 &&
3774 	    as_ptr_array_find_string (priv->architectures, arch)) {
3775 		return;
3776 	}
3777 
3778 	g_ptr_array_add (priv->architectures, as_ref_string_new (arch));
3779 }
3780 
3781 /**
3782  * as_app_add_language:
3783  * @app: a #AsApp instance.
3784  * @percentage: the percentage completion of the translation, or 0 for unknown
3785  * @locale: (nullable): the locale. e.g. "en_GB"
3786  *
3787  * Adds a language to the application.
3788  *
3789  * Since: 0.1.0
3790  **/
3791 void
as_app_add_language(AsApp * app,gint percentage,const gchar * locale)3792 as_app_add_language (AsApp *app,
3793 		     gint percentage,
3794 		     const gchar *locale)
3795 {
3796 	AsAppPrivate *priv = GET_PRIVATE (app);
3797 
3798 	/* handle untrusted */
3799 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3800 	    !as_app_validate_utf8 (locale)) {
3801 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
3802 		return;
3803 	}
3804 
3805 	if (locale == NULL)
3806 		locale = "C";
3807 	g_hash_table_insert (priv->languages,
3808 			     as_ref_string_new (locale),
3809 			     GINT_TO_POINTER (percentage));
3810 }
3811 
3812 /**
3813  * as_app_add_url:
3814  * @app: a #AsApp instance.
3815  * @url_kind: the URL kind, e.g. %AS_URL_KIND_HOMEPAGE
3816  * @url: the full URL.
3817  *
3818  * Adds some URL data to the application.
3819  *
3820  * Since: 0.1.0
3821  **/
3822 void
as_app_add_url(AsApp * app,AsUrlKind url_kind,const gchar * url)3823 as_app_add_url (AsApp *app,
3824 		AsUrlKind url_kind,
3825 		const gchar *url)
3826 {
3827 	AsAppPrivate *priv = GET_PRIVATE (app);
3828 
3829 	/* handle untrusted */
3830 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3831 	    !as_app_validate_utf8 (url)) {
3832 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
3833 		return;
3834 	}
3835 	if (url == NULL) {
3836 		g_hash_table_remove (priv->urls, as_url_kind_to_string (url_kind));
3837 	} else {
3838 		g_hash_table_insert (priv->urls,
3839 				     (AsRefString *) as_url_kind_to_string (url_kind),
3840 				     as_ref_string_new (url));
3841 	}
3842 }
3843 
3844 /**
3845  * as_app_add_metadata:
3846  * @app: a #AsApp instance.
3847  * @key: the metadata key.
3848  * @value: (nullable): the value to store.
3849  *
3850  * Adds a metadata entry to the application.
3851  *
3852  * Since: 0.1.0
3853  **/
3854 void
as_app_add_metadata(AsApp * app,const gchar * key,const gchar * value)3855 as_app_add_metadata (AsApp *app,
3856 		     const gchar *key,
3857 		     const gchar *value)
3858 {
3859 	AsAppPrivate *priv = GET_PRIVATE (app);
3860 	g_return_if_fail (key != NULL);
3861 
3862 	/* handle untrusted */
3863 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3864 	    !as_app_validate_utf8 (value)) {
3865 		return;
3866 	}
3867 
3868 	if (value == NULL)
3869 		value = "";
3870 	g_hash_table_insert (priv->metadata,
3871 			     as_ref_string_new (key),
3872 			     as_ref_string_new (value));
3873 }
3874 
3875 /**
3876  * as_app_remove_metadata:
3877  * @app: a #AsApp instance.
3878  * @key: the metadata key.
3879  *
3880  * Removes a metadata item from the application.
3881  *
3882  * Since: 0.1.0
3883  **/
3884 void
as_app_remove_metadata(AsApp * app,const gchar * key)3885 as_app_remove_metadata (AsApp *app, const gchar *key)
3886 {
3887 	AsAppPrivate *priv = GET_PRIVATE (app);
3888 	g_hash_table_remove (priv->metadata, key);
3889 }
3890 
3891 /**
3892  * as_app_add_extends:
3893  * @app: a #AsApp instance.
3894  * @extends: the full ID, e.g. "eclipse.desktop".
3895  *
3896  * Adds a parent ID to the application.
3897  *
3898  * Since: 0.1.7
3899  **/
3900 void
as_app_add_extends(AsApp * app,const gchar * extends)3901 as_app_add_extends (AsApp *app, const gchar *extends)
3902 {
3903 	AsAppPrivate *priv = GET_PRIVATE (app);
3904 
3905 	/* handle untrusted */
3906 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_VALID_UTF8) > 0 &&
3907 	    !as_app_validate_utf8 (extends)) {
3908 		priv->problems |= AS_APP_PROBLEM_NOT_VALID_UTF8;
3909 		return;
3910 	}
3911 	if ((priv->trust_flags & AS_APP_TRUST_FLAG_CHECK_DUPLICATES) > 0 &&
3912 	    as_ptr_array_find_string (priv->extends, extends)) {
3913 		return;
3914 	}
3915 
3916 	/* we can never extend ourself */
3917 	if (g_strcmp0 (priv->id, extends) == 0)
3918 		return;
3919 
3920 	g_ptr_array_add (priv->extends, as_ref_string_new (extends));
3921 }
3922 
3923 /**
3924  * as_app_add_addon:
3925  * @app: a #AsApp instance.
3926  * @addon: a #AsApp instance.
3927  *
3928  * Adds a addon to an application.
3929  *
3930  * Since: 0.1.7
3931  **/
3932 void
as_app_add_addon(AsApp * app,AsApp * addon)3933 as_app_add_addon (AsApp *app, AsApp *addon)
3934 {
3935 	AsAppPrivate *priv = GET_PRIVATE (app);
3936 	g_ptr_array_add (priv->addons, g_object_ref (addon));
3937 }
3938 
3939 /******************************************************************************/
3940 
3941 static void
as_app_subsume_dict(GHashTable * dest,GHashTable * src,guint64 flags)3942 as_app_subsume_dict (GHashTable *dest, GHashTable *src, guint64 flags)
3943 {
3944 	g_autoptr(GList) keys = g_hash_table_get_keys (src);
3945 	if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 && keys != NULL)
3946 		g_hash_table_remove_all (dest);
3947 	for (GList *l = keys; l != NULL; l = l->next) {
3948 		AsRefString *key = l->data;
3949 		AsRefString *value;
3950 		if (flags & AS_APP_SUBSUME_FLAG_NO_OVERWRITE) {
3951 			const gchar *tmp = g_hash_table_lookup (dest, key);
3952 			if (tmp != NULL)
3953 				continue;
3954 		}
3955 		value = g_hash_table_lookup (src, key);
3956 		g_hash_table_insert (dest, as_ref_string_ref (key), as_ref_string_ref (value));
3957 	}
3958 }
3959 
3960 static void
as_app_subsume_keywords(AsApp * app,AsApp * donor,gboolean overwrite)3961 as_app_subsume_keywords (AsApp *app, AsApp *donor, gboolean overwrite)
3962 {
3963 	AsAppPrivate *priv = GET_PRIVATE (donor);
3964 	GPtrArray *array;
3965 	g_autoptr(GList) keys = NULL;
3966 
3967 	/* get all locales in the keywords dict */
3968 	keys = g_hash_table_get_keys (priv->keywords);
3969 	for (GList *l = keys; l != NULL; l = l->next) {
3970 		AsRefString *key = l->data;
3971 		if (!overwrite) {
3972 			array = as_app_get_keywords (app, key);
3973 			if (array != NULL)
3974 				continue;
3975 		}
3976 
3977 		/* get the array of keywords for the specific locale */
3978 		array = g_hash_table_lookup (priv->keywords, key);
3979 		if (array == NULL)
3980 			continue;
3981 		for (guint i = 0; i < array->len; i++) {
3982 			AsRefString *tmp = g_ptr_array_index (array, i);
3983 			as_app_add_keyword_rstr (app, key, tmp);
3984 		}
3985 	}
3986 }
3987 
3988 static void
as_app_subsume_icon(AsApp * app,AsIcon * icon)3989 as_app_subsume_icon (AsApp *app, AsIcon *icon)
3990 {
3991 	AsAppPrivate *priv = GET_PRIVATE (app);
3992 	AsIcon *ic_tmp;
3993 	guint i;
3994 
3995 	/* does application already have this icon in this size */
3996 	for (i = 0; i < priv->icons->len; i++) {
3997 		ic_tmp = AS_ICON (g_ptr_array_index (priv->icons, i));
3998 		if (as_icon_get_height (ic_tmp) != as_icon_get_height (icon))
3999 			continue;
4000 		if (as_icon_get_width (ic_tmp) != as_icon_get_width (icon))
4001 			continue;
4002 		if (g_strcmp0 (as_icon_get_name (ic_tmp), as_icon_get_name (icon)) != 0)
4003 			continue;
4004 		return;
4005 	}
4006 
4007 	as_app_add_icon (app, icon);
4008 }
4009 
4010 static void
as_app_subsume_private(AsApp * app,AsApp * donor,guint64 flags)4011 as_app_subsume_private (AsApp *app, AsApp *donor, guint64 flags)
4012 {
4013 	AsAppPrivate *priv = GET_PRIVATE (donor);
4014 	AsAppPrivate *papp = GET_PRIVATE (app);
4015 	const gchar *tmp;
4016 	const gchar *key;
4017 	guint i;
4018 
4019 	/* stop us shooting ourselves in the foot */
4020 	papp->trust_flags |= AS_APP_TRUST_FLAG_CHECK_DUPLICATES;
4021 
4022 	/* id-kind */
4023 	if (flags & AS_APP_SUBSUME_FLAG_KIND) {
4024 		if (papp->kind == AS_APP_KIND_UNKNOWN)
4025 			as_app_set_kind (app, priv->kind);
4026 	}
4027 
4028 	/* AppData or AppStream can overwrite the id-kind of desktop files */
4029 	if (flags & AS_APP_SUBSUME_FLAG_SOURCE_KIND) {
4030 		if ((as_app_get_format_by_kind (donor, AS_FORMAT_KIND_APPDATA) != NULL ||
4031 		     as_app_get_format_by_kind (donor, AS_FORMAT_KIND_APPSTREAM) != NULL) &&
4032 		    as_app_get_format_by_kind (app, AS_FORMAT_KIND_DESKTOP) != NULL)
4033 			as_app_set_kind (app, priv->kind);
4034 	}
4035 
4036 	/* state */
4037 	if (flags & AS_APP_SUBSUME_FLAG_STATE) {
4038 		if (papp->state == AS_APP_STATE_UNKNOWN)
4039 			as_app_set_state (app, priv->state);
4040 	}
4041 
4042 	/* pkgnames */
4043 	if (flags & AS_APP_SUBSUME_FLAG_BUNDLES) {
4044 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4045 		    priv->pkgnames->len > 0)
4046 			g_ptr_array_set_size (papp->pkgnames, 0);
4047 		for (i = 0; i < priv->pkgnames->len; i++) {
4048 			tmp = g_ptr_array_index (priv->pkgnames, i);
4049 			as_app_add_pkgname (app, tmp);
4050 		}
4051 	}
4052 
4053 	/* bundles */
4054 	if (flags & AS_APP_SUBSUME_FLAG_BUNDLES) {
4055 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4056 		    priv->bundles->len > 0)
4057 			g_ptr_array_set_size (papp->bundles, 0);
4058 		for (i = 0; i < priv->bundles->len; i++) {
4059 			AsBundle *bundle = g_ptr_array_index (priv->bundles, i);
4060 			as_app_add_bundle (app, bundle);
4061 		}
4062 	}
4063 
4064 	/* translations */
4065 	if (flags & AS_APP_SUBSUME_FLAG_TRANSLATIONS) {
4066 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4067 		    priv->translations->len > 0)
4068 			g_ptr_array_set_size (papp->translations, 0);
4069 		for (i = 0; i < priv->translations->len; i++) {
4070 			AsTranslation *tr = g_ptr_array_index (priv->translations, i);
4071 			as_app_add_translation (app, tr);
4072 		}
4073 	}
4074 
4075 	/* suggests */
4076 	if (flags & AS_APP_SUBSUME_FLAG_SUGGESTS) {
4077 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4078 		    priv->suggests->len > 0)
4079 			g_ptr_array_set_size (papp->suggests, 0);
4080 		for (i = 0; i < priv->suggests->len; i++) {
4081 			AsSuggest *suggest = g_ptr_array_index (priv->suggests, i);
4082 			as_app_add_suggest (app, suggest);
4083 		}
4084 	}
4085 
4086 	/* requires */
4087 	if (flags & AS_APP_SUBSUME_FLAG_SUGGESTS) {
4088 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4089 		    priv->requires->len > 0)
4090 			g_ptr_array_set_size (papp->requires, 0);
4091 		for (i = 0; i < priv->requires->len; i++) {
4092 			AsRequire *require = g_ptr_array_index (priv->requires, i);
4093 			as_app_add_require (app, require);
4094 		}
4095 	}
4096 
4097 	/* releases */
4098 	if (flags & AS_APP_SUBSUME_FLAG_RELEASES) {
4099 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4100 		    priv->releases->len > 0)
4101 			g_ptr_array_set_size (papp->releases, 0);
4102 		for (i = 0; i < priv->releases->len; i++) {
4103 			AsRelease *rel= g_ptr_array_index (priv->releases, i);
4104 			as_app_add_release (app, rel);
4105 		}
4106 	}
4107 
4108 	/* kudos */
4109 	if (flags & AS_APP_SUBSUME_FLAG_KUDOS) {
4110 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4111 		    priv->kudos->len > 0)
4112 			g_ptr_array_set_size (papp->kudos, 0);
4113 		for (i = 0; i < priv->kudos->len; i++) {
4114 			tmp = g_ptr_array_index (priv->kudos, i);
4115 			as_app_add_kudo (app, tmp);
4116 		}
4117 	}
4118 
4119 	/* categories */
4120 	if (flags & AS_APP_SUBSUME_FLAG_CATEGORIES) {
4121 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4122 		    priv->categories->len > 0)
4123 			g_ptr_array_set_size (papp->categories, 0);
4124 		for (i = 0; i < priv->categories->len; i++) {
4125 			tmp = g_ptr_array_index (priv->categories, i);
4126 			as_app_add_category (app, tmp);
4127 		}
4128 	}
4129 
4130 	/* permissions */
4131 	if (flags & AS_APP_SUBSUME_FLAG_PERMISSIONS) {
4132 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4133 		    priv->permissions->len > 0)
4134 			g_ptr_array_set_size (papp->permissions, 0);
4135 		for (i = 0; i < priv->permissions->len; i++) {
4136 			tmp = g_ptr_array_index (priv->permissions, i);
4137 			as_app_add_permission (app, tmp);
4138 		}
4139 	}
4140 
4141 	/* extends */
4142 	if (flags & AS_APP_SUBSUME_FLAG_EXTENDS) {
4143 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4144 		    priv->extends->len > 0)
4145 			g_ptr_array_set_size (papp->extends, 0);
4146 		for (i = 0; i < priv->extends->len; i++) {
4147 			tmp = g_ptr_array_index (priv->extends, i);
4148 			as_app_add_extends (app, tmp);
4149 		}
4150 	}
4151 
4152 	/* compulsory_for_desktops */
4153 	if (flags & AS_APP_SUBSUME_FLAG_COMPULSORY) {
4154 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4155 		    priv->compulsory_for_desktops->len > 0)
4156 			g_ptr_array_set_size (papp->compulsory_for_desktops, 0);
4157 		for (i = 0; i < priv->compulsory_for_desktops->len; i++) {
4158 			tmp = g_ptr_array_index (priv->compulsory_for_desktops, i);
4159 			as_app_add_compulsory_for_desktop (app, tmp);
4160 		}
4161 	}
4162 
4163 	/* screenshots */
4164 	if (flags & AS_APP_SUBSUME_FLAG_SCREENSHOTS) {
4165 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4166 		    priv->screenshots->len > 0)
4167 			g_ptr_array_set_size (papp->screenshots, 0);
4168 		for (i = 0; i < priv->screenshots->len; i++) {
4169 			AsScreenshot *ss = g_ptr_array_index (priv->screenshots, i);
4170 			as_app_add_screenshot (app, ss);
4171 		}
4172 	}
4173 
4174 	/* reviews */
4175 	if (flags & AS_APP_SUBSUME_FLAG_REVIEWS) {
4176 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4177 		    priv->reviews->len > 0)
4178 			g_ptr_array_set_size (papp->reviews, 0);
4179 		for (i = 0; i < priv->reviews->len; i++) {
4180 			AsReview *review = g_ptr_array_index (priv->reviews, i);
4181 			as_app_add_review (app, review);
4182 		}
4183 	}
4184 
4185 	/* content_ratings */
4186 	if (flags & AS_APP_SUBSUME_FLAG_CONTENT_RATINGS) {
4187 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4188 		    priv->content_ratings->len > 0)
4189 			g_ptr_array_set_size (papp->content_ratings, 0);
4190 		for (i = 0; i < priv->content_ratings->len; i++) {
4191 			AsContentRating *content_rating;
4192 			content_rating = g_ptr_array_index (priv->content_ratings, i);
4193 			as_app_add_content_rating (app, content_rating);
4194 		}
4195 	}
4196 
4197 	/* agreements */
4198 	if (flags & AS_APP_SUBSUME_FLAG_AGREEMENTS) {
4199 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4200 		    priv->agreements->len > 0)
4201 			g_ptr_array_set_size (papp->agreements, 0);
4202 		for (i = 0; i < priv->agreements->len; i++) {
4203 			AsAgreement *agreement;
4204 			agreement = g_ptr_array_index (priv->agreements, i);
4205 			as_app_add_agreement (app, agreement);
4206 		}
4207 	}
4208 
4209 	/* provides */
4210 	if (flags & AS_APP_SUBSUME_FLAG_PROVIDES) {
4211 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4212 		    priv->provides->len > 0)
4213 			g_ptr_array_set_size (papp->provides, 0);
4214 		for (i = 0; i < priv->provides->len; i++) {
4215 			AsProvide *pr = g_ptr_array_index (priv->provides, i);
4216 			as_app_add_provide (app, pr);
4217 		}
4218 	}
4219 
4220 	/* launchables */
4221 	if (flags & AS_APP_SUBSUME_FLAG_LAUNCHABLES) {
4222 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4223 		    priv->launchables->len > 0)
4224 			g_ptr_array_set_size (papp->launchables, 0);
4225 		for (i = 0; i < priv->launchables->len; i++) {
4226 			AsLaunchable *lau = g_ptr_array_index (priv->launchables, i);
4227 			as_app_add_launchable (app, lau);
4228 		}
4229 	}
4230 
4231 	/* icons */
4232 	if (flags & AS_APP_SUBSUME_FLAG_ICONS) {
4233 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4234 		    priv->icons->len > 0)
4235 			g_ptr_array_set_size (papp->icons, 0);
4236 		for (i = 0; i < priv->icons->len; i++) {
4237 			AsIcon *ic = g_ptr_array_index (priv->icons, i);
4238 			as_app_subsume_icon (app, ic);
4239 		}
4240 	}
4241 
4242 	/* mimetypes */
4243 	if (flags & AS_APP_SUBSUME_FLAG_MIMETYPES) {
4244 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4245 		    priv->mimetypes->len > 0)
4246 			g_ptr_array_set_size (papp->mimetypes, 0);
4247 		for (i = 0; i < priv->mimetypes->len; i++) {
4248 			tmp = g_ptr_array_index (priv->mimetypes, i);
4249 			as_app_add_mimetype (app, tmp);
4250 		}
4251 	}
4252 
4253 	/* vetos */
4254 	if (flags & AS_APP_SUBSUME_FLAG_VETOS) {
4255 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 &&
4256 		    priv->vetos->len > 0)
4257 			g_ptr_array_set_size (papp->vetos, 0);
4258 		for (i = 0; i < priv->vetos->len; i++) {
4259 			tmp = g_ptr_array_index (priv->vetos, i);
4260 			as_app_add_veto (app, "%s", tmp);
4261 		}
4262 	}
4263 
4264 	/* languages */
4265 	if (flags & AS_APP_SUBSUME_FLAG_LANGUAGES) {
4266 		GList *l;
4267 		g_autoptr(GList) keys = g_hash_table_get_keys (priv->languages);
4268 		if ((flags & AS_APP_SUBSUME_FLAG_REPLACE) > 0 && keys != NULL)
4269 			g_hash_table_remove_all (papp->languages);
4270 		for (l = keys; l != NULL; l = l->next) {
4271 			gint percentage;
4272 			key = l->data;
4273 			if (flags & AS_APP_SUBSUME_FLAG_NO_OVERWRITE) {
4274 				percentage = as_app_get_language (app, key);
4275 				if (percentage >= 0)
4276 					continue;
4277 			}
4278 			percentage = GPOINTER_TO_INT (g_hash_table_lookup (priv->languages, key));
4279 			as_app_add_language (app, percentage, key);
4280 		}
4281 	}
4282 
4283 	/* dictionaries */
4284 	if (flags & AS_APP_SUBSUME_FLAG_NAME)
4285 		as_app_subsume_dict (papp->names, priv->names, flags);
4286 	if (flags & AS_APP_SUBSUME_FLAG_COMMENT)
4287 		as_app_subsume_dict (papp->comments, priv->comments, flags);
4288 	if (flags & AS_APP_SUBSUME_FLAG_DEVELOPER_NAME)
4289 		as_app_subsume_dict (papp->developer_names, priv->developer_names, flags);
4290 	if (flags & AS_APP_SUBSUME_FLAG_DESCRIPTION)
4291 		as_app_subsume_dict (papp->descriptions, priv->descriptions, flags);
4292 	if (flags & AS_APP_SUBSUME_FLAG_METADATA)
4293 		as_app_subsume_dict (papp->metadata, priv->metadata, flags);
4294 	if (flags & AS_APP_SUBSUME_FLAG_URL)
4295 		as_app_subsume_dict (papp->urls, priv->urls, flags);
4296 	if (flags & AS_APP_SUBSUME_FLAG_KEYWORDS)
4297 		as_app_subsume_keywords (app, donor, flags);
4298 
4299 	/* branch */
4300 	if (flags & AS_APP_SUBSUME_FLAG_BRANCH) {
4301 		if (priv->branch != NULL)
4302 			as_app_set_branch (app, priv->branch);
4303 	}
4304 
4305 	/* formats */
4306 	if (flags & AS_APP_SUBSUME_FLAG_FORMATS) {
4307 		for (i = 0; i < priv->formats->len; i++) {
4308 			AsFormat *fmt = g_ptr_array_index (priv->formats, i);
4309 			as_app_add_format (app, fmt);
4310 		}
4311 	}
4312 
4313 	/* source_pkgname */
4314 	if (flags & AS_APP_SUBSUME_FLAG_BUNDLES) {
4315 		if (priv->source_pkgname != NULL)
4316 			as_app_set_source_pkgname (app, priv->source_pkgname);
4317 	}
4318 
4319 	/* origin */
4320 	if (flags & AS_APP_SUBSUME_FLAG_ORIGIN) {
4321 		if (priv->origin != NULL)
4322 			as_app_set_origin (app, priv->origin);
4323 	}
4324 
4325 	/* licenses */
4326 	if (flags & AS_APP_SUBSUME_FLAG_PROJECT_LICENSE) {
4327 		if (priv->project_license != NULL)
4328 			as_app_set_project_license (app, priv->project_license);
4329 	}
4330 	if (flags & AS_APP_SUBSUME_FLAG_METADATA_LICENSE) {
4331 		if (priv->metadata_license != NULL)
4332 			as_app_set_metadata_license (app, priv->metadata_license);
4333 	}
4334 
4335 	/* project_group */
4336 	if (flags & AS_APP_SUBSUME_FLAG_PROJECT_GROUP) {
4337 		if (priv->project_group != NULL)
4338 			as_app_set_project_group (app, priv->project_group);
4339 	}
4340 }
4341 
4342 /**
4343  * as_app_subsume_full:
4344  * @app: a #AsApp instance.
4345  * @donor: the donor.
4346  * @flags: any optional #AsAppSubsumeFlags, e.g. %AS_APP_SUBSUME_FLAG_NO_OVERWRITE
4347  *
4348  * Copies information from the donor to the application object.
4349  *
4350  * Since: 0.1.4
4351  **/
4352 void
as_app_subsume_full(AsApp * app,AsApp * donor,guint64 flags)4353 as_app_subsume_full (AsApp *app, AsApp *donor, guint64 flags)
4354 {
4355 	g_assert (app != donor);
4356 
4357 	/* two way sync implies no overwriting */
4358 	if ((flags & AS_APP_SUBSUME_FLAG_BOTH_WAYS) > 0)
4359 		flags |= AS_APP_SUBSUME_FLAG_NO_OVERWRITE;
4360 
4361 	/* one way sync */
4362 	as_app_subsume_private (app, donor, flags);
4363 
4364 	/* and back again */
4365 	if ((flags & AS_APP_SUBSUME_FLAG_BOTH_WAYS) > 0)
4366 		as_app_subsume_private (donor, app, flags);
4367 }
4368 
4369 /**
4370  * as_app_subsume:
4371  * @app: a #AsApp instance.
4372  * @donor: the donor.
4373  *
4374  * Copies information from the donor to the application object.
4375  *
4376  * Since: 0.1.0
4377  **/
4378 void
as_app_subsume(AsApp * app,AsApp * donor)4379 as_app_subsume (AsApp *app, AsApp *donor)
4380 {
4381 	as_app_subsume_full (app, donor, AS_APP_SUBSUME_FLAG_DEDUPE);
4382 }
4383 
4384 static gint
gs_app_node_language_sort_cb(gconstpointer a,gconstpointer b)4385 gs_app_node_language_sort_cb (gconstpointer a, gconstpointer b)
4386 {
4387 	return g_strcmp0 ((const gchar *) a, (const gchar *) b);
4388 }
4389 
4390 static void
as_app_node_insert_languages(AsApp * app,GNode * parent)4391 as_app_node_insert_languages (AsApp *app, GNode *parent)
4392 {
4393 	GNode *node_tmp;
4394 	GList *l;
4395 	const gchar *locale;
4396 	gchar tmp[4];
4397 	gint percentage;
4398 	g_autoptr(GList) langs = NULL;
4399 
4400 	node_tmp = as_node_insert (parent, "languages", NULL, 0, NULL);
4401 	langs = as_app_get_languages (app);
4402 	langs = g_list_sort (langs, gs_app_node_language_sort_cb);
4403 	for (l = langs; l != NULL; l = l->next) {
4404 		locale = l->data;
4405 		percentage = as_app_get_language (app, locale);
4406 		if (percentage == 0) {
4407 			as_node_insert (node_tmp, "lang", locale, 0, NULL);
4408 		} else {
4409 			g_snprintf (tmp, sizeof (tmp), "%i", percentage);
4410 			as_node_insert (node_tmp, "lang", locale, 0,
4411 					"percentage", tmp,
4412 					NULL);
4413 		}
4414 	}
4415 }
4416 
4417 static gint
as_app_ptr_array_sort_cb(gconstpointer a,gconstpointer b)4418 as_app_ptr_array_sort_cb (gconstpointer a, gconstpointer b)
4419 {
4420 	return g_strcmp0 (*((const gchar **) a), *((const gchar **) b));
4421 }
4422 
4423 static gint
as_app_releases_sort_cb(gconstpointer a,gconstpointer b)4424 as_app_releases_sort_cb (gconstpointer a, gconstpointer b)
4425 {
4426 	AsRelease **rel1 = (AsRelease **) a;
4427 	AsRelease **rel2 = (AsRelease **) b;
4428 	return as_release_vercmp (*rel1, *rel2);
4429 }
4430 
4431 static gint
as_app_provides_sort_cb(gconstpointer a,gconstpointer b)4432 as_app_provides_sort_cb (gconstpointer a, gconstpointer b)
4433 {
4434 	AsProvide *prov1 = *((AsProvide **) a);
4435 	AsProvide *prov2 = *((AsProvide **) b);
4436 
4437 	if (as_provide_get_kind (prov1) < as_provide_get_kind (prov2))
4438 		return -1;
4439 	if (as_provide_get_kind (prov1) > as_provide_get_kind (prov2))
4440 		return 1;
4441 	return g_strcmp0 (as_provide_get_value (prov1),
4442 			  as_provide_get_value (prov2));
4443 }
4444 
4445 static gint
as_app_launchables_sort_cb(gconstpointer a,gconstpointer b)4446 as_app_launchables_sort_cb (gconstpointer a, gconstpointer b)
4447 {
4448 	AsLaunchable *lau1 = *((AsLaunchable **) a);
4449 	AsLaunchable *lau2 = *((AsLaunchable **) b);
4450 
4451 	if (as_launchable_get_kind (lau1) < as_launchable_get_kind (lau2))
4452 		return -1;
4453 	if (as_launchable_get_kind (lau1) > as_launchable_get_kind (lau2))
4454 		return 1;
4455 	return g_strcmp0 (as_launchable_get_value (lau1),
4456 			  as_launchable_get_value (lau2));
4457 }
4458 
4459 static gint
as_app_icons_sort_cb(gconstpointer a,gconstpointer b)4460 as_app_icons_sort_cb (gconstpointer a, gconstpointer b)
4461 {
4462 	AsIcon **ic1 = (AsIcon **) a;
4463 	AsIcon **ic2 = (AsIcon **) b;
4464 	return g_strcmp0 (as_icon_get_name (*ic1), as_icon_get_name (*ic2));
4465 }
4466 
4467 static gint
as_app_list_sort_cb(gconstpointer a,gconstpointer b)4468 as_app_list_sort_cb (gconstpointer a, gconstpointer b)
4469 {
4470 	return g_strcmp0 ((const gchar *) a, (const gchar *) b);
4471 }
4472 
4473 static void
as_app_node_insert_keywords(AsApp * app,GNode * parent,AsNodeContext * ctx)4474 as_app_node_insert_keywords (AsApp *app, GNode *parent, AsNodeContext *ctx)
4475 {
4476 	AsAppPrivate *priv = GET_PRIVATE (app);
4477 	GList *keys;
4478 	GList *l;
4479 	GNode *node_tmp;
4480 	GPtrArray *keywords;
4481 	const gchar *lang;
4482 	const gchar *tmp;
4483 	guint i;
4484 	g_autoptr(GHashTable) already_in_c = NULL;
4485 
4486 	/* don't add localized keywords that already exist in C, e.g.
4487 	 * there's no point adding "c++" in 14 different languages */
4488 	already_in_c = g_hash_table_new_full (g_str_hash, g_str_equal,
4489 					      (GDestroyNotify) as_ref_string_unref, NULL);
4490 	keywords = g_hash_table_lookup (priv->keywords, "C");
4491 	if (keywords != NULL) {
4492 		for (i = 0; i < keywords->len; i++) {
4493 			tmp = g_ptr_array_index (keywords, i);
4494 			g_hash_table_insert (already_in_c,
4495 					     as_ref_string_new (tmp),
4496 					     GINT_TO_POINTER (1));
4497 		}
4498 	}
4499 
4500 	keys = g_hash_table_get_keys (priv->keywords);
4501 	keys = g_list_sort (keys, as_app_list_sort_cb);
4502 	for (l = keys; l != NULL; l = l->next) {
4503 		lang = l->data;
4504 		keywords = g_hash_table_lookup (priv->keywords, lang);
4505 		g_ptr_array_sort (keywords, as_app_ptr_array_sort_cb);
4506 		for (i = 0; i < keywords->len; i++) {
4507 			tmp = g_ptr_array_index (keywords, i);
4508 			if (tmp == NULL)
4509 				continue;
4510 			if (g_strcmp0 (lang, "C") != 0 &&
4511 			    g_hash_table_lookup (already_in_c, tmp) != NULL)
4512 				continue;
4513 			node_tmp = as_node_insert (parent,
4514 						   "keyword", tmp,
4515 						   0, NULL);
4516 			if (g_strcmp0 (lang, "C") != 0) {
4517 				as_node_add_attribute (node_tmp,
4518 						       "xml:lang",
4519 						       lang);
4520 			}
4521 		}
4522 	}
4523 	g_list_free (keys);
4524 }
4525 
4526 /**
4527  * as_app_node_insert: (skip)
4528  * @app: a #AsApp instance.
4529  * @parent: the parent #GNode to use..
4530  * @ctx: the #AsNodeContext
4531  *
4532  * Inserts the application into the DOM tree.
4533  *
4534  * Returns: (transfer none): A populated #GNode, or %NULL
4535  *
4536  * Since: 0.1.0
4537  **/
4538 GNode *
as_app_node_insert(AsApp * app,GNode * parent,AsNodeContext * ctx)4539 as_app_node_insert (AsApp *app, GNode *parent, AsNodeContext *ctx)
4540 {
4541 	AsAppPrivate *priv = GET_PRIVATE (app);
4542 	AsRelease *rel;
4543 	AsScreenshot *ss;
4544 	GNode *node_app;
4545 	GNode *node_tmp;
4546 	const gchar *tmp;
4547 	guint i;
4548 
4549 	/* <component> or <application> */
4550 	node_app = as_node_insert (parent, "component", NULL, 0, NULL);
4551 	if (priv->kind != AS_APP_KIND_UNKNOWN) {
4552 		as_node_add_attribute (node_app,
4553 				       "type",
4554 				       as_app_kind_to_string (priv->kind));
4555 	}
4556 
4557 	/* merge type */
4558 	if (priv->merge_kind != AS_APP_MERGE_KIND_UNKNOWN &&
4559 	    priv->merge_kind != AS_APP_MERGE_KIND_NONE) {
4560 		as_node_add_attribute (node_app,
4561 				       "merge",
4562 				       as_app_merge_kind_to_string (priv->merge_kind));
4563 	}
4564 
4565 	/* <id> */
4566 	if (priv->id != NULL)
4567 		as_node_insert (node_app, "id", priv->id, 0, NULL);
4568 
4569 	/* <priority> */
4570 	if (priv->priority != 0)
4571 		as_node_add_attribute_as_int (node_app, "priority", priv->priority);
4572 
4573 	/* <pkgname> */
4574 	g_ptr_array_sort (priv->pkgnames, as_app_ptr_array_sort_cb);
4575 	for (i = 0; i < priv->pkgnames->len; i++) {
4576 		tmp = g_ptr_array_index (priv->pkgnames, i);
4577 		as_node_insert (node_app, "pkgname", tmp, 0, NULL);
4578 	}
4579 
4580 	/* <source_pkgname> */
4581 	if (priv->source_pkgname != NULL) {
4582 		as_node_insert (node_app, "source_pkgname",
4583 				priv->source_pkgname, 0, NULL);
4584 	}
4585 
4586 	/* <bundle> */
4587 	for (i = 0; i < priv->bundles->len; i++) {
4588 		AsBundle *bu = g_ptr_array_index (priv->bundles, i);
4589 		as_bundle_node_insert (bu, node_app, ctx);
4590 	}
4591 
4592 	/* <translation> */
4593 	for (i = 0; i < priv->translations->len; i++) {
4594 		AsTranslation *bu = g_ptr_array_index (priv->translations, i);
4595 		as_translation_node_insert (bu, node_app, ctx);
4596 	}
4597 
4598 	/* <suggests> */
4599 	for (i = 0; i < priv->suggests->len; i++) {
4600 		AsSuggest *suggest = g_ptr_array_index (priv->suggests, i);
4601 		as_suggest_node_insert (suggest, node_app, ctx);
4602 	}
4603 
4604 	/* <requires> */
4605 	if (priv->requires->len > 0) {
4606 		node_tmp = as_node_insert (node_app, "requires", NULL, 0, NULL);
4607 		for (i = 0; i < priv->requires->len; i++) {
4608 			AsRequire *require = g_ptr_array_index (priv->requires, i);
4609 			as_require_node_insert (require, node_tmp, ctx);
4610 		}
4611 	}
4612 
4613 	/* <name> */
4614 	as_node_insert_localized (node_app, "name",
4615 				  priv->names,
4616 				  AS_NODE_INSERT_FLAG_DEDUPE_LANG);
4617 
4618 	/* <summary> */
4619 	as_node_insert_localized (node_app, "summary",
4620 				  priv->comments,
4621 				  AS_NODE_INSERT_FLAG_DEDUPE_LANG);
4622 
4623 	/* <developer_name> */
4624 	as_node_insert_localized (node_app, "developer_name",
4625 				  priv->developer_names,
4626 				  AS_NODE_INSERT_FLAG_DEDUPE_LANG);
4627 
4628 	/* <description> */
4629 	as_node_insert_localized (node_app, "description",
4630 				  priv->descriptions,
4631 				  AS_NODE_INSERT_FLAG_PRE_ESCAPED |
4632 				  AS_NODE_INSERT_FLAG_DEDUPE_LANG);
4633 
4634 	/* <icon> */
4635 	g_ptr_array_sort (priv->icons, as_app_icons_sort_cb);
4636 	for (i = 0; i < priv->icons->len; i++) {
4637 		AsIcon *ic = g_ptr_array_index (priv->icons, i);
4638 		as_icon_node_insert (ic, node_app, ctx);
4639 	}
4640 
4641 	/* <categories> */
4642 	if (priv->categories->len > 0) {
4643 		g_ptr_array_sort (priv->categories, as_app_ptr_array_sort_cb);
4644 		node_tmp = as_node_insert (node_app, "categories", NULL, 0, NULL);
4645 		for (i = 0; i < priv->categories->len; i++) {
4646 			tmp = g_ptr_array_index (priv->categories, i);
4647 			as_node_insert (node_tmp, "category", tmp, 0, NULL);
4648 		}
4649 	}
4650 
4651 	/* <architectures> */
4652 	if (priv->architectures->len > 0) {
4653 		g_ptr_array_sort (priv->architectures, as_app_ptr_array_sort_cb);
4654 		node_tmp = as_node_insert (node_app, "architectures", NULL, 0, NULL);
4655 		for (i = 0; i < priv->architectures->len; i++) {
4656 			tmp = g_ptr_array_index (priv->architectures, i);
4657 			as_node_insert (node_tmp, "arch", tmp, 0, NULL);
4658 		}
4659 	}
4660 
4661 	/* <keywords> */
4662 	if (g_hash_table_size (priv->keywords) > 0) {
4663 		node_tmp = as_node_insert (node_app, "keywords", NULL, 0, NULL);
4664 		as_app_node_insert_keywords (app, node_tmp, ctx);
4665 	}
4666 
4667 	/* <kudos> */
4668 	if (priv->kudos->len > 0) {
4669 		g_ptr_array_sort (priv->kudos, as_app_ptr_array_sort_cb);
4670 		node_tmp = as_node_insert (node_app, "kudos", NULL, 0, NULL);
4671 		for (i = 0; i < priv->kudos->len; i++) {
4672 			tmp = g_ptr_array_index (priv->kudos, i);
4673 			as_node_insert (node_tmp, "kudo", tmp, 0, NULL);
4674 		}
4675 	}
4676 
4677 	/* <permissions> */
4678 	if (priv->permissions->len > 0) {
4679 		g_ptr_array_sort (priv->permissions, as_app_ptr_array_sort_cb);
4680 		node_tmp = as_node_insert (node_app, "permissions", NULL, 0, NULL);
4681 		for (i = 0; i < priv->permissions->len; i++) {
4682 			tmp = g_ptr_array_index (priv->permissions, i);
4683 			as_node_insert (node_tmp, "permission", tmp, 0, NULL);
4684 		}
4685 	}
4686 
4687 	/* <vetos> */
4688 	if (priv->vetos->len > 0) {
4689 		g_ptr_array_sort (priv->vetos, as_app_ptr_array_sort_cb);
4690 		node_tmp = as_node_insert (node_app, "vetos", NULL, 0, NULL);
4691 		for (i = 0; i < priv->vetos->len; i++) {
4692 			tmp = g_ptr_array_index (priv->vetos, i);
4693 			as_node_insert (node_tmp, "veto", tmp, 0, NULL);
4694 		}
4695 	}
4696 
4697 	/* <mimetypes> */
4698 	if (priv->mimetypes->len > 0) {
4699 		g_ptr_array_sort (priv->mimetypes, as_app_ptr_array_sort_cb);
4700 		node_tmp = as_node_insert (node_app, "mimetypes", NULL, 0, NULL);
4701 		for (i = 0; i < priv->mimetypes->len; i++) {
4702 			tmp = g_ptr_array_index (priv->mimetypes, i);
4703 			as_node_insert (node_tmp, "mimetype", tmp, 0, NULL);
4704 		}
4705 	}
4706 
4707 	/* <metadata_license> */
4708 	if (as_node_context_get_output (ctx) == AS_FORMAT_KIND_APPDATA ||
4709 	    as_node_context_get_output (ctx) == AS_FORMAT_KIND_METAINFO) {
4710 		if (priv->metadata_license != NULL) {
4711 			as_node_insert (node_app, "metadata_license",
4712 					priv->metadata_license, 0, NULL);
4713 		}
4714 	}
4715 
4716 	/* <project_license> */
4717 	if (priv->project_license != NULL) {
4718 		as_node_insert (node_app, "project_license",
4719 				priv->project_license, 0, NULL);
4720 	}
4721 
4722 	/* <url> */
4723 	as_node_insert_hash (node_app, "url", "type", priv->urls, 0);
4724 
4725 	/* <project_group> */
4726 	if (priv->project_group != NULL) {
4727 		as_node_insert (node_app, "project_group",
4728 				priv->project_group, 0, NULL);
4729 	}
4730 
4731 	/* <compulsory_for_desktop> */
4732 	if (priv->compulsory_for_desktops != NULL) {
4733 		g_ptr_array_sort (priv->compulsory_for_desktops,
4734 				  as_app_ptr_array_sort_cb);
4735 		for (i = 0; i < priv->compulsory_for_desktops->len; i++) {
4736 			tmp = g_ptr_array_index (priv->compulsory_for_desktops, i);
4737 			as_node_insert (node_app, "compulsory_for_desktop",
4738 					tmp, 0, NULL);
4739 		}
4740 	}
4741 
4742 	/* <extends> */
4743 	if (priv->extends->len > 0) {
4744 		g_ptr_array_sort (priv->extends, as_app_ptr_array_sort_cb);
4745 		for (i = 0; i < priv->extends->len; i++) {
4746 			tmp = g_ptr_array_index (priv->extends, i);
4747 			as_node_insert (node_app, "extends", tmp, 0, NULL);
4748 		}
4749 	}
4750 
4751 	/* <screenshots> */
4752 	if (priv->screenshots->len > 0) {
4753 		node_tmp = as_node_insert (node_app, "screenshots", NULL, 0, NULL);
4754 		for (i = 0; i < priv->screenshots->len; i++) {
4755 			ss = g_ptr_array_index (priv->screenshots, i);
4756 			as_screenshot_node_insert (ss, node_tmp, ctx);
4757 		}
4758 	}
4759 
4760 	/* <reviews> */
4761 	if (priv->reviews->len > 0) {
4762 		AsReview *review;
4763 		node_tmp = as_node_insert (node_app, "reviews", NULL, 0, NULL);
4764 		for (i = 0; i < priv->reviews->len; i++) {
4765 			review = g_ptr_array_index (priv->reviews, i);
4766 			as_review_node_insert (review, node_tmp, ctx);
4767 		}
4768 	}
4769 
4770 	/* <content_ratings> */
4771 	if (priv->content_ratings->len > 0) {
4772 		for (i = 0; i < priv->content_ratings->len; i++) {
4773 			AsContentRating *content_rating;
4774 			content_rating = g_ptr_array_index (priv->content_ratings, i);
4775 			as_content_rating_node_insert (content_rating, node_app, ctx);
4776 		}
4777 	}
4778 
4779 	/* <agreements> */
4780 	if (priv->agreements->len > 0) {
4781 		for (i = 0; i < priv->agreements->len; i++) {
4782 			AsAgreement *agreement;
4783 			agreement = g_ptr_array_index (priv->agreements, i);
4784 			as_agreement_node_insert (agreement, node_app, ctx);
4785 		}
4786 	}
4787 
4788 	/* <releases> */
4789 	if (priv->releases->len > 0) {
4790 		g_ptr_array_sort (priv->releases, as_app_releases_sort_cb);
4791 		node_tmp = as_node_insert (node_app, "releases", NULL, 0, NULL);
4792 		for (i = 0; i < priv->releases->len; i++) {
4793 			rel = g_ptr_array_index (priv->releases, i);
4794 			as_release_node_insert (rel, node_tmp, ctx);
4795 		}
4796 	}
4797 
4798 	/* <provides> */
4799 	if (priv->provides->len > 0) {
4800 		AsProvide *provide;
4801 		g_ptr_array_sort (priv->provides, as_app_provides_sort_cb);
4802 		node_tmp = as_node_insert (node_app, "provides", NULL, 0, NULL);
4803 		for (i = 0; i < priv->provides->len; i++) {
4804 			provide = g_ptr_array_index (priv->provides, i);
4805 			as_provide_node_insert (provide, node_tmp, ctx);
4806 		}
4807 	}
4808 
4809 	/* <launchables> */
4810 	if (priv->launchables->len > 0) {
4811 		g_ptr_array_sort (priv->launchables, as_app_launchables_sort_cb);
4812 		for (i = 0; i < priv->launchables->len; i++) {
4813 			AsLaunchable *launchable = g_ptr_array_index (priv->launchables, i);
4814 			as_launchable_node_insert (launchable, node_app, ctx);
4815 		}
4816 	}
4817 
4818 	/* <languages> */
4819 	if (g_hash_table_size (priv->languages) > 0)
4820 		as_app_node_insert_languages (app, node_app);
4821 
4822 	/* <update_contact> */
4823 	if (as_node_context_get_output (ctx) == AS_FORMAT_KIND_APPDATA ||
4824 	    as_node_context_get_output (ctx) == AS_FORMAT_KIND_METAINFO ||
4825 	    as_node_context_get_output_trusted (ctx)) {
4826 		if (priv->update_contact != NULL) {
4827 			as_node_insert (node_app, "update_contact",
4828 					priv->update_contact, 0, NULL);
4829 		}
4830 	}
4831 
4832 	/* <custom> or <metadata> */
4833 	if (g_hash_table_size (priv->metadata) > 0) {
4834 		tmp = as_node_context_get_version (ctx) > 0.9 ? "custom" : "metadata";
4835 		node_tmp = as_node_insert (node_app, tmp, NULL, 0, NULL);
4836 		as_node_insert_hash (node_tmp, "value", "key", priv->metadata, FALSE);
4837 	}
4838 
4839 	return node_app;
4840 }
4841 
4842 static gboolean
as_app_node_parse_child(AsApp * app,GNode * n,guint32 flags,AsNodeContext * ctx,GError ** error)4843 as_app_node_parse_child (AsApp *app, GNode *n, guint32 flags,
4844 			 AsNodeContext *ctx, GError **error)
4845 {
4846 	AsAppPrivate *priv = GET_PRIVATE (app);
4847 	AsRefString *str;
4848 	GNode *c;
4849 	const gchar *tmp;
4850 	g_autoptr(AsRefString) xml_lang = NULL;
4851 
4852 	switch (as_node_get_tag (n)) {
4853 
4854 	/* <id> */
4855 	case AS_TAG_ID:
4856 		if (as_node_get_attribute (n, "xml:lang") != NULL) {
4857 			priv->problems |= AS_APP_PROBLEM_TRANSLATED_ID;
4858 			break;
4859 		}
4860 		tmp = as_node_get_attribute (n, "type");
4861 		if (tmp != NULL)
4862 			as_app_set_kind (app, as_app_kind_from_string (tmp));
4863 		if (as_node_get_data (n) == NULL) {
4864 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
4865 			break;
4866 		}
4867 		as_app_set_id (app, as_node_get_data (n));
4868 		break;
4869 
4870 	/* <priority> */
4871 	case AS_TAG_PRIORITY:
4872 	{
4873 		gint64 tmp64 = g_ascii_strtoll (as_node_get_data (n), NULL, 10);
4874 		as_app_set_priority (app, (gint) tmp64);
4875 		break;
4876 	}
4877 
4878 	/* <pkgname> */
4879 	case AS_TAG_PKGNAME:
4880 		str = as_node_get_data_as_refstr (n);
4881 		if (str != NULL)
4882 			g_ptr_array_add (priv->pkgnames, as_ref_string_ref (str));
4883 		break;
4884 
4885 	/* <bundle> */
4886 	case AS_TAG_BUNDLE:
4887 	{
4888 		g_autoptr(AsBundle) bu = NULL;
4889 		bu = as_bundle_new ();
4890 		if (!as_bundle_node_parse (bu, n, ctx, error))
4891 			return FALSE;
4892 		as_app_add_bundle (app, bu);
4893 		break;
4894 	}
4895 
4896 	/* <translation> */
4897 	case AS_TAG_TRANSLATION:
4898 	{
4899 		g_autoptr(AsTranslation) ic = NULL;
4900 		ic = as_translation_new ();
4901 		if (!as_translation_node_parse (ic, n, ctx, error))
4902 			return FALSE;
4903 		as_app_add_translation (app, ic);
4904 		break;
4905 	}
4906 
4907 	/* <suggests> */
4908 	case AS_TAG_SUGGESTS:
4909 	{
4910 		g_autoptr(AsSuggest) ic = NULL;
4911 		ic = as_suggest_new ();
4912 		if (!as_suggest_node_parse (ic, n, ctx, error))
4913 			return FALSE;
4914 		as_app_add_suggest (app, ic);
4915 		break;
4916 	}
4917 
4918 	/* <requires> */
4919 	case AS_TAG_REQUIRES:
4920 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
4921 			g_ptr_array_set_size (priv->requires, 0);
4922 		for (c = n->children; c != NULL; c = c->next) {
4923 			g_autoptr(AsRequire) ic = NULL;
4924 			ic = as_require_new ();
4925 			if (!as_require_node_parse (ic, c, ctx, error))
4926 				return FALSE;
4927 			as_app_add_require (app, ic);
4928 		}
4929 		if (n->children == NULL)
4930 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
4931 		break;
4932 
4933 	/* <name> */
4934 	case AS_TAG_NAME:
4935 		xml_lang = as_node_fix_locale_full (n, as_node_get_attribute (n, "xml:lang"));
4936 		if (xml_lang == NULL)
4937 			break;
4938 		str = as_node_get_data_as_refstr (n);
4939 		if (str != NULL) {
4940 			g_hash_table_insert (priv->names,
4941 					     as_ref_string_ref (xml_lang),
4942 					     as_ref_string_ref (str));
4943 		}
4944 		break;
4945 
4946 	/* <summary> */
4947 	case AS_TAG_SUMMARY:
4948 		xml_lang = as_node_fix_locale_full (n, as_node_get_attribute (n, "xml:lang"));
4949 		if (xml_lang == NULL)
4950 			break;
4951 		str = as_node_get_data_as_refstr (n);
4952 		if (str != NULL) {
4953 			g_hash_table_insert (priv->comments,
4954 					     as_ref_string_ref (xml_lang),
4955 					     as_ref_string_ref (str));
4956 		}
4957 		break;
4958 
4959 	/* <developer_name> */
4960 	case AS_TAG_DEVELOPER_NAME:
4961 		xml_lang = as_node_fix_locale_full (n, as_node_get_attribute (n, "xml:lang"));
4962 		if (xml_lang == NULL)
4963 			break;
4964 		str = as_node_get_data_as_refstr (n);
4965 		if (str != NULL) {
4966 			g_hash_table_insert (priv->developer_names,
4967 					     as_ref_string_ref (xml_lang),
4968 					     as_ref_string_ref (str));
4969 		}
4970 		break;
4971 
4972 	/* <description> */
4973 	case AS_TAG_DESCRIPTION:
4974 	{
4975 		/* unwrap appdata inline */
4976 		AsFormat *format = as_app_get_format_by_kind (app, AS_FORMAT_KIND_APPDATA);
4977 		if (format != NULL) {
4978 			GError *error_local = NULL;
4979 			g_autoptr(GHashTable) unwrapped = NULL;
4980 			unwrapped = as_node_get_localized_unwrap (n, &error_local);
4981 			if (unwrapped == NULL) {
4982 				if (g_error_matches (error_local,
4983 						     AS_NODE_ERROR,
4984 						     AS_NODE_ERROR_INVALID_MARKUP)) {
4985 					g_autoptr(GString) debug = NULL;
4986 					debug = as_node_to_xml (n, AS_NODE_TO_XML_FLAG_NONE);
4987 					g_warning ("ignoring description '%s' from %s: %s",
4988 						   debug->str,
4989 						   as_format_get_filename (format),
4990 						   error_local->message);
4991 					g_error_free (error_local);
4992 					break;
4993 				}
4994 				g_propagate_error (error, error_local);
4995 				return FALSE;
4996 			}
4997 			as_app_subsume_dict (priv->descriptions, unwrapped, FALSE);
4998 			break;
4999 		}
5000 
5001 		if (n->children == NULL) {
5002 			/* pre-formatted */
5003 			priv->problems |= AS_APP_PROBLEM_PREFORMATTED_DESCRIPTION;
5004 			if (as_node_get_data (n) == NULL)
5005 				break;
5006 			as_app_set_description (app,
5007 						as_node_get_attribute (n, "xml:lang"),
5008 						as_node_get_data (n));
5009 		} else {
5010 			g_autoptr(GString) xml = NULL;
5011 			xml = as_node_to_xml (n->children,
5012 					      AS_NODE_TO_XML_FLAG_INCLUDE_SIBLINGS);
5013 			as_app_set_description (app,
5014 						as_node_get_attribute (n, "xml:lang"),
5015 						xml->str);
5016 		}
5017 		break;
5018 	}
5019 
5020 	/* <icon> */
5021 	case AS_TAG_ICON:
5022 	{
5023 		g_autoptr(AsIcon) ic = NULL;
5024 		ic = as_icon_new ();
5025 		as_icon_set_prefix_rstr (ic, priv->icon_path);
5026 		if (!as_icon_node_parse (ic, n, ctx, error))
5027 			return FALSE;
5028 		as_app_add_icon (app, ic);
5029 		break;
5030 	}
5031 
5032 	/* <categories> */
5033 	case AS_TAG_CATEGORIES:
5034 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5035 			g_ptr_array_set_size (priv->categories, 0);
5036 		for (c = n->children; c != NULL; c = c->next) {
5037 			if (as_node_get_tag (c) != AS_TAG_CATEGORY)
5038 				continue;
5039 			str = as_node_get_data_as_refstr (c);
5040 			if (str == NULL)
5041 				continue;
5042 			g_ptr_array_add (priv->categories, as_ref_string_ref (str));
5043 		}
5044 		if (n->children == NULL)
5045 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5046 		break;
5047 
5048 	/* <architectures> */
5049 	case AS_TAG_ARCHITECTURES:
5050 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5051 			g_ptr_array_set_size (priv->architectures, 0);
5052 		for (c = n->children; c != NULL; c = c->next) {
5053 			if (as_node_get_tag (c) != AS_TAG_ARCH)
5054 				continue;
5055 			str = as_node_get_data_as_refstr (c);
5056 			if (str == NULL)
5057 				continue;
5058 			g_ptr_array_add (priv->architectures,
5059 					 as_ref_string_ref (str));
5060 		}
5061 		if (n->children == NULL)
5062 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5063 		break;
5064 
5065 	/* <keywords> */
5066 	case AS_TAG_KEYWORDS:
5067 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5068 			g_hash_table_remove_all (priv->keywords);
5069 		for (c = n->children; c != NULL; c = c->next) {
5070 			g_autoptr(AsRefString) xml_lang2 = NULL;
5071 			if (as_node_get_tag (c) != AS_TAG_KEYWORD)
5072 				continue;
5073 			str = as_node_get_data_as_refstr (c);
5074 			if (str == NULL)
5075 				continue;
5076 			xml_lang2 = as_node_fix_locale_full (n, as_node_get_attribute (c, "xml:lang"));
5077 			if (xml_lang2 == NULL)
5078 				continue;
5079 			if (g_strstr_len (str, -1, ",") != NULL)
5080 				priv->problems |= AS_APP_PROBLEM_INVALID_KEYWORDS;
5081 			as_app_add_keyword_rstr (app, xml_lang2, str);
5082 		}
5083 		if (n->children == NULL)
5084 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5085 		break;
5086 
5087 	/* <kudos> */
5088 	case AS_TAG_KUDOS:
5089 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5090 			g_ptr_array_set_size (priv->kudos, 0);
5091 		for (c = n->children; c != NULL; c = c->next) {
5092 			if (as_node_get_tag (c) != AS_TAG_KUDO)
5093 				continue;
5094 			str = as_node_get_data_as_refstr (c);
5095 			if (str == NULL)
5096 				continue;
5097 			g_ptr_array_add (priv->kudos, as_ref_string_ref (str));
5098 		}
5099 		if (n->children == NULL)
5100 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5101 		break;
5102 
5103 	/* <permissions> */
5104 	case AS_TAG_PERMISSIONS:
5105 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5106 			g_ptr_array_set_size (priv->permissions, 0);
5107 		for (c = n->children; c != NULL; c = c->next) {
5108 			if (as_node_get_tag (c) != AS_TAG_PERMISSION)
5109 				continue;
5110 			str = as_node_get_data_as_refstr (c);
5111 			if (str == NULL)
5112 				continue;
5113 			g_ptr_array_add (priv->permissions, as_ref_string_ref (str));
5114 		}
5115 		if (n->children == NULL)
5116 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5117 		break;
5118 
5119 	/* <vetos> */
5120 	case AS_TAG_VETOS:
5121 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5122 			g_ptr_array_set_size (priv->vetos, 0);
5123 		for (c = n->children; c != NULL; c = c->next) {
5124 			if (as_node_get_tag (c) != AS_TAG_VETO)
5125 				continue;
5126 			str = as_node_get_data_as_refstr (c);
5127 			if (str == NULL)
5128 				continue;
5129 			g_ptr_array_add (priv->vetos, as_ref_string_ref (str));
5130 		}
5131 		if (n->children == NULL)
5132 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5133 		break;
5134 
5135 	/* <mimetypes> */
5136 	case AS_TAG_MIMETYPES:
5137 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5138 			g_ptr_array_set_size (priv->mimetypes, 0);
5139 		for (c = n->children; c != NULL; c = c->next) {
5140 			if (as_node_get_tag (c) != AS_TAG_MIMETYPE)
5141 				continue;
5142 			str = as_node_get_data_as_refstr (c);
5143 			if (str == NULL)
5144 				continue;
5145 			g_ptr_array_add (priv->mimetypes, as_ref_string_ref (str));
5146 		}
5147 		if (n->children == NULL)
5148 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5149 		break;
5150 
5151 	/* <project_license> */
5152 	case AS_TAG_PROJECT_LICENSE:
5153 		if (as_node_get_attribute (n, "xml:lang") != NULL) {
5154 			priv->problems |= AS_APP_PROBLEM_TRANSLATED_LICENSE;
5155 			break;
5156 		}
5157 		as_ref_string_assign (&priv->project_license, as_node_get_data_as_refstr (n));
5158 		break;
5159 
5160 	/* <project_license> */
5161 	case AS_TAG_METADATA_LICENSE:
5162 		if (as_node_get_attribute (n, "xml:lang") != NULL) {
5163 			priv->problems |= AS_APP_PROBLEM_TRANSLATED_LICENSE;
5164 			break;
5165 		}
5166 		as_app_set_metadata_license (app, as_node_get_data (n));
5167 		break;
5168 
5169 	/* <source_pkgname> */
5170 	case AS_TAG_SOURCE_PKGNAME:
5171 		as_app_set_source_pkgname (app, as_node_get_data (n));
5172 		break;
5173 
5174 	/* <update_contact> */
5175 	case AS_TAG_UPDATE_CONTACT:
5176 
5177 		/* this is the old name */
5178 		if (g_strcmp0 (as_node_get_name (n), "updatecontact") == 0)
5179 			priv->problems |= AS_APP_PROBLEM_UPDATECONTACT_FALLBACK;
5180 
5181 		as_app_set_update_contact (app, as_node_get_data (n));
5182 		break;
5183 
5184 	/* <url> */
5185 	case AS_TAG_URL:
5186 		tmp = as_node_get_attribute (n, "type");
5187 		as_app_add_url (app,
5188 				as_url_kind_from_string (tmp),
5189 				as_node_get_data (n));
5190 		break;
5191 
5192 	/* <project_group> */
5193 	case AS_TAG_PROJECT_GROUP:
5194 		if (as_node_get_attribute (n, "xml:lang") != NULL) {
5195 			priv->problems |= AS_APP_PROBLEM_TRANSLATED_PROJECT_GROUP;
5196 			break;
5197 		}
5198 		as_ref_string_assign (&priv->project_group,
5199 				      as_node_get_data_as_refstr (n));
5200 		break;
5201 
5202 	/* <compulsory_for_desktop> */
5203 	case AS_TAG_COMPULSORY_FOR_DESKTOP:
5204 		str = as_node_get_data_as_refstr (n);
5205 		if (str == NULL)
5206 			break;
5207 		g_ptr_array_add (priv->compulsory_for_desktops,
5208 				 as_ref_string_ref (str));
5209 		break;
5210 
5211 	/* <extends> */
5212 	case AS_TAG_EXTENDS:
5213 		str = as_node_get_data_as_refstr (n);
5214 		if (str == NULL)
5215 			break;
5216 		g_ptr_array_add (priv->extends, as_ref_string_ref (str));
5217 		break;
5218 
5219 	/* <screenshots> */
5220 	case AS_TAG_SCREENSHOTS:
5221 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5222 			g_ptr_array_set_size (priv->screenshots, 0);
5223 		for (c = n->children; c != NULL; c = c->next) {
5224 			g_autoptr(AsScreenshot) ss = NULL;
5225 			if (as_node_get_tag (c) != AS_TAG_SCREENSHOT)
5226 				continue;
5227 			/* we don't yet support localised screenshots */
5228 			if (as_node_get_attribute (c, "xml:lang") != NULL)
5229 				continue;
5230 			ss = as_screenshot_new ();
5231 			if (!as_screenshot_node_parse (ss, c, ctx, error))
5232 				return FALSE;
5233 			as_app_add_screenshot (app, ss);
5234 		}
5235 		if (n->children == NULL)
5236 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5237 		break;
5238 
5239 	/* <reviews> */
5240 	case AS_TAG_REVIEWS:
5241 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5242 			g_ptr_array_set_size (priv->reviews, 0);
5243 		for (c = n->children; c != NULL; c = c->next) {
5244 			g_autoptr(AsReview) review = NULL;
5245 			if (as_node_get_tag (c) != AS_TAG_REVIEW)
5246 				continue;
5247 			review = as_review_new ();
5248 			if (!as_review_node_parse (review, c, ctx, error))
5249 				return FALSE;
5250 			as_app_add_review (app, review);
5251 		}
5252 		break;
5253 
5254 	/* <content_ratings> */
5255 	case AS_TAG_CONTENT_RATING:
5256 	{
5257 		g_autoptr(AsContentRating) content_rating = NULL;
5258 		content_rating = as_content_rating_new ();
5259 		if (!as_content_rating_node_parse (content_rating, n, ctx, error))
5260 			return FALSE;
5261 		as_app_add_content_rating (app, content_rating);
5262 		break;
5263 	}
5264 
5265 	/* <agreements> */
5266 	case AS_TAG_AGREEMENT:
5267 	{
5268 		g_autoptr(AsAgreement) agreement = NULL;
5269 		agreement = as_agreement_new ();
5270 		if (!as_agreement_node_parse (agreement, n, ctx, error))
5271 			return FALSE;
5272 		as_app_add_agreement (app, agreement);
5273 		break;
5274 	}
5275 
5276 	/* <releases> */
5277 	case AS_TAG_RELEASES:
5278 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5279 			g_ptr_array_set_size (priv->releases, 0);
5280 		for (c = n->children; c != NULL; c = c->next) {
5281 			g_autoptr(AsRelease) r = NULL;
5282 			if (as_node_get_tag (c) != AS_TAG_RELEASE)
5283 				continue;
5284 			r = as_release_new ();
5285 			if (!as_release_node_parse (r, c, ctx, error))
5286 				return FALSE;
5287 			as_app_add_release (app, r);
5288 		}
5289 		if (n->children == NULL)
5290 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5291 		break;
5292 
5293 	/* <provides> */
5294 	case AS_TAG_PROVIDES:
5295 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5296 			g_ptr_array_set_size (priv->provides, 0);
5297 		for (c = n->children; c != NULL; c = c->next) {
5298 			g_autoptr(AsProvide) p = NULL;
5299 			p = as_provide_new ();
5300 			if (!as_provide_node_parse (p, c, ctx, error))
5301 				return FALSE;
5302 			as_app_add_provide (app, p);
5303 		}
5304 		break;
5305 
5306 	/* <launchables> */
5307 	case AS_TAG_LAUNCHABLE:
5308 	{
5309 		g_autoptr(AsLaunchable) lau = NULL;
5310 		lau = as_launchable_new ();
5311 		if (!as_launchable_node_parse (lau, n, ctx, error))
5312 			return FALSE;
5313 		as_app_add_launchable (app, lau);
5314 		break;
5315 	}
5316 
5317 	/* <languages> */
5318 	case AS_TAG_LANGUAGES:
5319 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5320 			g_hash_table_remove_all (priv->languages);
5321 		for (c = n->children; c != NULL; c = c->next) {
5322 			gint percent;
5323 			if (as_node_get_tag (c) != AS_TAG_LANG)
5324 				continue;
5325 			percent = as_node_get_attribute_as_int (c, "percentage");
5326 			if (percent == G_MAXINT)
5327 				percent = 0;
5328 			g_hash_table_insert (priv->languages,
5329 					     as_ref_string_ref (as_node_get_data_as_refstr (c)),
5330 					     GINT_TO_POINTER (percent));
5331 		}
5332 		if (n->children == NULL)
5333 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5334 		break;
5335 
5336 	/* <custom> or <metadata> */
5337 	case AS_TAG_METADATA:
5338 	case AS_TAG_CUSTOM:
5339 		if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA))
5340 			g_hash_table_remove_all (priv->metadata);
5341 		for (c = n->children; c != NULL; c = c->next) {
5342 			AsRefString *key;
5343 			AsRefString *value;
5344 			if (as_node_get_tag (c) != AS_TAG_VALUE)
5345 				continue;
5346 			key = as_node_get_attribute_as_refstr (c, "key");
5347 			value = as_node_get_data_as_refstr (c);
5348 			if (value == NULL) {
5349 				g_hash_table_insert (priv->metadata,
5350 						     as_ref_string_ref (key),
5351 						     as_ref_string_new_static (""));
5352 			} else {
5353 				g_hash_table_insert (priv->metadata,
5354 						     as_ref_string_ref (key),
5355 						     as_ref_string_ref (value));
5356 			}
5357 		}
5358 		if (n->children == NULL)
5359 			priv->problems |= AS_APP_PROBLEM_EXPECTED_CHILDREN;
5360 		break;
5361 	default:
5362 		priv->problems |= AS_APP_PROBLEM_INVALID_XML_TAG;
5363 		break;
5364 	}
5365 	return TRUE;
5366 }
5367 
5368 static void
as_app_check_for_hidpi_icons(AsApp * app)5369 as_app_check_for_hidpi_icons (AsApp *app)
5370 {
5371 	AsAppPrivate *priv = GET_PRIVATE (app);
5372 	AsIcon *icon_tmp;
5373 	g_autofree gchar *fn_size = NULL;
5374 	g_autoptr(AsIcon) icon_hidpi = NULL;
5375 
5376 	/* does the file exist */
5377 	icon_tmp = as_app_get_icon_default (app);
5378 	fn_size = g_build_filename (priv->icon_path,
5379 				    "128x128",
5380 				    as_icon_get_name (icon_tmp),
5381 				    NULL);
5382 	if (!g_file_test (fn_size, G_FILE_TEST_EXISTS))
5383 		return;
5384 
5385 	/* create the HiDPI version */
5386 	icon_hidpi = as_icon_new ();
5387 	as_icon_set_prefix (icon_hidpi, priv->icon_path);
5388 	as_icon_set_name (icon_hidpi, as_icon_get_name (icon_tmp));
5389 	as_icon_set_width (icon_hidpi, 128);
5390 	as_icon_set_height (icon_hidpi, 128);
5391 	as_app_add_icon (app, icon_hidpi);
5392 }
5393 
5394 static gboolean
as_app_node_parse_full(AsApp * app,GNode * node,guint32 flags,AsNodeContext * ctx,GError ** error)5395 as_app_node_parse_full (AsApp *app, GNode *node, guint32 flags,
5396 			AsNodeContext *ctx, GError **error)
5397 {
5398 	AsAppPrivate *priv = GET_PRIVATE (app);
5399 	GNode *n;
5400 	const gchar *tmp;
5401 	gint prio;
5402 
5403 	/* new style */
5404 	if (g_strcmp0 (as_node_get_name (node), "component") == 0) {
5405 		tmp = as_node_get_attribute (node, "type");
5406 		if (tmp == NULL)
5407 			as_app_set_kind (app, AS_APP_KIND_GENERIC);
5408 		else
5409 			as_app_set_kind (app, as_app_kind_from_string (tmp));
5410 		tmp = as_node_get_attribute (node, "merge");
5411 		if (tmp != NULL)
5412 			as_app_set_merge_kind (app, as_app_merge_kind_from_string (tmp));
5413 		prio = as_node_get_attribute_as_int (node, "priority");
5414 		if (prio != G_MAXINT && prio != 0)
5415 			as_app_set_priority (app, prio);
5416 	}
5417 
5418 	/* parse each node */
5419 	if (!(flags & AS_APP_PARSE_FLAG_APPEND_DATA)) {
5420 		g_ptr_array_set_size (priv->compulsory_for_desktops, 0);
5421 		g_ptr_array_set_size (priv->pkgnames, 0);
5422 		g_ptr_array_set_size (priv->architectures, 0);
5423 		g_ptr_array_set_size (priv->extends, 0);
5424 		g_ptr_array_set_size (priv->icons, 0);
5425 		g_ptr_array_set_size (priv->bundles, 0);
5426 		g_ptr_array_set_size (priv->translations, 0);
5427 		g_ptr_array_set_size (priv->suggests, 0);
5428 		g_ptr_array_set_size (priv->requires, 0);
5429 		g_ptr_array_set_size (priv->content_ratings, 0);
5430 		g_ptr_array_set_size (priv->agreements, 0);
5431 		g_ptr_array_set_size (priv->launchables, 0);
5432 		g_hash_table_remove_all (priv->keywords);
5433 	}
5434 	for (n = node->children; n != NULL; n = n->next) {
5435 		if (!as_app_node_parse_child (app, n, flags, ctx, error))
5436 			return FALSE;
5437 	}
5438 
5439 	/* if only one icon is listed, look for HiDPI versions too */
5440 	if (as_app_get_icons(app)->len == 1)
5441 		as_app_check_for_hidpi_icons (app);
5442 
5443 	/* add the launchable if missing for desktop apps */
5444 	if (priv->launchables->len == 0 &&
5445 	    priv->kind == AS_APP_KIND_DESKTOP &&
5446 	    priv->id != NULL) {
5447 		AsLaunchable *lau = as_launchable_new ();
5448 		as_launchable_set_kind (lau, AS_LAUNCHABLE_KIND_DESKTOP_ID);
5449 		if (g_str_has_suffix (priv->id, ".desktop")) {
5450 			as_launchable_set_value (lau, priv->id);
5451 		} else {
5452 			g_autofree gchar *id_tmp = NULL;
5453 			id_tmp = g_strdup_printf ("%s.desktop", priv->id);
5454 			as_launchable_set_value (lau, id_tmp);
5455 		}
5456 		g_ptr_array_add (priv->launchables, lau);
5457 	}
5458 
5459 	return TRUE;
5460 }
5461 
5462 /**
5463  * as_app_node_parse:
5464  * @app: a #AsApp instance.
5465  * @node: a #GNode.
5466  * @ctx: a #AsNodeContext.
5467  * @error: A #GError or %NULL.
5468  *
5469  * Populates the object from a DOM node.
5470  *
5471  * Returns: %TRUE for success
5472  *
5473  * Since: 0.1.0
5474  **/
5475 gboolean
as_app_node_parse(AsApp * app,GNode * node,AsNodeContext * ctx,GError ** error)5476 as_app_node_parse (AsApp *app, GNode *node, AsNodeContext *ctx, GError **error)
5477 {
5478 	return as_app_node_parse_full (app, node, AS_APP_PARSE_FLAG_NONE, ctx, error);
5479 }
5480 
5481 static gboolean
as_app_node_parse_dep11_icons(AsApp * app,GNode * node,AsNodeContext * ctx,GError ** error)5482 as_app_node_parse_dep11_icons (AsApp *app, GNode *node,
5483 			       AsNodeContext *ctx, GError **error)
5484 {
5485 	AsAppPrivate *priv = GET_PRIVATE (app);
5486 	const gchar *sizes[] = { "128x128", "64x64", "", NULL };
5487 	guint i;
5488 	guint size;
5489 	g_autoptr(AsIcon) ic_tmp = NULL;
5490 
5491 	if (g_strcmp0 (as_yaml_node_get_key (node), "cached") == 0) {
5492 		if (node->children == NULL) {
5493 			/* legacy compatibility */
5494 			ic_tmp = as_icon_new ();
5495 			as_icon_set_kind (ic_tmp, AS_ICON_KIND_CACHED);
5496 			as_icon_set_name (ic_tmp, as_yaml_node_get_value (node));
5497 		} else {
5498 			GNode *sn;
5499 			/* we have a modern YAML file */
5500 			for (sn = node->children; sn != NULL; sn = sn->next) {
5501 				g_autoptr(AsIcon) icon = NULL;
5502 				icon = as_icon_new ();
5503 				as_icon_set_kind (icon, AS_ICON_KIND_CACHED);
5504 				as_icon_set_prefix (icon, priv->icon_path);
5505 				if (!as_icon_node_parse_dep11 (icon, sn, ctx, error))
5506 					return FALSE;
5507 				as_app_add_icon (app, icon);
5508 			}
5509 		}
5510 	} else if (g_strcmp0 (as_yaml_node_get_key (node), "stock") == 0) {
5511 		g_autoptr(AsIcon) icon = NULL;
5512 		icon = as_icon_new ();
5513 		as_icon_set_name (icon, as_yaml_node_get_value (node));
5514 		as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
5515 		as_icon_set_prefix (icon, priv->icon_path);
5516 		as_app_add_icon (app, icon);
5517 	} else {
5518 		GNode *sn;
5519 		AsIconKind ikind;
5520 
5521 		if (g_strcmp0 (as_yaml_node_get_key (node), "remote") == 0) {
5522 			ikind = AS_ICON_KIND_REMOTE;
5523 		} else if (g_strcmp0 (as_yaml_node_get_key (node), "local") == 0) {
5524 			ikind = AS_ICON_KIND_REMOTE;
5525 		} else {
5526 			/* We have an unknown icon type, and just ignore that here */
5527 			return TRUE;
5528 		}
5529 
5530 		for (sn = node->children; sn != NULL; sn = sn->next) {
5531 			g_autoptr(AsIcon) icon = NULL;
5532 			icon = as_icon_new ();
5533 			as_icon_set_kind (icon, ikind);
5534 			if (!as_icon_node_parse_dep11 (icon, sn, ctx, error))
5535 				return FALSE;
5536 			as_app_add_icon (app, icon);
5537 		}
5538 	}
5539 
5540 	if (ic_tmp == NULL) {
5541 		/* we have no icon which we need to probe sizes for */
5542 		return TRUE;
5543 	}
5544 
5545 	/* find each size */
5546 	for (i = 0; sizes[i] != NULL; i++) {
5547 		g_autofree gchar *path = NULL;
5548 		g_autofree gchar *size_name = NULL;
5549 		g_autoptr(AsIcon) ic = NULL;
5550 
5551 		size_name = g_build_filename (sizes[i],
5552 					      as_icon_get_name (ic_tmp),
5553 					      NULL);
5554 		path = g_build_filename (priv->icon_path,
5555 					 size_name,
5556 					 NULL);
5557 		if (!g_file_test (path, G_FILE_TEST_EXISTS))
5558 			continue;
5559 
5560 		/* only the first try is a HiDPI icon, assume 64px otherwise */
5561 		size = (i == 0) ? 128 : 64;
5562 		ic = as_icon_new ();
5563 		as_icon_set_kind (ic, AS_ICON_KIND_CACHED);
5564 		as_icon_set_prefix (ic, priv->icon_path);
5565 		as_icon_set_name (ic, size_name);
5566 		as_icon_set_width (ic, size);
5567 		as_icon_set_height (ic, size);
5568 		as_app_add_icon (app, ic);
5569 	}
5570 	return TRUE;
5571 }
5572 
5573 /**
5574  * as_app_node_parse_dep11:
5575  * @app: a #AsApp instance.
5576  * @node: a #GNode.
5577  * @ctx: a #AsNodeContext.
5578  * @error: A #GError or %NULL.
5579  *
5580  * Populates the object from a DEP-11 node.
5581  *
5582  * Returns: %TRUE for success
5583  *
5584  * Since: 0.3.0
5585  **/
5586 gboolean
as_app_node_parse_dep11(AsApp * app,GNode * node,AsNodeContext * ctx,GError ** error)5587 as_app_node_parse_dep11 (AsApp *app, GNode *node,
5588 			 AsNodeContext *ctx, GError **error)
5589 {
5590 	GNode *c;
5591 	GNode *c2;
5592 	GNode *n;
5593 	const gchar *nonfatal_str = NULL;
5594 	const gchar *tmp;
5595 
5596 	for (n = node->children; n != NULL; n = n->next) {
5597 		tmp = as_yaml_node_get_key (n);
5598 		if (g_strcmp0 (tmp, "ID") == 0) {
5599 			as_app_set_id (app, as_yaml_node_get_value (n));
5600 			continue;
5601 		}
5602 		if (g_strcmp0 (tmp, "Type") == 0) {
5603 			tmp = as_yaml_node_get_value (n);
5604 			if (tmp == NULL)
5605 				continue;
5606 			as_app_set_kind (app, as_app_kind_from_string (tmp));
5607 			continue;
5608 		}
5609 		if (g_strcmp0 (tmp, "Package") == 0) {
5610 			as_app_add_pkgname (app, as_yaml_node_get_value (n));
5611 			continue;
5612 		}
5613 		if (g_strcmp0 (tmp, "Name") == 0) {
5614 			for (c = n->children; c != NULL; c = c->next) {
5615 				if (as_yaml_node_get_key (c) == NULL)
5616 					continue;
5617 				as_app_set_name (app,
5618 						 as_yaml_node_get_key (c),
5619 						 as_yaml_node_get_value (c));
5620 			}
5621 			continue;
5622 		}
5623 		if (g_strcmp0 (tmp, "Summary") == 0) {
5624 			for (c = n->children; c != NULL; c = c->next) {
5625 				if (as_yaml_node_get_key (c) == NULL)
5626 					continue;
5627 				as_app_set_comment (app,
5628 						    as_yaml_node_get_key (c),
5629 						    as_yaml_node_get_value (c));
5630 			}
5631 			continue;
5632 		}
5633 		if (g_strcmp0 (tmp, "Description") == 0) {
5634 			for (c = n->children; c != NULL; c = c->next) {
5635 				if (as_yaml_node_get_key (c) == NULL)
5636 					continue;
5637 				as_app_set_description (app,
5638 							as_yaml_node_get_key (c),
5639 							as_yaml_node_get_value (c));
5640 			}
5641 			continue;
5642 		}
5643 		if (g_strcmp0 (tmp, "Keywords") == 0) {
5644 			for (c = n->children; c != NULL; c = c->next) {
5645 				for (c2 = c->children; c2 != NULL; c2 = c2->next) {
5646 					if (as_yaml_node_get_key (c2) == NULL)
5647 						continue;
5648 					if (as_yaml_node_get_key (c) == NULL)
5649 						continue;
5650 					as_app_add_keyword (app,
5651 							   as_yaml_node_get_key (c),
5652 							   as_yaml_node_get_key (c2));
5653 				}
5654 			}
5655 			continue;
5656 		}
5657 		if (g_strcmp0 (tmp, "Categories") == 0) {
5658 			for (c = n->children; c != NULL; c = c->next) {
5659 				tmp = as_yaml_node_get_key (c);
5660 				if (tmp == NULL) {
5661 					nonfatal_str = "contained empty category";
5662 					continue;
5663 				}
5664 				as_app_add_category (app, tmp);
5665 			}
5666 			continue;
5667 		}
5668 		if (g_strcmp0 (tmp, "Icon") == 0) {
5669 			for (c = n->children; c != NULL; c = c->next) {
5670 				if (!as_app_node_parse_dep11_icons (app, c, ctx, error))
5671 					return FALSE;
5672 			}
5673 			continue;
5674 		}
5675 		if (g_strcmp0 (tmp, "Bundle") == 0) {
5676 			for (c = n->children; c != NULL; c = c->next) {
5677 				g_autoptr(AsBundle) bu = NULL;
5678 				bu = as_bundle_new ();
5679 				if (!as_bundle_node_parse_dep11 (bu, c, ctx, error))
5680 					return FALSE;
5681 				as_app_add_bundle (app, bu);
5682 			}
5683 			continue;
5684 		}
5685 		if (g_strcmp0 (tmp, "Translation") == 0) {
5686 			for (c = n->children; c != NULL; c = c->next) {
5687 				g_autoptr(AsTranslation) bu = NULL;
5688 				bu = as_translation_new ();
5689 				if (!as_translation_node_parse_dep11 (bu, c, ctx, error))
5690 					return FALSE;
5691 				as_app_add_translation (app, bu);
5692 			}
5693 			continue;
5694 		}
5695 		if (g_strcmp0 (tmp, "Suggests") == 0) {
5696 			for (c = n->children; c != NULL; c = c->next) {
5697 				g_autoptr(AsSuggest) bu = NULL;
5698 				bu = as_suggest_new ();
5699 				if (!as_suggest_node_parse_dep11 (bu, c, ctx, error))
5700 					return FALSE;
5701 				as_app_add_suggest (app, bu);
5702 			}
5703 			continue;
5704 		}
5705 		if (g_strcmp0 (tmp, "Url") == 0) {
5706 			for (c = n->children; c != NULL; c = c->next) {
5707 				if (g_strcmp0 (as_yaml_node_get_key (c), "homepage") == 0) {
5708 					as_app_add_url (app,
5709 							AS_URL_KIND_HOMEPAGE,
5710 							as_yaml_node_get_value (c));
5711 					continue;
5712 				}
5713 			}
5714 			continue;
5715 		}
5716 		if (g_strcmp0 (tmp, "Provides") == 0) {
5717 			for (c = n->children; c != NULL; c = c->next) {
5718 				if (g_strcmp0 (as_yaml_node_get_key (c), "mimetypes") == 0) {
5719 					for (c2 = c->children; c2 != NULL; c2 = c2->next) {
5720 						as_app_add_mimetype (app,
5721 								     as_yaml_node_get_key (c2));
5722 					}
5723 					continue;
5724 				} else {
5725 					g_autoptr(AsProvide) pr = NULL;
5726 					pr = as_provide_new ();
5727 					if (!as_provide_node_parse_dep11 (pr, c, ctx, error))
5728 						return FALSE;
5729 					as_app_add_provide (app, pr);
5730 				}
5731 			}
5732 			continue;
5733 		}
5734 		if (g_strcmp0 (tmp, "Screenshots") == 0) {
5735 			for (c = n->children; c != NULL; c = c->next) {
5736 				g_autoptr(AsScreenshot) ss = NULL;
5737 				ss = as_screenshot_new ();
5738 				if (!as_screenshot_node_parse_dep11 (ss, c, ctx, error))
5739 					return FALSE;
5740 				as_app_add_screenshot (app, ss);
5741 			}
5742 			continue;
5743 		}
5744 		if (g_strcmp0 (tmp, "Reviews") == 0) {
5745 			for (c = n->children; c != NULL; c = c->next) {
5746 				g_autoptr(AsReview) review = NULL;
5747 				review = as_review_new ();
5748 				if (!as_review_node_parse_dep11 (review, c, ctx, error))
5749 					return FALSE;
5750 				as_app_add_review (app, review);
5751 			}
5752 			continue;
5753 		}
5754 		if (g_strcmp0 (tmp, "Extends") == 0) {
5755 			for (c = n->children; c != NULL; c = c->next)
5756 				as_app_add_extends (app, as_yaml_node_get_key (c));
5757 			continue;
5758 		}
5759 		if (g_strcmp0 (tmp, "Releases") == 0) {
5760 			for (c = n->children; c != NULL; c = c->next) {
5761 				g_autoptr(AsRelease) rel = NULL;
5762 				rel = as_release_new ();
5763 				if (!as_release_node_parse_dep11 (rel, c, ctx, error))
5764 					return FALSE;
5765 				as_app_add_release (app, rel);
5766 			}
5767 			continue;
5768 		}
5769 		if (g_strcmp0 (tmp, "DeveloperName") == 0) {
5770 			for (c = n->children; c != NULL; c = c->next) {
5771 				as_app_set_developer_name (app,
5772 							   as_yaml_node_get_key (c),
5773 							   as_yaml_node_get_value (c));
5774 			}
5775 			continue;
5776 		}
5777 		if (g_strcmp0 (tmp, "ProjectLicense") == 0) {
5778 			as_app_set_project_license (app, as_yaml_node_get_value (n));
5779 			continue;
5780 		}
5781 		if (g_strcmp0 (tmp, "ProjectGroup") == 0) {
5782 			as_app_set_project_group (app, as_yaml_node_get_value (n));
5783 			continue;
5784 		}
5785 		if (g_strcmp0 (tmp, "CompulsoryForDesktops") == 0) {
5786 			for (c = n->children; c != NULL; c = c->next) {
5787 				tmp = as_yaml_node_get_key (c);
5788 				if (tmp == NULL) {
5789 					nonfatal_str = "contained empty desktop";
5790 					continue;
5791 				}
5792 				as_app_add_compulsory_for_desktop (app, tmp);
5793 			}
5794 			continue;
5795 		}
5796 	}
5797 	if (nonfatal_str != NULL) {
5798 		g_debug ("nonfatal warning from %s: %s",
5799 			 as_app_get_id (app), nonfatal_str);
5800 	}
5801 	return TRUE;
5802 }
5803 
5804 static gchar **
as_app_value_tokenize(const gchar * value)5805 as_app_value_tokenize (const gchar *value)
5806 {
5807 	g_autofree gchar *delim = NULL;
5808 	delim = g_utf8_strdown (value, -1);
5809 	g_strdelimit (delim, "/,.;:", ' ');
5810 	return g_strsplit (delim, " ", -1);
5811 }
5812 
5813 static void
as_app_add_token_internal(AsApp * app,const gchar * value,guint16 match_flag)5814 as_app_add_token_internal (AsApp *app,
5815 			   const gchar *value,
5816 			   guint16 match_flag)
5817 {
5818 	AsAppPrivate *priv = GET_PRIVATE (app);
5819 	AsAppTokenType *match_pval;
5820 	g_autoptr(AsRefString) value_stem = NULL;
5821 
5822 	/* invalid */
5823 	if (!as_utils_search_token_valid (value))
5824 		return;
5825 
5826 	/* get the most suitable value */
5827 	if (priv->stemmer != NULL)
5828 		value_stem = as_stemmer_process (priv->stemmer, value);
5829 	if (value_stem == NULL)
5830 		return;
5831 
5832 	/* blacklisted */
5833 	if (priv->search_blacklist != NULL &&
5834 	    g_hash_table_lookup (priv->search_blacklist, value_stem) != NULL)
5835 		return;
5836 
5837 	/* does the token already exist */
5838 	match_pval = g_hash_table_lookup (priv->token_cache, value_stem);
5839 	if (match_pval != NULL) {
5840 		*match_pval |= match_flag;
5841 		return;
5842 	}
5843 
5844 	/* create and add */
5845 	match_pval = g_new0 (AsAppTokenType, 1);
5846 	*match_pval = match_flag;
5847 	g_hash_table_insert (priv->token_cache,
5848 			     as_ref_string_ref (value_stem),
5849 			     match_pval);
5850 }
5851 
5852 static void
as_app_add_token(AsApp * app,const gchar * value,gboolean allow_split,guint16 match_flag)5853 as_app_add_token (AsApp *app,
5854 		  const gchar *value,
5855 		  gboolean allow_split,
5856 		  guint16 match_flag)
5857 {
5858 	/* add extra tokens for names like x-plane or half-life */
5859 	if (allow_split && g_strstr_len (value, -1, "-") != NULL) {
5860 		guint i;
5861 		g_auto(GStrv) split = g_strsplit (value, "-", -1);
5862 		for (i = 0; split[i] != NULL; i++)
5863 			as_app_add_token_internal (app, split[i], match_flag);
5864 	}
5865 
5866 	/* add the whole token always, even when we split on hyphen */
5867 	as_app_add_token_internal (app, value, match_flag);
5868 }
5869 
5870 static gboolean
as_app_needs_transliteration(const gchar * locale)5871 as_app_needs_transliteration (const gchar *locale)
5872 {
5873 	if (g_strcmp0 (locale, "C") == 0)
5874 		return FALSE;
5875 	if (g_strcmp0 (locale, "en") == 0)
5876 		return FALSE;
5877 	if (g_str_has_prefix (locale, "en_"))
5878 		return FALSE;
5879 	return TRUE;
5880 }
5881 
5882 static void
as_app_add_tokens(AsApp * app,const gchar * value,const gchar * locale,gboolean allow_split,guint16 match_flag)5883 as_app_add_tokens (AsApp *app,
5884 		   const gchar *value,
5885 		   const gchar *locale,
5886 		   gboolean allow_split,
5887 		   guint16 match_flag)
5888 {
5889 	guint i;
5890 	g_auto(GStrv) values_utf8 = NULL;
5891 	g_auto(GStrv) values_ascii = NULL;
5892 
5893 	/* sanity check */
5894 	if (value == NULL) {
5895 		g_critical ("trying to add NULL search token to %s",
5896 			    as_app_get_id (app));
5897 		return;
5898 	}
5899 
5900 #if GLIB_CHECK_VERSION(2,39,1)
5901 	/* tokenize with UTF-8 fallbacks */
5902 	if (as_app_needs_transliteration (locale) &&
5903 	    g_strstr_len (value, -1, "+") == NULL &&
5904 	    g_strstr_len (value, -1, "-") == NULL) {
5905 		values_utf8 = g_str_tokenize_and_fold (value, locale, &values_ascii);
5906 	}
5907 #endif
5908 	if (values_utf8 == NULL)
5909 		values_utf8 = as_app_value_tokenize (value);
5910 
5911 	/* add each token */
5912 	for (i = 0; values_utf8 != NULL && values_utf8[i] != NULL; i++)
5913 		as_app_add_token (app, values_utf8[i], allow_split, match_flag);
5914 	for (i = 0; values_ascii != NULL && values_ascii[i] != NULL; i++)
5915 		as_app_add_token (app, values_ascii[i], allow_split, match_flag);
5916 }
5917 
5918 static void
as_app_create_token_cache_target(AsApp * app,AsApp * donor)5919 as_app_create_token_cache_target (AsApp *app, AsApp *donor)
5920 {
5921 	AsAppPrivate *priv = GET_PRIVATE (donor);
5922 	GPtrArray *array;
5923 	const gchar * const *locales;
5924 	const gchar *tmp;
5925 	guint i;
5926 	guint j;
5927 
5928 	/* add all the data we have */
5929 	if (priv->search_match & AS_APP_SEARCH_MATCH_ID) {
5930 		if (priv->id_filename != NULL) {
5931 			/* add the whole ID */
5932 			as_app_add_token (app, priv->id_filename, FALSE,
5933 					  AS_APP_SEARCH_MATCH_ID);
5934 			/* tokenize and add individual parts */
5935 			as_app_add_tokens (app, priv->id_filename, "C", FALSE,
5936 					   AS_APP_SEARCH_MATCH_ID);
5937 		}
5938 	}
5939 	locales = g_get_language_names ();
5940 	for (i = 0; locales[i] != NULL; i++) {
5941 		if (g_str_has_suffix (locales[i], ".UTF-8"))
5942 			continue;
5943 		if (priv->search_match & AS_APP_SEARCH_MATCH_NAME) {
5944 			tmp = as_app_get_name (app, locales[i]);
5945 			if (tmp != NULL) {
5946 				as_app_add_tokens (app, tmp, locales[i], TRUE,
5947 						   AS_APP_SEARCH_MATCH_NAME);
5948 			}
5949 		}
5950 		if (priv->search_match & AS_APP_SEARCH_MATCH_COMMENT) {
5951 			tmp = as_app_get_comment (app, locales[i]);
5952 			if (tmp != NULL) {
5953 				as_app_add_tokens (app, tmp, locales[i], TRUE,
5954 						   AS_APP_SEARCH_MATCH_COMMENT);
5955 			}
5956 		}
5957 		if (priv->search_match & AS_APP_SEARCH_MATCH_DESCRIPTION) {
5958 			tmp = as_app_get_description (app, locales[i]);
5959 			if (tmp != NULL) {
5960 				as_app_add_tokens (app, tmp, locales[i], FALSE,
5961 						   AS_APP_SEARCH_MATCH_DESCRIPTION);
5962 			}
5963 		}
5964 		if (priv->search_match & AS_APP_SEARCH_MATCH_KEYWORD) {
5965 			array = as_app_get_keywords (app, locales[i]);
5966 			if (array != NULL) {
5967 				for (j = 0; j < array->len; j++) {
5968 					tmp = g_ptr_array_index (array, j);
5969 					as_app_add_tokens (app, tmp, locales[i], FALSE,
5970 							   AS_APP_SEARCH_MATCH_KEYWORD);
5971 				}
5972 			}
5973 		}
5974 	}
5975 	if (priv->search_match & AS_APP_SEARCH_MATCH_MIMETYPE) {
5976 		for (i = 0; i < priv->mimetypes->len; i++) {
5977 			tmp = g_ptr_array_index (priv->mimetypes, i);
5978 			as_app_add_token (app, tmp, FALSE, AS_APP_SEARCH_MATCH_MIMETYPE);
5979 		}
5980 	}
5981 	if (priv->search_match & AS_APP_SEARCH_MATCH_PKGNAME) {
5982 		for (i = 0; i < priv->pkgnames->len; i++) {
5983 			tmp = g_ptr_array_index (priv->pkgnames, i);
5984 			as_app_add_token (app, tmp, FALSE, AS_APP_SEARCH_MATCH_PKGNAME);
5985 		}
5986 	}
5987 	if (priv->search_match & AS_APP_SEARCH_MATCH_ORIGIN) {
5988 		if (priv->origin != NULL) {
5989 			as_app_add_token (app, priv->origin, TRUE,
5990 					  AS_APP_SEARCH_MATCH_ORIGIN);
5991 		}
5992 	}
5993 }
5994 
5995 static void
as_app_create_token_cache(AsApp * app)5996 as_app_create_token_cache (AsApp *app)
5997 {
5998 	AsApp *donor;
5999 	AsAppPrivate *priv = GET_PRIVATE (app);
6000 	guint i;
6001 
6002 	as_app_create_token_cache_target (app, app);
6003 	for (i = 0; i < priv->addons->len; i++) {
6004 		donor = g_ptr_array_index (priv->addons, i);
6005 		as_app_create_token_cache_target (app, donor);
6006 	}
6007 }
6008 
6009 /**
6010  * as_app_search_matches:
6011  * @app: a #AsApp instance.
6012  * @search: the search term.
6013  *
6014  * Searches application data for a specific keyword.
6015  *
6016  * Returns: a match scrore, where 0 is no match and 100 is the best match.
6017  *
6018  * Since: 0.1.0
6019  **/
6020 guint
as_app_search_matches(AsApp * app,const gchar * search)6021 as_app_search_matches (AsApp *app, const gchar *search)
6022 {
6023 	AsAppPrivate *priv = GET_PRIVATE (app);
6024 	AsAppTokenType *match_pval;
6025 	GList *l;
6026 	guint16 result = 0;
6027 	g_autoptr(GList) keys = NULL;
6028 	g_autoptr(AsRefString) search_stem = NULL;
6029 
6030 	/* ensure the token cache is created */
6031 	if (g_once_init_enter (&priv->token_cache_valid)) {
6032 		as_app_create_token_cache (app);
6033 		g_once_init_leave (&priv->token_cache_valid, TRUE);
6034 	}
6035 
6036 	/* nothing to do */
6037 	if (search == NULL)
6038 		return 0;
6039 
6040 	/* find the exact match (which is more awesome than a partial match) */
6041 	if (priv->stemmer != NULL)
6042 		search_stem = as_stemmer_process (priv->stemmer, search);
6043 	if (search_stem == NULL)
6044 		return 0;
6045 	match_pval = g_hash_table_lookup (priv->token_cache, search_stem);
6046 	if (match_pval != NULL)
6047 		return (guint) *match_pval << 2;
6048 
6049 	/* need to do partial match */
6050 	keys = g_hash_table_get_keys (priv->token_cache);
6051 	for (l = keys; l != NULL; l = l->next) {
6052 		const gchar *key = l->data;
6053 		if (g_str_has_prefix (key, search_stem)) {
6054 			match_pval = g_hash_table_lookup (priv->token_cache, key);
6055 			result |= *match_pval;
6056 		}
6057 	}
6058 	return result;
6059 }
6060 
6061 /**
6062  * as_app_get_search_tokens:
6063  * @app: a #AsApp instance.
6064  *
6065  * Returns all the search tokens for the application. These are unsorted.
6066  *
6067  * Returns: (transfer container): The string search tokens
6068  *
6069  * Since: 0.3.4
6070  */
6071 GPtrArray *
as_app_get_search_tokens(AsApp * app)6072 as_app_get_search_tokens (AsApp *app)
6073 {
6074 	AsAppPrivate *priv = GET_PRIVATE (app);
6075 	GList *l;
6076 	GPtrArray *array;
6077 	g_autoptr(GList) keys = NULL;
6078 
6079 	/* ensure the token cache is created */
6080 	if (g_once_init_enter (&priv->token_cache_valid)) {
6081 		as_app_create_token_cache (app);
6082 		g_once_init_leave (&priv->token_cache_valid, TRUE);
6083 	}
6084 
6085 	/* return all the token cache */
6086 	keys = g_hash_table_get_keys (priv->token_cache);
6087 	array = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
6088 	for (l = keys; l != NULL; l = l->next)
6089 		g_ptr_array_add (array, as_ref_string_ref (l->data));
6090 	return array;
6091 }
6092 
6093 /**
6094  * as_app_search_matches_all:
6095  * @app: a #AsApp instance.
6096  * @search: the search terms.
6097  *
6098  * Searches application data for all the specific keywords.
6099  *
6100  * Returns: a match scrore, where 0 is no match and larger numbers are better
6101  * matches.
6102  *
6103  * It's probably a good idea to use as_utils_search_tokenize() to populate
6104  * search as very short or common keywords will return a lot of matches.
6105  *
6106  * Since: 0.1.3
6107  */
6108 guint
as_app_search_matches_all(AsApp * app,gchar ** search)6109 as_app_search_matches_all (AsApp *app, gchar **search)
6110 {
6111 	guint i;
6112 	guint matches_sum = 0;
6113 	guint tmp;
6114 
6115 	/* do *all* search keywords match */
6116 	for (i = 0; search[i] != NULL; i++) {
6117 		tmp = as_app_search_matches (app, search[i]);
6118 		if (tmp == 0)
6119 			return 0;
6120 		matches_sum |= tmp;
6121 	}
6122 	return matches_sum;
6123 }
6124 
6125 static gboolean
as_app_parse_appdata_unintltoolize_cb(GNode * node,gpointer data)6126 as_app_parse_appdata_unintltoolize_cb (GNode *node, gpointer data)
6127 {
6128 	AsAppPrivate *priv = GET_PRIVATE (AS_APP (data));
6129 	const gchar *name;
6130 
6131 	name = as_node_get_name (node);
6132 	if (g_strcmp0 (name, "_name") == 0) {
6133 		as_node_set_name (node, "name");
6134 		priv->problems |= AS_APP_PROBLEM_INTLTOOL_NAME;
6135 		return FALSE;
6136 	}
6137 	if (g_strcmp0 (name, "_summary") == 0) {
6138 		as_node_set_name (node, "summary");
6139 		priv->problems |= AS_APP_PROBLEM_INTLTOOL_SUMMARY;
6140 		return FALSE;
6141 	}
6142 	if (g_strcmp0 (name, "_caption") == 0) {
6143 		as_node_set_name (node, "caption");
6144 		return FALSE;
6145 	}
6146 	if (g_strcmp0 (name, "_p") == 0) {
6147 		as_node_set_name (node, "p");
6148 		priv->problems |= AS_APP_PROBLEM_INTLTOOL_DESCRIPTION;
6149 		return FALSE;
6150 	}
6151 	if (g_strcmp0 (name, "_li") == 0) {
6152 		as_node_set_name (node, "li");
6153 		priv->problems |= AS_APP_PROBLEM_INTLTOOL_DESCRIPTION;
6154 		return FALSE;
6155 	}
6156 	if (g_strcmp0 (name, "_ul") == 0) {
6157 		as_node_set_name (node, "ul");
6158 		priv->problems |= AS_APP_PROBLEM_INTLTOOL_DESCRIPTION;
6159 		return FALSE;
6160 	}
6161 	if (g_strcmp0 (name, "_ol") == 0) {
6162 		as_node_set_name (node, "ol");
6163 		priv->problems |= AS_APP_PROBLEM_INTLTOOL_DESCRIPTION;
6164 		return FALSE;
6165 	}
6166 	return FALSE;
6167 }
6168 
6169 static void
as_app_parse_appdata_guess_project_group(AsApp * app)6170 as_app_parse_appdata_guess_project_group (AsApp *app)
6171 {
6172 #ifndef _WIN32
6173 	const gchar *tmp;
6174 	struct {
6175 		const gchar *project_group;
6176 		const gchar *url_glob;
6177 	} table[] = {
6178 		{ "elementary",		"http*://elementary.io*" },
6179 		{ "Enlightenment",	"http://*enlightenment.org*" },
6180 		{ "GNOME",		"http*://*.gnome.org*" },
6181 		{ "GNOME",		"http://gnome-*.sourceforge.net/" },
6182 		{ "KDE",		"http://*kde-apps.org/*" },
6183 		{ "KDE",		"http*://*.kde.org*" },
6184 		{ "LXDE",		"http://lxde.org*" },
6185 		{ "LXDE",		"http://lxde.sourceforge.net/*" },
6186 		{ "LXDE",		"http://pcmanfm.sourceforge.net/*" },
6187 		{ "MATE",		"http://*mate-desktop.org*" },
6188 		{ "XFCE",		"http://*xfce.org*" },
6189 		{ NULL, NULL }
6190 	};
6191 
6192 	/* match a URL glob and set the project group */
6193 	tmp = as_app_get_url_item (app, AS_URL_KIND_HOMEPAGE);
6194 	if (tmp == NULL)
6195 		return;
6196 	for (guint i = 0; table[i].project_group != NULL; i++) {
6197 		if (fnmatch (table[i].url_glob, tmp, 0) == 0) {
6198 			as_app_set_project_group (app, table[i].project_group);
6199 			return;
6200 		}
6201 	}
6202 
6203 	/* use summary to guess the project group */
6204 	tmp = as_app_get_comment (AS_APP (app), NULL);
6205 	if (tmp != NULL && g_strstr_len (tmp, -1, "for KDE") != NULL) {
6206 		as_app_set_project_group (AS_APP (app), "KDE");
6207 		return;
6208 	}
6209 #endif
6210 }
6211 
6212 static int
as_utils_fnmatch(const gchar * pattern,const gchar * text,gsize text_sz,gint flags)6213 as_utils_fnmatch (const gchar *pattern, const gchar *text, gsize text_sz, gint flags)
6214 {
6215 #ifndef _WIN32
6216 	if (text_sz != -1 && text[text_sz-1] != '\0') {
6217 		g_autofree gchar *text_with_nul = g_strndup (text, text_sz);
6218 		return fnmatch (pattern, text_with_nul, flags);
6219 	}
6220 	return fnmatch (pattern, text, flags);
6221 #else
6222 	return 1;
6223 #endif
6224 }
6225 
6226 /**
6227  * as_app_parse_data:
6228  * @app: a #AsApp instance.
6229  * @data: data to parse.
6230  * @flags: #AsAppParseFlags, e.g. %AS_APP_PARSE_FLAG_USE_HEURISTICS
6231  * @error: A #GError or %NULL.
6232  *
6233  * Parses an AppData file and populates the application state.
6234  *
6235  * Returns: %TRUE for success
6236  *
6237  * Since: 0.7.5
6238  **/
6239 gboolean
as_app_parse_data(AsApp * app,GBytes * data,guint32 flags,GError ** error)6240 as_app_parse_data (AsApp *app, GBytes *data, guint32 flags, GError **error)
6241 {
6242 	AsAppPrivate *priv = GET_PRIVATE (app);
6243 	AsNodeFromXmlFlags from_xml_flags = AS_NODE_FROM_XML_FLAG_NONE;
6244 	GNode *node;
6245 	const gchar *data_raw;
6246 	gboolean seen_application = FALSE;
6247 	gsize len = 0;
6248 	g_autoptr(AsNodeContext) ctx = NULL;
6249 	g_autoptr(AsNode) root = NULL;
6250 
6251 	/* validate */
6252 	data_raw = g_bytes_get_data (data, &len);
6253 	if (g_str_has_prefix (data_raw, "[Desktop Entry]"))
6254 		return as_app_parse_desktop_data (app, data, flags, error);
6255 	if (g_strstr_len (data_raw, (gssize) len, "<?xml version=") == NULL)
6256 		priv->problems |= AS_APP_PROBLEM_NO_XML_HEADER;
6257 
6258 	/* check for copyright */
6259 	if (as_utils_fnmatch ("*<!--*Copyright*-->*", data_raw, len, 0) != 0)
6260 		priv->problems |= AS_APP_PROBLEM_NO_COPYRIGHT_INFO;
6261 
6262 	/* parse */
6263 	if (flags & AS_APP_PARSE_FLAG_KEEP_COMMENTS)
6264 		from_xml_flags |= AS_NODE_FROM_XML_FLAG_KEEP_COMMENTS;
6265 	root = as_node_from_bytes (data, from_xml_flags, error);
6266 	if (root == NULL)
6267 		return FALSE;
6268 
6269 	/* make the <_summary> tags into <summary> */
6270 	if (flags & AS_APP_PARSE_FLAG_CONVERT_TRANSLATABLE) {
6271 		g_node_traverse (root,
6272 				 G_IN_ORDER,
6273 				 G_TRAVERSE_ALL,
6274 				 10,
6275 				 as_app_parse_appdata_unintltoolize_cb,
6276 				 app);
6277 	}
6278 
6279 	node = as_node_find (root, "application");
6280 	if (node == NULL)
6281 		node = as_node_find (root, "component");
6282 	if (node == NULL) {
6283 		g_set_error_literal (error,
6284 				     AS_APP_ERROR,
6285 				     AS_APP_ERROR_INVALID_TYPE,
6286 				     "no <component> node");
6287 		return FALSE;
6288 	}
6289 	for (GNode *l = node->children; l != NULL; l = l->next) {
6290 		if (g_strcmp0 (as_node_get_name (l), "licence") == 0 ||
6291 		    g_strcmp0 (as_node_get_name (l), "license") == 0) {
6292 			as_node_set_name (l, "metadata_license");
6293 			priv->problems |= AS_APP_PROBLEM_DEPRECATED_LICENCE;
6294 			continue;
6295 		}
6296 		if (as_node_get_tag (l) == AS_TAG_COMPONENT) {
6297 			if (seen_application)
6298 				priv->problems |= AS_APP_PROBLEM_MULTIPLE_ENTRIES;
6299 			seen_application = TRUE;
6300 		}
6301 	}
6302 	ctx = as_node_context_new ();
6303 	as_node_context_set_format_kind (ctx, AS_FORMAT_KIND_APPDATA);
6304 	if (!as_app_node_parse_full (app, node, flags, ctx, error))
6305 		return FALSE;
6306 
6307 	/* use heuristics */
6308 	if (flags & AS_APP_PARSE_FLAG_USE_HEURISTICS) {
6309 		if (as_app_get_project_group (app) == NULL)
6310 			as_app_parse_appdata_guess_project_group (app);
6311 	}
6312 
6313 	return TRUE;
6314 }
6315 
6316 static gboolean
as_app_parse_appdata_file(AsApp * app,const gchar * filename,guint32 flags,GError ** error)6317 as_app_parse_appdata_file (AsApp *app,
6318 			   const gchar *filename,
6319 			   guint32 flags,
6320 			   GError **error)
6321 {
6322 	gsize len;
6323 	g_autofree gchar *data_raw = NULL;
6324 	g_autoptr(GBytes) data = NULL;
6325 	g_autoptr(GError) error_local = NULL;
6326 
6327 	/* open file */
6328 	if (!g_file_get_contents (filename, &data_raw, &len, &error_local)) {
6329 		g_set_error (error,
6330 			     AS_APP_ERROR,
6331 			     AS_APP_ERROR_INVALID_TYPE,
6332 			     "%s could not be read: %s",
6333 			     filename, error_local->message);
6334 		return FALSE;
6335 	}
6336 	data = g_bytes_new_take (g_steal_pointer (&data_raw), len);
6337 	if (!as_app_parse_data (app, data, flags, &error_local)) {
6338 		g_set_error (error,
6339 			     AS_APP_ERROR,
6340 			     AS_APP_ERROR_INVALID_TYPE,
6341 			     "failed to parse %s: %s",
6342 			     filename, error_local->message);
6343 		return FALSE;
6344 	}
6345 	return TRUE;
6346 }
6347 
6348 /**
6349  * as_app_parse_file:
6350  * @app: a #AsApp instance.
6351  * @filename: file to load.
6352  * @flags: #AsAppParseFlags, e.g. %AS_APP_PARSE_FLAG_USE_HEURISTICS
6353  * @error: A #GError or %NULL.
6354  *
6355  * Parses a desktop or AppData file and populates the application state.
6356  *
6357  * Applications that are not suitable for the store will have vetos added.
6358  *
6359  * Returns: %TRUE for success
6360  *
6361  * Since: 0.1.2
6362  **/
6363 gboolean
as_app_parse_file(AsApp * app,const gchar * filename,guint32 flags,GError ** error)6364 as_app_parse_file (AsApp *app, const gchar *filename, guint32 flags, GError **error)
6365 {
6366 	GPtrArray *vetos;
6367 	g_autoptr(AsFormat) format = as_format_new ();
6368 
6369 	/* autodetect */
6370 	as_format_set_filename (format, filename);
6371 	if (as_format_get_kind (format) == AS_FORMAT_KIND_UNKNOWN) {
6372 		g_set_error (error,
6373 			     AS_APP_ERROR,
6374 			     AS_APP_ERROR_INVALID_TYPE,
6375 			     "%s has an unrecognised extension",
6376 			     filename);
6377 		return FALSE;
6378 	}
6379 	as_app_add_format (app, format);
6380 
6381 	/* convert <_p> into <p> for easy validation */
6382 	if (g_str_has_suffix (filename, ".appdata.xml.in") ||
6383 	    g_str_has_suffix (filename, ".metainfo.xml.in"))
6384 		flags |= AS_APP_PARSE_FLAG_CONVERT_TRANSLATABLE;
6385 
6386 	/* all untrusted */
6387 	as_app_set_trust_flags (AS_APP (app),
6388 				AS_APP_TRUST_FLAG_CHECK_DUPLICATES |
6389 				AS_APP_TRUST_FLAG_CHECK_VALID_UTF8);
6390 
6391 	/* parse */
6392 	switch (as_format_get_kind (format)) {
6393 	case AS_FORMAT_KIND_DESKTOP:
6394 		if (!as_app_parse_desktop_file (app, filename, flags, error))
6395 			return FALSE;
6396 		break;
6397 	case AS_FORMAT_KIND_APPDATA:
6398 	case AS_FORMAT_KIND_METAINFO:
6399 		if (!as_app_parse_appdata_file (app, filename, flags, error))
6400 			return FALSE;
6401 		break;
6402 	default:
6403 		g_set_error (error,
6404 			     AS_APP_ERROR,
6405 			     AS_APP_ERROR_INVALID_TYPE,
6406 			     "%s has an unhandled type",
6407 			     filename);
6408 		return FALSE;
6409 		break;
6410 	}
6411 
6412 	/* vetos are errors by default */
6413 	vetos = as_app_get_vetos (app);
6414 	if ((flags & AS_APP_PARSE_FLAG_ALLOW_VETO) == 0 && vetos->len > 0) {
6415 		const gchar *tmp = g_ptr_array_index (vetos, 0);
6416 		g_set_error_literal (error,
6417 				     AS_APP_ERROR,
6418 				     AS_APP_ERROR_INVALID_TYPE,
6419 				     tmp);
6420 		return FALSE;
6421 	}
6422 
6423 	return TRUE;
6424 }
6425 
6426 /**
6427  * as_app_to_xml:
6428  * @app: a #AsApp instance.
6429  * @error: A #GError or %NULL
6430  *
6431  * Exports a DOM tree to an XML string.
6432  *
6433  * Returns: (transfer full): an XML string, or %NULL
6434  *
6435  * Since: 0.7.14
6436  **/
6437 GString *
as_app_to_xml(AsApp * app,GError ** error)6438 as_app_to_xml (AsApp *app, GError **error)
6439 {
6440 	g_autoptr(AsNodeContext) ctx = as_node_context_new ();
6441 	g_autoptr(AsNode) root = as_node_new ();
6442 	as_node_context_set_version (ctx, 1.0);
6443 	as_node_context_set_output (ctx, AS_FORMAT_KIND_APPDATA);
6444 	as_app_node_insert (app, root, ctx);
6445 	return as_node_to_xml (root,
6446 			       AS_NODE_TO_XML_FLAG_ADD_HEADER |
6447 			       AS_NODE_TO_XML_FLAG_FORMAT_INDENT |
6448 			       AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE);
6449 }
6450 
6451 /**
6452  * as_app_to_file:
6453  * @app: a #AsApp instance.
6454  * @file: a #GFile
6455  * @cancellable: (nullable): A #GCancellable
6456  * @error: A #GError or %NULL
6457  *
6458  * Exports a DOM tree to an XML file.
6459  *
6460  * Returns: %TRUE for success
6461  *
6462  * Since: 0.2.0
6463  **/
6464 gboolean
as_app_to_file(AsApp * app,GFile * file,GCancellable * cancellable,GError ** error)6465 as_app_to_file (AsApp *app,
6466 		GFile *file,
6467 		GCancellable *cancellable,
6468 		GError **error)
6469 {
6470 	g_autoptr(GString) xml = as_app_to_xml (app, error);
6471 	if (xml == NULL)
6472 		return FALSE;
6473 	return g_file_replace_contents (file,
6474 					xml->str,
6475 					xml->len,
6476 					NULL,
6477 					FALSE,
6478 					G_FILE_CREATE_NONE,
6479 					NULL,
6480 					cancellable,
6481 					error);
6482 }
6483 
6484 /**
6485  * as_app_get_vetos:
6486  * @app: A #AsApp
6487  *
6488  * Gets the list of vetos.
6489  *
6490  * Returns: (transfer none) (element-type utf8): A list of vetos
6491  *
6492  * Since: 0.2.5
6493  **/
6494 GPtrArray *
as_app_get_vetos(AsApp * app)6495 as_app_get_vetos (AsApp *app)
6496 {
6497 	AsAppPrivate *priv = GET_PRIVATE (app);
6498 	return priv->vetos;
6499 }
6500 
6501 /**
6502  * as_app_get_icon_default:
6503  * @app: A #AsApp
6504  *
6505  * Finds the default icon.
6506  *
6507  * Returns: (transfer none): a #AsIcon, or %NULL
6508  *
6509  * Since: 0.3.1
6510  **/
6511 AsIcon *
as_app_get_icon_default(AsApp * app)6512 as_app_get_icon_default (AsApp *app)
6513 {
6514 	AsAppPrivate *priv = GET_PRIVATE (app);
6515 	AsIcon *icon;
6516 	guint i;
6517 	guint j;
6518 	AsIconKind kinds[] = {
6519 		AS_ICON_KIND_STOCK,
6520 		AS_ICON_KIND_LOCAL,
6521 		AS_ICON_KIND_CACHED,
6522 		AS_ICON_KIND_EMBEDDED,
6523 		AS_ICON_KIND_REMOTE,
6524 		AS_ICON_KIND_UNKNOWN };
6525 
6526 	/* nothing */
6527 	if (priv->icons->len == 0)
6528 		return NULL;
6529 
6530 	/* optimise common case */
6531 	if (priv->icons->len == 1) {
6532 		icon = g_ptr_array_index (priv->icons, 0);
6533 		return icon;
6534 	}
6535 
6536 	/* search for icons in the preferred order */
6537 	for (j = 0; kinds[j] != AS_ICON_KIND_UNKNOWN; j++) {
6538 		for (i = 0; i < priv->icons->len; i++) {
6539 			icon = g_ptr_array_index (priv->icons, i);
6540 			if (as_icon_get_kind (icon) == kinds[j])
6541 				return icon;
6542 		}
6543 	}
6544 
6545 	/* we can't decide, just return the first added */
6546 	icon = g_ptr_array_index (priv->icons, 0);
6547 	return icon;
6548 }
6549 
6550 /**
6551  * as_app_get_bundle_default:
6552  * @app: A #AsApp
6553  *
6554  * Finds the default bundle.
6555  *
6556  * Returns: (transfer none): a #AsBundle, or %NULL
6557  *
6558  * Since: 0.3.5
6559  **/
6560 AsBundle *
as_app_get_bundle_default(AsApp * app)6561 as_app_get_bundle_default (AsApp *app)
6562 {
6563 	AsAppPrivate *priv = GET_PRIVATE (app);
6564 	AsBundle *bundle;
6565 
6566 	if (priv->bundles->len == 0)
6567 		return NULL;
6568 	bundle = g_ptr_array_index (priv->bundles, 0);
6569 	return bundle;
6570 }
6571 
6572 /**
6573  * as_app_get_icon_for_size:
6574  * @app: A #AsApp
6575  * @width: Size in pixels
6576  * @height: Size in pixels
6577  *
6578  * Finds an icon of a specific size.
6579  *
6580  * Returns: (transfer none): a #AsIcon, or %NULL
6581  *
6582  * Since: 0.3.1
6583  **/
6584 AsIcon *
as_app_get_icon_for_size(AsApp * app,guint width,guint height)6585 as_app_get_icon_for_size (AsApp *app, guint width, guint height)
6586 {
6587 	AsAppPrivate *priv = GET_PRIVATE (app);
6588 	guint i;
6589 
6590 	for (i = 0; i < priv->icons->len; i++) {
6591 		AsIcon *ic = g_ptr_array_index (priv->icons, i);
6592 		if (as_icon_get_width (ic) == width &&
6593 		    as_icon_get_height (ic) == height)
6594 			return ic;
6595 	}
6596 	return NULL;
6597 }
6598 
6599 /**
6600  * as_app_convert_icons:
6601  * @app: A #AsApp.
6602  * @kind: the AsIconKind, e.g. %AS_ICON_KIND_EMBEDDED.
6603  * @error: A #GError or %NULL
6604  *
6605  * Converts all the icons in the application to a specific kind.
6606  *
6607  * Returns: %TRUE for success
6608  *
6609  * Since: 0.3.1
6610  **/
6611 gboolean
as_app_convert_icons(AsApp * app,AsIconKind kind,GError ** error)6612 as_app_convert_icons (AsApp *app, AsIconKind kind, GError **error)
6613 {
6614 	AsAppPrivate *priv = GET_PRIVATE (app);
6615 	AsIcon *icon;
6616 	guint i;
6617 
6618 	/* convert icons */
6619 	for (i = 0; i < priv->icons->len; i++) {
6620 		icon = g_ptr_array_index (priv->icons, i);
6621 		if (!as_icon_convert_to_kind (icon, kind, error))
6622 			return FALSE;
6623 	}
6624 	return TRUE;
6625 }
6626 
6627 /**
6628  * as_app_add_veto:
6629  * @app: A #AsApp
6630  * @fmt: format string
6631  * @...: varargs
6632  *
6633  * Adds a reason to not include the application in the metadata.
6634  *
6635  * Since: 0.2.5
6636  **/
6637 void
as_app_add_veto(AsApp * app,const gchar * fmt,...)6638 as_app_add_veto (AsApp *app, const gchar *fmt, ...)
6639 {
6640 	AsAppPrivate *priv = GET_PRIVATE (app);
6641 	g_autofree gchar *tmp = NULL;
6642 	va_list args;
6643 	va_start (args, fmt);
6644 	tmp = g_strdup_vprintf (fmt, args);
6645 	va_end (args);
6646 	g_ptr_array_add (priv->vetos, as_ref_string_new (tmp));
6647 }
6648 
6649 /**
6650  * as_app_remove_veto:
6651  * @app: A #AsApp
6652  * @description: veto string
6653  *
6654  * Removes a reason to not include the application in the metadata.
6655  *
6656  * Since: 0.4.1
6657  **/
6658 void
as_app_remove_veto(AsApp * app,const gchar * description)6659 as_app_remove_veto (AsApp *app, const gchar *description)
6660 {
6661 	AsAppPrivate *priv = GET_PRIVATE (app);
6662 	const gchar *tmp;
6663 	guint i;
6664 
6665 	for (i = 0; i < priv->vetos->len; i++) {
6666 		tmp = g_ptr_array_index (priv->vetos, i);
6667 		if (g_strcmp0 (tmp, description) == 0) {
6668 			g_ptr_array_remove (priv->vetos, (gpointer) tmp);
6669 			break;
6670 		}
6671 	}
6672 }
6673 
6674 /**
6675  * as_app_set_stemmer: (skip)
6676  **/
6677 void
as_app_set_stemmer(AsApp * app,AsStemmer * stemmer)6678 as_app_set_stemmer (AsApp *app, AsStemmer *stemmer)
6679 {
6680 	AsAppPrivate *priv = GET_PRIVATE (app);
6681 	g_set_object (&priv->stemmer, stemmer);
6682 }
6683 
6684 /**
6685  * as_app_set_search_blacklist: (skip)
6686  **/
6687 void
as_app_set_search_blacklist(AsApp * app,GHashTable * search_blacklist)6688 as_app_set_search_blacklist (AsApp *app, GHashTable *search_blacklist)
6689 {
6690 	AsAppPrivate *priv = GET_PRIVATE (app);
6691 	if (priv->search_blacklist != NULL)
6692 		g_hash_table_unref (priv->search_blacklist);
6693 	priv->search_blacklist = g_hash_table_ref (search_blacklist);
6694 }
6695 
6696 /**
6697  * as_app_set_search_match:
6698  * @app: a #AsApp instance.
6699  * @search_match: the #AsAppSearchMatch, e.g. %AS_APP_SEARCH_MATCH_PKGNAME
6700  *
6701  * Sets the token match fields. The bitfield given here is used to choose what
6702  * is included in the token cache.
6703  *
6704  * Since: 0.6.13
6705  **/
6706 void
as_app_set_search_match(AsApp * app,guint16 search_match)6707 as_app_set_search_match (AsApp *app, guint16 search_match)
6708 {
6709 	AsAppPrivate *priv = GET_PRIVATE (app);
6710 	priv->search_match = search_match;
6711 }
6712 
6713 /**
6714  * as_app_get_search_match:
6715  * @app: a #AsApp instance.
6716  *
6717  * Gets the token match fields. The bitfield given here is used to choose what
6718  * is included in the token cache.
6719  *
6720  * Returns: a #AsAppSearchMatch, e.g. %AS_APP_SEARCH_MATCH_PKGNAME
6721  *
6722  * Since: 0.6.13
6723  **/
6724 guint16
as_app_get_search_match(AsApp * app)6725 as_app_get_search_match (AsApp *app)
6726 {
6727 	AsAppPrivate *priv = GET_PRIVATE (app);
6728 	return priv->search_match;
6729 }
6730 
6731 /**
6732  * as_app_new:
6733  *
6734  * Creates a new #AsApp.
6735  *
6736  * Returns: (transfer full): a #AsApp
6737  *
6738  * Since: 0.1.0
6739  **/
6740 AsApp *
as_app_new(void)6741 as_app_new (void)
6742 {
6743 	AsApp *app;
6744 	app = g_object_new (AS_TYPE_APP, NULL);
6745 	return AS_APP (app);
6746 }
6747