[geary/wip/721828-undo-2: 1/5] Going an alternate route
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/721828-undo-2: 1/5] Going an alternate route
- Date: Mon, 2 Feb 2015 20:26:40 +0000 (UTC)
commit 955eef1cc8c92414b4787938dc8e705edfe30a4c
Author: Jim Nelson <jim yorba org>
Date: Thu Jan 29 18:57:21 2015 -0800
Going an alternate route
Revokable operations are postponed until an event occurs which
forces them to be committed, or the user revokes them.
src/CMakeLists.txt | 4 +-
src/client/application/geary-controller.vala | 12 +-
src/engine/api/geary-revokable.vala | 90 +++++++++++++---
.../imap-engine/imap-engine-minimal-folder.vala | 46 +++++++-
.../imap-engine/imap-engine-replay-queue.vala | 21 +++--
.../imap-engine/imap-engine-revokable-move.vala | 107 +++++++++-----------
.../imap-engine-send-replay-operation.vala | 8 +-
...ail.vala => imap-engine-move-email-commit.vala} | 70 ++++---------
.../replay-ops/imap-engine-move-email-prepare.vala | 65 ++++++++++++
.../replay-ops/imap-engine-move-email-revoke.vala | 55 ++++++++++
10 files changed, 329 insertions(+), 149 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 652be39..6a991b3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -213,7 +213,9 @@ engine/imap-engine/replay-ops/imap-engine-fetch-email.vala
engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala
engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala
engine/imap-engine/replay-ops/imap-engine-mark-email.vala
-engine/imap-engine/replay-ops/imap-engine-move-email.vala
+engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala
+engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala
+engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala
engine/imap-engine/replay-ops/imap-engine-remove-email.vala
engine/imap-engine/replay-ops/imap-engine-replay-append.vala
engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 12e84be..76f5fcf 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -2476,28 +2476,28 @@ public class GearyController : Geary.BaseObject {
private void save_revokable(Geary.Revokable? new_revokable, string? description) {
// disconnect old revokable
if (revokable != null)
- revokable.notify[Geary.Revokable.PROP_CAN_REVOKE].disconnect(on_can_revoke_changed);
+ revokable.notify[Geary.Revokable.PROP_VALID].disconnect(on_revokable_valid_changed);
// store new revokable
revokable = new_revokable;
// connect to new revokable
if (revokable != null)
- revokable.notify[Geary.Revokable.PROP_CAN_REVOKE].connect(on_can_revoke_changed);
+ revokable.notify[Geary.Revokable.PROP_VALID].connect(on_revokable_valid_changed);
Gtk.Action undo_action = GearyApplication.instance.get_action(ACTION_UNDO);
- undo_action.sensitive = revokable != null && revokable.can_revoke;
+ undo_action.sensitive = revokable != null && revokable.valid;
undo_action.tooltip = (revokable != null && description != null) ? description : _("Undo (Ctrl+Z)");
}
- private void on_can_revoke_changed() {
+ private void on_revokable_valid_changed() {
// remove revokable if it goes invalid
- if (revokable != null && !revokable.can_revoke)
+ if (revokable != null && !revokable.valid)
save_revokable(null, null);
}
private void on_revoke() {
- if (revokable != null && revokable.can_revoke)
+ if (revokable != null && revokable.valid)
revokable.revoke_async.begin(null, on_revoke_completed);
}
diff --git a/src/engine/api/geary-revokable.vala b/src/engine/api/geary-revokable.vala
index 51b6f95..dbf938d 100644
--- a/src/engine/api/geary-revokable.vala
+++ b/src/engine/api/geary-revokable.vala
@@ -10,26 +10,27 @@
*/
public abstract class Geary.Revokable : BaseObject {
- public const string PROP_CAN_REVOKE = "can-revoke";
- public const string PROP_IS_REVOKING = "is-revoking";
+ public const string PROP_VALID = "valid";
+ public const string PROP_IN_PROCESS = "in-process";
/**
- * Indicates if { link revoke_async} is a valid operation for this { link Revokable}.
+ * Indicates if { link revoke_async} or { link commit_async} are valid operations for this
+ * { link Revokable}.
*
- * Due to later operations or notifications, it's possible for the Revokable to go invalid.
- * In some circumstances, this may be that it cannot fully revoke the original operation, in
- * others it may be that it can't revoke any part of the original operation, depending on the
- * nature of the operation.
+ * Due to later operations or notifications, it's possible for the Revokable to go invalid
+ * after being issued to the caller.
*/
- public bool can_revoke { get; protected set; default = true; }
+ public bool valid { get; protected set; default = true; }
/**
- * Indicates a { link revoke_async} operation is underway.
+ * Indicates a { link revoke_async} or { link commit_async} operation is underway.
*
- * Only one revoke operation can occur at a time. If this is true when revoke_async() is
- * called, it will throw an Error.
+ * Only one operation can occur at a time, and when complete the { link Revokable} will be
+ * invalid.
+ *
+ * @see valid
*/
- public bool is_revoking { get; protected set; default = false; }
+ public bool in_process { get; protected set; default = false; }
protected Revokable() {
}
@@ -37,13 +38,68 @@ public abstract class Geary.Revokable : BaseObject {
/**
* Revoke (undo) the operation.
*
- * Returns false if the operation failed and is no longer revokable.
- *
* If the call throws an Error that does not necessarily mean the { link Revokable} is
- * invalid. Check the return value or { link can_revoke}.
+ * invalid. Check { link valid}.
+ *
+ * @throws EngineError.ALREADY_OPEN if { link in_process} is true.
+ */
+ public virtual async void revoke_async(Cancellable? cancellable = null) throws Error {
+ if (in_process)
+ throw new EngineError.ALREADY_OPEN("Already revoking or committing operation");
+
+ in_process = true;
+ try {
+ yield internal_revoke_async(cancellable);
+ } finally {
+ in_process = false;
+ }
+ }
+
+ /**
+ * The child class's implementation of { link revoke_async}.
+ *
+ * The default implementation of { link revoke_async} deals with state issues
+ * ({ link in_process}, throwing the appropriate Error, etc.) Child classes can override this
+ * method and only worry about the revoke operation itself.
+ *
+ * This call *must* set { link valid} before exiting.
+ */
+ protected abstract async void internal_revoke_async(Cancellable? cancellable) throws Error;
+
+ /**
+ * Commits (completes) the operation immediately.
+ *
+ * Some { link Revokable} operations work by delaying the operation until time has passed or
+ * some situation occurs which requires the operation to complete. This call forces the
+ * operation to complete immediately rather than delay it for later.
+ *
+ * Even if the operation "actually" commits and is not delayed, calling commit_async() will
+ * make this Revokable invalid.
+ *
+ * @throws EngineError.ALREADY_OPEN if { link is_revoking} or { link is_committing} is true
+ * when called.
+ */
+ public virtual async void commit_async(Cancellable? cancellable = null) throws Error {
+ if (in_process)
+ throw new EngineError.ALREADY_OPEN("Already revoking or committing operation");
+
+ in_process = true;
+ try {
+ yield internal_commit_async(cancellable);
+ } finally {
+ in_process = false;
+ }
+ }
+
+ /**
+ * The child class's implementation of { link commit_async}.
+ *
+ * The default implementation of { link commit_async} deals with state issues
+ * ({ link in_process}, throwing the appropriate Error, etc.) Child classes can override this
+ * method and only worry about the revoke operation itself.
*
- * @throws EngineError.ALREADY_OPEN if { link is_revoking} is true when called.
+ * This call *must* set { link valid} before exiting.
*/
- public abstract async bool revoke_async(Cancellable? cancellable = null) throws Error;
+ protected abstract async void internal_commit_async(Cancellable? cancellable) throws Error;
}
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index e073cce..21d9c59 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -52,6 +52,15 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
private Nonblocking.Mutex open_mutex = new Nonblocking.Mutex();
private Nonblocking.Mutex close_mutex = new Nonblocking.Mutex();
+ /**
+ * Called when the folder is closing (and not reestablishing a connection) and will be flushing
+ * the replay queue. Subscribers may add ReplayOperations to the list, which will be enqueued
+ * before the queue is flushed.
+ *
+ * Note that this is ''not'' fired if the queue is not being flushed.
+ */
+ public signal void closing(Gee.List<ReplayOperation> final_ops);
+
public MinimalFolder(GenericAccount account, Imap.Account remote, ImapDB.Account local,
ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
_account = account;
@@ -79,6 +88,10 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
local_folder.email_complete.disconnect(on_email_complete);
}
+ protected virtual void notify_closing(Gee.List<ReplayOperation> final_ops) {
+ closing(final_ops);
+ }
+
/*
* These signal notifiers are marked public (note this is a private class) so the various
* ReplayOperations can directly fire the associated signals while within the queue.
@@ -814,6 +827,16 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
// That said, only flush, close, and destroy the ReplayQueue if fully closing and not
// preparing for a connection reestablishment
if (open_count <= 0) {
+ // if closing and flushing the queue, give Revokables a chance to schedule their
+ // commit operations before going down
+ if (flush_pending) {
+ Gee.List<ReplayOperation> final_ops = new Gee.ArrayList<ReplayOperation>();
+ notify_closing(final_ops);
+
+ foreach (ReplayOperation op in final_ops)
+ replay_queue.schedule(op);
+ }
+
// 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 {
@@ -1381,16 +1404,27 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
if (destination.equal_to(path))
return null;
- MoveEmail move = new MoveEmail(this, (Gee.List<ImapDB.EmailIdentifier>) to_move, destination);
- replay_queue.schedule(move);
+ MoveEmailPrepare prepare = new MoveEmailPrepare(this, (Gee.List<ImapDB.EmailIdentifier>) to_move,
+ cancellable);
+ replay_queue.schedule(prepare);
- yield move.wait_for_ready_async(cancellable);
+ yield prepare.wait_for_ready_async(cancellable);
- // If no COPYUIDs returned, can't revoke this operation
- if (move.destination_uids.size == 0)
+ if (prepare.prepared_for_move == null || prepare.prepared_for_move.size == 0)
return null;
- return new RevokableMove(_account, path, destination, move.destination_uids);
+ return new RevokableMove(_account, this, destination, prepare.prepared_for_move);
+ }
+
+ public void schedule_op(ReplayOperation op) throws Error {
+ check_open("schedule_op");
+
+ replay_queue.schedule(op);
+ }
+
+ public async void exec_op_async(ReplayOperation op, Cancellable? cancellable) throws Error {
+ schedule_op(op);
+ yield op.wait_for_ready_async(cancellable);
}
private void on_email_flags_changed(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> changed) {
diff --git a/src/engine/imap-engine/imap-engine-replay-queue.vala
b/src/engine/imap-engine/imap-engine-replay-queue.vala
index af43bad..a2e3b2b 100644
--- a/src/engine/imap-engine/imap-engine-replay-queue.vala
+++ b/src/engine/imap-engine/imap-engine-replay-queue.vala
@@ -9,6 +9,12 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
// see as high as 250ms
private const int NOTIFICATION_QUEUE_WAIT_MSEC = 1000;
+ private enum State {
+ OPEN,
+ CLOSING,
+ CLOSED
+ }
+
private class CloseReplayQueue : ReplayOperation {
public CloseReplayQueue() {
// LOCAL_AND_REMOTE to make sure this operation is flushed all the way down the pipe
@@ -57,8 +63,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
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;
+ private State state = State.OPEN;
public virtual signal void scheduled(ReplayOperation op) {
Logging.debug(Logging.Flag.REPLAY, "[%s] ReplayQueue::scheduled: %s %s", to_string(),
@@ -143,7 +148,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
*/
public bool schedule(ReplayOperation op) {
// ReplayClose is allowed past the velvet ropes even as the hoi palloi is turned away
- if (is_closed && !(op is CloseReplayQueue)) {
+ if (state != State.OPEN && !(op is CloseReplayQueue)) {
debug("Unable to schedule replay operation %s on %s: replay queue closed", op.to_string(),
to_string());
@@ -188,7 +193,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
* Returns false if the operation was not schedule (queue already closed).
*/
public bool schedule_server_notification(ReplayOperation op) {
- if (is_closed) {
+ if (state != State.OPEN) {
debug("Unable to schedule notification operation %s on %s: replay queue closed", op.to_string(),
to_string());
@@ -293,7 +298,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
* A ReplayQueue cannot be re-opened.
*/
public async void close_async(bool flush_pending, Cancellable? cancellable = null) throws Error {
- if (is_closed)
+ if (state != State.OPEN)
return;
// cancel notification queue timeout
@@ -306,8 +311,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
// mark as closed now to prevent further scheduling ... ReplayClose gets special
// consideration in schedule()
- is_closed = true;
-
+ state = State.CLOSING;
closing();
// if not flushing pending, clear out all waiting operations, backing out any that need to
@@ -322,6 +326,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
yield close_op.wait_for_ready_async(cancellable);
+ state = State.CLOSED;
closed();
}
@@ -472,7 +477,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
// wait until the remote folder is opened (or throws an exception, in which case closed)
try {
- if (!is_close_op && folder_opened)
+ if (!is_close_op && folder_opened && state == State.OPEN)
yield owner.wait_for_open_async();
} catch (Error remote_err) {
debug("Folder %s closed or failed to open, remote replay queue closing: %s",
diff --git a/src/engine/imap-engine/imap-engine-revokable-move.vala
b/src/engine/imap-engine/imap-engine-revokable-move.vala
index f288faa..3c5ce8d 100644
--- a/src/engine/imap-engine/imap-engine-revokable-move.vala
+++ b/src/engine/imap-engine/imap-engine-revokable-move.vala
@@ -6,79 +6,65 @@
private class Geary.ImapEngine.RevokableMove : Revokable {
private GenericAccount account;
- private FolderPath original_source;
- private FolderPath original_dest;
- private Gee.Set<Imap.UID> destination_uids;
+ private ImapEngine.MinimalFolder source;
+ private FolderPath destination;
+ private Gee.Set<ImapDB.EmailIdentifier> move_ids;
- public RevokableMove(GenericAccount account, FolderPath original_source, FolderPath original_dest,
- Gee.Set<Imap.UID> destination_uids) {
+ public RevokableMove(GenericAccount account, ImapEngine.MinimalFolder source, FolderPath destination,
+ Gee.Set<ImapDB.EmailIdentifier> move_ids) {
this.account = account;
- this.original_source = original_source;
- this.original_dest = original_dest;
- this.destination_uids = destination_uids;
+ this.source = source;
+ this.destination = destination;
+ this.move_ids = move_ids;
account.folders_available_unavailable.connect(on_folders_available_unavailable);
- account.email_removed.connect(on_folder_email_removed);
+ source.email_removed.connect(on_source_email_removed);
+ source.closing.connect(on_source_closing);
}
~RevokableMove() {
account.folders_available_unavailable.disconnect(on_folders_available_unavailable);
- account.email_removed.disconnect(on_folder_email_removed);
+ source.email_removed.disconnect(on_source_email_removed);
+ source.closing.disconnect(on_source_closing);
+
+ // if still valid, schedule operation so its executed
+ if (valid) {
+ debug("Freeing revokable, scheduling move %d emails from %s to %s", move_ids.size,
+ source.path.to_string(), destination.to_string());
+
+ try {
+ source.schedule_op(new MoveEmailCommit(source, move_ids, destination, null));
+ } catch (Error err) {
+ debug("Move from %s to %s failed: %s", source.path.to_string(), destination.to_string(),
+ err.message);
+ }
+ }
}
- public override async bool revoke_async(Cancellable? cancellable) throws Error {
- if (is_revoking)
- throw new EngineError.ALREADY_OPEN("Already revoking operation");
-
- is_revoking = true;
+ protected override async void internal_revoke_async(Cancellable? cancellable) throws Error {
try {
- return yield internal_revoke_async(cancellable);
+ yield source.exec_op_async(new MoveEmailRevoke(source, move_ids, cancellable),
+ cancellable);
} finally {
- is_revoking = false;
+ valid = false;
}
}
- private async bool internal_revoke_async(Cancellable? cancellable) throws Error {
- // at this point, it's a one-shot deal: any error from here on out, or success, revoke
- // is spent
- can_revoke = false;
-
- // Use a detached Folder object, which bypasses synchronization on the destination folder
- // for "quick" operations
- Imap.Folder dest_folder = yield account.fetch_detached_folder_async(original_dest, cancellable);
- yield dest_folder.open_async(cancellable);
-
- // open, revoke, close, ensuring the close and signal disconnect are performed in all cases
+ protected override async void internal_commit_async(Cancellable? cancellable) throws Error {
try {
- // watch out for messages detected as gone when folder is opened
- if (destination_uids.size > 0) {
- Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(destination_uids);
-
- // copy the moved email back to its source
- foreach (Imap.MessageSet msg_set in msg_sets)
- yield dest_folder.copy_email_async(msg_set, original_source, cancellable);
-
- // remove it from the original destination in one fell swoop
- yield dest_folder.remove_email_async(msg_sets, cancellable);
- }
+ yield source.exec_op_async(new MoveEmailCommit(source, move_ids, destination, cancellable),
+ cancellable);
} finally {
- // note that the Cancellable is not used
- try {
- yield dest_folder.close_async(null);
- } catch (Error err) {
- // ignored
- }
+ valid = false;
}
-
- return can_revoke;
}
private void on_folders_available_unavailable(Gee.List<Folder>? available, Gee.List<Folder>?
unavailable) {
- // look for either of the original folders going away
+ // look for either of the folders going away
if (unavailable != null) {
foreach (Folder folder in unavailable) {
- if (folder.path.equal_to(original_source) || folder.path.equal_to(original_dest)) {
- can_revoke = false;
+ if (folder.path.equal_to(source.path) || folder.path.equal_to(destination)) {
+ valid = false;
break;
}
@@ -86,21 +72,20 @@ private class Geary.ImapEngine.RevokableMove : Revokable {
}
}
- private void on_folder_email_removed(Folder folder, Gee.Collection<EmailIdentifier> ids) {
+ private void on_source_email_removed(Gee.Collection<EmailIdentifier> ids) {
// one-way switch, and only interested in destination folder activity
- if (!can_revoke || !folder.path.equal_to(original_dest))
+ if (!valid)
return;
- // convert generic identifiers to UIDs
- Gee.HashSet<Imap.UID> removed_uids = traverse<EmailIdentifier>(ids)
- .cast_object<ImapDB.EmailIdentifier>()
- .filter(id => id.uid == null)
- .map<Imap.UID>(id => id.uid)
- .to_hash_set();
+ foreach (EmailIdentifier id in ids)
+ move_ids.remove((ImapDB.EmailIdentifier) id);
- // otherwise, ability to revoke is best-effort
- destination_uids.remove_all(removed_uids);
- can_revoke = destination_uids.size > 0;
+ valid = move_ids.size > 0;
+ }
+
+ private void on_source_closing(Gee.List<ReplayOperation> final_ops) {
+ if (valid)
+ final_ops.add(new MoveEmailCommit(source, move_ids, destination, null));
}
}
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 5ae0dce..50449ce 100644
--- a/src/engine/imap-engine/imap-engine-send-replay-operation.vala
+++ b/src/engine/imap-engine/imap-engine-send-replay-operation.vala
@@ -5,11 +5,15 @@
*/
private abstract class Geary.ImapEngine.SendReplayOperation : Geary.ImapEngine.ReplayOperation {
- public SendReplayOperation(string name, ReplayOperation.OnError on_remote_error = OnError.THROW) {
+ protected 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, ReplayOperation.OnError on_remote_error =
OnError.THROW) {
+ protected SendReplayOperation.only_local(string name, ReplayOperation.OnError on_remote_error =
OnError.THROW) {
+ base (name, ReplayOperation.Scope.LOCAL_ONLY, on_remote_error);
+ }
+
+ protected SendReplayOperation.only_remote(string name, ReplayOperation.OnError on_remote_error =
OnError.THROW) {
base (name, ReplayOperation.Scope.REMOTE_ONLY, on_remote_error);
}
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-commit.vala
similarity index 54%
rename from src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
rename to src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala
index e4b19db..2d46ae0 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala
@@ -4,70 +4,43 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation {
- public Gee.Set<Imap.UID> destination_uids = new Gee.HashSet<Imap.UID>();
-
+private class Geary.ImapEngine.MoveEmailCommit : Geary.ImapEngine.SendReplayOperation {
private MinimalFolder engine;
private Gee.List<ImapDB.EmailIdentifier> to_move = new Gee.ArrayList<ImapDB.EmailIdentifier>();
private Geary.FolderPath destination;
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", OnError.RETRY);
-
+
+ public MoveEmailCommit(MinimalFolder engine, Gee.Collection<ImapDB.EmailIdentifier> to_move,
+ Geary.FolderPath destination, Cancellable? cancellable) {
+ base.only_remote("MoveEmailCommit", OnError.RETRY);
+
this.engine = engine;
-
+
this.to_move.add_all(to_move);
this.destination = destination;
this.cancellable = cancellable;
}
public override void notify_remote_removed_ids(Gee.Collection<ImapDB.EmailIdentifier> ids) {
- // don't bother updating on server or backing out locally
- if (moved_ids != null)
- moved_ids.remove_all(ids);
+ to_move.remove_all(ids);
}
public override async ReplayOperation.Status replay_local_async() throws Error {
- if (to_move.size <= 0)
- return ReplayOperation.Status.COMPLETED;
-
- int remote_count;
- int last_seen_remote_count;
- original_count = engine.get_remote_counts(out remote_count, out last_seen_remote_count);
-
- // as this value is only used for reporting, offer best-possible service
- if (original_count < 0)
- original_count = to_move.size;
-
- moved_ids = yield engine.local_folder.mark_removed_async(to_move, true, cancellable);
- if (moved_ids == null || moved_ids.size == 0)
- return ReplayOperation.Status.COMPLETED;
-
- engine.replay_notify_email_removed(moved_ids);
-
- engine.replay_notify_email_count_changed(Numeric.int_floor(original_count - to_move.size, 0),
- Geary.Folder.CountChangeReason.REMOVED);
-
return ReplayOperation.Status.CONTINUE;
}
public override void get_ids_to_be_remote_removed(Gee.Collection<ImapDB.EmailIdentifier> ids) {
- if (moved_ids != null)
- ids.add_all(moved_ids);
+ ids.add_all(to_move);
}
public override async ReplayOperation.Status replay_remote_async() throws Error {
- if (moved_ids.size == 0)
+ if (to_move.size == 0)
return ReplayOperation.Status.COMPLETED;
// 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));
+ remaining_msg_sets = Imap.MessageSet.uid_sparse(ImapDB.EmailIdentifier.to_uids(to_move));
if (remaining_msg_sets == null || remaining_msg_sets.size == 0)
return ReplayOperation.Status.COMPLETED;
@@ -81,11 +54,7 @@ private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation
Imap.MessageSet msg_set = iter.get();
- Gee.Map<Imap.UID, Imap.UID>? src_dst_uids = yield engine.remote_folder.copy_email_async(
- msg_set, destination, null);
- if (src_dst_uids != null)
- destination_uids.add_all(src_dst_uids.values);
-
+ 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
@@ -94,14 +63,19 @@ private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation
return ReplayOperation.Status.COMPLETED;
}
-
+
public override async void backout_local_async() throws Error {
- yield engine.local_folder.mark_removed_async(moved_ids, false, cancellable);
+ if (to_move.size == 0)
+ return;
+
+ yield engine.local_folder.mark_removed_async(to_move, false, cancellable);
+
+ int count = engine.get_remote_counts(null, null);
- engine.replay_notify_email_inserted(moved_ids);
- engine.replay_notify_email_count_changed(original_count, Geary.Folder.CountChangeReason.INSERTED);
+ engine.replay_notify_email_inserted(to_move);
+ engine.replay_notify_email_count_changed(count + to_move.size, Folder.CountChangeReason.INSERTED);
}
-
+
public override string describe_state() {
return "%d email IDs to %s".printf(to_move.size, destination.to_string());
}
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala
b/src/engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala
new file mode 100644
index 0000000..7e0db69
--- /dev/null
+++ b/src/engine/imap-engine/replay-ops/imap-engine-move-email-prepare.vala
@@ -0,0 +1,65 @@
+/* Copyright 2012-2014 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.
+ */
+
+private class Geary.ImapEngine.MoveEmailPrepare : Geary.ImapEngine.SendReplayOperation {
+ public Gee.Set<ImapDB.EmailIdentifier>? prepared_for_move = null;
+
+ private MinimalFolder engine;
+ private Cancellable? cancellable;
+ private Gee.List<ImapDB.EmailIdentifier> to_move = new Gee.ArrayList<ImapDB.EmailIdentifier>();
+
+ public MoveEmailPrepare(MinimalFolder engine, Gee.Collection<ImapDB.EmailIdentifier> to_move,
+ Cancellable? cancellable) {
+ base.only_local("MoveEmailPrepare", OnError.RETRY);
+
+ this.engine = engine;
+ this.to_move.add_all(to_move);
+ this.cancellable = cancellable;
+ }
+
+ public override void notify_remote_removed_ids(Gee.Collection<ImapDB.EmailIdentifier> ids) {
+ if (prepared_for_move != null)
+ prepared_for_move.remove_all(ids);
+ }
+
+ public override async ReplayOperation.Status replay_local_async() throws Error {
+ if (to_move.size <= 0)
+ return ReplayOperation.Status.COMPLETED;
+
+ int count = engine.get_remote_counts(null, null);
+
+ // as this value is only used for reporting, offer best-possible service
+ if (count < 0)
+ count = to_move.size;
+
+ prepared_for_move = yield engine.local_folder.mark_removed_async(to_move, true, cancellable);
+ if (prepared_for_move == null || prepared_for_move.size == 0)
+ return ReplayOperation.Status.COMPLETED;
+
+ engine.replay_notify_email_removed(prepared_for_move);
+
+ engine.replay_notify_email_count_changed(
+ Numeric.int_floor(count - prepared_for_move.size, 0),
+ Folder.CountChangeReason.REMOVED);
+
+ return ReplayOperation.Status.COMPLETED;
+ }
+
+ public override void get_ids_to_be_remote_removed(Gee.Collection<ImapDB.EmailIdentifier> ids) {
+ }
+
+ public override async ReplayOperation.Status replay_remote_async() throws Error {
+ return ReplayOperation.Status.COMPLETED;
+ }
+
+ public override async void backout_local_async() throws Error {
+ }
+
+ public override string describe_state() {
+ return "%d email IDs".printf(prepared_for_move.size);
+ }
+}
+
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala
b/src/engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala
new file mode 100644
index 0000000..62df914
--- /dev/null
+++ b/src/engine/imap-engine/replay-ops/imap-engine-move-email-revoke.vala
@@ -0,0 +1,55 @@
+/* Copyright 2012-2014 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.
+ */
+
+private class Geary.ImapEngine.MoveEmailRevoke : Geary.ImapEngine.SendReplayOperation {
+ private MinimalFolder engine;
+ private Gee.List<ImapDB.EmailIdentifier> to_revoke = new Gee.ArrayList<ImapDB.EmailIdentifier>();
+ private Cancellable? cancellable;
+
+ public MoveEmailRevoke(MinimalFolder engine, Gee.Collection<ImapDB.EmailIdentifier> to_revoke,
+ Cancellable? cancellable) {
+ base.only_local("MoveEmailRevoke", OnError.RETRY);
+
+ this.engine = engine;
+
+ this.to_revoke.add_all(to_revoke);
+ this.cancellable = cancellable;
+ }
+
+ public override void notify_remote_removed_ids(Gee.Collection<ImapDB.EmailIdentifier> ids) {
+ to_revoke.remove_all(ids);
+ }
+
+ public override async ReplayOperation.Status replay_local_async() throws Error {
+ if (to_revoke.size == 0)
+ return ReplayOperation.Status.COMPLETED;
+
+ yield engine.local_folder.mark_removed_async(to_revoke, false, cancellable);
+
+ int count = engine.get_remote_counts(null, null);
+
+ engine.replay_notify_email_inserted(to_revoke);
+ engine.replay_notify_email_count_changed(count + to_revoke.size,
+ Geary.Folder.CountChangeReason.INSERTED);
+
+ return ReplayOperation.Status.COMPLETED;
+ }
+
+ public override void get_ids_to_be_remote_removed(Gee.Collection<ImapDB.EmailIdentifier> ids) {
+ }
+
+ public override async ReplayOperation.Status replay_remote_async() throws Error {
+ return ReplayOperation.Status.COMPLETED;
+ }
+
+ public override async void backout_local_async() throws Error {
+ }
+
+ public override string describe_state() {
+ return "%d email IDs".printf(to_revoke.size);
+ }
+}
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]