From: Joey Riches <josephriches@gmail.com>
Date: Sun, 15 Jun 2025 19:22:44 +0100
Subject: app: Port from appstream-glib to appstream

From the horse's mouth:
"WARNING: appstream-glib is heavy maintenance mode, use appstream
instead"

Additionally, appstream-glib no longer conforms fully to the
appstream specification.

Some care is taken to taken to support both libappstream 1.0 as well
as libappstream 0.16.x to support stable distros.

(cherry picked from commit 8277817b6250632a5b2499f5fb650f393821b780)

Origin: upstream, after 3.1.2
---
 .gitlab-ci.yml                 |   2 +-
 INSTALL                        |   2 +-
 app/config/meson.build         |   2 +-
 app/core/gimpextension.c       | 208 +++++++++++++++++++++++++++--------------
 app/core/meson.build           |   2 +-
 app/dialogs/meson.build        |   2 +-
 app/file-data/file-data-gex.c  |  26 ++----
 app/file-data/meson.build      |   2 +-
 app/tests/meson.build          |   2 +-
 build/windows/all-deps-uni.txt |   2 +-
 meson.build                    |   6 +-
 11 files changed, 157 insertions(+), 99 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e3019b4..5bf27f3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -180,7 +180,7 @@ deps-debian:
             graphviz-dev
             iso-codes
             libaa1-dev
-            libappstream-glib-dev
+            libappstream-dev
             libarchive-dev
             libbz2-dev
             libcfitsio-dev
diff --git a/INSTALL b/INSTALL
index 8a7b272..e7c77d3 100644
--- a/INSTALL
+++ b/INSTALL
@@ -244,7 +244,7 @@ help in that regard:
 
      Package Name         Version
 
-     appstream-glib       0.7.7
+     appstream            0.16.2
      ATK                  2.4.0
      babl                 0.1.114
      cairo                1.14.0
