[geary/wip/721828-undo-2] Further work on undo, using timer and folder/app close to commit



commit aa0a383dc7330fb62a8b91c8c5e7eefa39042609
Author: Jim Nelson <jim yorba org>
Date:   Mon Feb 2 17:26:28 2015 -0800

    Further work on undo, using timer and folder/app close to commit

 src/CMakeLists.txt                                 |    1 +
 src/client/application/geary-application.vala      |   19 ++++++-
 src/client/application/geary-controller.vala       |   55 ++++++++++++++++++--
 src/engine/api/geary-abstract-local-folder.vala    |   13 ++++-
 src/engine/api/geary-engine.vala                   |   14 +++--
 src/engine/api/geary-folder.vala                   |    8 +++
 src/engine/api/geary-revokable.vala                |   39 ++++++++++++--
 src/engine/app/app-conversation-monitor.vala       |    7 ++-
 .../imap-engine/imap-engine-minimal-folder.vala    |   36 +++++++++++--
 .../imap-engine/imap-engine-revokable-move.vala    |   13 ++++-
 .../replay-ops/imap-engine-move-email-commit.vala  |    4 ++
 .../replay-ops/imap-engine-move-email-prepare.vala |    7 ++-
 .../replay-ops/imap-engine-move-email-revoke.vala  |   13 ++++-
 .../replay-ops/imap-engine-user-close.vala         |   45 ++++++++++++++++
 .../transport/imap-client-session-manager.vala     |   13 ++++-
 15 files changed, 251 insertions(+), 36 deletions(-)
---
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6a991b3..f1d7928 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -221,6 +221,7 @@ engine/imap-engine/replay-ops/imap-engine-replay-append.vala
 engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
 engine/imap-engine/replay-ops/imap-engine-replay-removal.vala
 engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
+engine/imap-engine/replay-ops/imap-engine-user-close.vala
 engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
 engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala
 
diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala
index f533254..5000ae6 100644
--- a/src/client/application/geary-application.vala
+++ b/src/client/application/geary-application.vala
@@ -59,7 +59,6 @@ public class GearyApplication : Gtk.Application {
      * an exit, a callback should return true.
      */
     public virtual signal bool exiting(bool panicked) {
-        controller.close();
         Date.terminate();
         
         return true;
@@ -89,9 +88,9 @@ public class GearyApplication : Gtk.Application {
     
     private string bin;
     private File exec_dir;
-    
     private bool exiting_fired = false;
     private int exitcode = 0;
+    private bool is_destroyed = false;
     
     public GearyApplication() {
         Object(application_id: APP_ID);
@@ -199,6 +198,17 @@ public class GearyApplication : Gtk.Application {
         release();
     }
     
+    private async void destroy_async() {
+        // see create_async() for reasoning hold/release is used
+        hold();
+        
+        yield controller.close_async();
+        
+        release();
+        
+        is_destroyed = true;
+    }
+    
     public bool compose(string mailto) {
         if (controller == null)
             return false;
@@ -327,6 +337,11 @@ public class GearyApplication : Gtk.Application {
             return;
         }
         
+        // Give asynchronous destroy_async() a chance to complete
+        destroy_async.begin();
+        while (!is_destroyed || Gtk.events_pending())
+            Gtk.main_iteration();
+        
         if (Gtk.main_level() > 0)
             Gtk.main_quit();
         else
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 76f5fcf..fdbc192 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -256,13 +256,55 @@ public class GearyController : Geary.BaseObject {
     }
     
     /**
-     * Stops the controller and shuts down Geary.
+     * At the moment, this is non-reversible, i.e. once closed a GearyController cannot be
+     * re-opened.
      */
-    public void close() {
+    public async void close_async() {
+        // hide window while shutting down, as this can take a few seconds under certain conditions
+        main_window.hide();
+        
+        // drop the Revokable, which will commit it if necessary
+        save_revokable(null, null);
+        
+        try {
+            if (current_conversations != null) {
+                yield current_conversations.stop_monitoring_async(null);
+                
+                // If not an Inbox, wait for it to close so all pending operations are flushed
+                if (!inboxes.values.contains(current_conversations.folder))
+                    yield current_conversations.folder.wait_for_close_async(null);
+            }
+        } catch (Error err) {
+            message("Error closing conversation at shutdown: %s", err.message);
+        } finally {
+            current_conversations = null;
+        }
+        
+        foreach (Geary.Folder inbox in inboxes.values) {
+            try {
+                // close and wait for all pending operations to be flushed
+                yield inbox.close_async(null);
+                yield inbox.wait_for_close_async(null);
+            } catch (Error err) {
+                message("Error closing Inbox %s at shutdown: %s", inbox.to_string(), err.message);
+            }
+        }
+        
+        foreach (Geary.Account account in email_stores.keys) {
+            try {
+                yield account.close_async(null);
+            } catch (Error err) {
+                message("Error closing account %s at shutdown: %s", account.to_string(), err.message);
+            }
+        }
+        
         main_window.destroy();
-        main_window = null;
-        current_account = null;
-        account_selected(null);
+        
+        try {
+            yield Geary.Engine.instance.close_async(null);
+        } catch (Error err) {
+            message("Error closing Geary Engine instance: %s", err.message);
+        }
     }
     
     private void add_accelerator(string accelerator, string action) {
@@ -1283,6 +1325,9 @@ public class GearyController : Geary.BaseObject {
         Cancellable? conversation_cancellable = (current_is_inbox ?
             inbox_cancellables.get(folder.account) : cancellable_folder);
         
+        // clear Revokable, as Undo is only available while a folder is selected
+        save_revokable(null, null);
+        
         // stop monitoring for conversations and close the folder
         if (current_conversations != null) {
             yield current_conversations.stop_monitoring_async(null);
diff --git a/src/engine/api/geary-abstract-local-folder.vala b/src/engine/api/geary-abstract-local-folder.vala
index f78dcab..3b32a92 100644
--- a/src/engine/api/geary-abstract-local-folder.vala
+++ b/src/engine/api/geary-abstract-local-folder.vala
@@ -8,11 +8,12 @@
  * Handles open/close for local folders.
  */
 public abstract class Geary.AbstractLocalFolder : Geary.Folder {
-    private int open_count = 0;
-    
     private ProgressMonitor _opening_monitor = new 
Geary.ReentrantProgressMonitor(Geary.ProgressType.ACTIVITY);
     public override Geary.ProgressMonitor opening_monitor { get { return _opening_monitor; } }
     
+    private int open_count = 0;
+    private Nonblocking.Semaphore closed_semaphore = new Nonblocking.Semaphore();
+    
     protected AbstractLocalFolder() {
     }
     
@@ -39,6 +40,8 @@ public abstract class Geary.AbstractLocalFolder : Geary.Folder {
         if (open_count++ > 0)
             return false;
         
+        closed_semaphore.reset();
+        
         notify_opened(Geary.Folder.OpenState.LOCAL, properties.email_total);
         
         return true;
@@ -48,8 +51,14 @@ public abstract class Geary.AbstractLocalFolder : Geary.Folder {
         if (open_count == 0 || --open_count > 0)
             return;
         
+        closed_semaphore.blind_notify();
+        
         notify_closed(Geary.Folder.CloseReason.LOCAL_CLOSE);
         notify_closed(Geary.Folder.CloseReason.FOLDER_CLOSED);
     }
+    
+    public override async void wait_for_close_async(Cancellable? cancellable = null) throws Error {
+        yield closed_semaphore.wait_async(cancellable);
+    }
 }
 
diff --git a/src/engine/api/geary-engine.vala b/src/engine/api/geary-engine.vala
index ae807e9..3d2c8b8 100644
--- a/src/engine/api/geary-engine.vala
+++ b/src/engine/api/geary-engine.vala
@@ -136,8 +136,7 @@ public class Geary.Engine : BaseObject {
      * when necessary.
      */
     public async void open_async(File user_data_dir, File resource_dir,
-                                 Geary.CredentialsMediator? authentication_mediator,
-                                 Cancellable? cancellable = null) throws Error {
+        Geary.CredentialsMediator? authentication_mediator, Cancellable? cancellable = null) throws Error {
         // initialize *before* opening the Engine ... all initialize code should assume the Engine
         // is closed
         initialize_library();
@@ -202,16 +201,19 @@ public class Geary.Engine : BaseObject {
     public async void close_async(Cancellable? cancellable = null) throws Error {
         if (!is_open)
             return;
-
-        foreach(AccountInformation account in accounts.values)
+        
+        Gee.Collection<AccountInformation> unavailable_accounts = accounts.values;
+        accounts.clear();
+        
+        foreach(AccountInformation account in unavailable_accounts)
             account_unavailable(account);
-
+        
         user_data_dir = null;
         resource_dir = null;
         authentication_mediator = null;
         accounts = null;
         account_instances = null;
-
+        
         is_open = false;
         closed();
     }
diff --git a/src/engine/api/geary-folder.vala b/src/engine/api/geary-folder.vala
index 8f76fb3..cba29de 100644
--- a/src/engine/api/geary-folder.vala
+++ b/src/engine/api/geary-folder.vala
@@ -457,6 +457,14 @@ public abstract class Geary.Folder : BaseObject {
     public abstract async void close_async(Cancellable? cancellable = null) throws Error;
     
     /**
+     * Wait for the Folder to fully close.
+     *
+     * Unlike { link wait_for_open_async}, this will ''always'' block until a { link Folder} is
+     * closed, even if it's not open.
+     */
+    public abstract async void wait_for_close_async(Cancellable? cancellable = null) throws Error;
+    
+    /**
      * Find the lowest- and highest-ordered { link EmailIdentifier}s in the
      * folder, among the given set of EmailIdentifiers that may or may not be
      * in the folder.  If none of the given set are in the folder, return null.
diff --git a/src/engine/api/geary-revokable.vala b/src/engine/api/geary-revokable.vala
index dbf938d..68707d3 100644
--- a/src/engine/api/geary-revokable.vala
+++ b/src/engine/api/geary-revokable.vala
@@ -32,7 +32,22 @@ public abstract class Geary.Revokable : BaseObject {
      */
     public bool in_process { get; protected set; default = false; }
     
-    protected Revokable() {
+    private uint commit_timeout_id = 0;
+    
+    /**
+     * Create a { link Revokable} with optional parameters.
+     *
+     * If commit_timeout_sec is nonzero, Revokable will automatically call { link commit_async}
+     * after the timeout expires if it is still { link valid}.
+     */
+    protected Revokable(int commit_timeout_sec = 0) {
+        if (commit_timeout_sec > 0)
+            commit_timeout_id = Timeout.add_seconds(commit_timeout_sec, on_commit);
+    }
+    
+    ~Revokable() {
+        if (commit_timeout_id > 0)
+            Source.remove(commit_timeout_id);
     }
     
     /**
@@ -41,12 +56,16 @@ public abstract class Geary.Revokable : BaseObject {
      * If the call throws an Error that does not necessarily mean the { link Revokable} is
      * invalid.  Check { link valid}.
      *
-     * @throws EngineError.ALREADY_OPEN if { link in_process} is true.
+     * @throws EngineError.ALREADY_OPEN if { link in_process} is true.  EngineError.ALREADY_CLOSED
+     * if { link valid} is false.
      */
     public virtual async void revoke_async(Cancellable? cancellable = null) throws Error {
         if (in_process)
             throw new EngineError.ALREADY_OPEN("Already revoking or committing operation");
         
+        if (!valid)
+            throw new EngineError.ALREADY_CLOSED("Revokable not valid");
+        
         in_process = true;
         try {
             yield internal_revoke_async(cancellable);
@@ -76,13 +95,16 @@ public abstract class Geary.Revokable : BaseObject {
      * 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.
+     * @throws EngineError.ALREADY_OPEN if { link in_process} is true.  EngineError.ALREADY_CLOSED
+     * if { link valid} is false.
      */
     public virtual async void commit_async(Cancellable? cancellable = null) throws Error {
         if (in_process)
             throw new EngineError.ALREADY_OPEN("Already revoking or committing operation");
         
+        if (!valid)
+            throw new EngineError.ALREADY_CLOSED("Revokable not valid");
+        
         in_process = true;
         try {
             yield internal_commit_async(cancellable);
@@ -101,5 +123,14 @@ public abstract class Geary.Revokable : BaseObject {
      * This call *must* set { link valid} before exiting.
      */
     protected abstract async void internal_commit_async(Cancellable? cancellable) throws Error;
+    
+    private bool on_commit() {
+        commit_timeout_id = 0;
+        
+        if (valid && !in_process)
+            commit_async.begin();
+        
+        return false;
+    }
 }
 
diff --git a/src/engine/app/app-conversation-monitor.vala b/src/engine/app/app-conversation-monitor.vala
index db4373a..48943c1 100644
--- a/src/engine/app/app-conversation-monitor.vala
+++ b/src/engine/app/app-conversation-monitor.vala
@@ -631,7 +631,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
     }
     
     internal async void remove_emails_async(Gee.Collection<Geary.EmailIdentifier> removed_ids) {
-        debug("%d messages(s) removed to %s, trimming/removing conversations...", removed_ids.size,
+        debug("%d messages(s) removed from %s, trimming/removing conversations...", removed_ids.size,
             folder.to_string());
         
         Gee.Collection<Geary.App.Conversation> removed;
@@ -732,7 +732,10 @@ public class Geary.App.ConversationMonitor : BaseObject {
      * Attempts to load enough conversations to fill min_window_count.
      */
     internal async void fill_window_async(bool is_insert) {
-        if (!is_monitoring || min_window_count <= conversations.size)
+        if (!is_monitoring)
+            return;
+        
+        if (!is_insert && min_window_count <= conversations.size)
             return;
         
         int initial_message_count = conversations.get_email_count();
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala 
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index 21d9c59..1e2985a 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -45,6 +45,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
     private bool remote_opened = false;
     private Nonblocking.ReportingSemaphore<bool> remote_semaphore =
         new Nonblocking.ReportingSemaphore<bool>(false);
+    private Nonblocking.Semaphore closed_semaphore = new Nonblocking.Semaphore();
     private ReplayQueue replay_queue;
     private int remote_count = -1;
     private uint open_remote_timer_id = 0;
@@ -537,6 +538,9 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
         // reset to force waiting in wait_for_open_async()
         remote_semaphore.reset();
         
+        // reset to force waiting in wait_for_close_async()
+        closed_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
         // fast local-only operations to occur, local-only either because (a) the folder has all
@@ -758,6 +762,22 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
     }
     
     public override async void close_async(Cancellable? cancellable = null) throws Error {
+        // Check open_count but only decrement inside of replay queue
+        if (open_count <= 0)
+            return;
+        
+        UserClose user_close = new UserClose(this, cancellable);
+        replay_queue.schedule(user_close);
+        
+        yield user_close.wait_for_ready_async(cancellable);
+    }
+    
+    public override async void wait_for_close_async(Cancellable? cancellable = null) throws Error {
+        yield closed_semaphore.wait_async(cancellable);
+    }
+    
+    internal async void user_close_async(Cancellable? cancellable) {
+        // decrement open_count and, if zero, continue closing Folder
         if (open_count == 0 || --open_count > 0)
             return;
         
@@ -767,11 +787,9 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
         // 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);
+        // don't yield here, close_internal_async() needs to be called outside of the replay queue
+        // the open_count protects against this path scheduling it more than once
+        close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, true, cancellable);
     }
     
     // Close the remote connection and, if open_count is zero, the Folder itself.  A Mutex is used
@@ -901,6 +919,10 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
         
         notify_closed(CloseReason.FOLDER_CLOSED);
         
+        // If not closing in the background, do it here
+        if (closing_remote_folder == null)
+            closed_semaphore.blind_notify();
+        
         debug("Folder %s closed", to_string());
     }
     
@@ -949,6 +971,10 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
         if (folder.open_count <= 0) {
             debug("Not reestablishing connection to %s: closed", folder.to_string());
             
+            // need to do it here if not done in close_internal_locked_async()
+            if (remote_folder != null)
+                folder.closed_semaphore.blind_notify();
+            
             return;
         }
         
diff --git a/src/engine/imap-engine/imap-engine-revokable-move.vala 
b/src/engine/imap-engine/imap-engine-revokable-move.vala
index 3c5ce8d..b7c8add 100644
--- a/src/engine/imap-engine/imap-engine-revokable-move.vala
+++ b/src/engine/imap-engine/imap-engine-revokable-move.vala
@@ -5,6 +5,8 @@
  */
 
 private class Geary.ImapEngine.RevokableMove : Revokable {
+    private const int REVOKE_TIMEOUT_SEC = 60;
+    
     private GenericAccount account;
     private ImapEngine.MinimalFolder source;
     private FolderPath destination;
@@ -12,6 +14,8 @@ 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);
+        
         this.account = account;
         this.source = source;
         this.destination = destination;
@@ -73,7 +77,7 @@ private class Geary.ImapEngine.RevokableMove : Revokable {
     }
     
     private void on_source_email_removed(Gee.Collection<EmailIdentifier> ids) {
-        // one-way switch, and only interested in destination folder activity
+        // one-way switch
         if (!valid)
             return;
         
@@ -84,8 +88,11 @@ private class Geary.ImapEngine.RevokableMove : Revokable {
     }
     
     private void on_source_closing(Gee.List<ReplayOperation> final_ops) {
-        if (valid)
-            final_ops.add(new MoveEmailCommit(source, move_ids, destination, null));
+        if (!valid)
+            return;
+        
+        final_ops.add(new MoveEmailCommit(source, move_ids, destination, null));
+        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 2d46ae0..fb71550 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
@@ -4,6 +4,10 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
+/**
+ * Stage two of a { link RevokableMove}: move messages from folder to destination.
+ */
+
 private class Geary.ImapEngine.MoveEmailCommit : Geary.ImapEngine.SendReplayOperation {
     private MinimalFolder engine;
     private Gee.List<ImapDB.EmailIdentifier> to_move = new Gee.ArrayList<ImapDB.EmailIdentifier>();
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
index 7e0db69..58b4e4a 100644
--- 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
@@ -4,6 +4,11 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
+/**
+ * Stage one of a { link RevokableMove}: collect valid { link ImapDB.EmailIdentifiers}, mark
+ * messages as removed, and update counts.
+ */
+
 private class Geary.ImapEngine.MoveEmailPrepare : Geary.ImapEngine.SendReplayOperation {
     public Gee.Set<ImapDB.EmailIdentifier>? prepared_for_move = null;
     
@@ -59,7 +64,7 @@ private class Geary.ImapEngine.MoveEmailPrepare : Geary.ImapEngine.SendReplayOpe
     }
     
     public override string describe_state() {
-        return "%d email IDs".printf(prepared_for_move.size);
+        return "%d email IDs".printf(prepared_for_move != null ? prepared_for_move.size : 0);
     }
 }
 
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
index 62df914..15de189 100644
--- 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
@@ -4,6 +4,10 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
+/**
+ * Revoked { link RevokableMove}: Unmark emails as removed and update counts.
+ */
+
 private class Geary.ImapEngine.MoveEmailRevoke : Geary.ImapEngine.SendReplayOperation {
     private MinimalFolder engine;
     private Gee.List<ImapDB.EmailIdentifier> to_revoke = new Gee.ArrayList<ImapDB.EmailIdentifier>();
@@ -27,12 +31,15 @@ private class Geary.ImapEngine.MoveEmailRevoke : Geary.ImapEngine.SendReplayOper
         if (to_revoke.size == 0)
             return ReplayOperation.Status.COMPLETED;
         
-        yield engine.local_folder.mark_removed_async(to_revoke, false, cancellable);
+        Gee.Set<ImapDB.EmailIdentifier>? revoked = yield engine.local_folder.mark_removed_async(
+            to_revoke, false, cancellable);
+        if (revoked == null || revoked.size == 0)
+            return ReplayOperation.Status.COMPLETED;
         
         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,
+        engine.replay_notify_email_inserted(revoked);
+        engine.replay_notify_email_count_changed(count + revoked.size,
             Geary.Folder.CountChangeReason.INSERTED);
         
         return ReplayOperation.Status.COMPLETED;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-user-close.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-user-close.vala
new file mode 100644
index 0000000..14e279f
--- /dev/null
+++ b/src/engine/imap-engine/replay-ops/imap-engine-user-close.vala
@@ -0,0 +1,45 @@
+/* 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.UserClose : Geary.ImapEngine.ReplayOperation {
+    private MinimalFolder owner;
+    private Cancellable? cancellable;
+    
+    public UserClose(MinimalFolder owner, Cancellable? cancellable) {
+        base ("UserClose", Scope.LOCAL_ONLY);
+        
+        this.owner = owner;
+        this.cancellable = cancellable;
+    }
+    
+    public override void notify_remote_removed_position(Imap.SequenceNumber removed) {
+    }
+    
+    public override void notify_remote_removed_ids(Gee.Collection<ImapDB.EmailIdentifier> ids) {
+    }
+    
+    public override void get_ids_to_be_remote_removed(Gee.Collection<ImapDB.EmailIdentifier> ids) {
+    }
+    
+    public override async ReplayOperation.Status replay_local_async() throws Error {
+        yield owner.user_close_async(cancellable);
+        
+        return ReplayOperation.Status.COMPLETED;
+    }
+    
+    public override async void backout_local_async() throws Error {
+    }
+    
+    public override async ReplayOperation.Status replay_remote_async() throws Error {
+        // should not be called
+        return ReplayOperation.Status.COMPLETED;
+    }
+    
+    public override string describe_state() {
+        return "";
+    }
+}
+
diff --git a/src/engine/imap/transport/imap-client-session-manager.vala 
b/src/engine/imap/transport/imap-client-session-manager.vala
index c84ccce..2f9f1de 100644
--- a/src/engine/imap/transport/imap-client-session-manager.vala
+++ b/src/engine/imap/transport/imap-client-session-manager.vala
@@ -341,7 +341,8 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
     
     public async void release_session_async(ClientSession session, Cancellable? cancellable)
         throws Error {
-        check_open();
+        // Don't check_open(), it's valid for this to be called when is_open is false, that happens
+        // during mop-up
         
         MailboxSpecifier? mailbox;
         ClientSession.Context context = session.get_context(out mailbox);
@@ -383,9 +384,15 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
                 assert_not_reached();
         }
         
-        if (unreserve) {
+        if (!unreserve)
+            return;
+        
+        // if not open, disconnect, which will remove from the reserved pool anyway
+        if (!is_open) {
+            yield force_disconnect_async(session, true);
+        } else {
             try {
-                // don't respect Cancellable because this *must* happen; don't want this lingering 
+                // don't respect Cancellable because this *must* happen; don't want this lingering
                 // on the reserved list forever
                 int token = yield sessions_mutex.claim_async();
                 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]