[gnome-software/mwleeds/pwa-plugin: 2/2] epiphany: Rewrite plugin (WIP)




commit 641ace5dd2cc1546bf2d39da0bd75ef33ec52dc7
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Thu Dec 9 16:34:55 2021 -0800

    epiphany: Rewrite plugin (WIP)

 lib/gs-plugin-loader.c                             |   4 +-
 lib/gs-utils.c                                     |  31 ++
 lib/gs-utils.h                                     |   1 +
 plugins/epiphany/gs-plugin-epiphany.c              | 540 +++++++++++----------
 plugins/epiphany/gs-plugin-epiphany.h              |  20 +
 plugins/epiphany/gs-self-test.c                    |  33 +-
 plugins/epiphany/meson.build                       |   8 +
 .../epiphany/org.gnome.Epiphany.WebAppProvider.xml | 124 +++++
 src/gs-installed-page.c                            |   6 +
 9 files changed, 503 insertions(+), 264 deletions(-)
---
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 0d3ce396a..6031cae7d 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -1394,7 +1394,9 @@ gs_plugin_loader_app_is_valid (GsApp *app, gpointer user_data)
                         gs_plugin_loader_get_app_str (app));
                return FALSE;
        }
-       if (gs_app_get_summary (app) == NULL) {
+       /* web apps usually don't have summaries */
+       if (gs_app_get_kind (app) != AS_COMPONENT_KIND_WEB_APP &&
+           gs_app_get_summary (app) == NULL) {
                g_debug ("app invalid as no summary %s",
                         gs_plugin_loader_get_app_str (app));
                return FALSE;
diff --git a/lib/gs-utils.c b/lib/gs-utils.c
index 8e81d11cf..6023b6fb6 100644
--- a/lib/gs-utils.c
+++ b/lib/gs-utils.c
@@ -41,6 +41,7 @@
 #include "gs-app.h"
 #include "gs-utils.h"
 #include "gs-plugin.h"
+#include "gs-icon.h"
 
 #define MB_IN_BYTES (1024 * 1024)
 
@@ -1427,6 +1428,36 @@ gs_utils_pixbuf_blur (GdkPixbuf *src, guint radius, guint iterations)
                gs_pixbuf_blur_private (src, tmp, radius, div_kernel_size);
 }
 
+/**
+ * gs_utils_file_icon_ensure_size:
+ * @icon: the #GIcon created with g_file_icon_new()
+ *
+ * This ensures that gs_icon_set_width() and gs_icon_set_height() have been
+ * called on @icon, by loading the width and height from the underlying file if
+ * needed.
+ **/
+void
+gs_utils_file_icon_ensure_size (GIcon *icon)
+{
+       GFile *file;
+       g_autofree char *file_path = NULL;
+       g_autoptr(GdkPixbuf) pixbuf = NULL;
+
+       if (gs_icon_get_width (icon) != 0)
+               return;
+
+       file = g_file_icon_get_file (G_FILE_ICON (icon));
+       g_assert (G_IS_FILE (file));
+       file_path = g_file_get_path (file);
+       pixbuf = gdk_pixbuf_new_from_file (file_path, NULL);
+       if (pixbuf == NULL)
+               g_warning ("%s: Failed to load pixbuf from %s", G_STRFUNC, file_path);
+       else {
+               gs_icon_set_width (icon, gdk_pixbuf_get_width (pixbuf));
+               gs_icon_set_height (icon, gdk_pixbuf_get_height (pixbuf));
+       }
+}
+
 /**
  * gs_utils_get_file_size:
  * @filename: a file name to get the size of; it can be a file or a directory
diff --git a/lib/gs-utils.h b/lib/gs-utils.h
index b5f590df8..ba9898737 100644
--- a/lib/gs-utils.h
+++ b/lib/gs-utils.h
@@ -111,6 +111,7 @@ gchar               *gs_utils_build_unique_id       (AsComponentScope scope,
 void            gs_utils_pixbuf_blur           (GdkPixbuf      *src,
                                                 guint          radius,
                                                 guint          iterations);
+void            gs_utils_file_icon_ensure_size (GIcon  *icon);
 
 /**
  * GsFileSizeIncludeFunc:
diff --git a/plugins/epiphany/gs-plugin-epiphany.c b/plugins/epiphany/gs-plugin-epiphany.c
index 9a83e8004..6a97bcbfc 100644
--- a/plugins/epiphany/gs-plugin-epiphany.c
+++ b/plugins/epiphany/gs-plugin-epiphany.c
@@ -1,322 +1,360 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
  *
- * Copyright (C) 2013-2014 Richard Hughes <richard hughsie com>
- * Copyright (C) 2015 Kalev Lember <klember redhat com>
+ * Copyright (C) 2021 Matthew Leeds <mwleeds protonmail com>
  *
  * SPDX-License-Identifier: GPL-2.0+
  */
 
 #include <config.h>
-
-#include <string.h>
-
+#include <glib/gi18n.h>
 #include <gnome-software.h>
 
+#include "gs-epiphany-generated.h"
+#include "gs-plugin-epiphany.h"
+
 /*
  * SECTION:
- * Uses epiphany to launch web applications.
+ * This plugin uses Epiphany to install, launch, and uninstall web applications.
  *
- * If the epiphany binary is not present then it self-disables.
+ * If the org.gnome.Epiphany.WebAppProvider D-Bus interface is not present then
+ * it self-disables. This should work with both Flatpak'd and not Flatpak'd
+ * Epiphany, for new enough versions of Epiphany.
  */
 
-void
-gs_plugin_initialize (GsPlugin *plugin)
+struct _GsPluginEpiphany
+{
+       GsPlugin parent;
+
+       GsEphyWebAppProvider *epiphany_proxy;  /* (owned) */
+};
+
+G_DEFINE_TYPE (GsPluginEpiphany, gs_plugin_epiphany, GS_TYPE_PLUGIN)
+
+static void
+gs_epiphany_error_convert (GError **perror)
+{
+       GError *error = perror != NULL ? *perror : NULL;
+
+       /* not set */
+       if (error == NULL)
+               return;
+
+       /* parse remote epiphany-webapp-provider error */
+       if (g_dbus_error_is_remote_error (error)) {
+               g_autofree gchar *remote_error = g_dbus_error_get_remote_error (error);
+
+               g_dbus_error_strip_remote_error (error);
+
+               if (g_str_equal (remote_error, "org.freedesktop.DBus.Error.ServiceUnknown")) {
+                       error->code = GS_PLUGIN_ERROR_NOT_SUPPORTED;
+               } else {
+                       g_warning ("Can’t reliably fixup remote error ‘%s’", remote_error);
+                       error->code = GS_PLUGIN_ERROR_FAILED;
+               }
+               error->domain = GS_PLUGIN_ERROR;
+               return;
+       }
+
+       /* this is allowed for low-level errors */
+       if (gs_utils_error_convert_gio (perror))
+               return;
+
+       /* this is allowed for low-level errors */
+       if (gs_utils_error_convert_gdbus (perror))
+               return;
+}
+
+static void
+proxy_new_cb (GObject      *source_object,
+              GAsyncResult *result,
+              gpointer      user_data)
 {
-       g_autofree gchar *epiphany = NULL;
-
-       /* we can only work with epiphany */
-       epiphany = g_find_program_in_path ("epiphany");
-       if (epiphany == NULL) {
-               gs_plugin_set_enabled (plugin, FALSE);
-               g_debug ("disabling '%s' as epiphany does not exist",
-                        gs_plugin_get_name (plugin));
+       g_autoptr(GTask) task = g_steal_pointer (&user_data);
+       GsPluginEpiphany *self = g_task_get_source_object (task);
+       g_autofree gchar *name_owner = NULL;
+       g_autoptr(GError) local_error = NULL;
+
+       self->epiphany_proxy = gs_ephy_web_app_provider_proxy_new_for_bus_finish (result, &local_error);
+       if (self->epiphany_proxy == NULL) {
+               gs_epiphany_error_convert (&local_error);
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
+       }
+
+       name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (self->epiphany_proxy));
+
+       if (name_owner == NULL) {
+               g_task_return_new_error (task, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                        "Couldn’t create Epiphany WebAppProvider proxy: couldn’t get name 
owner");
+               return;
        }
 
+       g_task_return_boolean (task, TRUE);
+}
+
+static void
+gs_plugin_epiphany_setup_async (GsPlugin            *plugin,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+       g_autoptr(GTask) task = NULL;
+
+       task = g_task_new (plugin, cancellable, callback, user_data);
+       g_task_set_source_tag (task, gs_plugin_epiphany_setup_async);
+
+       g_debug ("%s", G_STRFUNC);
+
+       /* Check that the proxy exists (and is owned; it should auto-start) so
+        * we can disable the plugin for systems which don’t have new enough
+        * Epiphany.
+        */
+       gs_ephy_web_app_provider_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                                   G_DBUS_PROXY_FLAGS_NONE,
+                                                   "org.gnome.Epiphany.WebAppProvider",
+                                                   "/org/gnome/Epiphany/WebAppProvider",
+                                                   cancellable,
+                                                   proxy_new_cb,
+                                                   g_steal_pointer (&task));
+}
+
+static gboolean
+gs_plugin_epiphany_setup_finish (GsPlugin      *plugin,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+       return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gs_plugin_epiphany_init (GsPluginEpiphany *self)
+{
        /* set name of MetaInfo file */
-       gs_plugin_set_appstream_id (plugin, "org.gnome.Software.Plugin.Epiphany");
+       gs_plugin_set_appstream_id (GS_PLUGIN (self), "org.gnome.Software.Plugin.Epiphany");
 
        /* need help from appstream */
-       gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "appstream");
+       gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_RUN_AFTER, "appstream");
+}
+
+static void
+gs_plugin_epiphany_dispose (GObject *object)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (object);
+
+       g_clear_object (&self->epiphany_proxy);
+
+       G_OBJECT_CLASS (gs_plugin_epiphany_parent_class)->dispose (object);
 }
 
 void
-gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app)
+gs_plugin_adopt_app (GsPlugin *plugin,
+                    GsApp    *app)
 {
-       if (gs_app_get_kind (app) == AS_APP_KIND_WEB_APP &&
+       if (gs_app_get_kind (app) == AS_COMPONENT_KIND_WEB_APP &&
            gs_app_get_bundle_kind (app) != AS_BUNDLE_KIND_PACKAGE) {
-               gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+               gs_app_set_management_plugin (app, plugin);
        }
+
+       if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN)
+               gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
 }
 
-static gchar *
-_gs_app_get_id_nonfull (GsApp *app)
+gboolean
+gs_plugin_add_installed (GsPlugin      *plugin,
+                        GsAppList     *list,
+                        GCancellable  *cancellable,
+                        GError       **error)
 {
-       gchar *id;
-       gchar *tmp;
-
-       id = g_strdup (gs_app_get_id (app));
-       tmp = g_strrstr (id, ".desktop");
-       if (tmp != NULL)
-               *tmp = '\0';
-       return id;
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       g_autoptr(GVariant) webapps_v = NULL;
+       gsize n_webapps;
+
+       if (!gs_ephy_web_app_provider_call_get_installed_web_apps_sync (self->epiphany_proxy,
+                                                                       &webapps_v,
+                                                                       cancellable,
+                                                                       error)) {
+               gs_epiphany_error_convert (error);
+               return FALSE;
+       }
+
+       g_assert (g_variant_is_of_type (webapps_v, G_VARIANT_TYPE ("aa{sv}")));
+       n_webapps = g_variant_n_children (webapps_v);
+       g_debug ("%s: epiphany-webapp-provider returned %" G_GSIZE_FORMAT " installed web apps", G_STRFUNC, 
n_webapps);
+       for (gsize i = 0; i < n_webapps; i++) {
+               g_autoptr(GVariant) webapp_v = g_variant_get_child_value (webapps_v, i);
+               GVariantDict dict;
+               const gchar *desktop_path;
+               const gchar *name;
+               const gchar *url;
+               const gchar *icon_path = NULL;
+               guint64 install_date = 0;
+               g_autofree char *app_id = NULL;
+               g_autoptr(GsApp) app = NULL;
+
+               g_variant_dict_init (&dict, webapp_v);
+               if (!g_variant_dict_lookup (&dict, "desktop-path", "&s", &desktop_path)) {
+                       g_warning ("%s: webapp missing desktop-path", G_STRFUNC);
+                       continue;
+               }
+               if (!g_variant_dict_lookup (&dict, "name", "&s", &name)) {
+                       g_warning ("%s: webapp missing name", G_STRFUNC);
+                       continue;
+               }
+               if (!g_variant_dict_lookup (&dict, "url", "&s", &url)) {
+                       g_warning ("%s: webapp missing url", G_STRFUNC);
+                       continue;
+               }
+               g_variant_dict_lookup (&dict, "icon-path", "&s", &icon_path);
+               g_variant_dict_lookup (&dict, "install-date", "t", &install_date);
+
+               app_id = g_path_get_basename (desktop_path);
+               //TODO do we need to check a cache here, see if a GsApp already exists?
+               app = gs_app_new (app_id);
+               gs_app_set_management_plugin (app, plugin);
+               gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+               gs_app_set_kind (app, AS_COMPONENT_KIND_WEB_APP);
+               gs_app_set_name (app, GS_APP_QUALITY_NORMAL, name);
+               gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, url);
+               gs_app_set_metadata (app, "epiphany::desktop-path", desktop_path);
+
+               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);
+                       gs_utils_file_icon_ensure_size (icon);
+                       gs_app_add_icon (app, icon);
+               }
+               if (install_date) {
+                       gs_app_set_install_date (app, install_date);
+               }
+               gs_app_list_add (list, app);
+       }
+
+       return TRUE;
 }
 
 gboolean
