diff options
author | Owen W. Taylor <otaylor@fishsoup.net> | 2018-06-28 08:20:04 -0400 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2018-06-29 08:43:31 +0000 |
commit | 8f73dbd32d9d542ddf6046d64e6a9bd81fc93796 (patch) | |
tree | d90eab99680fe55d4f2e1234ec146db84bc71223 | |
parent | 79154b63fc9143ce2ffd0a267de6974af7aa911e (diff) | |
download | flatpak-8f73dbd32d9d542ddf6046d64e6a9bd81fc93796.tar.gz |
Include appstream and icons in OCI bundles as annotations
Include annotations:
org.freedesktop.appstream.appdata
org.freedesktop.appstream.icon-{64,128}
into OCI bundles. This not only makes the bundle self-describing, but also
if the bundle is imported into a registry, it becomes possible to browse
the registry and recreate an appstream by only retrieving the annotations
for relevant images, without having to download the actual images.
The icon annotations are formatted as data: URI's with base64 data. The idea
is that a server-side process to collect annotations could extract the icons
to separate storage and rewrite the URI's to remote URL's.
Closes: #1834
Approved by: alexlarsson
-rw-r--r-- | app/flatpak-builtins-build-bundle.c | 210 | ||||
-rwxr-xr-x | tests/test-oci.sh | 7 |
2 files changed, 163 insertions, 54 deletions
diff --git a/app/flatpak-builtins-build-bundle.c b/app/flatpak-builtins-build-bundle.c index 197817a6..c0ce3c84 100644 --- a/app/flatpak-builtins-build-bundle.c +++ b/app/flatpak-builtins-build-bundle.c @@ -108,6 +108,110 @@ read_gpg_data (GCancellable *cancellable, } static gboolean +get_bundle_appstream_data (GFile *root, + const char *full_branch, + const char *name, + GKeyFile *metadata, + gboolean compress, + GBytes **result, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) xmls_dir = NULL; + g_autofree char *appstream_basename = NULL; + g_autoptr(GFile) appstream_file = NULL; + g_autoptr(GInputStream) xml_in = NULL; + + *result = NULL; + + xmls_dir = g_file_resolve_relative_path (root, "files/share/app-info/xmls"); + appstream_basename = g_strconcat (name, ".xml.gz", NULL); + appstream_file = g_file_get_child (xmls_dir, appstream_basename); + + xml_in = (GInputStream *) g_file_read (appstream_file, cancellable, NULL); + if (xml_in) + { + g_autoptr(FlatpakXml) appstream_root = NULL; + g_autoptr(FlatpakXml) xml_root = flatpak_xml_parse (xml_in, TRUE, + cancellable, error); + if (xml_root == NULL) + return FALSE; + + appstream_root = flatpak_appstream_xml_new (); + if (flatpak_appstream_xml_migrate (xml_root, appstream_root, + full_branch, name, metadata)) + { + g_autoptr(GBytes) xml_data = NULL; + gboolean success = FALSE; + + if (compress) + success = flatpak_appstream_xml_root_to_data (appstream_root, NULL, &xml_data, error); + else + success = flatpak_appstream_xml_root_to_data (appstream_root, &xml_data, NULL, error); + + if (!success) + return FALSE; + + *result = g_steal_pointer (&xml_data); + } + } + + return TRUE; +} + +typedef void (*IterateBundleIconsCallback) (const char *icon_size_name, + GBytes *png_data, + gpointer user_data); + +static gboolean +iterate_bundle_icons (GFile *root, + const char *name, + IterateBundleIconsCallback callback, + gpointer user_data, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFile) icons_dir = + g_file_resolve_relative_path (root, + "files/share/app-info/icons/flatpak"); + const char *icon_sizes[] = { "64x64", "128x128" }; + const char *icon_sizes_key[] = { "icon-64", "icon-128" }; + g_autofree char *icon_name = g_strconcat (name, ".png", NULL); + gint i; + + for (i = 0; i < G_N_ELEMENTS (icon_sizes); i++) + { + g_autoptr(GFile) size_dir = g_file_get_child (icons_dir, icon_sizes[i]); + g_autoptr(GFile) icon_file = g_file_get_child (size_dir, icon_name); + g_autoptr(GInputStream) png_in = NULL; + + png_in = (GInputStream *) g_file_read (icon_file, cancellable, NULL); + if (png_in != NULL) + { + g_autoptr(GBytes) png_data = flatpak_read_stream (png_in, FALSE, error); + if (png_data == NULL) + return FALSE; + + callback (icon_sizes_key[i], png_data, user_data); + } + } + + return TRUE; +} + +static void +add_icon_to_metadata (const char *icon_size_name, + GBytes *png_data, + gpointer user_data) +{ + GVariantBuilder *metadata_builder = user_data; + + g_variant_builder_add (metadata_builder, "{sv}", icon_size_name, + g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, + png_data, TRUE)); +} + +static gboolean build_bundle (OstreeRepo *repo, GFile *file, const char *name, const char *full_branch, const char *from_commit, @@ -117,12 +221,9 @@ build_bundle (OstreeRepo *repo, GFile *file, GVariantBuilder param_builder; g_autoptr(GKeyFile) keyfile = NULL; - g_autoptr(GFile) xmls_dir = NULL; + g_autoptr(GBytes) xml_data = NULL; g_autoptr(GFile) metadata_file = NULL; - g_autoptr(GFile) appstream_file = NULL; - g_autofree char *appstream_basename = NULL; g_autoptr(GInputStream) in = NULL; - g_autoptr(GInputStream) xml_in = NULL; g_autoptr(GFile) root = NULL; g_autofree char *commit_checksum = NULL; g_autoptr(GBytes) gpg_data = NULL; @@ -173,59 +274,19 @@ build_bundle (OstreeRepo *repo, GFile *file, g_variant_new_string (g_bytes_get_data (bytes, NULL))); } - xmls_dir = g_file_resolve_relative_path (root, "files/share/app-info/xmls"); - appstream_basename = g_strconcat (name, ".xml.gz", NULL); - appstream_file = g_file_get_child (xmls_dir, appstream_basename); + if (!get_bundle_appstream_data (root, full_branch, name, keyfile, TRUE, + &xml_data, cancellable, error)) + return FALSE; - xml_in = (GInputStream *) g_file_read (appstream_file, cancellable, NULL); - if (xml_in) + if (xml_data) { - g_autoptr(FlatpakXml) appstream_root = NULL; - g_autoptr(FlatpakXml) xml_root = flatpak_xml_parse (xml_in, TRUE, - cancellable, error); - if (xml_root == NULL) - return FALSE; - - appstream_root = flatpak_appstream_xml_new (); - if (flatpak_appstream_xml_migrate (xml_root, appstream_root, - full_branch, name, keyfile)) - { - g_autoptr(GBytes) xml_data = NULL; - int i; - g_autoptr(GFile) icons_dir = - g_file_resolve_relative_path (root, - "files/share/app-info/icons/flatpak"); - const char *icon_sizes[] = { "64x64", "128x128" }; - const char *icon_sizes_key[] = { "icon-64", "icon-128" }; - g_autofree char *icon_name = g_strconcat (name, ".png", NULL); - - if (!flatpak_appstream_xml_root_to_data (appstream_root, NULL, &xml_data, error)) - return FALSE; - - g_variant_builder_add (&metadata_builder, "{sv}", "appdata", - g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, - xml_data, TRUE)); - - for (i = 0; i < G_N_ELEMENTS (icon_sizes); i++) - { - g_autoptr(GFile) size_dir = g_file_get_child (icons_dir, icon_sizes[i]); - g_autoptr(GFile) icon_file = g_file_get_child (size_dir, icon_name); - g_autoptr(GInputStream) png_in = NULL; - - png_in = (GInputStream *) g_file_read (icon_file, cancellable, NULL); - if (png_in != NULL) - { - g_autoptr(GBytes) png_data = flatpak_read_stream (png_in, FALSE, error); - if (png_data == NULL) - return FALSE; - - g_variant_builder_add (&metadata_builder, "{sv}", icon_sizes_key[i], - g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, - png_data, TRUE)); - } - } - } + g_variant_builder_add (&metadata_builder, "{sv}", "appdata", + g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, + xml_data, TRUE)); + if (!iterate_bundle_icons (root, name, add_icon_to_metadata, + &metadata_builder, cancellable, error)) + return FALSE; } if (opt_repo_url) @@ -307,6 +368,20 @@ export_commit_to_archive (OstreeRepo *repo, return TRUE; } +static void +add_icon_to_annotations (const char *icon_size_name, + GBytes *png_data, + gpointer user_data) +{ + GHashTable *annotations = user_data; + g_autofree char *encoded = g_base64_encode (g_bytes_get_data (png_data, NULL), + g_bytes_get_size (png_data)); + + g_hash_table_replace (annotations, + g_strconcat ("org.freedesktop.appstream.", icon_size_name, NULL), + g_strconcat ("data:image/png;base64,", encoded, NULL)); +} + static gboolean build_oci (OstreeRepo *repo, GFile *dir, const char *name, const char *ref, @@ -329,6 +404,8 @@ build_oci (OstreeRepo *repo, GFile *dir, g_autoptr(FlatpakOciManifest) manifest = NULL; g_autoptr(FlatpakOciIndex) index = NULL; g_autoptr(GFile) metadata_file = NULL; + g_autoptr(GKeyFile) keyfile = NULL; + g_autoptr(GBytes) xml_data = NULL; guint64 installed_size = 0; GHashTable *annotations; gsize metadata_size; @@ -396,6 +473,14 @@ build_oci (OstreeRepo *repo, GFile *dir, if (g_file_load_contents (metadata_file, cancellable, &metadata_contents, &metadata_size, NULL, NULL) && g_utf8_validate (metadata_contents, -1, NULL)) { + keyfile = g_key_file_new (); + + if (!g_key_file_load_from_data (keyfile, + metadata_contents, + metadata_size, + G_KEY_FILE_NONE, error)) + return FALSE; + g_hash_table_replace (annotations, g_strdup ("org.flatpak.metadata"), g_steal_pointer (&metadata_contents)); @@ -412,6 +497,23 @@ build_oci (OstreeRepo *repo, GFile *dir, g_strdup ("org.flatpak.download-size"), g_strdup_printf ("%" G_GUINT64_FORMAT, layer_desc->size)); + if (!get_bundle_appstream_data (root, ref, name, keyfile, FALSE, + &xml_data, cancellable, error)) + return FALSE; + + if (xml_data) + { + gsize xml_data_len; + + g_hash_table_replace (annotations, + g_strdup ("org.freedesktop.appstream.appdata"), + g_bytes_unref_to_data (g_steal_pointer (&xml_data), &xml_data_len)); + + if (!iterate_bundle_icons (root, name, add_icon_to_annotations, + annotations, cancellable, error)) + return FALSE; + } + manifest_desc = flatpak_oci_registry_store_json (registry, FLATPAK_JSON (manifest), cancellable, error); if (manifest_desc == NULL) return FALSE; diff --git a/tests/test-oci.sh b/tests/test-oci.sh index 916e533c..df279199 100755 --- a/tests/test-oci.sh +++ b/tests/test-oci.sh @@ -44,6 +44,13 @@ for i in oci/registry/blobs/sha256/*; do done sha256sum -c sums +digest=$(grep sha256: oci/registry/index.json | sed s'@.*sha256:\([a-fA-F0-9]\+\).*@\1@') +manifest=oci/registry/blobs/sha256/$digest + +assert_has_file $manifest +assert_file_has_content $manifest "org.freedesktop.appstream.appdata.*<summary>Print a greeting</summary>" +assert_file_has_content $manifest "org.freedesktop.appstream.icon-64" + echo "ok export oci" ostree --repo=repo2 init --mode=archive-z2 |