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




commit e9bdb768205cbf2d2305ccc11fceef4ef362e0af
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Thu Mar 24 15:26:40 2022 -0700

    epiphany: Refine appstream-provided web apps
    
    This commit significantly reworks the epiphany plugin, so that rather
    than only handling web apps that have been installed via Epiphany, we
    handle web apps provided in AppStream data, and allow them to be
    installed, removed, etc.

 plugins/epiphany/gs-plugin-epiphany.c | 511 ++++++++++++++++++++++++++--------
 plugins/epiphany/gs-self-test.c       |   6 +-
 2 files changed, 402 insertions(+), 115 deletions(-)
---
diff --git a/plugins/epiphany/gs-plugin-epiphany.c b/plugins/epiphany/gs-plugin-epiphany.c
index 9747cdca1..e2d84d47e 100644
--- a/plugins/epiphany/gs-plugin-epiphany.c
+++ b/plugins/epiphany/gs-plugin-epiphany.c
@@ -39,7 +39,10 @@ struct _GsPluginEpiphany
        GFileMonitor *monitor; /* (owned) */
        GDBusConnection *connection; /* (owned) */
        guint changed_id;
-       GMutex installed_apps_mutex; /* protects the plugin cache */
+       GMutex installed_apps_mutex; /* protects installed_apps_cached */
+       /* installed_apps_cached: whether the plugin cache has all installed apps */
+       gboolean installed_apps_cached;
+       GHashTable *url_id_map; /* (owned) */
 };
 
 G_DEFINE_TYPE (GsPluginEpiphany, gs_plugin_epiphany, GS_TYPE_PLUGIN)
@@ -93,6 +96,13 @@ gs_plugin_epiphany_changed_cb (GFileMonitor      *monitor,
 {
        GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (user_data);
 
+       {
+         g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->installed_apps_mutex);
+         gs_plugin_cache_invalidate (GS_PLUGIN (self));
+         g_hash_table_remove_all (self->url_id_map);
+         self->installed_apps_cached = FALSE;
+       }
+
        /* FIXME: With the current API this is the only way to reload the list
         * of installed apps.
         */
@@ -125,6 +135,14 @@ gs_plugin_epiphany_setup_async (GsPlugin            *plugin,
        if (connection)
                self->connection = g_object_ref (connection);
 
+       self->installed_apps_cached = FALSE;
+
+       /* This is a mapping from URL to app ID, where the app ID comes from
+        * Epiphany. This allows us to use that app ID rather than the
+        * AppStream app ID in certain contexts.
+        */
+       self->url_id_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
        /* Watch for changes to the set of installed apps in the main thread.
         * This will also trigger when other apps' dynamic launchers are
         * installed or removed but that is expected to be infrequent.
@@ -312,6 +330,7 @@ gs_plugin_epiphany_dispose (GObject *object)
        g_clear_object (&self->monitor);
        g_clear_object (&self->worker);
        g_clear_object (&self->connection);
+       g_clear_pointer (&self->url_id_map, g_hash_table_unref);
 
        G_OBJECT_CLASS (gs_plugin_epiphany_parent_class)->dispose (object);
 }
@@ -326,6 +345,44 @@ gs_plugin_epiphany_finalize (GObject *object)
        G_OBJECT_CLASS (gs_plugin_epiphany_parent_class)->finalize (object);
 }
 
+static gboolean refresh_installed_apps_cache (GsPluginEpiphany  *self,
+                                             GCancellable      *cancellable,
+                                             GError           **error);
+
+/* Run in @worker */
+static void
+gs_epiphany_refine_app_state (GsPlugin *plugin,
+                             GsApp    *app)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+
+       assert_in_worker (self);
+
+       if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN) {
+               g_autoptr(GsApp) cached_app = NULL;
+               g_autoptr(GError) local_error = NULL;
+               const char *appstream_source;
+
+               if (!refresh_installed_apps_cache (self, NULL, &local_error)) {
+                       g_warning ("Failed to refresh installed apps cache: %s", local_error->message);
+                       return;
+               }
+
+               /* If we have a cached app, set the state from there. Otherwise
+                * only set the state to available if the app came from
+                * appstream data, because there's no way to re-install an app
+                * in Software that was originally installed from Epiphany,
+                * unless we have appstream metainfo for it.
+                */
+               cached_app = gs_plugin_cache_lookup (plugin, gs_app_get_id (app));
+               appstream_source = gs_app_get_metadata_item (app, "appstream::source-file");
+               if (cached_app)
+                       gs_app_set_state (app, gs_app_get_state (cached_app));
+               else if (appstream_source)
+                       gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+       }
+}
+
 void
 gs_plugin_adopt_app (GsPlugin *plugin,
                     GsApp    *app)
