[epiphany/mwleeds/webapp-dbus-api: 6/17] Migrate web apps to portal locations




commit e7ce49611ed4c852db0ac32189dc46775db57ea8
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Wed Jan 19 19:54:39 2022 -0800

    Migrate web apps to portal locations
    
    This is yet another migration of web apps. This time we are changing the
    GApplication ID to make it simpler and more D-Bus friendly. We also have
    to ensure the GApp ID has the app ID as a prefix to be able to use the
    dynamic launcher portal (and in order to own the right bus name when the
    web app is run). We also migrate the web apps' desktop files and
    icons to the locations they'd be in if we had used the portal to create
    them, so that going forward they will all be in one place and things
    should be simpler.

 embed/ephy-about-handler.c                   |  15 +-
 lib/ephy-profile-utils.h                     |   2 +-
 lib/ephy-web-app-utils.c                     | 108 +++-----------
 src/profile-migrator/ephy-profile-migrator.c | 208 +++++++++++++++++++++++++++
 4 files changed, 235 insertions(+), 98 deletions(-)
---
diff --git a/embed/ephy-about-handler.c b/embed/ephy-about-handler.c
index 9abfe65fc..ea6658710 100644
--- a/embed/ephy-about-handler.c
+++ b/embed/ephy-about-handler.c
@@ -272,15 +272,12 @@ handle_applications_finished_cb (EphyAboutHandler       *handler,
       if (ephy_web_application_is_system (app))
         continue;
 
-      /* Most of these fields are at least semi-trusted. The web app suggests
-       * its own title, which gets used in the app ID and icon URL, but it ought
-       * to be safe because we validate that it is a valid component of a
-       * GApplication ID, which should not permit anything nasty. The icon URL
-       * could be changed by the user to something else after web app creation,
-       * though, so better not fully trust it. Then the app name and the main
-       * URL could contain contain anything at all, so those need to be encoded
-       * for sure. Install date should be fine because it's constructed by
-       * Epiphany.
+      /* Most of these fields are at least semi-trusted. The app ID was chosen
+       * by ephy so it's safe. The icon URL could be changed by the user to
+       * something else after web app creation, though, so better not fully
+       * trust it. Then the app name and the main URL could contain contain
+       * anything at all, so those need to be encoded for sure. Install date
+       * should be fine because it's constructed by Epiphany.
        */
       encoded_icon_url = ephy_encode_for_html_attribute (app->icon_url);
       encoded_name = ephy_encode_for_html_entity (app->name);
diff --git a/lib/ephy-profile-utils.h b/lib/ephy-profile-utils.h
index 6da1ef834..31181318c 100644
--- a/lib/ephy-profile-utils.h
+++ b/lib/ephy-profile-utils.h
@@ -24,7 +24,7 @@
 
 G_BEGIN_DECLS
 
-#define EPHY_PROFILE_MIGRATION_VERSION 36
+#define EPHY_PROFILE_MIGRATION_VERSION 37
 #define EPHY_INSECURE_PASSWORDS_MIGRATION_VERSION 11
 #define EPHY_FIREFOX_SYNC_PASSWORDS_MIGRATION_VERSION 19
 #define EPHY_TARGET_ORIGIN_MIGRATION_VERSION 21
diff --git a/lib/ephy-web-app-utils.c b/lib/ephy-web-app-utils.c
index 233999942..9abfc26f0 100644
--- a/lib/ephy-web-app-utils.c
+++ b/lib/ephy-web-app-utils.c
@@ -23,6 +23,7 @@
 
 #include "ephy-debug.h"
 #include "ephy-file-helpers.h"
+#include "ephy-flatpak-utils.h"
 #include "ephy-profile-utils.h"
 #include "ephy-settings.h"
 
@@ -35,45 +36,31 @@
 
 /* Web apps are installed in the default data dir of the user. Every
  * app has its own profile directory. To create a web app, an ID needs
- * to be generated using the given name as <normalized-name>-checksum.
+ * to be generated using the checksum of the given name.
  * The ID is used to uniquely identify the app.
  *
  *  - Name: the user-visible pretty app name
  *  - Normalized name: lowercase name
- *  - Checksum: SHA-1 of name
- *  - ID: <normalized-name>-<checksum>
- *  - GApplication ID: see below
- *  - Profile directory: <gapplication-id>
- *  - Desktop file: <profile-dir>/<gapplication-id>.desktop
+ *  - ID: SHA-1 of name
+ *  - Epiphany App ID: org.gnome.Epiphany or org.gnome.Epiphany.Devel or org.gnome.Epiphany.Canary
+ *  - GApplication ID: <epiphany-app-id>.WebApp_<id>
+ *  - Profile directory: <user-data-dir>/<gapplication-id>
+ *  - Desktop file: <user-data-dir>/xdg-desktop-portal/applications/<gapplication-id>.desktop
+ *  - Icon: <user-data-dir>/xdg-desktop-portal/icon/<gapplication-id>.png
+ *  - Sym link: <user-data-dir>/applications/<gapplication-id>.desktop
  *
  * Note that our ID and GApplication ID are different. Yes, this is confusing.
  *
- * For GApplication ID, there are two cases:
- *
- *  - If <id> is safe to use in a D-Bus identifier,
- *    then: GApplication ID is org.gnome.Epiphany.WebApp-<id>
- *  - otherwise: GAppliation ID is org.gnome.Epiphany.WebApp-<checksum>
- *
  * System web applications have a profile dir without a desktop file.
  */
 
-#define EPHY_WEB_APP_GAPPLICATION_ID_PREFIX "org.gnome.Epiphany.WebApp-"
+/* The GApplication ID must begin with the app ID for the dynamic launcher portal to work */
+static const char *EPHY_WEB_APP_GAPPLICATION_ID_PREFIX = APPLICATION_ID ".WebApp_";
 
 char *
 ephy_web_application_get_app_id_from_name (const char *name)
 {
-  g_autofree char *normal_id = NULL;
-  g_autofree char *checksum = NULL;
-
-  /* FIXME: We should not use hyphens in app IDs. Sadly, changing this requires
-   * a new migration.
-   */
-  normal_id = g_utf8_strdown (name, -1);
-  g_strdelimit (normal_id, " ", '-');
-  g_strdelimit (normal_id, G_DIR_SEPARATOR_S, '-');
-
-  checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA1, name, -1);
-  return g_strdup_printf ("%s-%s", normal_id, checksum);
+  return g_compute_checksum_for_string (G_CHECKSUM_SHA1, name, -1);
 }
 
 static char *
