[epiphany/mwleeds/webapp-dbus-api: 57/69] Add a WebAppProvider D-Bus interface




commit 7f81c6a45ed71261a51055073ec3868b4744a40a
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Mon Nov 29 17:53:02 2021 -0800

    Add a WebAppProvider D-Bus interface
    
    This commit adds a D-Bus service that exposes the web apps managed by
    Epiphany so that a client can enumerate them, install a new one, or
    remove an installed one. The intended client is GNOME Software, since I
    am soon going to add back a webapp plugin to Software which will make
    use of this interface. This is part of a larger effort to improve the
    support for Progressive Web Apps in GNOME (though Epiphany's web apps do
    not support PWA manifest features).
    
    The great thing about having this be a service exposed by Epiphany,
    rather than having Software try to install Epiphany-compatible web apps
    on its own as it used to do, is that the implementation of how to create
    and manage the web apps can stay in Epiphany, and there can never be
    disagreement between Epiphany and Software about the proper on-disk
    format for them (e.g. the algorithm used for generating the app ID from
    the name).
    
    The goal for the PWA project is to support Flatpak'd Epiphany, whereas
    currently only non-Flatpak Epiphany can do web apps. This will be
    accomplished with new portals to allow installing and removing the
    .desktop launchers. The Flatpak support requirement is reflected in the
    design of the API here, specifically the install_token parameter for the
    InstallWebApp method. This token is to be acquired by the client of the
    D-Bus interface (Software) so that the installation can be achieved
    without any additional user interaction since the user would've already
    clicked "Install" in Software. Web app installation directly via
    Flatpak'd Epiphany's UI would involve a dialog created by the portal;
    this is because we don't sandboxed applications in general to be able to
    create desktop launchers without user interaction.
    
    The UninstallWebApp method by contrast doesn't require such a token,
    because the portal can ensure that only apps created by an application
    are deleted by that application.
    
    The GetInstalledWebApps method is implemented by looking at the profile
    directories of the apps, because we don't want to have to poke a sandbox
    hole to allow access to the actual desktop files. If we need read access
    to them, for example to keep the application manager interface working,
    we can add a portal method to provide that. But currently the plan is to
    avoid that and drop the app manager, since Software will provide
    management of these apps.

 data/meson.build                                   |   7 +
 data/org.gnome.Epiphany.WebAppProvider.service.in  |   3 +
 lib/ephy-web-app-utils.c                           | 145 +++++++++++--
 lib/ephy-web-app-utils.h                           |  17 +-
 src/meson.build                                    |  23 +-
 src/webapp-provider/ephy-webapp-provider-main.c    |  62 ++++++
 src/webapp-provider/ephy-webapp-provider.c         | 241 +++++++++++++++++++++
 src/webapp-provider/ephy-webapp-provider.h         |  36 +++
 .../org.gnome.Epiphany.WebAppProvider.xml          |  73 +++++++
 src/window-commands.c                              |   1 +
 tests/ephy-web-app-utils-test.c                    |   2 +-
 11 files changed, 586 insertions(+), 24 deletions(-)
---
diff --git a/data/meson.build b/data/meson.build
index eac6b8224..4d677fce7 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -96,6 +96,13 @@ configure_file(
   install_dir: servicedir
 )
 
+configure_file(
+  input: 'org.gnome.Epiphany.WebAppProvider.service.in',
+  output: '@0@.WebAppProvider.service'.format(application_id),
+  configuration: service_conf,
+  install_dir: servicedir
+)
+
 search_provider_conf = configuration_data()
 search_provider_conf.set('appid', application_id)
 search_provider_conf.set('profile', profile != '' ? '/' + profile : '')