-gs_plugin_app_install (GsPlugin *plugin, GsApp *app,
-                      GCancellable *cancellable, GError **error)
+gs_plugin_app_install (GsPlugin      *plugin,
+                      GsApp         *app,
+                      GCancellable  *cancellable,
+                      GError       **error)
 {
-       AsIcon *icon;
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       const char *url;
+       const char *name;
        GPtrArray *icons;
-       gboolean ret = TRUE;
-       gsize kf_length;
-       g_autoptr(GError) error_local = NULL;
-       g_autofree gchar *app_desktop = NULL;
-       g_autofree gchar *epi_desktop = NULL;
-       g_autofree gchar *epi_dir = NULL;
-       g_autofree gchar *epi_icon = NULL;
-       g_autofree gchar *exec = NULL;
-       g_autofree gchar *hash = NULL;
-       g_autofree gchar *id_nonfull = NULL;
-       g_autofree gchar *kf_data = NULL;
-       g_autofree gchar *wmclass = NULL;
-       g_autoptr(GKeyFile) kf = NULL;
-       g_autoptr(GFile) symlink_desktop = NULL;
-       g_autoptr(GFile) symlink_icon = NULL;
-       const gchar *url = NULL;
-
-       /* only process this app if was created by this plugin */
-       if (g_strcmp0 (gs_app_get_management_plugin (app),
-                      gs_plugin_get_name (plugin)) != 0)
+       g_autofree char *icon_path = NULL;
+
+       if (!gs_app_has_management_plugin (app, plugin))
                return TRUE;
 
-       /* create the correct directory */
-       id_nonfull = _gs_app_get_id_nonfull (app);
-       hash = g_compute_checksum_for_string (G_CHECKSUM_SHA1, gs_app_get_name (app), -1);
-       epi_dir = g_strdup_printf ("%s/epiphany/app-%s-%s",
-                                  g_get_user_config_dir (),
-                                  id_nonfull,
-                                  hash);
-       g_mkdir_with_parents (epi_dir, 0755);
-
-       /* symlink icon */
-       epi_icon = g_build_filename (epi_dir, "app-icon.png", NULL);
-       symlink_icon = g_file_new_for_path (epi_icon);
-       icons = gs_app_get_icons (app);
-       if (icons->len == 0) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "no icons for %s",
+       url = gs_app_get_url (app, AS_URL_KIND_HOMEPAGE);
+       if (url == NULL || *url == '\0') {
+               g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+                            "Can't install web app %s without url",
                             gs_app_get_id (app));
                return FALSE;
        }
