1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2013-2016 Richard Hughes <richard@hughsie.com>
4  * Copyright (C) 2015-2018 Kalev Lember <klember@redhat.com>
5  *
6  * SPDX-License-Identifier: LGPL-2.1+
7  */
8 
9 /**
10  * SECTION:as-store
11  * @short_description: a hashed array store of applications
12  * @include: appstream-glib.h
13  * @stability: Stable
14  *
15  * This store contains both an array of #AsApp's but also a pair of hashes
16  * to quickly retrieve an application from the ID or package name.
17  *
18  * Applications can also be removed, and the whole store can be loaded and
19  * saved to a compressed XML file.
20  *
21  * See also: #AsApp
22  */
23 
24 #include "config.h"
25 
26 #include "as-app-private.h"
27 #include "as-node-private.h"
28 #include "as-problem.h"
29 #include "as-profile.h"
30 #include "as-monitor.h"
31 #include "as-ref-string.h"
32 #include "as-stemmer.h"
33 #include "as-store.h"
34 #include "as-utils-private.h"
35 #include "as-yaml.h"
36 #include "as-store-cab.h"
37 
38 #define AS_API_VERSION_NEWEST	0.8
39 
40 typedef enum {
41 	AS_STORE_PROBLEM_NONE			= 0,
42 	AS_STORE_PROBLEM_LEGACY_ROOT		= 1 << 0,
43 	AS_STORE_PROBLEM_LAST
44 } AsStoreProblems;
45 
46 typedef struct
47 {
48 	gchar			*destdir;
49 	gchar			*origin;
50 	gchar			*builder_id;
51 	gdouble			 api_version;
52 	GPtrArray		*array;		/* of AsApp */
53 	GHashTable		*hash_id;	/* of GPtrArray of AsApp{id} */
54 	GHashTable		*hash_merge_id;	/* of GPtrArray of AsApp{id} */
55 	GHashTable		*hash_unique_id;	/* of AsApp{unique_id} */
56 	GHashTable		*hash_pkgname;	/* of AsApp{pkgname} */
57 	GMutex			 mutex;
58 	AsMonitor		*monitor;
59 	GHashTable		*metadata_indexes;	/* GHashTable{key} */
60 	GHashTable		*appinfo_dirs;	/* GHashTable{path:AsStorePathData} */
61 	GHashTable		*search_blacklist;	/* GHashTable{AsRefString:1} */
62 	guint32			 add_flags;
63 	guint32			 watch_flags;
64 	guint32			 problems;
65 	guint16			 search_match;
66 	guint32			 filter;
67 	guint			 changed_block_refcnt;
68 	gboolean		 is_pending_changed_signal;
69 	AsProfile		*profile;
70 	AsStemmer		*stemmer;
71 } AsStorePrivate;
72 
73 typedef struct {
74 	AsAppScope		 scope;
75 	gchar			*arch;
76 } AsStorePathData;
77 
78 G_DEFINE_TYPE_WITH_PRIVATE (AsStore, as_store, G_TYPE_OBJECT)
79 
80 enum {
81 	SIGNAL_CHANGED,
82 	SIGNAL_APP_ADDED,
83 	SIGNAL_APP_REMOVED,
84 	SIGNAL_APP_CHANGED,
85 	SIGNAL_LAST
86 };
87 
88 static guint signals [SIGNAL_LAST] = { 0 };
89 
90 #define GET_PRIVATE(o) (as_store_get_instance_private (o))
91 
92 /**
93  * as_store_error_quark:
94  *
95  * Return value: An error quark.
96  *
97  * Since: 0.1.2
98  **/
99 G_DEFINE_QUARK (as-store-error-quark, as_store_error)
100 
101 static gboolean	as_store_from_file_internal (AsStore *store,
102 					     GFile *file,
103 					     AsAppScope scope,
104 					     const gchar *arch,
105 					     guint32 load_flags,
106 					     guint32 watch_flags,
107 					     GCancellable *cancellable,
108 					     GError **error);
109 
110 static void
as_store_finalize(GObject * object)111 as_store_finalize (GObject *object)
112 {
113 	AsStore *store = AS_STORE (object);
114 	AsStorePrivate *priv = GET_PRIVATE (store);
115 
116 	g_free (priv->destdir);
117 	g_free (priv->origin);
118 	g_free (priv->builder_id);
119 	g_ptr_array_unref (priv->array);
120 	g_object_unref (priv->monitor);
121 	g_object_unref (priv->profile);
122 	g_object_unref (priv->stemmer);
123 	g_hash_table_unref (priv->hash_id);
124 	g_hash_table_unref (priv->hash_merge_id);
125 	g_hash_table_unref (priv->hash_unique_id);
126 	g_hash_table_unref (priv->hash_pkgname);
127 	g_hash_table_unref (priv->metadata_indexes);
128 	g_hash_table_unref (priv->appinfo_dirs);
129 	g_hash_table_unref (priv->search_blacklist);
130 	g_mutex_clear (&priv->mutex);
131 
132 	G_OBJECT_CLASS (as_store_parent_class)->finalize (object);
133 }
134 
135 static void
as_store_class_init(AsStoreClass * klass)136 as_store_class_init (AsStoreClass *klass)
137 {
138 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
139 
140 	/**
141 	 * AsStore::changed:
142 	 * @store: the #AsStore instance that emitted the signal
143 	 *
144 	 * The ::changed signal is emitted when components have been added
145 	 * or removed from the store.
146 	 *
147 	 * Since: 0.1.2
148 	 **/
149 	signals [SIGNAL_CHANGED] =
150 		g_signal_new ("changed",
151 			      G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
152 			      G_STRUCT_OFFSET (AsStoreClass, changed),
153 			      NULL, NULL, g_cclosure_marshal_VOID__VOID,
154 			      G_TYPE_NONE, 0);
155 
156 	/**
157 	 * AsStore::app-added:
158 	 * @store: the #AsStore instance that emitted the signal
159 	 * @app: the #AsApp instance
160 	 *
161 	 * The ::app-added signal is emitted when a component has been added to
162 	 * the store.
163 	 *
164 	 * Since: 0.6.5
165 	 **/
166 	signals [SIGNAL_APP_ADDED] =
167 		g_signal_new ("app-added",
168 			      G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
169 			      G_STRUCT_OFFSET (AsStoreClass, app_added),
170 			      NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
171 			      G_TYPE_NONE, 1, AS_TYPE_APP);
172 
173 	/**
174 	 * AsStore::app-removed:
175 	 * @store: the #AsStore instance that emitted the signal
176 	 * @app: the #AsApp instance
177 	 *
178 	 * The ::app-removed signal is emitted when a component has been removed
179 	 * from the store.
180 	 *
181 	 * Since: 0.6.5
182 	 **/
183 	signals [SIGNAL_APP_REMOVED] =
184 		g_signal_new ("app-removed",
185 			      G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
186 			      G_STRUCT_OFFSET (AsStoreClass, app_removed),
187 			      NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
188 			      G_TYPE_NONE, 1, AS_TYPE_APP);
189 
190 	/**
191 	 * AsStore::app-changed:
192 	 * @store: the #AsStore instance that emitted the signal
193 	 * @app: the #AsApp instance
194 	 *
195 	 * The ::app-changed signal is emitted when a component has been changed
196 	 * in the store.
197 	 *
198 	 * Since: 0.6.5
199 	 **/
200 	signals [SIGNAL_APP_CHANGED] =
201 		g_signal_new ("app-changed",
202 			      G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
203 			      G_STRUCT_OFFSET (AsStoreClass, app_changed),
204 			      NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
205 			      G_TYPE_NONE, 1, AS_TYPE_APP);
206 
207 	object_class->finalize = as_store_finalize;
208 }
209 
210 static void
as_store_perhaps_emit_changed(AsStore * store,const gchar * details)211 as_store_perhaps_emit_changed (AsStore *store, const gchar *details)
212 {
213 	AsStorePrivate *priv = GET_PRIVATE (store);
214 	if (priv->changed_block_refcnt > 0) {
215 		priv->is_pending_changed_signal = TRUE;
216 		return;
217 	}
218 	if (!priv->is_pending_changed_signal) {
219 		priv->is_pending_changed_signal = TRUE;
220 		return;
221 	}
222 	g_debug ("Emitting ::changed() [%s]", details);
223 	g_signal_emit (store, signals[SIGNAL_CHANGED], 0);
224 	priv->is_pending_changed_signal = FALSE;
225 }
226 
227 static guint32 *
as_store_changed_inhibit(AsStore * store)228 as_store_changed_inhibit (AsStore *store)
229 {
230 	AsStorePrivate *priv = GET_PRIVATE (store);
231 	priv->changed_block_refcnt++;
232 	return &priv->changed_block_refcnt;
233 }
234 
235 static void
as_store_changed_uninhibit(guint32 ** tok)236 as_store_changed_uninhibit (guint32 **tok)
237 {
238 	if (tok == NULL || *tok == NULL)
239 		return;
240 	if (*(*tok) == 0) {
241 		g_critical ("changed_block_refcnt already zero");
242 		return;
243 	}
244 	(*(*tok))--;
245 	*tok = NULL;
246 }
247 
248 static void
as_store_changed_uninhibit_cb(void * v)249 as_store_changed_uninhibit_cb (void *v)
250 {
251 	as_store_changed_uninhibit ((guint32 **)v);
252 }
253 
254 #define _cleanup_uninhibit_ __attribute__ ((cleanup(as_store_changed_uninhibit_cb)))
255 
256 static GPtrArray *
_dup_app_array(GPtrArray * array)257 _dup_app_array (GPtrArray *array)
258 {
259 	GPtrArray *array_dup;
260 
261 	g_return_val_if_fail (array != NULL, NULL);
262 
263 	array_dup = g_ptr_array_new_full (array->len, (GDestroyNotify) g_object_unref);
264 	for (guint i = 0; i < array->len; i++) {
265 		AsApp *app = g_ptr_array_index (array, i);
266 		g_ptr_array_add (array_dup, g_object_ref (app));
267 	}
268 
269 	return array_dup;
270 }
271 
272 /**
273  * as_store_add_filter:
274  * @store: a #AsStore instance.
275  * @kind: a #AsAppKind, e.g. %AS_APP_KIND_FIRMWARE
276  *
277  * Adds a filter to the store so that only components of this type are
278  * loaded into the store. This may be useful if the client is only interested
279  * in certain types of component, or not interested in loading components
280  * it cannot process.
281  *
282  * If no filter is set then all types of components are loaded.
283  *
284  * Since: 0.3.5
285  **/
286 void
as_store_add_filter(AsStore * store,AsAppKind kind)287 as_store_add_filter (AsStore *store, AsAppKind kind)
288 {
289 	AsStorePrivate *priv = GET_PRIVATE (store);
290 	g_return_if_fail (AS_IS_STORE (store));
291 	priv->filter |= 1u << kind;
292 }
293 
294 /**
295  * as_store_remove_filter:
296  * @store: a #AsStore instance.
297  * @kind: a #AsAppKind, e.g. %AS_APP_KIND_FIRMWARE
298  *
299  * Removed a filter from the store so that components of this type are no longer
300  * loaded into the store. This may be useful if the client is only interested
301  * in certain types of component.
302  *
303  * If all filters are removed then all types of components are loaded.
304  *
305  * Since: 0.3.5
306  **/
307 void
as_store_remove_filter(AsStore * store,AsAppKind kind)308 as_store_remove_filter (AsStore *store, AsAppKind kind)
309 {
310 	AsStorePrivate *priv = GET_PRIVATE (store);
311 	g_return_if_fail (AS_IS_STORE (store));
312 	priv->filter &= ~(1u << kind);
313 }
314 
315 /**
316  * as_store_get_size:
317  * @store: a #AsStore instance.
318  *
319  * Gets the size of the store after deduplication and prioritization has
320  * taken place.
321  *
322  * Returns: the number of usable applications in the store
323  *
324  * Since: 0.1.0
325  **/
326 guint
as_store_get_size(AsStore * store)327 as_store_get_size (AsStore *store)
328 {
329 	AsStorePrivate *priv = GET_PRIVATE (store);
330 	g_autoptr(GMutexLocker) locker = NULL;
331 
332 	g_return_val_if_fail (AS_IS_STORE (store), 0);
333 
334 	locker = g_mutex_locker_new (&priv->mutex);
335 	return priv->array->len;
336 }
337 
338 /**
339  * as_store_get_apps:
340  * @store: a #AsStore instance.
341  *
342  * Gets an array of all the valid applications in the store.
343  *
344  * Returns: (element-type AsApp) (transfer none): an array
345  *
346  * Since: 0.1.0
347  **/
348 GPtrArray *
as_store_get_apps(AsStore * store)349 as_store_get_apps (AsStore *store)
350 {
351 	AsStorePrivate *priv = GET_PRIVATE (store);
352 	g_autoptr(GMutexLocker) locker = NULL;
353 
354 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
355 
356 	locker = g_mutex_locker_new (&priv->mutex);
357 	return priv->array;
358 }
359 
360 /**
361  * as_store_dup_apps:
362  * @store: a #AsStore instance.
363  *
364  * Gets an array of all the valid applications in the store.
365  *
366  * Returns: (element-type AsApp) (transfer container): an array
367  *
368  * Since: 0.7.15
369  **/
370 GPtrArray *
as_store_dup_apps(AsStore * store)371 as_store_dup_apps (AsStore *store)
372 {
373 
374 	AsStorePrivate *priv = GET_PRIVATE (store);
375 	g_autoptr(GMutexLocker) locker = NULL;
376 
377 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
378 
379 	locker = g_mutex_locker_new (&priv->mutex);
380 	return _dup_app_array (priv->array);
381 }
382 
383 /**
384  * as_store_remove_all:
385  * @store: a #AsStore instance.
386  *
387  * Removes all applications from the store.
388  *
389  * Since: 0.2.5
390  **/
391 void
as_store_remove_all(AsStore * store)392 as_store_remove_all (AsStore *store)
393 {
394 	AsStorePrivate *priv = GET_PRIVATE (store);
395 	g_autoptr(GMutexLocker) locker = NULL;
396 
397 	g_return_if_fail (AS_IS_STORE (store));
398 
399 	locker = g_mutex_locker_new (&priv->mutex);
400 	g_ptr_array_set_size (priv->array, 0);
401 	g_hash_table_remove_all (priv->hash_id);
402 	g_hash_table_remove_all (priv->hash_merge_id);
403 	g_hash_table_remove_all (priv->hash_unique_id);
404 	g_hash_table_remove_all (priv->hash_pkgname);
405 }
406 
407 static void
as_store_regen_metadata_index_key(AsStore * store,const gchar * key)408 as_store_regen_metadata_index_key (AsStore *store, const gchar *key)
409 {
410 	AsApp *app;
411 	AsStorePrivate *priv = GET_PRIVATE (store);
412 	GHashTable *md;
413 	GPtrArray *apps;
414 	const gchar *tmp;
415 	guint i;
416 
417 	/* regenerate cache */
418 	md = g_hash_table_new_full (g_str_hash, g_str_equal,
419 				    g_free, (GDestroyNotify) g_ptr_array_unref);
420 	for (i = 0; i < priv->array->len; i++) {
421 		app = g_ptr_array_index (priv->array, i);
422 
423 		/* no data */
424 		tmp = as_app_get_metadata_item (app, key);
425 		if (tmp == NULL)
426 			continue;
427 
428 		/* seen before */
429 		apps = g_hash_table_lookup (md, tmp);
430 		if (apps != NULL) {
431 			g_ptr_array_add (apps, g_object_ref (app));
432 			continue;
433 		}
434 
435 		/* never seen before */
436 		apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
437 		g_ptr_array_add (apps, g_object_ref (app));
438 		g_hash_table_insert (md, g_strdup (tmp), apps);
439 
440 	}
441 	g_hash_table_insert (priv->metadata_indexes, g_strdup (key), md);
442 }
443 
444 /**
445  * as_store_get_apps_by_metadata:
446  * @store: a #AsStore instance.
447  * @key: metadata key
448  * @value: metadata value
449  *
450  * Gets an array of all the applications that match a specific metadata element.
451  *
452  * Returns: (element-type AsApp) (transfer container): an array
453  *
454  * Since: 0.1.4
455  **/
456 GPtrArray *
as_store_get_apps_by_metadata(AsStore * store,const gchar * key,const gchar * value)457 as_store_get_apps_by_metadata (AsStore *store,
458 			       const gchar *key,
459 			       const gchar *value)
460 {
461 	AsApp *app;
462 	AsStorePrivate *priv = GET_PRIVATE (store);
463 	GHashTable *index;
464 	GPtrArray *apps;
465 	guint i;
466 	g_autoptr(GMutexLocker) locker = NULL;
467 
468 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
469 
470 	locker = g_mutex_locker_new (&priv->mutex);
471 
472 	/* do we have this indexed? */
473 	index = g_hash_table_lookup (priv->metadata_indexes, key);
474 	if (index != NULL) {
475 		if (g_hash_table_size (index) == 0) {
476 			as_store_regen_metadata_index_key (store, key);
477 			index = g_hash_table_lookup (priv->metadata_indexes, key);
478 		}
479 		apps = g_hash_table_lookup (index, value);
480 		if (apps != NULL)
481 			return _dup_app_array (apps);
482 		return g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
483 	}
484 
485 	/* find all the apps with this specific metadata key */
486 	apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
487 	for (i = 0; i < priv->array->len; i++) {
488 		app = g_ptr_array_index (priv->array, i);
489 		if (g_strcmp0 (as_app_get_metadata_item (app, key), value) != 0)
490 			continue;
491 		g_ptr_array_add (apps, g_object_ref (app));
492 	}
493 	return apps;
494 }
495 
496 
497 /**
498  * as_store_get_apps_by_id:
499  * @store: a #AsStore instance.
500  * @id: the application full ID.
501  *
502  * Gets an array of all the applications that match a specific ID,
503  * ignoring the prefix type.
504  *
505  * Returns: (element-type AsApp) (transfer container): an array
506  *
507  * Since: 0.5.12
508  **/
509 GPtrArray *
as_store_get_apps_by_id(AsStore * store,const gchar * id)510 as_store_get_apps_by_id (AsStore *store, const gchar *id)
511 {
512 	AsStorePrivate *priv = GET_PRIVATE (store);
513 	GPtrArray *apps;
514 	g_autoptr(GMutexLocker) locker = NULL;
515 
516 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
517 
518 	locker = g_mutex_locker_new (&priv->mutex);
519 
520 	apps = g_hash_table_lookup (priv->hash_id, id);
521 	if (apps != NULL)
522 		return _dup_app_array (apps);
523 	return g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
524 }
525 
526 /**
527  * as_store_get_apps_by_id_merge:
528  * @store: a #AsStore instance.
529  * @id: the application full ID.
530  *
531  * Gets an array of all the merge applications that match a specific ID.
532  *
533  * Returns: (element-type AsApp) (transfer none): an array
534  *
535  * Since: 0.7.0
536  **/
537 GPtrArray *
as_store_get_apps_by_id_merge(AsStore * store,const gchar * id)538 as_store_get_apps_by_id_merge (AsStore *store, const gchar *id)
539 {
540 	AsStorePrivate *priv = GET_PRIVATE (store);
541 	g_autoptr(GMutexLocker) locker = NULL;
542 
543 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
544 
545 	locker = g_mutex_locker_new (&priv->mutex);
546 	return g_hash_table_lookup (priv->hash_merge_id, id);
547 }
548 
549 /**
550  * as_store_dup_apps_by_id_merge:
551  * @store: a #AsStore instance.
552  * @id: the application full ID.
553  *
554  * Gets an array of all the merge applications that match a specific ID.
555  *
556  * Returns: (element-type AsApp) (transfer container): an array
557  *
558  * Since: 0.7.15
559  **/
560 GPtrArray *
as_store_dup_apps_by_id_merge(AsStore * store,const gchar * id)561 as_store_dup_apps_by_id_merge (AsStore *store, const gchar *id)
562 {
563 	AsStorePrivate *priv = GET_PRIVATE (store);
564 	GPtrArray *apps;
565 	g_autoptr(GMutexLocker) locker = NULL;
566 
567 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
568 
569 	locker = g_mutex_locker_new (&priv->mutex);
570 
571 	apps = g_hash_table_lookup (priv->hash_merge_id, id);
572 	if (apps != NULL)
573 		return _dup_app_array (apps);
574 	return g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
575 }
576 
577 /**
578  * as_store_add_metadata_index:
579  * @store: a #AsStore instance.
580  * @key: the metadata key.
581  *
582  * Adds a metadata index key.
583  *
584  * NOTE: if applications are removed *all* the indexes will be invalid and
585  * will have to be re-added.
586  *
587  * Since: 0.3.0
588  **/
589 void
as_store_add_metadata_index(AsStore * store,const gchar * key)590 as_store_add_metadata_index (AsStore *store, const gchar *key)
591 {
592 	AsStorePrivate *priv = GET_PRIVATE (store);
593 	g_autoptr(GMutexLocker) locker = NULL;
594 
595 	g_return_if_fail (AS_IS_STORE (store));
596 
597 	locker = g_mutex_locker_new (&priv->mutex);
598 	as_store_regen_metadata_index_key (store, key);
599 }
600 
601 /**
602  * as_store_get_app_by_id:
603  * @store: a #AsStore instance.
604  * @id: the application full ID.
605  *
606  * Finds an application in the store by ID.
607  * If more than one application exists matching the specific ID,
608  * (for instance when using %AS_STORE_ADD_FLAG_USE_UNIQUE_ID) then the
609  * first item that was added is returned.
610  *
611  * Returns: (transfer none): a #AsApp or %NULL
612  *
613  * Since: 0.1.0
614  **/
615 AsApp *
as_store_get_app_by_id(AsStore * store,const gchar * id)616 as_store_get_app_by_id (AsStore *store, const gchar *id)
617 {
618 	AsStorePrivate *priv = GET_PRIVATE (store);
619 	GPtrArray *apps;
620 	g_autoptr(GMutexLocker) locker = NULL;
621 
622 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
623 
624 	locker = g_mutex_locker_new (&priv->mutex);
625 	apps = g_hash_table_lookup (priv->hash_id, id);
626 	if (apps == NULL)
627 		return NULL;
628 	return g_ptr_array_index (apps, 0);
629 }
630 
631 static AsApp *
_as_app_new_from_unique_id(const gchar * unique_id)632 _as_app_new_from_unique_id (const gchar *unique_id)
633 {
634 	g_auto(GStrv) split = NULL;
635 	g_autoptr(AsApp) app = as_app_new ();
636 
637 	split = g_strsplit (unique_id, "/", -1);
638 	if (g_strv_length (split) != AS_UTILS_UNIQUE_ID_PARTS)
639 		return NULL;
640 	if (g_strcmp0 (split[0], AS_APP_UNIQUE_WILDCARD) != 0)
641 		as_app_set_scope (app, as_app_scope_from_string (split[0]));
642 	if (g_strcmp0 (split[1], AS_APP_UNIQUE_WILDCARD) != 0) {
643 		if (g_strcmp0 (split[1], "package") == 0) {
644 			as_app_add_pkgname (app, "");
645 		} else {
646 			AsBundleKind kind = as_bundle_kind_from_string (split[1]);
647 			if (kind != AS_BUNDLE_KIND_UNKNOWN) {
648 				g_autoptr(AsBundle) bundle = as_bundle_new ();
649 				as_bundle_set_kind (bundle, kind);
650 				as_app_add_bundle (app, bundle);
651 			}
652 		}
653 	}
654 	if (g_strcmp0 (split[2], AS_APP_UNIQUE_WILDCARD) != 0)
655 		as_app_set_origin (app, split[2]);
656 	if (g_strcmp0 (split[3], AS_APP_UNIQUE_WILDCARD) != 0)
657 		as_app_set_kind (app, as_app_kind_from_string (split[3]));
658 	if (g_strcmp0 (split[4], AS_APP_UNIQUE_WILDCARD) != 0)
659 		as_app_set_id (app, split[4]);
660 	if (g_strcmp0 (split[5], AS_APP_UNIQUE_WILDCARD) != 0)
661 		as_app_set_branch (app, split[5]);
662 
663 	return g_steal_pointer (&app);
664 }
665 
666 static AsApp *
as_store_get_app_by_app(AsStore * store,AsApp * app)667 as_store_get_app_by_app (AsStore *store, AsApp *app)
668 {
669 	AsStorePrivate *priv = GET_PRIVATE (store);
670 	guint i;
671 	g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex);
672 
673 	for (i = 0; i < priv->array->len; i++) {
674 		AsApp *app_tmp = g_ptr_array_index (priv->array, i);
675 		if (as_app_equal (app_tmp, app))
676 			return app_tmp;
677 	}
678 	return NULL;
679 }
680 
681 /**
682  * as_store_get_app_by_unique_id:
683  * @store: a #AsStore instance.
684  * @unique_id: the application unique ID, e.g.
685  *      `user/flatpak/gnome-apps-nightly/app/gimp.desktop/master`
686  * @search_flags: the search flags, e.g. %AS_STORE_SEARCH_FLAG_USE_WILDCARDS
687  *
688  * Finds an application in the store by matching the unique ID.
689  *
690  * Returns: (transfer none): a #AsApp or %NULL
691  *
692  * Since: 0.6.1
693  **/
694 AsApp *
as_store_get_app_by_unique_id(AsStore * store,const gchar * unique_id,guint32 search_flags)695 as_store_get_app_by_unique_id (AsStore *store,
696 			       const gchar *unique_id,
697 			       guint32 search_flags)
698 {
699 	AsStorePrivate *priv = GET_PRIVATE (store);
700 	g_autoptr(AsApp) app_tmp = NULL;
701 
702 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
703 	g_return_val_if_fail (unique_id != NULL, NULL);
704 
705 	/* no globs */
706 	if ((search_flags & AS_STORE_SEARCH_FLAG_USE_WILDCARDS) == 0) {
707 		g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex);
708 		return g_hash_table_lookup (priv->hash_unique_id, unique_id);
709 	}
710 
711 	/* create virtual app using scope/system/origin/kind/id/branch */
712 	app_tmp = _as_app_new_from_unique_id (unique_id);
713 	if (app_tmp == NULL)
714 		return NULL;
715 	return as_store_get_app_by_app (store, app_tmp);
716 }
717 
718 /**
719  * as_store_get_app_by_provide:
720  * @store: a #AsStore instance.
721  * @kind: the #AsProvideKind
722  * @value: the provide value, e.g. "com.hughski.ColorHug2.firmware"
723  *
724  * Finds an application in the store by something that it provides.
725  *
726  * Returns: (transfer none): a #AsApp or %NULL
727  *
728  * Since: 0.5.0
729  **/
730 AsApp *
as_store_get_app_by_provide(AsStore * store,AsProvideKind kind,const gchar * value)731 as_store_get_app_by_provide (AsStore *store, AsProvideKind kind, const gchar *value)
732 {
733 	AsApp *app;
734 	AsProvide *tmp;
735 	AsStorePrivate *priv = GET_PRIVATE (store);
736 	guint i;
737 	guint j;
738 	GPtrArray *provides;
739 	g_autoptr(GMutexLocker) locker = NULL;
740 
741 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
742 	g_return_val_if_fail (kind != AS_PROVIDE_KIND_UNKNOWN, NULL);
743 	g_return_val_if_fail (value != NULL, NULL);
744 
745 	locker = g_mutex_locker_new (&priv->mutex);
746 
747 	/* find an application that provides something */
748 	for (i = 0; i < priv->array->len; i++) {
749 		app = g_ptr_array_index (priv->array, i);
750 		provides = as_app_get_provides (app);
751 		for (j = 0; j < provides->len; j++) {
752 			tmp = g_ptr_array_index (provides, j);
753 			if (kind != as_provide_get_kind (tmp))
754 				continue;
755 			if (g_strcmp0 (as_provide_get_value (tmp), value) != 0)
756 				continue;
757 			return app;
758 		}
759 	}
760 	return NULL;
761 
762 }
763 
764 /**
765  * as_store_get_app_by_launchable:
766  * @store: a #AsStore instance.
767  * @kind: the #AsLaunchableKind
768  * @value: the provide value, e.g. "gimp.desktop"
769  *
770  * Finds an application in the store that provides a specific launchable.
771  *
772  * Returns: (transfer none): a #AsApp or %NULL
773  *
774  * Since: 0.7.8
775  **/
776 AsApp *
as_store_get_app_by_launchable(AsStore * store,AsLaunchableKind kind,const gchar * value)777 as_store_get_app_by_launchable (AsStore *store, AsLaunchableKind kind, const gchar *value)
778 {
779 	AsStorePrivate *priv = GET_PRIVATE (store);
780 	g_autoptr(GMutexLocker) locker = NULL;
781 
782 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
783 	g_return_val_if_fail (kind != AS_LAUNCHABLE_KIND_UNKNOWN, NULL);
784 	g_return_val_if_fail (value != NULL, NULL);
785 
786 	locker = g_mutex_locker_new (&priv->mutex);
787 
788 	for (guint i = 0; i < priv->array->len; i++) {
789 		AsApp *app = g_ptr_array_index (priv->array, i);
790 		GPtrArray *launchables = as_app_get_launchables (app);
791 		for (guint j = 0; j < launchables->len; j++) {
792 			AsLaunchable *tmp = g_ptr_array_index (launchables, j);
793 			if (kind != as_launchable_get_kind (tmp))
794 				continue;
795 			if (g_strcmp0 (as_launchable_get_value (tmp), value) != 0)
796 				continue;
797 			return app;
798 		}
799 	}
800 	return NULL;
801 
802 }
803 
804 /**
805  * as_store_get_apps_by_provide:
806  * @store: a #AsStore instance.
807  * @kind: the #AsProvideKind
808  * @value: the provide value, e.g. "com.hughski.ColorHug2.firmware"
809  *
810  * Finds any applications in the store by something that they provides.
811  *
812  * Returns: (transfer container) (element-type AsApp): an array of applications
813  *
814  * Since: 0.7.5
815  **/
816 GPtrArray *
as_store_get_apps_by_provide(AsStore * store,AsProvideKind kind,const gchar * value)817 as_store_get_apps_by_provide (AsStore *store, AsProvideKind kind, const gchar *value)
818 {
819 	AsStorePrivate *priv = GET_PRIVATE (store);
820 	GPtrArray *apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
821 	g_autoptr(GMutexLocker) locker = NULL;
822 
823 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
824 	g_return_val_if_fail (kind != AS_PROVIDE_KIND_UNKNOWN, NULL);
825 	g_return_val_if_fail (value != NULL, NULL);
826 
827 	locker = g_mutex_locker_new (&priv->mutex);
828 
829 	/* find an application that provides something */
830 	for (guint i = 0; i < priv->array->len; i++) {
831 		AsApp *app = g_ptr_array_index (priv->array, i);
832 		GPtrArray *provides = as_app_get_provides (app);
833 		for (guint j = 0; j < provides->len; j++) {
834 			AsProvide *tmp = g_ptr_array_index (provides, j);
835 			if (kind != as_provide_get_kind (tmp))
836 				continue;
837 			if (g_strcmp0 (as_provide_get_value (tmp), value) != 0)
838 				continue;
839 			g_ptr_array_add (apps, g_object_ref (app));
840 		}
841 	}
842 	return apps;
843 }
844 
845 /**
846  * as_store_get_app_by_id_ignore_prefix:
847  * @store: a #AsStore instance.
848  * @id: the application full ID.
849  *
850  * Finds an application in the store ignoring the prefix type.
851  *
852  * Returns: (transfer none): a #AsApp or %NULL
853  *
854  * Since: 0.5.12
855  **/
856 AsApp *
as_store_get_app_by_id_ignore_prefix(AsStore * store,const gchar * id)857 as_store_get_app_by_id_ignore_prefix (AsStore *store, const gchar *id)
858 {
859 	AsApp *app;
860 	AsStorePrivate *priv = GET_PRIVATE (store);
861 	guint i;
862 	g_autoptr(GMutexLocker) locker = NULL;
863 
864 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
865 	g_return_val_if_fail (id != NULL, NULL);
866 
867 	locker = g_mutex_locker_new (&priv->mutex);
868 
869 	/* find an application that provides something */
870 	for (i = 0; i < priv->array->len; i++) {
871 		app = g_ptr_array_index (priv->array, i);
872 		if (g_strcmp0 (as_app_get_id_no_prefix (app), id) == 0)
873 			return app;
874 	}
875 	return NULL;
876 }
877 
878 /**
879  * as_store_get_app_by_id_with_fallbacks:
880  * @store: a #AsStore instance.
881  * @id: the application full ID.
882  *
883  * Finds an application in the store by either by the current desktop ID
884  * or a desktop ID that it has used previously. This allows upstream software
885  * to change their ID (e.g. from cheese.desktop to org.gnome.Cheese.desktop)
886  * without us duplicating entries in the software center.
887  *
888  * Returns: (transfer none): a #AsApp or %NULL
889  *
890  * Since: 0.4.1
891  **/
892 AsApp *
as_store_get_app_by_id_with_fallbacks(AsStore * store,const gchar * id)893 as_store_get_app_by_id_with_fallbacks (AsStore *store, const gchar *id)
894 {
895 	AsApp *app;
896 	guint i;
897 	const struct {
898 		const gchar	*old;
899 		const gchar	*new;
900 	} id_map[] = {
901 		/* GNOME */
902 		{ "baobab.desktop",		"org.gnome.baobab.desktop" },
903 		{ "bijiben.desktop",		"org.gnome.bijiben.desktop" },
904 		{ "cheese.desktop",		"org.gnome.Cheese.desktop" },
905 		{ "devhelp.desktop",		"org.gnome.Devhelp.desktop" },
906 		{ "epiphany.desktop",		"org.gnome.Epiphany.desktop" },
907 		{ "file-roller.desktop",	"org.gnome.FileRoller.desktop" },
908 		{ "font-manager.desktop",	"org.gnome.FontManager.desktop" },
909 		{ "gcalctool.desktop",		"gnome-calculator.desktop" },
910 		{ "gcm-viewer.desktop",		"org.gnome.ColorProfileViewer.desktop" },
911 		{ "geary.desktop",		"org.gnome.Geary.desktop" },
912 		{ "gedit.desktop",		"org.gnome.gedit.desktop" },
913 		{ "glchess.desktop",		"gnome-chess.desktop" },
914 		{ "glines.desktop",		"five-or-more.desktop" },
915 		{ "gnect.desktop",		"four-in-a-row.desktop" },
916 		{ "gnibbles.desktop",		"gnome-nibbles.desktop" },
917 		{ "gnobots2.desktop",		"gnome-robots.desktop" },
918 		{ "gnome-2048.desktop",		"org.gnome.gnome-2048.desktop" },
919 		{ "gnome-boxes.desktop",	"org.gnome.Boxes.desktop" },
920 		{ "gnome-calculator.desktop",	"org.gnome.Calculator.desktop" },
921 		{ "gnome-clocks.desktop",	"org.gnome.clocks.desktop" },
922 		{ "gnome-contacts.desktop",	"org.gnome.Contacts.desktop" },
923 		{ "gnome-dictionary.desktop",	"org.gnome.Dictionary.desktop" },
924 		{ "gnome-disks.desktop",	"org.gnome.DiskUtility.desktop" },
925 		{ "gnome-documents.desktop",	"org.gnome.Documents.desktop" },
926 		{ "gnome-font-viewer.desktop",	"org.gnome.font-viewer.desktop" },
927 		{ "gnome-maps.desktop",		"org.gnome.Maps.desktop" },
928 		{ "gnome-nibbles.desktop",	"org.gnome.Nibbles.desktop" },
929 		{ "gnome-photos.desktop",	"org.gnome.Photos.desktop" },
930 		{ "gnome-power-statistics.desktop", "org.gnome.PowerStats.desktop" },
931 		{ "gnome-screenshot.desktop",	"org.gnome.Screenshot.desktop" },
932 		{ "gnome-software.desktop",	"org.gnome.Software.desktop" },
933 		{ "gnome-sound-recorder.desktop", "org.gnome.SoundRecorder.desktop" },
934 		{ "gnome-terminal.desktop",	"org.gnome.Terminal.desktop" },
935 		{ "gnome-weather.desktop",	"org.gnome.Weather.Application.desktop" },
936 		{ "gnomine.desktop",		"gnome-mines.desktop" },
937 		{ "gnotravex.desktop",		"gnome-tetravex.desktop" },
938 		{ "gnotski.desktop",		"gnome-klotski.desktop" },
939 		{ "gtali.desktop",		"tali.desktop" },
940 		{ "hitori.desktop",		"org.gnome.Hitori.desktop" },
941 		{ "latexila.desktop",		"org.gnome.latexila.desktop" },
942 		{ "lollypop.desktop",		"org.gnome.Lollypop.desktop" },
943 		{ "nautilus.desktop",		"org.gnome.Nautilus.desktop" },
944 		{ "polari.desktop",		"org.gnome.Polari.desktop" },
945 		{ "sound-juicer.desktop",	"org.gnome.SoundJuicer.desktop" },
946 		{ "totem.desktop",		"org.gnome.Totem.desktop" },
947 
948 		/* KDE */
949 		{ "akregator.desktop",		"org.kde.akregator.desktop" },
950 		{ "apper.desktop",		"org.kde.apper.desktop" },
951 		{ "ark.desktop",		"org.kde.ark.desktop" },
952 		{ "blinken.desktop",		"org.kde.blinken.desktop" },
953 		{ "cantor.desktop",		"org.kde.cantor.desktop" },
954 		{ "digikam.desktop",		"org.kde.digikam.desktop" },
955 		{ "dolphin.desktop",		"org.kde.dolphin.desktop" },
956 		{ "dragonplayer.desktop",	"org.kde.dragonplayer.desktop" },
957 		{ "filelight.desktop",		"org.kde.filelight.desktop" },
958 		{ "gwenview.desktop",		"org.kde.gwenview.desktop" },
959 		{ "juk.desktop",		"org.kde.juk.desktop" },
960 		{ "kajongg.desktop",		"org.kde.kajongg.desktop" },
961 		{ "kalgebra.desktop",		"org.kde.kalgebra.desktop" },
962 		{ "kalzium.desktop",		"org.kde.kalzium.desktop" },
963 		{ "kamoso.desktop",		"org.kde.kamoso.desktop" },
964 		{ "kanagram.desktop",		"org.kde.kanagram.desktop" },
965 		{ "kapman.desktop",		"org.kde.kapman.desktop" },
966 		{ "kapptemplate.desktop",	"org.kde.kapptemplate.desktop" },
967 		{ "kbruch.desktop",		"org.kde.kbruch.desktop" },
968 		{ "kdevelop.desktop",		"org.kde.kdevelop.desktop" },
969 		{ "kfind.desktop",		"org.kde.kfind.desktop" },
970 		{ "kgeography.desktop",		"org.kde.kgeography.desktop" },
971 		{ "kgpg.desktop",		"org.kde.kgpg.desktop" },
972 		{ "khangman.desktop",		"org.kde.khangman.desktop" },
973 		{ "kig.desktop",		"org.kde.kig.desktop" },
974 		{ "kiriki.desktop",		"org.kde.kiriki.desktop" },
975 		{ "kiten.desktop",		"org.kde.kiten.desktop" },
976 		{ "klettres.desktop",		"org.kde.klettres.desktop" },
977 		{ "klipper.desktop",		"org.kde.klipper.desktop" },
978 		{ "KMail2.desktop",		"org.kde.kmail.desktop" },
979 		{ "kmplot.desktop",		"org.kde.kmplot.desktop" },
980 		{ "kollision.desktop",		"org.kde.kollision.desktop" },
981 		{ "kolourpaint.desktop",	"org.kde.kolourpaint.desktop" },
982 		{ "konsole.desktop",		"org.kde.konsole.desktop" },
983 		{ "Kontact.desktop",		"org.kde.kontact.desktop" },
984 		{ "korganizer.desktop",		"org.kde.korganizer.desktop" },
985 		{ "krita.desktop",		"org.kde.krita.desktop" },
986 		{ "kshisen.desktop",		"org.kde.kshisen.desktop" },
987 		{ "kstars.desktop",		"org.kde.kstars.desktop" },
988 		{ "ksudoku.desktop",		"org.kde.ksudoku.desktop" },
989 		{ "ktouch.desktop",		"org.kde.ktouch.desktop" },
990 		{ "ktp-log-viewer.desktop",	"org.kde.ktplogviewer.desktop" },
991 		{ "kturtle.desktop",		"org.kde.kturtle.desktop" },
992 		{ "kwordquiz.desktop",		"org.kde.kwordquiz.desktop" },
993 		{ "marble.desktop",		"org.kde.marble.desktop" },
994 		{ "okteta.desktop",		"org.kde.okteta.desktop" },
995 		{ "parley.desktop",		"org.kde.parley.desktop" },
996 		{ "partitionmanager.desktop",	"org.kde.PartitionManager.desktop" },
997 		{ "picmi.desktop",		"org.kde.picmi.desktop" },
998 		{ "rocs.desktop",		"org.kde.rocs.desktop" },
999 		{ "showfoto.desktop",		"org.kde.showfoto.desktop" },
1000 		{ "skrooge.desktop",		"org.kde.skrooge.desktop" },
1001 		{ "step.desktop",		"org.kde.step.desktop" },
1002 		{ "yakuake.desktop",		"org.kde.yakuake.desktop" },
1003 
1004 		/* others */
1005 		{ "colorhug-ccmx.desktop",	"com.hughski.ColorHug.CcmxLoader.desktop" },
1006 		{ "colorhug-flash.desktop",	"com.hughski.ColorHug.FlashLoader.desktop" },
1007 		{ "dconf-editor.desktop",	"ca.desrt.dconf-editor.desktop" },
1008 		{ "feedreader.desktop",		"org.gnome.FeedReader.desktop" },
1009 		{ "qtcreator.desktop",		"org.qt-project.qtcreator.desktop" },
1010 
1011 		{ NULL, NULL }
1012 	};
1013 
1014 	/* trivial case */
1015 	app = as_store_get_app_by_id (store, id);
1016 	if (app != NULL)
1017 		return app;
1018 
1019 	/* has the application ID been renamed */
1020 	for (i = 0; id_map[i].old != NULL; i++) {
1021 		if (g_strcmp0 (id, id_map[i].old) == 0)
1022 			return as_store_get_app_by_id (store, id_map[i].new);
1023 		if (g_strcmp0 (id, id_map[i].new) == 0)
1024 			return as_store_get_app_by_id (store, id_map[i].old);
1025 	}
1026 
1027 	return NULL;
1028 }
1029 
1030 static gboolean
as_app_has_pkgname(AsApp * app,const gchar * pkgname)1031 as_app_has_pkgname (AsApp *app, const gchar *pkgname)
1032 {
1033 	guint i;
1034 	GPtrArray *pkgnames;
1035 
1036 	pkgnames = as_app_get_pkgnames (app);
1037 	for (i = 0; i < pkgnames->len; i++) {
1038 		const gchar *tmp = g_ptr_array_index (pkgnames, i);
1039 		if (g_strcmp0 (tmp, pkgname) == 0)
1040 			return TRUE;
1041 	}
1042 	return FALSE;
1043 }
1044 
1045 /**
1046  * as_store_get_app_by_pkgname:
1047  * @store: a #AsStore instance.
1048  * @pkgname: the package name.
1049  *
1050  * Finds an application in the store by package name.
1051  *
1052  * Returns: (transfer none): a #AsApp or %NULL
1053  *
1054  * Since: 0.1.0
1055  **/
1056 AsApp *
as_store_get_app_by_pkgname(AsStore * store,const gchar * pkgname)1057 as_store_get_app_by_pkgname (AsStore *store, const gchar *pkgname)
1058 {
1059 	AsApp *app;
1060 	AsStorePrivate *priv = GET_PRIVATE (store);
1061 	guint i;
1062 	g_autoptr(GMutexLocker) locker = NULL;
1063 
1064 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
1065 
1066 	locker = g_mutex_locker_new (&priv->mutex);
1067 
1068 	/* in most cases, we can use the cache */
1069 	app = g_hash_table_lookup (priv->hash_pkgname, pkgname);
1070 	if (app != NULL)
1071 		return app;
1072 
1073 	/* fall back in case the user adds to app to the store, *then*
1074 	 * uses as_app_add_pkgname() on the app */
1075 	for (i = 0; i < priv->array->len; i++) {
1076 		app = g_ptr_array_index (priv->array, i);
1077 		if (as_app_has_pkgname (app, pkgname))
1078 			return app;
1079 	}
1080 
1081 	/* not found */
1082 	return NULL;
1083 }
1084 
1085 /**
1086  * as_store_get_app_by_pkgnames:
1087  * @store: a #AsStore instance.
1088  * @pkgnames: the package names to find.
1089  *
1090  * Finds an application in the store by any of the possible package names.
1091  *
1092  * Returns: (transfer none): a #AsApp or %NULL
1093  *
1094  * Since: 0.4.1
1095  **/
1096 AsApp *
as_store_get_app_by_pkgnames(AsStore * store,gchar ** pkgnames)1097 as_store_get_app_by_pkgnames (AsStore *store, gchar **pkgnames)
1098 {
1099 	AsApp *app;
1100 	AsStorePrivate *priv = GET_PRIVATE (store);
1101 	guint i;
1102 
1103 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
1104 	g_return_val_if_fail (pkgnames != NULL, NULL);
1105 
1106 	for (i = 0; pkgnames[i] != NULL; i++) {
1107 		g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex);
1108 		app = g_hash_table_lookup (priv->hash_pkgname, pkgnames[i]);
1109 		if (app != NULL)
1110 			return app;
1111 	}
1112 	return NULL;
1113 }
1114 
1115 /**
1116  * as_store_remove_app:
1117  * @store: a #AsStore instance.
1118  * @app: a #AsApp instance.
1119  *
1120  * Removes an application from the store if it exists.
1121  *
1122  * Since: 0.1.0
1123  **/
1124 void
as_store_remove_app(AsStore * store,AsApp * app)1125 as_store_remove_app (AsStore *store, AsApp *app)
1126 {
1127 	AsStorePrivate *priv = GET_PRIVATE (store);
1128 	GPtrArray *apps;
1129 
1130 	g_return_if_fail (AS_IS_STORE (store));
1131 
1132 	/* emit before removal */
1133 	g_signal_emit (store, signals[SIGNAL_APP_REMOVED], 0, app);
1134 
1135 	/* only remove this specific unique app */
1136 	g_mutex_lock (&priv->mutex);
1137 	apps = g_hash_table_lookup (priv->hash_id, as_app_get_id (app));
1138 	if (apps != NULL) {
1139 		g_ptr_array_remove (apps, app);
1140 
1141 		/* remove the array as well if it was the last app as the
1142 		 * AsRefString with the app ID may get freed now */
1143 		if (apps->len == 0)
1144 			g_hash_table_remove (priv->hash_id, as_app_get_id (app));
1145 	}
1146 
1147 	g_hash_table_remove (priv->hash_unique_id, as_app_get_unique_id (app));
1148 	g_ptr_array_remove (priv->array, app);
1149 	g_hash_table_remove_all (priv->metadata_indexes);
1150 	g_mutex_unlock (&priv->mutex);
1151 
1152 	/* removed */
1153 	as_store_perhaps_emit_changed (store, "remove-app");
1154 }
1155 
1156 /**
1157  * as_store_remove_app_by_id:
1158  * @store: a #AsStore instance.
1159  * @id: an application id
1160  *
1161  * Removes an application from the store if it exists.
1162  *
1163  * Since: 0.3.0
1164  **/
1165 void
as_store_remove_app_by_id(AsStore * store,const gchar * id)1166 as_store_remove_app_by_id (AsStore *store, const gchar *id)
1167 {
1168 	AsStorePrivate *priv = GET_PRIVATE (store);
1169 	g_autoptr(GPtrArray) apps = NULL;
1170 
1171 	g_return_if_fail (AS_IS_STORE (store));
1172 
1173 	g_mutex_lock (&priv->mutex);
1174 	if (!g_hash_table_remove (priv->hash_id, id)) {
1175 		g_mutex_unlock (&priv->mutex);
1176 		return;
1177 	}
1178 	g_mutex_unlock (&priv->mutex);
1179 
1180 	apps = as_store_dup_apps (store);
1181 	for (guint i = 0; i < apps->len; i++) {
1182 		AsApp *app = g_ptr_array_index (apps, i);
1183 
1184 		if (g_strcmp0 (id, as_app_get_id (app)) != 0)
1185 			continue;
1186 
1187 		/* emit before removal */
1188 		g_signal_emit (store, signals[SIGNAL_APP_REMOVED], 0, app);
1189 
1190 		g_mutex_lock (&priv->mutex);
1191 		g_ptr_array_remove (priv->array, app);
1192 		g_hash_table_remove (priv->hash_unique_id,
1193 				     as_app_get_unique_id (app));
1194 		g_mutex_unlock (&priv->mutex);
1195 	}
1196 	g_mutex_lock (&priv->mutex);
1197 	g_hash_table_remove_all (priv->metadata_indexes);
1198 	g_mutex_unlock (&priv->mutex);
1199 
1200 	/* removed */
1201 	as_store_perhaps_emit_changed (store, "remove-app-by-id");
1202 }
1203 
1204 static gboolean
_as_app_is_perhaps_merge_component(AsApp * app)1205 _as_app_is_perhaps_merge_component (AsApp *app)
1206 {
1207 	if (as_app_get_kind (app) != AS_APP_KIND_DESKTOP)
1208 		return FALSE;
1209 	if (as_app_get_format_by_kind (app, AS_FORMAT_KIND_APPSTREAM) == NULL)
1210 		return FALSE;
1211 	if (as_app_get_bundle_kind (app) != AS_BUNDLE_KIND_UNKNOWN)
1212 		return FALSE;
1213 	if (as_app_get_name (app, NULL) != NULL)
1214 		return FALSE;
1215 	return TRUE;
1216 }
1217 
1218 /**
1219  * as_store_add_apps:
1220  * @store: a #AsStore instance.
1221  * @apps: (element-type AsApp): an array of apps
1222  *
1223  * Adds several applications to the store.
1224  *
1225  * Additionally only applications where the kind is known will be added.
1226  *
1227  * Since: 0.6.4
1228  **/
1229 void
as_store_add_apps(AsStore * store,GPtrArray * apps)1230 as_store_add_apps (AsStore *store, GPtrArray *apps)
1231 {
1232 	guint i;
1233 	_cleanup_uninhibit_ guint32 *tok = NULL;
1234 
1235 	g_return_if_fail (AS_IS_STORE (store));
1236 
1237 	/* emit once when finished */
1238 	tok = as_store_changed_inhibit (store);
1239 	for (i = 0; i < apps->len; i++) {
1240 		AsApp *app = g_ptr_array_index (apps, i);
1241 		as_store_add_app (store, app);
1242 	}
1243 
1244 	/* this store has changed */
1245 	as_store_changed_uninhibit (&tok);
1246 	as_store_perhaps_emit_changed (store, "add-apps");
1247 }
1248 
1249 /**
1250  * as_store_add_app:
1251  * @store: a #AsStore instance.
1252  * @app: a #AsApp instance.
1253  *
1254  * Adds an application to the store. If a lower priority application has already
1255  * been added then this new application will replace it.
1256  *
1257  * Additionally only applications where the kind is known will be added.
1258  *
1259  * Since: 0.1.0
1260  **/
1261 void
as_store_add_app(AsStore * store,AsApp * app)1262 as_store_add_app (AsStore *store, AsApp *app)
1263 {
1264 	AsApp *item = NULL;
1265 	AsStorePrivate *priv = GET_PRIVATE (store);
1266 	GPtrArray *apps;
1267 	GPtrArray *pkgnames;
1268 	const gchar *id;
1269 	const gchar *pkgname;
1270 	guint i;
1271 
1272 	g_return_if_fail (AS_IS_STORE (store));
1273 
1274 	/* have we recorded this before? */
1275 	id = as_app_get_id (app);
1276 	if (id == NULL) {
1277 		g_warning ("application has no ID set");
1278 		return;
1279 	}
1280 
1281 	/* use some hacky logic to support older files */
1282 	if ((priv->add_flags & AS_STORE_ADD_FLAG_USE_MERGE_HEURISTIC) > 0 &&
1283 	    _as_app_is_perhaps_merge_component (app)) {
1284 		as_app_set_merge_kind (app, AS_APP_MERGE_KIND_APPEND);
1285 	}
1286 
1287 	/* FIXME: deal with the differences between append and replace */
1288 	if (as_app_get_merge_kind (app) == AS_APP_MERGE_KIND_APPEND ||
1289 	    as_app_get_merge_kind (app) == AS_APP_MERGE_KIND_REPLACE)
1290 		as_app_add_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX);
1291 
1292 	/* ensure app has format set */
1293 	if (as_app_get_format_default (app) == NULL) {
1294 		g_autoptr(AsFormat) format = as_format_new ();
1295 		as_format_set_kind (format, AS_FORMAT_KIND_UNKNOWN);
1296 		as_app_add_format (app, format);
1297 	}
1298 
1299 	/* this is a special merge component */
1300 	if (as_app_has_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX)) {
1301 		guint64 flags = AS_APP_SUBSUME_FLAG_MERGE;
1302 		AsAppMergeKind merge_kind = as_app_get_merge_kind (app);
1303 		g_autoptr(GPtrArray) apps_changed = NULL;
1304 
1305 		g_mutex_lock (&priv->mutex);
1306 		apps = g_hash_table_lookup (priv->hash_merge_id, id);
1307 		if (apps == NULL) {
1308 			apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
1309 			g_hash_table_insert (priv->hash_merge_id,
1310 					     g_strdup (as_app_get_id (app)),
1311 					     apps);
1312 		}
1313 		g_debug ("added %s merge component: %s",
1314 			 as_app_merge_kind_to_string (merge_kind),
1315 			 as_app_get_unique_id (app));
1316 		g_ptr_array_add (apps, g_object_ref (app));
1317 		g_mutex_unlock (&priv->mutex);
1318 
1319 		/* apply to existing components */
1320 		flags |= AS_APP_SUBSUME_FLAG_NO_OVERWRITE;
1321 		if (merge_kind == AS_APP_MERGE_KIND_REPLACE)
1322 			flags |= AS_APP_SUBSUME_FLAG_REPLACE;
1323 
1324 		apps_changed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
1325 
1326 		g_mutex_lock (&priv->mutex);
1327 		for (i = 0; i < priv->array->len; i++) {
1328 			AsApp *app_tmp = g_ptr_array_index (priv->array, i);
1329 			if (g_strcmp0 (as_app_get_id (app_tmp), id) != 0)
1330 				continue;
1331 			g_debug ("using %s merge component %s on %s",
1332 				 as_app_merge_kind_to_string (merge_kind),
1333 				 id, as_app_get_unique_id (app_tmp));
1334 			as_app_subsume_full (app_tmp, app, flags);
1335 			g_ptr_array_add (apps_changed, g_object_ref (app_tmp));
1336 		}
1337 		g_mutex_unlock (&priv->mutex);
1338 		for (i = 0; i < apps_changed->len; i++) {
1339 			AsApp *app_tmp = g_ptr_array_index (apps_changed, i);
1340 			/* emit after changes have been made */
1341 			g_signal_emit (store, signals[SIGNAL_APP_CHANGED],
1342 				       0, app_tmp);
1343 		}
1344 		return;
1345 	}
1346 
1347 	/* is there any merge components to add to this app */
1348 	g_mutex_lock (&priv->mutex);
1349 	apps = g_hash_table_lookup (priv->hash_merge_id, id);
1350 	if (apps != NULL) {
1351 		for (i = 0; i < apps->len; i++) {
1352 			AsApp *app_tmp = g_ptr_array_index (apps, i);
1353 			AsAppMergeKind merge_kind = as_app_get_merge_kind (app_tmp);
1354 			guint64 flags = AS_APP_SUBSUME_FLAG_MERGE;
1355 			g_debug ("using %s merge component %s on %s",
1356 				 as_app_merge_kind_to_string (merge_kind),
1357 				 as_app_get_unique_id (app_tmp),
1358 				 as_app_get_unique_id (app));
1359 			flags |= AS_APP_SUBSUME_FLAG_NO_OVERWRITE;
1360 			if (merge_kind == AS_APP_MERGE_KIND_REPLACE)
1361 				flags |= AS_APP_SUBSUME_FLAG_REPLACE;
1362 			as_app_subsume_full (app, app_tmp, flags);
1363 		}
1364 	}
1365 	g_mutex_unlock (&priv->mutex);
1366 
1367 	/* find the item */
1368 	if (priv->add_flags & AS_STORE_ADD_FLAG_USE_UNIQUE_ID) {
1369 		item = as_store_get_app_by_app (store, app);
1370 	} else {
1371 		g_mutex_lock (&priv->mutex);
1372 		apps = g_hash_table_lookup (priv->hash_id, id);
1373 		if (apps != NULL && apps->len > 0)
1374 			item = g_ptr_array_index (apps, 0);
1375 		g_mutex_unlock (&priv->mutex);
1376 	}
1377 	if (item != NULL) {
1378 		AsFormat *app_format = as_app_get_format_default (app);
1379 		AsFormat *item_format = as_app_get_format_default (item);
1380 
1381 		/* sanity check */
1382 		if (app_format == NULL) {
1383 			g_warning ("no format specified in %s",
1384 				   as_app_get_unique_id (app));
1385 			return;
1386 		}
1387 		if (item_format == NULL) {
1388 			g_warning ("no format specified in %s",
1389 				   as_app_get_unique_id (item));
1390 			return;
1391 		}
1392 
1393 		/* the previously stored app is what we actually want */
1394 		if ((priv->add_flags & AS_STORE_ADD_FLAG_PREFER_LOCAL) > 0) {
1395 
1396 			if (as_format_get_kind (app_format) == AS_FORMAT_KIND_APPSTREAM &&
1397 			    as_format_get_kind (item_format) == AS_FORMAT_KIND_APPDATA) {
1398 				g_debug ("ignoring AppStream entry as AppData exists: %s:%s",
1399 					 as_app_get_unique_id (app),
1400 					 as_app_get_unique_id (item));
1401 				as_app_subsume_full (app, item,
1402 						     AS_APP_SUBSUME_FLAG_FORMATS |
1403 						     AS_APP_SUBSUME_FLAG_RELEASES);
1404 				return;
1405 			}
1406 			if (as_format_get_kind (app_format) == AS_FORMAT_KIND_APPSTREAM &&
1407 			    as_format_get_kind (item_format) == AS_FORMAT_KIND_DESKTOP) {
1408 				g_debug ("ignoring AppStream entry as desktop exists: %s:%s",
1409 					 as_app_get_unique_id (app),
1410 					 as_app_get_unique_id (item));
1411 				return;
1412 			}
1413 			if (as_format_get_kind (app_format) == AS_FORMAT_KIND_APPDATA &&
1414 			    as_format_get_kind (item_format) == AS_FORMAT_KIND_DESKTOP) {
1415 				g_debug ("merging duplicate AppData:desktop entries: %s:%s",
1416 					 as_app_get_unique_id (app),
1417 					 as_app_get_unique_id (item));
1418 				as_app_subsume_full (app, item,
1419 						     AS_APP_SUBSUME_FLAG_BOTH_WAYS |
1420 						     AS_APP_SUBSUME_FLAG_DEDUPE);
1421 				return;
1422 			}
1423 			if (as_format_get_kind (app_format) == AS_FORMAT_KIND_DESKTOP &&
1424 			    as_format_get_kind (item_format) == AS_FORMAT_KIND_APPDATA) {
1425 				g_debug ("merging duplicate desktop:AppData entries: %s:%s",
1426 					 as_app_get_unique_id (app),
1427 					 as_app_get_unique_id (item));
1428 				as_app_subsume_full (app, item,
1429 						     AS_APP_SUBSUME_FLAG_BOTH_WAYS |
1430 						     AS_APP_SUBSUME_FLAG_DEDUPE);
1431 				return;
1432 			}
1433 
1434 			/* xxx */
1435 			as_app_subsume_full (app, item,
1436 					     AS_APP_SUBSUME_FLAG_FORMATS |
1437 					     AS_APP_SUBSUME_FLAG_RELEASES);
1438 
1439 		} else {
1440 			if (as_format_get_kind (app_format) == AS_FORMAT_KIND_APPDATA &&
1441 			    as_format_get_kind (item_format) == AS_FORMAT_KIND_APPSTREAM &&
1442 			    as_app_get_scope (app) == AS_APP_SCOPE_SYSTEM) {
1443 				g_debug ("ignoring AppData entry as AppStream exists: %s:%s",
1444 					 as_app_get_unique_id (app),
1445 					 as_app_get_unique_id (item));
1446 				as_app_subsume_full (item, app,
1447 						     AS_APP_SUBSUME_FLAG_FORMATS |
1448 						     AS_APP_SUBSUME_FLAG_RELEASES);
1449 				return;
1450 			}
1451 			if (as_format_get_kind (app_format) == AS_FORMAT_KIND_DESKTOP &&
1452 			    as_format_get_kind (item_format) == AS_FORMAT_KIND_APPSTREAM &&
1453 			    as_app_get_scope (app) == AS_APP_SCOPE_SYSTEM) {
1454 				g_debug ("ignoring desktop entry as AppStream exists: %s:%s",
1455 					 as_app_get_unique_id (app),
1456 					 as_app_get_unique_id (item));
1457 				as_app_subsume_full (item, app,
1458 						     AS_APP_SUBSUME_FLAG_FORMATS);
1459 				return;
1460 			}
1461 
1462 			/* the previously stored app is higher priority */
1463 			if (as_app_get_priority (item) >
1464 			    as_app_get_priority (app)) {
1465 				g_debug ("ignoring duplicate %s:%s entry: %s:%s",
1466 					 as_format_kind_to_string (as_format_get_kind (app_format)),
1467 					 as_format_kind_to_string (as_format_get_kind (item_format)),
1468 					 as_app_get_unique_id (app),
1469 					 as_app_get_unique_id (item));
1470 				as_app_subsume_full (item, app,
1471 						     AS_APP_SUBSUME_FLAG_FORMATS |
1472 						     AS_APP_SUBSUME_FLAG_RELEASES);
1473 				return;
1474 			}
1475 
1476 			/* same priority */
1477 			if (as_app_get_priority (item) ==
1478 			    as_app_get_priority (app)) {
1479 				g_debug ("merging duplicate %s:%s entries: %s:%s",
1480 					 as_format_kind_to_string (as_format_get_kind (app_format)),
1481 					 as_format_kind_to_string (as_format_get_kind (item_format)),
1482 					 as_app_get_unique_id (app),
1483 					 as_app_get_unique_id (item));
1484 				as_app_subsume_full (app, item,
1485 						     AS_APP_SUBSUME_FLAG_BOTH_WAYS |
1486 						     AS_APP_SUBSUME_FLAG_DEDUPE);
1487 				return;
1488 			}
1489 		}
1490 
1491 		/* this new item has a higher priority than the one we've
1492 		 * previously stored */
1493 		g_debug ("removing %s entry: %s",
1494 			 as_format_kind_to_string (as_format_get_kind (item_format)),
1495 			 as_app_get_unique_id (item));
1496 		as_app_subsume_full (app, item,
1497 				     AS_APP_SUBSUME_FLAG_FORMATS |
1498 				     AS_APP_SUBSUME_FLAG_RELEASES);
1499 		as_store_remove_app (store, item);
1500 	}
1501 
1502 	/* create hash of id:[apps] if required */
1503 	g_mutex_lock (&priv->mutex);
1504 	apps = g_hash_table_lookup (priv->hash_id, id);
1505 	if (apps == NULL) {
1506 		apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
1507 		g_hash_table_insert (priv->hash_id,
1508 				     g_strdup (as_app_get_id (app)),
1509 				     apps);
1510 	}
1511 	g_ptr_array_add (apps, g_object_ref (app));
1512 
1513 	/* success, add to array */
1514 	g_ptr_array_add (priv->array, g_object_ref (app));
1515 	g_hash_table_insert (priv->hash_unique_id,
1516 			     g_strdup (as_app_get_unique_id (app)),
1517 			     g_object_ref (app));
1518 	pkgnames = as_app_get_pkgnames (app);
1519 	for (i = 0; i < pkgnames->len; i++) {
1520 		pkgname = g_ptr_array_index (pkgnames, i);
1521 		g_hash_table_insert (priv->hash_pkgname,
1522 				     g_strdup (pkgname),
1523 				     g_object_ref (app));
1524 	}
1525 	g_mutex_unlock (&priv->mutex);
1526 
1527 	/* add helper objects */
1528 	as_app_set_stemmer (app, priv->stemmer);
1529 	as_app_set_search_blacklist (app, priv->search_blacklist);
1530 	as_app_set_search_match (app, priv->search_match);
1531 
1532 	/* added */
1533 	g_signal_emit (store, signals[SIGNAL_APP_ADDED], 0, app);
1534 	as_store_perhaps_emit_changed (store, "add-app");
1535 }
1536 
1537 static void
as_store_match_addons_app(AsStore * store,AsApp * app)1538 as_store_match_addons_app (AsStore *store, AsApp *app)
1539 {
1540 	GPtrArray *plugin_ids;
1541 	guint i;
1542 	guint j;
1543 
1544 	plugin_ids = as_app_get_extends (app);
1545 	if (plugin_ids->len == 0) {
1546 		g_warning ("%s was of type addon but had no extends",
1547 			   as_app_get_id (app));
1548 		return;
1549 	}
1550 	for (j = 0; j < plugin_ids->len; j++) {
1551 		g_autoptr(GPtrArray) parents = NULL;
1552 		const gchar *tmp = g_ptr_array_index (plugin_ids, j);
1553 
1554 		/* restrict to same scope and bundle kind */
1555 		parents = as_store_get_apps_by_id (store, tmp);
1556 		for (i = 0; i < parents->len;  i++) {
1557 			AsApp *parent = g_ptr_array_index (parents, i);
1558 			if (as_app_get_scope (app) != as_app_get_scope (parent))
1559 				continue;
1560 			if (as_app_get_bundle_kind (app) != as_app_get_bundle_kind (parent))
1561 				continue;
1562 			as_app_add_addon (parent, app);
1563 		}
1564 	}
1565 }
1566 
1567 static void
as_store_match_addons(AsStore * store)1568 as_store_match_addons (AsStore *store)
1569 {
1570 	AsStorePrivate *priv = GET_PRIVATE (store);
1571 	guint i;
1572 	g_autoptr(AsProfileTask) ptask = NULL;
1573 	g_autoptr(GPtrArray) apps = NULL;
1574 
1575 	/* profile */
1576 	ptask = as_profile_start_literal (priv->profile, "AsStore:match-addons");
1577 	g_assert (ptask != NULL);
1578 	apps = as_store_dup_apps (store);
1579 	for (i = 0; i < apps->len; i++) {
1580 		AsApp *app = g_ptr_array_index (apps, i);
1581 		if (as_app_get_kind (app) == AS_APP_KIND_ADDON)
1582 			as_store_match_addons_app (store, app);
1583 	}
1584 }
1585 
1586 /**
1587  * as_store_fixup_id_prefix:
1588  *
1589  * When we lived in a world where all software got installed to /usr we could
1590  * continue to use the application ID as the primary identifier.
1591  *
1592  * Now we support installing things per-user, and also per-system and per-user
1593  * flatpak (not even including jhbuild) we need to use the id prefix to
1594  * disambiguate the different applications according to a 'scope'.
1595  *
1596  * This means when we launch a specific application in the software center
1597  * we know what desktop file to use, and we can also then support different
1598  * versions of applications installed system wide and per-user.
1599  **/
1600 static void
as_store_fixup_id_prefix(AsApp * app,const gchar * id_prefix)1601 as_store_fixup_id_prefix (AsApp *app, const gchar *id_prefix)
1602 {
1603 	g_autofree gchar *id = NULL;
1604 
1605 	/* ignore this for compatibility reasons */
1606 	if (id_prefix == NULL || g_strcmp0 (id_prefix, "system") == 0)
1607 		return;
1608 	id = g_strdup_printf ("%s:%s", id_prefix, as_app_get_id (app));
1609 	as_app_set_id (app, id);
1610 }
1611 
1612 static gboolean
as_store_from_root(AsStore * store,AsNode * root,AsAppScope scope,const gchar * icon_prefix,const gchar * source_filename,const gchar * arch,guint32 load_flags,GError ** error)1613 as_store_from_root (AsStore *store,
1614 		    AsNode *root,
1615 		    AsAppScope scope,
1616 		    const gchar *icon_prefix,
1617 		    const gchar *source_filename,
1618 		    const gchar *arch,
1619 		    guint32 load_flags,
1620 		    GError **error)
1621 {
1622 	AsStorePrivate *priv = GET_PRIVATE (store);
1623 	AsNode *apps;
1624 	AsNode *n;
1625 	const gchar *tmp;
1626 	const gchar *origin_delim = ":";
1627 	gchar *str;
1628 	g_autoptr(AsNodeContext) ctx = NULL;
1629 	g_autofree gchar *icon_path = NULL;
1630 	g_autofree gchar *id_prefix_app = NULL;
1631 	g_autofree gchar *origin_app = NULL;
1632 	g_autofree gchar *origin_app_icons = NULL;
1633 	_cleanup_uninhibit_ guint32 *tok = NULL;
1634 	g_autoptr(AsFormat) format = NULL;
1635 	g_autoptr(AsProfileTask) ptask = NULL;
1636 	g_autoptr(AsRefString) icon_path_str = NULL;
1637 	g_autoptr(AsRefString) origin_str = NULL;
1638 	gboolean origin_is_flatpak;
1639 
1640 	g_return_val_if_fail (AS_IS_STORE (store), FALSE);
1641 
1642 	/* make throws us under a bus, yet again */
1643 	tmp = g_getenv ("AS_SELF_TEST_PREFIX_DELIM");
1644 	if (tmp != NULL)
1645 		origin_delim = tmp;
1646 
1647 	/* profile */
1648 	ptask = as_profile_start_literal (priv->profile, "AsStore:store-from-root");
1649 	g_assert (ptask != NULL);
1650 
1651 	/* emit once when finished */
1652 	tok = as_store_changed_inhibit (store);
1653 
1654 	apps = as_node_find (root, "components");
1655 	if (apps == NULL) {
1656 		apps = as_node_find (root, "applications");
1657 		if (apps == NULL) {
1658 			g_set_error_literal (error,
1659 					     AS_STORE_ERROR,
1660 					     AS_STORE_ERROR_FAILED,
1661 					     "No valid root node specified");
1662 			return FALSE;
1663 		}
1664 		priv->problems |= AS_STORE_PROBLEM_LEGACY_ROOT;
1665 	}
1666 
1667 	/* get version */
1668 	tmp = as_node_get_attribute (apps, "version");
1669 	if (tmp != NULL)
1670 		priv->api_version = g_ascii_strtod (tmp, NULL);
1671 
1672 	/* set in the XML file */
1673 	tmp = as_node_get_attribute (apps, "origin");
1674 	if (tmp != NULL)
1675 		as_store_set_origin (store, tmp);
1676 
1677 	/* origin has prefix already specified in the XML */
1678 	if (priv->origin != NULL) {
1679 		str = g_strstr_len (priv->origin, -1, origin_delim);
1680 		if (str != NULL) {
1681 			id_prefix_app = g_strdup (priv->origin);
1682 			str = g_strstr_len (id_prefix_app, -1, origin_delim);
1683 			if (str != NULL) {
1684 				str[0] = '\0';
1685 				origin_app = g_strdup (str + 1);
1686 				origin_app_icons = g_strdup (str + 1);
1687 			}
1688 		}
1689 	}
1690 
1691 	origin_is_flatpak = g_strcmp0 (priv->origin, "flatpak") == 0;
1692 
1693 	/* special case flatpak symlinks -- scope:name.xml.gz */
1694 	if (origin_app == NULL &&
1695 	    origin_is_flatpak &&
1696 	    source_filename != NULL &&
1697 	    g_file_test (source_filename, G_FILE_TEST_IS_SYMLINK)) {
1698 		g_autofree gchar *source_basename = NULL;
1699 
1700 		/* get the origin */
1701 		source_basename = g_path_get_basename (source_filename);
1702 		str = g_strrstr (source_basename, ".xml");
1703 		if (str != NULL) {
1704 			str[0] = '\0';
1705 			origin_app_icons = g_strdup (source_basename);
1706 		}
1707 
1708 		/* get the id-prefix */
1709 		str = g_strstr_len (source_basename, -1, origin_delim);
1710 		if (str != NULL) {
1711 			str[0] = '\0';
1712 			origin_app = g_strdup (str + 1);
1713 			id_prefix_app = g_strdup (source_basename);
1714 		}
1715 
1716 		/* although in ~, this is a system scope app */
1717 		if (g_strcmp0 (id_prefix_app, "flatpak") == 0)
1718 			scope = AS_APP_SCOPE_SYSTEM;
1719 	}
1720 
1721 	/* fallback */
1722 	if (origin_app == NULL && !origin_is_flatpak) {
1723 		id_prefix_app = g_strdup (as_app_scope_to_string (scope));
1724 		origin_app = g_strdup (priv->origin);
1725 		origin_app_icons = g_strdup (priv->origin);
1726 	}
1727 
1728 	/* print what cleverness we did */
1729 	if (g_strcmp0 (origin_app, priv->origin) != 0) {
1730 		g_debug ("using app origin of '%s' rather than '%s'",
1731 			 origin_app, priv->origin);
1732 	}
1733 
1734 	/* guess the icon path after we've read the origin and then look for
1735 	 * ../icons/$origin if the topdir is 'xmls', falling back to ./icons */
1736 	if (icon_prefix != NULL) {
1737 		g_autofree gchar *topdir = NULL;
1738 		topdir = g_path_get_basename (icon_prefix);
1739 		if ((g_strcmp0 (topdir, "xmls") == 0 ||
1740 		     g_strcmp0 (topdir, "yaml") == 0)
1741 		    && origin_app_icons != NULL) {
1742 			g_autofree gchar *dirname = NULL;
1743 			dirname = g_path_get_dirname (icon_prefix);
1744 			icon_path = g_build_filename (dirname,
1745 						      "icons",
1746 						      origin_app_icons,
1747 						      NULL);
1748 		} else {
1749 			icon_path = g_build_filename (icon_prefix, "icons", NULL);
1750 		}
1751 	}
1752 	g_debug ("using icon path %s", icon_path);
1753 
1754 	/* set in the XML file */
1755 	tmp = as_node_get_attribute (apps, "builder_id");
1756 	if (tmp != NULL)
1757 		as_store_set_builder_id (store, tmp);
1758 
1759 	/* create refcounted versions */
1760 	if (origin_app != NULL)
1761 		origin_str = as_ref_string_new (origin_app);
1762 	if (icon_path != NULL)
1763 		icon_path_str = as_ref_string_new (icon_path);
1764 
1765 	/* create format for all added apps */
1766 	format = as_format_new ();
1767 	as_format_set_kind (format, AS_FORMAT_KIND_APPSTREAM);
1768 	if (source_filename != NULL)
1769 		as_format_set_filename (format, source_filename);
1770 
1771 	ctx = as_node_context_new ();
1772 	for (n = apps->children; n != NULL; n = n->next) {
1773 		g_autoptr(GError) error_local = NULL;
1774 		g_autoptr(AsApp) app = NULL;
1775 		if (as_node_get_tag (n) != AS_TAG_COMPONENT)
1776 			continue;
1777 
1778 		/* do the filtering here */
1779 		if (priv->filter != 0) {
1780 			if (g_strcmp0 (as_node_get_name (n), "component") == 0) {
1781 				AsAppKind kind_tmp;
1782 				tmp = as_node_get_attribute (n, "type");
1783 				kind_tmp = as_app_kind_from_string (tmp);
1784 				if ((priv->filter & (1u << kind_tmp)) == 0)
1785 					continue;
1786 			}
1787 		}
1788 
1789 		app = as_app_new ();
1790 		if (icon_path_str != NULL)
1791 			as_app_set_icon_path_rstr (app, icon_path_str);
1792 		if (arch != NULL)
1793 			as_app_add_arch (app, arch);
1794 		as_app_add_format (app, format);
1795 		as_app_set_scope (app, scope);
1796 		if (!as_app_node_parse (app, n, ctx, &error_local)) {
1797 			g_set_error (error,
1798 				     AS_STORE_ERROR,
1799 				     AS_STORE_ERROR_FAILED,
1800 				     "Failed to parse root: %s",
1801 				     error_local->message);
1802 			return FALSE;
1803 		}
1804 
1805 		/* filter out non-merge types */
1806 		if (load_flags & AS_STORE_LOAD_FLAG_ONLY_MERGE_APPS) {
1807 			if (as_app_get_merge_kind (app) != AS_APP_MERGE_KIND_REPLACE &&
1808 			    as_app_get_merge_kind (app) != AS_APP_MERGE_KIND_APPEND) {
1809 				continue;
1810 			}
1811 		}
1812 
1813 		/* set the ID prefix */
1814 		if ((priv->add_flags & AS_STORE_ADD_FLAG_USE_UNIQUE_ID) == 0)
1815 			as_store_fixup_id_prefix (app, id_prefix_app);
1816 
1817 		if (origin_str != NULL)
1818 			as_app_set_origin_rstr (app, origin_str);
1819 		as_store_add_app (store, app);
1820 	}
1821 
1822 	/* add addon kinds to their parent AsApp */
1823 	as_store_match_addons (store);
1824 
1825 	/* this store has changed */
1826 	as_store_changed_uninhibit (&tok);
1827 	as_store_perhaps_emit_changed (store, "from-root");
1828 
1829 	return TRUE;
1830 }
1831 
1832 static gboolean
is_dep11_data(GBytes * bytes)1833 is_dep11_data (GBytes *bytes)
1834 {
1835 	const gchar *data;
1836 	gsize size;
1837 
1838 	/* look for DEP-11 header */
1839 	data = g_bytes_get_data (bytes, &size);
1840 	return g_strstr_len (data, size, "File: DEP-11") != NULL;
1841 }
1842 
1843 static gboolean
load_yaml(AsStore * store,AsYaml * root,const gchar * source_filename,AsAppScope scope,GCancellable * cancellable,GError ** error)1844 load_yaml (AsStore *store,
1845 	   AsYaml *root,
1846 	   const gchar *source_filename,
1847 	   AsAppScope scope,
1848 	   GCancellable *cancellable,
1849 	   GError **error)
1850 {
1851 	AsStorePrivate *priv = GET_PRIVATE (store);
1852 	AsNode *app_n;
1853 	AsNode *n;
1854 	const gchar *tmp;
1855 	g_autoptr(AsNodeContext) ctx = NULL;
1856 	g_autofree gchar *icon_path = NULL;
1857 	g_autoptr(AsFormat) format = NULL;
1858 	_cleanup_uninhibit_ guint32 *tok = NULL;
1859 
1860 	/* get header information */
1861 	ctx = as_node_context_new ();
1862 	for (n = root->children->children; n != NULL; n = n->next) {
1863 		tmp = as_yaml_node_get_key (n);
1864 		if (g_strcmp0 (tmp, "Origin") == 0) {
1865 			as_store_set_origin (store, as_yaml_node_get_value (n));
1866 			continue;
1867 		}
1868 		if (g_strcmp0 (tmp, "Version") == 0) {
1869 			if (as_yaml_node_get_value (n) != NULL)
1870 				as_store_set_api_version (store, g_ascii_strtod (as_yaml_node_get_value (n), NULL));
1871 			continue;
1872 		}
1873 		if (g_strcmp0 (tmp, "MediaBaseUrl") == 0) {
1874 			as_node_context_set_media_base_url (ctx, as_yaml_node_get_value (n));
1875 			continue;
1876 		}
1877 	}
1878 
1879 	/* if we have an origin either from the YAML or _set_origin() */
1880 	if (priv->origin != NULL && source_filename != NULL) {
1881 		g_autofree gchar *icon_prefix1 = NULL;
1882 		g_autofree gchar *icon_prefix2 = NULL;
1883 		icon_prefix1 = g_path_get_dirname (source_filename);
1884 		icon_prefix2 = g_path_get_dirname (icon_prefix1);
1885 		icon_path = g_build_filename (icon_prefix2,
1886 					      "icons",
1887 					      priv->origin,
1888 					      NULL);
1889 	}
1890 
1891 	/* emit once when finished */
1892 	tok = as_store_changed_inhibit (store);
1893 
1894 	/* add format to each app */
1895 	if (source_filename != NULL) {
1896 		format = as_format_new ();
1897 		as_format_set_kind (format, AS_FORMAT_KIND_APPSTREAM);
1898 		as_format_set_filename (format, source_filename);
1899 	}
1900 
1901 	/* parse applications */
1902 	for (app_n = root->children->next; app_n != NULL; app_n = app_n->next) {
1903 		g_autoptr(AsApp) app = NULL;
1904 		if (app_n->children == NULL)
1905 			continue;
1906 		app = as_app_new ();
1907 
1908 		/* do the filtering here */
1909 		if (priv->filter != 0) {
1910 			if ((priv->filter & (1u << as_app_get_kind (app))) == 0)
1911 				continue;
1912 		}
1913 
1914 		if (icon_path != NULL)
1915 			as_app_set_icon_path (app, icon_path);
1916 		as_app_set_scope (app, scope);
1917 		if (format != NULL)
1918 			as_app_add_format (app, format);
1919 		if (!as_app_node_parse_dep11 (app, app_n, ctx, error))
1920 			return FALSE;
1921 		as_app_set_origin (app, priv->origin);
1922 		if (as_app_get_id (app) != NULL)
1923 			as_store_add_app (store, app);
1924 	}
1925 
1926 	/* emit changed */
1927 	as_store_changed_uninhibit (&tok);
1928 	as_store_perhaps_emit_changed (store, "yaml-file");
1929 
1930 	return TRUE;
1931 }
1932 
1933 static gboolean
as_store_load_yaml_file(AsStore * store,GFile * file,AsAppScope scope,GCancellable * cancellable,GError ** error)1934 as_store_load_yaml_file (AsStore *store,
1935 			 GFile *file,
1936 			 AsAppScope scope,
1937 			 GCancellable *cancellable,
1938 			 GError **error)
1939 {
1940 	AsStorePrivate *priv = GET_PRIVATE (store);
1941 	AsYamlFromFlags flags = AS_YAML_FROM_FLAG_NONE;
1942 	g_autoptr(AsYaml) root = NULL;
1943 	g_autofree gchar *source_filename = NULL;
1944 
1945 	/* load file */
1946 	if (priv->add_flags & AS_STORE_ADD_FLAG_ONLY_NATIVE_LANGS)
1947 		flags |= AS_YAML_FROM_FLAG_ONLY_NATIVE_LANGS;
1948 	root = as_yaml_from_file (file, flags, cancellable, error);
1949 	if (root == NULL)
1950 		return FALSE;
1951 
1952 	source_filename = g_file_get_path (file);
1953 	return load_yaml (store, root, source_filename, scope, cancellable, error);
1954 }
1955 
1956 static gboolean
as_store_load_yaml_data(AsStore * store,GBytes * data,AsAppScope scope,GCancellable * cancellable,GError ** error)1957 as_store_load_yaml_data (AsStore *store,
1958 			 GBytes *data,
1959 			 AsAppScope scope,
1960 			 GCancellable *cancellable,
1961 			 GError **error)
1962 {
1963 	AsStorePrivate *priv = GET_PRIVATE (store);
1964 	AsYamlFromFlags flags = AS_YAML_FROM_FLAG_NONE;
1965 	g_autoptr(AsYaml) root = NULL;
1966 
1967 	/* load file */
1968 	if (priv->add_flags & AS_STORE_ADD_FLAG_ONLY_NATIVE_LANGS)
1969 		flags |= AS_YAML_FROM_FLAG_ONLY_NATIVE_LANGS;
1970 	root = as_yaml_from_data (g_bytes_get_data (data, NULL), g_bytes_get_size (data), flags, error);
1971 	if (root == NULL)
1972 		return FALSE;
1973 
1974 	return load_yaml (store, root, NULL, scope, cancellable, error);
1975 }
1976 
1977 static void
as_store_remove_by_source_file(AsStore * store,const gchar * filename)1978 as_store_remove_by_source_file (AsStore *store, const gchar *filename)
1979 {
1980 	AsApp *app;
1981 	guint i;
1982 	const gchar *tmp;
1983 	_cleanup_uninhibit_ guint32 *tok = NULL;
1984 	g_autoptr(GPtrArray) apps = NULL;
1985 	g_autoptr(GPtrArray) ids = NULL;
1986 
1987 	/* find any applications in the store with this source file */
1988 	ids = g_ptr_array_new_with_free_func (g_free);
1989 	apps = as_store_dup_apps (store);
1990 	for (i = 0; i < apps->len; i++) {
1991 		AsFormat *format;
1992 		app = g_ptr_array_index (apps, i);
1993 		format = as_app_get_format_by_filename (app, filename);
1994 		if (format == NULL)
1995 			continue;
1996 		as_app_remove_format (app, format);
1997 
1998 		/* remove the app when all the formats have gone */
1999 		if (as_app_get_formats(app)->len == 0) {
2000 			g_debug ("no more formats for %s, deleting from store",
2001 				 as_app_get_unique_id (app));
2002 			g_ptr_array_add (ids, g_strdup (as_app_get_id (app)));
2003 		}
2004 	}
2005 
2006 	/* remove these from the store */
2007 	tok = as_store_changed_inhibit (store);
2008 	for (i = 0; i < ids->len; i++) {
2009 		tmp = g_ptr_array_index (ids, i);
2010 		g_debug ("removing %s as %s invalid", tmp, filename);
2011 		as_store_remove_app_by_id (store, tmp);
2012 	}
2013 
2014 	/* the store changed */
2015 	as_store_changed_uninhibit (&tok);
2016 	as_store_perhaps_emit_changed (store, "remove-by-source-file");
2017 }
2018 
2019 static void
as_store_watch_source_added(AsStore * store,const gchar * filename)2020 as_store_watch_source_added (AsStore *store, const gchar *filename)
2021 {
2022 	AsStorePrivate *priv = GET_PRIVATE (store);
2023 	AsStorePathData *path_data;
2024 	g_autofree gchar *dirname = NULL;
2025 	g_autoptr(GError) error = NULL;
2026 	g_autoptr(GFile) file = NULL;
2027 
2028 	/* ignore directories */
2029 	if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
2030 		return;
2031 
2032 	dirname = g_path_get_dirname (filename);
2033 	g_debug ("parsing new file %s from %s", filename, dirname);
2034 
2035 	/* we helpfully saved this */
2036 	g_mutex_lock (&priv->mutex);
2037 	path_data = g_hash_table_lookup (priv->appinfo_dirs, filename);
2038 	if (path_data == NULL)
2039 		path_data = g_hash_table_lookup (priv->appinfo_dirs, dirname);
2040 	if (path_data == NULL) {
2041 		g_warning ("no path data for %s", dirname);
2042 		g_mutex_unlock (&priv->mutex);
2043 		return;
2044 	}
2045 	g_mutex_unlock (&priv->mutex);
2046 
2047 	file = g_file_new_for_path (filename);
2048 	/* Do not watch the file for changes: we're already watching its
2049 	 * parent directory */
2050 	if (!as_store_from_file_internal (store,
2051 					  file,
2052 					  path_data->scope,
2053 					  path_data->arch,
2054 					  AS_STORE_LOAD_FLAG_NONE,
2055 					  AS_STORE_WATCH_FLAG_NONE,
2056 					  NULL, /* cancellable */
2057 					  &error)){
2058 		g_warning ("failed to rescan: %s", error->message);
2059 	}
2060 }
2061 
2062 static void
as_store_watch_source_changed(AsStore * store,const gchar * filename)2063 as_store_watch_source_changed (AsStore *store, const gchar *filename)
2064 {
2065 	/* remove all the apps provided by the source file then re-add them */
2066 	g_debug ("re-parsing changed file %s", filename);
2067 	as_store_remove_by_source_file (store, filename);
2068 	as_store_watch_source_added (store, filename);
2069 }
2070 
2071 static void
as_store_monitor_changed_cb(AsMonitor * monitor,const gchar * filename,AsStore * store)2072 as_store_monitor_changed_cb (AsMonitor *monitor,
2073 			     const gchar *filename,
2074 			     AsStore *store)
2075 {
2076 	AsStorePrivate *priv = GET_PRIVATE (store);
2077 	_cleanup_uninhibit_ guint32 *tok = NULL;
2078 
2079 	/* reload, or emit a signal */
2080 	tok = as_store_changed_inhibit (store);
2081 	if (priv->watch_flags & AS_STORE_WATCH_FLAG_ADDED)
2082 		as_store_watch_source_changed (store, filename);
2083 	as_store_changed_uninhibit (&tok);
2084 	as_store_perhaps_emit_changed (store, "file changed");
2085 }
2086 
2087 static void
as_store_monitor_added_cb(AsMonitor * monitor,const gchar * filename,AsStore * store)2088 as_store_monitor_added_cb (AsMonitor *monitor,
2089 			     const gchar *filename,
2090 			     AsStore *store)
2091 {
2092 	AsStorePrivate *priv = GET_PRIVATE (store);
2093 	_cleanup_uninhibit_ guint32 *tok = NULL;
2094 
2095 	/* reload, or emit a signal */
2096 	tok = as_store_changed_inhibit (store);
2097 	if (priv->watch_flags & AS_STORE_WATCH_FLAG_ADDED)
2098 		as_store_watch_source_added (store, filename);
2099 	as_store_changed_uninhibit (&tok);
2100 	as_store_perhaps_emit_changed (store, "file added");
2101 }
2102 
2103 static void
as_store_monitor_removed_cb(AsMonitor * monitor,const gchar * filename,AsStore * store)2104 as_store_monitor_removed_cb (AsMonitor *monitor,
2105 			     const gchar *filename,
2106 			     AsStore *store)
2107 {
2108 	AsStorePrivate *priv = GET_PRIVATE (store);
2109 	/* remove, or emit a signal */
2110 	if (priv->watch_flags & AS_STORE_WATCH_FLAG_REMOVED) {
2111 		as_store_remove_by_source_file (store, filename);
2112 	} else {
2113 		as_store_perhaps_emit_changed (store, "file removed");
2114 	}
2115 }
2116 
2117 /**
2118  * as_store_add_path_data:
2119  *
2120  * Save the path data so we can add any newly-discovered applications with the
2121  * correct prefix and architecture.
2122  **/
2123 static void
as_store_add_path_data(AsStore * store,const gchar * path,AsAppScope scope,const gchar * arch)2124 as_store_add_path_data (AsStore *store,
2125 			const gchar *path,
2126 			AsAppScope scope,
2127 			const gchar *arch)
2128 {
2129 	AsStorePrivate *priv = GET_PRIVATE (store);
2130 	AsStorePathData *path_data;
2131 
2132 	/* don't scan non-existent directories */
2133 	if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
2134 		return;
2135 	}
2136 
2137 	/* check not already exists */
2138 	g_mutex_lock (&priv->mutex);
2139 	path_data = g_hash_table_lookup (priv->appinfo_dirs, path);
2140 	g_mutex_unlock (&priv->mutex);
2141 	if (path_data != NULL) {
2142 		if (path_data->scope != scope ||
2143 		    g_strcmp0 (path_data->arch, arch) != 0) {
2144 			g_warning ("already added path %s [%s:%s] vs new [%s:%s]",
2145 				   path,
2146 				   as_app_scope_to_string (path_data->scope),
2147 				   path_data->arch,
2148 				   as_app_scope_to_string (scope),
2149 				   arch);
2150 		} else {
2151 			g_debug ("already added path %s [%s:%s]",
2152 				 path,
2153 				 as_app_scope_to_string (path_data->scope),
2154 				 path_data->arch);
2155 		}
2156 		return;
2157 	}
2158 
2159 	/* create new */
2160 	path_data = g_slice_new0 (AsStorePathData);
2161 	path_data->scope = scope;
2162 	path_data->arch = g_strdup (arch);
2163 	g_mutex_lock (&priv->mutex);
2164 	g_hash_table_insert (priv->appinfo_dirs, g_strdup (path), path_data);
2165 	g_mutex_unlock (&priv->mutex);
2166 }
2167 
2168 static gboolean
as_store_from_file_internal(AsStore * store,GFile * file,AsAppScope scope,const gchar * arch,guint32 load_flags,guint32 watch_flags,GCancellable * cancellable,GError ** error)2169 as_store_from_file_internal (AsStore *store,
2170 			     GFile *file,
2171 			     AsAppScope scope,
2172 			     const gchar *arch,
2173 			     guint32 load_flags,
2174 			     guint32 watch_flags,
2175 			     GCancellable *cancellable,
2176 			     GError **error)
2177 {
2178 	AsStorePrivate *priv = GET_PRIVATE (store);
2179 	guint32 flags = AS_NODE_FROM_XML_FLAG_LITERAL_TEXT;
2180 	g_autofree gchar *filename = NULL;
2181 	g_autofree gchar *icon_prefix = NULL;
2182 	g_autoptr(GError) error_local = NULL;
2183 	g_autoptr(AsNode) root = NULL;
2184 	g_autoptr(AsProfileTask) ptask = NULL;
2185 
2186 	g_return_val_if_fail (AS_IS_STORE (store), FALSE);
2187 
2188 	/* profile */
2189 	filename = g_file_get_path (file);
2190 	ptask = as_profile_start (priv->profile,
2191 				  "AsStore:store-from-file{%s}",
2192 				  filename);
2193 	g_assert (ptask != NULL);
2194 
2195 	/* a DEP-11 file */
2196 	if (g_strstr_len (filename, -1, ".yml") != NULL) {
2197 		return as_store_load_yaml_file (store,
2198 						file,
2199 						scope,
2200 						cancellable,
2201 						error);
2202 	}
2203 
2204 	/* a cab archive */
2205 	if (g_str_has_suffix (filename, ".cab"))
2206 		return as_store_cab_from_file (store, file, cancellable, error);
2207 
2208 	/* an AppStream XML file */
2209 	if (priv->add_flags & AS_STORE_ADD_FLAG_ONLY_NATIVE_LANGS)
2210 		flags |= AS_NODE_FROM_XML_FLAG_ONLY_NATIVE_LANGS;
2211 	root = as_node_from_file (file, flags, cancellable, &error_local);
2212 	if (root == NULL && g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
2213 		g_propagate_error (error, g_steal_pointer (&error_local));
2214 		return FALSE;
2215 	} else if (root == NULL) {
2216 		g_set_error (error,
2217 			     AS_STORE_ERROR,
2218 			     AS_STORE_ERROR_FAILED,
2219 			     "Failed to parse %s file: %s",
2220 			     filename, error_local->message);
2221 		return FALSE;
2222 	}
2223 
2224 	/* watch for file changes */
2225 	if (watch_flags > 0) {
2226 		as_store_add_path_data (store, filename, scope, arch);
2227 		if (!as_monitor_add_file (priv->monitor,
2228 					  filename,
2229 					  cancellable,
2230 					  error))
2231 			return FALSE;
2232 	}
2233 
2234 	/* icon prefix is the directory the XML has been found in */
2235 	icon_prefix = g_path_get_dirname (filename);
2236 	return as_store_from_root (store, root, scope,
2237 				   icon_prefix, filename, arch, load_flags,
2238 				   error);
2239 }
2240 
2241 /**
2242  * as_store_from_file:
2243  * @store: a #AsStore instance.
2244  * @file: a #GFile.
2245  * @icon_root: (nullable): the icon path, or %NULL for the default (unused)
2246  * @cancellable: a #GCancellable.
2247  * @error: A #GError or %NULL.
2248  *
2249  * Parses an AppStream XML or DEP-11 YAML file and adds any valid applications
2250  * to the store.
2251  *
2252  * If the root node does not have a 'origin' attribute, then the method
2253  * as_store_set_origin() should be called *before* this function if cached
2254  * icons are required.
2255  *
2256  * If @file does not exist, %G_IO_ERROR_NOT_FOUND will be returned. Other
2257  * #GIOErrors and #AsStoreErrors may be returned as appropriate.
2258  *
2259  * Returns: %TRUE for success
2260  *
2261  * Since: 0.1.0
2262  **/
2263 gboolean
as_store_from_file(AsStore * store,GFile * file,const gchar * icon_root,GCancellable * cancellable,GError ** error)2264 as_store_from_file (AsStore *store,
2265 		    GFile *file,
2266 		    const gchar *icon_root, /* unused */
2267 		    GCancellable *cancellable,
2268 		    GError **error)
2269 {
2270 	AsStorePrivate *priv = GET_PRIVATE (store);
2271 
2272 	g_return_val_if_fail (AS_IS_STORE (store), FALSE);
2273 
2274 	return as_store_from_file_internal (store, file,
2275 					    AS_APP_SCOPE_UNKNOWN,
2276 					    NULL, /* arch */
2277 					    AS_STORE_LOAD_FLAG_NONE,
2278 					    priv->watch_flags,
2279 					    cancellable, error);
2280 }
2281 
2282 /**
2283  * as_store_from_bytes:
2284  * @store: a #AsStore instance.
2285  * @bytes: a #GBytes.
2286  * @cancellable: a #GCancellable.
2287  * @error: A #GError or %NULL.
2288  *
2289  * Parses an appstream store presented as an archive. This is typically
2290  * a .cab file containing firmware files.
2291  *
2292  * Returns: %TRUE for success
2293  *
2294  * Since: 0.5.2
2295  **/
2296 gboolean
as_store_from_bytes(AsStore * store,GBytes * bytes,GCancellable * cancellable,GError ** error)2297 as_store_from_bytes (AsStore *store,
2298 		     GBytes *bytes,
2299 		     GCancellable *cancellable,
2300 		     GError **error)
2301 {
2302 	g_autofree gchar *content_type = NULL;
2303 	gconstpointer data;
2304 	gsize size;
2305 
2306 	/* find content type */
2307 	data = g_bytes_get_data (bytes, &size);
2308 	content_type = g_content_type_guess (NULL, data, size, NULL);
2309 
2310 	/* is an AppStream file */
2311 	if (g_strcmp0 (content_type, "application/xml") == 0) {
2312 		g_autofree gchar *tmp = g_strndup (data, size);
2313 		return as_store_from_xml (store, tmp, NULL, error);
2314 	}
2315 
2316 	/* is a DEP-11 file */
2317 	if (g_strcmp0 (content_type, "text/plain") == 0 && is_dep11_data (bytes))
2318 		return as_store_load_yaml_data (store, bytes, AS_APP_SCOPE_UNKNOWN, cancellable, error);
2319 
2320 	/* is firmware */
2321 	if (g_strcmp0 (content_type, "application/vnd.ms-cab-compressed") == 0) {
2322 		return as_store_cab_from_bytes (store, bytes, cancellable, error);
2323 	}
2324 
2325 	/* not sure what to do */
2326 	g_set_error (error,
2327 		     AS_STORE_ERROR,
2328 		     AS_STORE_ERROR_FAILED,
2329 		     "cannot load store of type %s",
2330 		     content_type);
2331 	return FALSE;
2332 }
2333 
2334 /**
2335  * as_store_from_xml:
2336  * @store: a #AsStore instance.
2337  * @data: XML data
2338  * @icon_root: (nullable): the icon path, or %NULL for the default.
2339  * @error: A #GError or %NULL.
2340  *
2341  * Parses AppStream XML file and adds any valid applications to the store.
2342  *
2343  * If the root node does not have a 'origin' attribute, then the method
2344  * as_store_set_origin() should be called *before* this function if cached
2345  * icons are required.
2346  *
2347  * Returns: %TRUE for success
2348  *
2349  * Since: 0.1.1
2350  **/
2351 gboolean
as_store_from_xml(AsStore * store,const gchar * data,const gchar * icon_root,GError ** error)2352 as_store_from_xml (AsStore *store,
2353 		   const gchar *data,
2354 		   const gchar *icon_root,
2355 		   GError **error)
2356 {
2357 	AsStorePrivate *priv = GET_PRIVATE (store);
2358 	guint32 flags = AS_NODE_FROM_XML_FLAG_LITERAL_TEXT;
2359 	g_autoptr(GError) error_local = NULL;
2360 	g_autoptr(AsNode) root = NULL;
2361 
2362 	g_return_val_if_fail (AS_IS_STORE (store), FALSE);
2363 	g_return_val_if_fail (data != NULL, FALSE);
2364 
2365 	/* ignore empty file */
2366 	if (data[0] == '\0')
2367 		return TRUE;
2368 
2369 	/* load XML data */
2370 	if (priv->add_flags & AS_STORE_ADD_FLAG_ONLY_NATIVE_LANGS)
2371 		flags |= AS_NODE_FROM_XML_FLAG_ONLY_NATIVE_LANGS;
2372 	root = as_node_from_xml (data, flags, &error_local);
2373 	if (root == NULL) {
2374 		g_set_error (error,
2375 			     AS_STORE_ERROR,
2376 			     AS_STORE_ERROR_FAILED,
2377 			     "Failed to parse XML: %s",
2378 			     error_local->message);
2379 		return FALSE;
2380 	}
2381 	return as_store_from_root (store, root,
2382 				   AS_APP_SCOPE_UNKNOWN,
2383 				   icon_root,
2384 				   NULL, /* filename */
2385 				   NULL, /* arch */
2386 				   AS_STORE_LOAD_FLAG_NONE,
2387 				   error);
2388 }
2389 
2390 static gint
as_store_apps_sort_cb(gconstpointer a,gconstpointer b)2391 as_store_apps_sort_cb (gconstpointer a, gconstpointer b)
2392 {
2393 	return g_strcmp0 (as_app_get_id (AS_APP (*(AsApp **) a)),
2394 			  as_app_get_id (AS_APP (*(AsApp **) b)));
2395 }
2396 
2397 static void
as_store_check_app_for_veto(AsApp * app)2398 as_store_check_app_for_veto (AsApp *app)
2399 {
2400 	/* these categories need AppData files */
2401 	if (as_app_get_description_size (app) == 0) {
2402 		guint i;
2403 		const gchar *cats_require_appdata[] = {
2404 			"ConsoleOnly",
2405 			"DesktopSettings",
2406 			"Settings",
2407 			NULL };
2408 		for (i = 0; cats_require_appdata[i] != NULL; i++) {
2409 			if (as_app_has_category (app, cats_require_appdata[i])) {
2410 				as_app_add_veto (app, "%s requires an AppData file",
2411 						 cats_require_appdata[i]);
2412 			}
2413 		}
2414 	}
2415 }
2416 
2417 static void
as_store_check_apps_for_veto(AsStore * store)2418 as_store_check_apps_for_veto (AsStore *store)
2419 {
2420 	guint i;
2421 	AsApp *app;
2422 	AsStorePrivate *priv = GET_PRIVATE (store);
2423 	g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex);
2424 
2425 	/* add any vetos */
2426 	for (i = 0; i < priv->array->len; i++) {
2427 		app = g_ptr_array_index (priv->array, i);
2428 		as_store_check_app_for_veto (app);
2429 	}
2430 }
2431 
2432 /**
2433  * as_store_remove_apps_with_veto:
2434  * @store: a #AsStore instance.
2435  *
2436  * Removes any applications from the store if they have any vetos.
2437  *
2438  * Since: 0.5.13
2439  **/
2440 void
as_store_remove_apps_with_veto(AsStore * store)2441 as_store_remove_apps_with_veto (AsStore *store)
2442 {
2443 	_cleanup_uninhibit_ guint32 *tok = NULL;
2444 	g_autoptr(GPtrArray) apps = NULL;
2445 	g_autoptr(GPtrArray) apps_remove = NULL;
2446 
2447 	g_return_if_fail (AS_IS_STORE (store));
2448 
2449 	/* don't shortcut the list as we have to use as_store_remove_app()
2450 	 * rather than just removing from the GPtrArray */
2451 	tok = as_store_changed_inhibit (store);
2452 	apps = as_store_dup_apps (store);
2453 	apps_remove = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
2454 	for (guint i = 0; i < apps->len; i++) {
2455 		AsApp *app = g_ptr_array_index (apps, i);
2456 		if (as_app_get_vetos (app)->len > 0)
2457 			g_ptr_array_add (apps_remove, g_object_ref (app));
2458 	}
2459 	for (guint i = 0; i < apps_remove->len; i++) {
2460 		AsApp *app = g_ptr_array_index (apps_remove, i);
2461 		g_debug ("removing %s as vetoed",
2462 		         as_app_get_id (app));
2463 		as_store_remove_app (store, app);
2464 	}
2465 	as_store_changed_uninhibit (&tok);
2466 	as_store_perhaps_emit_changed (store, "remove-apps-with-veto");
2467 }
2468 
2469 /**
2470  * as_store_to_xml:
2471  * @store: a #AsStore instance.
2472  * @flags: the AsNodeToXmlFlags, e.g. %AS_NODE_TO_XML_FLAG_NONE.
2473  *
2474  * Outputs an XML representation of all the applications in the store.
2475  *
2476  * Returns: A #GString
2477  *
2478  * Since: 0.1.0
2479  **/
2480 GString *
as_store_to_xml(AsStore * store,guint32 flags)2481 as_store_to_xml (AsStore *store, guint32 flags)
2482 {
2483 	AsApp *app;
2484 	AsStorePrivate *priv = GET_PRIVATE (store);
2485 	AsNode *node_apps;
2486 	AsNode *node_root;
2487 	GString *xml;
2488 	gboolean output_trusted = FALSE;
2489 	guint i;
2490 	gchar version[6];
2491 	g_autoptr(AsNodeContext) ctx = NULL;
2492 
2493 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
2494 
2495 	/* check categories of apps about to be written */
2496 	as_store_check_apps_for_veto (store);
2497 
2498 	/* get XML text */
2499 	node_root = as_node_new ();
2500 	node_apps = as_node_insert (node_root, "components", NULL, 0, NULL);
2501 
2502 	/* set origin attribute */
2503 	if (priv->origin != NULL)
2504 		as_node_add_attribute (node_apps, "origin", priv->origin);
2505 
2506 	/* set origin attribute */
2507 	if (priv->builder_id != NULL)
2508 		as_node_add_attribute (node_apps, "builder_id", priv->builder_id);
2509 
2510 	/* set version attribute */
2511 	if (priv->api_version > 0.1f) {
2512 		g_ascii_formatd (version, sizeof (version),
2513 				 "%.1f", priv->api_version);
2514 		as_node_add_attribute (node_apps, "version", version);
2515 	}
2516 
2517 	/* output is trusted, so include update_contact */
2518 	if (g_getenv ("APPSTREAM_GLIB_OUTPUT_TRUSTED") != NULL)
2519 		output_trusted = TRUE;
2520 
2521 	ctx = as_node_context_new ();
2522 	as_node_context_set_version (ctx, priv->api_version);
2523 	as_node_context_set_output (ctx, AS_FORMAT_KIND_APPSTREAM);
2524 	as_node_context_set_output_trusted (ctx, output_trusted);
2525 
2526 	g_mutex_lock (&priv->mutex);
2527 
2528 	/* sort by ID */
2529 	g_ptr_array_sort (priv->array, as_store_apps_sort_cb);
2530 
2531 	/* add applications */
2532 	for (i = 0; i < priv->array->len; i++) {
2533 		app = g_ptr_array_index (priv->array, i);
2534 		as_app_node_insert (app, node_apps, ctx);
2535 	}
2536 
2537 	g_mutex_unlock (&priv->mutex);
2538 
2539 	xml = as_node_to_xml (node_root, flags);
2540 	as_node_unref (node_root);
2541 	return xml;
2542 }
2543 
2544 /**
2545  * as_store_convert_icons:
2546  * @store: a #AsStore instance.
2547  * @kind: the AsIconKind, e.g. %AS_ICON_KIND_EMBEDDED.
2548  * @error: A #GError or %NULL
2549  *
2550  * Converts all the icons in the store to a specific kind.
2551  *
2552  * Returns: %TRUE for success
2553  *
2554  * Since: 0.3.1
2555  **/
2556 gboolean
as_store_convert_icons(AsStore * store,AsIconKind kind,GError ** error)2557 as_store_convert_icons (AsStore *store, AsIconKind kind, GError **error)
2558 {
2559 	AsStorePrivate *priv = GET_PRIVATE (store);
2560 	AsApp *app;
2561 	guint i;
2562 	g_autoptr(GMutexLocker) locker = NULL;
2563 
2564 	g_return_val_if_fail (AS_IS_STORE (store), FALSE);
2565 
2566 	locker = g_mutex_locker_new (&priv->mutex);
2567 
2568 	/* convert application icons */
2569 	for (i = 0; i < priv->array->len; i++) {
2570 		app = g_ptr_array_index (priv->array, i);
2571 		if (!as_app_convert_icons (app, kind, error))
2572 			return FALSE;
2573 	}
2574 	return TRUE;
2575 }
2576 
2577 /**
2578  * as_store_to_file:
2579  * @store: a #AsStore instance.
2580  * @file: file
2581  * @flags: the AsNodeToXmlFlags, e.g. %AS_NODE_TO_XML_FLAG_NONE.
2582  * @cancellable: A #GCancellable, or %NULL
2583  * @error: A #GError or %NULL
2584  *
2585  * Outputs an optionally compressed XML file of all the applications in the store.
2586  *
2587  * Returns: A #GString
2588  *
2589  * Since: 0.1.0
2590  **/
2591 gboolean
as_store_to_file(AsStore * store,GFile * file,guint32 flags,GCancellable * cancellable,GError ** error)2592 as_store_to_file (AsStore *store,
2593 		  GFile *file,
2594 		  guint32 flags,
2595 		  GCancellable *cancellable,
2596 		  GError **error)
2597 {
2598 	g_autoptr(GError) error_local = NULL;
2599 	g_autoptr(GOutputStream) out2 = NULL;
2600 	g_autoptr(GOutputStream) out = NULL;
2601 	g_autoptr(GZlibCompressor) compressor = NULL;
2602 	g_autoptr(GString) xml = NULL;
2603 	g_autofree gchar *basename = NULL;
2604 
2605 	/* check if compressed */
2606 	basename = g_file_get_basename (file);
2607 	if (g_strstr_len (basename, -1, ".gz") == NULL) {
2608 		xml = as_store_to_xml (store, flags);
2609 		if (!g_file_replace_contents (file, xml->str, xml->len,
2610 					      NULL,
2611 					      FALSE,
2612 					      G_FILE_CREATE_REPLACE_DESTINATION,
2613 					      NULL,
2614 					      cancellable,
2615 					      &error_local)) {
2616 			g_set_error (error,
2617 				     AS_STORE_ERROR,
2618 				     AS_STORE_ERROR_FAILED,
2619 				     "Failed to write file: %s",
2620 				     error_local->message);
2621 			return FALSE;
2622 		}
2623 		return TRUE;
2624 	}
2625 
2626 	/* compress as a gzip file */
2627 	compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
2628 	out = g_memory_output_stream_new_resizable ();
2629 	out2 = g_converter_output_stream_new (out, G_CONVERTER (compressor));
2630 	xml = as_store_to_xml (store, flags);
2631 	if (!g_output_stream_write_all (out2, xml->str, xml->len,
2632 					NULL, NULL, &error_local)) {
2633 		g_set_error (error,
2634 			     AS_STORE_ERROR,
2635 			     AS_STORE_ERROR_FAILED,
2636 			     "Failed to write stream: %s",
2637 			     error_local->message);
2638 		return FALSE;
2639 	}
2640 	if (!g_output_stream_close (out2, NULL, &error_local)) {
2641 		g_set_error (error,
2642 			     AS_STORE_ERROR,
2643 			     AS_STORE_ERROR_FAILED,
2644 			     "Failed to close stream: %s",
2645 			     error_local->message);
2646 		return FALSE;
2647 	}
2648 
2649 	/* write file */
2650 	if (!g_file_replace_contents (file,
2651 		g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (out)),
2652 		g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (out)),
2653 				      NULL,
2654 				      FALSE,
2655 				      G_FILE_CREATE_NONE,
2656 				      NULL,
2657 				      cancellable,
2658 				      &error_local)) {
2659 		g_set_error (error,
2660 			     AS_STORE_ERROR,
2661 			     AS_STORE_ERROR_FAILED,
2662 			     "Failed to write file: %s",
2663 			     error_local->message);
2664 		return FALSE;
2665 	}
2666 	return TRUE;
2667 }
2668 
2669 /**
2670  * as_store_get_origin:
2671  * @store: a #AsStore instance.
2672  *
2673  * Gets the metadata origin, which is used to locate icons.
2674  *
2675  * Returns: the origin string, or %NULL if unset
2676  *
2677  * Since: 0.1.1
2678  **/
2679 const gchar *
as_store_get_origin(AsStore * store)2680 as_store_get_origin (AsStore *store)
2681 {
2682 	AsStorePrivate *priv = GET_PRIVATE (store);
2683 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
2684 	return priv->origin;
2685 }
2686 
2687 /**
2688  * as_store_set_origin:
2689  * @store: a #AsStore instance.
2690  * @origin: the origin, e.g. "fedora-21"
2691  *
2692  * Sets the metadata origin, which is used to locate icons.
2693  *
2694  * Since: 0.1.1
2695  **/
2696 void
as_store_set_origin(AsStore * store,const gchar * origin)2697 as_store_set_origin (AsStore *store, const gchar *origin)
2698 {
2699 	AsStorePrivate *priv = GET_PRIVATE (store);
2700 	g_return_if_fail (AS_IS_STORE (store));
2701 	g_free (priv->origin);
2702 	priv->origin = g_strdup (origin);
2703 }
2704 
2705 /**
2706  * as_store_get_builder_id:
2707  * @store: a #AsStore instance.
2708  *
2709  * Gets the metadata builder identifier, which is used to work out if old
2710  * metadata is compatible with this builder.
2711  *
2712  * Returns: the builder_id string, or %NULL if unset
2713  *
2714  * Since: 0.2.5
2715  **/
2716 const gchar *
as_store_get_builder_id(AsStore * store)2717 as_store_get_builder_id (AsStore *store)
2718 {
2719 	AsStorePrivate *priv = GET_PRIVATE (store);
2720 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
2721 	return priv->builder_id;
2722 }
2723 
2724 /**
2725  * as_store_set_builder_id:
2726  * @store: a #AsStore instance.
2727  * @builder_id: the builder_id, e.g. "appstream-glib:1"
2728  *
2729  * Sets the metadata builder identifier, which is used to work out if old
2730  * metadata can be used.
2731  *
2732  * Since: 0.2.5
2733  **/
2734 void
as_store_set_builder_id(AsStore * store,const gchar * builder_id)2735 as_store_set_builder_id (AsStore *store, const gchar *builder_id)
2736 {
2737 	AsStorePrivate *priv = GET_PRIVATE (store);
2738 	g_return_if_fail (AS_IS_STORE (store));
2739 	g_free (priv->builder_id);
2740 	priv->builder_id = g_strdup (builder_id);
2741 }
2742 
2743 /**
2744  * as_store_set_destdir:
2745  * @store: a #AsStore instance.
2746  * @destdir: the destdir, e.g. "/tmp"
2747  *
2748  * Sets the destdir, which is used to prefix usr.
2749  *
2750  * Since: 0.2.4
2751  **/
2752 void
as_store_set_destdir(AsStore * store,const gchar * destdir)2753 as_store_set_destdir (AsStore *store, const gchar *destdir)
2754 {
2755 	AsStorePrivate *priv = GET_PRIVATE (store);
2756 	g_return_if_fail (AS_IS_STORE (store));
2757 	g_free (priv->destdir);
2758 	priv->destdir = g_strdup (destdir);
2759 }
2760 
2761 /**
2762  * as_store_get_destdir:
2763  * @store: a #AsStore instance.
2764  *
2765  * Gets the destdir, which is used to prefix usr.
2766  *
2767  * Returns: the destdir path, or %NULL if unset
2768  *
2769  * Since: 0.2.4
2770  **/
2771 const gchar *
as_store_get_destdir(AsStore * store)2772 as_store_get_destdir (AsStore *store)
2773 {
2774 	AsStorePrivate *priv = GET_PRIVATE (store);
2775 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
2776 	return priv->destdir;
2777 }
2778 
2779 /**
2780  * as_store_get_api_version:
2781  * @store: a #AsStore instance.
2782  *
2783  * Gets the AppStream API version.
2784  *
2785  * Returns: the #AsNodeInsertFlags, or 0 if unset
2786  *
2787  * Since: 0.1.1
2788  **/
2789 gdouble
as_store_get_api_version(AsStore * store)2790 as_store_get_api_version (AsStore *store)
2791 {
2792 	AsStorePrivate *priv = GET_PRIVATE (store);
2793 	g_return_val_if_fail (AS_IS_STORE (store), 0.0);
2794 	return priv->api_version;
2795 }
2796 
2797 /**
2798  * as_store_set_api_version:
2799  * @store: a #AsStore instance.
2800  * @api_version: the API version
2801  *
2802  * Sets the AppStream API version.
2803  *
2804  * Since: 0.1.1
2805  **/
2806 void
as_store_set_api_version(AsStore * store,gdouble api_version)2807 as_store_set_api_version (AsStore *store, gdouble api_version)
2808 {
2809 	AsStorePrivate *priv = GET_PRIVATE (store);
2810 	g_return_if_fail (AS_IS_STORE (store));
2811 	priv->api_version = api_version;
2812 }
2813 
2814 /**
2815  * as_store_get_add_flags:
2816  * @store: a #AsStore instance.
2817  *
2818  * Gets the flags used for adding applications to the store.
2819  *
2820  * Returns: the #AsStoreAddFlags, or 0 if unset
2821  *
2822  * Since: 0.2.2
2823  **/
2824 guint32
as_store_get_add_flags(AsStore * store)2825 as_store_get_add_flags (AsStore *store)
2826 {
2827 	AsStorePrivate *priv = GET_PRIVATE (store);
2828 	g_return_val_if_fail (AS_IS_STORE (store), 0);
2829 	return priv->add_flags;
2830 }
2831 
2832 /**
2833  * as_store_set_add_flags:
2834  * @store: a #AsStore instance.
2835  * @add_flags: the #AsStoreAddFlags, e.g. %AS_STORE_ADD_FLAG_NONE
2836  *
2837  * Sets the flags used when adding applications to the store.
2838  *
2839  * NOTE: Using %AS_STORE_ADD_FLAG_PREFER_LOCAL may be a privacy risk depending on
2840  * your level of paranoia, and should not be used by default.
2841  *
2842  * Since: 0.2.2
2843  **/
2844 void
as_store_set_add_flags(AsStore * store,guint32 add_flags)2845 as_store_set_add_flags (AsStore *store, guint32 add_flags)
2846 {
2847 	AsStorePrivate *priv = GET_PRIVATE (store);
2848 	g_return_if_fail (AS_IS_STORE (store));
2849 	priv->add_flags = add_flags;
2850 }
2851 
2852 /**
2853  * as_store_get_watch_flags:
2854  * @store: a #AsStore instance.
2855  *
2856  * Gets the flags used for adding files to the store.
2857  *
2858  * Returns: the #AsStoreWatchFlags, or 0 if unset
2859  *
2860  * Since: 0.4.2
2861  **/
2862 guint32
as_store_get_watch_flags(AsStore * store)2863 as_store_get_watch_flags (AsStore *store)
2864 {
2865 	AsStorePrivate *priv = GET_PRIVATE (store);
2866 	g_return_val_if_fail (AS_IS_STORE (store), AS_STORE_WATCH_FLAG_NONE);
2867 	return priv->watch_flags;
2868 }
2869 
2870 /**
2871  * as_store_set_watch_flags:
2872  * @store: a #AsStore instance.
2873  * @watch_flags: the #AsStoreWatchFlags, e.g. %AS_STORE_WATCH_FLAG_NONE
2874  *
2875  * Sets the flags used when adding files to the store.
2876  *
2877  * Since: 0.4.2
2878  **/
2879 void
as_store_set_watch_flags(AsStore * store,guint32 watch_flags)2880 as_store_set_watch_flags (AsStore *store, guint32 watch_flags)
2881 {
2882 	AsStorePrivate *priv = GET_PRIVATE (store);
2883 	g_return_if_fail (AS_IS_STORE (store));
2884 	priv->watch_flags = watch_flags;
2885 }
2886 
2887 static gboolean
as_store_guess_origin_fallback(AsStore * store,const gchar * filename,GError ** error)2888 as_store_guess_origin_fallback (AsStore *store,
2889 				const gchar *filename,
2890 				GError **error)
2891 {
2892 	gchar *tmp;
2893 	g_autofree gchar *origin_fallback = NULL;
2894 
2895 	/* the first component of the file (e.g. "fedora-20.xml.gz)
2896 	 * is used for the icon directory as we might want to clean up
2897 	 * the icons manually if they are installed in /var/cache */
2898 	origin_fallback = g_path_get_basename (filename);
2899 	tmp = g_strstr_len (origin_fallback, -1, ".xml");
2900 	if (tmp == NULL)
2901 		tmp = g_strstr_len (origin_fallback, -1, ".yml");
2902 	if (tmp == NULL) {
2903 		g_set_error (error,
2904 			     AS_STORE_ERROR,
2905 			     AS_STORE_ERROR_FAILED,
2906 			     "AppStream metadata name %s not valid, "
2907 			     "expected .xml[.*] or .yml[.*]",
2908 			     filename);
2909 		return FALSE;
2910 	}
2911 	tmp[0] = '\0';
2912 
2913 	/* load this specific file */
2914 	as_store_set_origin (store, origin_fallback);
2915 	return TRUE;
2916 }
2917 
2918 static gboolean
as_store_load_app_info_file(AsStore * store,AsAppScope scope,const gchar * path_xml,const gchar * arch,guint32 flags,GCancellable * cancellable,GError ** error)2919 as_store_load_app_info_file (AsStore *store,
2920 			     AsAppScope scope,
2921 			     const gchar *path_xml,
2922 			     const gchar *arch,
2923 			     guint32 flags,
2924 			     GCancellable *cancellable,
2925 			     GError **error)
2926 {
2927 	g_autoptr(GFile) file = NULL;
2928 
2929 	/* ignore large compressed files */
2930 	if (flags & AS_STORE_LOAD_FLAG_ONLY_UNCOMPRESSED &&
2931 	    g_str_has_suffix (path_xml, ".gz")) {
2932 		g_debug ("ignoring compressed file %s", path_xml);
2933 		return TRUE;
2934 	}
2935 
2936 	/* guess this based on the name */
2937 	if (!as_store_guess_origin_fallback (store, path_xml, error))
2938 		return FALSE;
2939 
2940 	/* load without adding monitor */
2941 	file = g_file_new_for_path (path_xml);
2942 	return as_store_from_file_internal (store,
2943 					    file,
2944 					    scope,
2945 					    arch,
2946 					    flags,
2947 					    AS_STORE_WATCH_FLAG_NONE,
2948 					    cancellable,
2949 					    error);
2950 }
2951 
2952 static gboolean
as_store_load_app_info(AsStore * store,AsAppScope scope,const gchar * path,const gchar * arch,guint32 flags,GCancellable * cancellable,GError ** error)2953 as_store_load_app_info (AsStore *store,
2954 			AsAppScope scope,
2955 			const gchar *path,
2956 			const gchar *arch,
2957 			guint32 flags,
2958 			GCancellable *cancellable,
2959 			GError **error)
2960 {
2961 	AsStorePrivate *priv = GET_PRIVATE (store);
2962 	_cleanup_uninhibit_ guint32 *tok = NULL;
2963 
2964 	/* Don't add the same dir twice, we're monitoring it for changes anyway */
2965 	g_mutex_lock (&priv->mutex);
2966 	if (g_hash_table_contains (priv->appinfo_dirs, path)) {
2967 		g_mutex_unlock (&priv->mutex);
2968 		return TRUE;
2969 	}
2970 	g_mutex_unlock (&priv->mutex);
2971 
2972 	/* emit once when finished */
2973 	tok = as_store_changed_inhibit (store);
2974 
2975 	/* search all files, if the location already exists */
2976 	if (g_file_test (path, G_FILE_TEST_IS_DIR)) {
2977 		const gchar *tmp;
2978 		g_autoptr(GDir) dir = NULL;
2979 		g_autoptr(GError) error_local = NULL;
2980 		dir = g_dir_open (path, 0, &error_local);
2981 		if (dir == NULL) {
2982 			if (flags & AS_STORE_LOAD_FLAG_IGNORE_INVALID) {
2983 				g_warning ("ignoring invalid AppStream path %s: %s",
2984 					   path, error_local->message);
2985 				return TRUE;
2986 			}
2987 			g_set_error (error,
2988 				     AS_STORE_ERROR,
2989 				     AS_STORE_ERROR_FAILED,
2990 				     "Failed to open %s: %s",
2991 				     path, error_local->message);
2992 			return FALSE;
2993 		}
2994 
2995 		while ((tmp = g_dir_read_name (dir)) != NULL) {
2996 			GError *error_store = NULL;
2997 			g_autofree gchar *filename_md = NULL;
2998 			if (g_strcmp0 (tmp, "icons") == 0)
2999 				continue;
3000 			filename_md = g_build_filename (path, tmp, NULL);
3001 			if (!as_store_load_app_info_file (store,
3002 							  scope,
3003 							  filename_md,
3004 							  arch,
3005 							  flags,
3006 							  cancellable,
3007 							  &error_store)) {
3008 				if (flags & AS_STORE_LOAD_FLAG_IGNORE_INVALID) {
3009 					g_warning ("Ignoring invalid AppStream file %s: %s",
3010 						   filename_md, error_store->message);
3011 					g_clear_error (&error_store);
3012 				} else {
3013 					g_propagate_error (error, error_store);
3014 					return FALSE;
3015 				}
3016 			}
3017 		}
3018 	}
3019 
3020 	/* watch the directories for changes, even if it does not exist yet */
3021 	as_store_add_path_data (store, path, scope, arch);
3022 	if (!as_monitor_add_directory (priv->monitor,
3023 				       path,
3024 				       cancellable,
3025 				       error))
3026 		return FALSE;
3027 
3028 	/* emit changed */
3029 	as_store_changed_uninhibit (&tok);
3030 	as_store_perhaps_emit_changed (store, "load-app-info");
3031 
3032 	return TRUE;
3033 }
3034 
3035 static void
as_store_set_app_installed(AsApp * app)3036 as_store_set_app_installed (AsApp *app)
3037 {
3038 	GPtrArray *releases = as_app_get_releases (app);
3039 	for (guint i = 0; i < releases->len; i++) {
3040 		AsRelease *rel = g_ptr_array_index (releases, i);
3041 		as_release_set_state (rel, AS_RELEASE_STATE_INSTALLED);
3042 	}
3043 }
3044 
3045 static gboolean
as_store_load_installed_file_is_valid(const gchar * filename)3046 as_store_load_installed_file_is_valid (const gchar *filename)
3047 {
3048 	if (g_str_has_suffix (filename, ".desktop"))
3049 		return TRUE;
3050 	if (g_str_has_suffix (filename, ".metainfo.xml"))
3051 		return TRUE;
3052 	if (g_str_has_suffix (filename, ".appdata.xml"))
3053 		return TRUE;
3054 	g_debug ("ignoring filename with invalid suffix: %s", filename);
3055 	return FALSE;
3056 }
3057 
3058 static gboolean
as_store_load_installed(AsStore * store,guint32 flags,AsAppScope scope,const gchar * path,GCancellable * cancellable,GError ** error)3059 as_store_load_installed (AsStore *store,
3060 			 guint32 flags,
3061 			 AsAppScope scope,
3062 			 const gchar *path,
3063 			 GCancellable *cancellable,
3064 			 GError **error)
3065 {
3066 	guint32 parse_flags = AS_APP_PARSE_FLAG_USE_HEURISTICS;
3067 	AsStorePrivate *priv = GET_PRIVATE (store);
3068 	GError *error_local = NULL;
3069 	const gchar *tmp;
3070 	g_autoptr(GDir) dir = NULL;
3071 	_cleanup_uninhibit_ guint32 *tok = NULL;
3072 	g_autoptr(AsProfileTask) ptask = NULL;
3073 
3074 	/* profile */
3075 	ptask = as_profile_start (priv->profile, "AsStore:load-installed{%s}", path);
3076 	g_assert (ptask != NULL);
3077 
3078 	dir = g_dir_open (path, 0, error);
3079 	if (dir == NULL)
3080 		return FALSE;
3081 
3082 	/* watch the directories for changes */
3083 	as_store_add_path_data (store, path, scope, NULL);
3084 	if (!as_monitor_add_directory (priv->monitor,
3085 				       path,
3086 				       cancellable,
3087 				       error))
3088 		return FALSE;
3089 
3090 	/* emit once when finished */
3091 	tok = as_store_changed_inhibit (store);
3092 
3093 	/* always load all the .desktop 'X-' metadata */
3094 	parse_flags |= AS_APP_PARSE_FLAG_ADD_ALL_METADATA;
3095 
3096 	/* relax the checks when parsing */
3097 	if (flags & AS_STORE_LOAD_FLAG_ALLOW_VETO)
3098 		parse_flags |= AS_APP_PARSE_FLAG_ALLOW_VETO;
3099 
3100 	/* propagate flag */
3101 	if (priv->add_flags & AS_STORE_ADD_FLAG_ONLY_NATIVE_LANGS)
3102 		parse_flags |= AS_APP_PARSE_FLAG_ONLY_NATIVE_LANGS;
3103 
3104 	while ((tmp = g_dir_read_name (dir)) != NULL) {
3105 		AsApp *app_tmp;
3106 		GPtrArray *icons;
3107 		guint i;
3108 		g_autofree gchar *filename = NULL;
3109 		g_autoptr(AsApp) app = NULL;
3110 		filename = g_build_filename (path, tmp, NULL);
3111 		if (!as_store_load_installed_file_is_valid (filename))
3112 			continue;
3113 		if ((priv->add_flags & AS_STORE_ADD_FLAG_PREFER_LOCAL) == 0) {
3114 			app_tmp = as_store_get_app_by_id (store, tmp);
3115 			if (app_tmp != NULL &&
3116 			    as_app_get_format_by_kind (app_tmp, AS_FORMAT_KIND_DESKTOP) != NULL) {
3117 				as_app_set_state (app_tmp, AS_APP_STATE_INSTALLED);
3118 				g_debug ("not parsing %s as %s already exists",
3119 					 filename, tmp);
3120 				continue;
3121 			}
3122 		}
3123 		app = as_app_new ();
3124 		as_app_set_scope (app, scope);
3125 		if (!as_app_parse_file (app, filename, parse_flags, &error_local)) {
3126 			if (g_error_matches (error_local,
3127 					     AS_APP_ERROR,
3128 					     AS_APP_ERROR_INVALID_TYPE)) {
3129 				g_debug ("Ignoring %s: %s", filename,
3130 					 error_local->message);
3131 				g_clear_error (&error_local);
3132 				continue;
3133 			}
3134 			g_propagate_error (error, error_local);
3135 			return FALSE;
3136 		}
3137 
3138 		/* convert any UNKNOWN icons to LOCAL */
3139 		icons = as_app_get_icons (app);
3140 		for (i = 0; i < icons->len; i++) {
3141 			AsIcon *icon = g_ptr_array_index (icons, i);
3142 			if (as_icon_get_kind (icon) == AS_ICON_KIND_UNKNOWN)
3143 				as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
3144 		}
3145 
3146 		/* set the ID prefix */
3147 		if ((priv->add_flags & AS_STORE_ADD_FLAG_USE_UNIQUE_ID) == 0)
3148 			as_store_fixup_id_prefix (app, as_app_scope_to_string (scope));
3149 
3150 		/* do not load applications with vetos */
3151 		if ((flags & AS_STORE_LOAD_FLAG_ALLOW_VETO) == 0 &&
3152 		    as_app_get_vetos(app)->len > 0)
3153 			continue;
3154 
3155 		/* as these are added from installed AppData files then all the
3156 		 * releases can also be marked as installed */
3157 		as_store_set_app_installed (app);
3158 
3159 		/* set lower priority than AppStream entries */
3160 		as_app_set_priority (app, -1);
3161 		as_store_add_app (store, app);
3162 	}
3163 
3164 	/* emit changed */
3165 	as_store_changed_uninhibit (&tok);
3166 	as_store_perhaps_emit_changed (store, "load-installed");
3167 
3168 	return TRUE;
3169 }
3170 
3171 /**
3172  * as_store_load_path:
3173  * @store: a #AsStore instance.
3174  * @path: A path to load
3175  * @cancellable: a #GCancellable.
3176  * @error: A #GError or %NULL.
3177  *
3178  * Loads the store from a specific path.
3179  *
3180  * Returns: %TRUE for success
3181  *
3182  * Since: 0.2.2
3183  **/
3184 gboolean
as_store_load_path(AsStore * store,const gchar * path,GCancellable * cancellable,GError ** error)3185 as_store_load_path (AsStore *store, const gchar *path,
3186 		    GCancellable *cancellable, GError **error)
3187 {
3188 	return as_store_load_installed (store, AS_STORE_LOAD_FLAG_NONE,
3189 					AS_APP_SCOPE_UNKNOWN,
3190 					path, cancellable, error);
3191 }
3192 
3193 static void
store_load_path_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)3194 store_load_path_thread (GTask *task,
3195 			gpointer source_object,
3196 			gpointer task_data,
3197 			GCancellable *cancellable)
3198 {
3199 	AsStore *store = source_object;
3200 	const char *path = task_data;
3201 	GError *error = NULL;
3202 	gboolean success;
3203 
3204 	success = as_store_load_path (store, path, cancellable, &error);
3205 	if (error)
3206 		g_task_return_error (task, error);
3207 	else
3208 		g_task_return_boolean (task, success);
3209 }
3210 
3211 
3212 /**
3213  * as_store_load_path_async:
3214  * @store: a #AsStore instance.
3215  * @path: A path to load
3216  * @cancellable: a #GCancellable.
3217  * @callback: A #GAsyncReadyCallback
3218  * @user_data: Data to pass to @callback
3219  *
3220  * Asynchronously loads the store from a specific path.
3221  *
3222  * Since: 0.7.11
3223  **/
3224 void
as_store_load_path_async(AsStore * store,const gchar * path,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3225 as_store_load_path_async (AsStore *store, const gchar *path,
3226 			  GCancellable *cancellable,
3227 			  GAsyncReadyCallback callback,
3228 			  gpointer user_data)
3229 {
3230 	GTask *task = g_task_new (store, cancellable, callback, user_data);
3231 	g_task_set_task_data (task, g_strdup (path), g_free);
3232 	g_task_run_in_thread (task, store_load_path_thread);
3233 	g_object_unref (task);
3234 }
3235 
3236 /**
3237  * as_store_load_path_finish:
3238  * @store: a #AsStore instance.
3239  * @result: A #GAsyncResult
3240  * @error: A #GError or %NULL.
3241  *
3242  * Retrieve the result of as_store_load_path_async().
3243  *
3244  * Returns: %TRUE for success
3245  *
3246  * Since: 0.7.11
3247  **/
3248 gboolean
as_store_load_path_finish(AsStore * store,GAsyncResult * result,GError ** error)3249 as_store_load_path_finish (AsStore *store,
3250 			   GAsyncResult *result,
3251 			   GError **error)
3252 {
3253 	g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
3254 	return g_task_propagate_boolean (G_TASK (result), error);
3255 }
3256 
3257 static gboolean
as_store_search_installed(AsStore * store,guint32 flags,AsAppScope scope,const gchar * path,GCancellable * cancellable,GError ** error)3258 as_store_search_installed (AsStore *store,
3259 			   guint32 flags,
3260 			   AsAppScope scope,
3261 			   const gchar *path,
3262 			   GCancellable *cancellable,
3263 			   GError **error)
3264 {
3265 	AsStorePrivate *priv = GET_PRIVATE (store);
3266 	g_autofree gchar *dest = NULL;
3267 	dest = g_build_filename (priv->destdir ? priv->destdir : "/", path, NULL);
3268 	g_debug ("searching path %s", dest);
3269 	if (!g_file_test (dest, G_FILE_TEST_EXISTS))
3270 		return TRUE;
3271 	return as_store_load_installed (store, flags, scope,
3272 					dest, cancellable, error);
3273 }
3274 
3275 static gboolean
as_store_search_app_info(AsStore * store,guint32 flags,AsAppScope scope,const gchar * path,GCancellable * cancellable,GError ** error)3276 as_store_search_app_info (AsStore *store,
3277 			  guint32 flags,
3278 			  AsAppScope scope,
3279 			  const gchar *path,
3280 			  GCancellable *cancellable,
3281 			  GError **error)
3282 {
3283 	AsStorePrivate *priv = GET_PRIVATE (store);
3284 	const gchar *supported_kinds[] = { "yaml", "xmls", NULL };
3285 	guint i;
3286 
3287 	for (i = 0; supported_kinds[i] != NULL; i++) {
3288 		g_autofree gchar *dest = NULL;
3289 		dest = g_build_filename (priv->destdir ? priv->destdir : "/",
3290 					 path,
3291 					 supported_kinds[i],
3292 					 NULL);
3293 		if (!as_store_load_app_info (store, scope, dest, NULL,
3294 					     flags, cancellable, error))
3295 			return FALSE;
3296 	}
3297 	return TRUE;
3298 }
3299 
3300 static gboolean
as_store_search_per_system(AsStore * store,guint32 flags,GCancellable * cancellable,GError ** error)3301 as_store_search_per_system (AsStore *store,
3302 			    guint32 flags,
3303 			    GCancellable *cancellable,
3304 			    GError **error)
3305 {
3306 	AsStorePrivate *priv = GET_PRIVATE (store);
3307 	const gchar * const * data_dirs;
3308 	guint i;
3309 	g_autoptr(AsProfileTask) ptask = NULL;
3310 
3311 	/* profile */
3312 	ptask = as_profile_start_literal (priv->profile, "AsStore:load{per-system}");
3313 	g_assert (ptask != NULL);
3314 
3315 	/* datadir AppStream, AppData and desktop */
3316 	data_dirs = g_get_system_data_dirs ();
3317 	for (i = 0; data_dirs[i] != NULL; i++) {
3318 		if (g_strstr_len (data_dirs[i], -1, "flatpak/exports") != NULL) {
3319 			g_debug ("skipping %s as invalid", data_dirs[i]);
3320 			continue;
3321 		}
3322 		if (g_str_has_prefix (data_dirs[i], "/home/")) {
3323 			g_debug ("skipping %s as invalid", data_dirs[i]);
3324 			continue;
3325 		}
3326 		if (g_strstr_len (data_dirs[i], -1, "snapd/desktop") != NULL) {
3327 			g_debug ("skippping %s as invalid", data_dirs[i]);
3328 			continue;
3329 		}
3330 		if ((flags & AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM) > 0) {
3331 			g_autofree gchar *dest = NULL;
3332 			dest = g_build_filename (data_dirs[i], "app-info", NULL);
3333 			if (!as_store_search_app_info (store, flags, AS_APP_SCOPE_SYSTEM,
3334 						       dest, cancellable, error))
3335 				return FALSE;
3336 		}
3337 		if ((flags & AS_STORE_LOAD_FLAG_APPDATA) > 0) {
3338 			g_autofree gchar *dest = NULL;
3339 			dest = g_build_filename (data_dirs[i], "appdata", NULL);
3340 			if (!as_store_search_installed (store, flags, AS_APP_SCOPE_SYSTEM,
3341 							dest, cancellable, error))
3342 				return FALSE;
3343 		}
3344 		if ((flags & AS_STORE_LOAD_FLAG_APPDATA) > 0) {
3345 			g_autofree gchar *dest = NULL;
3346 			dest = g_build_filename (data_dirs[i], "metainfo", NULL);
3347 			if (!as_store_search_installed (store, flags, AS_APP_SCOPE_SYSTEM,
3348 							dest, cancellable, error))
3349 				return FALSE;
3350 		}
3351 		if ((flags & AS_STORE_LOAD_FLAG_DESKTOP) > 0) {
3352 			g_autofree gchar *dest = NULL;
3353 			dest = g_build_filename (data_dirs[i], "applications", NULL);
3354 			if (!as_store_search_installed (store, flags, AS_APP_SCOPE_SYSTEM,
3355 							dest, cancellable, error))
3356 				return FALSE;
3357 		}
3358 	}
3359 
3360 	/* cached AppStream, AppData and desktop */
3361 	if ((flags & AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM) > 0) {
3362 		g_autofree gchar *dest1 = NULL;
3363 		g_autofree gchar *dest2 = NULL;
3364 		dest1 = g_build_filename (LOCALSTATEDIR, "lib", "app-info", NULL);
3365 		if (!as_store_search_app_info (store, flags, AS_APP_SCOPE_SYSTEM, dest1,
3366 					       cancellable, error))
3367 			return FALSE;
3368 		dest2 = g_build_filename (LOCALSTATEDIR, "cache", "app-info", NULL);
3369 		if (!as_store_search_app_info (store, flags, AS_APP_SCOPE_SYSTEM, dest2,
3370 					       cancellable, error))
3371 			return FALSE;
3372 		/* ignore the prefix; we actually want to use the
3373 		 * distro-provided data in this case. */
3374 		if (g_strcmp0 (LOCALSTATEDIR, "/var") != 0) {
3375 			g_autofree gchar *dest3 = NULL;
3376 			g_autofree gchar *dest4 = NULL;
3377 			dest3 = g_build_filename ("/var", "lib", "app-info", NULL);
3378 			if (!as_store_search_app_info (store, flags, AS_APP_SCOPE_SYSTEM,
3379 						       dest3, cancellable, error))
3380 				return FALSE;
3381 			dest4 = g_build_filename ("/var", "cache", "app-info", NULL);
3382 			if (!as_store_search_app_info (store, flags, AS_APP_SCOPE_SYSTEM,
3383 						       dest4, cancellable, error))
3384 				return FALSE;
3385 		}
3386 	}
3387 	return TRUE;
3388 }
3389 
3390 static gboolean
as_store_search_per_user(AsStore * store,guint32 flags,GCancellable * cancellable,GError ** error)3391 as_store_search_per_user (AsStore *store,
3392 			  guint32 flags,
3393 			  GCancellable *cancellable,
3394 			  GError **error)
3395 {
3396 	AsStorePrivate *priv = GET_PRIVATE (store);
3397 	g_autoptr(AsProfileTask) ptask = NULL;
3398 
3399 	/* profile */
3400 	ptask = as_profile_start_literal (priv->profile, "AsStore:load{per-user}");
3401 	g_assert (ptask != NULL);
3402 
3403 	/* AppStream */
3404 	if ((flags & AS_STORE_LOAD_FLAG_APP_INFO_USER) > 0) {
3405 		g_autofree gchar *dest = NULL;
3406 		dest = g_build_filename (g_get_user_data_dir (), "app-info", NULL);
3407 		if (!as_store_search_app_info (store, flags, AS_APP_SCOPE_USER,
3408 					       dest, cancellable, error))
3409 			return FALSE;
3410 	}
3411 
3412 	/* AppData */
3413 	if ((flags & AS_STORE_LOAD_FLAG_APPDATA) > 0) {
3414 		g_autofree gchar *dest = NULL;
3415 		dest = g_build_filename (g_get_user_data_dir (), "appdata", NULL);
3416 		if (!as_store_search_installed (store, flags, AS_APP_SCOPE_USER,
3417 						dest, cancellable, error))
3418 			return FALSE;
3419 	}
3420 
3421 	/* MetaInfo */
3422 	if ((flags & AS_STORE_LOAD_FLAG_APPDATA) > 0) {
3423 		g_autofree gchar *dest = NULL;
3424 		dest = g_build_filename (g_get_user_data_dir (), "metainfo", NULL);
3425 		if (!as_store_search_installed (store, flags, AS_APP_SCOPE_USER,
3426 						dest, cancellable, error))
3427 			return FALSE;
3428 	}
3429 
3430 	/* desktop files */
3431 	if ((flags & AS_STORE_LOAD_FLAG_DESKTOP) > 0) {
3432 		g_autofree gchar *dest = NULL;
3433 		dest = g_build_filename (g_get_user_data_dir (), "applications", NULL);
3434 		if (!as_store_search_installed (store, flags, AS_APP_SCOPE_USER,
3435 						dest, cancellable, error))
3436 			return FALSE;
3437 	}
3438 	return TRUE;
3439 }
3440 
3441 static void
as_store_load_search_cache_cb(gpointer data,gpointer user_data)3442 as_store_load_search_cache_cb (gpointer data, gpointer user_data)
3443 {
3444 	g_autoptr(AsApp) app = AS_APP (data);
3445 	as_app_search_matches (app, NULL);
3446 }
3447 
3448 /**
3449  * as_store_load_search_cache:
3450  * @store: a #AsStore instance.
3451  *
3452  * Populates the token cache for all applications in the store. This allows
3453  * all the search keywords for all applications in the store to be
3454  * pre-processed at one time in multiple threads rather than on demand.
3455  *
3456  * Note: Calling as_app_search_matches() automatically generates the search
3457  * cache for the #AsApp object if it has not already been generated.
3458  *
3459  * Since: 0.6.5
3460  **/
3461 void
as_store_load_search_cache(AsStore * store)3462 as_store_load_search_cache (AsStore *store)
3463 {
3464 	AsStorePrivate *priv = GET_PRIVATE (store);
3465 	guint i;
3466 	GThreadPool *pool;
3467 	g_autoptr(AsProfileTask) ptask = NULL;
3468 
3469 	g_return_if_fail (AS_IS_STORE (store));
3470 
3471 	/* profile */
3472 	ptask = as_profile_start_literal (priv->profile,
3473 					  "AsStore:load-token-cache");
3474 	as_profile_task_set_threaded (ptask, TRUE);
3475 
3476 	/* load the token cache for each app in multiple threads */
3477 	pool = g_thread_pool_new (as_store_load_search_cache_cb,
3478 				  store, 4, TRUE, NULL);
3479 	g_assert (pool != NULL);
3480 	g_mutex_lock (&priv->mutex);
3481 	for (i = 0; i < priv->array->len; i++) {
3482 		AsApp *app = g_ptr_array_index (priv->array, i);
3483 		g_thread_pool_push (pool, g_object_ref (app), NULL);
3484 	}
3485 	g_mutex_unlock (&priv->mutex);
3486 	g_thread_pool_free (pool, FALSE, TRUE);
3487 }
3488 
3489 /**
3490  * as_store_load:
3491  * @store: a #AsStore instance.
3492  * @flags: #AsStoreLoadFlags, e.g. %AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM
3493  * @cancellable: a #GCancellable.
3494  * @error: A #GError or %NULL.
3495  *
3496  * Loads the store from the default locations.
3497  *
3498  * Returns: %TRUE for success
3499  *
3500  * Since: 0.1.2
3501  **/
3502 gboolean
as_store_load(AsStore * store,guint32 flags,GCancellable * cancellable,GError ** error)3503 as_store_load (AsStore *store, guint32 flags, GCancellable *cancellable, GError **error)
3504 {
3505 	AsStorePrivate *priv = GET_PRIVATE (store);
3506 	g_autoptr(AsProfileTask) ptask = NULL;
3507 	_cleanup_uninhibit_ guint32 *tok = NULL;
3508 
3509 	g_return_val_if_fail (AS_IS_STORE (store), FALSE);
3510 
3511 	/* profile */
3512 	ptask = as_profile_start_literal (priv->profile, "AsStore:load");
3513 	g_assert (ptask != NULL);
3514 	tok = as_store_changed_inhibit (store);
3515 
3516 	/* per-user locations */
3517 	if (!as_store_search_per_user (store, flags, cancellable, error))
3518 		return FALSE;
3519 
3520 	/* system locations */
3521 	if (!as_store_search_per_system (store, flags, cancellable, error))
3522 		return FALSE;
3523 
3524 	/* find and remove any vetoed applications */
3525 	as_store_check_apps_for_veto (store);
3526 	if ((flags & AS_STORE_LOAD_FLAG_ALLOW_VETO) == 0)
3527 		as_store_remove_apps_with_veto (store);
3528 
3529 	/* match again, for applications extended from different roots */
3530 	as_store_match_addons (store);
3531 
3532 	/* emit changed */
3533 	as_store_changed_uninhibit (&tok);
3534 	as_store_perhaps_emit_changed (store, "store-load");
3535 	return TRUE;
3536 }
3537 
3538 static void
store_load_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)3539 store_load_thread (GTask *task,
3540 		   gpointer source_object,
3541 		   gpointer task_data,
3542 		   GCancellable *cancellable)
3543 {
3544 	AsStore *store = AS_STORE (source_object);
3545 	AsStoreLoadFlags flags = GPOINTER_TO_INT (task_data);
3546 	GError *error = NULL;
3547 	gboolean success;
3548 
3549 	success = as_store_load (store, flags, cancellable, &error);
3550 	if (error != NULL)
3551 		g_task_return_error (task, error);
3552 	else
3553 		g_task_return_boolean (task, success);
3554 }
3555 
3556 /**
3557  * as_store_load_async:
3558  * @store: a #AsStore instance.
3559  * @flags: #AsStoreLoadFlags, e.g. %AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM
3560  * @cancellable: a #GCancellable.
3561  * @callback: A #GAsyncReadyCallback
3562  * @user_data: Data to pass to @callback
3563  *
3564  * Asynchronously loads the store from the default locations.
3565  *
3566  * Since: 0.7.11
3567  **/
3568 void
as_store_load_async(AsStore * store,AsStoreLoadFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3569 as_store_load_async (AsStore *store,
3570 		     AsStoreLoadFlags flags,
3571 		     GCancellable *cancellable,
3572 		     GAsyncReadyCallback callback,
3573 		     gpointer user_data)
3574 {
3575 	GTask *task = g_task_new (store, cancellable, callback, user_data);
3576 	g_task_set_task_data (task, GINT_TO_POINTER (flags), NULL);
3577 	g_task_run_in_thread (task, store_load_thread);
3578 	g_object_unref (task);
3579 }
3580 
3581 /**
3582  * as_store_load_finish:
3583  * @store: a #AsStore instance.
3584  * @result: A #GAsyncResult
3585  * @error: A #GError or %NULL.
3586  *
3587  * Retrieve the result of as_store_load_async().
3588  *
3589  * Returns: %TRUE for success
3590  *
3591  * Since: 0.7.11
3592  **/
3593 gboolean
as_store_load_finish(AsStore * store,GAsyncResult * result,GError ** error)3594 as_store_load_finish (AsStore *store,
3595 		      GAsyncResult *result,
3596 		      GError **error)
3597 {
3598 	g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
3599 	return g_task_propagate_boolean (G_TASK (result), error);
3600 }
3601 
3602 G_GNUC_PRINTF (3, 4) static void
as_store_validate_add(GPtrArray * problems,AsProblemKind kind,const gchar * fmt,...)3603 as_store_validate_add (GPtrArray *problems, AsProblemKind kind, const gchar *fmt, ...)
3604 {
3605 	AsProblem *problem;
3606 	guint i;
3607 	va_list args;
3608 	g_autofree gchar *str = NULL;
3609 
3610 	va_start (args, fmt);
3611 	str = g_strdup_vprintf (fmt, args);
3612 	va_end (args);
3613 
3614 	/* already added */
3615 	for (i = 0; i < problems->len; i++) {
3616 		problem = g_ptr_array_index (problems, i);
3617 		if (g_strcmp0 (as_problem_get_message (problem), str) == 0)
3618 			return;
3619 	}
3620 
3621 	/* add new problem to list */
3622 	problem = as_problem_new ();
3623 	as_problem_set_kind (problem, kind);
3624 	as_problem_set_message (problem, str);
3625 	g_ptr_array_add (problems, problem);
3626 }
3627 
3628 static gchar *
as_store_get_unique_name_app_key(AsApp * app)3629 as_store_get_unique_name_app_key (AsApp *app)
3630 {
3631 	const gchar *name;
3632 	g_autofree gchar *name_lower = NULL;
3633 	name = as_app_get_name (app, NULL);
3634 	if (name == NULL)
3635 		return NULL;
3636 	name_lower = g_utf8_strdown (name, -1);
3637 	return g_strdup_printf ("<%s:%s>",
3638 				as_app_kind_to_string (as_app_get_kind (app)),
3639 				name_lower);
3640 }
3641 
3642 /**
3643  * as_store_validate:
3644  * @store: a #AsStore instance.
3645  * @flags: the #AsAppValidateFlags to use, e.g. %AS_APP_VALIDATE_FLAG_NONE
3646  * @error: A #GError or %NULL.
3647  *
3648  * Validates information in the store for data applicable to the defined
3649  * metadata version.
3650  *
3651  * Returns: (transfer container) (element-type AsProblem): A list of problems, or %NULL
3652  *
3653  * Since: 0.2.4
3654  **/
3655 GPtrArray *
as_store_validate(AsStore * store,guint32 flags,GError ** error)3656 as_store_validate (AsStore *store, guint32 flags, GError **error)
3657 {
3658 	AsStorePrivate *priv = GET_PRIVATE (store);
3659 	AsApp *app;
3660 	g_autoptr(GPtrArray) probs = NULL;
3661 	guint i;
3662 	g_autoptr(GHashTable) hash_names = NULL;
3663 	g_autoptr(GPtrArray) apps = NULL;
3664 
3665 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
3666 
3667 	probs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
3668 
3669 	/* check the root node */
3670 	if (priv->api_version < 0.6) {
3671 		if ((priv->problems & AS_STORE_PROBLEM_LEGACY_ROOT) == 0) {
3672 			as_store_validate_add (probs,
3673 					       AS_PROBLEM_KIND_TAG_INVALID,
3674 					       "metadata version is v%.1f and "
3675 					       "XML root is not <applications>",
3676 					       priv->api_version);
3677 		}
3678 	} else {
3679 		if ((priv->problems & AS_STORE_PROBLEM_LEGACY_ROOT) != 0) {
3680 			as_store_validate_add (probs,
3681 					       AS_PROBLEM_KIND_TAG_INVALID,
3682 					       "metadata version is v%.1f and "
3683 					       "XML root is not <components>",
3684 					       priv->api_version);
3685 		}
3686 		if (priv->origin == NULL) {
3687 			as_store_validate_add (probs,
3688 					       AS_PROBLEM_KIND_TAG_MISSING,
3689 					       "metadata version is v%.1f and "
3690 					       "origin attribute is missing",
3691 					       priv->api_version);
3692 		}
3693 	}
3694 
3695 	/* check there exists only one application with a specific name */
3696 	hash_names = g_hash_table_new_full (g_str_hash, g_str_equal,
3697 					    g_free, (GDestroyNotify) g_object_unref);
3698 
3699 	/* check each application */
3700 	apps = as_store_dup_apps (store);
3701 	for (i = 0; i < apps->len; i++) {
3702 		AsApp *app_tmp;
3703 		AsProblem *prob;
3704 		guint j;
3705 		g_autofree gchar *app_key = NULL;
3706 		g_autoptr(GPtrArray) probs_app = NULL;
3707 
3708 		app = g_ptr_array_index (apps, i);
3709 		if (priv->api_version < 0.3) {
3710 			if (as_app_get_source_pkgname (app) != NULL) {
3711 				as_store_validate_add (probs,
3712 						       AS_PROBLEM_KIND_TAG_INVALID,
3713 						       "metadata version is v%.1f and "
3714 						       "<source_pkgname> only introduced in v0.3",
3715 						       priv->api_version);
3716 			}
3717 			if (as_app_get_priority (app) != 0) {
3718 				as_store_validate_add (probs,
3719 						       AS_PROBLEM_KIND_TAG_INVALID,
3720 						       "metadata version is v%.1f and "
3721 						       "<priority> only introduced in v0.3",
3722 						       priv->api_version);
3723 			}
3724 		}
3725 		if (priv->api_version < 0.4) {
3726 			if (as_app_get_project_group (app) != NULL) {
3727 				as_store_validate_add (probs,
3728 						       AS_PROBLEM_KIND_TAG_INVALID,
3729 						       "metadata version is v%.1f and "
3730 						       "<project_group> only introduced in v0.4",
3731 						       priv->api_version);
3732 			}
3733 			if (as_app_get_mimetypes(app)->len > 0) {
3734 				as_store_validate_add (probs,
3735 						       AS_PROBLEM_KIND_TAG_INVALID,
3736 						       "metadata version is v%.1f and "
3737 						       "<mimetypes> only introduced in v0.4",
3738 						       priv->api_version);
3739 			}
3740 			if (as_app_get_screenshots(app)->len > 0) {
3741 				as_store_validate_add (probs,
3742 						       AS_PROBLEM_KIND_TAG_INVALID,
3743 						       "metadata version is v%.1f and "
3744 						       "<screenshots> only introduced in v0.4",
3745 						       priv->api_version);
3746 			}
3747 			if (as_app_get_compulsory_for_desktops(app)->len > 0) {
3748 				as_store_validate_add (probs,
3749 						       AS_PROBLEM_KIND_TAG_INVALID,
3750 						       "metadata version is v%.1f and "
3751 						       "<compulsory_for_desktop> only introduced in v0.4",
3752 						       priv->api_version);
3753 			}
3754 			if (g_list_length (as_app_get_languages(app)) > 0) {
3755 				as_store_validate_add (probs,
3756 						       AS_PROBLEM_KIND_TAG_INVALID,
3757 						       "metadata version is v%.1f and "
3758 						       "<languages> only introduced in v0.4",
3759 						       priv->api_version);
3760 			}
3761 		}
3762 		if (priv->api_version < 0.6) {
3763 			if ((as_app_get_problems (app) & AS_APP_PROBLEM_PREFORMATTED_DESCRIPTION) == 0) {
3764 				as_store_validate_add (probs,
3765 						       AS_PROBLEM_KIND_TAG_INVALID,
3766 						       "metadata version is v%.1f and "
3767 						       "<description> markup "
3768 						       "was introduced in v0.6",
3769 						       priv->api_version);
3770 			}
3771 			if (as_app_get_architectures(app)->len > 0) {
3772 				as_store_validate_add (probs,
3773 						       AS_PROBLEM_KIND_TAG_INVALID,
3774 						       "metadata version is v%.1f and "
3775 						       "<architectures> only introduced in v0.6",
3776 						       priv->api_version);
3777 			}
3778 			if (as_app_get_releases(app)->len > 0) {
3779 				as_store_validate_add (probs,
3780 						       AS_PROBLEM_KIND_TAG_INVALID,
3781 						       "metadata version is v%.1f and "
3782 						       "<releases> only introduced in v0.6",
3783 						       priv->api_version);
3784 			}
3785 			if (as_app_get_provides(app)->len > 0) {
3786 				as_store_validate_add (probs,
3787 						       AS_PROBLEM_KIND_TAG_INVALID,
3788 						       "metadata version is v%.1f and "
3789 						       "<provides> only introduced in v0.6",
3790 						       priv->api_version);
3791 			}
3792 		} else {
3793 			if ((as_app_get_problems (app) & AS_APP_PROBLEM_PREFORMATTED_DESCRIPTION) != 0) {
3794 				as_store_validate_add (probs,
3795 						       AS_PROBLEM_KIND_TAG_INVALID,
3796 						       "%s: metadata version is v%.1f and "
3797 						       "<description> requiring markup "
3798 						       "was introduced in v0.6",
3799 						       as_app_get_id (app),
3800 						       priv->api_version);
3801 			}
3802 		}
3803 		if (priv->api_version < 0.7) {
3804 			if (as_app_get_kind (app) == AS_APP_KIND_ADDON) {
3805 				as_store_validate_add (probs,
3806 						       AS_PROBLEM_KIND_TAG_INVALID,
3807 						       "metadata version is v%.1f and "
3808 						       "addon kinds only introduced in v0.7",
3809 						       priv->api_version);
3810 			}
3811 			if (as_app_get_developer_name (app, NULL) != NULL) {
3812 				as_store_validate_add (probs,
3813 						       AS_PROBLEM_KIND_TAG_INVALID,
3814 						       "metadata version is v%.1f and "
3815 						       "<developer_name> only introduced in v0.7",
3816 						       priv->api_version);
3817 			}
3818 			if (as_app_get_extends(app)->len > 0) {
3819 				as_store_validate_add (probs,
3820 						       AS_PROBLEM_KIND_TAG_INVALID,
3821 						       "metadata version is v%.1f and "
3822 						       "<extends> only introduced in v0.7",
3823 						       priv->api_version);
3824 			}
3825 		}
3826 
3827 		/* check for translations where there should be none */
3828 		if ((as_app_get_problems (app) & AS_APP_PROBLEM_TRANSLATED_ID) != 0) {
3829 			as_store_validate_add (probs,
3830 					       AS_PROBLEM_KIND_TAG_INVALID,
3831 					       "<id> values cannot be translated");
3832 		}
3833 		if ((as_app_get_problems (app) & AS_APP_PROBLEM_TRANSLATED_LICENSE) != 0) {
3834 			as_store_validate_add (probs,
3835 					       AS_PROBLEM_KIND_TAG_INVALID,
3836 					       "<license> values cannot be translated");
3837 		}
3838 		if ((as_app_get_problems (app) & AS_APP_PROBLEM_TRANSLATED_PROJECT_GROUP) != 0) {
3839 			as_store_validate_add (probs,
3840 					       AS_PROBLEM_KIND_TAG_INVALID,
3841 					       "<project_group> values cannot be translated");
3842 		}
3843 
3844 		/* validate each application */
3845 		if (flags & AS_APP_VALIDATE_FLAG_ALL_APPS) {
3846 			probs_app = as_app_validate (app, flags, error);
3847 			if (probs_app == NULL)
3848 				return NULL;
3849 			for (j = 0; j < probs_app->len; j++) {
3850 				prob = g_ptr_array_index (probs_app, j);
3851 				as_store_validate_add (probs,
3852 						       as_problem_get_kind (prob),
3853 						       "%s: %s",
3854 						       as_app_get_id (app),
3855 						       as_problem_get_message (prob));
3856 			}
3857 		}
3858 
3859 		/* check uniqueness */
3860 		if (as_app_get_kind (app) != AS_APP_KIND_ADDON) {
3861 			app_key = as_store_get_unique_name_app_key (app);
3862 			if (app_key != NULL) {
3863 				app_tmp = g_hash_table_lookup (hash_names, app_key);
3864 				if (app_tmp != NULL) {
3865 					as_store_validate_add (probs,
3866 							       AS_PROBLEM_KIND_DUPLICATE_DATA,
3867 							       "%s[%s] as the same name as %s[%s]: %s",
3868 							       as_app_get_id (app),
3869 							       as_app_get_pkgname_default (app),
3870 							       as_app_get_id (app_tmp),
3871 							       as_app_get_pkgname_default (app_tmp),
3872 							       app_key);
3873 				} else {
3874 					g_hash_table_insert (hash_names,
3875 							     g_strdup (app_key),
3876 							     g_object_ref (app));
3877 				}
3878 			}
3879 		}
3880 	}
3881 	return g_steal_pointer (&probs);
3882 }
3883 
3884 static void
as_store_path_data_free(AsStorePathData * path_data)3885 as_store_path_data_free (AsStorePathData *path_data)
3886 {
3887 	g_free (path_data->arch);
3888 	g_slice_free (AsStorePathData, path_data);
3889 }
3890 
3891 static void
as_store_create_search_blacklist(AsStore * store)3892 as_store_create_search_blacklist (AsStore *store)
3893 {
3894 	AsStorePrivate *priv = GET_PRIVATE (store);
3895 	guint i;
3896 	const gchar *blacklist[] = {
3897 		"and", "the", "application", "for", "you", "your",
3898 		"with", "can", "are", "from", "that", "use", "allows", "also",
3899 		"this", "other", "all", "using", "has", "some", "like", "them",
3900 		"well", "not", "using", "not", "but", "set", "its", "into",
3901 		"such", "was", "they", "where", "want", "only", "about",
3902 		"uses", "font", "features", "designed", "provides", "which",
3903 		"many", "used", "org", "fonts", "open", "more", "based",
3904 		"different", "including", "will", "multiple", "out", "have",
3905 		"each", "when", "need", "most", "both", "their", "even",
3906 		"way", "several", "been", "while", "very", "add", "under",
3907 		"what", "those", "much", "either", "currently", "one",
3908 		"support", "make", "over", "these", "there", "without", "etc",
3909 		"main",
3910 		NULL };
3911 	for (i = 0; blacklist[i] != NULL; i++)  {
3912 		g_hash_table_insert (priv->search_blacklist,
3913 				     as_stemmer_process (priv->stemmer, blacklist[i]),
3914 				     GUINT_TO_POINTER (1));
3915 	}
3916 }
3917 
3918 /**
3919  * as_store_set_search_match:
3920  * @store: a #AsStore instance.
3921  * @search_match: the #AsAppSearchMatch, e.g. %AS_APP_SEARCH_MATCH_PKGNAME
3922  *
3923  * Sets the token match fields. The bitfield given here is used to choose what
3924  * is included in the token cache.
3925  *
3926  * Since: 0.6.5
3927  **/
3928 void
as_store_set_search_match(AsStore * store,guint16 search_match)3929 as_store_set_search_match (AsStore *store, guint16 search_match)
3930 {
3931 	AsStorePrivate *priv = GET_PRIVATE (store);
3932 	g_return_if_fail (AS_IS_STORE (store));
3933 	priv->search_match = search_match;
3934 }
3935 
3936 /**
3937  * as_store_get_search_match:
3938  * @store: a #AsStore instance.
3939  *
3940  * Gets the token match fields. The bitfield given here is used to choose what
3941  * is included in the token cache.
3942  *
3943  * Returns: a #AsAppSearchMatch, e.g. %AS_APP_SEARCH_MATCH_PKGNAME
3944  *
3945  * Since: 0.6.13
3946  **/
3947 guint16
as_store_get_search_match(AsStore * store)3948 as_store_get_search_match (AsStore *store)
3949 {
3950 	AsStorePrivate *priv = GET_PRIVATE (store);
3951 	g_return_val_if_fail (AS_IS_STORE (store), 0);
3952 	return priv->search_match;
3953 }
3954 
3955 static void
as_store_init(AsStore * store)3956 as_store_init (AsStore *store)
3957 {
3958 	AsStorePrivate *priv = GET_PRIVATE (store);
3959 	g_mutex_init (&priv->mutex);
3960 	priv->profile = as_profile_new ();
3961 	priv->stemmer = as_stemmer_new ();
3962 	priv->api_version = AS_API_VERSION_NEWEST;
3963 	priv->array = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
3964 	priv->watch_flags = AS_STORE_WATCH_FLAG_NONE;
3965 	priv->search_match = AS_APP_SEARCH_MATCH_LAST;
3966 	priv->search_blacklist = g_hash_table_new_full (g_str_hash,
3967 							g_str_equal,
3968 							(GDestroyNotify) as_ref_string_unref,
3969 							NULL);
3970 	priv->hash_id = g_hash_table_new_full (g_str_hash,
3971 					       g_str_equal,
3972 					       g_free,
3973 					       (GDestroyNotify) g_ptr_array_unref);
3974 	priv->hash_merge_id = g_hash_table_new_full (g_str_hash,
3975 						     g_str_equal,
3976 						     g_free,
3977 						     (GDestroyNotify) g_ptr_array_unref);
3978 	priv->hash_unique_id = g_hash_table_new_full (g_str_hash,
3979 						      g_str_equal,
3980 						      g_free,
3981 						      g_object_unref);
3982 	priv->hash_pkgname = g_hash_table_new_full (g_str_hash,
3983 						    g_str_equal,
3984 						    g_free,
3985 						    (GDestroyNotify) g_object_unref);
3986 	priv->appinfo_dirs = g_hash_table_new_full (g_str_hash,
3987 						    g_str_equal,
3988 						    g_free,
3989 						    (GDestroyNotify) as_store_path_data_free);
3990 	priv->monitor = as_monitor_new ();
3991 	g_signal_connect (priv->monitor, "changed",
3992 			  G_CALLBACK (as_store_monitor_changed_cb),
3993 			  store);
3994 	g_signal_connect (priv->monitor, "added",
3995 			  G_CALLBACK (as_store_monitor_added_cb),
3996 			  store);
3997 	g_signal_connect (priv->monitor, "removed",
3998 			  G_CALLBACK (as_store_monitor_removed_cb),
3999 			  store);
4000 	priv->metadata_indexes = g_hash_table_new_full (g_str_hash,
4001 							  g_str_equal,
4002 							  g_free,
4003 							  (GDestroyNotify) g_hash_table_unref);
4004 
4005 	/* add stemmed keywords to the search blacklist */
4006 	as_store_create_search_blacklist (store);
4007 }
4008 
4009 /**
4010  * as_store_new:
4011  *
4012  * Creates a new #AsStore.
4013  *
4014  * Returns: (transfer full): a #AsStore
4015  *
4016  * Since: 0.1.0
4017  **/
4018 AsStore *
as_store_new(void)4019 as_store_new (void)
4020 {
4021 	AsStore *store;
4022 	store = g_object_new (AS_TYPE_STORE, NULL);
4023 	return AS_STORE (store);
4024 }
4025