diff --git a/app/config/meson.build b/app/config/meson.build
index a739ad6..8a88818 100644
--- a/app/config/meson.build
+++ b/app/config/meson.build
@@ -53,7 +53,7 @@ test('app-config',
     [ 'test-config.c', app_debug_files, ],
 
     dependencies: [
-      appstream_glib,
+      appstream,
       libapp_dep,
     ],
     link_with: [
diff --git a/app/core/gimpextension.c b/app/core/gimpextension.c
index ae2c83d..7038f06 100644
--- a/app/core/gimpextension.c
+++ b/app/core/gimpextension.c
@@ -20,7 +20,8 @@
 
 #include "config.h"
 
-#include <appstream-glib.h>
+#include <appstream.h>
+#include <gdk-pixbuf-2.0/gdk-pixbuf/gdk-pixbuf.h>
 #include <gegl.h>
 
 #include "libgimpbase/gimpbase.h"
@@ -45,11 +46,11 @@ enum
 
 struct _GimpExtensionPrivate
 {
-  gchar    *path;
+  gchar       *path;
 
-  AsApp    *app;
-  gboolean  writable;
-  gboolean  running;
+  AsComponent *app;
+  gboolean     writable;
+  gboolean     running;
 
   /* Extension metadata: directories. */
   GList    *brush_paths;
@@ -224,19 +225,16 @@ gimp_extension_get_name (GimpExtension *extension)
 {
   g_return_val_if_fail (extension->p->app != NULL, NULL);
 
-  return as_app_get_name (extension->p->app, g_getenv ("LANGUAGE")) ?
-    as_app_get_name (extension->p->app, g_getenv ("LANGUAGE")) :
-    as_app_get_name (extension->p->app, NULL);
+  return as_component_get_name (extension->p->app);
 }
 
 const gchar *
 gimp_extension_get_comment (GimpExtension *extension)
 {
+
   g_return_val_if_fail (extension->p->app != NULL, NULL);
 
-  return as_app_get_comment (extension->p->app, g_getenv ("LANGUAGE")) ?
-    as_app_get_comment (extension->p->app, g_getenv ("LANGUAGE")) :
-    as_app_get_comment (extension->p->app, NULL);
+  return as_component_get_summary (extension->p->app);
 }
 
 const gchar *
@@ -244,9 +242,7 @@ gimp_extension_get_description (GimpExtension *extension)
 {
   g_return_val_if_fail (extension->p->app != NULL, NULL);
 
-  return as_app_get_description (extension->p->app, g_getenv ("LANGUAGE")) ?
-    as_app_get_description (extension->p->app, g_getenv ("LANGUAGE")) :
-    as_app_get_description (extension->p->app, NULL);
+  return as_component_get_description (extension->p->app);
 }
 
 GdkPixbuf *
@@ -255,30 +251,73 @@ gimp_extension_get_screenshot (GimpExtension  *extension,
                                gint            height,
                                const gchar   **caption)
 {
-  GdkPixbuf    *pixbuf = NULL;
-  AsScreenshot *screenshot;
+  GdkPixbuf       *pixbuf       = NULL;
+  AsScreenshot    *screenshot   = NULL;
+  const GPtrArray *screenshots;
 
   g_return_val_if_fail (extension->p->app != NULL, NULL);
 
-  screenshot = as_app_get_screenshot_default (extension->p->app);
+#if AS_CHECK_VERSION(1, 0, 0)
+  screenshots = as_component_get_screenshots_all (extension->p->app);
+#else
+  screenshots = as_component_get_screenshots (extension->p->app);
+#endif
+
+  if (screenshots == NULL || screenshots->len == 0)
+    {
+      return pixbuf;
+    }
+
+  /* Look for the screenshot marked as default */
+  for (guint i = 0; i < screenshots->len; i++)
+    {
+      AsScreenshot *ss = g_ptr_array_index (screenshots, i);
+      if (as_screenshot_get_kind (ss) == AS_SCREENSHOT_KIND_DEFAULT)
+        {
+          screenshot = ss;
+          break;
+        }
+    }
+
+  if (screenshot == NULL)
+    {
+      g_printerr (_("Invalid AppStream metadata: Failed to find default screenshot for: \"%s\""), as_component_get_id (extension->p->app));
+    }
+
   if (screenshot)
     {
-      AsImage *image;
+      AsImage          *image   = NULL;
+      GFile            *file    = NULL;
+      GFileInputStream *istream = NULL;
+      GError           *error   = NULL;
+
+#if AS_CHECK_VERSION(1, 0, 0)
+      image = as_screenshot_get_image (screenshot, width, height, 1);
+#else
+      image = as_screenshot_get_image (screenshot, width, height);
+#endif
+
+      file = g_file_new_for_uri (as_image_get_url (image));
+      istream = g_file_read (file, NULL, &error);
+      if (istream != NULL)
+        {
+          pixbuf = gdk_pixbuf_new_from_stream (G_INPUT_STREAM (istream), NULL, &error);
+        }
 
-      image = as_screenshot_get_image_for_locale (screenshot, g_getenv ("LANGUAGE"), width, height);
-      if (! image)
-        image = as_screenshot_get_image_for_locale (screenshot, NULL, width, height);
+      if (error != NULL)
+        {
+          g_printerr (_("Invalid AppStream metadata: Error loading image: \"%s\""), error->message);
+        }
 
-      pixbuf = as_image_get_pixbuf (image);
       if (pixbuf)
         {
           g_object_ref (pixbuf);
         }
       else
         {
-          GFile            *file;
-          GFileInputStream *istream;
-          GError           *error = NULL;
+          GFile            *file    = NULL;
+          GFileInputStream *istream = NULL;
+          GError           *error   = NULL;
 
           file = g_file_new_for_uri (as_image_get_url (image));
           istream = g_file_read (file, NULL, &error);
@@ -298,9 +337,7 @@ gimp_extension_get_screenshot (GimpExtension  *extension,
 
       if (caption)
         {
-          *caption = as_screenshot_get_caption (screenshot, g_getenv ("LANGUAGE"));
-          if (*caption == NULL)
-            *caption = as_screenshot_get_caption (screenshot, NULL);
+          *caption = as_screenshot_get_caption (screenshot);
         }
     }
 
@@ -331,14 +368,26 @@ gboolean
 gimp_extension_load (GimpExtension  *extension,
                      GError        **error)
 {
-  AsApp     *app;
-  GPtrArray *extends;
-  GPtrArray *requires;
-  AsRelease *release;
-  gchar     *appdata_name;
-  gchar     *path;
-  gboolean   success     = FALSE;
-  gboolean   has_require = FALSE;
+  AsComponent    *app;
+#if AS_CHECK_VERSION(1, 0, 0)
+  AsComponentBox *components   = NULL;
+#else
+  GPtrArray      *components   = NULL;
+#endif
+  AsMetadata     *metadata;
+  GPtrArray      *extends;
+  GPtrArray      *relations;
+  AsRelease      *release      = NULL;
+#if AS_CHECK_VERSION(1, 0, 0)
+  AsReleaseList  *rlist        = NULL;
+#else
+  GPtrArray      *rlist        = NULL;
+#endif
+  gchar          *appdata_name;
+  gchar          *path;
+  GFile          *file;
+  gboolean        success      = FALSE;
+  gboolean        has_require  = FALSE;
 
   g_clear_object (&extension->p->app);
 
@@ -350,12 +399,23 @@ gimp_extension_load (GimpExtension  *extension,
   path = g_build_filename (extension->p->path, appdata_name, NULL);
   g_free (appdata_name);
 
-  app = as_app_new ();
-  success = as_app_parse_file (app, path,
-                               AS_APP_PARSE_FLAG_USE_HEURISTICS,
-                               error);
+  file = g_file_new_for_path (path);
+
+  metadata = as_metadata_new ();
+  success = as_metadata_parse_file (metadata, file, AS_FORMAT_KIND_XML, error);
+
+#if AS_CHECK_VERSION(1, 0, 0)
+  components = as_metadata_get_components (metadata);
+  app = as_component_box_index (components, 0);
+#else
+  components = as_metadata_get_components (metadata);
+  app = g_ptr_array_index (components, 0);
+#endif
+
+  g_free (file);
   g_free (path);
-  if (success && as_app_get_kind (app) != AS_APP_KIND_ADDON)
+
+  if (success && as_component_get_kind (app) != AS_COMPONENT_KIND_ADDON)
     {
       /* Properly setting the type will allow extensions to be
        * distributed appropriately through other means.
@@ -364,11 +424,11 @@ gimp_extension_load (GimpExtension  *extension,
         *error = g_error_new (GIMP_EXTENSION_ERROR,
                               GIMP_EXTENSION_BAD_APPDATA,
                               _("Extension AppData must be of type \"addon\", found \"%s\" instead."),
-                              as_app_kind_to_string (as_app_get_kind (app)));
+                              as_component_kind_to_string (as_component_get_kind (app)));
       success = FALSE;
     }
 
-  extends = as_app_get_extends (app);
+  extends = as_component_get_extends (app);
   if (success &&
       ! g_ptr_array_find_with_equal_func (extends, "org.gimp.GIMP",
                                           g_str_equal, NULL))
@@ -384,7 +444,7 @@ gimp_extension_load (GimpExtension  *extension,
     }
 
   if (success &&
-      g_strcmp0 (as_app_get_id (app),
+      g_strcmp0 (as_component_get_id (app),
                  gimp_object_get_name (extension)) != 0)
     {
       /* Extension IDs will be unique and we want therefore the
@@ -394,12 +454,25 @@ gimp_extension_load (GimpExtension  *extension,
         *error = g_error_new (GIMP_EXTENSION_ERROR,
                               GIMP_EXTENSION_FAILED,
                               _("Extension AppData id (\"%s\") and directory (\"%s\") must be the same."),
-                              as_app_get_id (app), gimp_object_get_name (extension));
+                              as_component_get_id (app), gimp_object_get_name (extension));
       success = FALSE;
     }
 
-  release = as_app_get_release_default (app);
-  if (success && (! release || ! as_release_get_version (release)))
+#if AS_CHECK_VERSION(1, 0, 0)
+  rlist = as_component_get_releases_plain (app);
+  if (rlist != NULL && !as_release_list_is_empty (rlist))
+    {
+      release = as_release_list_index (rlist, 0);
+    }
+#else
+  rlist = as_component_get_releases (app);
+  if (rlist != NULL && rlist->len > 0)
+    {
+      release = g_ptr_array_index (rlist, 0);
+    }
+#endif
+
+  if (success && (!release || ! as_release_get_version (release)))
     {
       /* We don't need the detail, just to know that the extension has a
        * release tag with a version. This is very important since it is
@@ -412,39 +485,30 @@ gimp_extension_load (GimpExtension  *extension,
       success = FALSE;
     }
 
-  requires = as_app_get_requires (app);
-  if (success && requires)
+  relations = as_component_get_requires (app);
+  if (success && relations != NULL)
     {
-      gint i;
-
-      /* An extension could set requirements, in particular a range of
-       * supported version of GIMP, but also other extensions.
-       */
-
-      for (i = 0; i < requires->len; i++)
+      for (guint i = 0; i < relations->len; i++)
         {
-          AsRequire *require = g_ptr_array_index (requires, i);
+          AsRelation *relation = g_ptr_array_index(relations, i);
 
-          if (as_require_get_kind (require) == AS_REQUIRE_KIND_ID &&
-              g_strcmp0 (as_require_get_value (require), "org.gimp.GIMP") == 0)
+          if (as_relation_get_item_kind(relation) == AS_RELATION_ITEM_KIND_ID &&
+              g_strcmp0(as_relation_get_value_str(relation), "org.gimp.GIMP") == 0)
             {
               has_require = TRUE;
-              if (! as_require_version_compare (require, GIMP_VERSION, error))
-                {
+
+              if (!as_relation_version_compare(relation, GIMP_VERSION, error)) {
                   success = FALSE;
                   break;
-                }
+              }
             }
           else if (error && *error == NULL)
             {
-              /* Right now we only support requirement relative to GIMP
-               * version.
-               */
-              *error = g_error_new (GIMP_EXTENSION_ERROR,
-                                    GIMP_EXTENSION_FAILED,
-                                    _("Unsupported <requires> \"%s\" (type %s)."),
-                                    as_require_get_value (require),
-                                    as_require_kind_to_string (as_require_get_kind (require)));
+              *error = g_error_new(GIMP_EXTENSION_ERROR,
+                                  GIMP_EXTENSION_FAILED,
+                                  _("Unsupported <relation> \"%s\" (type %s)."),
+                                  as_relation_get_value_str(relation),
+                                  as_relation_item_kind_to_string(as_relation_get_item_kind(relation)));
               success = FALSE;
               break;
             }
@@ -480,7 +544,7 @@ gimp_extension_run (GimpExtension  *extension,
   g_return_val_if_fail (error && *error == NULL, FALSE);
 
   gimp_extension_clean (extension);
-  metadata = as_app_get_metadata (extension->p->app);
+  metadata = as_component_get_custom (extension->p->app);
 
   value = g_hash_table_lookup (metadata, "GIMP::brush-path");
   extension->p->brush_paths = gimp_extension_validate_paths (extension,
@@ -738,7 +802,7 @@ gimp_extension_validate_paths (GimpExtension  *extension,
 
   for (i = 0; patharray[i]; i++)
     {
-      /* Note: appstream-glib is supposed to return everything as UTF-8,
+      /* Note: appstream is supposed to return everything as UTF-8,
        * so we should not have to bother about this. */
       gchar    *path;
       GFile    *file;
diff --git a/app/core/meson.build b/app/core/meson.build
index a7430da..40db629 100644
--- a/app/core/meson.build
+++ b/app/core/meson.build
@@ -275,7 +275,7 @@ libappcore = static_library('appcore',
     gdk_pixbuf,
     libmypaint,
     gexiv2,
-    appstream_glib,
+    appstream,
     math,
     dl,
     libunwind,
diff --git a/app/dialogs/meson.build b/app/dialogs/meson.build
index 2555e12..672cd93 100644
--- a/app/dialogs/meson.build
+++ b/app/dialogs/meson.build
@@ -76,7 +76,7 @@ libappdialogs = static_library('appdialogs',
   include_directories: [ rootInclude, rootAppInclude, ],
   c_args: '-DG_LOG_DOMAIN="Gimp-Dialogs"',
   dependencies: [
-    appstream_glib,
+    appstream,
     gegl,
     gexiv2,
     gtk3,
diff --git a/app/file-data/file-data-gex.c b/app/file-data/file-data-gex.c
index b301de6..dc33c87 100644
--- a/app/file-data/file-data-gex.c
+++ b/app/file-data/file-data-gex.c
@@ -18,7 +18,7 @@
 
 #include "config.h"
 
-#include <appstream-glib.h>
+#include <appstream.h>
 #include <archive.h>
 #include <archive_entry.h>
 #include <cairo.h>
@@ -76,7 +76,7 @@ static gboolean   file_gex_validate_path  (const gchar     *path,
                                            gchar          **plugin_id,
                                            GError         **error);
 static gboolean   file_gex_validate       (GFile           *file,
-                                           AsApp          **appstream,
+                                           AsComponent    **appstream,
                                            GError         **error);
 static gchar *    file_gex_decompress     (GFile           *file,
                                            gchar           *plugin_id,
@@ -205,9 +205,9 @@ file_gex_validate_path (const gchar  *path,
  *          with @error set.
  */
 static gboolean
-file_gex_validate (GFile   *file,
-                   AsApp  **appstream,
-                   GError **error)
+file_gex_validate (GFile        *file,
+                   AsComponent  **appstream,
+                   GError       **error)
 {
   GInputStream *input;
   gboolean      success = FALSE;
@@ -285,20 +285,14 @@ file_gex_validate (GFile   *file,
                 {
                   if (appdata)
                     {
-                      *appstream = as_app_new ();
+                      *appstream = as_component_new ();
 
-                      if (! as_app_parse_data (*appstream, appdata,
-                                               AS_APP_PARSE_FLAG_USE_HEURISTICS,
-                                               error))
-                        {
-                          g_clear_object (appstream);
-                        }
-                      else if (g_strcmp0 (as_app_get_id (*appstream), plugin_id) != 0)
+                      if (g_strcmp0 (as_component_get_id (*appstream), plugin_id) != 0)
                         {
                           *error = g_error_new (GIMP_EXTENSION_ERROR, GIMP_EXTENSION_FAILED,
                                                 _("GIMP extension '%s' directory (%s) different from AppStream id: %s"),
                                                 gimp_file_get_utf8_name (file),
-                                                plugin_id, as_app_get_id (*appstream));
+                                                plugin_id, as_component_get_id (*appstream));
                           g_clear_object (appstream);
                         }
                     }
@@ -515,7 +509,7 @@ file_gex_load_invoker (GimpProcedure         *procedure,
   GimpValueArray *return_vals;
   GFile          *file;
   gchar          *ext_dir = NULL;
-  AsApp          *appdata = NULL;
+  AsComponent    *appdata = NULL;
   gboolean        success = FALSE;
 
   gimp_set_busy (gimp);
@@ -524,7 +518,7 @@ file_gex_load_invoker (GimpProcedure         *procedure,
 
   success = file_gex_validate (file, &appdata, error);
   if (success)
-    ext_dir = file_gex_decompress (file, (gchar *) as_app_get_id (appdata),
+    ext_dir = file_gex_decompress (file, (gchar *) as_component_get_id (appdata),
                                    error);
 
   if (ext_dir)
diff --git a/app/file-data/meson.build b/app/file-data/meson.build
index 8a21f3c..4235b58 100644
--- a/app/file-data/meson.build
+++ b/app/file-data/meson.build
@@ -12,7 +12,7 @@ libappfiledata = static_library('appfiledata',
   include_directories: [ rootInclude, rootAppInclude, ],
   c_args: '-DG_LOG_DOMAIN="Gimp-File-Data"',
   dependencies: [
-    appstream_glib,
+    appstream,
     cairo,
     gdk_pixbuf,
     gegl,
diff --git a/app/tests/meson.build b/app/tests/meson.build
index 8f919af..18e6c43 100644
--- a/app/tests/meson.build
+++ b/app/tests/meson.build
@@ -52,7 +52,7 @@ foreach test_name : app_tests
   test_exe = executable(test_name,
     'test-@0@.c'.format(test_name),
     'tests.c',
-    dependencies: [ libapp_dep, appstream_glib ],
+    dependencies: [ libapp_dep, appstream ],
     link_with: apptests_links,
   )
 
diff --git a/build/windows/all-deps-uni.txt b/build/windows/all-deps-uni.txt
index 85e6520..0346161 100644
--- a/build/windows/all-deps-uni.txt
+++ b/build/windows/all-deps-uni.txt
@@ -9,7 +9,7 @@ ${MINGW_PACKAGE_PREFIX}-toolchain
 ${MINGW_PACKAGE_PREFIX}-vala \
 
 ${MINGW_PACKAGE_PREFIX}-aalib \
-${MINGW_PACKAGE_PREFIX}-appstream-glib \
+${MINGW_PACKAGE_PREFIX}-appstream \
 ${MINGW_PACKAGE_PREFIX}-atk \
 ${MINGW_PACKAGE_PREFIX}-cairo \
 ${MINGW_PACKAGE_PREFIX}-cfitsio \
diff --git a/meson.build b/meson.build
index c069c68..eedac5c 100644
--- a/meson.build
+++ b/meson.build
@@ -594,8 +594,8 @@ conf.set('HAVE_LIBSOCKET', libsocket.found())
 ################################################################################
 # Check for extension support
 
-appstream_glib_minver = '0.7.7'
-appstream_glib = dependency('appstream-glib', version: '>='+appstream_glib_minver)
+appstream_minver = '0.16.2'
+appstream = dependency('appstream', version: '>='+appstream_minver)
 
 libarchive = dependency('libarchive')
 
@@ -1773,7 +1773,7 @@ install_conf = configuration_data()
 install_conf.set('GIMP_APP_VERSION', gimp_app_version)
 install_conf.set('GIMP_PKGCONFIG_VERSION', gimp_version)
 install_conf.set('GIMP_VERSION', gimp_version)
-install_conf.set('APPSTREAM_GLIB_REQUIRED_VERSION', appstream_glib_minver)
+install_conf.set('APPSTREAM_REQUIRED_VERSION', appstream_minver)
 install_conf.set('ATK_REQUIRED_VERSION',          atk_minver)
 install_conf.set('BABL_REQUIRED_VERSION',         babl_minver)
 install_conf.set('CAIRO_PDF_REQUIRED_VERSION',    cairopdf_minver)
