Merge branch 'mjog/data-location-migration' into 'mainline'

Automatic config migration and per-build-profile directories

Closes #326

See merge request GNOME/geary!608
This commit is contained in:
Michael Gratton 2020-10-14 07:26:00 +00:00
commit 3aa9eb77a6
7 changed files with 246 additions and 34 deletions

View file

@ -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": [

View file

@ -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"

View file

@ -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 <jim@yorba.org>",
"Eric Gregory <eric@yorba.org>",
@ -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();
}

View file

@ -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);
}

View file

@ -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

View file

@ -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");
}

View file

@ -1,10 +1,12 @@
/* Copyright 2016 Software Freedom Conservancy Inc.
/*
* Copyright © 2016 Software Freedom Conservancy Inc.
* Copyright © 2020 Michael Gratton <mike@vee.net>
*
* 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);
}
}