-       icon = g_ptr_array_index (icons, 0);
-       if (as_icon_get_filename (icon) == NULL) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "no filename for icon %s",
-                            as_icon_get_name (icon));
+       name = gs_app_get_name (app);
+       if (name == NULL || *name == '\0') {
+               g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+                            "Can't install web app %s without name",
+                            gs_app_get_id (app));
                return FALSE;
        }
-       ret = g_file_make_symbolic_link (symlink_icon,
-                                        as_icon_get_filename (icon),
-                                        NULL,
-                                        &error_local);
-       if (!ret) {
-               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
-                       g_debug ("ignoring icon symlink failure: %s",
-                                error_local->message);
-               } else {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_WRITE_FAILED,
-                                    "Can't symlink icon: %s",
-                                    error_local->message);
-                       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)) {
+                       icon_path = g_file_get_path (g_file_icon_get_file (G_FILE_ICON (icon)));
+                       break;
                }
        }
-
-       /* add desktop file */
-       wmclass = g_strdup_printf ("%s-%s", id_nonfull, hash);
-       kf = g_key_file_new ();
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_NAME,
-                              gs_app_get_name (app));
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_COMMENT,
-                              gs_app_get_summary (app));
-       url = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_URL);
-       if (url == NULL)
-               url = gs_app_get_url (app, AS_URL_KIND_HOMEPAGE);
-       exec = g_strdup_printf ("epiphany --application-mode --profile=\"%s\" %s",
-                               epi_dir,
-                               url);
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_EXEC,
-                              exec);
-       g_key_file_set_boolean (kf,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY,
-                               TRUE);
-       g_key_file_set_boolean (kf,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_TERMINAL,
-                               FALSE);
-       g_key_file_set_boolean (kf,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY,
-                               FALSE);
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_TYPE,
-                              G_KEY_FILE_DESKTOP_TYPE_APPLICATION);
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_ICON,
-                              epi_icon);
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_STARTUP_WM_CLASS,
-                              wmclass);
-
-       /* save keyfile */
-       kf_data = g_key_file_to_data (kf, &kf_length, error);
-       if (kf_data == NULL)
-               return FALSE;
-       epi_desktop = g_strdup_printf ("%s/%s.desktop", epi_dir, wmclass);
-       if (!g_file_set_contents (epi_desktop, kf_data, (gssize) kf_length, error))
-               return FALSE;
-
-       /* symlink it to somewhere the shell will notice */
-       app_desktop = g_build_filename (g_get_user_data_dir (),
-                                       "applications",
-                                       gs_app_get_id (app),
-                                       NULL);
-       symlink_desktop = g_file_new_for_path (app_desktop);
-       ret = g_file_make_symbolic_link (symlink_desktop,
-                                        epi_desktop,
-                                        NULL,
-                                        error);
-       if (!ret) {
-               gs_utils_error_convert_gio (error);
+       if (icon_path == NULL) {
+               g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+                            "Can't install web app %s without icon",
+                            gs_app_get_id (app));
                return FALSE;
        }
 