@@ -95,7 +82,8 @@ static const char *
 get_app_id_from_gapplication_id (const char *name)
 {
   if (!g_str_has_prefix (name, EPHY_WEB_APP_GAPPLICATION_ID_PREFIX)) {
-    g_warning ("GApplication ID %s does not begin with required prefix %s", name, 
EPHY_WEB_APP_GAPPLICATION_ID_PREFIX);
+    g_warning ("GApplication ID %s does not begin with required prefix %s",
+               name, EPHY_WEB_APP_GAPPLICATION_ID_PREFIX);
     return NULL;
   }
 
@@ -106,47 +94,10 @@ static char *
 get_gapplication_id_from_id (const char *id)
 {
   g_autofree char *gapplication_id = NULL;
-  const char *final_hyphen;
-  const char *checksum;
-
-  /* FIXME: Ideally we would convert hyphens to underscores here, because
-   * hyphens are not very friendly to D-Bus. However, changing this
-   * would require a new profile migration, because the GApplication ID
-   * must exactly match the desktop file basename.
-   *
-   * If we're willing to do another migration in the future, then we
-   * should drop this path and always return just the prefix plus hash,
-   * using underscores rather than hyphens.
-   */
-  gapplication_id = g_strconcat (EPHY_WEB_APP_GAPPLICATION_ID_PREFIX, id, NULL);
-  if (g_application_id_is_valid (gapplication_id))
-    return g_steal_pointer (&gapplication_id);
 
-  /* Split ID into: <normalized-name>-<checksum> */
-  final_hyphen = strrchr (id, '-');
-  if (!final_hyphen) {
-    g_warning ("Web app ID %s is broken: must contain a hyphen", id);
-    return NULL;
-  }
-  checksum = final_hyphen + 1;
-
-  if (*checksum == '\0') {
-    g_warning ("Web app ID %s is broken: should end with checksum, not hyphen", id);
-    return NULL;
-  }
-
-  /* We'll simply omit the <normalized-name> from the app ID, in order
-   * to avoid problematic characters. Ideally we would use an underscore
-   * here too, rather than a hyphen, but let's be consistent with
-   * existing web apps.
-   */
-  g_clear_pointer (&gapplication_id, g_free);
-  gapplication_id = g_strconcat (EPHY_WEB_APP_GAPPLICATION_ID_PREFIX, checksum, NULL);
-
-  if (!g_application_id_is_valid (gapplication_id)) {
-    g_warning ("Web app ID %s is broken: derived GApplication ID %s is not a valid app ID (is the final 
component alphanumeric?)", id, gapplication_id);
-    return NULL;
-  }
+  gapplication_id = g_strconcat (EPHY_WEB_APP_GAPPLICATION_ID_PREFIX, id, NULL);
+  if (!g_application_id_is_valid (gapplication_id))
+    g_error ("Failed to get GApplication ID from app ID %s", id);
 
   return g_steal_pointer (&gapplication_id);
 }
@@ -157,9 +108,6 @@ get_app_profile_directory_name (const char *id)
   g_autofree char *gapplication_id = NULL;
 
   gapplication_id = get_gapplication_id_from_id (id);
-  if (!gapplication_id)
-    g_error ("Failed to get GApplication ID from app ID %s", id);
-
   return get_encoded_path (gapplication_id);
 }
 
