1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2014-2018 Richard Hughes <richard@hughsie.com>
4  * Copyright (C) 2018 Matthias Klumpp <matthias@tenstral.net>
5  *
6  * SPDX-License-Identifier: LGPL-2.1+
7  */
8 
9 /**
10  * SECTION:as-release
11  * @short_description: Object representing a single upstream release
12  * @include: appstream-glib.h
13  * @stability: Stable
14  *
15  * This object represents a single upstream release, typically a minor update.
16  * Releases can contain a localized description of paragraph and list elements
17  * and also have a version number and timestamp.
18  *
19  * Releases can be automatically generated by parsing upstream ChangeLogs or
20  * .spec files, or can be populated using AppData files.
21  *
22  * See also: #AsRelease
23  */
24 
25 #include "config.h"
26 
27 #include <stdlib.h>
28 
29 #include "as-checksum-private.h"
30 #include "as-node-private.h"
31 #include "as-ref-string.h"
32 #include "as-release-private.h"
33 #include "as-tag.h"
34 #include "as-utils-private.h"
35 #include "as-yaml.h"
36 
37 typedef struct
38 {
39 	AsUrgencyKind		 urgency;
40 	AsReleaseKind		 kind;
41 	AsReleaseState		 state;
42 	guint64			*sizes;
43 	AsRefString		*version;
44 	GHashTable		*blobs;		/* of AsRefString:GBytes */
45 	GHashTable		*descriptions;
46 	GHashTable		*urls;		/* of AsRefString:AsRefString */
47 	guint64			 timestamp;
48 	guint64			 install_duration;
49 	GPtrArray		*locations;	/* of AsRefString, lazy */
50 	GPtrArray		*checksums;	/* of AsChecksum, lazy */
51 } AsReleasePrivate;
52 
G_DEFINE_TYPE_WITH_PRIVATE(AsRelease,as_release,G_TYPE_OBJECT)53 G_DEFINE_TYPE_WITH_PRIVATE (AsRelease, as_release, G_TYPE_OBJECT)
54 
55 #define GET_PRIVATE(o) (as_release_get_instance_private (o))
56 
57 static void
58 as_release_finalize (GObject *object)
59 {
60 	AsRelease *release = AS_RELEASE (object);
61 	AsReleasePrivate *priv = GET_PRIVATE (release);
62 
63 	g_free (priv->sizes);
64 	g_hash_table_unref (priv->urls);
65 	if (priv->version != NULL)
66 		as_ref_string_unref (priv->version);
67 	if (priv->blobs != NULL)
68 		g_hash_table_unref (priv->blobs);
69 	if (priv->checksums != NULL)
70 		g_ptr_array_unref (priv->checksums);
71 	if (priv->locations != NULL)
72 		g_ptr_array_unref (priv->locations);
73 	if (priv->descriptions != NULL)
74 		g_hash_table_unref (priv->descriptions);
75 
76 	G_OBJECT_CLASS (as_release_parent_class)->finalize (object);
77 }
78 
79 static void
as_release_init(AsRelease * release)80 as_release_init (AsRelease *release)
81 {
82 	AsReleasePrivate *priv = GET_PRIVATE (release);
83 	priv->urgency = AS_URGENCY_KIND_UNKNOWN;
84 	priv->kind = AS_RELEASE_KIND_UNKNOWN;
85 	priv->state = AS_RELEASE_STATE_UNKNOWN;
86 	priv->urls = g_hash_table_new_full (g_str_hash, g_str_equal,
87 					    (GDestroyNotify) as_ref_string_unref,
88 					    (GDestroyNotify) as_ref_string_unref);
89 }
90 
91 static void
as_release_ensure_checksums(AsRelease * release)92 as_release_ensure_checksums  (AsRelease *release)
93 {
94 	AsReleasePrivate *priv = GET_PRIVATE (release);
95 	if (priv->checksums != NULL)
96 		return;
97 	priv->checksums = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
98 }
99 
100 static void
as_release_ensure_sizes(AsRelease * release)101 as_release_ensure_sizes  (AsRelease *release)
102 {
103 	AsReleasePrivate *priv = GET_PRIVATE (release);
104 	if (priv->sizes != NULL)
105 		return;
106 	priv->sizes = g_new0 (guint64, AS_SIZE_KIND_LAST);
107 }
108 
109 static void
as_release_ensure_locations(AsRelease * release)110 as_release_ensure_locations  (AsRelease *release)
111 {
112 	AsReleasePrivate *priv = GET_PRIVATE (release);
113 	if (priv->locations != NULL)
114 		return;
115 	priv->locations = g_ptr_array_new_with_free_func ((GDestroyNotify) as_ref_string_unref);
116 }
117 
118 static void
as_release_ensure_blobs(AsRelease * release)119 as_release_ensure_blobs  (AsRelease *release)
120 {
121 	AsReleasePrivate *priv = GET_PRIVATE (release);
122 	if (priv->blobs != NULL)
123 		return;
124 	priv->blobs = g_hash_table_new_full (g_str_hash, g_str_equal,
125 					     (GDestroyNotify) as_ref_string_unref,
126 					     (GDestroyNotify) g_bytes_unref);
127 }
128 
129 static void
as_release_class_init(AsReleaseClass * klass)130 as_release_class_init (AsReleaseClass *klass)
131 {
132 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
133 	object_class->finalize = as_release_finalize;
134 }
135 /**
136  * as_release_kind_to_string:
137  * @kind: the #AsReleaseKind.
138  *
139  * Converts the enumerated value to an text representation.
140  *
141  * Returns: string version of @kind
142  *
143  * Since: 0.7.6
144  **/
145 const gchar*
as_release_kind_to_string(AsReleaseKind kind)146 as_release_kind_to_string (AsReleaseKind kind)
147 {
148 	if (kind == AS_RELEASE_KIND_STABLE)
149 		return "stable";
150 	if (kind == AS_RELEASE_KIND_DEVELOPMENT)
151 		return "development";
152 	return "unknown";
153 }
154 
155 /**
156  * as_release_kind_from_string:
157  * @kind_str: the string.
158  *
159  * Converts the text representation to an enumerated value.
160  *
161  * Returns: an #AsReleaseKind or %AS_RELEASE_KIND_UNKNOWN for unknown
162  *
163  * Since: 0.7.6
164  **/
165 AsReleaseKind
as_release_kind_from_string(const gchar * kind_str)166 as_release_kind_from_string (const gchar *kind_str)
167 {
168 	if (g_strcmp0 (kind_str, "stable") == 0)
169 		return AS_RELEASE_KIND_STABLE;
170 	if (g_strcmp0 (kind_str, "development") == 0)
171 		return AS_RELEASE_KIND_DEVELOPMENT;
172 	return AS_RELEASE_KIND_UNKNOWN;
173 }
174 
175 /**
176  * as_release_state_from_string:
177  * @state: a string
178  *
179  * Converts the text representation to an enumerated value.
180  *
181  * Return value: A #AsReleaseState, e.g. %AS_RELEASE_STATE_INSTALLED.
182  *
183  * Since: 0.6.6
184  **/
185 AsReleaseState
as_release_state_from_string(const gchar * state)186 as_release_state_from_string (const gchar *state)
187 {
188 	if (g_strcmp0 (state, "installed") == 0)
189 		return AS_RELEASE_STATE_INSTALLED;
190 	if (g_strcmp0 (state, "available") == 0)
191 		return AS_RELEASE_STATE_AVAILABLE;
192 	return AS_APP_MERGE_KIND_NONE;
193 }
194 
195 /**
196  * as_release_state_to_string:
197  * @state: the #AsReleaseState, e.g. %AS_RELEASE_STATE_INSTALLED
198  *
199  * Converts the enumerated value to an text representation.
200  *
201  * Returns: string version of @state, or %NULL for unknown
202  *
203  * Since: 0.6.6
204  **/
205 const gchar *
as_release_state_to_string(AsReleaseState state)206 as_release_state_to_string (AsReleaseState state)
207 {
208 	if (state == AS_RELEASE_STATE_INSTALLED)
209 		return "installed";
210 	if (state == AS_RELEASE_STATE_AVAILABLE)
211 		return "available";
212 	return NULL;
213 }
214 
215 /**
216  * as_release_vercmp:
217  * @rel1: a #AsRelease instance.
218  * @rel2: a #AsRelease instance.
219  *
220  * Compares two release.
221  *
222  * Returns: -1 if rel1 > rel2, +1 if rel1 < rel2, 0 otherwise
223  *
224  * Since: 0.4.2
225  **/
226 gint
as_release_vercmp(AsRelease * rel1,AsRelease * rel2)227 as_release_vercmp (AsRelease *rel1, AsRelease *rel2)
228 {
229 	AsReleasePrivate *priv1 = GET_PRIVATE (rel1);
230 	AsReleasePrivate *priv2 = GET_PRIVATE (rel2);
231 	gint val;
232 
233 	g_return_val_if_fail (AS_IS_RELEASE (rel1), 0);
234 	g_return_val_if_fail (AS_IS_RELEASE (rel2), 0);
235 
236 	/* prefer the timestamp */
237 	if (priv1->timestamp > priv2->timestamp)
238 		return -1;
239 	if (priv1->timestamp < priv2->timestamp)
240 		return 1;
241 
242 	/* fall back to the version strings */
243 	val = as_utils_vercmp_full (priv2->version, priv1->version, AS_VERSION_COMPARE_FLAG_NONE);
244 	if (val != G_MAXINT)
245 		return val;
246 
247 	return 0;
248 }
249 
250 /**
251  * as_release_get_size:
252  * @release: a #AsRelease instance
253  * @kind: a #AsSizeKind, e.g. #AS_SIZE_KIND_DOWNLOAD
254  *
255  * Gets the release size.
256  *
257  * Returns: The size in bytes, or 0 for unknown.
258  *
259  * Since: 0.5.2
260  **/
261 guint64
as_release_get_size(AsRelease * release,AsSizeKind kind)262 as_release_get_size (AsRelease *release, AsSizeKind kind)
263 {
264 	AsReleasePrivate *priv = GET_PRIVATE (release);
265 	g_return_val_if_fail (AS_IS_RELEASE (release), 0);
266 	if (kind >= AS_SIZE_KIND_LAST)
267 		return 0;
268 	if (priv->sizes == NULL)
269 		return 0;
270 	return priv->sizes[kind];
271 }
272 
273 /**
274  * as_release_set_size:
275  * @release: a #AsRelease instance
276  * @kind: a #AsSizeKind, e.g. #AS_SIZE_KIND_DOWNLOAD
277  * @size: a size in bytes, or 0 for unknown
278  *
279  * Sets the release size.
280  *
281  * Since: 0.5.2
282  **/
283 void
as_release_set_size(AsRelease * release,AsSizeKind kind,guint64 size)284 as_release_set_size (AsRelease *release, AsSizeKind kind, guint64 size)
285 {
286 	AsReleasePrivate *priv = GET_PRIVATE (release);
287 	g_return_if_fail (AS_IS_RELEASE (release));
288 	if (kind >= AS_SIZE_KIND_LAST)
289 		return;
290 	as_release_ensure_sizes (release);
291 	priv->sizes[kind] = size;
292 }
293 
294 /**
295  * as_release_get_urgency:
296  * @release: a #AsRelease instance.
297  *
298  * Gets the release urgency.
299  *
300  * Returns: enumberated value, or %AS_URGENCY_KIND_UNKNOWN for not set or invalid
301  *
302  * Since: 0.5.1
303  **/
304 AsUrgencyKind
as_release_get_urgency(AsRelease * release)305 as_release_get_urgency (AsRelease *release)
306 {
307 	AsReleasePrivate *priv = GET_PRIVATE (release);
308 	g_return_val_if_fail (AS_IS_RELEASE (release), AS_URGENCY_KIND_UNKNOWN);
309 	return priv->urgency;
310 }
311 
312 /**
313  * as_release_get_state:
314  * @release: a #AsRelease instance.
315  *
316  * Gets the release state.
317  *
318  * Returns: enumberated value, or %AS_RELEASE_STATE_UNKNOWN for not set or invalid
319  *
320  * Since: 0.5.8
321  **/
322 AsReleaseState
as_release_get_state(AsRelease * release)323 as_release_get_state (AsRelease *release)
324 {
325 	AsReleasePrivate *priv = GET_PRIVATE (release);
326 	g_return_val_if_fail (AS_IS_RELEASE (release), AS_RELEASE_STATE_UNKNOWN);
327 	return priv->state;
328 }
329 
330 /**
331  * as_release_get_kind:
332  * @release: a #AsRelease instance.
333  *
334  * Gets the type of the release.
335  *
336  * Returns: enumerated value, e.g. %AS_RELEASE_KIND_STABLE
337  *
338  * Since: 0.7.6
339  **/
340 AsReleaseKind
as_release_get_kind(AsRelease * release)341 as_release_get_kind (AsRelease *release)
342 {
343 	AsReleasePrivate *priv = GET_PRIVATE (release);
344 	g_return_val_if_fail (AS_IS_RELEASE (release), AS_RELEASE_KIND_UNKNOWN);
345 	return priv->kind;
346 }
347 
348 /**
349  * as_release_get_version:
350  * @release: a #AsRelease instance.
351  *
352  * Gets the release version.
353  *
354  * Returns: string, or %NULL for not set or invalid
355  *
356  * Since: 0.1.0
357  **/
358 const gchar *
as_release_get_version(AsRelease * release)359 as_release_get_version (AsRelease *release)
360 {
361 	AsReleasePrivate *priv = GET_PRIVATE (release);
362 	g_return_val_if_fail (AS_IS_RELEASE (release), NULL);
363 	return priv->version;
364 }
365 
366 /**
367  * as_release_get_blob:
368  * @release: a #AsRelease instance.
369  * @filename: a filename
370  *
371  * Gets the release blob, which is typically firmware file data.
372  *
373  * Returns: a #GBytes, or %NULL for not set
374  *
375  * Since: 0.5.2
376  **/
377 GBytes *
as_release_get_blob(AsRelease * release,const gchar * filename)378 as_release_get_blob (AsRelease *release, const gchar *filename)
379 {
380 	AsReleasePrivate *priv = GET_PRIVATE (release);
381 	g_return_val_if_fail (AS_IS_RELEASE (release), NULL);
382 	g_return_val_if_fail (filename != NULL, NULL);
383 	if (priv->blobs == NULL)
384 		return NULL;
385 	return g_hash_table_lookup (priv->blobs, filename);
386 }
387 
388 /**
389  * as_release_get_locations:
390  * @release: a #AsRelease instance.
391  *
392  * Gets the release locations, typically URLs.
393  *
394  * Returns: (transfer none) (element-type utf8): list of locations
395  *
396  * Since: 0.3.5
397  **/
398 GPtrArray *
as_release_get_locations(AsRelease * release)399 as_release_get_locations (AsRelease *release)
400 {
401 	AsReleasePrivate *priv = GET_PRIVATE (release);
402 	g_return_val_if_fail (AS_IS_RELEASE (release), NULL);
403 	as_release_ensure_locations (release);
404 	return priv->locations;
405 }
406 
407 /**
408  * as_release_get_location_default:
409  * @release: a #AsRelease instance.
410  *
411  * Gets the default release location, typically a URL.
412  *
413  * Returns: string, or %NULL for not set or invalid
414  *
415  * Since: 0.3.5
416  **/
417 const gchar *
as_release_get_location_default(AsRelease * release)418 as_release_get_location_default (AsRelease *release)
419 {
420 	AsReleasePrivate *priv = GET_PRIVATE (release);
421 	g_return_val_if_fail (AS_IS_RELEASE (release), NULL);
422 	if (priv->locations == NULL)
423 		return NULL;
424 	if (priv->locations->len == 0)
425 		return NULL;
426 	return g_ptr_array_index (priv->locations, 0);
427 }
428 
429 /**
430  * as_release_get_checksums:
431  * @release: a #AsRelease instance.
432  *
433  * Gets the release checksums.
434  *
435  * Returns: (transfer none) (element-type AsChecksum): list of checksums
436  *
437  * Since: 0.4.2
438  **/
439 GPtrArray *
as_release_get_checksums(AsRelease * release)440 as_release_get_checksums (AsRelease *release)
441 {
442 	AsReleasePrivate *priv = GET_PRIVATE (release);
443 	g_return_val_if_fail (AS_IS_RELEASE (release), NULL);
444 	as_release_ensure_checksums (release);
445 	return priv->checksums;
446 }
447 
448 /**
449  * as_release_get_checksum_by_fn:
450  * @release: a #AsRelease instance
451  * @fn: a file basename
452  *
453  * Gets the checksum for a release.
454  *
455  * Returns: (transfer none): an #AsChecksum, or %NULL for not found
456  *
457  * Since: 0.4.2
458  **/
459 AsChecksum *
as_release_get_checksum_by_fn(AsRelease * release,const gchar * fn)460 as_release_get_checksum_by_fn (AsRelease *release, const gchar *fn)
461 {
462 	AsChecksum *checksum;
463 	AsReleasePrivate *priv = GET_PRIVATE (release);
464 	guint i;
465 
466 	g_return_val_if_fail (AS_IS_RELEASE (release), NULL);
467 
468 	for (i = 0; i < priv->checksums->len; i++) {
469 		checksum = g_ptr_array_index (priv->checksums, i);
470 		if (g_strcmp0 (fn, as_checksum_get_filename (checksum)) == 0)
471 			return checksum;
472 	}
473 	return NULL;
474 }
475 
476 /**
477  * as_release_get_checksum_by_target:
478  * @release: a #AsRelease instance
479  * @target: a #AsChecksumTarget, e.g. %AS_CHECKSUM_TARGET_CONTAINER
480  *
481  * Gets the checksum for a release.
482  *
483  * Returns: (transfer none): an #AsChecksum, or %NULL for not found
484  *
485  * Since: 0.4.2
486  **/
487 AsChecksum *
as_release_get_checksum_by_target(AsRelease * release,AsChecksumTarget target)488 as_release_get_checksum_by_target (AsRelease *release, AsChecksumTarget target)
489 {
490 	AsChecksum *checksum;
491 	AsReleasePrivate *priv = GET_PRIVATE (release);
492 	guint i;
493 
494 	g_return_val_if_fail (AS_IS_RELEASE (release), NULL);
495 
496 	if (priv->checksums == NULL)
497 		return NULL;
498 	for (i = 0; i < priv->checksums->len; i++) {
499 		checksum = g_ptr_array_index (priv->checksums, i);
500 		if (as_checksum_get_target (checksum) == target)
501 			return checksum;
502 	}
503 	return NULL;
504 }
505 
506 /**
507  * as_release_get_timestamp:
508  * @release: a #AsRelease instance.
509  *
510  * Gets the release timestamp.
511  *
512  * Returns: timestamp, or 0 for unset
513  *
514  * Since: 0.1.0
515  **/
516 guint64
as_release_get_timestamp(AsRelease * release)517 as_release_get_timestamp (AsRelease *release)
518 {
519 	AsReleasePrivate *priv = GET_PRIVATE (release);
520 	g_return_val_if_fail (AS_IS_RELEASE (release), 0);
521 	return priv->timestamp;
522 }
523 
524 /**
525  * as_release_get_install_duration:
526  * @release: a #AsRelease instance.
527  *
528  * Gets the typical install duration.
529  *
530  * Returns: install duration in seconds, or 0 for unset
531  *
532  * Since: 0.7.15
533  **/
534 guint64
as_release_get_install_duration(AsRelease * release)535 as_release_get_install_duration (AsRelease *release)
536 {
537 	AsReleasePrivate *priv = GET_PRIVATE (release);
538 	g_return_val_if_fail (AS_IS_RELEASE (release), 0);
539 	return priv->install_duration;
540 }
541 
542 /**
543  * as_release_get_description:
544  * @release: a #AsRelease instance.
545  * @locale: (nullable): the locale. e.g. "en_GB"
546  *
547  * Gets the release description markup for a given locale.
548  *
549  * Returns: markup, or %NULL for not set or invalid
550  *
551  * Since: 0.1.0
552  **/
553 const gchar *
as_release_get_description(AsRelease * release,const gchar * locale)554 as_release_get_description (AsRelease *release, const gchar *locale)
555 {
556 	AsReleasePrivate *priv = GET_PRIVATE (release);
557 	g_return_val_if_fail (AS_IS_RELEASE (release), NULL);
558 	if (priv->descriptions == NULL)
559 		return NULL;
560 	return as_hash_lookup_by_locale (priv->descriptions, locale);
561 }
562 
563 /**
564  * as_release_set_version:
565  * @release: a #AsRelease instance.
566  * @version: the version string.
567  *
568  * Sets the release version.
569  *
570  * Since: 0.1.0
571  **/
572 void
as_release_set_version(AsRelease * release,const gchar * version)573 as_release_set_version (AsRelease *release, const gchar *version)
574 {
575 	AsReleasePrivate *priv = GET_PRIVATE (release);
576 	g_return_if_fail (AS_IS_RELEASE (release));
577 	as_ref_string_assign_safe (&priv->version, version);
578 }
579 
580 /**
581  * as_release_set_blob:
582  * @release: a #AsRelease instance.
583  * @filename: a filename
584  * @blob: the #GBytes data blob
585  *
586  * Sets a release blob, which is typically firmware data or a detached signature.
587  *
588  * NOTE: This is not stored in the XML file, and is only available in-memory.
589  *
590  * Since: 0.5.2
591  **/
592 void
as_release_set_blob(AsRelease * release,const gchar * filename,GBytes * blob)593 as_release_set_blob (AsRelease *release, const gchar *filename, GBytes *blob)
594 {
595 	AsReleasePrivate *priv = GET_PRIVATE (release);
596 	g_return_if_fail (AS_IS_RELEASE (release));
597 	g_return_if_fail (filename != NULL);
598 	g_return_if_fail (blob != NULL);
599 
600 	as_release_ensure_blobs (release);
601 	g_hash_table_insert (priv->blobs,
602 			     as_ref_string_new (filename),
603 			     g_bytes_ref (blob));
604 }
605 
606 /**
607  * as_release_set_urgency:
608  * @release: a #AsRelease instance.
609  * @urgency: the release urgency, e.g. %AS_URGENCY_KIND_CRITICAL
610  *
611  * Sets the release urgency.
612  *
613  * Since: 0.5.1
614  **/
615 void
as_release_set_urgency(AsRelease * release,AsUrgencyKind urgency)616 as_release_set_urgency (AsRelease *release, AsUrgencyKind urgency)
617 {
618 	AsReleasePrivate *priv = GET_PRIVATE (release);
619 	g_return_if_fail (AS_IS_RELEASE (release));
620 	priv->urgency = urgency;
621 }
622 
623 /**
624  * as_release_set_kind:
625  * @release: a #AsRelease instance.
626  * @kind: the #AsReleaseKind
627  *
628  * Sets the release kind.
629  *
630  * Since: 0.7.6
631  **/
632 void
as_release_set_kind(AsRelease * release,AsReleaseKind kind)633 as_release_set_kind (AsRelease *release, AsReleaseKind kind)
634 {
635 	AsReleasePrivate *priv = GET_PRIVATE (release);
636 	g_return_if_fail (AS_IS_RELEASE (release));
637 	priv->kind = kind;
638 }
639 
640 /**
641  * as_release_set_state:
642  * @release: a #AsRelease instance.
643  * @state: the release state, e.g. %AS_RELEASE_STATE_INSTALLED
644  *
645  * Sets the release state.
646  *
647  * Since: 0.5.8
648  **/
649 void
as_release_set_state(AsRelease * release,AsReleaseState state)650 as_release_set_state (AsRelease *release, AsReleaseState state)
651 {
652 	AsReleasePrivate *priv = GET_PRIVATE (release);
653 	g_return_if_fail (AS_IS_RELEASE (release));
654 	priv->state = state;
655 }
656 
657 /**
658  * as_release_add_location:
659  * @release: a #AsRelease instance.
660  * @location: the location string.
661  *
662  * Adds a release location.
663  *
664  * Since: 0.3.5
665  **/
666 void
as_release_add_location(AsRelease * release,const gchar * location)667 as_release_add_location (AsRelease *release, const gchar *location)
668 {
669 	AsReleasePrivate *priv = GET_PRIVATE (release);
670 
671 	g_return_if_fail (AS_IS_RELEASE (release));
672 
673 	/* deduplicate */
674 	as_release_ensure_locations (release);
675 	if (as_ptr_array_find_string (priv->locations, location))
676 		return;
677 
678 	g_ptr_array_add (priv->locations, as_ref_string_new (location));
679 }
680 
681 /**
682  * as_release_add_checksum:
683  * @release: a #AsRelease instance.
684  * @checksum: a #AsChecksum instance.
685  *
686  * Adds a release checksum.
687  *
688  * Since: 0.4.2
689  **/
690 void
as_release_add_checksum(AsRelease * release,AsChecksum * checksum)691 as_release_add_checksum (AsRelease *release, AsChecksum *checksum)
692 {
693 	AsReleasePrivate *priv = GET_PRIVATE (release);
694 	g_return_if_fail (AS_IS_RELEASE (release));
695 	as_release_ensure_checksums (release);
696 	g_ptr_array_add (priv->checksums, g_object_ref (checksum));
697 }
698 
699 /**
700  * as_release_set_timestamp:
701  * @release: a #AsRelease instance.
702  * @timestamp: the timestamp value.
703  *
704  * Sets the release timestamp.
705  *
706  * Since: 0.1.0
707  **/
708 void
as_release_set_timestamp(AsRelease * release,guint64 timestamp)709 as_release_set_timestamp (AsRelease *release, guint64 timestamp)
710 {
711 	AsReleasePrivate *priv = GET_PRIVATE (release);
712 	g_return_if_fail (AS_IS_RELEASE (release));
713 	priv->timestamp = timestamp;
714 }
715 
716 /**
717  * as_release_set_install_duration:
718  * @release: a #AsRelease instance.
719  * @install_duration: the install duration in seconds
720  *
721  * Sets the typical duration of the install.
722  *
723  * Since: 0.7.15
724  **/
725 void
as_release_set_install_duration(AsRelease * release,guint64 install_duration)726 as_release_set_install_duration (AsRelease *release, guint64 install_duration)
727 {
728 	AsReleasePrivate *priv = GET_PRIVATE (release);
729 	g_return_if_fail (AS_IS_RELEASE (release));
730 	priv->install_duration = install_duration;
731 }
732 
733 /**
734  * as_release_set_description:
735  * @release: a #AsRelease instance.
736  * @locale: (nullable): the locale. e.g. "en_GB"
737  * @description: the description markup.
738  *
739  * Sets the description release markup.
740  *
741  * Since: 0.1.0
742  **/
743 void
as_release_set_description(AsRelease * release,const gchar * locale,const gchar * description)744 as_release_set_description (AsRelease *release,
745 			    const gchar *locale,
746 			    const gchar *description)
747 {
748 	AsReleasePrivate *priv = GET_PRIVATE (release);
749 	g_return_if_fail (AS_IS_RELEASE (release));
750 	if (locale == NULL)
751 		locale = "C";
752 	if (priv->descriptions == NULL) {
753 		priv->descriptions = g_hash_table_new_full (g_str_hash,
754 							    g_str_equal,
755 							    (GDestroyNotify) as_ref_string_unref,
756 							    (GDestroyNotify) as_ref_string_unref);
757 	}
758 	g_hash_table_insert (priv->descriptions,
759 			     as_ref_string_new (locale),
760 			     as_ref_string_new (description));
761 }
762 
763 /**
764  * as_release_get_url:
765  * @release: a #AsRelease instance.
766  * @url_kind: the URL kind, e.g. %AS_URL_KIND_HOMEPAGE.
767  *
768  * Gets a URL.
769  *
770  * Returns: string, or %NULL if unset
771  *
772  * Since: 0.7.15
773  **/
774 const gchar *
as_release_get_url(AsRelease * release,AsUrlKind url_kind)775 as_release_get_url (AsRelease *release, AsUrlKind url_kind)
776 {
777 	AsReleasePrivate *priv = GET_PRIVATE (release);
778 	return g_hash_table_lookup (priv->urls,
779 				    as_url_kind_to_string (url_kind));
780 }
781 
782 /**
783  * as_release_set_url:
784  * @release: a #AsRelease instance.
785  * @url_kind: the URL kind, e.g. %AS_URL_KIND_DETAILS
786  * @url: the full URL.
787  *
788  * Adds some URL data to the application.
789  *
790  * Since: 0.7.15
791  **/
792 void
as_release_set_url(AsRelease * release,AsUrlKind url_kind,const gchar * url)793 as_release_set_url (AsRelease *release,
794 		    AsUrlKind url_kind,
795 		    const gchar *url)
796 {
797 	AsReleasePrivate *priv = GET_PRIVATE (release);
798 	if (url == NULL) {
799 		g_hash_table_remove (priv->urls, as_url_kind_to_string (url_kind));
800 	} else {
801 		g_hash_table_insert (priv->urls,
802 				     (AsRefString *) as_url_kind_to_string (url_kind),
803 				     as_ref_string_new (url));
804 	}
805 }
806 
807 /**
808  * as_release_node_insert: (skip)
809  * @release: a #AsRelease instance.
810  * @parent: the parent #GNode to use..
811  * @ctx: the #AsNodeContext
812  *
813  * Inserts the release into the DOM tree.
814  *
815  * Returns: (transfer none): A populated #GNode
816  *
817  * Since: 0.1.1
818  **/
819 GNode *
as_release_node_insert(AsRelease * release,GNode * parent,AsNodeContext * ctx)820 as_release_node_insert (AsRelease *release, GNode *parent, AsNodeContext *ctx)
821 {
822 	AsReleasePrivate *priv = GET_PRIVATE (release);
823 	AsChecksum *checksum;
824 	GNode *n;
825 
826 	g_return_val_if_fail (AS_IS_RELEASE (release), NULL);
827 
828 	n = as_node_insert (parent, "release", NULL,
829 			    AS_NODE_INSERT_FLAG_NONE,
830 			    NULL);
831 	if (priv->timestamp > 0) {
832 		g_autofree gchar *timestamp_str = NULL;
833 		timestamp_str = g_strdup_printf ("%" G_GUINT64_FORMAT,
834 						 priv->timestamp);
835 		as_node_add_attribute (n, "timestamp", timestamp_str);
836 	}
837 	if (priv->urgency != AS_URGENCY_KIND_UNKNOWN) {
838 		as_node_add_attribute (n, "urgency",
839 				       as_urgency_kind_to_string (priv->urgency));
840 	}
841 	if (priv->kind != AS_RELEASE_KIND_UNKNOWN) {
842 		as_node_add_attribute (n, "type",
843 				       as_release_kind_to_string (priv->kind));
844 	}
845 	if (as_node_context_get_output_trusted (ctx) &&
846 	    priv->state != AS_RELEASE_STATE_UNKNOWN) {
847 		as_node_add_attribute (n, "state",
848 				       as_release_state_to_string (priv->state));
849 	}
850 	if (priv->version != NULL)
851 		as_node_add_attribute (n, "version", priv->version);
852 	if (priv->install_duration > 0) {
853 		g_autofree gchar *install_duration_str = NULL;
854 		install_duration_str = g_strdup_printf ("%" G_GUINT64_FORMAT,
855 							priv->install_duration);
856 		as_node_add_attribute (n, "install_duration", install_duration_str);
857 	}
858 	for (guint i = 0; priv->locations != NULL && i < priv->locations->len; i++) {
859 		const gchar *tmp = g_ptr_array_index (priv->locations, i);
860 		as_node_insert (n, "location", tmp,
861 				AS_NODE_INSERT_FLAG_NONE, NULL);
862 	}
863 	for (guint i = 0; priv->checksums != NULL && i < priv->checksums->len; i++) {
864 		checksum = g_ptr_array_index (priv->checksums, i);
865 		as_checksum_node_insert (checksum, n, ctx);
866 	}
867 	if (priv->urls != NULL)
868 		as_node_insert_hash (n, "url", "type", priv->urls, 0);
869 	if (priv->descriptions != NULL) {
870 		as_node_insert_localized (n, "description", priv->descriptions,
871 					  AS_NODE_INSERT_FLAG_PRE_ESCAPED |
872 					  AS_NODE_INSERT_FLAG_DEDUPE_LANG);
873 	}
874 
875 	/* add sizes */
876 	if (priv->sizes != NULL) {
877 		for (guint i = 0; i < AS_SIZE_KIND_LAST; i++) {
878 			g_autofree gchar *size_str = NULL;
879 			if (priv->sizes[i] == 0)
880 				continue;
881 			size_str = g_strdup_printf ("%" G_GUINT64_FORMAT, priv->sizes[i]);
882 			as_node_insert (n, "size", size_str,
883 					AS_NODE_INSERT_FLAG_NONE,
884 					"type", as_size_kind_to_string (i),
885 					NULL);
886 		}
887 	}
888 	return n;
889 }
890 
891 /**
892  * as_release_node_parse:
893  * @release: a #AsRelease instance.
894  * @node: a #GNode.
895  * @ctx: a #AsNodeContext.
896  * @error: A #GError or %NULL.
897  *
898  * Populates the object from a DOM node.
899  *
900  * Returns: %TRUE for success
901  *
902  * Since: 0.1.0
903  **/
904 gboolean
as_release_node_parse(AsRelease * release,GNode * node,AsNodeContext * ctx,GError ** error)905 as_release_node_parse (AsRelease *release, GNode *node,
906 		       AsNodeContext *ctx, GError **error)
907 {
908 	AsReleasePrivate *priv = GET_PRIVATE (release);
909 	GNode *n;
910 	const gchar *tmp;
911 
912 	g_return_val_if_fail (AS_IS_RELEASE (release), FALSE);
913 
914 	tmp = as_node_get_attribute (node, "timestamp");
915 	if (tmp != NULL)
916 		as_release_set_timestamp (release, g_ascii_strtoull (tmp, NULL, 10));
917 	tmp = as_node_get_attribute (node, "date");
918 	if (tmp != NULL) {
919 		g_autoptr(GDateTime) dt = NULL;
920 		dt = as_utils_iso8601_to_datetime (tmp);
921 		if (dt != NULL)
922 			as_release_set_timestamp (release, (guint64) g_date_time_to_unix (dt));
923 	}
924 	tmp = as_node_get_attribute (node, "urgency");
925 	if (tmp != NULL)
926 		as_release_set_urgency (release, as_urgency_kind_from_string (tmp));
927 	tmp = as_node_get_attribute (node, "type");
928 	if (tmp != NULL)
929 		as_release_set_kind (release, as_release_kind_from_string (tmp));
930 	tmp = as_node_get_attribute (node, "version");
931 	if (tmp != NULL)
932 		as_release_set_version (release, tmp);
933 	tmp = as_node_get_attribute (node, "install_duration");
934 	if (tmp != NULL)
935 		priv->install_duration = g_ascii_strtoull (tmp, NULL, 10);
936 
937 	/* <url> */
938 	for (n = node->children; n != NULL; n = n->next) {
939 		if (as_node_get_tag (n) != AS_TAG_URL)
940 			continue;
941 		tmp = as_node_get_attribute (n, "type");
942 		as_release_set_url (release,
943 				    as_url_kind_from_string (tmp),
944 				    as_node_get_data (n));
945 	}
946 
947 	/* get optional locations */
948 	if (priv->locations != NULL)
949 		g_ptr_array_set_size (priv->locations, 0);
950 	for (n = node->children; n != NULL; n = n->next) {
951 		AsRefString *str;
952 		if (as_node_get_tag (n) != AS_TAG_LOCATION)
953 			continue;
954 		str = as_node_get_data_as_refstr (n);
955 		if (str == NULL)
956 			continue;
957 		as_release_ensure_locations (release);
958 		g_ptr_array_add (priv->locations, as_ref_string_ref (str));
959 	}
960 
961 	/* get optional checksums */
962 	for (n = node->children; n != NULL; n = n->next) {
963 		g_autoptr(AsChecksum) csum = NULL;
964 		if (as_node_get_tag (n) != AS_TAG_CHECKSUM)
965 			continue;
966 		csum = as_checksum_new ();
967 		if (!as_checksum_node_parse (csum, n, ctx, error))
968 			return FALSE;
969 		as_release_add_checksum (release, csum);
970 	}
971 
972 	/* get optional sizes */
973 	for (n = node->children; n != NULL; n = n->next) {
974 		AsSizeKind kind;
975 		if (as_node_get_tag (n) != AS_TAG_SIZE)
976 			continue;
977 		tmp = as_node_get_attribute (n, "type");
978 		if (tmp == NULL)
979 			continue;
980 		kind = as_size_kind_from_string (tmp);
981 		if (kind == AS_SIZE_KIND_UNKNOWN)
982 			continue;
983 		tmp = as_node_get_data (n);
984 		if (tmp == NULL)
985 			continue;
986 		as_release_ensure_sizes (release);
987 		priv->sizes[kind] = g_ascii_strtoull (tmp, NULL, 10);
988 	}
989 
990 	/* AppStream: multiple <description> tags */
991 	if (as_node_context_get_format_kind (ctx) == AS_FORMAT_KIND_APPSTREAM) {
992 		for (n = node->children; n != NULL; n = n->next) {
993 			g_autoptr(GString) xml = NULL;
994 			if (as_node_get_tag (n) != AS_TAG_DESCRIPTION)
995 				continue;
996 			if (n->children == NULL)
997 				continue;
998 			xml = as_node_to_xml (n->children,
999 					      AS_NODE_TO_XML_FLAG_INCLUDE_SIBLINGS);
1000 			if (xml == NULL)
1001 				continue;
1002 			as_release_set_description (release,
1003 						    as_node_get_attribute (n, "xml:lang"),
1004 						    xml->str);
1005 		}
1006 
1007 	/* AppData: multiple languages encoded in one <description> tag */
1008 	} else {
1009 		n = as_node_find (node, "description");
1010 		if (n != NULL) {
1011 			if (priv->descriptions != NULL)
1012 				g_hash_table_unref (priv->descriptions);
1013 			priv->descriptions = as_node_get_localized_unwrap (n, error);
1014 			if (priv->descriptions == NULL)
1015 				return FALSE;
1016 		}
1017 	}
1018 
1019 	return TRUE;
1020 }
1021 
1022 /**
1023  * as_release_node_parse_dep11:
1024  * @release: a #AsRelease instance.
1025  * @node: a #GNode.
1026  * @ctx: a #AsNodeContext.
1027  * @error: A #GError or %NULL.
1028  *
1029  * Populates the object from a DEP-11 node.
1030  *
1031  * Returns: %TRUE for success
1032  *
1033  * Since: 0.5.13
1034  **/
1035 gboolean
as_release_node_parse_dep11(AsRelease * release,GNode * node,AsNodeContext * ctx,GError ** error)1036 as_release_node_parse_dep11 (AsRelease *release, GNode *node,
1037 			     AsNodeContext *ctx, GError **error)
1038 {
1039 	GNode *c;
1040 	GNode *n;
1041 	const gchar *tmp;
1042 	const gchar *value;
1043 
1044 	g_return_val_if_fail (AS_IS_RELEASE (release), FALSE);
1045 
1046 	for (n = node->children; n != NULL; n = n->next) {
1047 		tmp = as_yaml_node_get_key (n);
1048 		if (g_strcmp0 (tmp, "unix-timestamp") == 0) {
1049 			value = as_yaml_node_get_value (n);
1050 			as_release_set_timestamp (release, g_ascii_strtoull (value, NULL, 10));
1051 			continue;
1052 		}
1053 		if (g_strcmp0 (tmp, "version") == 0) {
1054 			as_release_set_version (release, as_yaml_node_get_value (n));
1055 			continue;
1056 		}
1057 		if (g_strcmp0 (tmp, "type") == 0) {
1058 			as_release_set_kind (release, as_release_kind_from_string (as_yaml_node_get_value (n)));
1059 			continue;
1060 		}
1061 		if (g_strcmp0 (tmp, "description") == 0) {
1062 			for (c = n->children; c != NULL; c = c->next) {
1063 				as_release_set_description (release,
1064 							    as_yaml_node_get_key (c),
1065 							    as_yaml_node_get_value (c));
1066 			}
1067 			continue;
1068 		}
1069 		if (g_strcmp0 (tmp, "url") == 0) {
1070 			for (c = n->children; c != NULL; c = c->next) {
1071 				if (g_strcmp0 (as_yaml_node_get_key (c), "details") == 0) {
1072 					as_release_set_url (release,
1073 							    AS_URL_KIND_DETAILS,
1074 							    as_yaml_node_get_value (c));
1075 					continue;
1076 				}
1077 			}
1078 			continue;
1079 		}
1080 	}
1081 	return TRUE;
1082 }
1083 
1084 /**
1085  * as_release_new:
1086  *
1087  * Creates a new #AsRelease.
1088  *
1089  * Returns: (transfer full): a #AsRelease
1090  *
1091  * Since: 0.1.0
1092  **/
1093 AsRelease *
as_release_new(void)1094 as_release_new (void)
1095 {
1096 	AsRelease *release;
1097 	release = g_object_new (AS_TYPE_RELEASE, NULL);
1098 	return AS_RELEASE (release);
1099 }
1100