From 34396624462b32eaa511aa840eafe0df9e21fa09 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 15 Mar 2024 16:23:36 +0100 Subject: [PATCH 1/2] Add `ImapStoreConfig.isExpungeImmediately()` --- .../src/main/java/com/fsck/k9/backends/ImapBackendFactory.kt | 3 +++ .../main/java/com/fsck/k9/mail/store/imap/ImapFolderFetcher.kt | 1 + .../com/fsck/k9/mail/store/imap/ImapServerSettingsValidator.kt | 1 + .../main/java/com/fsck/k9/mail/store/imap/ImapStoreConfig.kt | 1 + .../test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt | 1 + 5 files changed, 7 insertions(+) diff --git a/app/common/src/main/java/com/fsck/k9/backends/ImapBackendFactory.kt b/app/common/src/main/java/com/fsck/k9/backends/ImapBackendFactory.kt index 62202b3bf..123ca4d22 100644 --- a/app/common/src/main/java/com/fsck/k9/backends/ImapBackendFactory.kt +++ b/app/common/src/main/java/com/fsck/k9/backends/ImapBackendFactory.kt @@ -2,6 +2,7 @@ package com.fsck.k9.backends import android.content.Context import com.fsck.k9.Account +import com.fsck.k9.Account.Expunge import com.fsck.k9.backend.BackendFactory import com.fsck.k9.backend.api.Backend import com.fsck.k9.backend.imap.ImapBackend @@ -74,6 +75,8 @@ class ImapBackendFactory( override fun isSubscribedFoldersOnly() = account.isSubscribedFoldersOnly + override fun isExpungeImmediately() = account.expungePolicy == Expunge.EXPUNGE_IMMEDIATELY + override fun clientId() = ImapClientId(appName = clientIdAppName, appVersion = clientIdAppVersion) } } diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolderFetcher.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolderFetcher.kt index 11c32bd47..6479e0d38 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolderFetcher.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolderFetcher.kt @@ -41,6 +41,7 @@ class ImapFolderFetcher internal constructor( val config = object : ImapStoreConfig { override val logLabel = "folder-fetcher" override fun isSubscribedFoldersOnly() = false + override fun isExpungeImmediately() = false override fun clientId() = ImapClientId(appName = clientIdAppName, appVersion = clientIdAppVersion) } val oAuth2TokenProvider = createOAuth2TokenProviderOrNull(authStateStorage) diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapServerSettingsValidator.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapServerSettingsValidator.kt index 2c71b3d48..82cb34bae 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapServerSettingsValidator.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapServerSettingsValidator.kt @@ -32,6 +32,7 @@ class ImapServerSettingsValidator( val config = object : ImapStoreConfig { override val logLabel = "check" override fun isSubscribedFoldersOnly() = false + override fun isExpungeImmediately() = false override fun clientId() = ImapClientId(appName = clientIdAppName, appVersion = clientIdAppVersion) } val oAuth2TokenProvider = createOAuth2TokenProviderOrNull(authStateStorage) diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStoreConfig.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStoreConfig.kt index e767f16aa..989899e4c 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStoreConfig.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapStoreConfig.kt @@ -3,5 +3,6 @@ package com.fsck.k9.mail.store.imap interface ImapStoreConfig { val logLabel: String fun isSubscribedFoldersOnly(): Boolean + fun isExpungeImmediately(): Boolean fun clientId(): ImapClientId } diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt index 5bd444635..42485bc9a 100644 --- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt +++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapStoreTest.kt @@ -408,6 +408,7 @@ class RealImapStoreTest { return object : ImapStoreConfig { override val logLabel: String = "irrelevant" override fun isSubscribedFoldersOnly(): Boolean = isSubscribedFoldersOnly + override fun isExpungeImmediately(): Boolean = true override fun clientId(): ImapClientId = ImapClientId(appName = "irrelevant", appVersion = "irrelevant") } } From e59839c21e25515afc8e1085bdb888efe23a7ad4 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 15 Mar 2024 17:00:12 +0100 Subject: [PATCH 2/2] Change EXPUNGE behavior after moving a message --- .../k9/mail/store/imap/InternalImapStore.kt | 1 + .../fsck/k9/mail/store/imap/RealImapFolder.kt | 6 ++--- .../fsck/k9/mail/store/imap/RealImapStore.kt | 2 +- .../k9/mail/store/imap/FakeImapStoreConfig.kt | 17 ++++++++++++++ .../k9/mail/store/imap/RealImapFolderTest.kt | 23 ++++++++++++++++++- 5 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/FakeImapStoreConfig.kt diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/InternalImapStore.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/InternalImapStore.kt index 2ae729c46..647327f8f 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/InternalImapStore.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/InternalImapStore.kt @@ -4,6 +4,7 @@ import com.fsck.k9.mail.Flag internal interface InternalImapStore { val logLabel: String + val config: ImapStoreConfig fun getCombinedPrefix(): String diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt index 84262d4e2..9d36ec012 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt @@ -354,7 +354,7 @@ internal class RealImapFolder( val uidMapping = copyMessages(messages, folder) setFlags(messages, setOf(Flag.DELETED), true) - expungeUidsOnly(uids) + expungeUidsAfterDelete(uids) return uidMapping } @@ -1117,8 +1117,8 @@ internal class RealImapFolder( expungeUids(uids, fullExpungeFallback = true) } - private fun expungeUidsOnly(uids: List) { - expungeUids(uids, fullExpungeFallback = false) + private fun expungeUidsAfterDelete(uids: List) { + expungeUids(uids, fullExpungeFallback = internalImapStore.config.isExpungeImmediately()) } private fun expungeUids(uids: List, fullExpungeFallback: Boolean) { diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt index 1542fb798..7f3e07ab2 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapStore.kt @@ -20,7 +20,7 @@ import java.util.LinkedList internal open class RealImapStore( private val serverSettings: ServerSettings, - private val config: ImapStoreConfig, + override val config: ImapStoreConfig, private val trustedSocketFactory: TrustedSocketFactory, private val oauthTokenProvider: OAuth2TokenProvider?, ) : ImapStore, ImapConnectionManager, InternalImapStore { diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/FakeImapStoreConfig.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/FakeImapStoreConfig.kt new file mode 100644 index 000000000..2e7fbfdea --- /dev/null +++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/FakeImapStoreConfig.kt @@ -0,0 +1,17 @@ +package com.fsck.k9.mail.store.imap + +class FakeImapStoreConfig : ImapStoreConfig { + var expungeImmediately = true + + override var logLabel: String = "irrelevant" + + override fun isSubscribedFoldersOnly(): Boolean { + throw UnsupportedOperationException("not implemented") + } + + override fun isExpungeImmediately(): Boolean = expungeImmediately + + override fun clientId(): ImapClientId { + throw UnsupportedOperationException("not implemented") + } +} diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt index acb4b80c5..a213ccde1 100644 --- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt +++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt @@ -54,8 +54,10 @@ import org.mockito.kotlin.stub import org.mockito.kotlin.whenever class RealImapFolderTest { + private val imapStoreConfig = FakeImapStoreConfig() private val internalImapStore = object : InternalImapStore { override val logLabel = "Account" + override val config = imapStoreConfig override fun getCombinedPrefix() = "" override fun getPermanentFlagsIndex() = mutableSetOf() } @@ -355,7 +357,26 @@ class RealImapFolderTest { } @Test - fun `moveMessages() should delete messages from source folder but not issue EXPUNGE command`() { + fun `moveMessages() with expungeImmediately = true should delete and expunge`() { + imapStoreConfig.expungeImmediately = true + val sourceFolder = createFolder("Folder") + prepareImapFolderForOpen(OpenMode.READ_WRITE) + imapConnection.stub { + on { isUidPlusCapable } doReturn false + } + val destinationFolder = createFolder("Destination") + val messages = listOf(createImapMessage("1")) + sourceFolder.open(OpenMode.READ_WRITE) + + sourceFolder.moveMessages(messages, destinationFolder) + + assertCommandWithIdsIssued("UID STORE 1 +FLAGS.SILENT (\\Deleted)") + assertCommandIssued("EXPUNGE") + } + + @Test + fun `moveMessages() with expungeImmediately = false should delete but not expunge`() { + imapStoreConfig.expungeImmediately = false val sourceFolder = createFolder("Folder") prepareImapFolderForOpen(OpenMode.READ_WRITE) imapConnection.stub {