-       /* update state */
-       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
-       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
+       //TODO get install_token, do install
+
        return TRUE;
 }
 
 gboolean
-gs_plugin_app_remove (GsPlugin *plugin, GsApp *app,
-                     GCancellable *cancellable, GError **error)
+gs_plugin_app_remove (GsPlugin      *plugin,
+                     GsApp         *app,
+                     GCancellable  *cancellable,
+                     GError       **error)
 {
-       const gchar *epi_desktop;
-       g_autofree gchar *app_desktop = NULL;
-       g_autoptr(GFile) file_epi = NULL;
-       g_autoptr(GFile) file_app = NULL;
-
-       /* only process this app if was created by this plugin */
-       if (g_strcmp0 (gs_app_get_management_plugin (app),
-                      gs_plugin_get_name (plugin)) != 0)
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       const char *desktop_path;
+
+       if (!gs_app_has_management_plugin (app, plugin))
                return TRUE;
 
-       /* remove the epi 'config' file */
-       gs_app_set_state (app, AS_APP_STATE_REMOVING);
-       epi_desktop = gs_app_get_source_id_default (app);
-       file_epi = g_file_new_for_path (epi_desktop);
-       if (!g_file_delete (file_epi, NULL, error))
+       desktop_path = gs_app_get_metadata_item (app, "epiphany::desktop-path");
+       if (desktop_path == NULL) {
+               g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+                            "Can't remove app %s without desktop file path",
+                            gs_app_get_id (app));
                return FALSE;
+       }
 
-       /* remove the shared desktop file */
-       app_desktop = g_build_filename (g_get_user_data_dir (),
-                                       "applications",
-                                       gs_app_get_id (app),
-                                       NULL);
-       file_app = g_file_new_for_path (app_desktop);
-       if (!g_file_delete (file_app, NULL, error)) {
-               gs_utils_error_convert_gio (error);
+       if (!gs_ephy_web_app_provider_call_uninstall_web_app_sync (self->epiphany_proxy,
+                                                                  desktop_path,
+                                                                  cancellable,
+                                                                  error)) {
+               gs_epiphany_error_convert (error);
                return FALSE;
        }
-       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+
        return TRUE;
 }
 
 gboolean
