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