@@ -361,9 +418,14 @@ gs_plugin_epiphany_list_installed_apps_async (GsPlugin                       *pl
 
 /* Run in @worker */
 static void
-set_license_from_hostname (GsApp      *app,
-                          const char *hostname)
+refine_app (GsPluginEpiphany    *self,
+           GsApp               *app,
+           GsPluginRefineFlags  flags,
+           GUri                *uri,
+           const char          *url)
 {
+       const char *hostname;
+       const char *installed_app_id;
        const struct {
                const gchar *hostname;
                const gchar *license_spdx;
@@ -381,14 +443,25 @@ set_license_from_hostname (GsApp      *app,
        };
 
        g_return_if_fail (GS_IS_APP (app));
+       g_return_if_fail (uri != NULL);
+       g_return_if_fail (url != NULL);
 
-       if (hostname == NULL || *hostname == '\0')
-               return;
+       gs_app_set_origin (app, "gnome-web");
+       gs_app_set_origin_ui (app, _("GNOME Web"));
+
+       gs_app_set_scope (app, AS_COMPONENT_SCOPE_USER);
+       gs_app_set_launchable (app, AS_LAUNCHABLE_KIND_URL, url);
+
+       installed_app_id = g_hash_table_lookup (self->url_id_map, url);
+       if (installed_app_id) {
+               gs_app_set_launchable (app, AS_LAUNCHABLE_KIND_DESKTOP_ID, installed_app_id);
+       }
 
        /* Hard-code the licenses as it's hard to get them programmatically. We
         * can move them to an AppStream file if needed.
         */
-       if (gs_app_get_license (app) == NULL) {
+       hostname = g_uri_get_host (uri);
+       if (gs_app_get_license (app) == NULL && hostname != NULL) {
                for (gsize i = 0; i < G_N_ELEMENTS (app_licenses); i++) {
                        if (g_str_equal (hostname, app_licenses[i].hostname)) {
                                gs_app_set_license (app, GS_APP_QUALITY_NORMAL,
@@ -397,6 +470,108 @@ set_license_from_hostname (GsApp      *app,
                        }
                }
        }
+
+       /* 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 (installed_app_id == NULL)
+               return;
+
+       {
+               const gchar *name;
+               g_autofree char *icon_path = NULL;
+               goffset desktop_size = 0, icon_size = 0;
+               g_autoptr(GDesktopAppInfo) desktop_info = NULL;
+               g_autoptr(GFileInfo) file_info = NULL;
+               g_autoptr(GFile) icon_file = NULL;
+
+               desktop_info = g_desktop_app_info_new (installed_app_id);
+
+               if (desktop_info == NULL) {
+                       g_warning ("Couldn't get GDesktopAppInfo for app %s", installed_app_id);
+                       return;
+               }
+
+               name = g_app_info_get_name (G_APP_INFO (desktop_info));
+               gs_app_set_name (app, GS_APP_QUALITY_NORMAL, name);
+
+               if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE) {
+                       g_autoptr(GFile) desktop_file = NULL;
+                       const gchar *desktop_path;
+                       guint64 install_date = 0;
+
+                       desktop_path = g_desktop_app_info_get_filename (desktop_info);
+                       g_assert (desktop_path);
+                       desktop_file = g_file_new_for_path (desktop_path);
+
+                       file_info = g_file_query_info (desktop_file,
+                                                      G_FILE_ATTRIBUTE_TIME_CREATED "," 
G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                                      0, NULL, NULL);
+                       if (file_info) {
+                               install_date = g_file_info_get_attribute_uint64 (file_info, 
G_FILE_ATTRIBUTE_TIME_CREATED);
+                               desktop_size = g_file_info_get_size (file_info);
+                       }
+                       if (install_date) {
+                               gs_app_set_install_date (app, install_date);
+                       }
+               }
+
+               icon_path = g_desktop_app_info_get_string (desktop_info, "Icon");
+               if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE &&
+                   icon_path) {
+                       icon_file = g_file_new_for_path (icon_path);
+
+                       g_clear_object (&file_info);
+                       file_info = g_file_query_info (icon_file,
+                                                      G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                                      0, NULL, NULL);
+                       if (file_info)
+                               icon_size = g_file_info_get_size (file_info);
+               }
+               if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON &&
+                   gs_app_get_icons (app) == NULL &&
+                   icon_path) {
+                       g_autoptr(GIcon) icon = g_file_icon_new (icon_file);
+                       g_autofree char *icon_dir = g_path_get_dirname (icon_path);
+                       g_autofree char *icon_dir_basename = g_path_get_basename (icon_dir);
+                       const char *x;
+                       guint64 width = 0;
+
+                       /* dir should be either scalable or e.g. 512x512 */
+                       if (g_strcmp0 (icon_dir_basename, "scalable") == 0) {
+                               /* Ensure scalable icons are preferred */
+                               width = 4096;
+                       } else if ((x = strchr (icon_dir_basename, 'x')) != NULL) {
+                               g_ascii_string_to_unsigned (x + 1, 10, 1, G_MAXINT, &width, NULL);
+                       }
+                       if (width > 0 && width <= 4096) {
+                               gs_icon_set_width (icon, width);
+                               gs_icon_set_height (icon, width);
+                       } else {
+                               g_warning ("Unexpectedly unable to determine width of icon %s", icon_path);
+                       }
+
+                       gs_app_add_icon (app, icon);
+               }
+               if (desktop_size > 0 || icon_size > 0) {
+                       gs_app_set_size_installed (app, desktop_size + icon_size);
+               }
+       }
 }
 
 /* Run in @worker */
@@ -404,71 +579,62 @@ 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_metadata (app, "GnomeSoftware::Creator",
+                            gs_plugin_get_name (GS_PLUGIN (self)));
 
-       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 */
-static void
-list_installed_apps_thread_cb (GTask        *task,
-                               gpointer      source_object,
-                               gpointer      task_data,
-                               GCancellable *cancellable)
+static gboolean
+refresh_installed_apps_cache (GsPluginEpiphany  *self,
+                             GCancellable      *cancellable,
+                             GError           **error)
 {
-       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (source_object);
-       g_autoptr(GsAppList) list = gs_app_list_new ();
-       g_autoptr(GsAppList) installed_cache = gs_app_list_new ();
-       g_autoptr(GError) local_error = NULL;
        g_autoptr(GVariant) webapps_v = NULL;
        g_auto(GStrv) webapps = NULL;
-       g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->installed_apps_mutex);
        guint n_webapps;
+       g_autoptr(GsAppList) installed_cache = gs_app_list_new ();
+       g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&self->installed_apps_mutex);
 
        assert_in_worker (self);
 
+       if (self->installed_apps_cached)
+               return TRUE;
+
        if (!gs_ephy_web_app_provider_call_get_installed_apps_sync (self->epiphany_proxy,
                                                                    &webapps,
                                                                    cancellable,
-                                                                   &local_error)) {
-               gs_epiphany_error_convert (&local_error);
-               g_task_return_error (task, g_steal_pointer (&local_error));
-               return;
+                                                                   error)) {
+               gs_epiphany_error_convert (error);
+               return FALSE;
        }
 
        n_webapps = g_strv_length (webapps);
        g_debug ("%s: epiphany-webapp-provider returned %u installed web apps", G_STRFUNC, n_webapps);
        for (guint i = 0; i < n_webapps; i++) {
                const gchar *desktop_file_id = webapps[i];
-               const gchar *desktop_path;
-               const gchar *name;
                const gchar *url = NULL;
-               g_autofree char *icon_path = NULL;
+               g_autofree char *url_hash = NULL;
+               g_autofree char *metainfo_app_id = NULL;
                const gchar *exec;
-               const gchar *host;
                int argc;
+               GsPluginRefineFlags refine_flags;
                g_auto(GStrv) argv = NULL;
-               guint64 install_date = 0;
-               goffset desktop_size = 0, icon_size = 0;
                g_autoptr(GsApp) app = NULL;
                g_autoptr(GDesktopAppInfo) desktop_info = NULL;
-               g_autoptr(GFileInfo) file_info = NULL;
-               g_autoptr(GFile) desktop_file = NULL;
                g_autoptr(GUri) uri = NULL;
 
                g_debug ("%s: Working on installed web app %s", G_STRFUNC, desktop_file_id);
@@ -480,8 +646,6 @@ list_installed_apps_thread_cb (GTask        *task,
                        continue;
                }
 
-               name = g_app_info_get_name (G_APP_INFO (desktop_info));
-
                /* This way of getting the URL is a bit hacky but it's what
                 * Epiphany does, specifically in
                 * ephy_web_application_for_profile_directory() which lives in
@@ -493,78 +657,36 @@ 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;
                }
 
-               icon_path = g_desktop_app_info_get_string (desktop_info, "Icon");
-
-               desktop_path = g_desktop_app_info_get_filename (desktop_info);
-               g_assert (desktop_path);
-               desktop_file = g_file_new_for_path (desktop_path);
+               /* Store the installed app id for use in refine_app() */
+               g_hash_table_insert (self->url_id_map, g_strdup (url),
+                                    g_strdup (desktop_file_id));
 
-               file_info = g_file_query_info (desktop_file,
-                                              G_FILE_ATTRIBUTE_TIME_CREATED "," 
G_FILE_ATTRIBUTE_STANDARD_SIZE,
-                                              0, NULL, NULL);
-               if (file_info) {
-                       install_date = g_file_info_get_attribute_uint64 (file_info, 
G_FILE_ATTRIBUTE_TIME_CREATED);
-                       desktop_size = g_file_info_get_size (file_info);
-               }
-
-               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);
-
-               /* Use the domain name as a fallback summary.
-                * FIXME: Fetch the summary from the site's webapp manifest.
+               /* Generate the app ID used in the AppStream data using the
+                * same method as pwa-metainfo-generator.py in
+                * https://gitlab.gnome.org/mwleeds/gnome-pwa-list
+                * Using this app ID rather than the one provided by Epiphany
+                * makes it possible for the appstream plugin to refine the
+                * GsApp we create.
                 */
-               host = g_uri_get_host (uri);
-               gs_app_set_summary (app, GS_APP_QUALITY_LOWEST, host ? host : url);
-
-               set_license_from_hostname (app, host);
+               url_hash = g_compute_checksum_for_string (G_CHECKSUM_SHA1, url, -1);
+               metainfo_app_id = g_strconcat ("org.gnome.Software.WebApp_", url_hash, ".desktop", NULL);
+               g_debug ("Creating GsApp for webapp with URL %s using app ID %s (desktop file id: %s)",
+                        url, metainfo_app_id, desktop_file_id);
 
-               if (icon_path) {
-                       g_autoptr(GFile) icon_file = g_file_new_for_path (icon_path);
-                       g_autoptr(GIcon) icon = g_file_icon_new (icon_file);
-                       g_autofree char *icon_dir = g_path_get_dirname (icon_path);
-                       g_autofree char *icon_dir_basename = g_path_get_basename (icon_dir);
-                       const char *x;
-                       guint64 size = 0;
-
-                       g_debug ("%s: finding size for icon %s", G_STRFUNC, icon_path);
-
-                       g_clear_object (&file_info);
-                       file_info = g_file_query_info (icon_file,
-                                                      G_FILE_ATTRIBUTE_STANDARD_SIZE,
-                                                      0, NULL, NULL);
-                       if (file_info)
-                               icon_size = g_file_info_get_size (file_info);
+               /* App gets added to the plugin cache here */
+               app = gs_epiphany_create_app (self, metainfo_app_id);
 
-                       /* dir should be either scalable or e.g. 512x512 */
-                       if (g_strcmp0 (icon_dir_basename, "scalable") == 0) {
-                               /* Ensure scalable icons are preferred */
-                               size = 4096;
-                       } else if ((x = strchr (icon_dir_basename, 'x')) != NULL) {
-                               g_ascii_string_to_unsigned (x + 1, 10, 1, G_MAXINT, &size, NULL);
-                       }
-                       if (size > 0 && size <= 4096) {
-                               gs_icon_set_width (icon, size);
-                               gs_icon_set_height (icon, size);
-                       } else {
-                               g_warning ("Unexpectedly unable to determine size of icon %s", icon_path);
-                       }
+               gs_app_set_state (app, GS_APP_STATE_INSTALLED);
 
-                       gs_app_add_icon (app, icon);
-               }
-               if (install_date) {
-                       gs_app_set_install_date (app, install_date);
-               }
-               if (desktop_size > 0 || icon_size > 0) {
-                       gs_app_set_size_installed (app, desktop_size + icon_size);
-               }
-               gs_app_list_add (list, app);
+               refine_flags = GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
+                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_SIZE |
+                              GS_PLUGIN_REFINE_FLAGS_REQUIRE_ID;
+               refine_app (self, app, refine_flags, uri, url);
        }
 
        /* Update the state on any apps that were uninstalled outside
@@ -573,16 +695,48 @@ list_installed_apps_thread_cb (GTask        *task,
        gs_plugin_cache_lookup_by_state (GS_PLUGIN (self), installed_cache, GS_APP_STATE_INSTALLED);
        for (guint i = 0; i < gs_app_list_length (installed_cache); i++) {
                GsApp *app = gs_app_list_index (installed_cache, i);
-               const char *app_id = gs_app_get_id (app);
-               g_autoptr(GsApp) app_cached = NULL;
+               const char *installed_app_id;
+               const char *appstream_source;
+
+               installed_app_id = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_DESKTOP_ID);
+               if (installed_app_id == NULL) {
+                       g_warning ("Installed app unexpectedly has no desktop id: %s", gs_app_get_id (app));
+                       continue;
+               }
 
-               if (g_strv_contains ((const char * const *)webapps, app_id))
+               if (g_strv_contains ((const char * const *)webapps, installed_app_id))
                        continue;
 
-               gs_app_set_state (app, GS_APP_STATE_UNKNOWN);
-               gs_plugin_cache_remove (GS_PLUGIN (self), app_id);
+               gs_plugin_cache_remove (GS_PLUGIN (self), gs_app_get_id (app));
+
+               appstream_source = gs_app_get_metadata_item (app, "appstream::source-file");
+               if (appstream_source)
+                       gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+               else
+                       gs_app_set_state (app, GS_APP_STATE_UNKNOWN);
+       }
+
+       self->installed_apps_cached = TRUE;
+       return TRUE;
+}
+
+/* Run in @worker */
+static void
+list_installed_apps_thread_cb (GTask        *task,
+                               gpointer      source_object,
+                               gpointer      task_data,
+                               GCancellable *cancellable)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (source_object);
+       g_autoptr(GsAppList) list = gs_app_list_new ();
+       g_autoptr(GError) local_error = NULL;
+
+       if (!refresh_installed_apps_cache (self, cancellable, &local_error)) {
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
        }
 
