[gnome-software/mwleeds/hardcoded-pwa-list: 3/6] epiphany: Refine appstream-provided web apps




commit 4b840ffa0f20a3d32f4b2a31be7b3819e2567959
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Wed Mar 9 15:22:57 2022 -0800

    epiphany: Refine appstream-provided web apps
    
    The epiphany plugin already adopts GsApp objects for web apps that are
    created by the appstream plugin in gs_plugin_adopt_app(). This commit
    makes it so that we refine those apps to make them display well on the
    app details pages.

 plugins/epiphany/gs-plugin-epiphany.c | 285 +++++++++++++++++++++++-----------
 1 file changed, 196 insertions(+), 89 deletions(-)
---
diff --git a/plugins/epiphany/gs-plugin-epiphany.c b/plugins/epiphany/gs-plugin-epiphany.c
index 6517f6b1b..b0f306e92 100644
--- a/plugins/epiphany/gs-plugin-epiphany.c
+++ b/plugins/epiphany/gs-plugin-epiphany.c
@@ -382,33 +382,59 @@ gs_plugin_epiphany_list_installed_apps_async (GsPlugin            *plugin,
 
 /* Run in @worker */
 static void
-set_license_from_hostname (GsApp      *app,
-                          const char *hostname)
+refine_app (GsApp      *app,
+           GUri       *uri,
+           const char *url)
 {
-       if (gs_app_get_license (app) != NULL)
-               return;
+       const char *hostname;
+       hostname = g_uri_get_host (uri);
 
-       if (hostname == NULL || *hostname == '\0')
-               return;
+       g_return_if_fail (GS_IS_APP (app));
+       g_return_if_fail (uri != NULL);
+       g_return_if_fail (url != NULL);
+
+       gs_app_set_origin (app, "gnome-web");
+       gs_app_set_origin_ui (app, _("GNOME Web"));
+
+       /* 1 is a special value meaning 0 */
+       gs_app_set_size_download (app, 1);
+
+       gs_app_set_permissions (app, GS_APP_PERMISSIONS_NETWORK);
+
+       if (gs_app_get_url (app, AS_URL_KIND_HOMEPAGE) == NULL)
+               gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, url);
+
+       /* Use the domain name (e.g. "discourse.gnome.org") as a fallback summary.
+        * FIXME: Fetch the summary from the site's webapp manifest.
+        */
+       if (gs_app_get_summary (app) == NULL) {
+               if (hostname != NULL && *hostname != '\0')
+                       gs_app_set_summary (app, GS_APP_QUALITY_LOWEST, hostname);
+               else
+                       gs_app_set_summary (app, GS_APP_QUALITY_LOWEST, url);
+       }
 
-       if (g_str_equal (hostname, "app.diagrams.net"))
-               gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "Apache-2.0");
-       else if (g_str_equal (hostname, "pinafore.social"))
-               gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "AGPL-3.0-only");
-       else if (g_str_equal (hostname, "snapdrop.net"))
-               gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-3.0-only");
-       else if (g_str_equal (hostname, "stackedit.io"))
-               gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "Apache-2.0");
-       else if (g_str_equal (hostname, "squoosh.app"))
-               gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "Apache-2.0");
-       else if (g_str_equal (hostname, "excalidraw.com"))
-               gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "MIT");
-       else if (g_str_equal (hostname, "discourse.gnome.org"))
-               gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-2.0-or-later");
-       else if (g_str_equal (hostname, "discourse.flathub.org"))
-               gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-2.0-or-later");
-       else if (g_str_equal (hostname, "devdocs.io"))
-               gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "MPL-2.0");
+       /* Hard-code these as it's hard to get them programmatically */
+       if (gs_app_get_license (app) == NULL && hostname != NULL) {
+               if (g_str_equal (hostname, "app.diagrams.net"))
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "Apache-2.0");
+               else if (g_str_equal (hostname, "pinafore.social"))
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "AGPL-3.0-only");
+               else if (g_str_equal (hostname, "snapdrop.net"))
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-3.0-only");
+               else if (g_str_equal (hostname, "stackedit.io"))
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "Apache-2.0");
+               else if (g_str_equal (hostname, "squoosh.app"))
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "Apache-2.0");
+               else if (g_str_equal (hostname, "excalidraw.com"))
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "MIT");
+               else if (g_str_equal (hostname, "discourse.gnome.org"))
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-2.0-or-later");
+               else if (g_str_equal (hostname, "discourse.flathub.org"))
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-2.0-or-later");
+               else if (g_str_equal (hostname, "devdocs.io"))
+                       gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "MPL-2.0");
+       }
 }
 
 /* Run in @worker */
