[geary/wip/remote-retry] Will properly reconnect and retry mark_email_async() calls
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/remote-retry] Will properly reconnect and retry mark_email_async() calls
- Date: Fri, 16 Jan 2015 00:50:18 +0000 (UTC)
commit 56279ed3471c3b705dea382877f32150b58136e1
Author: Jim Nelson <jim yorba org>
Date: Thu Jan 15 16:49:45 2015 -0800
Will properly reconnect and retry mark_email_async() calls
src/CMakeLists.txt | 1 +
src/engine/api/geary-folder-supports-mark.vala | 12 ++-
src/engine/api/geary-future.vala | 107 ++++++++++++++++++++
.../imap-engine/imap-engine-minimal-folder.vala | 88 +++++++---------
.../imap-engine/imap-engine-replay-operation.vala | 44 +++++++-
.../imap-engine/imap-engine-replay-queue.vala | 36 ++++++-
.../imap-engine-send-replay-operation.vala | 8 +-
src/engine/imap-engine/imap-engine.vala | 15 +++
.../replay-ops/imap-engine-mark-email.vala | 8 +-
src/engine/imap/api/imap-folder.vala | 17 ++--
src/engine/imap/command/imap-store-command.vala | 20 ++++-
11 files changed, 274 insertions(+), 82 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0d04959..30d00cc 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -33,6 +33,7 @@ engine/api/geary-folder-supports-empty.vala
engine/api/geary-folder-supports-mark.vala
engine/api/geary-folder-supports-move.vala
engine/api/geary-folder-supports-remove.vala
+engine/api/geary-future.vala
engine/api/geary-logging.vala
engine/api/geary-named-flag.vala
engine/api/geary-named-flags.vala
diff --git a/src/engine/api/geary-folder-supports-mark.vala b/src/engine/api/geary-folder-supports-mark.vala
index 928aa10..4556fa7 100644
--- a/src/engine/api/geary-folder-supports-mark.vala
+++ b/src/engine/api/geary-folder-supports-mark.vala
@@ -8,14 +8,18 @@
* 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.
*
+ * If a { link Future} is returned, the operation has been scheduled for completion in the
+ * background. The Future can be monitored or waited upon for final completion.
+ *
* 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,
+ public abstract async Geary.Future? mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
+ Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
Cancellable? cancellable = null) throws Error;
/**
@@ -23,13 +27,13 @@ public interface Geary.FolderSupport.Mark : Geary.Folder {
*
* The { link Geary.Folder} must be opened prior to attempting this operation.
*/
- public virtual async void mark_single_email_async(Geary.EmailIdentifier to_mark,
+ public virtual async Geary.Future? 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);
+ return yield mark_email_async(list, flags_to_add, flags_to_remove, cancellable);
}
}
diff --git a/src/engine/api/geary-future.vala b/src/engine/api/geary-future.vala
new file mode 100644
index 0000000..9d76cbf
--- /dev/null
+++ b/src/engine/api/geary-future.vala
@@ -0,0 +1,107 @@
+/* Copyright 2015 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+/**
+ * A generic mechanism for a method to return a result value after completing.
+ *
+ * Although designed with Geary's asynchronous interface in mind, this can be expanded for
+ * synchronous (nonblocking) methods as well.
+ */
+
+public class Geary.Future : BaseObject {
+ /**
+ * The name of the { link Future}.
+ *
+ * This is non-unique and should not be displayed to the user. It's merely used for logging
+ * purposes.
+ */
+ public string name { get; private set; }
+
+ /**
+ * Returns true if the { link Future} has been { link fulfilled}.
+ *
+ * @see fulfill
+ */
+ public bool is_fulfilled { get { return semaphore.is_passed(); } }
+
+ private Nonblocking.Semaphore semaphore = new Nonblocking.Semaphore();
+ private Value? result = null;
+ private Error? err = null;
+
+ /**
+ * Fired when the { link Future} is fulfilled, that is, the awaited result (or Error) has been
+ * determined.
+ *
+ * The promise (the method that returns this Future) should state in its contract the data type
+ * held by the Value, if any, as the result.
+ *
+ * If err is non-null, then result ''must'' be null.
+ *
+ * @see fulfill
+ */
+ public signal void fulfilled(Value? result, Error? err);
+
+ /**
+ * Create a { link Future}.
+ */
+ public Future(string name) {
+ this.name = name;
+ }
+
+ /**
+ * Fulfill the { link Future} with the results of the operation.
+ *
+ * This can only be called once. Additional calls will be ignored.
+ *
+ * If err is non-null, then result ''must'' be null. However, fulfill() does not enforce this,
+ * but will log a message.
+ *
+ *
+ */
+ public void fulfill(Value? result, Error? err) {
+ if (is_fulfilled)
+ return;
+
+ if (err != null && result != null)
+ message("%s fulfilled with non-null Error and Value result", to_string());
+
+ this.result = result;
+ this.err = err;
+
+ try {
+ semaphore.notify();
+ } catch (Error err) {
+ message("Unable to fulfill %s: %s", to_string(), err.message);
+ }
+
+ fulfilled(result, err);
+ }
+
+ /**
+ * Wait for the { link Future} to be { link fulfilled}.
+ *
+ * If the Future was fulfilled with an Error, it is thrown. Otherwise, the result Value is
+ * returned.
+ *
+ * This method may be called any number of times. If already fulfilled, it will complete
+ * instantly.
+ *
+ * @see fulfill
+ */
+ public async Value? wait_for_fulfillment_async(Cancellable? cancellable = null) throws Error {
+ yield semaphore.wait_async(cancellable);
+
+ if (err != null)
+ throw err;
+
+ return result;
+ }
+
+ public string to_string() {
+ return "Future:%s".printf(name);
+ }
+}
+
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index 11c262e..a86a327 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -638,10 +638,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;
@@ -696,44 +693,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 {
@@ -770,21 +746,25 @@ 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 {
+ 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());
+ }
+ } 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);
}
- 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();
@@ -849,11 +829,16 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
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;
@@ -1282,14 +1267,15 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
check_id(method, id);
}
- public virtual async void mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
- Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
+ public virtual async Geary.Future? mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
+ 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);
+
+ return yield mark.wait_for_ready_async(cancellable);
}
public virtual async void copy_email_async(Gee.List<Geary.EmailIdentifier> to_copy,
diff --git a/src/engine/imap-engine/imap-engine-replay-operation.vala
b/src/engine/imap-engine/imap-engine-replay-operation.vala
index 5662521..e5cf26b 100644
--- a/src/engine/imap-engine/imap-engine-replay-operation.vala
+++ b/src/engine/imap-engine/imap-engine-replay-operation.vala
@@ -29,20 +29,30 @@ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
CONTINUE
}
+ [Flags]
+ public enum Retry {
+ NONE = 0,
+ REMOTE
+ }
+
private static int next_opnum = 0;
public string name { get; set; }
public int opnum { get; private set; }
public Scope scope { get; private set; }
+ public Retry retry { get; protected set; }
+ public int 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();
+ private Geary.Future? future = null;
- public ReplayOperation(string name, Scope scope) {
+ public ReplayOperation(string name, Scope scope, Retry retry = Retry.NONE) {
this.name = name;
opnum = next_opnum++;
this.scope = scope;
+ this.retry = retry;
}
/**
@@ -124,18 +134,29 @@ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
/**
* Completes when the operation has completed execution. If the operation threw an error
* during execution, it will be thrown here.
+ *
+ * Returns a Semaphore if the operation is retrying. The Semaphore will be triggered when the
+ * retry completes or errors out.
*/
- public async void wait_for_ready_async(Cancellable? cancellable = null) throws Error {
+ public async Geary.Future? wait_for_ready_async(Cancellable? cancellable = null) throws Error {
yield semaphore.wait_async(cancellable);
if (err != null)
throw err;
+
+ return future;
}
+ // Can only be called once, although must be called after notify_retrying() is called.
internal void notify_ready(Error? err) {
- assert(!notified);
+ if (semaphore.is_passed() && future != null) {
+ future.fulfill(null, err);
+
+ return;
+ }
- notified = true;
+ // otherwise, if notified but not due to retry, trouble
+ assert(!semaphore.is_passed());
this.err = err;
@@ -146,6 +167,19 @@ private abstract class Geary.ImapEngine.ReplayOperation : Geary.BaseObject {
}
}
+ // Can only be called once, although notify_ready() may be called after calling this.
+ internal void notify_retrying() {
+ assert(!semaphore.is_passed());
+
+ future = new Future(name);
+
+ try {
+ semaphore.notify();
+ } catch (Error notify_err) {
+ debug("Unable to notify replay operation as retrying: [%s] %s", name, notify_err.message);
+ }
+ }
+
public abstract string describe_state();
public string to_string() {
diff --git a/src/engine/imap-engine/imap-engine-replay-queue.vala
b/src/engine/imap-engine/imap-engine-replay-queue.vala
index e376774..f3c4ff4 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, ReplayOperation.Retry.NONE);
}
public override void notify_remote_removed_position(Imap.SequenceNumber removed) {
@@ -50,8 +50,10 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
} }
private weak MinimalFolder owner;
- private Nonblocking.Mailbox<ReplayOperation> local_queue = new Nonblocking.Mailbox<ReplayOperation>();
- private Nonblocking.Mailbox<ReplayOperation> remote_queue = new Nonblocking.Mailbox<ReplayOperation>();
+ private Nonblocking.Mailbox<ReplayOperation> local_queue = new Nonblocking.Mailbox<ReplayOperation>(
+ replay_operation_comparator);
+ private Nonblocking.Mailbox<ReplayOperation> remote_queue = new Nonblocking.Mailbox<ReplayOperation>(
+ replay_operation_comparator);
private ReplayOperation? local_op_active = null;
private ReplayOperation? remote_op_active = null;
private Gee.ArrayList<ReplayOperation> notification_queue = new Gee.ArrayList<ReplayOperation>();
@@ -344,6 +346,10 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
}
}
+ private static int replay_operation_comparator(ReplayOperation a, ReplayOperation b) {
+ return a.retry_count - b.retry_count;
+ }
+
private async void do_replay_local_async() {
bool queue_running = true;
while (queue_running) {
@@ -415,6 +421,9 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
}
}
+ // completed one way or another, clear retry count for next round
+ op.retry_count = 0;
+
if (remote_enqueue) {
if (!remote_queue.send(op)) {
debug("Unable to enqueue operation %s for %s remote operation", op.to_string(),
@@ -483,12 +492,33 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
Error? remote_err = null;
if (folder_opened || is_close_op) {
+ if (op.retry_count > 0) {
+ debug("RETRYING OP %s on %s: retry_count=%d", op.to_string(), to_string(),
+ op.retry_count);
+ }
+
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);
+ // If a hard failure and operation allows remote replay, schedule now
+ if (is_hard_failure(replay_err) && (op.retry & ReplayOperation.Retry.REMOTE) != 0) {
+ debug("SCHEDULING RETRY OP %s on %s: retry_count=%d", op.to_string(),
+ to_string(), op.retry_count);
+
+ // if this is the first retry, complete the waiting async caller but allow
+ // them to wait later or be signaled when completed
+ if (op.retry_count == 0)
+ op.notify_retrying();
+
+ op.retry_count++;
+ remote_queue.send(op);
+
+ continue;
+ }
+
remote_err = replay_err;
}
} else if (!is_close_op) {
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..dcfc041 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.Retry retry = ReplayOperation.Retry.NONE) {
+ base (name, ReplayOperation.Scope.LOCAL_AND_REMOTE, retry);
}
- public SendReplayOperation.only_remote(string name) {
- base (name, ReplayOperation.Scope.REMOTE_ONLY);
+ public SendReplayOperation.only_remote(string name, ReplayOperation.Retry retry =
ReplayOperation.Retry.NONE) {
+ base (name, ReplayOperation.Scope.REMOTE_ONLY, retry);
}
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..9380dab 100644
--- a/src/engine/imap-engine/imap-engine.vala
+++ b/src/engine/imap-engine/imap-engine.vala
@@ -58,5 +58,20 @@ 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) {
+ // 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-mark-email.vala
b/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
index 35b51c2..f7c6069 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", Retry.REMOTE);
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/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" : "")));
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]