diff --git a/data/org.gnome.Epiphany.WebAppProvider.service.in 
b/data/org.gnome.Epiphany.WebAppProvider.service.in
new file mode 100644
index 000000000..de411d239
--- /dev/null
+++ b/data/org.gnome.Epiphany.WebAppProvider.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=@appid@.WebAppProvider
+Exec=@libexecdir@/epiphany-webapp-provider
diff --git a/lib/ephy-web-app-utils.c b/lib/ephy-web-app-utils.c
index dd2291833..63776eee0 100644
--- a/lib/ephy-web-app-utils.c
+++ b/lib/ephy-web-app-utils.c
@@ -242,6 +242,21 @@ ephy_web_application_get_profile_directory (const char *id)
   return ephy_web_application_get_directory_under (id, g_get_user_data_dir ());
 }
 
+/**
+ * ephy_web_application_get_desktop_path:
+ * @app: the #EphyWebApplication
+ *
+ * Gets the path to the .desktop file for @app
+ *
+ * Returns: (transfer full): A newly allocated string.
+ **/
+char *
+ephy_web_application_get_desktop_path (EphyWebApplication *app)
+{
+  g_autofree char *profile_dir = ephy_web_application_get_profile_directory (app->id);
+  return g_build_filename (profile_dir, app->desktop_file, NULL);
+}
+
 static char *
 ephy_web_application_get_cache_directory (const char *id)
 {
@@ -330,12 +345,40 @@ ephy_web_application_delete (const char *id)
   return TRUE;
 }
 
