Skip to content

Commit aa80dc0

Browse files
committed
refactor(mailstore): extract push folders query logic to PushFoldersQueryRepository
- Add PushFoldersQueryRepository interface with observeAllByAccountId and getAllByAccountId methods - Add DefaultPushFoldersQueryRepository implementation - Remove getPushFoldersFlow and getPushFolders from FolderRepository - Update AccountPushController to use new repository and handle Outcome - Add comprehensive tests for DefaultPushFoldersQueryRepository
1 parent 377d788 commit aa80dc0

9 files changed

Lines changed: 445 additions & 57 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package net.thunderbird.feature.mail.folder.api.data.repository
2+
3+
import kotlinx.coroutines.flow.Flow
4+
import net.thunderbird.core.outcome.Outcome
5+
import net.thunderbird.feature.account.AccountId
6+
import net.thunderbird.feature.mail.folder.api.RemoteFolder
7+
import net.thunderbird.feature.mail.folder.api.data.FolderError
8+
9+
interface PushFoldersQueryRepository {
10+
/**
11+
* Returns a [Flow] of [RemoteFolder]s for the given [accountId] that should be used for push.
12+
*
13+
* @param accountId The account identifier.
14+
*/
15+
fun observeAllByAccountId(accountId: AccountId): Flow<Outcome<List<RemoteFolder>, FolderError>>
16+
17+
/**
18+
* Returns a list of [RemoteFolder]s for the given [accountId] that should be used for push.
19+
*
20+
* @param accountId The account identifier.
21+
*/
22+
fun getAllByAccountId(accountId: AccountId): Outcome<List<RemoteFolder>, FolderError>
23+
}

legacy/core/src/main/java/com/fsck/k9/controller/push/AccountPushController.kt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.fsck.k9.controller.push
22

3-
import app.k9mail.legacy.mailstore.FolderRepository
43
import com.fsck.k9.backend.BackendManager
54
import com.fsck.k9.backend.api.BackendPusher
65
import com.fsck.k9.backend.api.BackendPusherCallback
@@ -10,13 +9,15 @@ import kotlinx.coroutines.Dispatchers
109
import kotlinx.coroutines.cancel
1110
import kotlinx.coroutines.launch
1211
import net.thunderbird.core.logging.Logger
12+
import net.thunderbird.core.outcome.fold
1313
import net.thunderbird.feature.account.AccountId
14+
import net.thunderbird.feature.mail.folder.api.data.repository.PushFoldersQueryRepository
1415

1516
private const val TAG = "AccountPushController"
1617

