From ebb507e8579152bed740ef0b8d94436f050ab8ca Mon Sep 17 00:00:00 2001 From: Rafael Tonholo Date: Tue, 1 Sep 2026 11:31:55 -0300 Subject: [PATCH] refactor(mailstore): extract remote folder queries to dedicated repositories - Add RemoteFolderQueryRepository interface with getAllByAccountId method - Add RemoteFolderDetailsRepository interface with getAllByAccountId method - Add DefaultRemoteFolderQueryRepository and DefaultRemoteFolderDetailsRepository implementations - Remove getRemoteFolders and getRemoteFolderDetails from FolderRepository - Update GetAccountFolders, AccountSettingsViewModel, DefaultDisplayFolderRepository, DefaultSpecialFolderUpdater, and FolderSettingsProvider to use new repositories - Make SettingsExporter.exportPreferences and related methods suspend functions - Make FolderSettingsChangedListener.onFolderSettingsChanged suspend function and notify on main immediate dispatcher - Migrate RemoteFolderDetails from data class to typealias referencing API module --- .../data/repository/FolderQueryRepository.kt | 24 +- .../LoadFolderInformationSideEffect.kt | 12 +- .../inject/MessageListSideEffectsModule.kt | 2 +- .../fakes/FakeFolderQueryRepository.kt | 27 ++ .../internal/fakes/FakeFolderRepository.kt | 30 -- .../LoadFolderInformationSideEffectTest.kt | 36 +- .../feature/widget/unread/KoinModule.kt | 4 +- .../widget/unread/UnreadWidgetDataProvider.kt | 8 +- .../widget/unread/UnreadWidgetMigrations.kt | 13 +- .../widget/unread/UnreadWidgetRepository.kt | 2 +- .../unread/UnreadWidgetDataProviderTest.kt | 29 +- .../push/AccountBackendPusherCallback.kt | 6 +- .../push/AccountPushControllerFactory.kt | 6 +- .../com/fsck/k9/controller/push/KoinModule.kt | 4 +- .../fsck/k9/controller/push/PushController.kt | 10 +- ...pandFolderBackendFoldersRefreshListener.kt | 15 +- .../BackendFoldersRefreshListener.kt | 2 +- .../com/fsck/k9/mailstore/K9BackendStorage.kt | 3 +- .../k9/mailstore/K9BackendStorageFactory.kt | 6 +- .../java/com/fsck/k9/mailstore/KoinModule.kt | 15 +- ...cialFolderBackendFoldersRefreshListener.kt | 2 +- .../com/fsck/k9/preferences/KoinModule.kt | 2 +- .../fsck/k9/preferences/SettingsExporter.kt | 17 +- .../k9/preferences/SettingsExporterTest.kt | 19 +- .../mailstore/DefaultFolderQueryRepository.kt | 106 +++++ .../mailstore/DefaultFolderRepository.kt | 60 --- .../DefaultFolderQueryRepositoryTest.kt | 405 ++++++++++++++++++ .../fsck/k9/ui/messagedetails/KoinModule.kt | 2 +- .../messagedetails/MessageDetailsViewModel.kt | 8 +- 29 files changed, 674 insertions(+), 201 deletions(-) rename legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/FolderRepository.kt => feature/mail/folder/api/src/commonMain/kotlin/net/thunderbird/feature/mail/folder/api/data/repository/FolderQueryRepository.kt (51%) create mode 100644 feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeFolderQueryRepository.kt delete mode 100644 feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeFolderRepository.kt create mode 100644 legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/DefaultFolderQueryRepository.kt delete mode 100644 legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/DefaultFolderRepository.kt create mode 100644 legacy/mailstore/src/test/java/app/k9mail/legacy/mailstore/DefaultFolderQueryRepositoryTest.kt diff --git a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/FolderRepository.kt b/feature/mail/folder/api/src/commonMain/kotlin/net/thunderbird/feature/mail/folder/api/data/repository/FolderQueryRepository.kt similarity index 51% rename from legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/FolderRepository.kt rename to feature/mail/folder/api/src/commonMain/kotlin/net/thunderbird/feature/mail/folder/api/data/repository/FolderQueryRepository.kt index 3e7e5e7d46c..d659054b690 100644 --- a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/FolderRepository.kt +++ b/feature/mail/folder/api/src/commonMain/kotlin/net/thunderbird/feature/mail/folder/api/data/repository/FolderQueryRepository.kt @@ -1,25 +1,19 @@ -package app.k9mail.legacy.mailstore +package net.thunderbird.feature.mail.folder.api.data.repository +import net.thunderbird.core.outcome.Outcome import net.thunderbird.feature.account.AccountId import net.thunderbird.feature.mail.folder.api.Folder -import net.thunderbird.feature.mail.folder.api.data.repository.PushFolderTrackingRepository +import net.thunderbird.feature.mail.folder.api.FolderServerId +import net.thunderbird.feature.mail.folder.api.data.FolderError -/** - * Provides access to local and remote folder information and management. - * - * TODO: Refactor FolderRepository into focused contracts. - * Problem: This interface mixes unrelated responsibilities (read models, remote listing, push tracking, - * and per-flag mutations), which makes it hard to test and evolve. - */ -@Suppress("TooManyFunctions") -interface FolderRepository : PushFolderTrackingRepository { +interface FolderQueryRepository { /** * Returns a [Folder] for the given [accountId] and [folderId]. * * @param accountId The account identifier. * @param folderId The folder identifier. */ - suspend fun getFolder(accountId: AccountId, folderId: Long): Folder? + suspend fun findById(accountId: AccountId, folderId: Long): Outcome /** * Returns the server ID for the given [accountId] and [folderId]. @@ -27,7 +21,7 @@ interface FolderRepository : PushFolderTrackingRepository { * @param accountId The account identifier. * @param folderId The folder identifier. */ - fun getFolderServerId(accountId: AccountId, folderId: Long): String? + suspend fun findFolderServerIdById(accountId: AccountId, folderId: Long): Outcome /** * Returns the folder ID for the given [accountId] and [folderServerId]. @@ -35,7 +29,7 @@ interface FolderRepository : PushFolderTrackingRepository { * @param accountId The account identifier. * @param folderServerId The folder server identifier. */ - fun getFolderId(accountId: AccountId, folderServerId: String): Long? + suspend fun findIdByServerId(accountId: AccountId, folderServerId: FolderServerId): Outcome /** * Returns `true` if the folder with [folderId] is present for the given [accountId]. @@ -43,5 +37,5 @@ interface FolderRepository : PushFolderTrackingRepository { * @param accountId The account identifier. * @param folderId The folder identifier. */ - fun isFolderPresent(accountId: AccountId, folderId: Long): Boolean + suspend fun isPresent(accountId: AccountId, folderId: Long): Boolean } diff --git a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/LoadFolderInformationSideEffect.kt b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/LoadFolderInformationSideEffect.kt index c7216280e8e..a5503595c75 100644 --- a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/LoadFolderInformationSideEffect.kt +++ b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/LoadFolderInformationSideEffect.kt @@ -1,7 +1,6 @@ package net.thunderbird.feature.mail.message.list.internal.ui.state.sideeffect import androidx.compose.ui.graphics.Color -import app.k9mail.legacy.mailstore.FolderRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first import net.thunderbird.core.logging.Logger @@ -10,6 +9,7 @@ import net.thunderbird.feature.account.AccountId import net.thunderbird.feature.account.UnifiedAccountId import net.thunderbird.feature.account.profile.AccountProfileRepository import net.thunderbird.feature.mail.folder.api.FolderType +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository import net.thunderbird.feature.mail.folder.api.data.repository.RemoteFolderQueryRepository import net.thunderbird.feature.mail.message.list.ui.effect.MessageListEffect import net.thunderbird.feature.mail.message.list.ui.event.FolderEvent @@ -27,7 +27,7 @@ internal class LoadFolderInformationSideEffect( private val folderId: Long?, dispatch: suspend (MessageListEvent) -> Unit, private val logger: Logger, - private val folderRepository: FolderRepository, + private val folderQueryRepository: FolderQueryRepository, private val remoteFolderQueryRepository: RemoteFolderQueryRepository, private val profileRepository: AccountProfileRepository, ) : MessageListStateSideEffectHandler(logger, dispatch) { @@ -64,7 +64,9 @@ internal class LoadFolderInformationSideEffect( private suspend fun consumeSingleAccountFolder(folderId: Long): ConsumeResult { val accountId = accountIds.first() - val folder = folderRepository.getFolder(accountId, folderId) + val folder = folderQueryRepository + .findById(accountId, folderId) + .fold(onSuccess = { it }, onFailure = { null }) return if (folder != null) { val remoteFolder = if (!folder.isLocalOnly) { remoteFolderQueryRepository @@ -97,7 +99,7 @@ internal class LoadFolderInformationSideEffect( private val accountIds: Set, private val folderId: Long?, private val logger: Logger, - private val folderRepository: FolderRepository, + private val folderQueryRepository: FolderQueryRepository, private val remoteFolderQueryRepository: RemoteFolderQueryRepository, private val profileRepository: AccountProfileRepository, ) : MessageListStateSideEffectHandlerFactory { @@ -110,7 +112,7 @@ internal class LoadFolderInformationSideEffect( folderId = folderId, dispatch = dispatch, logger = logger, - folderRepository = folderRepository, + folderQueryRepository = folderQueryRepository, remoteFolderQueryRepository = remoteFolderQueryRepository, profileRepository = profileRepository, ) diff --git a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/inject/MessageListSideEffectsModule.kt b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/inject/MessageListSideEffectsModule.kt index bc4dc18c94f..47128031ee3 100644 --- a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/inject/MessageListSideEffectsModule.kt +++ b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/inject/MessageListSideEffectsModule.kt @@ -53,7 +53,7 @@ internal val messageListSideEffectsModule = module { accountIds = args.accountIds, folderId = args.folderId, logger = get(), - folderRepository = get(), + folderQueryRepository = get(), remoteFolderQueryRepository = get(), profileRepository = get(), ) diff --git a/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeFolderQueryRepository.kt b/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeFolderQueryRepository.kt new file mode 100644 index 00000000000..6e9027d255f --- /dev/null +++ b/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeFolderQueryRepository.kt @@ -0,0 +1,27 @@ +package net.thunderbird.feature.mail.message.list.internal.fakes + +import net.thunderbird.core.outcome.Outcome +import net.thunderbird.feature.account.AccountId +import net.thunderbird.feature.mail.folder.api.Folder +import net.thunderbird.feature.mail.folder.api.FolderServerId +import net.thunderbird.feature.mail.folder.api.data.FolderError +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository + +class FakeFolderQueryRepository( + private val localFolders: Map> = emptyMap(), +) : FolderQueryRepository { + override suspend fun findById(accountId: AccountId, folderId: Long): Outcome = + Outcome.success(localFolders[accountId]?.find { it.id == folderId }) + + override suspend fun findFolderServerIdById( + accountId: AccountId, + folderId: Long, + ): Outcome = error("Not implemented") + + override suspend fun findIdByServerId( + accountId: AccountId, + folderServerId: FolderServerId, + ): Outcome = error("Not implemented") + + override suspend fun isPresent(accountId: AccountId, folderId: Long): Boolean = error("Not implemented") +} diff --git a/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeFolderRepository.kt b/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeFolderRepository.kt deleted file mode 100644 index 8fdf1352631..00000000000 --- a/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeFolderRepository.kt +++ /dev/null @@ -1,30 +0,0 @@ -package net.thunderbird.feature.mail.message.list.internal.fakes - -import app.k9mail.legacy.mailstore.FolderRepository -import kotlinx.coroutines.flow.Flow -import net.thunderbird.core.outcome.Outcome -import net.thunderbird.feature.account.AccountId -import net.thunderbird.feature.mail.folder.api.Folder -import net.thunderbird.feature.mail.folder.api.data.FolderError - -class FakeFolderRepository( - private val localFolders: Map> = emptyMap(), -) : FolderRepository { - override suspend fun getFolder( - accountId: AccountId, - folderId: Long, - ): Folder? = localFolders[accountId]?.find { it.id == folderId } - - override fun getFolderServerId(accountId: AccountId, folderId: Long): String? = error("Not implemented") - - override fun getFolderId(accountId: AccountId, folderServerId: String): Long? = error("Not implemented") - - override fun isFolderPresent(accountId: AccountId, folderId: Long): Boolean = error("Not implemented") - - override fun observeEnabled(accountId: AccountId): Flow> = - error("Not implemented") - - override suspend fun isEnabled(accountId: AccountId): Outcome = error("Not implemented") - - override suspend fun disable(accountId: AccountId): Outcome = error("Not implemented") -} diff --git a/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/LoadFolderInformationSideEffectTest.kt b/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/LoadFolderInformationSideEffectTest.kt index 9c3fe5630ed..114eca3cc1d 100644 --- a/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/LoadFolderInformationSideEffectTest.kt +++ b/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/state/sideeffect/LoadFolderInformationSideEffectTest.kt @@ -4,7 +4,6 @@ package net.thunderbird.feature.mail.message.list.internal.ui.state.sideeffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import app.k9mail.legacy.mailstore.FolderRepository import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isEmpty @@ -30,8 +29,9 @@ import net.thunderbird.feature.account.profile.AccountProfile import net.thunderbird.feature.account.profile.AccountProfileRepository import net.thunderbird.feature.mail.folder.api.FolderType import net.thunderbird.feature.mail.folder.api.RemoteFolder +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository import net.thunderbird.feature.mail.folder.api.data.repository.RemoteFolderQueryRepository -import net.thunderbird.feature.mail.message.list.internal.fakes.FakeFolderRepository +import net.thunderbird.feature.mail.message.list.internal.fakes.FakeFolderQueryRepository import net.thunderbird.feature.mail.message.list.internal.fakes.FakeRemoteFolderQueryRepository import net.thunderbird.feature.mail.message.list.internal.fakes.RecordingSuspendFunction import net.thunderbird.feature.mail.message.list.ui.event.FolderEvent @@ -52,7 +52,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { val testSubject = createTestSubject( accountIds = setOf(accountId), folderId = folderId, - folderRepository = createFolderRepository( + folderQueryRepository = createFolderQueryRepository( accountId = accountId, folderId = folderId, folder = folder, @@ -151,7 +151,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { accountIds = setOf(firstAccountId, secondAccountId), folderId = folderId, dispatch = dispatch.function, - folderRepository = createFolderRepository( + folderQueryRepository = createFolderQueryRepository( accountId = firstAccountId, folderId = folderId, folder = folder, @@ -192,7 +192,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { val folderId = 7L val folder = createMailFolder(id = folderId, name = "Local", isLocalOnly = true) val dispatch = RecordingSuspendFunction() - val folderRepository = createFolderRepository( + val folderQueryRepository = createFolderQueryRepository( accountId = accountId, folderId = folderId, folder = folder, @@ -202,7 +202,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { accountIds = setOf(accountId), folderId = folderId, dispatch = dispatch.function, - folderRepository = folderRepository, + folderQueryRepository = folderQueryRepository, profileRepository = FakeAccountProfileRepository( profiles = listOf(createAccountProfile(accountId = accountId, expectedColor)), ), @@ -241,7 +241,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { type = FolderType.INBOX, ) val dispatch = RecordingSuspendFunction() - val folderRepository = createFolderRepository( + val folderQueryRepository = createFolderQueryRepository( accountId = accountId, folderId = folderId, folder = folder, @@ -255,7 +255,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { accountIds = setOf(accountId), folderId = folderId, dispatch = dispatch.function, - folderRepository = folderRepository, + folderQueryRepository = folderQueryRepository, remoteFolderQueryRepository = remoteFolderQueryRepository, profileRepository = FakeAccountProfileRepository( profiles = listOf(createAccountProfile(accountId = accountId, expectedColor)), @@ -294,7 +294,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { accountIds = setOf(accountId), folderId = folderId, dispatch = dispatch.function, - folderRepository = createFolderRepository( + folderQueryRepository = createFolderQueryRepository( accountId = accountId, folderId = folderId, folder = folder, @@ -337,7 +337,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { val accountId = AccountIdFactory.create() val folderId = 10L val dispatch = RecordingSuspendFunction() - val folderRepository = createFolderRepository( + val folderQueryRepository = createFolderQueryRepository( accountId = accountId, folderId = folderId, folder = null, @@ -346,7 +346,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { accountIds = setOf(accountId), folderId = folderId, dispatch = dispatch.function, - folderRepository = folderRepository, + folderQueryRepository = folderQueryRepository, ) // Act @@ -374,7 +374,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { type = FolderType.INBOX, ) val dispatch = RecordingSuspendFunction() - val folderRepository = createFolderRepository( + val folderQueryRepository = createFolderQueryRepository( accountId = accountId, folderId = folderId, folder = folder, @@ -388,7 +388,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { accountIds = setOf(accountId), folderId = folderId, dispatch = dispatch.function, - folderRepository = folderRepository, + folderQueryRepository = folderQueryRepository, remoteFolderQueryRepository = remoteFolderQueryRepository, profileRepository = FakeAccountProfileRepository( profiles = listOf(createAccountProfile(accountId = accountId, expectedColor)), @@ -422,7 +422,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { accountIds = setOf(AccountIdFactory.create()), folderId = 1L, logger = TestLogger(), - folderRepository = createFolderRepository( + folderQueryRepository = createFolderQueryRepository( accountId = AccountIdFactory.create(), folderId = 1L, folder = null, @@ -447,7 +447,7 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { folderId: Long? = 1L, dispatch: suspend (MessageListEvent) -> Unit = {}, logger: Logger = TestLogger(), - folderRepository: FolderRepository = FakeFolderRepository(), + folderQueryRepository: FolderQueryRepository = FakeFolderQueryRepository(), remoteFolderQueryRepository: RemoteFolderQueryRepository = FakeRemoteFolderQueryRepository(), profileRepository: AccountProfileRepository = FakeAccountProfileRepository( profiles = accountIds.map(::createAccountProfile), @@ -457,16 +457,16 @@ class LoadFolderInformationSideEffectTest : BaseSideEffectHandlerTest() { folderId = folderId, dispatch = dispatch, logger = logger, - folderRepository = folderRepository, + folderQueryRepository = folderQueryRepository, remoteFolderQueryRepository = remoteFolderQueryRepository, profileRepository = profileRepository, ) - private fun createFolderRepository( + private fun createFolderQueryRepository( accountId: AccountId, folderId: Long, folder: MailFolder?, - ): FolderRepository = FakeFolderRepository( + ): FolderQueryRepository = FakeFolderQueryRepository( localFolders = mapOf(accountId to listOfNotNull(folder?.copy(id = folderId))), ) diff --git a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/KoinModule.kt b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/KoinModule.kt index 336d4fedaf7..587997cf665 100644 --- a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/KoinModule.kt +++ b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/KoinModule.kt @@ -16,7 +16,7 @@ val unreadWidgetModule = module { preferences = get(), messageCountsProvider = get(), defaultFolderProvider = get(), - folderRepository = get(), + folderQueryRepository = get(), folderNameFormatter = get(), coreResourceProvider = get(), logger = get(), @@ -34,5 +34,5 @@ val unreadWidgetModule = module { logger = get(), ) } - single { UnreadWidgetMigrations(accountRepository = get(), folderRepository = get()) } + single { UnreadWidgetMigrations(accountRepository = get(), folderQueryRepository = get()) } } diff --git a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProvider.kt b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProvider.kt index 85e6133e19b..d19458300f6 100644 --- a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProvider.kt +++ b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProvider.kt @@ -2,7 +2,6 @@ package app.k9mail.feature.widget.unread import android.content.Context import android.content.Intent -import app.k9mail.legacy.mailstore.FolderRepository import app.k9mail.legacy.message.controller.MessageCountsProvider import app.k9mail.legacy.ui.folder.FolderNameFormatter import com.fsck.k9.CoreResourceProvider @@ -13,6 +12,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.thunderbird.core.android.account.LegacyAccountDto import net.thunderbird.core.logging.Logger +import net.thunderbird.core.outcome.fold +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository import net.thunderbird.feature.search.legacy.LocalMessageSearch import net.thunderbird.feature.search.legacy.SearchAccount @@ -24,7 +25,7 @@ class UnreadWidgetDataProvider( private val preferences: Preferences, private val messageCountsProvider: MessageCountsProvider, private val defaultFolderProvider: DefaultFolderProvider, - private val folderRepository: FolderRepository, + private val folderQueryRepository: FolderQueryRepository, private val folderNameFormatter: FolderNameFormatter, private val coreResourceProvider: CoreResourceProvider, private val logger: Logger, @@ -103,7 +104,8 @@ class UnreadWidgetDataProvider( } private suspend fun getFolderDisplayName(account: LegacyAccountDto, folderId: Long): String { - val folder = folderRepository.getFolder(account.id, folderId) + val folder = folderQueryRepository.findById(account.id, folderId) + .fold(onSuccess = { it }, onFailure = { null }) return if (folder != null) { folderNameFormatter.displayName(folder) } else { diff --git a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetMigrations.kt b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetMigrations.kt index b53d7201d3c..0078677d4e7 100644 --- a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetMigrations.kt +++ b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetMigrations.kt @@ -4,14 +4,16 @@ import android.content.SharedPreferences import androidx.core.content.edit import app.k9mail.feature.widget.unread.UnreadWidgetRepository.Companion.PREFS_VERSION import app.k9mail.feature.widget.unread.UnreadWidgetRepository.Companion.PREF_VERSION_KEY -import app.k9mail.legacy.mailstore.FolderRepository import com.fsck.k9.Preferences +import net.thunderbird.core.outcome.fold +import net.thunderbird.feature.mail.folder.api.FolderServerId +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository internal class UnreadWidgetMigrations( private val accountRepository: Preferences, - private val folderRepository: FolderRepository, + private val folderQueryRepository: FolderQueryRepository, ) { - fun upgradePreferences(preferences: SharedPreferences, version: Int) { + suspend fun upgradePreferences(preferences: SharedPreferences, version: Int) { if (version < 2) rewriteFolderNameToFolderId(preferences) preferences.setVersion(PREFS_VERSION) @@ -22,7 +24,7 @@ internal class UnreadWidgetMigrations( } @Suppress("LoopWithTooManyJumpStatements") - private fun rewriteFolderNameToFolderId(preferences: SharedPreferences) { + private suspend fun rewriteFolderNameToFolderId(preferences: SharedPreferences) { val widgetIds = preferences.all.keys .filter { it.endsWith(".folder_name") } .map { it.split(".")[1] } @@ -34,7 +36,8 @@ internal class UnreadWidgetMigrations( val folderServerId = preferences.getString("unread_widget.$widgetId.folder_name", null) if (folderServerId != null) { - val folderId = folderRepository.getFolderId(account.id, folderServerId) + val folderId = folderQueryRepository.findIdByServerId(account.id, FolderServerId(folderServerId)) + .fold(onSuccess = { it }, onFailure = { null }) putString("unread_widget.$widgetId.folder_id", folderId?.toString()) } diff --git a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetRepository.kt b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetRepository.kt index 73e28712f67..22bf18de749 100644 --- a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetRepository.kt +++ b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetRepository.kt @@ -34,7 +34,7 @@ internal class UnreadWidgetRepository( return dataRetriever.loadUnreadWidgetData(configuration) } - private fun upgradePreferences(version: Int, preferences: SharedPreferences) { + private suspend fun upgradePreferences(version: Int, preferences: SharedPreferences) { if (version > PREFS_VERSION) { error("UnreadWidgetRepository: Version downgrades are not supported") } else { diff --git a/feature/widget/unread/src/test/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProviderTest.kt b/feature/widget/unread/src/test/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProviderTest.kt index 560b0b17414..02c517391bd 100644 --- a/feature/widget/unread/src/test/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProviderTest.kt +++ b/feature/widget/unread/src/test/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProviderTest.kt @@ -1,7 +1,6 @@ package app.k9mail.feature.widget.unread import android.content.Context -import app.k9mail.legacy.mailstore.FolderRepository import app.k9mail.legacy.message.controller.MessageCounts import app.k9mail.legacy.message.controller.MessageCountsProvider import app.k9mail.legacy.ui.folder.FolderNameFormatter @@ -15,8 +14,13 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.test.runTest import net.thunderbird.core.android.account.LegacyAccountDto import net.thunderbird.core.logging.testing.TestLogger +import net.thunderbird.core.outcome.Outcome +import net.thunderbird.feature.account.AccountId import net.thunderbird.feature.mail.folder.api.Folder +import net.thunderbird.feature.mail.folder.api.FolderServerId import net.thunderbird.feature.mail.folder.api.FolderType +import net.thunderbird.feature.mail.folder.api.data.FolderError +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository import net.thunderbird.feature.search.legacy.LocalMessageSearch import net.thunderbird.feature.search.legacy.SearchAccount import org.junit.Before @@ -37,7 +41,7 @@ class UnreadWidgetDataProviderTest : AutoCloseKoinTest() { private val preferences = createPreferences() private val messageCountsProvider = createMessageCountsProvider() private val defaultFolderStrategy = createDefaultFolderStrategy() - private val folderRepository = createFolderRepository() + private val folderQueryRepository = createFolderQueryRepository() private val folderNameFormatter = createFolderNameFormatter() private val coreResourceProvider = createCoreResourceProvider() private val provider = UnreadWidgetDataProvider( @@ -45,7 +49,7 @@ class UnreadWidgetDataProviderTest : AutoCloseKoinTest() { preferences, messageCountsProvider, defaultFolderStrategy, - folderRepository, + folderQueryRepository, folderNameFormatter, coreResourceProvider, logger = TestLogger(), @@ -158,10 +162,23 @@ class UnreadWidgetDataProviderTest : AutoCloseKoinTest() { on { getDefaultFolder(account) } doReturn FOLDER_ID } - private fun createFolderRepository(): FolderRepository { - return mock { - on { getFolder(account.id, FOLDER_ID) } doReturn FOLDER + private fun createFolderQueryRepository(): FolderQueryRepository = object : FolderQueryRepository { + override suspend fun findById(accountId: AccountId, folderId: Long): Outcome { + val folder = FOLDER.takeIf { accountId == account.id && folderId == FOLDER_ID } + return Outcome.success(folder) } + + override suspend fun findFolderServerIdById( + accountId: AccountId, + folderId: Long, + ): Outcome = Outcome.success(null) + + override suspend fun findIdByServerId( + accountId: AccountId, + folderServerId: FolderServerId, + ): Outcome = Outcome.success(null) + + override suspend fun isPresent(accountId: AccountId, folderId: Long): Boolean = false } private fun createFolderNameFormatter(): FolderNameFormatter = mock { diff --git a/legacy/core/src/main/java/com/fsck/k9/controller/push/AccountBackendPusherCallback.kt b/legacy/core/src/main/java/com/fsck/k9/controller/push/AccountBackendPusherCallback.kt index bfe06508deb..85673cbf72a 100644 --- a/legacy/core/src/main/java/com/fsck/k9/controller/push/AccountBackendPusherCallback.kt +++ b/legacy/core/src/main/java/com/fsck/k9/controller/push/AccountBackendPusherCallback.kt @@ -1,18 +1,18 @@ package com.fsck.k9.controller.push -import app.k9mail.legacy.mailstore.FolderRepository import com.fsck.k9.backend.api.BackendPusherCallback import com.fsck.k9.controller.MessagingController import net.thunderbird.core.android.account.LegacyAccountDtoManager import net.thunderbird.core.logging.Logger import net.thunderbird.feature.account.AccountId +import net.thunderbird.feature.mail.folder.api.data.repository.PushFolderTrackingRepository private const val TAG = "AccountBackendPusherCallback" class AccountBackendPusherCallback( private val accountManager: LegacyAccountDtoManager, private val messagingController: MessagingController, - private val folderRepository: FolderRepository, + private val pushFolderTrackingRepository: PushFolderTrackingRepository, private val accountId: AccountId, private val logger: Logger, ) : BackendPusherCallback { @@ -28,6 +28,6 @@ class AccountBackendPusherCallback( override suspend fun onPushNotSupported() { logger.verbose(TAG) { "Push not supported for account $accountId. Disabling push." } - folderRepository.disable(accountId) + pushFolderTrackingRepository.disable(accountId) } } diff --git a/legacy/core/src/main/java/com/fsck/k9/controller/push/AccountPushControllerFactory.kt b/legacy/core/src/main/java/com/fsck/k9/controller/push/AccountPushControllerFactory.kt index 6c9059b7ee1..9044f878104 100644 --- a/legacy/core/src/main/java/com/fsck/k9/controller/push/AccountPushControllerFactory.kt +++ b/legacy/core/src/main/java/com/fsck/k9/controller/push/AccountPushControllerFactory.kt @@ -1,18 +1,18 @@ package com.fsck.k9.controller.push -import app.k9mail.legacy.mailstore.FolderRepository import com.fsck.k9.backend.BackendManager import com.fsck.k9.controller.MessagingController import net.thunderbird.core.android.account.LegacyAccountDtoManager import net.thunderbird.core.logging.Logger import net.thunderbird.feature.account.AccountId +import net.thunderbird.feature.mail.folder.api.data.repository.PushFolderTrackingRepository import net.thunderbird.feature.mail.folder.api.data.repository.PushFoldersQueryRepository internal class AccountPushControllerFactory( private val accountManager: LegacyAccountDtoManager, private val backendManager: BackendManager, private val messagingController: MessagingController, - private val folderRepository: FolderRepository, + private val pushFolderTrackingRepository: PushFolderTrackingRepository, private val pushFoldersQueryRepository: PushFoldersQueryRepository, private val logger: Logger, ) { @@ -23,7 +23,7 @@ internal class AccountPushControllerFactory( backendPusherCallback = AccountBackendPusherCallback( accountManager = accountManager, messagingController = messagingController, - folderRepository = folderRepository, + pushFolderTrackingRepository = pushFolderTrackingRepository, accountId = accountId, logger = logger, ), diff --git a/legacy/core/src/main/java/com/fsck/k9/controller/push/KoinModule.kt b/legacy/core/src/main/java/com/fsck/k9/controller/push/KoinModule.kt index c7edfc16eb5..4aadee66577 100644 --- a/legacy/core/src/main/java/com/fsck/k9/controller/push/KoinModule.kt +++ b/legacy/core/src/main/java/com/fsck/k9/controller/push/KoinModule.kt @@ -11,7 +11,7 @@ internal val controllerPushModule = module { accountManager = get(), backendManager = get(), messagingController = get(), - folderRepository = get(), + pushFolderTrackingRepository = get(), pushFoldersQueryRepository = get(), logger = get(), ) @@ -28,7 +28,7 @@ internal val controllerPushModule = module { pushNotificationManager = get(), connectivityManager = get(), accountPushControllerFactory = get(), - folderRepository = get(), + pushFolderTrackingRepository = get(), ) } diff --git a/legacy/core/src/main/java/com/fsck/k9/controller/push/PushController.kt b/legacy/core/src/main/java/com/fsck/k9/controller/push/PushController.kt index 11609a16432..21f033593af 100644 --- a/legacy/core/src/main/java/com/fsck/k9/controller/push/PushController.kt +++ b/legacy/core/src/main/java/com/fsck/k9/controller/push/PushController.kt @@ -1,6 +1,5 @@ package com.fsck.k9.controller.push -import app.k9mail.legacy.mailstore.FolderRepository import com.fsck.k9.backend.BackendManager import com.fsck.k9.helper.mapToSet import com.fsck.k9.notification.PushNotificationManager @@ -33,6 +32,7 @@ import net.thunderbird.core.preference.BackgroundSync import net.thunderbird.core.preference.GeneralSettingsManager import net.thunderbird.feature.account.AccountId import net.thunderbird.feature.account.AccountIdFactory +import net.thunderbird.feature.mail.folder.api.data.repository.PushFolderTrackingRepository import net.thunderbird.legacy.logging.Log /** @@ -50,7 +50,7 @@ class PushController internal constructor( private val pushNotificationManager: PushNotificationManager, private val connectivityManager: ConnectivityManager, private val accountPushControllerFactory: AccountPushControllerFactory, - private val folderRepository: FolderRepository, + private val pushFolderTrackingRepository: PushFolderTrackingRepository, private val coroutineScope: CoroutineScope = GlobalScope, private val coroutineDispatcher: CoroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher(), ) { @@ -91,7 +91,7 @@ class PushController internal constructor( coroutineScope.launch(coroutineDispatcher) { for (account in accountManager.getAccounts()) { - folderRepository.disable(account.id) + pushFolderTrackingRepository.disable(account.id) } } } @@ -261,7 +261,7 @@ class PushController internal constructor( return getPushCapableAccounts() .asFlow() .filter { account -> - val outcome = folderRepository.isEnabled(account.id) + val outcome = pushFolderTrackingRepository.isEnabled(account.id) outcome.fold(onSuccess = { it }, onFailure = { false }) } .toSet() @@ -330,7 +330,7 @@ class PushController internal constructor( for (account in newAccounts) { pushEnabledCollectorJobs[account.uuid] = coroutineScope.launch(coroutineDispatcher) { Log.v("..Starting to listen for push enabled changes in account: %s", account.uuid) - folderRepository.observeEnabled(account.id) + pushFolderTrackingRepository.observeEnabled(account.id) .collect { updatePushers() } diff --git a/legacy/core/src/main/java/com/fsck/k9/mailstore/AutoExpandFolderBackendFoldersRefreshListener.kt b/legacy/core/src/main/java/com/fsck/k9/mailstore/AutoExpandFolderBackendFoldersRefreshListener.kt index 8df3684b422..b31ed3879cd 100644 --- a/legacy/core/src/main/java/com/fsck/k9/mailstore/AutoExpandFolderBackendFoldersRefreshListener.kt +++ b/legacy/core/src/main/java/com/fsck/k9/mailstore/AutoExpandFolderBackendFoldersRefreshListener.kt @@ -1,9 +1,11 @@ package com.fsck.k9.mailstore -import app.k9mail.legacy.mailstore.FolderRepository import net.thunderbird.core.android.account.LegacyAccount import net.thunderbird.core.android.account.LegacyAccountManager +import net.thunderbird.core.outcome.fold import net.thunderbird.feature.account.AccountId +import net.thunderbird.feature.mail.folder.api.FolderServerId +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository /** * Update an Account's auto-expand folder after the folder list has been refreshed. @@ -11,7 +13,7 @@ import net.thunderbird.feature.account.AccountId class AutoExpandFolderBackendFoldersRefreshListener( private val accountManager: LegacyAccountManager, private val accountId: AccountId, - private val folderRepository: FolderRepository, + private val folderQueryRepository: FolderQueryRepository, ) : BackendFoldersRefreshListener { private var isFirstSync = false @@ -19,7 +21,7 @@ class AutoExpandFolderBackendFoldersRefreshListener( isFirstSync = getAccountById(accountId).inboxFolderId == null } - override fun onAfterFolderListRefresh() { + override suspend fun onAfterFolderListRefresh() { var account = getAccountById(accountId) account = checkAutoExpandFolder(account) @@ -29,21 +31,22 @@ class AutoExpandFolderBackendFoldersRefreshListener( updateAccount(account) } - private fun checkAutoExpandFolder(account: LegacyAccount): LegacyAccount { + private suspend fun checkAutoExpandFolder(account: LegacyAccount): LegacyAccount { var updated = account updated.importedAutoExpandFolder?.let { folderName -> if (folderName.isEmpty()) { updated = updated.copy(autoExpandFolderId = null) } else { - val folderId = folderRepository.getFolderId(accountId, folderName) + val folderId = folderQueryRepository.findIdByServerId(accountId, FolderServerId(folderName)) + .fold(onSuccess = { it }, onFailure = { null }) updated = updated.copy(autoExpandFolderId = folderId) } return updated } updated.autoExpandFolderId?.let { autoExpandFolderId -> - if (!folderRepository.isFolderPresent(accountId, autoExpandFolderId)) { + if (!folderQueryRepository.isPresent(accountId, autoExpandFolderId)) { updated = updated.copy(autoExpandFolderId = null) } } diff --git a/legacy/core/src/main/java/com/fsck/k9/mailstore/BackendFoldersRefreshListener.kt b/legacy/core/src/main/java/com/fsck/k9/mailstore/BackendFoldersRefreshListener.kt index bd2a9f8bd18..6362ac77f0c 100644 --- a/legacy/core/src/main/java/com/fsck/k9/mailstore/BackendFoldersRefreshListener.kt +++ b/legacy/core/src/main/java/com/fsck/k9/mailstore/BackendFoldersRefreshListener.kt @@ -2,5 +2,5 @@ package com.fsck.k9.mailstore interface BackendFoldersRefreshListener { fun onBeforeFolderListRefresh() - fun onAfterFolderListRefresh() + suspend fun onAfterFolderListRefresh() } diff --git a/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorage.kt b/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorage.kt index 22c67e0cdf7..6b3c8a88c89 100644 --- a/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorage.kt +++ b/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorage.kt @@ -6,6 +6,7 @@ import com.fsck.k9.backend.api.BackendFolder import com.fsck.k9.backend.api.BackendFolderUpdater import com.fsck.k9.backend.api.BackendStorage import com.fsck.k9.backend.api.FolderInfo +import kotlinx.coroutines.runBlocking import com.fsck.k9.mail.FolderType as RemoteFolderType class K9BackendStorage( @@ -72,7 +73,7 @@ class K9BackendStorage( } override fun close() { - listeners.forEach { it.onAfterFolderListRefresh() } + listeners.forEach { runBlocking { it.onAfterFolderListRefresh() } } } } } diff --git a/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorageFactory.kt b/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorageFactory.kt index f4cfd24e53e..ff5c89c213c 100644 --- a/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorageFactory.kt +++ b/legacy/core/src/main/java/com/fsck/k9/mailstore/K9BackendStorageFactory.kt @@ -1,17 +1,17 @@ package com.fsck.k9.mailstore -import app.k9mail.legacy.mailstore.FolderRepository import app.k9mail.legacy.mailstore.MessageStoreManager import com.fsck.k9.Preferences import net.thunderbird.backend.api.BackendStorageFactory import net.thunderbird.core.android.account.LegacyAccountManager import net.thunderbird.feature.account.AccountId import net.thunderbird.feature.mail.folder.api.SpecialFolderUpdater +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository class K9BackendStorageFactory( private val preferences: Preferences, private val accountManager: LegacyAccountManager, - private val folderRepository: FolderRepository, + private val folderQueryRepository: FolderQueryRepository, private val messageStoreManager: MessageStoreManager, private val specialFolderUpdaterFactory: SpecialFolderUpdater.Factory, private val saveMessageDataCreator: SaveMessageDataCreator, @@ -24,7 +24,7 @@ class K9BackendStorageFactory( val autoExpandFolderListener = AutoExpandFolderBackendFoldersRefreshListener( accountManager = accountManager, accountId = accountId, - folderRepository = folderRepository, + folderQueryRepository = folderQueryRepository, ) val listeners = listOf(specialFolderListener, autoExpandFolderListener) return K9BackendStorage( diff --git a/legacy/core/src/main/java/com/fsck/k9/mailstore/KoinModule.kt b/legacy/core/src/main/java/com/fsck/k9/mailstore/KoinModule.kt index 7ba1f1a0f6d..3540db92798 100644 --- a/legacy/core/src/main/java/com/fsck/k9/mailstore/KoinModule.kt +++ b/legacy/core/src/main/java/com/fsck/k9/mailstore/KoinModule.kt @@ -1,8 +1,6 @@ package com.fsck.k9.mailstore -import app.k9mail.legacy.mailstore.AggregateRepositories -import app.k9mail.legacy.mailstore.DefaultFolderRepository -import app.k9mail.legacy.mailstore.FolderRepository +import app.k9mail.legacy.mailstore.DefaultFolderQueryRepository import app.k9mail.legacy.mailstore.MessageListRepository import app.k9mail.legacy.mailstore.MessageStoreManager import app.k9mail.legacy.mailstore.folder.DefaultFolderDetailsRepository @@ -19,6 +17,7 @@ import net.thunderbird.backend.api.BackendStorageFactory import net.thunderbird.core.common.cache.TimeLimitedCache import net.thunderbird.feature.mail.folder.api.OutboxFolderManager import net.thunderbird.feature.mail.folder.api.data.repository.FolderDetailsRepository +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository import net.thunderbird.feature.mail.folder.api.data.repository.PushFolderTrackingRepository import net.thunderbird.feature.mail.folder.api.data.repository.PushFoldersQueryRepository import net.thunderbird.feature.mail.folder.api.data.repository.RemoteFolderDetailsRepository @@ -60,14 +59,12 @@ val mailStoreModule = module { single { DefaultRemoteFolderQueryRepository(logger = get(), messageStoreManager = get()) } - single { - DefaultFolderRepository( + single { + DefaultFolderQueryRepository( + logger = get(), accountManager = get(), messageStoreManager = get(), outboxFolderManager = get(), - aggregateRepositories = AggregateRepositories( - pushFolderTrackingRepository = get(), - ), ) } single { MessageViewInfoExtractorFactory(get(), get(), get()) } @@ -77,7 +74,7 @@ val mailStoreModule = module { K9BackendStorageFactory( preferences = get(), accountManager = get(), - folderRepository = get(), + folderQueryRepository = get(), messageStoreManager = get(), specialFolderUpdaterFactory = get(), saveMessageDataCreator = get(), diff --git a/legacy/core/src/main/java/com/fsck/k9/mailstore/SpecialFolderBackendFoldersRefreshListener.kt b/legacy/core/src/main/java/com/fsck/k9/mailstore/SpecialFolderBackendFoldersRefreshListener.kt index b02fb485006..804fff2a482 100644 --- a/legacy/core/src/main/java/com/fsck/k9/mailstore/SpecialFolderBackendFoldersRefreshListener.kt +++ b/legacy/core/src/main/java/com/fsck/k9/mailstore/SpecialFolderBackendFoldersRefreshListener.kt @@ -11,7 +11,7 @@ class SpecialFolderBackendFoldersRefreshListener( override fun onBeforeFolderListRefresh() = Unit - override fun onAfterFolderListRefresh() { + override suspend fun onAfterFolderListRefresh() { specialFolderUpdater.updateSpecialFoldersSync() } } diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt b/legacy/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt index 137858b373c..ab7a31f7a49 100644 --- a/legacy/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt +++ b/legacy/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt @@ -42,7 +42,7 @@ val preferencesModule = module { contentResolver = get(), preferences = get(), folderSettingsProvider = get(), - folderRepository = get(), + folderQueryRepository = get(), notificationSettingsUpdater = get(), filePrefixProvider = get(), ) diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/SettingsExporter.kt b/legacy/core/src/main/java/com/fsck/k9/preferences/SettingsExporter.kt index 53b49686f11..7cd2edd1f29 100644 --- a/legacy/core/src/main/java/com/fsck/k9/preferences/SettingsExporter.kt +++ b/legacy/core/src/main/java/com/fsck/k9/preferences/SettingsExporter.kt @@ -3,7 +3,6 @@ package com.fsck.k9.preferences import android.content.ContentResolver import android.net.Uri import android.util.Xml -import app.k9mail.legacy.mailstore.FolderRepository import com.fsck.k9.Preferences import com.fsck.k9.notification.NotificationSettingsUpdater import com.fsck.k9.preferences.ServerTypeConverter.fromServerSettingsType @@ -14,10 +13,12 @@ import java.text.SimpleDateFormat import java.util.Calendar import java.util.Locale import net.thunderbird.core.android.account.LegacyAccountDto +import net.thunderbird.core.outcome.fold import net.thunderbird.feature.account.storage.legacy.LegacyAccountStorageHandler.Companion.ACCOUNT_DESCRIPTION_KEY import net.thunderbird.feature.account.storage.legacy.LegacyAccountStorageHandler.Companion.IDENTITY_DESCRIPTION_KEY import net.thunderbird.feature.account.storage.legacy.LegacyAccountStorageHandler.Companion.IDENTITY_EMAIL_KEY import net.thunderbird.feature.account.storage.legacy.LegacyAccountStorageHandler.Companion.IDENTITY_NAME_KEY +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository import net.thunderbird.legacy.logging.Log import org.xmlpull.v1.XmlSerializer @@ -25,7 +26,7 @@ class SettingsExporter( private val contentResolver: ContentResolver, private val preferences: Preferences, private val folderSettingsProvider: FolderSettingsProvider, - private val folderRepository: FolderRepository, + private val folderQueryRepository: FolderQueryRepository, private val notificationSettingsUpdater: NotificationSettingsUpdater, private val filePrefixProvider: FilePrefixProvider, ) { @@ -242,7 +243,7 @@ class SettingsExporter( } } - writeFolderNameSettings(account, folderRepository, serializer) + writeFolderNameSettings(account, folderQueryRepository, serializer) serializer.endTag(null, SETTINGS_ELEMENT) @@ -297,19 +298,21 @@ class SettingsExporter( } } - private fun writeFolderNameSettings( + private suspend fun writeFolderNameSettings( account: LegacyAccountDto, - folderRepository: FolderRepository, + folderQueryRepository: FolderQueryRepository, serializer: XmlSerializer, ) { - fun writeFolderNameSetting( + suspend fun writeFolderNameSetting( key: String, folderId: Long?, importedFolderServerId: String?, writeEmptyValue: Boolean = false, ) { val folderServerId = folderId?.let { - folderRepository.getFolderServerId(account.id, folderId) + folderQueryRepository.findFolderServerIdById(account.id, folderId) + .fold(onSuccess = { it }, onFailure = { null }) + ?.serverId } ?: importedFolderServerId if (folderServerId != null) { diff --git a/legacy/core/src/test/java/com/fsck/k9/preferences/SettingsExporterTest.kt b/legacy/core/src/test/java/com/fsck/k9/preferences/SettingsExporterTest.kt index 85fc7017d16..cab987bd7d9 100644 --- a/legacy/core/src/test/java/com/fsck/k9/preferences/SettingsExporterTest.kt +++ b/legacy/core/src/test/java/com/fsck/k9/preferences/SettingsExporterTest.kt @@ -1,6 +1,5 @@ package com.fsck.k9.preferences -import app.k9mail.legacy.mailstore.FolderRepository import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isNotNull @@ -8,6 +7,8 @@ import assertk.assertions.isNull import com.fsck.k9.K9RobolectricTest import com.fsck.k9.Preferences import java.io.ByteArrayOutputStream +import kotlinx.coroutines.test.runTest +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository import kotlinx.coroutines.runBlocking import org.jdom2.Document import org.jdom2.input.SAXBuilder @@ -20,52 +21,52 @@ class SettingsExporterTest : K9RobolectricTest() { private val contentResolver = RuntimeEnvironment.getApplication().contentResolver private val preferences: Preferences by inject() private val folderSettingsProvider: FolderSettingsProvider by inject() - private val folderRepository: FolderRepository by inject() + private val folderQueryRepository: FolderQueryRepository by inject() private val settingsExporter = SettingsExporter( contentResolver, preferences, folderSettingsProvider, - folderRepository, + folderQueryRepository, notificationSettingsUpdater = mock(), filePrefixProvider = mock(), ) @Test - fun exportPreferences_producesXML() { + fun exportPreferences_producesXML() = runTest { val document = exportPreferences(false, emptySet()) assertThat(document.rootElement.name).isEqualTo("k9settings") } @Test - fun exportPreferences_setsVersionToLatest() { + fun exportPreferences_setsVersionToLatest() = runTest { val document = exportPreferences(false, emptySet()) assertThat(document.rootElement.getAttributeValue("version")).isEqualTo(Settings.VERSION.toString()) } @Test - fun exportPreferences_setsFormatTo1() { + fun exportPreferences_setsFormatTo1() = runTest { val document = exportPreferences(false, emptySet()) assertThat(document.rootElement.getAttributeValue("format")).isEqualTo("1") } @Test - fun exportPreferences_exportsGlobalSettingsWhenRequested() { + fun exportPreferences_exportsGlobalSettingsWhenRequested() = runTest { val document = exportPreferences(true, emptySet()) assertThat(document.rootElement.getChild("global")).isNotNull() } @Test - fun exportPreferences_ignoresGlobalSettingsWhenRequested() { + fun exportPreferences_ignoresGlobalSettingsWhenRequested() = runTest { val document = exportPreferences(false, emptySet()) assertThat(document.rootElement.getChild("global")).isNull() } - private fun exportPreferences(globalSettings: Boolean, accounts: Set): Document = runBlocking { + private suspend fun exportPreferences(globalSettings: Boolean, accounts: Set): Document = runBlocking { ByteArrayOutputStream().use { outputStream -> settingsExporter.exportPreferences(outputStream, globalSettings, accounts, includePasswords = false) parseXml(outputStream.toByteArray()) diff --git a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/DefaultFolderQueryRepository.kt b/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/DefaultFolderQueryRepository.kt new file mode 100644 index 00000000000..d5d3a3d79ce --- /dev/null +++ b/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/DefaultFolderQueryRepository.kt @@ -0,0 +1,106 @@ +package app.k9mail.legacy.mailstore + +import app.k9mail.legacy.mailstore.folder.extension.getFolderType +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.withContext +import net.thunderbird.core.android.account.LegacyAccount +import net.thunderbird.core.android.account.LegacyAccountManager +import net.thunderbird.core.logging.Logger +import net.thunderbird.core.outcome.Outcome +import net.thunderbird.feature.account.AccountId +import net.thunderbird.feature.mail.folder.api.Folder +import net.thunderbird.feature.mail.folder.api.FolderServerId +import net.thunderbird.feature.mail.folder.api.OutboxFolderManager +import net.thunderbird.feature.mail.folder.api.data.FolderError +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository + +private const val LOG_ID = "[repository][folder-query]" + +class DefaultFolderQueryRepository( + private val logger: Logger, + private val accountManager: LegacyAccountManager, + private val messageStoreManager: MessageStoreManager, + private val outboxFolderManager: OutboxFolderManager, + private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, +) : FolderQueryRepository { + private suspend fun getAccountById(accountId: AccountId): LegacyAccount? = + accountManager.getById(accountId).firstOrNull() + + override suspend fun findById( + accountId: AccountId, + folderId: Long, + ): Outcome = withContext(ioDispatcher) { + logger.verbose { "$LOG_ID getting folder by id '$folderId' from account '$accountId'" } + val account = getAccountById(accountId) + ?: return@withContext Outcome.failure(FolderError.AccountNotFound) + val messageStore = createMessageStore(accountId) + ?: return@withContext Outcome.failure(FolderError.AccountNotFound) + val outboxFolderId = outboxFolderManager.getOutboxFolderId(accountId) + logger.verbose { "$LOG_ID found outbox folder id '$outboxFolderId' for account '$accountId'" } + val folder = messageStore.getFolder(folderId) { folder -> + Folder( + id = folder.id, + name = folder.name, + type = folder.getFolderType(account, outboxFolderId), + isLocalOnly = folder.isLocalOnly, + ) + } + logger.verbose { "$LOG_ID found folder: $folder" } + + Outcome.success(folder) + } + + override suspend fun findFolderServerIdById( + accountId: AccountId, + folderId: Long, + ): Outcome = withContext(ioDispatcher) { + logger.verbose { "$LOG_ID getting folder server_id by id '$folderId' from account '$accountId'" } + val messageStore = createMessageStore(accountId) + ?: return@withContext Outcome.failure(FolderError.AccountNotFound) + val serverId = messageStore.getFolder(folderId) { folder -> + folder.serverId?.let(::FolderServerId) + } + logger.verbose { "$LOG_ID found folder server_id: '${serverId?.serverId}'" } + + Outcome.success(serverId) + } + + override suspend fun findIdByServerId( + accountId: AccountId, + folderServerId: FolderServerId, + ): Outcome = withContext(ioDispatcher) { + logger.verbose { "$LOG_ID getting folder id by server_id '$folderServerId' from account '$accountId'" } + val messageStore = createMessageStore(accountId) + ?: return@withContext Outcome.failure(FolderError.AccountNotFound) + val folderId = messageStore.getFolderId(folderServerId.serverId) + logger.verbose { "$LOG_ID found folder id: '${folderId}'" } + + Outcome.success(folderId) + } + + override suspend fun isPresent( + accountId: AccountId, + folderId: Long, + ): Boolean = withContext(ioDispatcher) { + try { + val messageStore = messageStoreManager.getMessageStore(accountId) + messageStore.getFolder(folderId) { true } ?: false + } catch (e: IllegalStateException) { + logger.error(throwable = e) { + "$LOG_ID failed to verify if folder is present. Account '$accountId' was not found." + } + false + } + } + + private fun createMessageStore(accountId: AccountId): MessageStore? = try { + messageStoreManager.getMessageStore(accountId) + } catch (e: IllegalStateException) { + logger.error(throwable = e) { + "$LOG_ID failed to create MessageStore for account '$accountId'. Account was not found." + } + null + } +} diff --git a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/DefaultFolderRepository.kt b/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/DefaultFolderRepository.kt deleted file mode 100644 index 0ebe316a35f..00000000000 --- a/legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/DefaultFolderRepository.kt +++ /dev/null @@ -1,60 +0,0 @@ -package app.k9mail.legacy.mailstore - -import app.k9mail.legacy.mailstore.RemoteFolderTypeMapper.toFolderType -import app.k9mail.legacy.mailstore.folder.extension.getFolderType -import kotlinx.coroutines.flow.firstOrNull -import net.thunderbird.core.android.account.LegacyAccount -import net.thunderbird.core.android.account.LegacyAccountManager -import net.thunderbird.core.common.exception.MessagingException -import net.thunderbird.feature.account.AccountId -import net.thunderbird.feature.mail.folder.api.Folder -import net.thunderbird.feature.mail.folder.api.OutboxFolderManager -import net.thunderbird.feature.mail.folder.api.RemoteFolder -import net.thunderbird.feature.mail.folder.api.data.repository.PushFolderTrackingRepository - -@Suppress("TooManyFunctions") -class DefaultFolderRepository( - private val accountManager: LegacyAccountManager, - private val messageStoreManager: MessageStoreManager, - private val outboxFolderManager: OutboxFolderManager, - private val aggregateRepositories: AggregateRepositories, -) : FolderRepository, PushFolderTrackingRepository by aggregateRepositories.pushFolderTrackingRepository { - override suspend fun getFolder(accountId: AccountId, folderId: Long): Folder? { - val account = getAccountById(accountId) - val messageStore = messageStoreManager.getMessageStore(accountId) - val outboxFolderId = outboxFolderManager.getOutboxFolderId(accountId) - return messageStore.getFolder(folderId) { folder -> - Folder( - id = folder.id, - name = folder.name, - type = folder.getFolderType(account, outboxFolderId), - isLocalOnly = folder.isLocalOnly, - ) - } - } - - override fun getFolderServerId(accountId: AccountId, folderId: Long): String? { - val messageStore = messageStoreManager.getMessageStore(accountId) - return messageStore.getFolder(folderId) { folder -> - folder.serverId - } - } - - override fun getFolderId(accountId: AccountId, folderServerId: String): Long? { - val messageStore = messageStoreManager.getMessageStore(accountId) - return messageStore.getFolderId(folderServerId) - } - - override fun isFolderPresent(accountId: AccountId, folderId: Long): Boolean { - val messageStore = messageStoreManager.getMessageStore(accountId) - return messageStore.getFolder(folderId) { true } ?: false - } - - private suspend fun getAccountById(accountId: AccountId): LegacyAccount = - accountManager.getById(accountId).firstOrNull() - ?: error("Account not found: $accountId") -} - -class AggregateRepositories( - val pushFolderTrackingRepository: PushFolderTrackingRepository, -) diff --git a/legacy/mailstore/src/test/java/app/k9mail/legacy/mailstore/DefaultFolderQueryRepositoryTest.kt b/legacy/mailstore/src/test/java/app/k9mail/legacy/mailstore/DefaultFolderQueryRepositoryTest.kt new file mode 100644 index 00000000000..381c67daef1 --- /dev/null +++ b/legacy/mailstore/src/test/java/app/k9mail/legacy/mailstore/DefaultFolderQueryRepositoryTest.kt @@ -0,0 +1,405 @@ +package app.k9mail.legacy.mailstore + +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import assertk.assertions.isTrue +import com.fsck.k9.mail.AuthType +import com.fsck.k9.mail.ConnectionSecurity +import com.fsck.k9.mail.ServerSettings +import kotlin.test.Test +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import net.thunderbird.account.fake.FakeAccountData.ACCOUNT_ID_OTHER_RAW +import net.thunderbird.account.fake.FakeAccountData.ACCOUNT_ID_RAW +import net.thunderbird.core.android.account.AccountRemovedListener +import net.thunderbird.core.android.account.AccountsChangeListener +import net.thunderbird.core.android.account.Identity +import net.thunderbird.core.android.account.LegacyAccount +import net.thunderbird.core.android.account.LegacyAccountDto +import net.thunderbird.core.android.account.LegacyAccountDtoManager +import net.thunderbird.core.android.account.LegacyAccountManager +import net.thunderbird.core.logging.testing.TestLogger +import net.thunderbird.core.outcome.Outcome +import net.thunderbird.feature.account.AccountId +import net.thunderbird.feature.account.AccountIdFactory +import net.thunderbird.feature.account.storage.profile.AvatarDto +import net.thunderbird.feature.account.storage.profile.AvatarTypeDto +import net.thunderbird.feature.account.storage.profile.ProfileDto +import net.thunderbird.feature.mail.folder.api.Folder +import net.thunderbird.feature.mail.folder.api.FolderServerId +import net.thunderbird.feature.mail.folder.api.FolderType +import net.thunderbird.feature.mail.folder.api.OutboxFolderManager +import net.thunderbird.feature.mail.folder.api.data.FolderError +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +private const val OUTBOX_FOLDER_ID = 100L +private const val INBOX_FOLDER_ID = 1L +private const val REGULAR_FOLDER_ID = 42L + +@Suppress("MaxLineLength") +class DefaultFolderQueryRepositoryTest { + private val accountId = AccountIdFactory.of(ACCOUNT_ID_RAW) + private val account = createLegacyAccount(accountId) + private val accountDto = LegacyAccountDto(ACCOUNT_ID_RAW) + private val messageStore = mock() + private val accountManager = FakeFolderQueryLegacyAccountManager(accounts = listOf(account)) + private val outboxFolderManager = FakeOutboxFolderManager(outboxFolderId = OUTBOX_FOLDER_ID) + private var messageStoreManager = createMessageStoreManager(accountDto) + private val testSubject = DefaultFolderQueryRepository( + logger = TestLogger(), + accountManager = accountManager, + messageStoreManager = messageStoreManager, + outboxFolderManager = outboxFolderManager, + ioDispatcher = Dispatchers.Unconfined, + ) + + @Test + fun `findById should return Failure with AccountNotFound when account does not exist`() = runTest { + // Arrange + val unknownAccountId = AccountIdFactory.of(ACCOUNT_ID_OTHER_RAW) + + // Act + val result = testSubject.findById(unknownAccountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isEqualTo(Outcome.failure(FolderError.AccountNotFound)) + } + + @Test + fun `findById should return Failure with AccountNotFound when the message store is not found`() = runTest { + // Arrange + val subject = DefaultFolderQueryRepository( + logger = TestLogger(), + accountManager = accountManager, + messageStoreManager = createMessageStoreManager(accountDto = null), + outboxFolderManager = outboxFolderManager, + ioDispatcher = Dispatchers.Unconfined, + ) + + // Act + val result = subject.findById(accountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isEqualTo(Outcome.failure(FolderError.AccountNotFound)) + } + + @Test + fun `findById should return Success with mapped folder for a regular folder`() = runTest { + // Arrange + stubGetFolder(REGULAR_FOLDER_ID, FakeFolderDetailsAccessor(id = REGULAR_FOLDER_ID, name = "Regular")) + + // Act + val result = testSubject.findById(accountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isEqualTo( + Outcome.success( + Folder( + id = REGULAR_FOLDER_ID, + name = "Regular", + type = FolderType.REGULAR, + isLocalOnly = false, + ), + ), + ) + } + + @Test + fun `findById should map folder type to INBOX when folder id matches account inbox folder id`() = runTest { + // Arrange + stubGetFolder(INBOX_FOLDER_ID, FakeFolderDetailsAccessor(id = INBOX_FOLDER_ID, name = "Inbox")) + + // Act + val result = testSubject.findById(accountId, INBOX_FOLDER_ID) + + // Assert + val folder = (result as Outcome.Success).data + assertThat(folder?.type).isEqualTo(FolderType.INBOX) + } + + @Test + fun `findById should map folder type to OUTBOX when folder id matches the outbox folder id`() = runTest { + // Arrange + stubGetFolder(OUTBOX_FOLDER_ID, FakeFolderDetailsAccessor(id = OUTBOX_FOLDER_ID, name = "Outbox")) + + // Act + val result = testSubject.findById(accountId, OUTBOX_FOLDER_ID) + + // Assert + val folder = (result as Outcome.Success).data + assertThat(folder?.type).isEqualTo(FolderType.OUTBOX) + } + + @Test + fun `findById should return Success with null when folder does not exist`() = runTest { + // Arrange + stubGetFolder(REGULAR_FOLDER_ID, accessor = null) + + // Act + val result = testSubject.findById(accountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isEqualTo(Outcome.success(null)) + } + + @Test + fun `findFolderServerIdById should return Failure with AccountNotFound when the message store is not found`() = + runTest { + // Arrange + val subject = DefaultFolderQueryRepository( + logger = TestLogger(), + accountManager = accountManager, + messageStoreManager = createMessageStoreManager(accountDto = null), + outboxFolderManager = outboxFolderManager, + ioDispatcher = Dispatchers.Unconfined, + ) + + // Act + val result = subject.findFolderServerIdById(accountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isEqualTo(Outcome.failure(FolderError.AccountNotFound)) + } + + @Test + fun `findFolderServerIdById should return Success with the folder server id`() = runTest { + // Arrange + stubGetFolder( + REGULAR_FOLDER_ID, + FakeFolderDetailsAccessor(id = REGULAR_FOLDER_ID, serverId = "serverId"), + ) + + // Act + val result = testSubject.findFolderServerIdById(accountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isEqualTo(Outcome.success(FolderServerId("serverId"))) + } + + @Test + fun `findFolderServerIdById should return Success with null when the folder has no server id`() = runTest { + // Arrange + stubGetFolder( + REGULAR_FOLDER_ID, + FakeFolderDetailsAccessor(id = REGULAR_FOLDER_ID, serverId = null), + ) + + // Act + val result = testSubject.findFolderServerIdById(accountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isEqualTo(Outcome.success(null)) + } + + @Test + fun `findIdByServerId should return Failure with AccountNotFound when the message store is not found`() = runTest { + // Arrange + val subject = DefaultFolderQueryRepository( + logger = TestLogger(), + accountManager = accountManager, + messageStoreManager = createMessageStoreManager(accountDto = null), + outboxFolderManager = outboxFolderManager, + ioDispatcher = Dispatchers.Unconfined, + ) + + // Act + val result = subject.findIdByServerId(accountId, FolderServerId("serverId")) + + // Assert + assertThat(result).isEqualTo(Outcome.failure(FolderError.AccountNotFound)) + } + + @Test + fun `findIdByServerId should return Success with the folder id`() = runTest { + // Arrange + whenever(messageStore.getFolderId("serverId")).thenReturn(REGULAR_FOLDER_ID) + + // Act + val result = testSubject.findIdByServerId(accountId, FolderServerId("serverId")) + + // Assert + assertThat(result).isEqualTo(Outcome.success(REGULAR_FOLDER_ID)) + } + + @Test + fun `findIdByServerId should return Success with null when the server id is not found`() = runTest { + // Arrange + whenever(messageStore.getFolderId("unknown")).thenReturn(null) + + // Act + val result = testSubject.findIdByServerId(accountId, FolderServerId("unknown")) + + // Assert + assertThat(result).isEqualTo(Outcome.success(null)) + } + + @Test + fun `isPresent should return true when the folder is present`() = runTest { + // Arrange + whenever(messageStore.getFolder(eq(REGULAR_FOLDER_ID), any())).thenReturn(true) + + // Act + val result = testSubject.isPresent(accountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isTrue() + } + + @Test + fun `isPresent should return false when the folder is not present`() = runTest { + // Arrange + whenever(messageStore.getFolder(eq(REGULAR_FOLDER_ID), any())).thenReturn(null) + + // Act + val result = testSubject.isPresent(accountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isFalse() + } + + @Test + fun `isPresent should return false when account does not exist`() = runTest { + // Arrange + val subject = DefaultFolderQueryRepository( + logger = TestLogger(), + accountManager = accountManager, + messageStoreManager = createMessageStoreManager(accountDto = null), + outboxFolderManager = outboxFolderManager, + ioDispatcher = Dispatchers.Unconfined, + ) + + // Act + val result = subject.isPresent(accountId, REGULAR_FOLDER_ID) + + // Assert + assertThat(result).isFalse() + } + + private fun createMessageStoreManager(accountDto: LegacyAccountDto?): MessageStoreManager { + val accounts = accountDto?.let { listOf(it) } ?: emptyList() + return MessageStoreManager( + accountManager = FakeFolderQueryLegacyAccountDtoManager(accounts = accounts), + messageStoreFactory = FakeFolderQueryMessageStoreFactory( + messageStoresByUuid = accountDto?.let { mapOf(it.uuid to messageStore) } ?: emptyMap(), + ), + ) + } + + private fun stubGetFolder(folderId: Long, accessor: FolderDetailsAccessor?) { + whenever(messageStore.getFolder(eq(folderId), any())).thenAnswer { invocation -> + val mapper = invocation.getArgument>(1) + accessor?.let { mapper.map(it) } + } + } +} + +private fun createLegacyAccount(id: AccountId): LegacyAccount { + return LegacyAccount( + id = id, + name = "Account", + email = "user@example.com", + profile = ProfileDto( + id = id, + name = "Account", + color = 0, + avatar = AvatarDto(AvatarTypeDto.MONOGRAM, "A", null, null), + ), + incomingServerSettings = ServerSettings( + type = "imap", + host = "imap.example.com", + port = 993, + connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED, + authenticationType = AuthType.PLAIN, + username = "user", + password = "password", + clientCertificateAlias = null, + ), + outgoingServerSettings = ServerSettings( + type = "smtp", + host = "smtp.example.com", + port = 465, + connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED, + authenticationType = AuthType.PLAIN, + username = "user", + password = "password", + clientCertificateAlias = null, + ), + identities = listOf(Identity(name = "Account", email = "user@example.com")), + inboxFolderId = INBOX_FOLDER_ID, + ) +} + +private class FakeFolderDetailsAccessor( + override val id: Long, + override val name: String = "Folder", + override val serverId: String? = "serverId", + override val type: com.fsck.k9.mail.FolderType = com.fsck.k9.mail.FolderType.REGULAR, + override val isLocalOnly: Boolean = false, + override val isInTopGroup: Boolean = false, + override val isIntegrate: Boolean = false, + override val isSyncEnabled: Boolean = true, + override val isVisible: Boolean = true, + override val isNotificationsEnabled: Boolean = true, + override val isPushEnabled: Boolean = false, + override val visibleLimit: Int = 25, + override val moreMessages: MoreMessages = MoreMessages.UNKNOWN, + override val lastChecked: Long? = null, + override val unreadMessageCount: Int = 0, + override val starredMessageCount: Int = 0, +) : FolderDetailsAccessor { + override fun serverIdOrThrow(): String = serverId ?: error("serverId is null") +} + +private class FakeFolderQueryLegacyAccountManager( + private val accounts: List = emptyList(), +) : LegacyAccountManager { + override fun getAll(): Flow> = flowOf(accounts) + override fun getById(id: AccountId): Flow = flowOf(accounts.find { it.id == id }) + override suspend fun update(account: LegacyAccount) = error("Not implemented") + override fun getByIdSync(id: AccountId): LegacyAccount? = accounts.find { it.id == id } + override fun updateSync(account: LegacyAccount) = error("Not implemented") + override fun getAccounts(): List = accounts + override fun getAccountsFlow(): Flow> = flowOf(accounts) + override fun getAccount(accountUuid: String): LegacyAccount? = accounts.find { it.uuid == accountUuid } + override fun getAccountFlow(accountUuid: String): Flow = flowOf(getAccount(accountUuid)) + override fun moveAccount(account: LegacyAccount, newPosition: Int) = error("Not implemented") + override fun saveAccount(account: LegacyAccount) = error("Not implemented") +} + +private class FakeFolderQueryLegacyAccountDtoManager( + accounts: List = emptyList(), +) : LegacyAccountDtoManager { + private val accountsByUuid = accounts.associateBy { it.uuid } + + override fun getAccounts(): List = accountsByUuid.values.toList() + override fun getAccountsFlow(): Flow> = flowOf(getAccounts()) + override fun getAccount(accountUuid: String): LegacyAccountDto? = accountsByUuid[accountUuid] + override fun getAccountFlow(accountUuid: String): Flow = flowOf(getAccount(accountUuid)) + override fun addAccountRemovedListener(listener: AccountRemovedListener) = Unit + override fun moveAccount(account: LegacyAccountDto, newPosition: Int) = Unit + override fun addOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener) = Unit + override fun removeOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener) = Unit + override fun saveAccount(account: LegacyAccountDto) = Unit +} + +private class FakeFolderQueryMessageStoreFactory( + private val messageStoresByUuid: Map, +) : MessageStoreFactory { + override fun create(account: LegacyAccountDto): ListenableMessageStore = messageStoresByUuid.getValue(account.uuid) +} + +private class FakeOutboxFolderManager( + private val outboxFolderId: Long, +) : OutboxFolderManager { + override suspend fun getOutboxFolderId(accountId: AccountId, createIfMissing: Boolean): Long = outboxFolderId + override suspend fun createOutboxFolder(accountId: AccountId): Outcome = + error("Not implemented") + + override suspend fun hasPendingMessages(accountId: AccountId): Boolean = error("Not implemented") +} diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagedetails/KoinModule.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagedetails/KoinModule.kt index 34ab7620412..c4e3b3d68d3 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagedetails/KoinModule.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagedetails/KoinModule.kt @@ -8,7 +8,7 @@ val messageDetailsUiModule = module { MessageDetailsViewModel( resources = get(), messageRepository = get(), - folderRepository = get(), + folderQueryRepository = get(), contactSettingsProvider = get(), contactRepository = get(), contactPermissionResolver = get(), diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagedetails/MessageDetailsViewModel.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagedetails/MessageDetailsViewModel.kt index fa9303403bd..5b1205ec570 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagedetails/MessageDetailsViewModel.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/messagedetails/MessageDetailsViewModel.kt @@ -8,7 +8,6 @@ import androidx.lifecycle.viewModelScope import app.k9mail.core.android.common.contact.CachingRepository import app.k9mail.core.android.common.contact.ContactPermissionResolver import app.k9mail.core.android.common.contact.ContactRepository -import app.k9mail.legacy.mailstore.FolderRepository import app.k9mail.legacy.message.controller.MessageReference import app.k9mail.legacy.ui.folder.FolderNameFormatter import com.fsck.k9.helper.ClipboardManager @@ -29,13 +28,15 @@ import kotlinx.coroutines.launch import net.thunderbird.core.android.account.LegacyAccountDto import net.thunderbird.core.android.account.LegacyAccountDtoManager import net.thunderbird.core.common.mail.toEmailAddressOrNull +import net.thunderbird.core.outcome.fold import net.thunderbird.feature.mail.folder.api.Folder +import net.thunderbird.feature.mail.folder.api.data.repository.FolderQueryRepository @Suppress("TooManyFunctions", "LongParameterList") internal class MessageDetailsViewModel( private val resources: Resources, private val messageRepository: MessageRepository, - private val folderRepository: FolderRepository, + private val folderQueryRepository: FolderQueryRepository, private val contactSettingsProvider: ContactSettingsProvider, private val contactRepository: ContactRepository, private val contactPermissionResolver: ContactPermissionResolver, @@ -77,7 +78,8 @@ internal class MessageDetailsViewModel( val account = accountManager.getAccount(messageReference.accountUuid) ?: error("Account not found") val messageDetails = messageRepository.getMessageDetails(messageReference) - val folder = folderRepository.getFolder(account.id, folderId = messageReference.folderId) + val folder = folderQueryRepository.findById(account.id, folderId = messageReference.folderId) + .fold(onSuccess = { it }, onFailure = { null }) val senderList = messageDetails.sender?.let { listOf(it) } ?: emptyList() val messageDetailsUi = MessageDetailsUi(