[geary/wip/713592-drafts] Hook up to composer widget, expand functionality and instrumentation
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/713592-drafts] Hook up to composer widget, expand functionality and instrumentation
- Date: Fri, 9 Jan 2015 00:34:57 +0000 (UTC)
commit fcb9f77f1a4c1090a363e0d0b390bcbd9121a3e0
Author: Jim Nelson <jim yorba org>
Date: Thu Jan 8 16:34:25 2015 -0800
Hook up to composer widget, expand functionality and instrumentation
src/client/composer/composer-widget.vala | 259 +++++++++++++++++-------------
src/engine/api/geary-email-flags.vala | 12 ++
src/engine/app/app-draft-manager.vala | 199 +++++++++++++++++++----
3 files changed, 321 insertions(+), 149 deletions(-)
---
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index 5a6e422..f7b94c9 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -112,7 +112,7 @@ public class ComposerWidget : Gtk.EventBox {
</body></html>""";
private const string CURSOR = "<span id=\"cursormarker\"></span>";
- private const int DRAFT_TIMEOUT_SEC = 10;
+ private const int DRAFT_TIMEOUT_SEC = 2;
public const string ATTACHMENT_KEYWORDS_SUFFIX = ".doc|.pdf|.xls|.ppt|.rtf|.pps";
@@ -237,12 +237,10 @@ public class ComposerWidget : Gtk.EventBox {
private string reply_message_id = "";
private bool top_posting = true;
- private Geary.FolderSupport.Create? drafts_folder = null;
- private Geary.EmailIdentifier? draft_id = null;
+ private Geary.App.DraftManager? draft_manager = null;
+ private Geary.EmailIdentifier? editing_draft_id = null;
+ private Geary.EmailFlags draft_flags = new Geary.EmailFlags.with(Geary.EmailFlags.DRAFT);
private uint draft_save_timeout_id = 0;
- private Cancellable cancellable_drafts = new Cancellable();
- private Cancellable cancellable_save_draft = new Cancellable();
- private bool in_draft_save = false;
public WebKit.WebView editor;
// We need to keep a reference to the edit-fixer in composer-window, so it doesn't get
@@ -443,7 +441,7 @@ public class ComposerWidget : Gtk.EventBox {
}
if (is_referred_draft)
- draft_id = referred.id;
+ editing_draft_id = referred.id;
add_attachments(referred.attachments);
break;
@@ -558,12 +556,12 @@ public class ComposerWidget : Gtk.EventBox {
chain.append(attachments_box);
box.set_focus_chain(chain);
- // If there's only one account, open the drafts folder. If there's more than one account,
- // the drafts folder will be opened by on_from_changed().
+ // If there's only one account, open the drafts manager. If there's more than one account,
+ // the drafts manager will be opened by on_from_changed().
if (!from_multiple.visible)
- open_drafts_folder_async.begin(cancellable_drafts);
+ open_draft_manager_async.begin(null);
- destroy.connect(() => { close_drafts_folder_async.begin(); });
+ destroy.connect(() => { close_draft_manager_async.begin(null); });
}
public ComposerWidget.from_mailto(Geary.Account account, string mailto) {
@@ -891,9 +889,10 @@ public class ComposerWidget : Gtk.EventBox {
}
private bool can_save() {
- return (drafts_folder != null && drafts_folder.get_open_state() == Geary.Folder.OpenState.BOTH
- && !drafts_folder.properties.create_never_returns_id && editor.can_undo()
- && account.information.save_drafts);
+ return draft_manager != null
+ && draft_manager.is_open
+ && editor.can_undo()
+ && account.information.save_drafts;
}
public CloseStatus should_close() {
@@ -902,10 +901,7 @@ public class ComposerWidget : Gtk.EventBox {
container.present();
AlertDialog dialog;
- if (drafts_folder == null && try_to_save) {
- dialog = new ConfirmationDialog(container.top_window,
- _("Do you want to discard the unsaved message?"), null, Stock._DISCARD);
- } else if (try_to_save) {
+ if (try_to_save) {
dialog = new TernaryConfirmationDialog(container.top_window,
_("Do you want to discard this message?"), null, Stock._KEEP, Stock._DISCARD,
Gtk.ResponseType.CLOSE);
@@ -919,13 +915,13 @@ public class ComposerWidget : Gtk.EventBox {
return CloseStatus.CANCEL_CLOSE; // Cancel
} else if (response == Gtk.ResponseType.OK) {
if (try_to_save) {
- save_and_exit.begin(); // Save
+ save_and_exit(); // Save
return CloseStatus.PENDING_CLOSE;
} else {
return CloseStatus.DO_CLOSE;
}
} else {
- delete_and_exit.begin(); // Discard
+ discard_and_exit(); // Discard
return CloseStatus.PENDING_CLOSE;
}
}
@@ -1049,8 +1045,6 @@ public class ComposerWidget : Gtk.EventBox {
// Used internally by on_send()
private async void on_send_async() {
- cancellable_save_draft.cancel();
-
container.vanish();
linkify_document(editor.get_dom_document());
@@ -1062,83 +1056,157 @@ public class ComposerWidget : Gtk.EventBox {
GLib.message("Error sending email: %s", e.message);
}
- yield delete_draft_async();
- container.close_container(); // Only close window after draft is deleted; this closes the drafts
folder.
+ Geary.Nonblocking.Semaphore? semaphore = discard_draft();
+ if (semaphore != null) {
+ try {
+ yield semaphore.wait_async();
+ } catch (Error err) {
+ // ignored
+ }
+ }
+
+ // Only close window after draft is deleted; this closes the drafts folder.
+ container.close_container();
+ }
+
+ private void on_draft_state_changed() {
+ switch (draft_manager.draft_state) {
+ case Geary.App.DraftManager.DraftState.STORED:
+ draft_save_text = DRAFT_SAVED_TEXT;
+ break;
+
+ case Geary.App.DraftManager.DraftState.STORING:
+ draft_save_text = DRAFT_SAVING_TEXT;
+ break;
+
+ case Geary.App.DraftManager.DraftState.NOT_STORED:
+ draft_save_text = "";
+ break;
+
+ case Geary.App.DraftManager.DraftState.ERROR:
+ draft_save_text = DRAFT_ERROR_TEXT;
+ break;
+
+ default:
+ assert_not_reached();
+ }
}
- private void on_drafts_opened(Geary.Folder.OpenState open_state, int count) {
- if (open_state == Geary.Folder.OpenState.BOTH)
- reset_draft_timer();
+ private void on_draft_manager_fatal(Error err) {
+ draft_save_text = DRAFT_ERROR_TEXT;
+ }
+
+ private void connect_to_draft_manager() {
+ draft_manager.notify[Geary.App.DraftManager.PROP_DRAFT_STATE].connect(on_draft_state_changed);
+ draft_manager.fatal.connect(on_draft_manager_fatal);
+ }
+
+ // 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() {
+ draft_manager.notify[Geary.App.DraftManager.PROP_DRAFT_STATE].disconnect(on_draft_state_changed);
+ draft_manager.fatal.disconnect(on_draft_manager_fatal);
}
// Returns the drafts folder for the current From account.
- private async void open_drafts_folder_async(Cancellable cancellable) throws Error {
- yield close_drafts_folder_async(cancellable);
+ private async void open_draft_manager_async(Cancellable? cancellable) throws Error {
+ yield close_draft_manager_async(cancellable);
if (!account.information.save_drafts)
return;
- Geary.FolderSupport.Create? folder = (yield account.get_required_special_folder_async(
- Geary.SpecialFolderType.DRAFTS, cancellable)) as Geary.FolderSupport.Create;
+ draft_manager = new Geary.App.DraftManager(account);
+ try {
+ yield draft_manager.open_async(editing_draft_id, cancellable);
+ } catch (Error err) {
+ debug("Unable to open draft manager %s: %s", draft_manager.to_string(), err.message);
+
+ draft_manager = null;
+
+ throw err;
+ }
+
+ // clear now, as it was only needed to open draft manager
+ editing_draft_id = null;
+
+ connect_to_draft_manager();
+ }
+
+ private async void close_draft_manager_async(Cancellable? cancellable) throws Error {
+ // clear status text
+ draft_save_text = "";
- if (folder == null)
- return; // No drafts folder.
+ // only clear editing_draft_id if associated with prior draft_manager, not due to this
+ // widget being initialized with it
+ if (draft_manager == null)
+ return;
- yield folder.open_async(Geary.Folder.OpenFlags.FAST_OPEN | Geary.Folder.OpenFlags.NO_DELAY,
- cancellable);
+ disconnect_from_draft_manager();
- drafts_folder = folder;
- drafts_folder.opened.connect(on_drafts_opened);
+ // drop ref even if close failed
+ try {
+ yield draft_manager.close_async(cancellable);
+ } finally {
+ draft_manager = null;
+ editing_draft_id = null;
+ }
}
- private async void close_drafts_folder_async(Cancellable? cancellable = null) throws Error {
- if (drafts_folder == null)
+ // Resets the draft save timeout.
+ private void reset_draft_timer() {
+ draft_save_text = "";
+ cancel_draft_timer();
+
+ if (can_save())
+ draft_save_timeout_id = Timeout.add_seconds(DRAFT_TIMEOUT_SEC, on_save_draft_timeout);
+ }
+
+ // Cancels the draft save timeout
+ private void cancel_draft_timer() {
+ if (draft_save_timeout_id == 0)
return;
- // Close existing folder.
- drafts_folder.opened.disconnect(on_drafts_opened);
- yield drafts_folder.close_async(cancellable);
- drafts_folder = null;
+ Source.remove(draft_save_timeout_id);
+ draft_save_timeout_id = 0;
}
- // Save to the draft folder, if available.
- // Note that drafts are NOT "linkified."
private bool on_save_draft_timeout() {
- // since all control paths return false, this is not rescheduled by the event loop, so
- // kill the timeout id
+ // this is not rescheduled by the event loop, so kill the timeout id
draft_save_timeout_id = 0;
- if (in_draft_save)
- return false;
-
- in_draft_save = true;
- save_async.begin(cancellable_save_draft, () => { in_draft_save = false; });
+ save_draft();
return false;
}
- private async void save_async(Cancellable? cancellable) {
- if (drafts_folder == null || !can_save())
- return;
-
+ // Note that drafts are NOT "linkified."
+ private Geary.Nonblocking.Semaphore? save_draft() {
+ // cancel timer in favor of just doing it now
cancel_draft_timer();
- draft_save_text = DRAFT_SAVING_TEXT;
+ try {
+ if (draft_manager != null)
+ return draft_manager.update(get_composed_email(null, true), draft_flags, null);
+ } catch (Error err) {
+ GLib.message("Unable to save draft: %s", err.message);
+ }
- Geary.EmailFlags flags = new Geary.EmailFlags();
- flags.add(Geary.EmailFlags.DRAFT);
+ return null;
+ }
+
+ private Geary.Nonblocking.Semaphore? discard_draft() {
+ // cancel timer in favor of this operation
+ cancel_draft_timer();
try {
- // only save HTML drafts to avoid resetting the DOM (which happens when converting the
- // HTML to flowed text)
- draft_id = yield drafts_folder.create_email_async(new Geary.RFC822.Message.from_composed_email(
- get_composed_email(null, true), null), flags, null, draft_id, cancellable);
-
- draft_save_text = DRAFT_SAVED_TEXT;
- } catch (Error e) {
- GLib.message("Error saving draft: %s", e.message);
- draft_save_text = DRAFT_ERROR_TEXT;
+ if (draft_manager != null)
+ return draft_manager.discard();
+ } catch (Error err) {
+ GLib.message("Unable to discard draft: %s", err.message);
}
+
+ return null;
}
// Used while waiting for draft to save before closing widget.
@@ -1147,42 +1215,24 @@ public class ComposerWidget : Gtk.EventBox {
cancel_draft_timer();
}
- private async void save_and_exit() {
+ private void save_and_exit() {
make_gui_insensitive();
- // Do the save.
- yield save_async(null);
+ save_draft();
container.close_container();
}
- private async void delete_and_exit() {
+ private void discard_and_exit() {
make_gui_insensitive();
- // Do the delete.
- yield delete_draft_async();
+ discard_draft();
+ if (draft_manager != null)
+ draft_manager.discard_on_close = true;
container.close_container();
}
- private async void delete_draft_async() {
- if (drafts_folder == null || draft_id == null)
- return;
-
- Geary.FolderSupport.Remove? removable_drafts = drafts_folder as Geary.FolderSupport.Remove;
- if (removable_drafts == null) {
- debug("Draft folder does not support remove.\n");
-
- return;
- }
-
- try {
- yield removable_drafts.remove_single_email_async(draft_id);
- } catch (Error e) {
- debug("Unable to delete draft: %s", e.message);
- }
- }
-
private void on_add_attachment_button_clicked() {
AttachmentDialog dialog = null;
do {
@@ -1903,27 +1953,6 @@ public class ComposerWidget : Gtk.EventBox {
return false;
}
- // Resets the draft save timeout.
- private void reset_draft_timer() {
- if (!can_save())
- return;
-
- draft_save_text = "";
- cancel_draft_timer();
-
- if (drafts_folder != null)
- draft_save_timeout_id = Timeout.add_seconds(DRAFT_TIMEOUT_SEC, on_save_draft_timeout);
- }
-
- // Cancels the draft save timeout
- private void cancel_draft_timer() {
- if (draft_save_timeout_id == 0)
- return;
-
- Source.remove(draft_save_timeout_id);
- draft_save_timeout_id = 0;
- }
-
private void update_actions() {
// Undo/redo.
actions.get_action(ACTION_UNDO).sensitive = editor.can_undo();
@@ -2056,10 +2085,10 @@ public class ComposerWidget : Gtk.EventBox {
// if the Geary.Account didn't change and the drafts folder is open(ing), do nothing more;
// need to check for the drafts folder because opening it in the case of multiple From:
// is handled here alone, so need to open it if not already
- if (!changed && drafts_folder != null)
+ if (!changed && draft_manager != null)
return;
- open_drafts_folder_async.begin(cancellable_drafts);
+ open_draft_manager_async.begin(null);
reset_draft_timer();
}
diff --git a/src/engine/api/geary-email-flags.vala b/src/engine/api/geary-email-flags.vala
index 2eddfec..b67c72e 100644
--- a/src/engine/api/geary-email-flags.vala
+++ b/src/engine/api/geary-email-flags.vala
@@ -41,6 +41,18 @@ public class Geary.EmailFlags : Geary.NamedFlags {
public EmailFlags() {
}
+ /**
+ * Create a new { link EmailFlags} container initialized with one or more flags.
+ */
+ public EmailFlags.with(Geary.NamedFlag flag1, ...) {
+ va_list args = va_list();
+ NamedFlag? flag = flag1;
+
+ do {
+ add(flag);
+ } while((flag = args.arg()) != null);
+ }
+
// Convenience method to check if the unread flag is set.
public inline bool is_unread() {
return contains(UNREAD);
diff --git a/src/engine/app/app-draft-manager.vala b/src/engine/app/app-draft-manager.vala
index 2724686..2aad54c 100644
--- a/src/engine/app/app-draft-manager.vala
+++ b/src/engine/app/app-draft-manager.vala
@@ -13,6 +13,10 @@
* asynchronous) methods for the composer to schedule remote operations without worrying about
* synchronization, operation ordering, error-handling, and so forth.
*
+ * If successive drafts are submitted for storage, drafts waiting in the queue (i.e. not yet sent
+ * to the server) are dropped without further consideration. This prevents needless I/O with the
+ * server saving drafts that are only to be replaced by later versions.
+ *
* Important: This object should be used ''per'' composed email and not to manage multiple emails
* being composed to the same { link Account}. DraftManager's internal state is solely for managing
* the lifecycle of a single email being composed by the user.
@@ -24,6 +28,9 @@
public class Geary.App.DraftManager : BaseObject {
public const string PROP_IS_OPEN = "is-open";
public const string PROP_DRAFT_STATE = "draft-state";
+ public const string PROP_VERSIONS_SAVED = "versions-saved";
+ public const string PROP_VERSIONS_DROPPED = "versions-dropped";
+ public const string PROP_DISCARD_ON_CLOSE = "discard-on-close";
/**
* Current saved state of the draft.
@@ -84,25 +91,60 @@ public class Geary.App.DraftManager : BaseObject {
*/
public DraftState draft_state { get; private set; default = DraftState.NOT_STORED; }
+ /**
+ * The { link Geary.EmailIdentifier} of the last saved draft.
+ */
+ public Geary.EmailIdentifier? current_draft_id { get; private set; default = null; }
+
+ /**
+ * The version number of the most recently saved draft.
+ *
+ * Even if an initial draft is supplied (with { link open_async}, this always starts at zero.
+ * It merely represents the number of times a draft was successfully saved.
+ *
+ * A { link discard} operation will reset this counter to zero.
+ */
+ public int versions_saved { get; private set; default = 0; }
+
+ /**
+ * The number of drafts dropped as new ones are added to the queue.
+ *
+ * @see dropped
+ */
+ public int versions_dropped { get; private set; default = 0; }
+
+ /**
+ * When set, the draft will be discarded when { link close_async} is called.
+ *
+ * In addition, when set all future { link update}s will result in the draft being dropped.
+ */
+ public bool discard_on_close { get; set; default = false; }
+
private Account account;
private Folder? drafts_folder = null;
private FolderSupport.Create? create_support = null;
private FolderSupport.Remove? remove_support = null;
- private EmailIdentifier? current_draft = null;
private Nonblocking.Mailbox<Operation?> mailbox = new Nonblocking.Mailbox<Operation?>();
private bool was_opened = false;
private Error? fatal_err = null;
/**
- * Fired if an unrecoverable error occurs while processing drafts.
+ * Fired when a draft is successfully saved.
+ */
+ public signal void stored(Geary.ComposedEmail draft);
+
+ /**
+ * Fired when a draft is discarded.
+ */
+ public signal void discarded();
+
+ /**
+ * Fired when a draft is dropped.
*
- * The { link DraftManager} will be unable to process future drafts.
+ * This occurs when a draft is scheduled for { link update} while another draft is queued
+ * to be pushed to the server. The queued draft is dropped in favor of the new one.
*/
- public virtual signal void fatal(Error err) {
- fatal_err = err;
-
- debug("%s: Irrecoverable failure: %s", to_string(), err.message);
- }
+ public signal void dropped(Geary.ComposedEmail draft);
/**
* Fired when unable to save a draft but the { link DraftManager} remains open.
@@ -115,10 +157,31 @@ public class Geary.App.DraftManager : BaseObject {
debug("%s: Unable to create draft: %s", to_string(), err.message);
}
+ /**
+ * Fired if an unrecoverable error occurs while processing drafts.
+ *
+ * The { link DraftManager} will be unable to process future drafts.
+ */
+ public virtual signal void fatal(Error err) {
+ fatal_err = err;
+
+ debug("%s: Irrecoverable failure: %s", to_string(), err.message);
+ }
+
public DraftManager(Geary.Account account) {
this.account = account;
}
+ protected void notify_stored(Geary.ComposedEmail draft) {
+ versions_saved++;
+ stored(draft);
+ }
+
+ protected void notify_discarded() {
+ versions_saved = 0;
+ discarded();
+ }
+
/**
* Open the { link DraftManager} and prepare it for handling composed messages.
*
@@ -134,7 +197,7 @@ public class Geary.App.DraftManager : BaseObject {
*
* @see is_open
*/
- public async void open_async(Geary.EmailIdentifier? initial_draft, Cancellable? cancellable = null)
+ public async void open_async(Geary.EmailIdentifier? initial_draft_id, Cancellable? cancellable = null)
throws Error {
if (is_open)
throw new EngineError.ALREADY_OPEN("%s is already open", to_string());
@@ -143,14 +206,15 @@ public class Geary.App.DraftManager : BaseObject {
was_opened = true;
- current_draft = initial_draft;
- if (current_draft != null)
+ current_draft_id = initial_draft_id;
+ if (current_draft_id != null)
draft_state = DraftState.STORED;
drafts_folder = account.get_special_folder(SpecialFolderType.DRAFTS);
if (drafts_folder == null)
throw new EngineError.NOT_FOUND("%s: No drafts folder found", to_string());
+ // if drafts folder doesn't support create and remove, call it quits
create_support = drafts_folder as Geary.FolderSupport.Create;
remove_support = drafts_folder as Geary.FolderSupport.Remove;
if (create_support == null || remove_support == null) {
@@ -162,6 +226,19 @@ public class Geary.App.DraftManager : BaseObject {
yield drafts_folder.open_async(Folder.OpenFlags.NONE, cancellable);
+ // if drafts folder doesn't return the identifier of newly created emails, then this object
+ // can't do it's work ... wait until open to check for this, to be absolutely sure
+ if (drafts_folder.properties.create_never_returns_id) {
+ try {
+ yield drafts_folder.close_async();
+ } catch (Error err) {
+ // ignore
+ }
+
+ throw new EngineError.UNSUPPORTED("%s: Drafts folder %s does not return created mail ID",
+ to_string(), drafts_folder.to_string());
+ }
+
// start the operation message loop, which ensures commands are handled in orderly fashion
operation_loop_async.begin();
@@ -170,8 +247,10 @@ public class Geary.App.DraftManager : BaseObject {
}
private void on_folder_closed(Folder.CloseReason reason) {
- fatal(new EngineError.SERVER_UNAVAILABLE("%s: Unexpected drafts folder closed (%s)",
- to_string(), reason.to_string()));
+ if (reason == Folder.CloseReason.FOLDER_CLOSED) {
+ fatal(new EngineError.SERVER_UNAVAILABLE("%s: Unexpected drafts folder closed (%s)",
+ to_string(), reason.to_string()));
+ }
}
/**
@@ -191,21 +270,38 @@ public class Geary.App.DraftManager : BaseObject {
// don't flush a CLOSE down the pipe if failed, the operation loop is closed for business
if (fatal_err == null) {
+ // if discarding on close, do so now
+ if (discard_on_close) {
+ // don't use discard(), which checks if open, but submit_push() directly,
+ // which doesn't
+ submit_push(null, null, null);
+ }
+
// flush pending I/O
Nonblocking.Semaphore semaphore = new Nonblocking.Semaphore(cancellable);
mailbox.send(new Operation(OperationType.CLOSE, null, null, null, semaphore));
// wait for close to complete
- yield semaphore.wait_async(cancellable);
+ try {
+ yield semaphore.wait_async(cancellable);
+ } catch (Error err) {
+ if (err is IOError.CANCELLED)
+ throw err;
+
+ // fall through
+ }
}
// Disconnect before closing, as signal handler is for unexpected closes
drafts_folder.closed.disconnect(on_folder_closed);
- yield drafts_folder.close_async(cancellable);
- drafts_folder = null;
- create_support = null;
- remove_support = null;
+ try {
+ yield drafts_folder.close_async(cancellable);
+ } finally {
+ drafts_folder = null;
+ create_support = null;
+ remove_support = null;
+ }
}
private void check_open() throws EngineError {
@@ -218,10 +314,15 @@ public class Geary.App.DraftManager : BaseObject {
*
* See { link FolderSupport.Create.create_email_async} for more information on the flags and
* date_received arguments.
+ *
+ * @returns A { link Semaphore} that is notified when the operation completes (with or without
+ * error)
*/
- public void update(Geary.ComposedEmail draft, Geary.EmailFlags? flags, DateTime? date_received)
- throws Error {
- submit_push(draft, flags, date_received);
+ public Geary.Nonblocking.Semaphore? update(Geary.ComposedEmail draft, Geary.EmailFlags? flags,
+ DateTime? date_received) throws Error {
+ check_open();
+
+ return submit_push(draft, flags, date_received);
}
/**
@@ -232,22 +333,44 @@ public class Geary.App.DraftManager : BaseObject {
*
* Note: Replaced drafts are deleted, but on some services (i.e. Gmail) those deleted messages
* are actually moved to the Trash. This call does not currently solve that problem.
+ *
+ * @returns A { link Semaphore} that is notified when the operation completes (with or without
+ * error)
*/
- public void discard() throws Error {
- submit_push(null, null, null);
+ public Geary.Nonblocking.Semaphore? discard() throws Error {
+ check_open();
+
+ return submit_push(null, null, null);
}
- private void submit_push(ComposedEmail? draft, EmailFlags? flags, DateTime? date_received)
- throws Error {
- check_open();
+ // Note that this call doesn't check_open(), important when used within close_async()
+ private Nonblocking.Semaphore? submit_push(ComposedEmail? draft, EmailFlags? flags,
+ DateTime? date_received) {
+ // no drafts are pushed when discarding on close
+ if (draft != null && discard_on_close) {
+ versions_dropped++;
+ dropped(draft);
+
+ return null;
+ }
- // clear out pending updates
+ // clear out pending pushes (which can be updates or discards)
mailbox.revoke_matching((op) => {
+ // count and notify of dropped drafts
+ if (op.op_type == OperationType.PUSH && op.draft != null) {
+ versions_dropped++;
+ dropped(op.draft);
+ }
+
return op.op_type == OperationType.PUSH;
});
+ Nonblocking.Semaphore semaphore = new Nonblocking.Semaphore();
+
// schedule this draft for update (if null, it's a discard)
- mailbox.send(new Operation(OperationType.PUSH, draft, flags, date_received, null));
+ mailbox.send(new Operation(OperationType.PUSH, draft, flags, date_received, semaphore));
+
+ return semaphore;
}
private async void operation_loop_async() {
@@ -297,24 +420,32 @@ public class Geary.App.DraftManager : BaseObject {
// delete old draft for all PUSHes: best effort ... since create_email_async() will handle
// replacement in a transactional-kinda-way, only outright delete if not using create
- if (current_draft != null && op.draft == null) {
+ if (current_draft_id != null && op.draft == null) {
+ bool success = false;
try {
- yield remove_support.remove_single_email_async(current_draft);
+ yield remove_support.remove_single_email_async(current_draft_id);
+ success = true;
} catch (Error err) {
- debug("%s: Unable to remove existing draft %s: %s", to_string(), current_draft.to_string(),
+ debug("%s: Unable to remove existing draft %s: %s", to_string(),
current_draft_id.to_string(),
err.message);
}
- current_draft = null;
+ // always clear draft id (assuming that retrying a failed remove is unnecessary), but
+ // only signal the discard if it actually was removed
+ current_draft_id = null;
+ if (success)
+ notify_discarded();
}
// if draft supplied, save it
if (op.draft != null) {
RFC822.Message rfc822 = new RFC822.Message.from_composed_email(op.draft, null);
try {
- current_draft = yield create_support.create_email_async(rfc822, op.flags,
- op.date_received, current_draft, null);
+ current_draft_id = yield create_support.create_email_async(rfc822, op.flags,
+ op.date_received, current_draft_id, null);
+
draft_state = DraftState.STORED;
+ notify_stored(op.draft);
} catch (Error err) {
draft_state = DraftState.ERROR;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]