@@ -416,24 +442,21 @@ static GsApp *
 gs_epiphany_create_app (GsPluginEpiphany *self,
                        const char       *id)
 {
-       g_autoptr(GsApp) app_cached = NULL;
-       g_autoptr(GsApp) tmp_app = NULL;
+       g_autoptr(GsApp) app = NULL;
 
        assert_in_worker (self);
 
-       tmp_app = gs_app_new (id);
-       gs_app_set_management_plugin (tmp_app, GS_PLUGIN (self));
-       gs_app_set_origin (tmp_app, "gnome-web");
-       gs_app_set_origin_ui (tmp_app, _("GNOME Web"));
-       gs_app_set_kind (tmp_app, AS_COMPONENT_KIND_WEB_APP);
-       gs_app_set_scope (tmp_app, AS_COMPONENT_SCOPE_USER);
+       app = gs_plugin_cache_lookup (GS_PLUGIN (self), id);
+       if (app != NULL)
+               return g_steal_pointer (&app);
 
-       app_cached = gs_plugin_cache_lookup (GS_PLUGIN (self), id);
-       if (app_cached != NULL)
-               return g_steal_pointer (&app_cached);
+       app = gs_app_new (id);
+       gs_app_set_management_plugin (app, GS_PLUGIN (self));
+       gs_app_set_kind (app, AS_COMPONENT_KIND_WEB_APP);
+       gs_app_set_scope (app, AS_COMPONENT_SCOPE_USER);
 
-       gs_plugin_cache_add (GS_PLUGIN (self), id, tmp_app);
-       return g_steal_pointer (&tmp_app);
+       gs_plugin_cache_add (GS_PLUGIN (self), id, app);
+       return g_steal_pointer (&app);
 }
 
 /* Run in @worker */
