Merge branch 'wip/application-cleanup' into 'mainline'

Application cleanup

See merge request GNOME/geary!215
This commit is contained in:
Michael Gratton 2019-04-21 06:18:14 +00:00
commit a163dea96a
9 changed files with 228 additions and 186 deletions

View file

@ -14,12 +14,12 @@ public class Application.StartupManager : GLib.Object {
private const string AUTOSTART_DESKTOP_FILE = "geary-autostart.desktop";
private Configuration config;
private GLib.File? install_dir;
private GLib.File startup_file; // Startup '.desktop' file
private GLib.File installed_file;
private GLib.File startup_file;
public StartupManager(Configuration config, GLib.File? install_dir) {
public StartupManager(Configuration config, GLib.File desktop_dir) {
this.config = config;
this.install_dir = install_dir;
this.installed_file = desktop_dir.get_child(AUTOSTART_DESKTOP_FILE);
this.startup_file = GLib.File.new_for_path(
GLib.Environment.get_user_config_dir()
).get_child(AUTOSTART_FOLDER)
@ -32,28 +32,10 @@ public class Application.StartupManager : GLib.Object {
}
/**
* Returns the system-wide autostart desktop file
* Returns the system-wide autostart desktop file if it exists.
*/
public GLib.File? get_autostart_desktop_file() {
GLib.File? parent = null;
if (this.install_dir != null) {
// Running from the installation directory
parent = (
this.install_dir
.get_child("share")
.get_child("applications")
);
} else {
// Running from the source build directory
parent = (
GLib.File.new_for_path(GearyApplication.SOURCE_ROOT_DIR)
.get_child("build")
.get_child("desktop")
);
}
GLib.File desktop_file = parent.get_child(AUTOSTART_DESKTOP_FILE);
return desktop_file.query_exists() ? desktop_file : null;
public GLib.File? get_installed_desktop_file() {
return this.installed_file.query_exists() ? this.installed_file : null;
}
/**
@ -65,7 +47,7 @@ public class Application.StartupManager : GLib.Object {
if (!autostart_dir.query_exists()) {
autostart_dir.make_directory_with_parents();
}
GLib.File? autostart = get_autostart_desktop_file();
GLib.File? autostart = get_installed_desktop_file();
if (autostart == null) {
warning("Autostart file is not installed!");
} else {

View file

@ -232,8 +232,23 @@ public class GearyApplication : Gtk.Application {
}
}
private string binary;
/**
* Determines if this instance is running from the install directory.
*/
internal bool is_installed {
get {
return this.exec_dir.has_prefix(this.install_prefix);
}
}
/** Returns the compile-time configured installation directory. */
internal GLib.File install_prefix {
get; private set; default = GLib.File.new_for_path(INSTALL_PREFIX);
}
private File exec_dir;
private string binary;
private bool start_hidden = false;
private bool exiting_fired = false;
private int exitcode = 0;
@ -348,9 +363,14 @@ public class GearyApplication : Gtk.Application {
public override bool local_command_line(ref unowned string[] args,
out int exit_status) {
this.binary = args[0];
string current_path = Posix.realpath(
string? current_path = Posix.realpath(
GLib.Environment.find_program_in_path(this.binary)
);
if (current_path == null) {
// Couldn't find the path, are being run as a unit test?
// Probably should deal with the null either way though.
current_path = this.binary;
}
this.exec_dir = GLib.File.new_for_path(current_path).get_parent();
return base.local_command_line(ref args, out exit_status);
@ -371,72 +391,28 @@ public class GearyApplication : Gtk.Application {
Environment.set_application_name(NAME);
International.init(GETTEXT_PACKAGE, this.binary);
Configuration.init(is_installed(), GSETTINGS_DIR);
Configuration.init(this.is_installed, GSETTINGS_DIR);
Geary.Logging.init();
Geary.Logging.log_to(stderr);
GLib.Log.set_default_handler(Geary.Logging.default_handler);
Util.Date.init();
// Add application's actions before chaining up so they are
// present when the application is first registered on the
// session bus.
add_action_entries(ACTION_ENTRIES, this);
// Calls Gtk.init(), amongst other things
base.startup();
// Ensure all geary windows have an icon
Gtk.Window.set_default_icon_name(APP_ID);
this.config = new Configuration(APP_ID);
this.autostart = new Application.StartupManager(
this.config, get_install_dir()
this.config, this.get_desktop_directory()
);
add_action_entries(ACTION_ENTRIES, this);
if (this.is_background_service) {
// Since command_line won't be called below if running as
// a DBus service, disable logging spew and start the
// controller running.
Geary.Logging.log_to(null);
this.create_async.begin();
}
}
public override int command_line(GLib.ApplicationCommandLine command_line) {
int exit_value = handle_general_options(command_line);
if (exit_value != -1)
return exit_value;
activate();
return -1;
}
public override void activate() {
base.activate();
// Clear notifications immediately since we are showing a main
// window.
if (present()) {
this.controller.notifications.clear_all_notifications();
} else {
this.create_async.begin((obj, res) => {
this.create_async.end(res);
this.controller.notifications.clear_all_notifications();
});
}
}
private async void create_async() {
// Manually keep the main loop around for the duration of this call.
// Without this, the main loop will exit as soon as we hit the yield
// below, before we create the main window.
hold();
// do *after* parsing args, as they dicate where logging is sent to, if anywhere, and only
// after activate (which means this is only logged for the one user-visible instance, not
// the other instances called when sending commands to the app via the command-line)
message("%s %s prefix=%s exec_dir=%s is_installed=%s", NAME, VERSION, INSTALL_PREFIX,
exec_dir.get_path(), is_installed().to_string());
// Ensure all geary windows have an icon
Gtk.Window.set_default_icon_name(APP_ID);
// Application accels
add_app_accelerators(ACTION_COMPOSE, { "<Ctrl>N" });
@ -454,21 +430,40 @@ public class GearyApplication : Gtk.Application {
ComposerWidget.add_window_accelerators(this);
Components.Inspector.add_window_accelerators(this);
yield this.controller.open_async(null);
release();
if (this.is_background_service) {
// Since command_line won't be called below if running as
// a DBus service, disable logging spew and start the
// controller running.
Geary.Logging.log_to(null);
this.create_controller.begin();
}
}
private async void destroy_async() {
// see create_async() for reasoning hold/release is used
hold();
public override int command_line(GLib.ApplicationCommandLine command_line) {
int exit_value = handle_general_options(command_line);
if (exit_value != -1)
return exit_value;
if (this.controller != null && this.controller.is_open) {
yield this.controller.close_async();
activate();
return -1;
}
public override void activate() {
base.activate();
// Clear notifications immediately since we are showing a main
// window. If the controller isn't already open, need to wait
// for that to happen before doing so
if (present()) {
this.controller.notifications.clear_all_notifications();
} else {
this.create_controller.begin((obj, res) => {
this.create_controller.end(res);
this.controller.notifications.clear_all_notifications();
});
}
release();
this.is_destroyed = true;
}
public void add_window_accelerators(string action,
@ -492,34 +487,39 @@ public class GearyApplication : Gtk.Application {
}
public File get_user_data_directory() {
return File.new_for_path(Environment.get_user_data_dir()).get_child("geary");
/** Returns the application's base user configuration directory. */
public GLib.File get_user_config_directory() {
return GLib.File.new_for_path(
Environment.get_user_config_dir()
).get_child("geary");
}
public File get_user_cache_directory() {
return File.new_for_path(Environment.get_user_cache_dir()).get_child("geary");
/** Returns the application's base user cache directory. */
public GLib.File get_user_cache_directory() {
return GLib.File.new_for_path(
GLib.Environment.get_user_cache_dir()
).get_child("geary");
}
public File get_user_config_directory() {
return File.new_for_path(Environment.get_user_config_dir()).get_child("geary");
/** Returns the application's base user data directory. */
public GLib.File get_user_data_directory() {
return GLib.File.new_for_path(
GLib.Environment.get_user_data_dir()
).get_child("geary");
}
/**
* Returns the base directory that the application's various resource files are stored. If the
* application is running from its installed directory, this will point to
* $(BASEDIR)/share/<program name>. If it's running from the build directory, this points to
* that.
*/
public File get_resource_directory() {
if (get_install_dir() != null)
return get_install_dir().get_child("share").get_child("geary");
else
return File.new_for_path(SOURCE_ROOT_DIR);
/** Returns the application's base static resources directory. */
public GLib.File get_resource_directory() {
return (is_installed)
? this.install_prefix.get_child("share").get_child("geary")
: GLib.File.new_for_path(SOURCE_ROOT_DIR);
}
/** Returns the directory the application is currently executing from. */
public File get_exec_dir() {
return this.exec_dir;
/** Returns the location of the application's desktop files. */
public GLib.File get_desktop_directory() {
return (is_installed)
? this.install_prefix.get_child("share").get_child("applications")
: GLib.File.new_for_path(BUILD_ROOT_DIR).get_child("desktop");
}
/**
@ -529,42 +529,13 @@ public class GearyApplication : Gtk.Application {
* on the Meson `libdir` option, and can be set by invoking `meson
* configure` as appropriate.
*/
public File get_web_extensions_dir() {
return (get_install_dir() != null)
? File.new_for_path(_WEB_EXTENSIONS_DIR)
: File.new_for_path(BUILD_ROOT_DIR).get_child("src");
public GLib.File get_web_extensions_dir() {
return (is_installed)
? GLib.File.new_for_path(_WEB_EXTENSIONS_DIR)
: GLib.File.new_for_path(BUILD_ROOT_DIR).get_child("src");
}
public File? get_desktop_file() {
File? install_dir = get_install_dir();
File desktop_file = (install_dir != null)
? install_dir.get_child("share").get_child("applications").get_child("org.gnome.Geary.desktop")
: File.new_for_path(SOURCE_ROOT_DIR).get_child("build").get_child("desktop").get_child("org.gnome.Geary.desktop");
return desktop_file.query_exists() ? desktop_file : null;
}
public bool is_installed() {
return exec_dir.has_prefix(get_install_prefix_dir());
}
// Returns the configure installation prefix directory, which does not imply Geary is installed
// or that it's running from this directory.
public File get_install_prefix_dir() {
return File.new_for_path(INSTALL_PREFIX);
}
// Returns the installation directory, or null if we're running outside of the installation
// directory.
public File? get_install_dir() {
File prefix_dir = get_install_prefix_dir();
return exec_dir.has_prefix(prefix_dir) ? prefix_dir : null;
}
/**
* Displays a URI on the current active window, if any.
*/
/** Displays a URI on the current active window, if any. */
public void show_uri(string uri) throws Error {
bool success = Gtk.show_uri_on_window(
get_active_window(), uri, Gdk.CURRENT_TIME
@ -589,11 +560,12 @@ public class GearyApplication : Gtk.Application {
return;
}
// Give asynchronous destroy_async() a chance to complete, but to avoid bug(s) where
// Geary hangs at exit, shut the whole thing down if destroy_async() takes too long to
// complete
// Give asynchronous destroy_controller() a chance to
// complete, but to avoid bug(s) where Geary hangs at exit,
// shut the whole thing down if destroy_controller() takes too
// long to complete
int64 start_usec = get_monotonic_time();
destroy_async.begin();
destroy_controller.begin();
while (!is_destroyed || Gtk.events_pending()) {
Gtk.main_iteration();
@ -609,16 +581,20 @@ public class GearyApplication : Gtk.Application {
}
/**
* A callback for GearyApplication.exiting should return cancel_exit() to prevent the
* application from exiting.
* A callback for GearyApplication.exiting should return
* cancel_exit() to prevent the application from exiting.
*/
public bool cancel_exit() {
Signal.stop_emission_by_name(this, "exiting");
return false;
}
// This call will fire "exiting" only if it's not already been fired and halt the application
// in its tracks.
/**
* Causes the application to exit immediately.
*
* This call will fire "exiting" only if it's not already been
* fired and halt the application in its tracks
*/
public void panic() {
if (!exiting_fired) {
exiting_fired = true;
@ -628,13 +604,40 @@ public class GearyApplication : Gtk.Application {
Posix.exit(1);
}
public void add_app_accelerators(string action,
string[] accelerators,
Variant? param = null) {
set_accels_for_action("app." + action, accelerators);
// Opens the controller
private async void create_controller() {
// Manually keep the main loop around for the duration of this
// call. Without this, the main loop will exit as soon as we
// hit the yield below, before we create the main window.
hold();
// do *after* parsing args, as they dicate where logging is
// sent to, if anywhere, and only after activate (which means
// this is only logged for the one user-visible instance, not
// the other instances called when sending commands to the app
// via the command-line)
message("%s %s prefix=%s exec_dir=%s is_installed=%s", NAME, VERSION, INSTALL_PREFIX,
exec_dir.get_path(), this.is_installed.to_string());
yield this.controller.open_async(null);
release();
}
public int handle_general_options(GLib.ApplicationCommandLine command_line) {
// Closes the controller, if running
private async void destroy_controller() {
// see create_controller() for reasoning hold/release is used
hold();
if (this.controller.is_open) {
yield this.controller.close_async();
}
release();
this.is_destroyed = true;
}
private int handle_general_options(GLib.ApplicationCommandLine command_line) {
GLib.VariantDict options = command_line.get_options_dict();
if (options.contains(OPTION_QUIT)) {
exit();
@ -713,8 +716,7 @@ public class GearyApplication : Gtk.Application {
private bool present() {
bool ret = false;
if (this.controller != null &&
this.controller.main_window != null) {
if (this.controller.main_window != null) {
this.controller.main_window.present();
ret = true;
}
@ -733,6 +735,12 @@ public class GearyApplication : Gtk.Application {
}
}
private void add_app_accelerators(string action,
string[] accelerators,
GLib.Variant? param = null) {
set_accels_for_action("app." + action, accelerators);
}
private Geary.Folder? get_folder_from_action_target(GLib.Variant target) {
Geary.Folder? folder = null;
string account_id = (string) target.get_child_value(0);
@ -776,9 +784,7 @@ public class GearyApplication : Gtk.Application {
}
private void on_activate_compose() {
if (this.controller != null) {
this.controller.compose();
}
this.controller.compose();
}
private void on_activate_inspect() {
@ -794,7 +800,7 @@ public class GearyApplication : Gtk.Application {
}
private void on_activate_mailto(SimpleAction action, Variant? param) {
if (this.controller != null && param != null) {
if (param != null) {
this.controller.compose(param.get_string());
}
}
@ -845,11 +851,11 @@ public class GearyApplication : Gtk.Application {
private void on_activate_help() {
try {
if (is_installed()) {
if (this.is_installed) {
show_uri("help:geary");
} else {
Pid pid;
File exec_dir = get_exec_dir();
File exec_dir = this.exec_dir;
string[] argv = new string[3];
argv[0] = "yelp";
argv[1] = GearyApplication.SOURCE_ROOT_DIR + "/help/C/";

View file

@ -327,10 +327,12 @@ public class GearyController : Geary.BaseObject {
main_window.folder_list.set_new_messages_monitor(new_messages_monitor);
// New messages indicator (Ubuntuism)
new_messages_indicator = NewMessagesIndicator.create(new_messages_monitor);
new_messages_indicator.application_activated.connect(on_indicator_activated_application);
new_messages_indicator.composer_activated.connect(on_indicator_activated_composer);
new_messages_indicator.inbox_activated.connect(on_indicator_activated_inbox);
this.new_messages_indicator = NewMessagesIndicator.create(
this.new_messages_monitor, this.application.config
);
this.new_messages_indicator.application_activated.connect(on_indicator_activated_application);
this.new_messages_indicator.composer_activated.connect(on_indicator_activated_composer);
this.new_messages_indicator.inbox_activated.connect(on_indicator_activated_inbox);
unity_launcher = new UnityLauncher(new_messages_monitor);

View file

@ -8,18 +8,18 @@ public class Libmessagingmenu : NewMessagesIndicator {
#if HAVE_LIBMESSAGINGMENU
private MessagingMenu.App? app = null;
public Libmessagingmenu(NewMessagesMonitor monitor) {
base (monitor);
File? desktop_file = GearyApplication.instance.get_desktop_file();
if (desktop_file == null
|| !desktop_file.has_prefix(GearyApplication.instance.get_install_prefix_dir())) {
debug("Only an installed version of Geary with its .desktop file installed can use Messaging Menu");
private Configuration config;
return;
}
app = new MessagingMenu.App("org.gnome.Geary.desktop");
public Libmessagingmenu(NewMessagesMonitor monitor,
Configuration config) {
base(monitor);
this.config = config;
app = new MessagingMenu.App(
"%s.desktop".printf(GearyApplication.APP_ID)
);
app.register();
app.activate_source.connect(on_activate_source);
@ -63,7 +63,7 @@ public class Libmessagingmenu : NewMessagesIndicator {
}
private void show_new_messages_count(Geary.Folder folder, int count) {
if (!GearyApplication.instance.config.show_notifications || !monitor.should_notify_new_messages(folder))
if (!this.config.show_notifications || !monitor.should_notify_new_messages(folder))
return;
string source_id = get_source_id(folder);

View file

@ -20,7 +20,8 @@ public abstract class NewMessagesIndicator : Geary.BaseObject {
this.monitor = monitor;
}
public static NewMessagesIndicator create(NewMessagesMonitor monitor) {
public static NewMessagesIndicator create(NewMessagesMonitor monitor,
Configuration config) {
NewMessagesIndicator? indicator = null;
// Indicators are ordered from most to least prefered. If more than one is available,
@ -28,7 +29,7 @@ public abstract class NewMessagesIndicator : Geary.BaseObject {
#if HAVE_LIBMESSAGINGMENU
if (indicator == null)
indicator = new Libmessagingmenu(monitor);
indicator = new Libmessagingmenu(monitor, config);
#endif
if (indicator == null)

View file

@ -0,0 +1,49 @@
/*
* Copyright 2019 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.
*/
class GearyApplicationTest : TestCase {
private GearyApplication? test_article = null;
public GearyApplicationTest() {
base("GearyApplicationTest");
add_test("paths_when_installed", paths_when_installed);
}
public override void set_up() {
this.test_article = new GearyApplication();
}
public override void tear_down() {
this.test_article = null;
}
public void paths_when_installed() throws GLib.Error {
string[] args = new string[] {
_INSTALL_PREFIX + "/bin/geary",
// Specifiy this so the app doesn't actually attempt
// to start up
"-v"
};
unowned string[] unowned_args = args;
int status;
this.test_article.local_command_line(ref unowned_args, out status);
assert_string(
_INSTALL_PREFIX + "/share/geary",
this.test_article.get_resource_directory().get_path()
);
assert_string(
_INSTALL_PREFIX + "/share/applications",
this.test_article.get_desktop_directory().get_path()
);
}
}

View file

@ -5,8 +5,6 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
// Defined by CMake build script.
extern const string _BUILD_ROOT_DIR;
public abstract class ClientWebViewTestCase<V> : TestCase {

View file

@ -75,6 +75,7 @@ geary_test_client_sources = [
'engine/api/geary-credentials-mediator-mock.vala',
'client/accounts/accounts-manager-test.vala',
'client/application/geary-application-test.vala',
'client/application/geary-configuration-test.vala',
'client/components/client-web-view-test.vala',
'client/components/client-web-view-test-case.vala',

View file

@ -5,7 +5,9 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
// Defined by CMake build script.
extern const string _INSTALL_PREFIX;
extern const string _BUILD_ROOT_DIR;
extern const string _GSETTINGS_DIR;
int main(string[] args) {
@ -43,6 +45,7 @@ int main(string[] args) {
client.add_suite(new ClientWebViewTest().get_suite());
client.add_suite(new ComposerWebViewTest().get_suite());
client.add_suite(new ConfigurationTest().get_suite());
client.add_suite(new GearyApplicationTest().get_suite());
client.add_suite(new Util.Avatar.Test().get_suite());
client.add_suite(new Util.Cache.Test().get_suite());
client.add_suite(new Util.Email.Test().get_suite());