-gs_plugin_refine_app (GsPlugin *plugin,
-                     GsApp *app,
-                     GsPluginRefineFlags flags,
-                     GCancellable *cancellable,
-                     GError **error)
+gs_plugin_refine (GsPlugin             *plugin,
+                 GsAppList            *list,
+                 GsPluginRefineFlags   flags,
+                 GCancellable         *cancellable,
+                 GError              **error)
 {
-       const gchar *name;
-       g_autofree gchar *fn = NULL;
-       g_autofree gchar *hash = NULL;
-       g_autofree gchar *id_nonfull = NULL;
-
-       /* only process this app if was created by this plugin */
-       if (g_strcmp0 (gs_app_get_management_plugin (app),
-                      gs_plugin_get_name (plugin)) != 0)
-               return TRUE;
-
-       gs_app_set_size_installed (app, 4096);
-
-       /* i guess this is technically true */
-       gs_app_add_kudo (app, GS_APP_KUDO_SANDBOXED_SECURE);
+       //TODO
 
-       name = gs_app_get_name (app);
-       if (name == NULL) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_INVALID_FORMAT,
-                            "name unset for %s",
-                            gs_app_get_id (app));
-               return FALSE;
-       }
-       if (gs_app_get_summary (app) == NULL) {
-               g_debug ("faking summary for %s", gs_app_get_id (app));
-               gs_app_set_summary (app, GS_APP_QUALITY_LOWEST,
-                                   "Web Application");
-       }
-       hash = g_compute_checksum_for_string (G_CHECKSUM_SHA1, name, -1);
-       id_nonfull = _gs_app_get_id_nonfull (app);
-       fn = g_strdup_printf ("%s/epiphany/app-%s-%s/%s-%s.desktop",
-                             g_get_user_config_dir (),
-                             id_nonfull,
-                             hash,
-                             id_nonfull,
-                             hash);
-       /* try the new-style location */
-       if (!g_file_test (fn, G_FILE_TEST_EXISTS)) {
-               g_free (fn);
-               fn = g_strdup_printf ("%s/epiphany/app-%s/%s.desktop",
-                                     g_get_user_config_dir (),
-                                     id_nonfull, id_nonfull);
-       }
-       if (g_file_test (fn, G_FILE_TEST_EXISTS)) {
-               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
-               gs_app_add_source_id (app, fn);
-               gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
-               return TRUE;
-       }
-       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
        return TRUE;
 }
 
 gboolean
-gs_plugin_launch (GsPlugin *plugin,
-                 GsApp *app,
-                 GCancellable *cancellable,
-                 GError **error)
+gs_plugin_launch (GsPlugin      *plugin,
+                 GsApp         *app,
+                 GCancellable  *cancellable,
+                 GError       **error)
 {
-       /* only process this app if was created by this plugin */
-       if (g_strcmp0 (gs_app_get_management_plugin (app),
-                      gs_plugin_get_name (plugin)) != 0)
+       if (!gs_app_has_management_plugin (app, plugin))
                return TRUE;
+
        return gs_plugin_app_launch (plugin, app, error);
 }
+
+static void
+gs_plugin_epiphany_class_init (GsPluginEpiphanyClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GsPluginClass *plugin_class = GS_PLUGIN_CLASS (klass);
+
+       object_class->dispose = gs_plugin_epiphany_dispose;
+
+       plugin_class->setup_async = gs_plugin_epiphany_setup_async;
+       plugin_class->setup_finish = gs_plugin_epiphany_setup_finish;
+}
+
+GType
+gs_plugin_query_type (void)
+{
+       return GS_TYPE_PLUGIN_EPIPHANY;
+}
diff --git a/plugins/epiphany/gs-plugin-epiphany.h b/plugins/epiphany/gs-plugin-epiphany.h
new file mode 100644
index 000000000..24f6565dd
--- /dev/null
+++ b/plugins/epiphany/gs-plugin-epiphany.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Matthew Leeds <mwleeds>protonmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_PLUGIN_EPIPHANY (gs_plugin_epiphany_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsPluginEpiphany, gs_plugin_epiphany, GS, PLUGIN_EPIPHANY, GsPlugin)
+
+G_END_DECLS
diff --git a/plugins/epiphany/gs-self-test.c b/plugins/epiphany/gs-self-test.c
index 2d2f47888..1c7b42c6e 100644
--- a/plugins/epiphany/gs-self-test.c
+++ b/plugins/epiphany/gs-self-test.c
@@ -1,4 +1,5 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
  *
  * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  *
@@ -24,8 +25,8 @@ gs_plugins_epiphany_func (GsPluginLoader *plugin_loader)
                return;
 
        /* a webapp with a local icon */
