diff --git a/org.gnome.Geary.json b/org.gnome.Geary.json index 99e41612..7f55fb1f 100644 --- a/org.gnome.Geary.json +++ b/org.gnome.Geary.json @@ -25,6 +25,8 @@ "--talk-name=org.gnome.evolution.dataserver.Sources5", "--filesystem=xdg-cache/evolution/addressbook:ro", "--metadata=X-DConf=migrate-path=/org/gnome/Geary/", + "--filesystem=~/.config/geary:ro", + "--filesystem=~/.var/app/org.gnome.Geary/config/geary:ro", "--filesystem=/tmp" ], "cleanup": [ diff --git a/org.gnome.Geary.yaml b/org.gnome.Geary.yaml index 0dd89a18..f5d200f6 100644 --- a/org.gnome.Geary.yaml +++ b/org.gnome.Geary.yaml @@ -57,6 +57,10 @@ finish-args: # Migrate GSettings into the sandbox - "--metadata=X-DConf=migrate-path=/org/gnome/Geary/" + # Migrate Geary settings from other release versions + - "--filesystem=~/.config/geary:ro" + - "--filesystem=~/.var/app/org.gnome.Geary/config/geary:ro" + # Let view source keep on working as-sis for now. Bug 779311. */ - "--filesystem=/tmp" diff --git a/src/client/application/application-client.vala b/src/client/application/application-client.vala index e7f980c0..db8cdfdc 100644 --- a/src/client/application/application-client.vala +++ b/src/client/application/application-client.vala @@ -43,6 +43,11 @@ public class Application.Client : Gtk.Application { public const string SOURCE_ROOT_DIR = _SOURCE_ROOT_DIR; public const string BUILD_ROOT_DIR = _BUILD_ROOT_DIR; + // keep these in sync with meson_options.txt + public const string PROFILE_RELEASE = "release"; + public const string PROFILE_BETA = "beta"; + public const string PROFILE_DEVEL = "development"; + public const string[] AUTHORS = { "Jim Nelson ", "Eric Gregory ", @@ -212,6 +217,14 @@ public class Application.Client : Gtk.Application { } } + /** + * Determines if Geary appears to be running under Flatpak. + * + * If this returns `true`, then the application instance + * appears to be running inside a Flatpak sandbox. + */ + public bool is_flatpak_sandboxed { get; private set; } + /** * The global controller for this application instance. * @@ -317,6 +330,7 @@ public class Application.Client : Gtk.Application { ); this.add_main_option_entries(OPTION_ENTRIES); this.window_removed.connect_after(on_window_removed); + this.is_flatpak_sandboxed = GLib.FileUtils.test("/.flatpak-info", EXISTS); } public override bool local_command_line(ref unowned string[] args, @@ -759,25 +773,25 @@ public class Application.Client : Gtk.Application { } } - /** Returns the application's base user configuration directory. */ - public GLib.File get_user_config_directory() { + /** Returns the application's base home configuration directory. */ + public GLib.File get_home_config_directory() { return GLib.File.new_for_path( Environment.get_user_config_dir() - ).get_child("geary"); + ).get_child(get_geary_home_dir_name()); } - /** Returns the application's base user cache directory. */ - public GLib.File get_user_cache_directory() { + /** Returns the application's base home cache directory. */ + public GLib.File get_home_cache_directory() { return GLib.File.new_for_path( GLib.Environment.get_user_cache_dir() - ).get_child("geary"); + ).get_child(get_geary_home_dir_name()); } - /** Returns the application's base user data directory. */ - public GLib.File get_user_data_directory() { + /** Returns the application's base home data directory. */ + public GLib.File get_home_data_directory() { return GLib.File.new_for_path( GLib.Environment.get_user_data_dir() - ).get_child("geary"); + ).get_child(get_geary_home_dir_name()); } /** Returns the application's base static resources directory. */ @@ -858,6 +872,34 @@ public class Application.Client : Gtk.Application { } } + /** + * Returns a set of paths of possible config locations. + * + * This is useful only for migrating configuration from + * non-Flatpak to Flatpak or release-builds to non-release builds. + */ + internal GLib.File[] get_config_search_path() { + var paths = new GLib.File[] {}; + var home = GLib.File.new_for_path(GLib.Environment.get_home_dir()); + paths += home.get_child( + ".config" + ).get_child( + "geary" + ); + paths += home.get_child( + ".var" + ).get_child( + "app" + ).get_child( + "org.gnome.Geary" + ).get_child( + "config" + ).get_child( + "geary" + ); + return paths; + } + /** * Displays an error notification. * @@ -1146,6 +1188,22 @@ public class Application.Client : Gtk.Application { } } + private string get_geary_home_dir_name() { + // Return the standard name if running a release build or + // running under Flatpak, otherwise append the build profile + // as a suffix so (e.g.) devel builds don't mess with release + // build's config and databases. + // + // Note that non-release Flatpak builds already have their own + // separate directories since they have different app ids, and + // hence don't need the suffix. + return ( + _PROFILE == PROFILE_RELEASE || this.is_flatpak_sandboxed + ? "geary" + : "geary-" + _PROFILE + ); + } + private void on_activate_about() { this.show_about.begin(); } diff --git a/src/client/application/application-configuration.vala b/src/client/application/application-configuration.vala index 48542df6..eaaed36f 100644 --- a/src/client/application/application-configuration.vala +++ b/src/client/application/application-configuration.vala @@ -146,7 +146,7 @@ public class Application.Configuration : Geary.BaseObject { settings = new Settings(schema_id); gnome_interface = new Settings("org.gnome.desktop.interface"); - Migrate.old_app_config(settings); + Util.Migrate.old_app_config(settings); this.bind(SINGLE_KEY_SHORTCUTS, this, SINGLE_KEY_SHORTCUTS); } diff --git a/src/client/application/application-controller.vala b/src/client/application/application-controller.vala index 55864cda..2237aa61 100644 --- a/src/client/application/application-controller.vala +++ b/src/client/application/application-controller.vala @@ -125,6 +125,9 @@ internal class Application.Controller : this.application = application; this.controller_open = cancellable; + GLib.File config_dir = application.get_home_config_directory(); + GLib.File data_dir = application.get_home_data_directory(); + // This initializes the IconFactory, important to do before // the actions are created (as they refer to some of Geary's // custom icons) @@ -137,11 +140,11 @@ internal class Application.Controller : Components.WebView.init_web_context( this.application.config, this.application.get_web_extensions_dir(), - this.application.get_user_cache_directory().get_child("web-resources") - ); - Components.WebView.load_resources( - this.application.get_user_config_directory() + this.application.get_home_cache_directory().get_child( + "web-resources" + ) ); + Components.WebView.load_resources(config_dir); Composer.WebView.load_resources(); ConversationWebView.load_resources(); Accounts.SignatureWebView.load_resources(); @@ -170,14 +173,23 @@ internal class Application.Controller : this.application.get_app_plugins_dir() ); + // Create standard config directory + try { + config_dir.make_directory_with_parents(); + } catch (GLib.IOError.EXISTS err) { + // fine + } + // Migrate configuration if necessary. - Migrate.xdg_config_dir(this.application.get_user_data_directory(), - this.application.get_user_config_directory()); + Util.Migrate.xdg_config_dir(config_dir, data_dir); + Util.Migrate.release_config( + application.get_config_search_path(), config_dir + ); // Hook up cert, accounts and credentials machinery this.certificate_manager = yield new Application.CertificateManager( - this.application.get_user_data_directory().get_child("pinned-certs"), + config_dir.get_child("pinned-certs"), cancellable ); @@ -187,8 +199,8 @@ internal class Application.Controller : this.account_manager = new Accounts.Manager( libsecret, - this.application.get_user_config_directory(), - this.application.get_user_data_directory() + config_dir, + data_dir ); this.account_manager.account_added.connect( on_account_added diff --git a/src/client/application/application-main-window.vala b/src/client/application/application-main-window.vala index 2b77a6d7..90d5b249 100644 --- a/src/client/application/application-main-window.vala +++ b/src/client/application/application-main-window.vala @@ -507,7 +507,7 @@ public class Application.MainWindow : load_config(application.config); restore_saved_window_state(); - if (_PROFILE != "release") { + if (_PROFILE != Client.PROFILE_RELEASE) { this.get_style_context().add_class("devel"); } diff --git a/src/client/util/util-migrate.vala b/src/client/util/util-migrate.vala index f4a905ed..31109c45 100644 --- a/src/client/util/util-migrate.vala +++ b/src/client/util/util-migrate.vala @@ -1,10 +1,12 @@ -/* Copyright 2016 Software Freedom Conservancy Inc. +/* + * Copyright © 2016 Software Freedom Conservancy Inc. + * Copyright © 2020 Michael Gratton * * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ + * (version 2.1 or later). See the COPYING file in this distribution. +n */ -namespace Migrate { +namespace Util.Migrate { private const string GROUP = "AccountInformation"; private const string PRIMARY_EMAIL_KEY = "primary_email"; private const string SETTINGS_FILENAME = Accounts.Manager.SETTINGS_FILENAME; @@ -19,21 +21,14 @@ namespace Migrate { * It also appends a "primary_email" key to the new configuration file to reliaby keep * track of the user's email address. */ - public static void xdg_config_dir(File user_data_dir, File user_config_dir) throws Error { + public static void xdg_config_dir(GLib.File user_config_dir, + GLib.File user_data_dir) + throws GLib.Error { File new_config_dir; File old_data_dir; File new_config_file; File old_config_file; - // Create ~/.config/geary - try { - user_config_dir.make_directory_with_parents(); - } catch (Error err) { - // The user may have already created the directory, so don't throw EXISTS. - if (!(err is IOError.EXISTS)) - throw err; - } - // Return if Geary has never been run (~/.local/share/geary does not exist). if (!user_data_dir.query_exists()) return; @@ -109,6 +104,145 @@ namespace Migrate { } } + /** + * Migrates configuration from release build locations. + * + * This will migrate configuration from release build locations to + * the current config directory, if and only if the current config + * directory is empty. For example, from the standard + * distro-package config location to the current Flatpak location, + * or from either to a development config location. + */ + public static void release_config(GLib.File[] search_path, + GLib.File config_dir) + throws GLib.Error { + if (is_directory_empty(config_dir)) { + GLib.File? most_recent = null; + GLib.DateTime most_recent_modified = null; + foreach (var source in search_path) { + if (!source.equal(config_dir)) { + GLib.DateTime? src_modified = null; + try { + GLib.FileInfo? src_info = source.query_info( + GLib.FileAttribute.TIME_MODIFIED, 0 + ); + if (src_info != null) { + src_modified = + src_info.get_modification_date_time(); + } + } catch (GLib.IOError.NOT_FOUND err) { + // fine + } catch (GLib.Error err) { + debug( + "Error querying release config dir %s: %s", + source.get_path(), + err.message + ); + } + if (most_recent_modified == null || + (src_modified != null && + most_recent_modified.compare(src_modified) < 0)) { + most_recent = source; + most_recent_modified = src_modified; + } + } + } + + if (most_recent != null) { + try { + debug( + "Migrating release config from %s to %s", + most_recent.get_path(), + config_dir.get_path() + ); + recursive_copy(most_recent, config_dir); + } catch (GLib.Error err) { + debug("Error migrating release config: %s", err.message); + } + } + } + } + + private bool is_directory_empty(GLib.File dir) { + bool is_empty = true; + GLib.FileEnumerator? existing = null; + try { + existing = dir.enumerate_children( + GLib.FileAttribute.STANDARD_TYPE, 0 + ); + } catch (GLib.IOError.NOT_FOUND err) { + // fine + } catch (GLib.Error err) { + debug( + "Error enumerating directory %s: %s", + dir.get_path(), + err.message + ); + } + + if (existing != null) { + try { + is_empty = existing.next_file() == null; + } catch (GLib.Error err) { + debug( + "Error getting next child in directory %s: %s", + dir.get_path(), + err.message + ); + } + + try { + existing.close(); + } catch (GLib.Error err) { + debug( + "Error closing directory enumeration %s: %s", + dir.get_path(), + err.message + ); + } + } + + return is_empty; + } + + private static void recursive_copy(GLib.File src, + GLib.File dest, + GLib.Cancellable? cancellable = null + ) throws GLib.Error { + switch (src.query_file_type(NONE, cancellable)) { + case DIRECTORY: + try { + dest.make_directory(cancellable); + } catch (GLib.IOError.EXISTS err) { + // fine + } + src.copy_attributes(dest, NONE, cancellable); + + GLib.FileEnumerator children = src.enumerate_children( + GLib.FileAttribute.STANDARD_NAME, + NONE, + cancellable + ); + GLib.FileInfo? child = children.next_file(cancellable); + while (child != null) { + recursive_copy( + src.get_child(child.get_name()), + dest.get_child(child.get_name()) + ); + child = children.next_file(cancellable); + } + break; + + case REGULAR: + src.copy(dest, NONE, cancellable); + break; + + default: + // no-op + break; + } + } + public const string OLD_APP_ID = "org.yorba.geary"; private const string MIGRATED_CONFIG_KEY = "migrated-config"; @@ -137,4 +271,6 @@ namespace Migrate { newSettings.set_boolean(MIGRATED_CONFIG_KEY, true); } + + }