+       gs_plugin_cache_lookup_by_state (GS_PLUGIN (self), list, GS_APP_STATE_INSTALLED);
        g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref);
 }
 
@@ -595,6 +749,110 @@ gs_plugin_epiphany_list_installed_apps_finish (GsPlugin      *plugin,
        return g_task_propagate_pointer (G_TASK (result), error);
 }
 
+static void
+gs_epiphany_refine_app (GsPluginEpiphany    *self,
+                       GsApp               *app,
+                       GsPluginRefineFlags  refine_flags,
+                       const char          *url)
+{
+       g_autoptr(GUri) uri = NULL;
+
+       g_return_if_fail (url != NULL && *url != '\0');
+
+       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);
+               return;
+       }
+
+       refine_app (self, app, refine_flags, uri, url);
+}
+
+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;
+       GsPluginRefineFlags flags = data->flags;
+       GsAppList *list = data->list;
+       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;
+
+               /* 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;
+               }
+
+               g_debug ("epiphany: refining app %s", gs_app_get_id (app));
+               gs_epiphany_refine_app (self, app, flags, url);
+               gs_epiphany_refine_app_state (GS_PLUGIN (self), app);
+
+               /* Usually the way to refine wildcard apps is to create a new
+                * GsApp and add it to the results list, but in this case we
+                * need to use the app that was refined by the appstream plugin
+                * as it has all the metadata set already, and this is the only
+                * plugin for dealing with web apps, so it should be safe to
+                * adopt the wildcard app.
+                */
+               if (gs_app_has_quirk (app, GS_APP_QUIRK_IS_WILDCARD)) {
+                       gs_app_remove_quirk (app, GS_APP_QUIRK_IS_WILDCARD);
+                       gs_app_set_management_plugin (app, GS_PLUGIN (self));
+                       gs_plugin_cache_add (GS_PLUGIN (self), gs_app_get_id (app), app);
+               }
+       }
+
+       /* 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)
@@ -718,6 +976,9 @@ gs_plugin_app_install (GsPlugin      *plugin,
                gs_app_set_state_recover (app);
                return FALSE;
        }
+
+       g_hash_table_insert (self->url_id_map, g_strdup (url),
+                            g_strdup (installed_desktop_id));
        gs_app_set_launchable (app, AS_LAUNCHABLE_KIND_DESKTOP_ID, installed_desktop_id);
        gs_app_set_state (app, GS_APP_STATE_INSTALLED);
 
@@ -731,20 +992,40 @@ gs_plugin_app_remove (GsPlugin      *plugin,
                      GError       **error)
 {
        GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       const char *installed_app_id;
+       const char *url;
 
        if (!gs_app_has_management_plugin (app, plugin))
                return TRUE;
 
+       installed_app_id = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_DESKTOP_ID);
+       if (installed_app_id == NULL) {
+               g_set_error_literal (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                    "App can't be uninstalled without installed app ID");
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
+
        gs_app_set_state (app, GS_APP_STATE_REMOVING);
        if (!gs_ephy_web_app_provider_call_uninstall_sync (self->epiphany_proxy,
-                                                          gs_app_get_id (app),
+                                                          installed_app_id,
                                                           cancellable,
                                                           error)) {
                gs_epiphany_error_convert (error);
                gs_app_set_state_recover (app);
                return FALSE;
        }
-       gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+
+       url = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_URL);
+       if (url != NULL && *url != '\0')
+               g_hash_table_remove (self->url_id_map, url);
+
+       /* The app is not necessarily available; it may have been installed
+        * directly in Epiphany
+        */
+       gs_app_set_state (app, GS_APP_STATE_UNKNOWN);
 
        return TRUE;
 }