@@ -472,7 +495,6 @@ list_installed_apps_thread_cb (GTask        *task,
                const gchar *url = NULL;
                g_autofree char *icon_path = NULL;
                const gchar *exec;
-               const gchar *host;
                int argc;
                g_auto(GStrv) argv = NULL;
                guint64 install_date = 0;
@@ -507,7 +529,8 @@ list_installed_apps_thread_cb (GTask        *task,
                        url = argv[argc - 1];
                }
                if (!url || !(uri = g_uri_parse (url, G_URI_FLAGS_NONE, NULL))) {
-                       g_warning ("Failed to parse URL for web app %s", desktop_file_id);
+                       g_warning ("Failed to parse URL for web app %s: %s",
+                                  desktop_file_id, url ? url : "(null)");
                        continue;
                }
 
@@ -530,16 +553,9 @@ list_installed_apps_thread_cb (GTask        *task,
                app = gs_epiphany_create_app (self, desktop_file_id);
                gs_app_set_state (app, GS_APP_STATE_INSTALLED);
                gs_app_set_name (app, GS_APP_QUALITY_NORMAL, name);
-               gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, url);
-               gs_app_set_permissions (app, GS_APP_PERMISSIONS_NETWORK);
+               gs_app_set_launchable (app, AS_LAUNCHABLE_KIND_URL, url);
 
-               /* Use the domain name as a fallback summary.
-                * FIXME: Fetch the summary from the site's webapp manifest.
-                */
-               host = g_uri_get_host (uri);
-               gs_app_set_summary (app, GS_APP_QUALITY_LOWEST, host ? host : url);
-
-               set_license_from_hostname (app, host);
+               refine_app (app, uri, url);
 
                if (icon_path) {
                        g_autoptr(GFile) icon_file = g_file_new_for_path (icon_path);
@@ -607,9 +623,132 @@ gs_plugin_epiphany_list_installed_apps_finish (GsPlugin      *plugin,
                                                GAsyncResult  *result,
                                                GError       **error)
 {
+       g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == 
gs_plugin_epiphany_list_installed_apps_async, FALSE);
        return g_task_propagate_pointer (G_TASK (result), error);
 }
 
+static void refine_thread_cb (GTask        *task,
+                              gpointer      source_object,
+                              gpointer      task_data,
+                              GCancellable *cancellable);
+
+static void
+gs_plugin_epiphany_refine_async (GsPlugin            *plugin,
+                                 GsAppList           *list,
+                                 GsPluginRefineFlags  flags,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       g_autoptr(GTask) task = NULL;
+
+       task = gs_plugin_refine_data_new_task (plugin, list, flags, cancellable, callback, user_data);
+       g_task_set_source_tag (task, gs_plugin_epiphany_refine_async);
+
+       /* Queue a job for the refine. */
+       gs_worker_thread_queue (self->worker, G_PRIORITY_DEFAULT,
+                               refine_thread_cb, g_steal_pointer (&task));
+}
+
+/* Run in @worker. */
+static void
+refine_thread_cb (GTask        *task,
+                  gpointer      source_object,
+                  gpointer      task_data,
+                  GCancellable *cancellable)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (source_object);
+       GsPluginRefineData *data = task_data;
+       GsAppList *list = data->list;
+       GsPluginRefineFlags flags = data->flags;
+       g_autoptr(GsAppList) app_list = NULL;
+       g_autoptr(GError) local_error = NULL;
+
+       assert_in_worker (self);
+
+       for (guint i = 0; i < gs_app_list_length (list); i++) {
+               GsApp *app = gs_app_list_index (list, i);
+               const char *url;
+               g_autoptr(GUri) uri = NULL;
+
+               /* not us */
+               if (gs_app_get_kind (app) != AS_COMPONENT_KIND_WEB_APP ||
+                   gs_app_get_bundle_kind (app) == AS_BUNDLE_KIND_PACKAGE)
+                       continue;
+
+               url = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_URL);
+               if (url == NULL || *url == '\0') {
+                       /* A launchable URL is required by the AppStream spec */
+                       g_warning ("Web app %s missing launchable url", gs_app_get_id (app));
+                       continue;
+               } else if (!(uri = g_uri_parse (url, G_URI_FLAGS_NONE, NULL))) {
+                       g_warning ("Failed to parse URL for web app %s: %s", gs_app_get_id (app), url);
+                       continue;
+               }
+
+               refine_app (app, uri, url);
+       }
+
+       /* success */
+       g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gs_plugin_epiphany_refine_finish (GsPlugin      *plugin,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+       g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gs_plugin_epiphany_refine_async, 
FALSE);
+       return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static GVariant *
+get_serialized_icon (GsApp *app,
+                    GIcon *icon)
+{
+       g_autofree char *icon_path = NULL;
+       g_autoptr(GInputStream) stream = NULL;
+       g_autoptr(GBytes) bytes = NULL;
+       g_autoptr(GIcon) bytes_icon = NULL;
+       g_autoptr(GVariant) icon_v = NULL;
+
+       /* Note: GsRemoteIcon will work on this GFileIcon code path.
+        * The icons plugin should have called
+        * gs_app_ensure_icons_downloaded() for us.
+        */
+       if (!G_IS_FILE_ICON (icon))
+               return NULL;
+
+       icon_path = g_file_get_path (g_file_icon_get_file (G_FILE_ICON (icon)));
+       if (!g_str_has_suffix (icon_path, ".png") &&
+           !g_str_has_suffix (icon_path, ".svg") &&
+           !g_str_has_suffix (icon_path, ".jpeg") &&
+           !g_str_has_suffix (icon_path, ".jpg")) {
+               g_warning ("Icon for app %s has unsupported file extension: %s",
+                          gs_app_get_id (app), icon_path);
+               return NULL;
+       }
+
+       /* Serialize the icon as a #GBytesIcon since that's
+        * what the dynamic launcher portal requires.
+        */
+       stream = g_loadable_icon_load (G_LOADABLE_ICON (icon), 0, NULL, NULL, NULL);
+
+       /* Icons are usually smaller than 1 MiB. Set a 10 MiB
+        * limit so we can't use a huge amount of memory or hit
+        * the D-Bus message size limit
+        */
+       if (stream)
+               bytes = g_input_stream_read_bytes (stream, 10485760 /* 10 MiB */, NULL, NULL);
+       if (bytes)
+               bytes_icon = g_bytes_icon_new (bytes);
+       if (bytes_icon)
+               icon_v = g_icon_serialize (bytes_icon);
+
+       return g_steal_pointer (&icon_v);
+}
+
 gboolean
 gs_plugin_app_install (GsPlugin      *plugin,
                       GsApp         *app,
@@ -620,10 +759,10 @@ gs_plugin_app_install (GsPlugin      *plugin,
        const char *url;
        const char *name;
        g_autofree char *token = NULL;
-       GPtrArray *icons;
        g_autoptr(GVariant) token_v = NULL;
        g_autoptr(GVariant) icon_v = NULL;
        GVariantBuilder opt_builder;
+       const int icon_sizes[] = {512, 192, 128, 1};
 
        if (!gs_app_has_management_plugin (app, plugin))
                return TRUE;
@@ -642,46 +781,12 @@ gs_plugin_app_install (GsPlugin      *plugin,
                             gs_app_get_id (app));
                return FALSE;
        }
-       icons = gs_app_get_icons (app);
-       for (guint i = 0; icons != NULL && i < icons->len; i++) {
-               GIcon *icon = g_ptr_array_index (icons, i);
-               /* Note: GsRemoteIcon will work on this GFileIcon code path.
-                * The icons plugin should have called
-                * gs_app_ensure_icons_downloaded() for us
-                */
-               if (G_IS_FILE_ICON (icon)) {
-                       g_autofree char *icon_path = NULL;
-                       g_autoptr(GInputStream) stream = NULL;
-                       g_autoptr(GBytes) bytes = NULL;
-                       g_autoptr(GIcon) bytes_icon = NULL;
-
-                       icon_path = g_file_get_path (g_file_icon_get_file (G_FILE_ICON (icon)));
-                       if (!g_str_has_suffix (icon_path, ".png") &&
-                           !g_str_has_suffix (icon_path, ".svg") &&
-                           !g_str_has_suffix (icon_path, ".jpeg") &&
-                           !g_str_has_suffix (icon_path, ".jpg")) {
-                               g_warning ("Icon for app %s has unsupported file extension: %s",
-                                          gs_app_get_id (app), icon_path);
-                               continue;
-                       }
-
-                       /* Serialize the icon as a #GBytesIcon since that's
-                        * what the dynamic launcher portal requires.
-                        */
-                       stream = g_loadable_icon_load (G_LOADABLE_ICON (icon), 0, NULL, NULL, NULL);
-                       /* Icons are usually smaller than 1 MiB. Set a 10 MiB
-                        * limit so we can't use a huge amount of memory or hit
-                        * the D-Bus message size limit
-                        */
-                       if (stream)
-                               bytes = g_input_stream_read_bytes (stream, 10485760 /* 10 MiB */, NULL, NULL);
-                       if (bytes)
-                               bytes_icon = g_bytes_icon_new (bytes);
-                       if (bytes_icon)
-                               icon_v = g_icon_serialize (bytes_icon);
-                       if (icon_v)
-                               break;
-               }
+       for (guint i = 0; i < G_N_ELEMENTS (icon_sizes); i++) {
+               GIcon *icon = gs_app_get_icon_for_size (app, icon_sizes[i], 1, NULL);
+               if (icon != NULL)
+                       icon_v = get_serialized_icon (app, icon);
+               if (icon_v != NULL)
+                       break;
        }
        if (icon_v == NULL) {
                g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
@@ -774,6 +879,8 @@ gs_plugin_epiphany_class_init (GsPluginEpiphanyClass *klass)
        plugin_class->setup_finish = gs_plugin_epiphany_setup_finish;
        plugin_class->shutdown_async = gs_plugin_epiphany_shutdown_async;
        plugin_class->shutdown_finish = gs_plugin_epiphany_shutdown_finish;
+       plugin_class->refine_async = gs_plugin_epiphany_refine_async;
+       plugin_class->refine_finish = gs_plugin_epiphany_refine_finish;
        plugin_class->list_installed_apps_async = gs_plugin_epiphany_list_installed_apps_async;
        plugin_class->list_installed_apps_finish = gs_plugin_epiphany_list_installed_apps_finish;
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]