1718
internal class AccountPushController(
1819
private val backendManager: BackendManager,
19-
private val folderRepository: FolderRepository,
20+
private val pushFoldersQueryRepository: PushFoldersQueryRepository,
2021
private val backendPusherCallback: BackendPusherCallback,
2122
private val accountId: AccountId,
2223
private val logger: Logger,
@@ -58,10 +59,18 @@ internal class AccountPushController(
5859

5960
private fun startListeningForPushFolders() {
6061
coroutineScope.launch {
61-
folderRepository.getPushFoldersFlow(accountId).collect { remoteFolders ->
62-
val folderServerIds = remoteFolders.map { it.serverId }
63-
updatePushFolders(folderServerIds)
64-
}
62+
pushFoldersQueryRepository.observeAllByAccountId(accountId)
63+
.collect { outcome ->
64+
outcome.fold(
65+
onSuccess = { remoteFolders ->
66+
val folderServerIds = remoteFolders.map { it.serverId }
67+
updatePushFolders(folderServerIds)
68+
},
69+
onFailure = {
70+
logger.error { "Failed to start listening for push folders. Error: $it" }
71+
},
72+
)
73+
}
6574
}
6675
}
6776

legacy/core/src/main/java/com/fsck/k9/controller/push/AccountPushControllerFactory.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ import com.fsck.k9.controller.MessagingController
66
import net.thunderbird.core.android.account.LegacyAccountDtoManager
77
import net.thunderbird.core.logging.Logger
88
import net.thunderbird.feature.account.AccountId
9+
import net.thunderbird.feature.mail.folder.api.data.repository.PushFoldersQueryRepository
910

1011
internal class AccountPushControllerFactory(
1112
private val accountManager: LegacyAccountDtoManager,
1213
private val backendManager: BackendManager,
1314
private val messagingController: MessagingController,
1415
private val folderRepository: FolderRepository,
16+
private val pushFoldersQueryRepository: PushFoldersQueryRepository,
1517
private val logger: Logger,
1618
) {
1719
fun create(accountId: AccountId): AccountPushController {
1820
return AccountPushController(
1921
backendManager,
20-
folderRepository,
22+
pushFoldersQueryRepository,
2123
backendPusherCallback = AccountBackendPusherCallback(
2224
accountManager = accountManager,
2325
messagingController = messagingController,

legacy/core/src/main/java/com/fsck/k9/controller/push/KoinModule.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ internal val controllerPushModule = module {
1212
backendManager = get(),
1313
messagingController = get(),
1414
folderRepository = get(),
15+
pushFoldersQueryRepository = get(),
1516
logger = get(),
1617
)
1718
}

legacy/core/src/main/java/com/fsck/k9/mailstore/KoinModule.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import app.k9mail.legacy.mailstore.MessageListRepository
77
import app.k9mail.legacy.mailstore.MessageStoreManager
88
import app.k9mail.legacy.mailstore.folder.DefaultFolderDetailsRepository
99
import app.k9mail.legacy.mailstore.folder.push.DefaultPushFolderTrackingRepository
10+
import app.k9mail.legacy.mailstore.folder.push.DefaultPushFoldersQueryRepository
1011
import com.fsck.k9.mailstore.folder.DefaultOutboxFolderManager
1112
import com.fsck.k9.message.extractors.AttachmentCounter
1213
import com.fsck.k9.message.extractors.MessageFulltextCreator
@@ -17,12 +18,19 @@ import net.thunderbird.core.common.cache.TimeLimitedCache
1718
import net.thunderbird.feature.mail.folder.api.OutboxFolderManager
1819
import net.thunderbird.feature.mail.folder.api.data.repository.FolderDetailsRepository
1920
import net.thunderbird.feature.mail.folder.api.data.repository.PushFolderTrackingRepository
21+
import net.thunderbird.feature.mail.folder.api.data.repository.PushFoldersQueryRepository
2022
import org.koin.dsl.module
2123

2224
val mailStoreModule = module {
2325
single<PushFolderTrackingRepository> {
2426
DefaultPushFolderTrackingRepository(logger = get(), messageStoreManager = get())
2527
}
28+
single<PushFoldersQueryRepository> {
29+
DefaultPushFoldersQueryRepository(logger = get(), messageStoreManager = get())
30+
}
31+
single<PushFoldersQueryRepository> {
32+
DefaultPushFoldersQueryRepository(logger = get(), messageStoreManager = get())
33+
}
2634
single<FolderDetailsRepository> {
2735
DefaultFolderDetailsRepository(
2836
logger = get(),

legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/DefaultFolderRepository.kt

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,7 @@ package app.k9mail.legacy.mailstore
22

33
import app.k9mail.legacy.mailstore.RemoteFolderTypeMapper.toFolderType
44
import app.k9mail.legacy.mailstore.folder.extension.getFolderType
5-
import kotlinx.coroutines.CoroutineDispatcher
6-
import kotlinx.coroutines.Dispatchers
7-
import kotlinx.coroutines.channels.Channel
8-
import kotlinx.coroutines.channels.awaitClose
9-
import kotlinx.coroutines.channels.trySendBlocking
10-
import kotlinx.coroutines.flow.Flow
11-
import kotlinx.coroutines.flow.buffer
12-
import kotlinx.coroutines.flow.callbackFlow
13-
import kotlinx.coroutines.flow.distinctUntilChanged
145
import kotlinx.coroutines.flow.firstOrNull
15-
import kotlinx.coroutines.flow.flowOn
166
import net.thunderbird.core.android.account.LegacyAccount
177
import net.thunderbird.core.android.account.LegacyAccountManager
188
import net.thunderbird.core.common.exception.MessagingException
@@ -28,7 +18,6 @@ class DefaultFolderRepository(
2818
private val messageStoreManager: MessageStoreManager,
2919
private val outboxFolderManager: OutboxFolderManager,
3020
private val aggregateRepositories: AggregateRepositories,
31-
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
3221
) : FolderRepository, PushFolderTrackingRepository by aggregateRepositories.pushFolderTrackingRepository {
3322
override suspend fun getFolder(accountId: AccountId, folderId: Long): Folder? {
3423
val account = getAccountById(accountId)
@@ -77,32 +66,6 @@ class DefaultFolderRepository(
7766
}
7867
}
7968

80-
override fun getPushFoldersFlow(accountId: AccountId): Flow<List<RemoteFolder>> {
81-
val messageStore = messageStoreManager.getMessageStore(accountId)
82-
return callbackFlow {
83-
send(getPushFolders(accountId))
84-
85-
val listener = FolderSettingsChangedListener {
86-
trySendBlocking(getPushFolders(accountId))
87-
}
88-
messageStore.addFolderSettingsChangedListener(listener)
89-
90-
awaitClose {
91-
messageStore.removeFolderSettingsChangedListener(listener)
92-
}
93-
}.buffer(capacity = Channel.CONFLATED)
94-
.distinctUntilChanged()
95-
.flowOn(ioDispatcher)
96-
}
97-
98-
override fun getPushFolders(accountId: AccountId): List<RemoteFolder> {
99-
return getRemoteFolderDetails(accountId)
100-
.asSequence()
101-
.filter { folderDetails -> folderDetails.isPushEnabled }
102-
.map { folderDetails -> folderDetails.folder }
103-
.toList()
104-
}
105-
10669
override fun getFolderServerId(accountId: AccountId, folderId: Long): String? {
10770
val messageStore = messageStoreManager.getMessageStore(accountId)
10871
return messageStore.getFolder(folderId) { folder ->

legacy/mailstore/src/main/java/app/k9mail/legacy/mailstore/FolderRepository.kt

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,6 @@ interface FolderRepository : PushFolderTrackingRepository {
4040
*/
4141
fun getRemoteFolderDetails(accountId: AccountId): List<RemoteFolderDetails>
4242

43-
/**
44-
* Returns a [Flow] of [RemoteFolder]s for the given [accountId] that should be used for push.
45-
*
46-
* @param accountId The account identifier.
47-
*/
48-
fun getPushFoldersFlow(accountId: AccountId): Flow<List<RemoteFolder>>
49-
50-
/**
51-
* Returns a list of [RemoteFolder]s for the given [accountId] that should be used for push.
52-
*
53-
* @param accountId The account identifier.
54-
*/
55-
fun getPushFolders(accountId: AccountId): List<RemoteFolder>
5643

5744
/**
5845
* Returns the server ID for the given [accountId] and [folderId].
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package app.k9mail.legacy.mailstore.folder.push
2+
3+
import app.k9mail.legacy.mailstore.FolderSettingsChangedListener
4+
import app.k9mail.legacy.mailstore.MessageStoreManager
5+
import app.k9mail.legacy.mailstore.RemoteFolderDetails
6+
import app.k9mail.legacy.mailstore.RemoteFolderTypeMapper.toFolderType
7+
import kotlinx.coroutines.CoroutineDispatcher
8+
import kotlinx.coroutines.Dispatchers
9+
import kotlinx.coroutines.channels.Channel
10+
import kotlinx.coroutines.channels.awaitClose
11+
import kotlinx.coroutines.channels.trySendBlocking
12+
import kotlinx.coroutines.flow.Flow
13+
import kotlinx.coroutines.flow.buffer
14+
import kotlinx.coroutines.flow.callbackFlow
15+
import kotlinx.coroutines.flow.distinctUntilChanged
16+
import kotlinx.coroutines.flow.flowOn
17+
import net.thunderbird.core.common.exception.MessagingException
18+
import net.thunderbird.core.logging.Logger
19+
import net.thunderbird.core.outcome.Outcome
20+
import net.thunderbird.feature.account.AccountId
21+
import net.thunderbird.feature.mail.folder.api.RemoteFolder
22+
import net.thunderbird.feature.mail.folder.api.data.FolderError
23+
import net.thunderbird.feature.mail.folder.api.data.repository.PushFoldersQueryRepository
24+
25+
class DefaultPushFoldersQueryRepository(
26+
private val logger: Logger,
27+
private val messageStoreManager: MessageStoreManager,
28+
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
29+
) : PushFoldersQueryRepository {
30+
override fun observeAllByAccountId(accountId: AccountId): Flow<Outcome<List<RemoteFolder>, FolderError>> =
31+
callbackFlow {
32+
val messageStore = messageStoreManager.getMessageStore(accountId)
33+
send(getAllByAccountId(accountId))
34+
35+
val listener = FolderSettingsChangedListener {
36+
trySendBlocking(getAllByAccountId(accountId))
37+
}
38+
messageStore.addFolderSettingsChangedListener(listener)
39+
40+
awaitClose {
41+
messageStore.removeFolderSettingsChangedListener(listener)
42+
}
43+
44+
}.buffer(capacity = Channel.CONFLATED)
45+
.distinctUntilChanged()
46+
.flowOn(ioDispatcher)
47+
48+
override fun getAllByAccountId(accountId: AccountId): Outcome<List<RemoteFolder>, FolderError> {
49+
logger.verbose { "$LOG_ID getting push folders for account '$accountId'" }
50+
val pushFolders = getAllRemoteFolderDetails(accountId)
51+
.asSequence()
52+
.filter { folderDetails -> folderDetails.isPushEnabled }
53+
.map { folderDetails -> folderDetails.folder }
54+
.toList()
55+
56+
return if (pushFolders.isEmpty()) {
57+
logger.warn { "$LOG_ID could not find any push folders with the given account id '$accountId'" }
58+
Outcome.failure(FolderError.NotFound)
59+
} else {
60+
logger.verbose { "$LOG_ID found push folder: $pushFolders" }
61+
Outcome.success(pushFolders)
62+
}
63+
}
64+
65+
private fun getAllRemoteFolderDetails(accountId: AccountId): List<RemoteFolderDetails> {
66+
logger.verbose { "$LOG_ID fetching remote folders details" }
67+
return try {
68+
val messageStore = messageStoreManager.getMessageStore(accountId)
69+
messageStore.getFolders(excludeLocalOnly = true) { folder ->
70+
RemoteFolderDetails(
71+
folder = RemoteFolder(
72+
id = folder.id,
73+
serverId = folder.serverIdOrThrow(),
74+
name = folder.name,
75+
type = folder.type.toFolderType(),
76+
),
77+
isInTopGroup = folder.isInTopGroup,
78+
isIntegrate = folder.isIntegrate,
79+
isSyncEnabled = folder.isSyncEnabled,
80+
isVisible = folder.isVisible,
81+
isNotificationsEnabled = folder.isNotificationsEnabled,
82+
isPushEnabled = folder.isPushEnabled,
83+
)
84+
}
85+
} catch (e: MessagingException) {
86+
logger.error(throwable = e) {
87+
"$LOG_ID Failed to get remote folders details for account '$accountId'"
88+
}
89+
throw e
90+
} catch (e: IllegalStateException) {
91+
logger.error(throwable = e) {
92+
"$LOG_ID Failed to get remote folders details for account '$accountId'"
93+
}
94+
throw e
95+
}
96+
}
97+
98+
companion object {
99+
private const val LOG_ID = "[repository][push-folders-query]"
100+
}
101+
}

0 commit comments

Comments
 (0)