@@ -173,8 +121,6 @@ get_app_desktop_filename (const char *id)
    * basename. Don't overthink this or stuff will break, e.g. GNotification.
    */
   gapplication_id = get_gapplication_id_from_id (id);
-  if (!gapplication_id)
-    g_error ("Failed to get GApplication ID from app ID %s", id);
 
   filename = g_strconcat (gapplication_id, ".desktop", NULL);
   return get_encoded_path (filename);
@@ -199,7 +145,8 @@ ephy_web_application_get_gapplication_id_from_profile_directory (const char *pro
     name += strlen ("app-");
 
   if (!g_str_has_prefix (name, EPHY_WEB_APP_GAPPLICATION_ID_PREFIX)) {
-    g_warning ("Profile directory %s does not begin with required web app prefix %s", profile_dir, 
EPHY_WEB_APP_GAPPLICATION_ID_PREFIX);
+    g_warning ("Profile directory %s does not begin with required web app prefix %s",
+               profile_dir, EPHY_WEB_APP_GAPPLICATION_ID_PREFIX);
     return NULL;
   }
 
@@ -242,21 +189,6 @@ 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)
 {
@@ -816,7 +748,7 @@ ephy_web_application_get_desktop_id_list (void)
       break;
 
     name = g_file_info_get_name (info);
-    if (g_str_has_prefix (name, get_gapplication_id_prefix ())) {
+    if (g_str_has_prefix (name, EPHY_WEB_APP_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));
diff --git a/src/profile-migrator/ephy-profile-migrator.c b/src/profile-migrator/ephy-profile-migrator.c
index 9e5f54dfd..f69c47df0 100644
--- a/src/profile-migrator/ephy-profile-migrator.c
+++ b/src/profile-migrator/ephy-profile-migrator.c
@@ -24,6 +24,7 @@
 #include "ephy-debug.h"
 #include "ephy-file-helpers.h"
 #include "ephy-filters-manager.h"
+#include "ephy-flatpak-utils.h"
 #include "ephy-history-service.h"
 #include "ephy-password-manager.h"
 #include "ephy-prefs.h"
@@ -1474,6 +1475,212 @@ migrate_search_engines_to_vardict (void)
   g_settings_set_value (EPHY_SETTINGS_MAIN, EPHY_PREFS_SEARCH_ENGINES, g_variant_builder_end 
(&search_engines_builder));
 }
 
+static void
+migrate_pre_flatpak_webapps (void)
+{
+  /* Rename webapp folder, desktop file name and content from
+   *  org.gnome.Epiphany.WebApp-${id}
+   * to
+   *  ${APP_ID}.WebApp_${hash}
+   *
+   * The app id sometimes has a Devel or Canary suffix, and we need the
+   * GApplication ID to have the app id as a prefix for the dynamic launcher
+   * portal to work. Also change the hyphen to an underscore for friendliness
+   * with D-Bus. The ${id} sometimes has the app name so that is what
+   * differentiates it from ${hash}. Finally, we also move the .desktop file
+   * and the icon to the locations used by xdg-desktop-portal, which is what we
+   * will use for creating the desktop files going forward.
+   */
+  g_autoptr (GFile) parent_directory = NULL;
+  g_autoptr (GFileEnumerator) children = NULL;
+  g_autoptr (GFileInfo) info = NULL;
+  g_autoptr (GError) error = NULL;
+  const char *parent_directory_path = g_get_user_data_dir ();
+  g_autofree char *portal_desktop_dir = NULL;
+  g_autofree char *portal_icon_dir = NULL;
+
+  /* This migration is both not needed and not possible from within Flatpak. */
+  if (ephy_is_running_inside_sandbox ())
+    return;
+
+  portal_desktop_dir = g_build_filename (g_get_user_data_dir (), "xdg-desktop-portal", "applications", NULL);
+  portal_icon_dir = g_build_filename (g_get_user_data_dir (), "xdg-desktop-portal", "icons", "192x192", 
NULL);
+  if (g_mkdir_with_parents (portal_desktop_dir, 0700) == -1)
+    g_warning ("Failed to create portal desktop file dir: %s", g_strerror (errno));
+  if (g_mkdir_with_parents (portal_icon_dir, 0700) == -1)
+    g_warning ("Failed to create portal icon dir: %s", g_strerror (errno));
+
+  parent_directory = g_file_new_for_path (parent_directory_path);
+  children = g_file_enumerate_children (parent_directory,
+                                        "standard::name",
+                                        0, NULL, &error);
+  if (!children) {
+    g_warning ("Cannot enumerate profile directory: %s", error->message);
+    return;
+  }
+
+  info = g_file_enumerator_next_file (children, NULL, &error);
+  if (!info) {
+    g_warning ("Cannot enumerate profile directory: %s", error->message);
+    return;
+  }
+
+  while (info) {
+    const char *name;
+
+    name = g_file_info_get_name (info);
+    if (!g_str_has_prefix (name, "org.gnome.Epiphany.WebApp-"))
+      goto next;
+
+    /* the directory is either org.gnome.Epiphany.WebApp-name-checksum or
+     * org.gnome.Epiphany.WebApp-checksum but unfortunately name can include a
+     * hyphen
+     */
+    {
+      g_autofree char *old_desktop_file_name = NULL;
+      g_autofree char *new_desktop_file_name = NULL;
+      g_autofree char *old_desktop_path_name = NULL;
+      g_autofree char *new_desktop_path_name = NULL;
+      g_autofree char *app_desktop_file_name = NULL;
+      g_autofree char *old_cache_path = NULL;
+      g_autofree char *new_cache_path = NULL;
+      g_autofree char *old_config_path = NULL;
+      g_autofree char *new_config_path = NULL;
+      g_autofree char *old_data = NULL;
+      g_autofree char *new_data = NULL;
+      g_autofree char *icon = NULL;
+      g_autofree char *new_icon = NULL;
+      g_autofree char *tryexec = NULL;
+      g_autofree char *relative_path = NULL;
+      g_autoptr (GKeyFile) keyfile = NULL;
+      g_autoptr (GFile) app_link_file = NULL;
+      g_autoptr (GFile) old_desktop_file = NULL;
+      g_autoptr (GFileInfo) file_info = NULL;
+      const char *final_hyphen, *checksum;
+      g_autofree char *new_name = NULL;
+
+      final_hyphen = strrchr (name, '-');
+      g_assert (final_hyphen);
+      checksum = final_hyphen + 1;
+      if (*checksum == '\0') {
+        g_warning ("Web app ID %s is broken: should end with checksum, not hyphen", name);
+        goto next;
+      }
+      new_name = g_strconcat (APPLICATION_ID, ".WebApp_", checksum, NULL);
+
+      /* Rename profile directory */
+      old_desktop_path_name = g_strconcat (parent_directory_path, G_DIR_SEPARATOR_S, name, NULL);
+      new_desktop_path_name = g_strconcat (parent_directory_path, G_DIR_SEPARATOR_S, new_name, NULL);
+      LOG ("migrate_profile_directories: moving '%s' to '%s'", old_desktop_path_name, new_desktop_path_name);
+      if (g_rename (old_desktop_path_name, new_desktop_path_name) == -1) {
+        g_warning ("Cannot rename web application directory from '%s' to '%s'", old_desktop_path_name, 
new_desktop_path_name);
+        goto next;
+      }
+
+      /* Rename cache directory */
+      old_cache_path = g_strconcat (g_get_user_cache_dir (), G_DIR_SEPARATOR_S, name, NULL);
+      new_cache_path = g_strconcat (g_get_user_cache_dir (), G_DIR_SEPARATOR_S, new_name, NULL);
+      if (g_file_test (old_cache_path, G_FILE_TEST_IS_DIR) &&
+          g_rename (old_cache_path, new_cache_path) == -1) {
+        g_warning ("Cannot rename web application cache directory from '%s' to '%s'", old_cache_path, 
new_cache_path);
+      }
+
+      /* Rename config directory */
+      old_config_path = g_strconcat (g_get_user_config_dir (), G_DIR_SEPARATOR_S, name, NULL);
+      new_config_path = g_strconcat (g_get_user_config_dir (), G_DIR_SEPARATOR_S, new_name, NULL);
+      if (g_file_test (old_config_path, G_FILE_TEST_IS_DIR) &&
+          g_rename (old_config_path, new_config_path) == -1) {
+        g_warning ("Cannot rename web application config directory from '%s' to '%s'", old_config_path, 
new_config_path);
+      }
+
+      /* Create new desktop file */
+      old_desktop_file_name = g_strconcat (parent_directory_path, G_DIR_SEPARATOR_S, new_name, 
G_DIR_SEPARATOR_S, name, ".desktop", NULL);
+      new_desktop_file_name = g_strconcat (portal_desktop_dir, G_DIR_SEPARATOR_S, new_name, ".desktop", 
NULL);
+
+      /* Fix paths in desktop file */
+      if (!g_file_get_contents (old_desktop_file_name, &old_data, NULL, &error)) {
+        g_warning ("Cannot read contents of '%s': %s", old_desktop_file_name, error->message);
+        goto next;
+      }
+      new_data = ephy_string_find_and_replace ((const char *)old_data, name, new_name);
+      keyfile = g_key_file_new ();
+      if (!g_key_file_load_from_data (keyfile, new_data, -1,
+                                      G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
+                                      &error)) {
+        g_warning ("Cannot load old desktop file %s as key file: %s", old_desktop_file_name, error->message);
+        g_warning ("Key file contents:\n%s\n", (const char *)new_data);
+        goto next;
+      }
+      /* The StartupWMClass will not always have been corrected by the
+       * find-and-replace above, since it has the form
+       * org.gnome.Epiphany.WebApp-<normalized_name>-<checksum> whereas the
+       * gapplication id sometimes omits the <normalized_name> part
+       */
+      g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_STARTUP_WM_CLASS, 
new_name);
+
+      /* Correct the Icon= key */
+      icon = g_key_file_get_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, &error);
+      if (icon == NULL) {
+        g_warning ("Failed to get Icon key from %s: %s", old_desktop_file_name, error->message);
+        g_clear_error (&error);
+      } else if (g_str_has_suffix (icon, EPHY_WEB_APP_ICON_NAME)) {
+        new_icon = g_strconcat (portal_icon_dir, G_DIR_SEPARATOR_S, new_name, ".png", NULL);
+        if (g_rename (icon, new_icon) == -1) {
+          g_warning ("Cannot rename icon from '%s' to '%s'", icon, new_icon);
+          goto next;
+        }
+        LOG ("migrate_profile_directories: setting Icon to %s", new_icon);
+        g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, new_icon);
+      }
+
+      tryexec = g_key_file_get_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, 
NULL);
+      if (tryexec == NULL) {
+        g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, 
"epiphany");
+      }
+
+      if (!g_key_file_save_to_file (keyfile, new_desktop_file_name, &error)) {
+        g_warning ("Failed to save desktop file %s", error->message);
+        goto next;
+      }
+
+      old_desktop_file = g_file_new_for_path (old_desktop_file_name);
+      if (!g_file_delete (old_desktop_file, NULL, &error)) {
+        g_warning ("Cannot delete old desktop file: %s", error->message);
+        goto next;
+      }
+
+      /* Remove old symlink */
+      app_desktop_file_name = g_strconcat (g_get_user_data_dir (), G_DIR_SEPARATOR_S, "applications",
+                                           G_DIR_SEPARATOR_S, name, ".desktop", NULL);
+      if (g_remove (app_desktop_file_name) == -1) {
+        g_warning ("Cannot remove old desktop file symlink '%s'", app_desktop_file_name);
+        goto next;
+      }
+
+      /* Create new symlink */
+      g_free (app_desktop_file_name);
+      app_desktop_file_name = g_strconcat (g_get_user_data_dir (), G_DIR_SEPARATOR_S, "applications",
+                                           G_DIR_SEPARATOR_S, new_name, ".desktop", NULL);
+      app_link_file = g_file_new_for_path (app_desktop_file_name);
+      relative_path = g_strconcat ("..", G_DIR_SEPARATOR_S, "xdg-desktop-portal", G_DIR_SEPARATOR_S,
+                                   "applications", G_DIR_SEPARATOR_S, new_name, ".desktop", NULL);
+
+      if (!g_file_make_symbolic_link (app_link_file, relative_path, NULL, &error)) {
+        g_warning ("Cannot create symlink to new desktop file %s: %s", new_desktop_file_name, 
error->message);
+        goto next;
+      }
+    }
+
+next:
+    g_clear_error (&error);
+    info = g_file_enumerator_next_file (children, NULL, &error);
+    if (!info && error) {
+      g_warning ("Cannot enumerate next file: %s", error->message);
+      return;
+    }
+  }
+}
+
 static void
 migrate_nothing (void)
 {
@@ -1526,6 +1733,7 @@ const EphyProfileMigrator migrators[] = {
   /* 35 */ migrate_webapp_names,
   /* FIXME: Please also remove the "search-engines" deprecated gschema key when dropping this migrator in 
the future. */
   /* 36 */ migrate_search_engines_to_vardict,
+  /* 37 */ migrate_pre_flatpak_webapps,
 };
 
 static gboolean


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