[geary/wip/721828-undo-2: 7/8] Allow undoing a move even after email was moved (committed) to dest
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/721828-undo-2: 7/8] Allow undoing a move even after email was moved (committed) to dest
- Date: Tue, 3 Feb 2015 22:52:21 +0000 (UTC)
commit 1c0b5f211a86e6eb6f816d3ea50fadb0a73c6d81
Author: Jim Nelson <jim yorba org>
Date: Tue Feb 3 14:50:48 2015 -0800
Allow undoing a move even after email was moved (committed) to dest
src/CMakeLists.txt | 1 +
src/client/application/geary-controller.vala | 29 ++++++++-
src/engine/api/geary-revokable.vala | 30 +++++++++-
.../imap-engine-revokable-committed-move.vala | 64 ++++++++++++++++++++
.../imap-engine/imap-engine-revokable-move.vala | 23 ++++++-
.../replay-ops/imap-engine-move-email-commit.vala | 8 ++-
6 files changed, 145 insertions(+), 10 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f1d7928..80a715a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -193,6 +193,7 @@ engine/imap-engine/imap-engine-minimal-folder.vala
engine/imap-engine/imap-engine-replay-operation.vala
engine/imap-engine/imap-engine-replay-queue.vala
engine/imap-engine/imap-engine-revokable-move.vala
+engine/imap-engine/imap-engine-revokable-committed-move.vala
engine/imap-engine/imap-engine-send-replay-operation.vala
engine/imap-engine/gmail/imap-engine-gmail-account.vala
engine/imap-engine/gmail/imap-engine-gmail-all-mail-folder.vala
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 1a4e353..6424790 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -2519,19 +2519,31 @@ public class GearyController : Geary.BaseObject {
private void save_revokable(Geary.Revokable? new_revokable, string? description) {
// disconnect old revokable
- if (revokable != null)
+ if (revokable != null) {
revokable.notify[Geary.Revokable.PROP_VALID].disconnect(on_revokable_valid_changed);
+ revokable.notify[Geary.Revokable.PROP_IN_PROCESS].disconnect(update_revokable_action);
+ revokable.committed.disconnect(on_revokable_committed);
+ }
// store new revokable
revokable = new_revokable;
// connect to new revokable
- if (revokable != null)
+ if (revokable != null) {
revokable.notify[Geary.Revokable.PROP_VALID].connect(on_revokable_valid_changed);
+ revokable.notify[Geary.Revokable.PROP_IN_PROCESS].connect(update_revokable_action);
+ revokable.committed.connect(on_revokable_committed);
+ }
Gtk.Action undo_action = GearyApplication.instance.get_action(ACTION_UNDO);
- undo_action.sensitive = revokable != null && revokable.valid;
undo_action.tooltip = (revokable != null && description != null) ? description : _("Undo (Ctrl+Z)");
+
+ update_revokable_action();
+ }
+
+ private void update_revokable_action() {
+ Gtk.Action undo_action = GearyApplication.instance.get_action(ACTION_UNDO);
+ undo_action.sensitive = revokable != null && revokable.valid && !revokable.in_process;
}
private void on_revokable_valid_changed() {
@@ -2540,6 +2552,17 @@ public class GearyController : Geary.BaseObject {
save_revokable(null, null);
}
+ private void on_revokable_committed(Geary.Revokable? committed_revokable) {
+ if (committed_revokable == null)
+ return;
+
+ debug("Committed Revokable issued another Revokable");
+
+ // use existing description
+ Gtk.Action undo_action = GearyApplication.instance.get_action(ACTION_UNDO);
+ save_revokable(committed_revokable, undo_action.tooltip);
+ }
+
private void on_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 68707d3..c6bf35d 100644
--- a/src/engine/api/geary-revokable.vala
+++ b/src/engine/api/geary-revokable.vala
@@ -35,6 +35,22 @@ public abstract class Geary.Revokable : BaseObject {
private uint commit_timeout_id = 0;
/**
+ * Fired when the { link Revokable} has been revoked.
+ *
+ * { link valid} will stil be true when this is fired.
+ */
+ public signal void revoked();
+
+ /**
+ * Fired when the { link Revokable} has been committed.
+ *
+ * Some Revokables will offer a new Revokable to allow revoking the committed state.
+ *
+ * { link valid} will stil be true when this is fired.
+ */
+ public signal void committed(Geary.Revokable? commit_revokable);
+
+ /**
* Create a { link Revokable} with optional parameters.
*
* If commit_timeout_sec is nonzero, Revokable will automatically call { link commit_async}
@@ -50,6 +66,14 @@ public abstract class Geary.Revokable : BaseObject {
Source.remove(commit_timeout_id);
}
+ protected virtual void notify_revoked() {
+ revoked();
+ }
+
+ protected virtual void notify_committed(Geary.Revokable? commit_revokable) {
+ committed(commit_revokable);
+ }
+
/**
* Revoke (undo) the operation.
*
@@ -81,7 +105,8 @@ public abstract class Geary.Revokable : BaseObject {
* ({ 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.
+ * This call *must* set { link valid} before exiting. It must also call { link notify_revoked}
+ * if successful.
*/
protected abstract async void internal_revoke_async(Cancellable? cancellable) throws Error;
@@ -120,7 +145,8 @@ public abstract class Geary.Revokable : BaseObject {
* ({ 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.
+ * This call *must* set { link valid} before exiting. It must also call { link notify_committed}
+ * if successful.
*/
protected abstract async void internal_commit_async(Cancellable? cancellable) throws Error;
diff --git a/src/engine/imap-engine/imap-engine-revokable-committed-move.vala
b/src/engine/imap-engine/imap-engine-revokable-committed-move.vala
new file mode 100644
index 0000000..864672f
--- /dev/null
+++ b/src/engine/imap-engine/imap-engine-revokable-committed-move.vala
@@ -0,0 +1,64 @@
+/* Copyright 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.
+ */
+
+/**
+ * A { link Geary.Revokable} for moving email back to its source after committed with
+ * { link RevokableMove}.
+ */
+
+private class Geary.ImapEngine.RevokableCommittedMove : Revokable {
+ private GenericAccount account;
+ private FolderPath source;
+ private FolderPath destination;
+ private Gee.Set<Imap.UID> destination_uids;
+
+ public RevokableCommittedMove(GenericAccount account, FolderPath source, FolderPath destination,
+ Gee.Set<Imap.UID> destination_uids) {
+ this.account = account;
+ this.source = source;
+ this.destination = destination;
+ this.destination_uids = destination_uids;
+ }
+
+ protected override async void internal_revoke_async(Cancellable? cancellable) throws Error {
+ Imap.Folder? detached_destination = null;
+ try {
+ // use a detached folder to quickly open, issue command, and leave, without full
+ // normalization that MinimalFolder requires
+ detached_destination = yield account.fetch_detached_folder_async(destination, cancellable);
+
+ yield detached_destination.open_async(cancellable);
+
+ foreach (Imap.MessageSet msg_set in Imap.MessageSet.uid_sparse(destination_uids)) {
+ // don't use Cancellable to try to make operations atomic
+ yield detached_destination.copy_email_async(msg_set, source, null);
+ yield detached_destination.remove_email_async(msg_set.to_list(), null);
+
+ if (cancellable != null && cancellable.is_cancelled())
+ throw new IOError.CANCELLED("Revoke cancelled");
+ }
+
+ notify_revoked();
+ } finally {
+ if (detached_destination != null) {
+ try {
+ yield detached_destination.close_async(cancellable);
+ } catch (Error err) {
+ // ignored
+ }
+ }
+
+ valid = false;
+ }
+ }
+
+ protected override async void internal_commit_async(Cancellable? cancellable) throws Error {
+ // pretty simple: already committed, so done
+ notify_committed(null);
+ valid = false;
+ }
+}
+
diff --git a/src/engine/imap-engine/imap-engine-revokable-move.vala
b/src/engine/imap-engine/imap-engine-revokable-move.vala
index b7c8add..80991ce 100644
--- a/src/engine/imap-engine/imap-engine-revokable-move.vala
+++ b/src/engine/imap-engine/imap-engine-revokable-move.vala
@@ -4,8 +4,17 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
+/**
+ * A @{link Geary.Revokable} for { link MinimalFolder} move operations.
+ *
+ * This will delay executing the move until (a) the source Folder is closed or (b) a timeout passes.
+ * Even then, it will fire its "committed" signal with a { link RevokableCommittedMove} to allow
+ * the user to undo the operation, albeit taking more time to connect, open the destination folder,
+ * and move the mail back.
+ */
+
private class Geary.ImapEngine.RevokableMove : Revokable {
- private const int REVOKE_TIMEOUT_SEC = 60;
+ private const int COMMIT_TIMEOUT_SEC = 60;
private GenericAccount account;
private ImapEngine.MinimalFolder source;
@@ -14,7 +23,7 @@ private class Geary.ImapEngine.RevokableMove : Revokable {
public RevokableMove(GenericAccount account, ImapEngine.MinimalFolder source, FolderPath destination,
Gee.Set<ImapDB.EmailIdentifier> move_ids) {
- base (REVOKE_TIMEOUT_SEC);
+ base (COMMIT_TIMEOUT_SEC);
this.account = account;
this.source = source;
@@ -49,6 +58,9 @@ private class Geary.ImapEngine.RevokableMove : Revokable {
try {
yield source.exec_op_async(new MoveEmailRevoke(source, move_ids, cancellable),
cancellable);
+
+ // valid must still be true before firing
+ notify_revoked();
} finally {
valid = false;
}
@@ -56,8 +68,11 @@ private class Geary.ImapEngine.RevokableMove : Revokable {
protected override async void internal_commit_async(Cancellable? cancellable) throws Error {
try {
- yield source.exec_op_async(new MoveEmailCommit(source, move_ids, destination, cancellable),
- cancellable);
+ MoveEmailCommit op = new MoveEmailCommit(source, move_ids, destination, cancellable);
+ yield source.exec_op_async(op, cancellable);
+
+ // valid must still be true before firing
+ notify_committed(new RevokableCommittedMove(account, source.path, destination,
op.destination_uids));
} finally {
valid = false;
}
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala
b/src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala
index fb71550..d0de4d5 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-move-email-commit.vala
@@ -9,6 +9,8 @@
*/
private class Geary.ImapEngine.MoveEmailCommit : Geary.ImapEngine.SendReplayOperation {
+ public Gee.Set<Imap.UID> destination_uids = new Gee.HashSet<Imap.UID>();
+
private MinimalFolder engine;
private Gee.List<ImapDB.EmailIdentifier> to_move = new Gee.ArrayList<ImapDB.EmailIdentifier>();
private Geary.FolderPath destination;
@@ -58,7 +60,11 @@ private class Geary.ImapEngine.MoveEmailCommit : Geary.ImapEngine.SendReplayOper
Imap.MessageSet msg_set = iter.get();
- yield engine.remote_folder.copy_email_async(msg_set, destination, null);
+ Gee.Map<Imap.UID, Imap.UID>? map = yield engine.remote_folder.copy_email_async(msg_set,
+ destination, null);
+ if (map != null)
+ destination_uids.add_all(map.values);
+
yield engine.remote_folder.remove_email_async(msg_set.to_list(), null);
// completed successfully, remove from list in case of retry
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]