[geary] Retry commands properly if connection lost: Bug #714540
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary] Retry commands properly if connection lost: Bug #714540
- Date: Tue, 27 Jan 2015 01:09:49 +0000 (UTC)
commit d4cc681e91ab1c5353f2516254c0b703ddd5d03c
Author: Jim Nelson <jim yorba org>
Date: Mon Jan 26 17:07:10 2015 -0800
Retry commands properly if connection lost: Bug #714540
If a command fails due to a hard error (connection dropped, network
loss, etc.), the command is preserved in the in-memory Folder queue
and retried when the connection is reestablished. This makes Geary
more robust and resistant to simple errors due to connection loss,
i.e. archiving a message and having it return later because the
archive command was dropped and, when the connection reestablished,
the message "reappears" because it's still on the server.
Note that this is *not* offline mode or a replacement for it, merely
a way to make Geary more robust when a user's connection is flaky.
src/client/application/geary-controller.vala | 8 +-
src/engine/api/geary-folder-supports-archive.vala | 14 +--
src/engine/api/geary-folder-supports-copy.vala | 1 +
src/engine/api/geary-folder-supports-create.vala | 3 +-
src/engine/api/geary-folder-supports-mark.vala | 17 +--
src/engine/api/geary-folder-supports-move.vala | 1 +
src/engine/api/geary-folder-supports-remove.vala | 14 +--
src/engine/api/geary-folder.vala | 4 +
src/engine/app/app-draft-manager.vala | 3 +-
.../imap-engine/imap-engine-generic-folder.vala | 6 +-
.../imap-engine/imap-engine-minimal-folder.vala | 139 ++++++++++----------
.../imap-engine/imap-engine-replay-operation.vala | 38 ++++--
.../imap-engine/imap-engine-replay-queue.vala | 36 +++++-
.../imap-engine-send-replay-operation.vala | 8 +-
src/engine/imap-engine/imap-engine.vala | 19 +++
.../imap-engine-abstract-list-email.vala | 2 +-
.../replay-ops/imap-engine-copy-email.vala | 2 +-
.../replay-ops/imap-engine-create-email.vala | 23 +++-
.../replay-ops/imap-engine-empty-folder.vala | 2 +-
.../replay-ops/imap-engine-fetch-email.vala | 3 +-
.../replay-ops/imap-engine-mark-email.vala | 8 +-
.../replay-ops/imap-engine-move-email.vala | 27 +++-
.../replay-ops/imap-engine-remove-email.vala | 5 +-
.../replay-ops/imap-engine-replay-append.vala | 10 +-
.../replay-ops/imap-engine-replay-disconnect.vala | 15 ++-
.../replay-ops/imap-engine-replay-removal.vala | 9 +-
.../imap-engine-server-search-email.vala | 3 +
src/engine/imap/api/imap-folder.vala | 17 ++-
src/engine/imap/command/imap-store-command.vala | 20 +++-
.../nonblocking-abstract-semaphore.vala | 12 --
src/engine/nonblocking/nonblocking-mailbox.vala | 3 +-
.../nonblocking-reporting-semaphore.vala | 4 +-
32 files changed, 279 insertions(+), 197 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 2bae336..3ed92e9 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -1262,7 +1262,7 @@ public class GearyController : Geary.BaseObject {
debug("Switching to %s...", folder.to_string());
- cancel_folder();
+ closed_folder();
// This function is not reentrant. It should be, because it can be
// called reentrant-ly if you select folders quickly enough. This
@@ -1525,6 +1525,12 @@ public class GearyController : Geary.BaseObject {
old_cancellable.cancel();
}
+ // Like cancel_folder() but doesn't cancel outstanding operations, allowing them to complete
+ // in the background
+ private void closed_folder() {
+ cancellable_folder = new Cancellable();
+ }
+
private void cancel_inbox(Geary.Account account) {
if (!inbox_cancellables.has_key(account)) {
debug("Unable to cancel inbox operation for %s", account.to_string());
diff --git a/src/engine/api/geary-folder-supports-archive.vala
b/src/engine/api/geary-folder-supports-archive.vala
index 9796a76..02bcde8 100644
--- a/src/engine/api/geary-folder-supports-archive.vala
+++ b/src/engine/api/geary-folder-supports-archive.vala
@@ -13,6 +13,7 @@
* usually in an All Mail folder and perhaps others. It does not imply that the mail message was
* moved to the Trash folder.
*/
+
public interface Geary.FolderSupport.Archive : Geary.Folder {
/**
* Archives the specified emails from the folder.
@@ -21,18 +22,5 @@ public interface Geary.FolderSupport.Archive : Geary.Folder {
*/
public abstract async void archive_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
Cancellable? cancellable = null) throws Error;
-
- /**
- * Archive one email from the folder.
- *
- * The { link Geary.Folder} must be opened prior to attempting this operation.
- */
- public virtual async void archive_single_email_async(Geary.EmailIdentifier email_id,
- Cancellable? cancellable = null) throws Error {
- Gee.ArrayList<Geary.EmailIdentifier> ids = new Gee.ArrayList<Geary.EmailIdentifier>();
- ids.add(email_id);
-
- yield archive_email_async(ids, cancellable);
- }
}
diff --git a/src/engine/api/geary-folder-supports-copy.vala b/src/engine/api/geary-folder-supports-copy.vala
index 351a16a..f3fcdab 100644
--- a/src/engine/api/geary-folder-supports-copy.vala
+++ b/src/engine/api/geary-folder-supports-copy.vala
@@ -12,6 +12,7 @@
*
* Copy does not imply { link Geary.FolderSupport.Move}, or vice-versa.
*/
+
public interface Geary.FolderSupport.Copy : Geary.Folder {
/**
* Copies messages into another folder.
diff --git a/src/engine/api/geary-folder-supports-create.vala
b/src/engine/api/geary-folder-supports-create.vala
index babffe6..6b74581 100644
--- a/src/engine/api/geary-folder-supports-create.vala
+++ b/src/engine/api/geary-folder-supports-create.vala
@@ -13,6 +13,7 @@
* Note that creating an email in the Outbox will queue it for sending. Thus, it may be removed
* without user interaction at some point in the future.
*/
+
public interface Geary.FolderSupport.Create : Geary.Folder {
/**
* Creates (appends) the message to this folder.
@@ -29,6 +30,6 @@ public interface Geary.FolderSupport.Create : Geary.Folder {
* message is created. The new message's ID is returned.
*/
public abstract async Geary.EmailIdentifier? create_email_async(Geary.RFC822.Message rfc822, EmailFlags?
flags,
- DateTime? date_received, Geary.EmailIdentifier? id = null, Cancellable? cancellable = null) throws
Error;
+ DateTime? date_received, Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error;
}
diff --git a/src/engine/api/geary-folder-supports-mark.vala b/src/engine/api/geary-folder-supports-mark.vala
index 928aa10..5b1be59 100644
--- a/src/engine/api/geary-folder-supports-mark.vala
+++ b/src/engine/api/geary-folder-supports-mark.vala
@@ -8,6 +8,7 @@
* The addition of the Geary.FolderSupport.Mark interface indicates the { link Geary.Folder}
* supports marking and unmarking messages with system and user-defined flags.
*/
+
public interface Geary.FolderSupport.Mark : Geary.Folder {
/**
* Adds and removes flags from a list of messages.
@@ -15,21 +16,7 @@ public interface Geary.FolderSupport.Mark : Geary.Folder {
* The { link Geary.Folder} must be opened prior to attempting this operation.
*/
public abstract async void mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
- Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
- Cancellable? cancellable = null) throws Error;
-
- /**
- * Adds and removes flags from a single message.
- *
- * The { link Geary.Folder} must be opened prior to attempting this operation.
- */
- public virtual async void mark_single_email_async(Geary.EmailIdentifier to_mark,
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
- Cancellable? cancellable = null) throws Error {
- Gee.ArrayList<Geary.EmailIdentifier> list = new Gee.ArrayList<Geary.EmailIdentifier>();
- list.add(to_mark);
-
- yield mark_email_async(list, flags_to_add, flags_to_remove, cancellable);
- }
+ Cancellable? cancellable = null) throws Error;
}
diff --git a/src/engine/api/geary-folder-supports-move.vala b/src/engine/api/geary-folder-supports-move.vala
index 58ededf..e4f45c3 100644
--- a/src/engine/api/geary-folder-supports-move.vala
+++ b/src/engine/api/geary-folder-supports-move.vala
@@ -11,6 +11,7 @@
*
* Move does not imply { link Geary.FolderSupport.Copy}, or vice-versa.
*/
+
public interface Geary.FolderSupport.Move : Geary.Folder {
/**
* Moves messages to another folder.
diff --git a/src/engine/api/geary-folder-supports-remove.vala
b/src/engine/api/geary-folder-supports-remove.vala
index 8326fb5..0dcd37b 100644
--- a/src/engine/api/geary-folder-supports-remove.vala
+++ b/src/engine/api/geary-folder-supports-remove.vala
@@ -19,6 +19,7 @@
* A Folder that does not support Remove does not imply that email might not be removed later,
* such as by the server.
*/
+
public interface Geary.FolderSupport.Remove : Geary.Folder {
/**
* Removes the specified emails from the folder.
@@ -27,18 +28,5 @@ public interface Geary.FolderSupport.Remove : Geary.Folder {
*/
public abstract async void remove_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
Cancellable? cancellable = null) throws Error;
-
- /**
- * Removes one email from the folder.
- *
- * The { link Geary.Folder} must be opened prior to attempting this operation.
- */
- public virtual async void remove_single_email_async(Geary.EmailIdentifier email_id,
- Cancellable? cancellable = null) throws Error {
- Gee.ArrayList<Geary.EmailIdentifier> ids = new Gee.ArrayList<Geary.EmailIdentifier>();
- ids.add(email_id);
-
- yield remove_email_async(ids, cancellable);
- }
}
diff --git a/src/engine/api/geary-folder.vala b/src/engine/api/geary-folder.vala
index bf5b1cf..8f76fb3 100644
--- a/src/engine/api/geary-folder.vala
+++ b/src/engine/api/geary-folder.vala
@@ -483,6 +483,8 @@ public abstract class Geary.Folder : BaseObject {
* EmailIdentifier implies that the top most email is included in the result (i.e.
* ListFlags.INCLUDING_ID is not required);
*
+ * If the remote connection fails, this call will return locally-available Email without error.
+ *
* There's no guarantee of the returned messages' order.
*
* The Folder must be opened prior to attempting this operation.
@@ -499,6 +501,8 @@ public abstract class Geary.Folder : BaseObject {
* one email for each requested; duplicates are ignored. ListFlags.INCLUDING_ID is ignored
* for this call.
*
+ * If the remote connection fails, this call will return locally-available Email without error.
+ *
* The Folder must be opened prior to attempting this operation.
*/
public abstract async Gee.List<Geary.Email>? list_email_by_sparse_id_async(
diff --git a/src/engine/app/app-draft-manager.vala b/src/engine/app/app-draft-manager.vala
index 860248f..ff03b41 100644
--- a/src/engine/app/app-draft-manager.vala
+++ b/src/engine/app/app-draft-manager.vala
@@ -424,7 +424,8 @@ public class Geary.App.DraftManager : BaseObject {
if (current_draft_id != null && op.draft == null) {
bool success = false;
try {
- yield remove_support.remove_single_email_async(current_draft_id);
+ yield remove_support.remove_email_async(
+ iterate<EmailIdentifier>(current_draft_id).to_array_list());
success = true;
} catch (Error err) {
debug("%s: Unable to remove existing draft %s: %s", to_string(),
current_draft_id.to_string(),
diff --git a/src/engine/imap-engine/imap-engine-generic-folder.vala
b/src/engine/imap-engine/imap-engine-generic-folder.vala
index 2f234af..fb1d328 100644
--- a/src/engine/imap-engine/imap-engine-generic-folder.vala
+++ b/src/engine/imap-engine/imap-engine-generic-folder.vala
@@ -20,9 +20,9 @@ private class Geary.ImapEngine.GenericFolder : MinimalFolder, Geary.FolderSuppor
yield expunge_all_async(cancellable);
}
- public new async Geary.EmailIdentifier? create_email_async(
- RFC822.Message rfc822, Geary.EmailFlags? flags, DateTime? date_received,
- Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error {
+ public new async Geary.EmailIdentifier? create_email_async(RFC822.Message rfc822,
+ Geary.EmailFlags? flags, DateTime? date_received, Geary.EmailIdentifier? id,
+ Cancellable? cancellable = null) throws Error {
return yield base.create_email_async(rfc822, flags, date_received, id, cancellable);
}
}
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index 66d39b7..4d784f2 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -43,8 +43,9 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
private Folder.OpenFlags open_flags = OpenFlags.NONE;
private int open_count = 0;
private bool remote_opened = false;
- private Nonblocking.ReportingSemaphore<bool>? remote_semaphore = null;
- private ReplayQueue? replay_queue = null;
+ private Nonblocking.ReportingSemaphore<bool> remote_semaphore =
+ new Nonblocking.ReportingSemaphore<bool>(false);
+ private ReplayQueue replay_queue;
private int remote_count = -1;
private uint open_remote_timer_id = 0;
private int reestablish_delay_msec = DEFAULT_REESTABLISH_DELAY_MSEC;
@@ -59,6 +60,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
this.local_folder = local_folder;
_special_folder_type = special_folder_type;
_properties.add(local_folder.get_properties());
+ replay_queue = new ReplayQueue(this);
email_flag_watcher = new EmailFlagWatcher(this);
email_flag_watcher.email_flags_changed.connect(on_email_flags_changed);
@@ -484,7 +486,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
}
public override async void wait_for_open_async(Cancellable? cancellable = null) throws Error {
- if (open_count == 0 || remote_semaphore == null)
+ if (open_count == 0)
throw new EngineError.OPEN_REQUIRED("wait_for_open_async() can only be called after
open_async()");
// if remote has not yet been opened, do it now ... this bool can go true only once after
@@ -519,10 +521,8 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
// first open gets to name the flags, but see note above
this.open_flags = open_flags;
- remote_semaphore = new Geary.Nonblocking.ReportingSemaphore<bool>(false);
-
- // start the replay queue
- replay_queue = new ReplayQueue(this);
+ // reset to force waiting in wait_for_open_async()
+ remote_semaphore.reset();
// Unless NO_DELAY is set, do NOT open the remote side here; wait for the ReplayQueue to
// require a remote connection or wait_for_open_async() to be called ... this allows for
@@ -670,10 +670,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
// "hard" error in the sense of network conditions make connection impossible
// at the moment, "soft" error in the sense that some logical error prevented
// connect (like bad credentials)
- hard_failure = open_err is ImapError.NOT_CONNECTED
- || open_err is ImapError.TIMED_OUT
- || open_err is ImapError.SERVER_ERROR
- || open_err is EngineError.SERVER_UNAVAILABLE;
+ hard_failure = is_hard_failure(open_err);
} else if (open_err is IOError.CANCELLED) {
// user cancelled open, treat like soft error
hard_failure = false;
@@ -728,44 +725,23 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
// open success, reset reestablishment delay
reestablish_delay_msec = DEFAULT_REESTABLISH_DELAY_MSEC;
- int count;
- try {
- count = (remote_folder != null)
- ? remote_count
- : yield local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE, cancellable);
- } catch (Error count_err) {
- debug("Unable to fetch count from local folder %s: %s", to_string(), count_err.message);
-
- count = 0;
- }
+ // at this point, remote_folder should be set; there's no notion of a local-only open (yet)
+ assert(remote_folder != null);
// notify any threads of execution waiting for the remote folder to open that the result
// of that operation is ready
try {
- remote_semaphore.notify_result(remote_folder != null, null);
+ remote_semaphore.notify_result(true, null);
} catch (Error notify_err) {
- debug("%s: Unable to fire semaphore notifying remote folder ready/not ready: %s",
+ // This should only happen if cancelled, which can't happen without a Cancellable
+ warning("%s: Unable to fire semaphore notifying remote folder ready/not ready: %s",
to_string(), notify_err.message);
-
- // do this now rather than wait for close_internal_async() to execute to ensure that
- // any replay operations already queued don't attempt to run
- clear_remote_folder();
-
- notify_open_failed(Geary.Folder.OpenFailed.REMOTE_FAILED, notify_err);
-
- // schedule immediate close
- close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, false,
- cancellable);
-
- return;
}
_properties.add(remote_folder.properties);
// notify any subscribers with similar information
- notify_opened(
- (remote_folder != null) ? Geary.Folder.OpenState.BOTH : Geary.Folder.OpenState.LOCAL,
- count);
+ notify_opened(Geary.Folder.OpenState.BOTH, remote_count);
}
public override async void close_async(Cancellable? cancellable = null) throws Error {
@@ -775,17 +751,31 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
if (remote_folder != null)
_properties.remove(remote_folder.properties);
- yield close_internal_async(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, true,
- cancellable);
+ // block anyone from wait_until_open_async(), as this is no longer open
+ remote_semaphore.reset();
+
+ ReplayDisconnect disconnect_op = new ReplayDisconnect(this,
+ Imap.ClientSession.DisconnectReason.REMOTE_CLOSE, true, cancellable);
+ replay_queue.schedule(disconnect_op);
+
+ yield disconnect_op.wait_for_ready_async(cancellable);
}
// Close the remote connection and, if open_count is zero, the Folder itself. A Mutex is used
// to prevent concurrency.
//
+ // This is best called using a ReplayDisconnect operation, which ensures an orderly disconnect
+ // by going through the ReplayQueue. There are certain situations in open_remote_async() where
+ // this is not possible (because the queue hasn't been started).
+ //
// NOTE: This bypasses open_count and forces the Folder closed, reestablishing a connection if
// open_count is greater than zero
internal async void close_internal_async(Folder.CloseReason local_reason, Folder.CloseReason
remote_reason,
bool flush_pending, Cancellable? cancellable) {
+ // make sure no open is waiting in the wings to start; close_internal_locked_async() will
+ // reestablish a connection if necessary
+ cancel_remote_open_timer();
+
int token;
try {
token = yield close_mutex.claim_async(cancellable);
@@ -804,8 +794,6 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
// Should only be called when close_mutex is locked, i.e. use close_internal_async()
private async void close_internal_locked_async(Folder.CloseReason local_reason,
Folder.CloseReason remote_reason, bool flush_pending, Cancellable? cancellable) {
- cancel_remote_open_timer();
-
// only flushing pending ReplayOperations if this is a "clean" close, not forced due to
// error and if specified by caller (could be a non-error close on the server, i.e. "BYE",
// but the connection is dropping, so don't flush pending)
@@ -823,21 +811,26 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
if (!flush_pending)
closing_remote_folder = clear_remote_folder();
- // Close the replay queues; if a "clean" close, flush pending operations so everything
- // gets a chance to run; if forced close, drop everything outstanding
- try {
- if (replay_queue != null) {
- debug("Closing replay queue for %s... (flush_pending=%s)", to_string(),
- flush_pending.to_string());
- yield replay_queue.close_async(flush_pending);
- debug("Closed replay queue for %s", to_string());
+ // That said, only flush, close, and destroy the ReplayQueue if fully closing and not
+ // preparing for a connection reestablishment
+ if (open_count <= 0) {
+ // Close the replay queues; if a "clean" close, flush pending operations so everything
+ // gets a chance to run; if forced close, drop everything outstanding
+ try {
+ // swap out the ReplayQueue while closing so, if re-opened, future commands can
+ // be queued on the new queue
+ ReplayQueue closing_replay_queue = replay_queue;
+ replay_queue = new ReplayQueue(this);
+
+ debug("Closing replay queue for %s (flush_pending=%s): %s", to_string(),
+ flush_pending.to_string(), closing_replay_queue.to_string());
+ yield closing_replay_queue.close_async(flush_pending);
+ debug("Closed replay queue for %s: %s", to_string(), closing_replay_queue.to_string());
+ } catch (Error replay_queue_err) {
+ debug("Error closing %s replay queue: %s", to_string(), replay_queue_err.message);
}
- } catch (Error replay_queue_err) {
- debug("Error closing %s replay queue: %s", to_string(), replay_queue_err.message);
}
- replay_queue = new ReplayQueue(this);
-
// if a "clean" close, now go ahead and close the folder
if (flush_pending)
closing_remote_folder = clear_remote_folder();
@@ -901,12 +894,15 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
remote_folder = null;
remote_count = -1;
- remote_semaphore.reset();
- try {
- remote_semaphore.notify_result(false, null);
- } catch (Error err) {
- debug("Error attempting to notify that remote folder %s is now closed: %s", to_string(),
- err.message);
+ // only signal waiters in wait_for_open_async() that the open failed if there is no cx
+ // reestablishment to occur
+ if (open_count <= 0) {
+ try {
+ remote_semaphore.notify_result(false, null);
+ } catch (Error err) {
+ debug("Error attempting to notify that remote folder %s is now closed: %s", to_string(),
+ err.message);
+ }
}
return old_remote_folder;
@@ -955,6 +951,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
// prevent driving the value up
folder.open_count = Numeric.int_floor(folder.open_count - 1, 0);
+ debug("Reestablishing broken connection to %s", folder.to_string());
yield folder.open_async(OpenFlags.NO_DELAY, null);
} catch (Error err) {
debug("Error reestablishing broken connection to %s: %s", folder.to_string(), err.message);
@@ -1209,7 +1206,11 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
private void on_remote_disconnected(Imap.ClientSession.DisconnectReason reason) {
debug("on_remote_disconnected: reason=%s", reason.to_string());
- replay_queue.schedule(new ReplayDisconnect(this, reason));
+ // reset remote_semaphore to indicate that callers must again wait for the remote to open...
+ // do this now to avoid race conditions w/ wait_for_open_async()
+ remote_semaphore.reset();
+
+ replay_queue.schedule(new ReplayDisconnect(this, reason, false, null));
}
//
@@ -1293,7 +1294,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
// Helper function for child classes dealing with the delete/archive question. This method will
// mark the message as deleted and expunge it.
protected async void expunge_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
- Cancellable? cancellable = null) throws Error {
+ Cancellable? cancellable) throws Error {
check_open("expunge_email_async");
check_ids("expunge_email_async", email_ids);
@@ -1336,12 +1337,13 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
}
public virtual async void mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
- Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
+ Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
Cancellable? cancellable = null) throws Error {
check_open("mark_email_async");
MarkEmail mark = new MarkEmail(this, to_mark, flags_to_add, flags_to_remove, cancellable);
replay_queue.schedule(mark);
+
yield mark.wait_for_ready_async(cancellable);
}
@@ -1381,6 +1383,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
MoveEmail move = new MoveEmail(this, (Gee.List<ImapDB.EmailIdentifier>) to_move, destination);
replay_queue.schedule(move);
+
yield move.wait_for_ready_async(cancellable);
}
@@ -1439,9 +1442,9 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
return earliest_id;
}
- protected async Geary.EmailIdentifier? create_email_async(
- RFC822.Message rfc822, Geary.EmailFlags? flags, DateTime? date_received,
- Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error {
+ protected async Geary.EmailIdentifier? create_email_async(RFC822.Message rfc822,
+ Geary.EmailFlags? flags, DateTime? date_received, Geary.EmailIdentifier? id,
+ Cancellable? cancellable = null) throws Error {
check_open("create_email_async");
if (id != null)
check_id("create_email_async", id);
@@ -1465,7 +1468,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
// Remove old message.
if (id != null && remove_folder != null)
- yield remove_folder.remove_single_email_async(id, null);
+ yield remove_folder.remove_email_async(iterate<EmailIdentifier>(id).to_array_list());
// If the user cancelled the operation, throw the error here.
if (cancel_error != null)
@@ -1474,7 +1477,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
// If the caller cancelled during the remove operation, delete the newly created message to
// safely back out.
if (cancellable != null && cancellable.is_cancelled() && ret != null && remove_folder != null)
- yield remove_folder.remove_single_email_async(ret, null);
+ yield remove_folder.remove_email_async(iterate<EmailIdentifier>(ret).to_array_list());
return ret;
}
diff --git a/src/engine/imap-engine/imap-engine-replay-operation.vala
b/src/engine/imap-engine/imap-engine-replay-operation.vala
index 5662521..84b87ad 100644
--- a/src/engine/imap-engine/imap-engine-replay-operation.vala
+++ b/src/engine/imap-engine/imap-engine-replay-operation.vala
@@ -4,7 +4,7 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
+private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject, Gee.Comparable<ReplayOperation> {
/**
* Scope specifies what type of operations (remote, local, or both) are needed by this operation.
*
@@ -29,20 +29,26 @@ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
CONTINUE
}
- private static int next_opnum = 0;
+ public enum OnError {
+ THROW,
+ RETRY,
+ IGNORE
+ }
public string name { get; set; }
- public int opnum { get; private set; }
+ public int64 submission_number { get; set; default = -1; }
public Scope scope { get; private set; }
+ public OnError on_remote_error { get; protected set; }
+ public int remote_retry_count { get; set; default = 0; }
public Error? err { get; private set; default = null; }
- public bool notified { get; private set; default = false; }
+ public bool notified { get { return semaphore.is_passed(); } }
private Nonblocking.Semaphore semaphore = new Nonblocking.Semaphore();
- public ReplayOperation(string name, Scope scope) {
+ public ReplayOperation(string name, Scope scope, OnError on_remote_error = OnError.THROW) {
this.name = name;
- opnum = next_opnum++;
this.scope = scope;
+ this.on_remote_error = on_remote_error;
}
/**
@@ -132,10 +138,9 @@ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
throw err;
}
+ // Can only be called once
internal void notify_ready(Error? err) {
- assert(!notified);
-
- notified = true;
+ assert(!semaphore.is_passed());
this.err = err;
@@ -148,11 +153,22 @@ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
public abstract string describe_state();
+ // The Comparable interface is merely to ensure the ReplayQueue sorts operations by their
+ // submission order, ensuring that retry operations are retried in order of submissions
+ public int compare_to(ReplayOperation other) {
+ assert(submission_number >= 0);
+ assert(other.submission_number >= 0);
+
+ return (int) (submission_number - other.submission_number).clamp(-1, 1);
+ }
+
public string to_string() {
string state = describe_state();
- return (String.is_empty(state)) ? "[%d] %s".printf(opnum, name)
- : "[%d] %s: %s".printf(opnum, name, state);
+ return String.is_empty(state)
+ ? "[%s] %s remote_retry_count=%d".printf(submission_number.to_string(), name, remote_retry_count)
+ : "[%s] %s: %s remote_retry_count=%d".printf(submission_number.to_string(), name, state,
+ remote_retry_count);
}
}
diff --git a/src/engine/imap-engine/imap-engine-replay-queue.vala
b/src/engine/imap-engine/imap-engine-replay-queue.vala
index e376774..af43bad 100644
--- a/src/engine/imap-engine/imap-engine-replay-queue.vala
+++ b/src/engine/imap-engine/imap-engine-replay-queue.vala
@@ -12,7 +12,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
private class CloseReplayQueue : ReplayOperation {
public CloseReplayQueue() {
// LOCAL_AND_REMOTE to make sure this operation is flushed all the way down the pipe
- base ("CloseReplayQueue", ReplayOperation.Scope.LOCAL_AND_REMOTE);
+ base ("CloseReplayQueue", ReplayOperation.Scope.LOCAL_AND_REMOTE, OnError.IGNORE);
}
public override void notify_remote_removed_position(Imap.SequenceNumber removed) {
@@ -56,6 +56,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
private ReplayOperation? remote_op_active = null;
private Gee.ArrayList<ReplayOperation> notification_queue = new Gee.ArrayList<ReplayOperation>();
private Scheduler.Scheduled? notification_timer = null;
+ private int64 next_submission_number = 0;
private bool is_closed = false;
@@ -149,6 +150,10 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
return false;
}
+ // assign a submission number to operation ... this *must* happen before it's submitted to
+ // any Mailbox
+ op.submission_number = next_submission_number++;
+
// note that in order for this to work (i.e. for sent and received operations to be handled
// in order), it's *vital* that even REMOTE_ONLY operations go through the local queue,
// only being scheduled on the remote queue *after* local operations ahead of it have
@@ -483,13 +488,36 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
Error? remote_err = null;
if (folder_opened || is_close_op) {
+ if (op.remote_retry_count > 0)
+ debug("Retrying op %s on %s", op.to_string(), to_string());
+
try {
yield op.replay_remote_async();
} catch (Error replay_err) {
- debug("Replay remote error for %s on %s: %s", op.to_string(), to_string(),
- replay_err.message);
+ debug("Replay remote error for %s on %s: %s (%s)", op.to_string(), to_string(),
+ replay_err.message, op.on_remote_error.to_string());
- remote_err = replay_err;
+ // If a hard failure and operation allows remote replay, schedule now
+ if ((op.on_remote_error == ReplayOperation.OnError.RETRY) &&
is_hard_failure(replay_err)) {
+ debug("Schedule op retry %s on %s", op.to_string(), to_string());
+
+ // the Folder will disconnect and reconnect due to the hard error and
+ // wait_for_open_async() will block this command until reconnected and
+ // normalized
+ op.remote_retry_count++;
+ remote_queue.send(op);
+
+ continue;
+ } else if (op.on_remote_error == ReplayOperation.OnError.IGNORE) {
+ // ignoring error, simply notify as completed and continue
+ debug("Ignoring op %s on %s", op.to_string(), to_string());
+ } else {
+ debug("Throwing remote error for op %s on %s: %s", op.to_string(), to_string(),
+ replay_err.message);
+
+ // store for notification
+ remote_err = replay_err;
+ }
}
} else if (!is_close_op) {
remote_err = new EngineError.SERVER_UNAVAILABLE("Folder %s not available",
owner.to_string());
diff --git a/src/engine/imap-engine/imap-engine-send-replay-operation.vala
b/src/engine/imap-engine/imap-engine-send-replay-operation.vala
index e36cc52..5ae0dce 100644
--- a/src/engine/imap-engine/imap-engine-send-replay-operation.vala
+++ b/src/engine/imap-engine/imap-engine-send-replay-operation.vala
@@ -5,12 +5,12 @@
*/
private abstract class Geary.ImapEngine.SendReplayOperation : Geary.ImapEngine.ReplayOperation {
- public SendReplayOperation(string name) {
- base (name, ReplayOperation.Scope.LOCAL_AND_REMOTE);
+ public SendReplayOperation(string name, ReplayOperation.OnError on_remote_error = OnError.THROW) {
+ base (name, ReplayOperation.Scope.LOCAL_AND_REMOTE, on_remote_error);
}
- public SendReplayOperation.only_remote(string name) {
- base (name, ReplayOperation.Scope.REMOTE_ONLY);
+ public SendReplayOperation.only_remote(string name, ReplayOperation.OnError on_remote_error =
OnError.THROW) {
+ base (name, ReplayOperation.Scope.REMOTE_ONLY, on_remote_error);
}
public override void notify_remote_removed_position(Imap.SequenceNumber removed) {
diff --git a/src/engine/imap-engine/imap-engine.vala b/src/engine/imap-engine/imap-engine.vala
index 6a21e59..3c7d18f 100644
--- a/src/engine/imap-engine/imap-engine.vala
+++ b/src/engine/imap-engine/imap-engine.vala
@@ -58,5 +58,24 @@ private void on_synchronizer_stopped(Object? source, AsyncResult result) {
assert(removed);
}
+/**
+ * A hard failure is defined as one due to hardware or connectivity issues, where a soft failure
+ * is due to software reasons, like credential failure or protocol violation.
+ */
+private static bool is_hard_failure(Error err) {
+ // CANCELLED is not a hard error
+ if (err is IOError.CANCELLED)
+ return false;
+
+ // Treat other errors -- most likely IOErrors -- as hard failures
+ if (!(err is ImapError) && !(err is EngineError))
+ return true;
+
+ return err is ImapError.NOT_CONNECTED
+ || err is ImapError.TIMED_OUT
+ || err is ImapError.SERVER_ERROR
+ || err is EngineError.SERVER_UNAVAILABLE;
+}
+
}
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
b/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
index 758c29b..15243a7 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
@@ -66,7 +66,7 @@ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.Sen
public AbstractListEmail(string name, MinimalFolder owner, Geary.Email.Field required_fields,
Folder.ListFlags flags, Cancellable? cancellable) {
- base(name);
+ base(name, OnError.IGNORE);
this.owner = owner;
this.required_fields = required_fields;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
b/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
index f054780..703a0c3 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
@@ -14,7 +14,7 @@ private class Geary.ImapEngine.CopyEmail : Geary.ImapEngine.SendReplayOperation
public CopyEmail(MinimalFolder engine, Gee.List<ImapDB.EmailIdentifier> to_copy,
Geary.FolderPath destination, Cancellable? cancellable = null) {
- base("CopyEmail");
+ base("CopyEmail", OnError.RETRY);
this.engine = engine;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
b/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
index 3750ff0..f171357 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
@@ -8,14 +8,14 @@ private class Geary.ImapEngine.CreateEmail : Geary.ImapEngine.SendReplayOperatio
public Geary.EmailIdentifier? created_id { get; private set; default = null; }
private MinimalFolder engine;
- private RFC822.Message rfc822;
+ private RFC822.Message? rfc822;
private Geary.EmailFlags? flags;
private DateTime? date_received;
private Cancellable? cancellable;
public CreateEmail(MinimalFolder engine, RFC822.Message rfc822, Geary.EmailFlags? flags,
DateTime? date_received, Cancellable? cancellable) {
- base.only_remote("CreateEmail");
+ base.only_remote("CreateEmail", OnError.RETRY);
this.engine = engine;
@@ -47,18 +47,29 @@ private class Geary.ImapEngine.CreateEmail : Geary.ImapEngine.SendReplayOperatio
if (cancellable.is_cancelled())
throw new IOError.CANCELLED("CreateEmail op cancelled immediately");
- // use IMAP APPEND command on remote folders, which doesn't require opening a folder
- created_id = yield engine.remote_folder.create_email_async(rfc822, flags, date_received);
+ // use IMAP APPEND command on remote folders, which doesn't require opening a folder ...
+ // if retrying after a successful create, rfc822 will be null
+ if (rfc822 != null)
+ created_id = yield engine.remote_folder.create_email_async(rfc822, flags, date_received);
+
+ // because this command retries, the create completed, remove the RFC822 message to prevent
+ // creating it twice
+ rfc822 = null;
// If the user cancelled the operation, we need to wipe the new message to keep this
// operation atomic.
if (cancellable.is_cancelled()) {
- yield engine.remote_folder.remove_email_async(
- new Imap.MessageSet.uid(((ImapDB.EmailIdentifier) created_id).uid).to_list(), null);
+ if (created_id != null) {
+ yield engine.remote_folder.remove_email_async(
+ new Imap.MessageSet.uid(((ImapDB.EmailIdentifier) created_id).uid).to_list(), null);
+ }
throw new IOError.CANCELLED("CreateEmail op cancelled after create");
}
+ if (created_id == null)
+ return ReplayOperation.Status.COMPLETED;
+
// TODO: need to prevent gaps that may occur here
Geary.Email created = new Geary.Email(created_id);
Gee.Map<Geary.Email, bool> results = yield engine.local_folder.create_or_merge_email_async(
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-empty-folder.vala
b/src/engine/imap-engine/replay-ops/imap-engine-empty-folder.vala
index 1cf774a..05aedb4 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-empty-folder.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-empty-folder.vala
@@ -16,7 +16,7 @@ private class Geary.ImapEngine.EmptyFolder : Geary.ImapEngine.SendReplayOperatio
private int original_count = 0;
public EmptyFolder(MinimalFolder engine, Cancellable? cancellable) {
- base("EmptyFolder");
+ base("EmptyFolder", OnError.RETRY);
this.engine = engine;
this.cancellable = cancellable;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala
b/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala
index a6090fd..133d8a5 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala
@@ -18,7 +18,8 @@ private class Geary.ImapEngine.FetchEmail : Geary.ImapEngine.SendReplayOperation
public FetchEmail(MinimalFolder engine, ImapDB.EmailIdentifier id, Email.Field required_fields,
Folder.ListFlags flags, Cancellable? cancellable) {
- base ("FetchEmail");
+ // Unlike the list operations, fetch needs to retry remote
+ base ("FetchEmail", OnError.RETRY);
this.engine = engine;
this.id = id;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
b/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
index 35b51c2..aa3c019 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
@@ -15,7 +15,7 @@ private class Geary.ImapEngine.MarkEmail : Geary.ImapEngine.SendReplayOperation
public MarkEmail(MinimalFolder engine, Gee.List<Geary.EmailIdentifier> to_mark,
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
Cancellable? cancellable = null) {
- base("MarkEmail");
+ base("MarkEmail", OnError.RETRY);
this.engine = engine;
@@ -65,10 +65,8 @@ private class Geary.ImapEngine.MarkEmail : Geary.ImapEngine.SendReplayOperation
Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(
ImapDB.EmailIdentifier.to_uids(original_flags.keys));
- foreach (Imap.MessageSet msg_set in msg_sets) {
- yield engine.remote_folder.mark_email_async(msg_set, flags_to_add, flags_to_remove,
- cancellable);
- }
+ yield engine.remote_folder.mark_email_async(msg_sets, flags_to_add, flags_to_remove,
+ cancellable);
return ReplayOperation.Status.COMPLETED;
}
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
b/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
index 6be265d..d0ae775 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
@@ -11,10 +11,11 @@ private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation
private Cancellable? cancellable;
private Gee.Set<ImapDB.EmailIdentifier>? moved_ids = null;
private int original_count = 0;
+ private Gee.List<Imap.MessageSet>? remaining_msg_sets = null;
public MoveEmail(MinimalFolder engine, Gee.List<ImapDB.EmailIdentifier> to_move,
Geary.FolderPath destination, Cancellable? cancellable = null) {
- base("MoveEmail");
+ base("MoveEmail", OnError.RETRY);
this.engine = engine;
@@ -62,16 +63,26 @@ private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation
if (moved_ids.size == 0)
return ReplayOperation.Status.COMPLETED;
- // don't use Cancellable throughout I/O operations in order to assure transaction completes
- // fully
- if (cancellable != null && cancellable.is_cancelled())
- throw new IOError.CANCELLED("Move email to %s cancelled", engine.remote_folder.to_string());
+ // Remaining MessageSets are persisted in case of network retries
+ if (remaining_msg_sets == null)
+ remaining_msg_sets = Imap.MessageSet.uid_sparse(ImapDB.EmailIdentifier.to_uids(moved_ids));
- Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(
- ImapDB.EmailIdentifier.to_uids(moved_ids));
- foreach (Imap.MessageSet msg_set in msg_sets) {
+ if (remaining_msg_sets == null || remaining_msg_sets.size == 0)
+ return ReplayOperation.Status.COMPLETED;
+
+ Gee.Iterator<Imap.MessageSet> iter = remaining_msg_sets.iterator();
+ while (iter.next()) {
+ // don't use Cancellable throughout I/O operations in order to assure transaction completes
+ // fully
+ if (cancellable != null && cancellable.is_cancelled())
+ throw new IOError.CANCELLED("Move email to %s cancelled", engine.remote_folder.to_string());
+
+ Imap.MessageSet msg_set = iter.get();
yield engine.remote_folder.copy_email_async(msg_set, destination, null);
yield engine.remote_folder.remove_email_async(msg_set.to_list(), null);
+
+ // completed successfully, remove from list in case of retry
+ iter.remove();
}
return ReplayOperation.Status.COMPLETED;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
b/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
index 95cbed8..979c6f9 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
@@ -13,7 +13,7 @@ private class Geary.ImapEngine.RemoveEmail : Geary.ImapEngine.SendReplayOperatio
public RemoveEmail(MinimalFolder engine, Gee.List<ImapDB.EmailIdentifier> to_remove,
Cancellable? cancellable = null) {
- base("RemoveEmail");
+ base("RemoveEmail", OnError.RETRY);
this.engine = engine;
@@ -57,6 +57,9 @@ private class Geary.ImapEngine.RemoveEmail : Geary.ImapEngine.SendReplayOperatio
}
public override async ReplayOperation.Status replay_remote_async() throws Error {
+ if (removed_ids.size == 0)
+ return ReplayOperation.Status.COMPLETED;
+
// Remove from server. Note that this causes the receive replay queue to kick into
// action, removing the e-mail but *NOT* firing a signal; the "remove marker" indicates
// that the signal has already been fired.
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala
b/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala
index 6d561be..9de7fad 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala
@@ -5,12 +5,14 @@
*/
private class Geary.ImapEngine.ReplayAppend : Geary.ImapEngine.ReplayOperation {
- public MinimalFolder owner;
- public int remote_count;
- public Gee.List<Imap.SequenceNumber> positions;
+ private MinimalFolder owner;
+ private int remote_count;
+ private Gee.List<Imap.SequenceNumber> positions;
public ReplayAppend(MinimalFolder owner, int remote_count, Gee.List<Imap.SequenceNumber> positions) {
- base ("Append", Scope.REMOTE_ONLY);
+ // IGNORE remote errors because the reconnect will re-normalize the folder, making this
+ // append moot
+ base ("Append", Scope.REMOTE_ONLY, OnError.IGNORE);
this.owner = owner;
this.remote_count = remote_count;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
b/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
index 2a20990..2acf58f 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
@@ -5,14 +5,19 @@
*/
private class Geary.ImapEngine.ReplayDisconnect : Geary.ImapEngine.ReplayOperation {
- public MinimalFolder owner;
- public Imap.ClientSession.DisconnectReason reason;
+ private MinimalFolder owner;
+ private Imap.ClientSession.DisconnectReason reason;
+ private bool flush_pending;
+ private Cancellable? cancellable;
- public ReplayDisconnect(MinimalFolder owner, Imap.ClientSession.DisconnectReason reason) {
+ public ReplayDisconnect(MinimalFolder owner, Imap.ClientSession.DisconnectReason reason,
+ bool flush_pending, Cancellable? cancellable) {
base ("Disconnect", Scope.LOCAL_ONLY);
this.owner = owner;
this.reason = reason;
+ this.flush_pending = flush_pending;
+ this.cancellable = cancellable;
}
public override void notify_remote_removed_position(Imap.SequenceNumber removed) {
@@ -37,7 +42,7 @@ private class Geary.ImapEngine.ReplayDisconnect : Geary.ImapEngine.ReplayOperati
// ReplayDisconnect is only used when remote disconnects, so never flush pending, the
// connection is down or going down
owner.close_internal_async.begin(Geary.Folder.CloseReason.LOCAL_CLOSE, remote_reason,
- false, null);
+ flush_pending, cancellable);
return false;
});
@@ -49,7 +54,7 @@ private class Geary.ImapEngine.ReplayDisconnect : Geary.ImapEngine.ReplayOperati
}
public override async ReplayOperation.Status replay_remote_async() throws Error {
- // shot not be called
+ // should not be called
return ReplayOperation.Status.COMPLETED;
}
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala
b/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala
index eeea5e2..4fc652b 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala
@@ -5,12 +5,13 @@
*/
private class Geary.ImapEngine.ReplayRemoval : Geary.ImapEngine.ReplayOperation {
- public MinimalFolder owner;
- public int remote_count;
- public Imap.SequenceNumber position;
+ private MinimalFolder owner;
+ private int remote_count;
+ private Imap.SequenceNumber position;
public ReplayRemoval(MinimalFolder owner, int remote_count, Imap.SequenceNumber position) {
- base ("Removal", Scope.LOCAL_AND_REMOTE);
+ // remote error will cause folder to reconnect and re-normalize, making this remove moot
+ base ("Removal", Scope.LOCAL_AND_REMOTE, OnError.IGNORE);
this.owner = owner;
this.remote_count = remote_count;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
b/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
index 5b1443d..b8d6f5d 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
@@ -19,6 +19,9 @@ private class Geary.ImapEngine.ServerSearchEmail : Geary.ImapEngine.AbstractList
base ("ServerSearchEmail", owner, required_fields, Geary.Folder.ListFlags.OLDEST_TO_NEWEST,
cancellable);
+ // unlike list, need to retry this as there's no local component to return
+ on_remote_error = OnError.RETRY;
+
this.criteria = criteria;
}
diff --git a/src/engine/imap/api/imap-folder.vala b/src/engine/imap/api/imap-folder.vala
index b4b6ab1..f6dbee3 100644
--- a/src/engine/imap/api/imap-folder.vala
+++ b/src/engine/imap/api/imap-folder.vala
@@ -641,7 +641,7 @@ private class Geary.Imap.Folder : BaseObject {
if (!msg_set.is_uid)
all_uid = false;
- cmds.add(new StoreCommand(msg_set, flags, true, false));
+ cmds.add(new StoreCommand(msg_set, flags, StoreCommand.Option.ADD_FLAGS));
}
// TODO: Only use old-school EXPUNGE when closing folder (or rely on CLOSE to do that work
@@ -661,7 +661,7 @@ private class Geary.Imap.Folder : BaseObject {
yield exec_commands_async(cmds, null, null, cancellable);
}
- public async void mark_email_async(MessageSet msg_set, Geary.EmailFlags? flags_to_add,
+ public async void mark_email_async(Gee.List<MessageSet> msg_sets, Geary.EmailFlags? flags_to_add,
Geary.EmailFlags? flags_to_remove, Cancellable? cancellable) throws Error {
check_open();
@@ -674,12 +674,13 @@ private class Geary.Imap.Folder : BaseObject {
return;
Gee.Collection<Command> cmds = new Gee.ArrayList<Command>();
-
- if (msg_flags_add.size > 0)
- cmds.add(new StoreCommand(msg_set, msg_flags_add, true, false));
-
- if (msg_flags_remove.size > 0)
- cmds.add(new StoreCommand(msg_set, msg_flags_remove, false, false));
+ foreach (MessageSet msg_set in msg_sets) {
+ if (msg_flags_add.size > 0)
+ cmds.add(new StoreCommand(msg_set, msg_flags_add, StoreCommand.Option.ADD_FLAGS));
+
+ if (msg_flags_remove.size > 0)
+ cmds.add(new StoreCommand(msg_set, msg_flags_remove, StoreCommand.Option.REMOVE_FLAGS));
+ }
yield exec_commands_async(cmds, null, null, cancellable);
}
diff --git a/src/engine/imap/command/imap-store-command.vala b/src/engine/imap/command/imap-store-command.vala
index f25365d..1bed913 100644
--- a/src/engine/imap/command/imap-store-command.vala
+++ b/src/engine/imap/command/imap-store-command.vala
@@ -15,10 +15,26 @@ public class Geary.Imap.StoreCommand : Command {
public const string NAME = "store";
public const string UID_NAME = "uid store";
- public StoreCommand(MessageSet message_set, Gee.List<MessageFlag> flag_list, bool add_flag,
- bool silent) {
+ /**
+ * Options indicating functionality of the { link StoreCommand}.
+ *
+ * Note that { link ADD_FLAGS} and { link REMOVE_FLAGS} are mutally exclusive. REMOVE_FLAGS
+ * actually does not set a bit, meaning that removing is the default operation and, if both
+ * add and remove are set, an add occurs.
+ */
+ [Flags]
+ public enum Option {
+ REMOVE_FLAGS = 0,
+ ADD_FLAGS,
+ SILENT
+ }
+
+ public StoreCommand(MessageSet message_set, Gee.List<MessageFlag> flag_list, Option options) {
base (message_set.is_uid ? UID_NAME : NAME);
+ bool add_flag = (options & Option.ADD_FLAGS) != 0;
+ bool silent = (options & Option.SILENT) != 0;
+
add(message_set.to_parameter());
add(new AtomParameter("%sflags%s".printf(add_flag ? "+" : "-", silent ? ".silent" : "")));
diff --git a/src/engine/nonblocking/nonblocking-abstract-semaphore.vala
b/src/engine/nonblocking/nonblocking-abstract-semaphore.vala
index f689003..2c3672a 100644
--- a/src/engine/nonblocking/nonblocking-abstract-semaphore.vala
+++ b/src/engine/nonblocking/nonblocking-abstract-semaphore.vala
@@ -46,9 +46,6 @@ public abstract class Geary.Nonblocking.AbstractSemaphore : BaseObject {
private bool passed = false;
private Gee.List<Pending> pending_queue = new Gee.LinkedList<Pending>();
- public virtual signal void at_reset() {
- }
-
protected AbstractSemaphore(bool broadcast, bool autoreset, Cancellable? cancellable = null) {
this.broadcast = broadcast;
this.autoreset = autoreset;
@@ -70,10 +67,6 @@ public abstract class Geary.Nonblocking.AbstractSemaphore : BaseObject {
cancellable.cancelled.disconnect(on_cancelled);
}
- protected virtual void notify_at_reset() {
- at_reset();
- }
-
private void trigger(bool all) {
if (pending_queue.size == 0)
return;
@@ -138,12 +131,7 @@ public abstract class Geary.Nonblocking.AbstractSemaphore : BaseObject {
}
public virtual void reset() {
- if (!passed)
- return;
-
passed = false;
-
- notify_at_reset();
}
public bool is_passed() {
diff --git a/src/engine/nonblocking/nonblocking-mailbox.vala b/src/engine/nonblocking/nonblocking-mailbox.vala
index 3337b44..93854da 100644
--- a/src/engine/nonblocking/nonblocking-mailbox.vala
+++ b/src/engine/nonblocking/nonblocking-mailbox.vala
@@ -28,8 +28,7 @@ public class Geary.Nonblocking.Mailbox<G> : BaseObject {
private Nonblocking.Spinlock spinlock = new Nonblocking.Spinlock();
public Mailbox(owned CompareDataFunc<G>? comparator = null) {
- // can't use ternary here, Vala bug
- if (comparator == null)
+ if (comparator == null && !typeof(G).is_a(typeof(Gee.Comparable)))
queue = new Gee.LinkedList<G>();
else
queue = new Gee.PriorityQueue<G>((owned) comparator);
diff --git a/src/engine/nonblocking/nonblocking-reporting-semaphore.vala
b/src/engine/nonblocking/nonblocking-reporting-semaphore.vala
index fd8cda5..0ffcb7f 100644
--- a/src/engine/nonblocking/nonblocking-reporting-semaphore.vala
+++ b/src/engine/nonblocking/nonblocking-reporting-semaphore.vala
@@ -17,11 +17,11 @@ public class Geary.Nonblocking.ReportingSemaphore<G> : Geary.Nonblocking.Semapho
result = default_result;
}
- protected override void notify_at_reset() {
+ public override void reset() {
result = default_result;
err = null;
- base.notify_at_reset();
+ base.reset();
}
public void notify_result(G result, Error? err) throws Error {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]