-       app = gs_app_new ("arachne.desktop");
-       gs_app_set_kind (app, AS_APP_KIND_WEB_APP);
+       app = gs_app_new ("app.squoosh.webapp.desktop");
+       gs_app_set_kind (app, AS_COMPONENT_KIND_WEB_APP);
        plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
                                         "app", app,
                                         "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
@@ -33,10 +34,10 @@ gs_plugins_epiphany_func (GsPluginLoader *plugin_loader)
        ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
-       g_assert (ret);
+       g_assert_true (ret);
 
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
-       g_assert (gs_app_get_pixbuf (app) != NULL);
+       g_assert_cmpint (gs_app_get_state (app), ==, GS_APP_STATE_AVAILABLE);
+       g_assert_nonnull (gs_app_get_icons (app));
 }
 
 int
@@ -47,7 +48,7 @@ main (int argc, char **argv)
        g_autofree gchar *xml = NULL;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsPluginLoader) plugin_loader = NULL;
-       const gchar *whitelist[] = {
+       const gchar *allowlist[] = {
                "appstream",
                "epiphany",
                "icons",
@@ -58,15 +59,23 @@ main (int argc, char **argv)
        g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
        g_setenv ("GS_XMLB_VERBOSE", "1", TRUE);
 
+       /* Use an icon we already have locally */
        fn = gs_test_get_filename (TESTDATADIR, "icons/hicolor/scalable/org.gnome.Software.svg");
        g_assert (fn != NULL);
        xml = g_strdup_printf ("<?xml version=\"1.0\"?>\n"
-               "<components version=\"0.9\">\n"
+               "<components version=\"0.14\">\n"
                "  <component type=\"webapp\">\n"
-               "    <id>arachne.desktop</id>\n"
-               "    <name>test</name>\n"
-               "    <pkgname>test</pkgname>\n"
+               "    <id>app.squoosh.webapp.desktop</id>\n"
+               "    <metadata_license>CC0-1.0</metadata_license>\n"
+               "    <project_license>Apache-2.0</project_license>\n"
+               "    <name>Squoosh</name>\n"
+               "    <summary>Compress and compare images with different codecs, right in your 
browser</summary>\n"
+               "    <launchable type=\"url\">https://squoosh.app/</launchable>\n"
                "    <icon type=\"remote\">file://%s</icon>\n"
+               "    <categories>\n"
+               "      <category>Utility</category>\n"
+               "    </categories>\n"
+               "    <pkgname>test</pkgname>\n"
                "  </component>\n"
                "  <info>\n"
                "    <scope>user</scope>\n"
@@ -82,12 +91,12 @@ main (int argc, char **argv)
        gs_plugin_loader_add_location (plugin_loader, LOCALPLUGINDIR);
        gs_plugin_loader_add_location (plugin_loader, LOCALPLUGINDIR_CORE);
        ret = gs_plugin_loader_setup (plugin_loader,
-                                     (gchar**) whitelist,
+                                     (gchar**) allowlist,
                                      NULL,
                                      NULL,
                                      &error);
        g_assert_no_error (error);
-       g_assert (ret);
+       g_assert_true (ret);
 
        /* plugin tests go here */
        g_test_add_data_func ("/gnome-software/plugins/epiphany",
diff --git a/plugins/epiphany/meson.build b/plugins/epiphany/meson.build
index 9d57c4d16..f7447c276 100644
--- a/plugins/epiphany/meson.build
+++ b/plugins/epiphany/meson.build
@@ -1,7 +1,15 @@
 cargs = ['-DG_LOG_DOMAIN="GsPluginEpiphany"']
 
+epiphany_generated = gnome.gdbus_codegen(
+  'gs-epiphany-generated',
+  sources : ['org.gnome.Epiphany.WebAppProvider.xml'],
+  interface_prefix : 'org.gnome.Epiphany',
+  namespace : 'GsEphy',
+)
+
 shared_module(
   'gs_plugin_epiphany',
+  epiphany_generated,
   sources : 'gs-plugin-epiphany.c',
   include_directories : [
     include_directories('../..'),
diff --git a/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml 
b/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml
new file mode 100644
index 000000000..67acd2ca5
--- /dev/null
+++ b/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml
@@ -0,0 +1,124 @@
+<!DOCTYPE node PUBLIC
+'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
+'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
+<node>
+
+  <!--
+      org.gnome.Epiphany.WebAppProvider:
+      @short_description: Webapp provider interface
+
+      The interface used for handling Epiphany Webapps in GNOME Software.
+
+      This documentation describes version 1 of this interface.
+  -->
+  <interface name="org.gnome.Epiphany.WebAppProvider">
+    <!--
+        version:
+
+        The API version number.
+    -->
+    <property name="version" type="u" access="read"/>
+
+    <!--
+        sandboxed:
+
+        This indicates whether Epiphany is running under Flatpak or a
+        Flatpak-compatible sandbox which means that portals will be necessary
+        for installing or removing web apps from the host system.
+    -->
+    <property name="sandboxed" type="b" access="read"/>
+
+    <!--
+        GetInstalledWebApps:
+        @webapps: An array of dictionaries, one for each installed Epiphany web
+          app.
+
+        Returns the set of installed Epiphany web applications.
+
+        The following information may be included in each @webapps dictionary:
+        <variablelist>
+          <varlistentry>
+            <term>desktop-path s</term>
+            <listitem><para>
+              The path to the .desktop file on the local filesystem.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>name s</term>
+            <listitem><para>
+              The human readable name of the application.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>url s</term>
+            <listitem><para>
+              The URL of the application.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>icon-path s</term>
+            <listitem><para>
+              The path to the icon on the local filesystem.
+            </para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term>install-date t</term>
+            <listitem><para>
+              The install time, in seconds since the UNIX epoch.
+            </para></listitem>
+          </varlistentry>
+        </variablelist>
+
+        Clients should gracefully handle unrecognized keys in the @webapps
+        dictionary, to allow extending it in the future without using new
+        interface and method names.
+    -->
+    <method name="GetInstalledWebApps">
+      <arg type="aa{sv}" name="webapps" direction="out" />
+    </method>
+
+    <!--
+        InstallWebApp:
+        @url: the URL of the web app
+        @name: the human readable name of the web app
+        @icon_path: the path to the icon on the local filesystem
+        @install_token: the token acquired via org.freedesktop.portal.InstallDynamicLauncher
+
+        Installs a web app. This interface is expected to be used by trusted
+        system components such as GNOME Software, which can acquire an
+        @install_token by talking to the InstallDynamicLauncher portal. This allows Epiphany
+        to install the web app without user interaction and despite being sandboxed.
+        This is desirable because the user would've already clicked "Install" in
+        Software; they should not have to confirm the operation again in a different
+        app (Epiphany).
+
+        The @install_token can be the empty string if and only if Epiphany is
+        not running as a Flatpak (or similar sandbox, see the "sandboxed" property).
+
+        The @icon_path only needs to be valid for the duration of the method call,
+        since the icon will be copied elsewhere.
+    -->
+    <method name="InstallWebApp">
+      <arg type="s" name="url" direction="in" />
+      <arg type="s" name="name" direction="in" />
+      <arg type="s" name="icon_path" direction="in" />
+      <arg type="s" name="install_token" direction="in" />
+    </method>
+
+    <!--
+        UninstallWebApp:
+        @desktop_path: the path to the .desktop file of an installed web app,
+          as returned by GetInstalledWebApps()
+
+        Uninstalls a web app. Note that the @desktop_path is the target of a
+        symbolic link created in $XDG_DATA_DIRS, not the path of the symbolic
+        link itself. If you use the path returned by GetInstalledWebApps() you
+        don't have to worry about that distinction.
+
+        An error will be returned if the specified web app is not installed.
+    -->
+    <method name="UninstallWebApp">
+      <arg type="s" name="desktop_path" direction="in" />
+    </method>
+  </interface>
+</node>
diff --git a/src/gs-installed-page.c b/src/gs-installed-page.c
index 6da76365a..78ae6d464 100644
--- a/src/gs-installed-page.c
+++ b/src/gs-installed-page.c
@@ -311,9 +311,15 @@ gs_installed_page_is_actual_app (GsApp *app)
 {
        if (gs_app_get_description (app) != NULL)
                return TRUE;
+
        /* special snowflake */
        if (g_strcmp0 (gs_app_get_id (app), "google-chrome.desktop") == 0)
                return TRUE;
+
+       /* web apps sometimes don't have descriptions */
+       if (gs_app_get_kind (app) == AS_COMPONENT_KIND_WEB_APP)
+               return TRUE;
+
        g_debug ("%s is not an actual app", gs_app_get_unique_id (app));
        return FALSE;
 }


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