+/**
+ * ephy_web_application_delete_by_desktop_file_id:
+ * @desktop_file_id: the .desktop file name for the web app to be deleted, with
+ *   the extension
+ *
+ * Deletes all the data associated with a Web Application created by
+ * Epiphany.
+ *
+ * Returns: %TRUE if the web app was succesfully deleted, %FALSE otherwise
+ **/
+gboolean
+ephy_web_application_delete_by_desktop_file_id (const char *desktop_file_id)
+{
+  const char *id;
+  g_autofree char *gapp_id = NULL;
+
+  g_assert (desktop_file_id);
+
+  gapp_id = g_strdup (desktop_file_id);
+  if (g_str_has_suffix (desktop_file_id, ".desktop")) {
+    gapp_id[strlen(desktop_file_id) - strlen(".desktop")] = '\0';
+  }
+
+  id = get_app_id_from_gapplication_id (gapp_id);
+
+  return ephy_web_application_delete (id);
+}
+
 static char *
 create_desktop_file (const char *id,
                      const char *name,
                      const char *address,
                      const char *profile_dir,
-                     GdkPixbuf  *icon)
+                     const char *icon_path)
 {
   g_autofree char *filename = NULL;
   g_autoptr (GKeyFile) file = NULL;
@@ -365,18 +408,8 @@ create_desktop_file (const char *id,
   g_key_file_set_value (file, "Desktop Entry", "Type", "Application");
   g_key_file_set_value (file, "Desktop Entry", "Categories", "GNOME;GTK;");
 
-  if (icon) {
-    g_autoptr (GOutputStream) stream = NULL;
-    g_autofree char *path = NULL;
-    g_autoptr (GFile) image = NULL;
-
-    path = g_build_filename (profile_dir, EPHY_WEB_APP_ICON_NAME, NULL);
-    image = g_file_new_for_path (path);
-
-    stream = (GOutputStream *)g_file_create (image, 0, NULL, NULL);
-    gdk_pixbuf_save_to_stream (icon, stream, "png", NULL, NULL, NULL);
-    g_key_file_set_value (file, "Desktop Entry", "Icon", path);
-  }
+  if (icon_path)
+    g_key_file_set_value (file, "Desktop Entry", "Icon", icon_path);
 
   wm_class = g_strconcat (EPHY_WEB_APP_GAPPLICATION_ID_PREFIX, id, NULL);
   g_key_file_set_value (file, "Desktop Entry", "StartupWMClass", wm_class);
@@ -409,7 +442,10 @@ create_desktop_file (const char *id,
  * @id: the identifier for the new web application
  * @address: the address of the new web application
  * @name: the name for the new web application
- * @icon: the icon for the new web application
+ * @icon_pixbuf: the icon for the new web application as a #GdkPixbuf
+ * @icon_path: the path to the icon, used instead of @icon_pixbuf
+ * @install_token: the install token acquired via portal, used for
+ *   non-interactive sandboxed installation
  * @options: the options for the new web application
  *
  * Creates a new Web Application for @address.
@@ -420,14 +456,19 @@ char *
 ephy_web_application_create (const char                *id,
                              const char                *address,
                              const char                *name,
-                             GdkPixbuf                 *icon,
+                             GdkPixbuf                 *icon_pixbuf,
+                             const char                *icon_path,
+                             const char                *install_token,
                              EphyWebApplicationOptions  options)
 {
   g_autofree char *app_file = NULL;
   g_autofree char *profile_dir = NULL;
   g_autofree char *desktop_file_path = NULL;
+  g_autofree char *icon_path_owned = NULL;
   int fd;
 
+  g_return_val_if_fail (!icon_pixbuf || !icon_path, NULL);
+
   /* If there's already a WebApp profile for the contents of this
    * view, do nothing. */
   profile_dir = ephy_web_application_get_profile_directory (id);
@@ -454,8 +495,22 @@ ephy_web_application_create (const char                *id,
   }
   close (fd);
 
+  /* Write the icon to a file */
+  if (icon_pixbuf) {
+    g_autoptr (GOutputStream) stream = NULL;
+    g_autoptr (GFile) image = NULL;
+
+    icon_path_owned = g_build_filename (profile_dir, EPHY_WEB_APP_ICON_NAME, NULL);
+    image = g_file_new_for_path (icon_path_owned);
+
+    stream = (GOutputStream *)g_file_create (image, 0, NULL, NULL);
+    gdk_pixbuf_save_to_stream (icon_pixbuf, stream, "png", NULL, NULL, NULL);
+  } else {
+    icon_path_owned = g_strdup (icon_path);
+  }
+
   /* Create the deskop file. */
-  desktop_file_path = create_desktop_file (id, name, address, profile_dir, icon);
+  desktop_file_path = create_desktop_file (id, name, address, profile_dir, icon_path_owned);
   if (desktop_file_path)
     ephy_web_application_initialize_settings (profile_dir, options);
 
@@ -586,7 +641,6 @@ ephy_web_application_for_profile_directory (const char *profile_dir)
   g_auto (GStrv) argv = NULL;
   g_autoptr (GFile) file = NULL;
   g_autoptr (GFileInfo) file_info = NULL;
-  guint64 created;
   g_autoptr (GDate) date = NULL;
 
   id = get_app_id_from_profile_directory (profile_dir);
@@ -614,15 +668,22 @@ ephy_web_application_for_profile_directory (const char *profile_dir)
 
   /* FIXME: this should use TIME_CREATED but it does not seem to be working. */
   file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
-  created = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+  app->install_date_uint64 = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
 
   date = g_date_new ();
-  g_date_set_time_t (date, (time_t)created);
+  g_date_set_time_t (date, (time_t)app->install_date_uint64);
   g_date_strftime (app->install_date, 127, "%x", date);
 
   return g_steal_pointer (&app);
 }
 
+EphyWebApplication *
+ephy_web_application_for_desktop_path (const char *desktop_path)
+{
+  g_autofree char *profile_dir = g_path_get_dirname (desktop_path);
+  return ephy_web_application_for_profile_directory (profile_dir);
+}
+
 static GList *
 ephy_web_application_get_application_list_internal (gboolean only_legacy)
 {
@@ -705,7 +766,6 @@ ephy_web_application_get_legacy_application_list (void)
   return ephy_web_application_get_application_list_internal (TRUE);
 }
 
-
 /**
  * ephy_web_application_free_application_list:
  * @list: an #EphyWebApplication GList
@@ -718,6 +778,51 @@ ephy_web_application_free_application_list (GList *list)
   g_list_free_full (list, (GDestroyNotify)ephy_web_application_free);
 }
 
+/**
+ * ephy_web_application_get_desktop_id_list:
+ *
+ * Gets a list of the currently installed web applications' .desktop filenames.
+ * This is useful even though we don't have access to the actual .desktop files
+ * when running under Flatpak, because we return it over D-Bus in the
+ * WebAppProvider service.
+ *
+ * Returns: (transfer-full): a %NULL-terminated array of strings
+ **/
+char **
+ephy_web_application_get_desktop_id_list (void)
+{
+  g_autoptr (GFileEnumerator) children = NULL;
+  g_autoptr (GFile) parent_directory = NULL;
+  GPtrArray *desktop_file_ids;
+
+  parent_directory = g_file_new_for_path (g_get_user_data_dir ());
+  children = g_file_enumerate_children (parent_directory,
+                                        "standard::name",
+                                        0, NULL, NULL);
+  if (!children)
+    return NULL;
+
+  desktop_file_ids = g_ptr_array_new_with_free_func (g_free);
+  for (;;) {
+    g_autoptr (GFileInfo) info = g_file_enumerator_next_file (children, NULL, NULL);
+    const char *name;
+
+    if (!info)
+      break;
+
+    name = g_file_info_get_name (info);
+    if (g_str_has_prefix (name, get_gapplication_id_prefix ())) {
+      g_autofree char *desktop_file_id = NULL;
+      desktop_file_id = g_strconcat (name, ".desktop", NULL);
+      g_ptr_array_add (desktop_file_ids, g_steal_pointer (&desktop_file_id));
+    }
+  }
+
+  g_ptr_array_add (desktop_file_ids, NULL);
+
+  return (char **)g_ptr_array_free (desktop_file_ids, FALSE);
+}
+
 /**
  * ephy_web_application_exists:
  * @id: the potential identifier of the web application
diff --git a/lib/ephy-web-app-utils.h b/lib/ephy-web-app-utils.h
index 4f98fc678..66414b7a1 100644
--- a/lib/ephy-web-app-utils.h
+++ b/lib/ephy-web-app-utils.h
@@ -33,6 +33,7 @@ typedef struct {
   char *url;
   char *desktop_file;
   char install_date[128];
+  guint64 install_date_uint64;
 } EphyWebApplication;
 
 /**
@@ -57,20 +58,32 @@ char               *ephy_web_application_get_app_id_from_name (const char *name)
 
 const char         *ephy_web_application_get_gapplication_id_from_profile_directory (const char 
*profile_dir);
 
-char               *ephy_web_application_create (const char *id, const char *address, const char *name, 
GdkPixbuf *icon, EphyWebApplicationOptions options);
+char               *ephy_web_application_create (const char                *id,
+                                                 const char                *address,
+                                                 const char                *name,
+                                                 GdkPixbuf                 *icon_pixbuf,
+                                                 const char                *icon_path,
+                                                 const char                *install_token,
+                                                 EphyWebApplicationOptions  options);
 
 char               *ephy_web_application_ensure_for_app_info (GAppInfo *app_info);
 
 gboolean            ephy_web_application_delete (const char *id);
 
+gboolean            ephy_web_application_delete_by_desktop_file_id (const char *desktop_file_id);
+
 void                ephy_web_application_setup_from_profile_directory (const char *profile_directory);
 
 void                ephy_web_application_setup_from_desktop_file (GDesktopAppInfo *desktop_info);
 
 char               *ephy_web_application_get_profile_directory (const char *id);
 
+char               *ephy_web_application_get_desktop_path (EphyWebApplication *app);
+
 EphyWebApplication *ephy_web_application_for_profile_directory (const char *profile_dir);
 
+EphyWebApplication *ephy_web_application_for_desktop_path (const char *desktop_path);
+
 void                ephy_web_application_free (EphyWebApplication *app);
 
 gboolean            ephy_web_application_exists (const char *id);
@@ -79,6 +92,8 @@ GList              *ephy_web_application_get_application_list (void);
 
 GList              *ephy_web_application_get_legacy_application_list (void);
 
+char              **ephy_web_application_get_desktop_id_list (void);
+
 void                ephy_web_application_free_application_list (GList *list);
 
 void                ephy_web_application_initialize_settings (const char *profile_directory, 
EphyWebApplicationOptions options);
diff --git a/src/meson.build b/src/meson.build
index eaee92180..50f6710b3 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -111,7 +111,7 @@ ephy_profile_migrator = executable('ephy-profile-migrator',
 )
 
 
-codegen = gnome.gdbus_codegen('ephy-shell-search-provider-generated',
+search_provider_codegen = gnome.gdbus_codegen('ephy-shell-search-provider-generated',
   'search-provider/org.gnome.ShellSearchProvider2.xml',
   interface_prefix: 'org.gnome',
   namespace: 'Ephy'
@@ -120,7 +120,7 @@ codegen = gnome.gdbus_codegen('ephy-shell-search-provider-generated',
 search_provider_sources = [
   'search-provider/ephy-search-provider.c',
   'search-provider/ephy-search-provider-main.c',
-  codegen
+  search_provider_codegen
 ]
 
 executable('epiphany-search-provider',
@@ -131,6 +131,25 @@ executable('epiphany-search-provider',
   install_rpath: pkglibdir
 )
 
+webapp_codegen = gnome.gdbus_codegen('ephy-webapp-provider-generated',
+  'webapp-provider/org.gnome.Epiphany.WebAppProvider.xml',
+  interface_prefix: 'org.gnome.Epiphany',
+  namespace: 'Ephy'
+)
+
+webapp_provider_sources = [
+  'webapp-provider/ephy-webapp-provider.c',
+  'webapp-provider/ephy-webapp-provider-main.c',
+  webapp_codegen
+]
+
+executable('epiphany-webapp-provider',
+  webapp_provider_sources,
+  dependencies: ephymain_dep,
+  install: true,
+  install_dir: libexecdir,
+  install_rpath: pkglibdir
+)
 
 resource_files = files('resources/epiphany.gresource.xml')
 resources = gnome.compile_resources('epiphany-resources',
diff --git a/src/webapp-provider/ephy-webapp-provider-main.c b/src/webapp-provider/ephy-webapp-provider-main.c
new file mode 100644
index 000000000..36695344e
--- /dev/null
+++ b/src/webapp-provider/ephy-webapp-provider-main.c
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright (c) 2013 Igalia S.L.
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "ephy-webapp-provider.h"
+#include "ephy-file-helpers.h"
+
+#include <glib/gi18n.h>
+#include <locale.h>
+
+gint
+main (gint    argc,
+      gchar **argv)
+{
+  g_autoptr (EphyWebAppProviderService) webapp_provider = NULL;
+  int status;
+  GError *error = NULL;
+
+  g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+  g_debug ("started %s", argv[0]);
+
+  /* Initialize the i18n stuff */
+  setlocale (LC_ALL, "");
+  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+  textdomain (GETTEXT_PACKAGE);
+
+  if (!ephy_file_helpers_init (NULL, 0, &error)) {
+    g_printerr ("%s\n", error->message);
+    g_error_free (error);
+
+    return 1;
+  }
+
+  webapp_provider = ephy_web_app_provider_service_new ();
+  status = g_application_run (G_APPLICATION (webapp_provider), argc, argv);
+
+  ephy_file_helpers_shutdown ();
+
+  g_debug ("stopping %s with status %d", argv[0], status);
+
+  return status;
+}
diff --git a/src/webapp-provider/ephy-webapp-provider.c b/src/webapp-provider/ephy-webapp-provider.c
new file mode 100644
index 000000000..24f086c66
--- /dev/null
+++ b/src/webapp-provider/ephy-webapp-provider.c
@@ -0,0 +1,241 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright (c) 2021 Matthew Leeds <mwleeds protonmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "ephy-webapp-provider.h"
+
+#include "ephy-web-app-utils.h"
+#include "ephy-flatpak-utils.h"
+
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+struct _EphyWebAppProviderService {
+  GApplication parent_instance;
+
+  EphyWebAppProvider *skeleton;
+};
+
+struct _EphyWebAppProviderServiceClass {
+  GApplicationClass parent_class;
+};
+
+G_DEFINE_TYPE (EphyWebAppProviderService, ephy_web_app_provider_service, G_TYPE_APPLICATION)
+
+#define INACTIVITY_TIMEOUT 60 * 1000 /* One minute, in milliseconds */
+
+static gboolean
+handle_get_installed_apps (EphyWebAppProvider        *skeleton,
+                           GDBusMethodInvocation     *invocation,
+                           EphyWebAppProviderService *self)
+{
+  g_auto(GStrv) desktop_ids = NULL;
+
+  g_debug ("%s", G_STRFUNC);
+
+  g_application_hold (G_APPLICATION (self));
+
+  desktop_ids = ephy_web_application_get_desktop_id_list ();
+
+  ephy_web_app_provider_complete_get_installed_apps (skeleton, invocation,
+                                                     (const gchar * const *)desktop_ids);
+
+  g_application_release (G_APPLICATION (self));
+
+  return TRUE;
+}
+
+static gboolean
+handle_install (EphyWebAppProvider        *skeleton,
+                GDBusMethodInvocation     *invocation,
+                char                      *url,
+                char                      *name,
+                char                      *install_token,
+                EphyWebAppProviderService *self)
+{
+  g_autofree char *id = NULL;
+  g_autofree char *desktop_path = NULL;
+
+  g_debug ("%s", G_STRFUNC);
+
+  g_application_hold (G_APPLICATION (self));
+
+  /* We need an install token acquired by a trusted system component such as
+   * gnome-software because otherwise the Flatpak/Snap sandbox prevents us from
+   * installing the app without using a portal (which would not be appropriate
+   * since Epiphany is not the focused application). We use the same code path
+   * when not running under a sandbox too.
+   */
+  if (!install_token || *install_token == '\0') {
+    g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+                                           "The install_token is required for the Install() method");
+    goto out;
+  }
+  if (!g_uri_is_valid (url, G_URI_FLAGS_NONE, NULL)) {
+    g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+                                           "The url passed was not valid");
+    goto out;
+  }
+  if (!name || *name == '\0') {
+    g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+                                           "The name passed was not valid");
+    goto out;
+  }
+
+  id = ephy_web_application_get_app_id_from_name (name);
+
+  desktop_path = ephy_web_application_create (id, url, name,
+                                              NULL, NULL, /* icon_pixbuf, icon_path */
+                                              install_token,
+                                              EPHY_WEB_APPLICATION_NONE);
+  if (!desktop_path) {
+    g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                                           "Installing the web application '%s' (%s) failed", name, url);
+    goto out;
+  }
+
+  ephy_web_app_provider_complete_install (skeleton, invocation);
+
+out:
+  g_application_release (G_APPLICATION (self));
+
+  return TRUE;
+}
+
+static gboolean
+handle_uninstall (EphyWebAppProvider        *skeleton,
+                  GDBusMethodInvocation     *invocation,
+                  char                      *desktop_file_id,
+                  EphyWebAppProviderService *self)
+{
+  g_debug ("%s", G_STRFUNC);
+
+  g_application_hold (G_APPLICATION (self));
+
+  if (!desktop_file_id || !g_str_has_suffix (desktop_file_id, ".desktop")) {
+    g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
+                                           "The desktop file id passed was not valid");
+    goto out;
+  }
+
+  if (!ephy_web_application_delete_by_desktop_file_id (desktop_file_id)) {
+    g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+                                           "The web application '%s' could not be deleted", desktop_file_id);
+    goto out;
+  }
+
+  ephy_web_app_provider_complete_uninstall (skeleton, invocation);
+
+out:
+  g_application_release (G_APPLICATION (self));
+
+  return TRUE;
+}
+
+static void
+ephy_web_app_provider_service_init (EphyWebAppProviderService *self)
+{
+  g_application_set_flags (G_APPLICATION (self), G_APPLICATION_IS_SERVICE);
+
+  g_application_set_inactivity_timeout (G_APPLICATION (self), INACTIVITY_TIMEOUT);
+}
+
+static gboolean
+ephy_web_app_provider_service_dbus_register (GApplication     *application,
+                                             GDBusConnection  *connection,
+                                             const gchar      *object_path,
+                                             GError          **error)
+{
+  EphyWebAppProviderService *self;
+
+  g_debug ("registering at object path %s", object_path);
+
+  if (!G_APPLICATION_CLASS (ephy_web_app_provider_service_parent_class)->dbus_register (application,
+                                                                                        connection,
+                                                                                        object_path,
+                                                                                        error))
+    return FALSE;
+
+  self = EPHY_WEB_APP_PROVIDER_SERVICE (application);
+  self->skeleton = ephy_web_app_provider_skeleton_new ();
+
+  ephy_web_app_provider_set_version (EPHY_WEB_APP_PROVIDER (self->skeleton), 1);
+
+  g_signal_connect (self->skeleton, "handle-get-installed-apps",
+                    G_CALLBACK (handle_get_installed_apps), self);
+  g_signal_connect (self->skeleton, "handle-install",
+                    G_CALLBACK (handle_install), self);
+  g_signal_connect (self->skeleton, "handle-uninstall",
+                    G_CALLBACK (handle_uninstall), self);
+
+  return g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton),
+                                           connection, object_path, error);
+}
+
+static void
+ephy_web_app_provider_service_dbus_unregister (GApplication    *application,
+                                               GDBusConnection *connection,
+                                               const gchar     *object_path)
+{
+  EphyWebAppProviderService *self;
+  GDBusInterfaceSkeleton *skeleton;
+
+  g_debug ("unregistering at object path %s", object_path);
+
+  self = EPHY_WEB_APP_PROVIDER_SERVICE (application);
+  skeleton = G_DBUS_INTERFACE_SKELETON (self->skeleton);
+  if (g_dbus_interface_skeleton_has_connection (skeleton, connection))
+    g_dbus_interface_skeleton_unexport_from_connection (skeleton, connection);
+
+  g_clear_object (&self->skeleton);
+
+  G_APPLICATION_CLASS (ephy_web_app_provider_service_parent_class)->dbus_unregister (application,
+                                                                                     connection,
+                                                                                     object_path);
+}
+
+static void
+ephy_web_app_provider_service_dispose (GObject *object)
+{
+  G_OBJECT_CLASS (ephy_web_app_provider_service_parent_class)->dispose (object);
+}
+
+static void
+ephy_web_app_provider_service_class_init (EphyWebAppProviderServiceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GApplicationClass *application_class = G_APPLICATION_CLASS (klass);
+
+  object_class->dispose = ephy_web_app_provider_service_dispose;
+
+  application_class->dbus_register = ephy_web_app_provider_service_dbus_register;
+  application_class->dbus_unregister = ephy_web_app_provider_service_dbus_unregister;
+}
+
+EphyWebAppProviderService *
+ephy_web_app_provider_service_new (void)
+{
+  g_autofree gchar *app_id = g_strconcat (APPLICATION_ID, ".WebAppProvider", NULL);
+
+  return g_object_new (EPHY_TYPE_WEB_APP_PROVIDER_SERVICE,
+                       "application-id", app_id,
+                       NULL);
+}
diff --git a/src/webapp-provider/ephy-webapp-provider.h b/src/webapp-provider/ephy-webapp-provider.h
new file mode 100644
index 000000000..1286cf0e0
--- /dev/null
+++ b/src/webapp-provider/ephy-webapp-provider.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright (c) 2021 Matthew Leeds <mwleeds protonmail com>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ephy-webapp-provider-generated.h"
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_WEB_APP_PROVIDER_SERVICE (ephy_web_app_provider_service_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyWebAppProviderService, ephy_web_app_provider_service, EPHY, 
WEB_APP_PROVIDER_SERVICE, GApplication)
+
+EphyWebAppProviderService *ephy_web_app_provider_service_new (void);
+
+G_END_DECLS
diff --git a/src/webapp-provider/org.gnome.Epiphany.WebAppProvider.xml 
b/src/webapp-provider/org.gnome.Epiphany.WebAppProvider.xml
new file mode 100644
index 000000000..9ff86cd55
--- /dev/null
+++ b/src/webapp-provider/org.gnome.Epiphany.WebAppProvider.xml
@@ -0,0 +1,73 @@
+<!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"/>
+
+    <!--
+        GetInstalledApps:
+        @desktop_file_ids: An array of .desktop file names, one for each
+          installed web app
+
+        Returns the set of installed Epiphany web applications. The caller can
+        use them with g_desktop_app_info_new() if outside the sandbox.
+    -->
+    <method name="GetInstalledApps">
+      <arg type="as" name="webapps" direction="out" />
+    </method>
+
+    <!--
+        Install:
+        @url: the URL of the web app
+        @name: the human readable name of the web app
+        @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 must be provided so that Epiphany can complete the
+        installation without a user facing dialog. The icon given to
+        org.freedesktop.portal.InstallDynamicLauncher.RequestInstallToken() will
+        be used, and the name given to that method should match the @name given here.
+    -->
+    <method name="Install">
+      <arg type="s" name="url" direction="in" />
+      <arg type="s" name="name" direction="in" />
+      <arg type="s" name="install_token" direction="in" />
+    </method>
+
+    <!--
+        Uninstall:
+        @desktop_file_id: the filename of the .desktop file for an installed web app
+
+        Uninstalls a web app. Note that the @desktop_file_id is just a filename
+        not a full path, and it's the same one returned by the
+        GetInstalledWebApps() method.
+
+        An error will be returned if the specified web app is not installed.
+    -->
+    <method name="Uninstall">
+      <arg type="s" name="desktop_path" direction="in" />
+    </method>
+  </interface>
+</node>
diff --git a/src/window-commands.c b/src/window-commands.c
index 8b138257d..07ed22cea 100644
--- a/src/window-commands.c
+++ b/src/window-commands.c
@@ -1788,6 +1788,7 @@ save_as_application_proceed (EphyApplicationDialogData *data)
                                               webkit_web_view_get_uri (WEBKIT_WEB_VIEW (data->view)),
                                               app_name,
                                               gtk_image_get_pixbuf (GTK_IMAGE (data->image)),
+                                              NULL, NULL, /* icon_path, install_token */
                                               data->webapp_options);
 
   if (desktop_file)
diff --git a/tests/ephy-web-app-utils-test.c b/tests/ephy-web-app-utils-test.c
index 5026388db..2278b5f89 100644
--- a/tests/ephy-web-app-utils-test.c
+++ b/tests/ephy-web-app-utils-test.c
@@ -68,7 +68,7 @@ test_web_app_lifetime (void)
 
     /* Test creation */
     id = ephy_web_application_get_app_id_from_name (test.name);
-    desktop_file = ephy_web_application_create (id, test.url, test.name, NULL, EPHY_WEB_APPLICATION_NONE);
+    desktop_file = ephy_web_application_create (id, test.url, test.name, NULL, NULL, NULL, 
EPHY_WEB_APPLICATION_NONE);
     g_assert_true (g_str_has_prefix (desktop_file, ephy_profile_dir ()));
     g_assert_true (g_file_test (desktop_file, G_FILE_TEST_EXISTS));
 


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