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