Convert GearyController to RAII and rename

Convert the controller's open_async() method into its constructor, make
previously nullable properties not nullable, make the application's
instance null until it is contructed, rename to match dir convention.
This commit is contained in:
Michael Gratton 2019-04-19 14:59:35 +10:00 committed by Michael James Gratton
parent d4c29c72d7
commit e01132d856
7 changed files with 122 additions and 158 deletions

View file

@ -20,9 +20,9 @@ src/client/application/application-certificate-manager.vala
src/client/application/application-command.vala
src/client/application/application-contact-store.vala
src/client/application/application-contact.vala
src/client/application/application-controller.vala
src/client/application/application-startup-manager.vala
src/client/application/geary-application.vala
src/client/application/geary-controller.vala
src/client/application/goa-mediator.vala
src/client/application/main.vala
src/client/application/secret-mediator.vala

View file

@ -8,9 +8,11 @@
/**
* Primary controller for a Geary application instance.
* Primary controller for a application instance.
*
* @see GearyAplication
*/
public class GearyController : Geary.BaseObject {
public class Application.Controller : Geary.BaseObject {
// Named actions.
public const string ACTION_REPLY_TO_MESSAGE = "reply-to-message";
@ -55,7 +57,7 @@ public class GearyController : Geary.BaseObject {
static construct {
// Translators: File name used in save chooser when saving
// attachments that do not otherwise have a name.
GearyController.untitled_file_name = _("Untitled");
Controller.untitled_file_name = _("Untitled");
}
@ -109,49 +111,64 @@ public class GearyController : Geary.BaseObject {
}
/** Determines if the controller is opening or is open. */
/** Determines if the controller is open. */
public bool is_open {
get {
return (this.open_cancellable != null);
return !this.open_cancellable.is_cancelled();
}
}
public weak GearyApplication application { get; private set; } // circular ref
public Accounts.Manager? account_manager { get; private set; default = null; }
/** Account management for the application. */
public Accounts.Manager account_manager { get; private set; }
/** Application-wide {@link Application.CertificateManager} instance. */
public Application.CertificateManager? certificate_manager {
get; private set; default = null;
/** Certificate management for the application. */
public Application.CertificateManager certificate_manager {
get; private set;
}
public MainWindow? main_window { get; private set; default = null; }
/** Desktop notifications for the application. */
public Notification.Desktop notifications { get; private set; }
public Geary.App.ConversationMonitor? current_conversations { get; private set; default = null; }
public Application.AvatarStore? avatars {
/** Avatar store for the application. */
public Application.AvatarStore avatars {
get; private set; default = new Application.AvatarStore();
}
/** Contact store cache for the application. */
public ContactListStoreCache contact_list_store_cache {
get; private set; default = new ContactListStoreCache();
}
public Notification.Desktop? notifications { get; private set; default = null; }
/** Default main window */
public MainWindow main_window { get; private set; }
private Geary.Account? current_account = null;
/** Conversations for the current folder, null if none selected */
public Geary.App.ConversationMonitor? current_conversations {
get; private set; default = null;
}
// Primary collection of the application's open accounts
private Gee.Map<Geary.AccountInformation,AccountContext> accounts =
new Gee.HashMap<Geary.AccountInformation,AccountContext>();
// Created when controller is opened, cancelled and nulled out
// when closed.
private GLib.Cancellable? open_cancellable = null;
// Cancelled if the controller is closed
private GLib.Cancellable open_cancellable;
private Folks.IndividualAggregator? folks = null;
private Canberra.Context? sound_context = null;
private UpgradeDialog upgrade_dialog;
private Folks.IndividualAggregator folks;
private Canberra.Context sound_context;
private NewMessagesMonitor new_messages_monitor;
private NewMessagesIndicator new_messages_indicator;
private UnityLauncher unity_launcher;
// Null if none selected
private Geary.Folder? current_folder = null;
// Null if no folder ever selected
private Geary.Account? current_account = null;
private Cancellable cancellable_folder = new Cancellable();
private Cancellable cancellable_search = new Cancellable();
private Cancellable cancellable_open_account = new Cancellable();
@ -159,14 +176,10 @@ public class GearyController : Geary.BaseObject {
private Gee.Set<Geary.App.Conversation> selected_conversations = new Gee.HashSet<Geary.App.Conversation>();
private Geary.App.Conversation? last_deleted_conversation = null;
private Gee.LinkedList<ComposerWidget> composer_widgets = new Gee.LinkedList<ComposerWidget>();
private NewMessagesMonitor? new_messages_monitor = null;
private NewMessagesIndicator? new_messages_indicator = null;
private UnityLauncher? unity_launcher = null;
private uint select_folder_timeout_id = 0;
private int64 next_folder_select_allowed_usec = 0;
private Geary.Nonblocking.Mutex select_folder_mutex = new Geary.Nonblocking.Mutex();
private Geary.Folder? previous_non_search_folder = null;
private UpgradeDialog upgrade_dialog;
private Gee.List<string?> pending_mailtos = new Gee.ArrayList<string>();
private uint operation_count = 0;
@ -229,18 +242,11 @@ public class GearyController : Geary.BaseObject {
/**
* Constructs a new instance of the controller.
*/
public GearyController(GearyApplication application) {
public async Controller(GearyApplication application,
GLib.Cancellable cancellable) {
this.application = application;
}
this.open_cancellable = cancellable;
~GearyController() {
assert(current_account == null);
}
/**
* Starts the controller and brings up Geary.
*/
public async void open_async(GLib.Cancellable? cancellable) {
Geary.Engine engine = this.application.engine;
// This initializes the IconFactory, important to do before
@ -248,16 +254,14 @@ public class GearyController : Geary.BaseObject {
// custom icons)
IconFactory.instance.init();
apply_app_menu_fix();
this.open_cancellable = new GLib.Cancellable();
// Listen for attempts to close the application.
this.application.exiting.connect(on_application_exiting);
// Create DB upgrade dialog.
upgrade_dialog = new UpgradeDialog();
upgrade_dialog.notify[UpgradeDialog.PROP_VISIBLE_NAME].connect(display_main_window_if_ready);
this.upgrade_dialog = new UpgradeDialog();
this.upgrade_dialog.notify[UpgradeDialog.PROP_VISIBLE_NAME].connect(
display_main_window_if_ready
);
// Initialise WebKit and WebViews
ClientWebView.init_web_context(
@ -324,7 +328,7 @@ public class GearyController : Geary.BaseObject {
this.get_contact_store_for_account,
this.should_notify_new_messages
);
main_window.folder_list.set_new_messages_monitor(new_messages_monitor);
main_window.folder_list.set_new_messages_monitor(this.new_messages_monitor);
// New messages indicator (Ubuntuism)
this.new_messages_indicator = NewMessagesIndicator.create(
@ -334,12 +338,12 @@ public class GearyController : Geary.BaseObject {
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);
this.unity_launcher = new UnityLauncher(this.new_messages_monitor);
this.notifications = new Notification.Desktop(
this.new_messages_monitor,
this.application,
this.open_cancellable
cancellable
);
this.main_window.conversation_list_view.grab_focus();
@ -416,50 +420,39 @@ public class GearyController : Geary.BaseObject {
this.expunge_accounts.begin();
}
/**
* At the moment, this is non-reversible, i.e. once closed a GearyController cannot be
* re-opened.
*/
/** Closes all accounts and windows, releasing held resources. */
public async void close_async() {
// Cancel internal processes early so they don't block
// shutdown
this.open_cancellable.cancel();
this.open_cancellable = null;
this.application.engine.account_available.disconnect(on_account_available);
if (this.main_window != null) {
// Release folder and conversations in the main window
on_conversations_selected(new Gee.HashSet<Geary.App.Conversation>());
on_folder_selected(null);
// Release folder and conversations in the main window
on_conversations_selected(new Gee.HashSet<Geary.App.Conversation>());
on_folder_selected(null);
// Disconnect from various UI signals.
this.main_window.conversation_list_view.conversations_selected.disconnect(on_conversations_selected);
this.main_window.conversation_list_view.conversation_activated.disconnect(on_conversation_activated);
this.main_window.conversation_list_view.load_more.disconnect(on_load_more);
this.main_window.conversation_list_view.mark_conversations.disconnect(on_mark_conversations);
this.main_window.conversation_list_view.visible_conversations_changed.disconnect(on_visible_conversations_changed);
this.main_window.folder_list.folder_selected.disconnect(on_folder_selected);
this.main_window.folder_list.copy_conversation.disconnect(on_copy_conversation);
this.main_window.folder_list.move_conversation.disconnect(on_move_conversation);
this.main_window.main_toolbar.copy_folder_menu.folder_selected.disconnect(on_copy_conversation);
this.main_window.main_toolbar.move_folder_menu.folder_selected.disconnect(on_move_conversation);
this.main_window.conversation_viewer.conversation_added.disconnect(
on_conversation_view_added
);
// Disconnect from various UI signals.
this.main_window.conversation_list_view.conversations_selected.disconnect(on_conversations_selected);
this.main_window.conversation_list_view.conversation_activated.disconnect(on_conversation_activated);
this.main_window.conversation_list_view.load_more.disconnect(on_load_more);
this.main_window.conversation_list_view.mark_conversations.disconnect(on_mark_conversations);
this.main_window.conversation_list_view.visible_conversations_changed.disconnect(on_visible_conversations_changed);
this.main_window.folder_list.folder_selected.disconnect(on_folder_selected);
this.main_window.folder_list.copy_conversation.disconnect(on_copy_conversation);
this.main_window.folder_list.move_conversation.disconnect(on_move_conversation);
this.main_window.main_toolbar.copy_folder_menu.folder_selected.disconnect(on_copy_conversation);
this.main_window.main_toolbar.move_folder_menu.folder_selected.disconnect(on_move_conversation);
this.main_window.conversation_viewer.conversation_added.disconnect(
on_conversation_view_added
);
// hide window while shutting down, as this can take a few
// seconds under certain conditions
this.main_window.hide();
}
// hide window while shutting down, as this can take a few
// seconds under certain conditions
this.main_window.hide();
// Release monitoring early so held resources can be freed up
this.sound_context = null;
this.notifications = null;
this.new_messages_indicator = null;
this.unity_launcher = null;
this.new_messages_monitor.clear_folders();
this.new_messages_monitor = null;
// drop the Revokable, which will commit it if necessary
save_revokable(null, null);
@ -547,20 +540,16 @@ public class GearyController : Geary.BaseObject {
this.account_manager.account_removed.disconnect(
on_account_removed
);
this.account_manager = null;
if (this.main_window != null) {
this.application.remove_window(this.main_window);
this.main_window.destroy();
this.main_window = null;
}
this.upgrade_dialog = null;
this.current_folder = null;
this.previous_non_search_folder = null;
this.current_account = null;
this.current_folder = null;
this.previous_non_search_folder = null;
this.selected_conversations = new Gee.HashSet<Geary.App.Conversation>();
this.last_deleted_conversation = null;
@ -570,10 +559,8 @@ public class GearyController : Geary.BaseObject {
this.waiting_to_close.clear();
this.avatars.close();
this.avatars = null;
debug("Closed GearyController");
debug("Closed Application.Controller");
}
/**
@ -605,28 +592,6 @@ public class GearyController : Geary.BaseObject {
}
}
// Fix for clients having both:
// * disabled Gtk/ShellShowsAppMenu setting
// * no 'menu' setting in Gtk/DecorationLayout
// See https://bugzilla.gnome.org/show_bug.cgi?id=770617
private void apply_app_menu_fix() {
Gtk.Settings? settings = Gtk.Settings.get_default();
if (settings == null) {
warning("Couldn't fetch Gtk default settings");
return;
}
string decoration_layout = settings.gtk_decoration_layout ?? "";
if (!decoration_layout.contains("menu")) {
string prefix = "menu:";
if (decoration_layout.contains(":")) {
prefix = (decoration_layout.has_prefix(":")) ? "menu" : "menu,";
}
settings.gtk_decoration_layout = prefix + settings.gtk_decoration_layout;
}
}
private void setup_actions() {
this.main_window.add_action_entries(win_action_entries, this);
@ -1882,7 +1847,7 @@ public class GearyController : Geary.BaseObject {
string? alt_text,
GLib.Cancellable cancellable) {
string alt_display_name = Geary.String.is_empty_or_whitespace(alt_text)
? GearyController.untitled_file_name : alt_text;
? Application.Controller.untitled_file_name : alt_text;
string display_name = yield attachment.get_safe_file_name(
alt_display_name
);
@ -1920,7 +1885,7 @@ public class GearyController : Geary.BaseObject {
content = new Geary.Memory.FileBuffer(attachment.file, true);
dest = dest_dir.get_child_for_display_name(
yield attachment.get_safe_file_name(
GearyController.untitled_file_name
Application.Controller.untitled_file_name
)
);
} catch (GLib.Error err) {
@ -3136,7 +3101,7 @@ public class GearyController : Geary.BaseObject {
// chars anyway.
string? display_name = source.get_basename();
if (Geary.String.is_empty_or_whitespace(display_name)) {
display_name = GearyController.untitled_file_name;
display_name = Application.Controller.untitled_file_name;
}
this.prompt_save_buffer.begin(

View file

@ -176,12 +176,14 @@ public class GearyApplication : Gtk.Application {
/**
* The global UI controller for this app instance.
* The global controller for this application instance.
*
* This will be non-null in the primary application instance, only
* after initial activation, or after startup if {@link
* is_background_service} is true.
*/
public GearyController controller {
get;
private set;
default = new GearyController(this);
public Application.Controller? controller {
get; private set; default = null;
}
/**
@ -253,6 +255,7 @@ public class GearyApplication : Gtk.Application {
private bool exiting_fired = false;
private int exitcode = 0;
private bool is_destroyed = false;
private GLib.Cancellable controller_cancellable = new GLib.Cancellable();
private Components.Inspector? inspector = null;
@ -451,19 +454,7 @@ public class GearyApplication : Gtk.Application {
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();
});
}
this.present.begin();
}
public void add_window_accelerators(string action,
@ -560,6 +551,8 @@ public class GearyApplication : Gtk.Application {
return;
}
this.controller_cancellable.cancel();
// 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
@ -604,6 +597,15 @@ public class GearyApplication : Gtk.Application {
Posix.exit(1);
}
// Presents a main window. If the controller is not open, opens it
// first.
private async void present() {
if (this.controller == null) {
yield create_controller();
}
this.controller.main_window.present();
}
// Opens the controller
private async void create_controller() {
// Manually keep the main loop around for the duration of this
@ -616,11 +618,16 @@ public class GearyApplication : Gtk.Application {
// 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);
message(
"%s %s prefix=%s exec_dir=%s is_installed=%s",
NAME, VERSION, INSTALL_PREFIX,
exec_dir.get_path(),
this.is_installed.to_string()
);
this.controller = yield new Application.Controller(
this, this.controller_cancellable
);
release();
}
@ -629,8 +636,9 @@ public class GearyApplication : Gtk.Application {
// see create_controller() for reasoning hold/release is used
hold();
if (this.controller.is_open) {
if (this.controller != null) {
yield this.controller.close_async();
this.controller = null;
}
release();
@ -714,15 +722,6 @@ public class GearyApplication : Gtk.Application {
return -1;
}
private bool present() {
bool ret = false;
if (this.controller.main_window != null) {
this.controller.main_window.present();
ret = true;
}
return ret;
}
/** Removes and re-adds the autostart file if needed. */
private async void update_autostart_file() {
try {

View file

@ -161,7 +161,7 @@ public class MainToolbar : Gtk.Box {
);
if (this.show_trash_button) {
this.trash_delete_button.action_name = "win."+GearyController.ACTION_TRASH_CONVERSATION;
this.trash_delete_button.action_name = "win."+Application.Controller.ACTION_TRASH_CONVERSATION;
this.trash_delete_button.image = trash_image;
this.trash_delete_button.tooltip_text = ngettext(
"Move conversation to Trash (Delete, Backspace)",
@ -169,7 +169,7 @@ public class MainToolbar : Gtk.Box {
this.selected_conversations
);
} else {
this.trash_delete_button.action_name = "win."+GearyController.ACTION_DELETE_CONVERSATION;
this.trash_delete_button.action_name = "win."+Application.Controller.ACTION_DELETE_CONVERSATION;
this.trash_delete_button.image = delete_image;
this.trash_delete_button.tooltip_text = ngettext(
"Delete conversation (Shift+Delete)",

View file

@ -102,7 +102,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
load_config(application.config);
restore_saved_window_state();
application.controller.notify[GearyController.PROP_CURRENT_CONVERSATION]
application.controller.notify[Application.Controller.PROP_CURRENT_CONVERSATION]
.connect(on_conversation_monitor_changed);
application.controller.folder_selected.connect(on_folder_selected);
this.application.engine.account_available.connect(on_account_available);
@ -126,7 +126,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
}
public void open_composer_for_mailbox(Geary.RFC822.MailboxAddress to) {
GearyController controller = this.application.controller;
Application.Controller controller = this.application.controller;
ComposerWidget composer = new ComposerWidget(
this.current_folder.account,
controller.contact_list_store_cache,
@ -225,7 +225,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
new ComposerWindow(composer);
} else {
this.conversation_viewer.do_compose(composer);
get_action(GearyController.ACTION_FIND_IN_CONVERSATION).set_enabled(false);
get_action(Application.Controller.ACTION_FIND_IN_CONVERSATION).set_enabled(false);
}
}

View file

@ -59,7 +59,7 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
GearyApplication.instance.config.settings.changed[Configuration.DISPLAY_PREVIEW_KEY].connect(
on_display_preview_changed);
GearyApplication.instance.controller.notify[GearyController.PROP_CURRENT_CONVERSATION].
GearyApplication.instance.controller.notify[Application.Controller.PROP_CURRENT_CONVERSATION].
connect(on_conversation_monitor_changed);
// Watch for mouse events.
@ -291,23 +291,23 @@ public class ConversationListView : Gtk.TreeView, Geary.BaseInterface {
Geary.App.Conversation conversation = get_model().get_conversation_at_path(path);
Menu context_menu_model = new Menu();
context_menu_model.append(_("Delete conversation"), "win."+GearyController.ACTION_DELETE_CONVERSATION);
context_menu_model.append(_("Delete conversation"), "win."+Application.Controller.ACTION_DELETE_CONVERSATION);
if (conversation.is_unread())
context_menu_model.append(_("Mark as _Read"), "win."+GearyController.ACTION_MARK_AS_READ);
context_menu_model.append(_("Mark as _Read"), "win."+Application.Controller.ACTION_MARK_AS_READ);
if (conversation.has_any_read_message())
context_menu_model.append(_("Mark as _Unread"), "win."+GearyController.ACTION_MARK_AS_UNREAD);
context_menu_model.append(_("Mark as _Unread"), "win."+Application.Controller.ACTION_MARK_AS_UNREAD);
if (conversation.is_flagged())
context_menu_model.append(_("U_nstar"), "win."+GearyController.ACTION_MARK_AS_UNSTARRED);
context_menu_model.append(_("U_nstar"), "win."+Application.Controller.ACTION_MARK_AS_UNSTARRED);
else
context_menu_model.append(_("_Star"), "win."+GearyController.ACTION_MARK_AS_STARRED);
context_menu_model.append(_("_Star"), "win."+Application.Controller.ACTION_MARK_AS_STARRED);
Menu actions_section = new Menu();
actions_section.append(_("_Reply"), "win."+GearyController.ACTION_REPLY_TO_MESSAGE);
actions_section.append(_("R_eply All"), "win."+GearyController.ACTION_REPLY_ALL_MESSAGE);
actions_section.append(_("_Forward"), "win."+GearyController.ACTION_FORWARD_MESSAGE);
actions_section.append(_("_Reply"), "win."+Application.Controller.ACTION_REPLY_TO_MESSAGE);
actions_section.append(_("R_eply All"), "win."+Application.Controller.ACTION_REPLY_ALL_MESSAGE);
actions_section.append(_("_Forward"), "win."+Application.Controller.ACTION_FORWARD_MESSAGE);
context_menu_model.append_section(null, actions_section);
Gtk.Menu context_menu = new Gtk.Menu.from_model(context_menu_model);

View file

@ -5,10 +5,10 @@ geary_client_vala_sources = files(
'application/application-command.vala',
'application/application-contact-store.vala',
'application/application-contact.vala',
'application/application-controller.vala',
'application/application-startup-manager.vala',
'application/geary-application.vala',
'application/geary-config.vala',
'application/geary-controller.vala',
'application/goa-mediator.vala',
'application/secret-mediator.vala',