@@ -774,6 +1055,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;
 }
diff --git a/plugins/epiphany/gs-self-test.c b/plugins/epiphany/gs-self-test.c
index 5d68b87c4..4dc857812 100644
--- a/plugins/epiphany/gs-self-test.c
+++ b/plugins/epiphany/gs-self-test.c
@@ -150,6 +150,8 @@ gs_plugins_epiphany_installed_func (GsPluginLoader *plugin_loader)
        g_autoptr(GsAppList) list = NULL;
        GsApp *app;
        const char *app_id = "org.gnome.Epiphany.WebApp_e9d0e1e4b0a10856aa3b38d9eb4375de4070d043";
+       const char *metainfo_app_id = 
"org.gnome.Software.WebApp_e636aa5f2069f6e9c02deccc7b65f43da7985e32.desktop";
+       const char *launchable_app_id;
        g_autofree char *app_id_desktop = NULL;
        g_autofree char *desktop_path = NULL;
 
@@ -166,7 +168,9 @@ gs_plugins_epiphany_installed_func (GsPluginLoader *plugin_loader)
 
        g_assert_cmpint (gs_app_list_length (list), ==, 1);
        app = gs_app_list_index (list, 0);
-       g_assert_cmpstr (gs_app_get_id (app), ==, app_id_desktop);
+       g_assert_cmpstr (gs_app_get_id (app), ==, metainfo_app_id);
+       launchable_app_id = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_DESKTOP_ID);
+       g_assert_cmpstr (launchable_app_id, ==, app_id_desktop);
        g_assert_cmpint (gs_app_get_kind (app), ==, AS_COMPONENT_KIND_WEB_APP);
        g_assert_cmpint (gs_app_get_scope (app), ==, AS_COMPONENT_SCOPE_USER);
        g_assert_cmpint (gs_app_get_state (app), ==, GS_APP_STATE_INSTALLED);


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