Repository: thundernest/k-9
Issue: Clear local messages -> freeze

### Expected behavior
"Clear local messages" should clear the locally stored messages, allowing one to re-download the messages from the server.

### Actual behavior
Freeze.

### Steps to reproduce
1. Have a folder with some large amount of messages. A few hundred should suffice.
2. Make sure it's NOT up-to-date with the server
3. Clear its local messages.

### Environment
K-9 Mail version: 5.203

Android version: 6.0.1

Account type (IMAP, POP3, WebDAV/Exchange): IMAP


Changed files:
k9mail/src/main/java/com/fsck/k9/activity/FolderList.java, k9mail/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java, k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java

diff --git a/k9mail/src/main/java/com/fsck/k9/activity/FolderList.java b/k9mail/src/main/java/com/fsck/k9/activity/FolderList.java
index 4235018bf..203b6eb54 100644
--- a/k9mail/src/main/java/com/fsck/k9/activity/FolderList.java
+++ b/k9mail/src/main/java/com/fsck/k9/activity/FolderList.java
@@ -476,31 +476,9 @@ public class FolderList extends K9ListActivity {
     }
 
     private void onClearFolder(Account account, String folderName) {
-        // There has to be a cheaper way to get at the localFolder object than this
-        LocalFolder localFolder = null;
-        try {
-            if (account == null || folderName == null || !account.isAvailable(FolderList.this)) {
-                Log.i(K9.LOG_TAG, "not clear folder of unavailable account");
-                return;
-            }
-            localFolder = account.getLocalStore().getFolder(folderName);
-            localFolder.open(Folder.OPEN_MODE_RW);
-            localFolder.clearAllMessages();
-        } catch (Exception e) {
-            Log.e(K9.LOG_TAG, "Exception while clearing folder", e);
-        } finally {
-            if (localFolder != null) {
-                localFolder.close();
-            }
-        }
-
-        onRefresh(!REFRESH_REMOTE);
+        MessagingController.getInstance(getApplication()).clearFolder(account, folderName, mAdapter.mListener);
     }
 
-
-
-
-
     private void sendMail(Account account) {
         MessagingController.getInstance(getApplication()).sendPendingMessages(account, mAdapter.mListener);
     }
diff --git a/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java b/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java
index 3f0af1a22..51d1d8a23 100644
--- a/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java
+++ b/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java
@@ -53,6 +53,7 @@ import com.fsck.k9.K9;
 import com.fsck.k9.K9.Intents;
 import com.fsck.k9.Preferences;
 import com.fsck.k9.R;
+import com.fsck.k9.activity.ActivityListener;
 import com.fsck.k9.activity.MessageReference;
 import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
 import com.fsck.k9.cache.EmailProviderCache;
@@ -3513,6 +3514,36 @@ public class MessagingController {
         });
     }
 
