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