[gnome-software/mwleeds/hardcoded-pwa-list: 8/11] epiphany: Refine appstream-provided web apps
- From: Phaedrus Leeds <mwleeds src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/mwleeds/hardcoded-pwa-list: 8/11] epiphany: Refine appstream-provided web apps
- Date: Fri, 11 Mar 2022 01:46:57 +0000 (UTC)
commit 5c18a4ce91bf29a13e55f12d70f55e469b917e64
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]