+    public void clearFolder(final Account account, final String folderName, final ActivityListener listener) {
+        putBackground("clearFolder", listener, new Runnable() {
+            @Override
+            public void run() {
+                clearFolderSynchronous(account, folderName, listener);
+            }
+        });
+    }
+
+    @VisibleForTesting
+    protected void clearFolderSynchronous(Account account, String folderName, MessagingListener listener) {
+        LocalFolder localFolder = null;
+        try {
+            localFolder = account.getLocalStore().getFolder(folderName);
+            localFolder.open(Folder.OPEN_MODE_RW);
+            localFolder.clearAllMessages();
+        } catch (UnavailableStorageException e) {
+            Log.i(K9.LOG_TAG, "Failed to clear folder because storage is not available - trying again later.");
+            throw new UnavailableAccountException(e);
+        } catch (Exception e) {
+            Log.e(K9.LOG_TAG, "clearFolder failed", e);
+            addErrorMessage(account, null, e);
+        } finally {
+            closeFolder(localFolder);
+        }
+
+        listFoldersSynchronous(account, false, listener);
+    }
+
+
     /**
      * Find out whether the account type only supports a local Trash folder.
      *
diff --git a/k9mail/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java b/k9mail/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java
index e0254d7a1..8429a6cea 100644
--- a/k9mail/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java
+++ b/k9mail/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java
@@ -13,6 +13,7 @@ import android.content.Context;
 
 import com.fsck.k9.Account;
 import com.fsck.k9.AccountStats;
+import com.fsck.k9.K9;
 import com.fsck.k9.K9RobolectricTestRunner;
 import com.fsck.k9.Preferences;
 import com.fsck.k9.helper.Contacts;
@@ -26,6 +27,7 @@ import com.fsck.k9.mail.Store;
 import com.fsck.k9.mailstore.LocalFolder;
 import com.fsck.k9.mailstore.LocalMessage;
 import com.fsck.k9.mailstore.LocalStore;
+import com.fsck.k9.mailstore.UnavailableStorageException;
 import com.fsck.k9.notification.NotificationController;
 import com.fsck.k9.search.LocalSearch;
 import org.junit.After;
@@ -51,6 +53,7 @@ import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -80,6 +83,8 @@ public class MessagingControllerTest {
     @Mock
     private LocalFolder localFolder;
     @Mock
+    private LocalFolder errorFolder;
+    @Mock
     private Folder remoteFolder;
     @Mock
     private LocalStore localStore;
@@ -130,6 +135,62 @@ public class MessagingControllerTest {
         controller.stop();
     }
 
+    @Test
+    public void clearFolderSynchronous_shouldOpenFolderForWriting() throws MessagingException {
+        controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
+
+        verify(localFolder).open(Folder.OPEN_MODE_RW);
+    }
+
+    @Test
+    public void clearFolderSynchronous_shouldClearAllMessagesInTheFolder() throws MessagingException {
+        controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
+
+        verify(localFolder).clearAllMessages();
+    }
+
+    @Test
+    public void clearFolderSynchronous_shouldCloseTheFolder() throws MessagingException {
+        controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
+
+        verify(localFolder, atLeastOnce()).close();
+    }
+
+    @Test(expected = UnavailableAccountException.class)
+    public void clearFolderSynchronous_whenStorageUnavailable_shouldThrowUnavailableAccountException() throws MessagingException {
+        doThrow(new UnavailableStorageException("Test")).when(localFolder).open(Folder.OPEN_MODE_RW);
+
+        controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
+    }
+
+    @Test()
+    public void clearFolderSynchronous_whenExceptionThrown_shouldAddErrorMessage() throws MessagingException {
+        doThrow(new RuntimeException("Test")).when(localFolder).open(Folder.OPEN_MODE_RW);
+
+        controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
+
+        verify(errorFolder).appendMessages(any(List.class));
+    }
+
+    @Test()
+    public void clearFolderSynchronous_whenExceptionThrown_shouldStillCloseFolder() throws MessagingException {
+        doThrow(new RuntimeException("Test")).when(localFolder).open(Folder.OPEN_MODE_RW);
+
+        try {
+            controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
+        } catch (Exception ignored){
+        }
+
+        verify(localFolder, atLeastOnce()).close();
+    }
+
+    @Test()
+    public void clearFolderSynchronous_shouldListFolders() throws MessagingException {
+        controller.clearFolderSynchronous(account, FOLDER_NAME, listener);
+
+        verify(listener, atLeastOnce()).listFoldersStarted(account);
+    }
+
     @Test
     public void listFoldersSynchronous_shouldNotifyTheListenerListingStarted() throws MessagingException {
         List<LocalFolder> folders = Collections.singletonList(localFolder);
@@ -758,11 +819,15 @@ public class MessagingControllerTest {
         when(account.getLocalStore()).thenReturn(localStore);
         when(account.getStats(any(Context.class))).thenReturn(accountStats);
         when(account.getMaximumAutoDownloadMessageSize()).thenReturn(MAXIMUM_SMALL_MESSAGE_SIZE);
+        when(account.getErrorFolderName()).thenReturn(K9.ERROR_FOLDER_NAME);
+        when(account.getEmail()).thenReturn("user@host.com");
     }
 
-    private void configureLocalStore() {
+    private void configureLocalStore() throws MessagingException {
         when(localStore.getFolder(FOLDER_NAME)).thenReturn(localFolder);
         when(localFolder.getName()).thenReturn(FOLDER_NAME);
+        when(localStore.getFolder(K9.ERROR_FOLDER_NAME)).thenReturn(errorFolder);
+        when(localStore.getPersonalNamespaces(false)).thenReturn(Collections.singletonList(localFolder));
     }
 
     private void configureRemoteStoreWithFolder() throws MessagingException {