Ensure drafts are removed when composer from address changes accounts.

This also ensure that if the user causes the from account quickly, that
there isn't any undesirable interleaving of drafts managers as multiple
instances are possibly opened and closed.

Fixes 778976.

* src/client/composer/composer-widget.vala (ComposerWidget): When
  reopening the draft manager and one exists already, discard its draft
  first. When opening a new draft manager, use a cancellable to stop any
  existing draft manager being opened, and to close any being opened when
  the widget is destroyed. Use a private var when opening/closing the
  draft managers so the class field is only updated when the instance has
  fully opened and as soon as it starts to be closed.
This commit is contained in:
Michael James Gratton 2018-04-14 09:35:11 +10:00
parent 77d33d7346
commit 7954a224cc

View file

@ -358,6 +358,7 @@ public class ComposerWidget : Gtk.EventBox {
private Gee.Map<string,File> cid_files = new Gee.HashMap<string,File>();
private Geary.App.DraftManager? draft_manager = null;
private GLib.Cancellable? draft_manager_opening = null;
private Geary.EmailFlags draft_flags = new Geary.EmailFlags.with(Geary.EmailFlags.DRAFT);
private Geary.TimeoutManager draft_timer;
private bool is_draft_saved = false;
@ -530,6 +531,10 @@ public class ComposerWidget : Gtk.EventBox {
public override void destroy() {
this.draft_timer.reset();
if (this.draft_manager_opening != null) {
this.draft_manager_opening.cancel();
this.draft_manager_opening = null;
}
if (this.draft_manager != null)
close_draft_manager_async.begin(null);
base.destroy();
@ -720,40 +725,6 @@ public class ComposerWidget : Gtk.EventBox {
}
}
/**
* Creates and opens the composer's draft manager.
*/
private async void open_draft_manager_async(
Geary.EmailIdentifier? editing_draft_id = null,
Cancellable? cancellable = null)
throws Error {
if (!this.account.information.save_drafts) {
this.header.save_and_close_button.hide();
return;
}
this.draft_manager = new Geary.App.DraftManager(account);
try {
yield this.draft_manager.open_async(editing_draft_id, cancellable);
debug("Draft manager opened");
} catch (Error err) {
debug("Unable to open draft manager %s: %s",
this.draft_manager.to_string(), err.message);
this.draft_manager = null;
throw err;
}
update_draft_state();
get_action(ACTION_CLOSE_AND_SAVE).set_enabled(true);
this.header.save_and_close_button.show();
this.draft_manager.notify[Geary.App.DraftManager.PROP_DRAFT_STATE]
.connect(on_draft_state_changed);
this.draft_manager.notify[Geary.App.DraftManager.PROP_CURRENT_DRAFT_ID]
.connect(on_draft_id_changed);
this.draft_manager.fatal.connect(on_draft_manager_fatal);
}
// Copies the addresses (e.g. From/To/CC) and content from referred into this one
private string fill_in_from_referred(Geary.Email referred, string? quote) {
string referred_quote = "";
@ -1317,45 +1288,86 @@ public class ComposerWidget : Gtk.EventBox {
this.container.close_container();
}
/**
* Creates and opens the composer's draft manager.
*/
private async void
open_draft_manager_async(Geary.EmailIdentifier? editing_draft_id = null)
throws Error {
if (!this.account.information.save_drafts) {
this.header.save_and_close_button.hide();
return;
}
// Cancel any existing opening first
if (this.draft_manager_opening != null) {
this.draft_manager_opening.cancel();
}
this.draft_manager_opening = new GLib.Cancellable();
Geary.App.DraftManager new_manager = new Geary.App.DraftManager(account);
try {
yield new_manager.open_async(editing_draft_id, this.draft_manager_opening);
debug("Draft manager opened");
} catch (Error err) {
debug("Unable to open draft manager %s: %s",
new_manager.to_string(), err.message);
throw err;
}
new_manager.notify[Geary.App.DraftManager.PROP_DRAFT_STATE]
.connect(on_draft_state_changed);
new_manager.notify[Geary.App.DraftManager.PROP_CURRENT_DRAFT_ID]
.connect(on_draft_id_changed);
new_manager.fatal.connect(on_draft_manager_fatal);
this.draft_manager_opening = null;
this.draft_manager = new_manager;
update_draft_state();
get_action(ACTION_CLOSE_AND_SAVE).set_enabled(true);
this.header.save_and_close_button.show();
}
/**
* Closes current draft manager, if any, then opens a new one.
*/
private async void reopen_draft_manager_async(Cancellable? cancellable)
private async void reopen_draft_manager_async()
throws Error {
if (this.draft_manager != null) {
yield close_draft_manager_async(cancellable);
// Discard the draft, if any, since it may be on a
// different account
discard_draft();
this.draft_manager.discard_on_close = true;
yield close_draft_manager_async(null);
}
// XXX Need to work out what do to with any existing draft in
// this case. See Bug 713533.
yield open_draft_manager_async(null);
yield open_draft_manager_async();
}
private async void close_draft_manager_async(Cancellable? cancellable)
throws Error {
this.draft_save_text = "";
get_action(ACTION_CLOSE_AND_SAVE).set_enabled(false);
disconnect_from_draft_manager();
Geary.App.DraftManager old_manager = this.draft_manager;
this.draft_manager = null;
old_manager.notify[Geary.App.DraftManager.PROP_DRAFT_STATE]
.disconnect(on_draft_state_changed);
old_manager.notify[Geary.App.DraftManager.PROP_CURRENT_DRAFT_ID]
.disconnect(on_draft_id_changed);
old_manager.fatal.disconnect(on_draft_manager_fatal);
// drop ref even if close failed
try {
yield this.draft_manager.close_async(cancellable);
} finally {
this.draft_manager = null;
yield old_manager.close_async(cancellable);
} catch (Error err) {
debug("Error closing draft manager: %s", err.message);
}
debug("Draft manager closed");
}
// This code is in a separate method due to https://bugzilla.gnome.org/show_bug.cgi?id=742621
// connect_to_draft_manager() is simply for symmetry. When above bug is fixed, this code can
// be moved back into open/close methods
private void disconnect_from_draft_manager() {
this.draft_manager.notify[Geary.App.DraftManager.PROP_DRAFT_STATE]
.disconnect(on_draft_state_changed);
this.draft_manager.notify[Geary.App.DraftManager.PROP_CURRENT_DRAFT_ID]
.disconnect(on_draft_id_changed);
this.draft_manager.fatal.disconnect(on_draft_manager_fatal);
}
private void update_draft_state() {
switch (this.draft_manager.draft_state) {
case Geary.App.DraftManager.DraftState.STORED:
@ -2092,26 +2104,21 @@ public class ComposerWidget : Gtk.EventBox {
return !set_active;
}
private bool update_from_account() throws Error {
private void update_from_account() throws Error {
int index = this.from_multiple.get_active();
if (index < 0)
return false;
if (index >= 0) {
FromAddressMap selected = this.from_list.get(index);
this.from = selected.from;
assert(this.from_list.size > index);
Geary.Account new_account = this.from_list.get(index).account;
from = this.from_list.get(index).from;
if (new_account == this.account)
return false;
this.account = new_account;
this.load_signature.begin(null, (obj, res) => {
this.editor.update_signature(this.load_signature.end(res));
});
load_entry_completions();
return true;
if (selected.account != this.account) {
this.account = selected.account;
this.load_signature.begin(null, (obj, res) => {
this.editor.update_signature(this.load_signature.end(res));
});
load_entry_completions();
reopen_draft_manager_async();
}
}
}
private async string load_signature(Cancellable? cancellable = null) {
@ -2184,20 +2191,11 @@ public class ComposerWidget : Gtk.EventBox {
}
private void on_from_changed() {
bool changed = false;
try {
changed = update_from_account();
update_from_account();
} catch (Error err) {
debug("Unable to update From: Account in composer: %s", err.message);
}
// if the Geary.Account didn't change and the drafts manager
// is open(ing), do nothing more; need to check for the drafts
// manager because opening it in the case of multiple From: is
// handled here alone, so if changed open it if not already
if (changed || this.draft_manager == null) {
reopen_draft_manager_async.begin(null);
}
}
private bool on_button_release(Gdk.Event event) {