Add FolderRepository.getPushFoldersFlow()

This method returns a flow that emits the list of folders to sync via Push. It will automatically update when the account's 'Push folders' setting or a folder's Push class is changed.
This commit is contained in:
cketti 2021-05-28 13:26:19 +02:00
parent 3b2a8adb1c
commit 6862b737ed
11 changed files with 184 additions and 15 deletions

View file

@ -233,11 +233,11 @@ class Preferences internal constructor(
}
}
fun addOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener) {
override fun addOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener) {
accountsChangeListeners.add(accountsChangeListener)
}
fun removeOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener) {
override fun removeOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener) {
accountsChangeListeners.remove(accountsChangeListener)
}

View file

@ -2,12 +2,29 @@ package com.fsck.k9.mailstore
import com.fsck.k9.Account
import com.fsck.k9.Account.FolderMode
import com.fsck.k9.AccountsChangeListener
import com.fsck.k9.mail.FolderClass
import com.fsck.k9.preferences.AccountManager
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import com.fsck.k9.mail.FolderType as RemoteFolderType
@OptIn(ExperimentalCoroutinesApi::class)
class FolderRepository(
private val messageStoreManager: MessageStoreManager,
private val account: Account
private val accountManager: AccountManager,
private val account: Account,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
private val sortForDisplay =
compareByDescending<DisplayFolder> { it.folder.type == FolderType.INBOX }
@ -99,6 +116,54 @@ class FolderRepository(
}
}
fun getPushFoldersFlow(): Flow<List<RemoteFolder>> {
return account.getFolderPushModeFlow()
.flatMapLatest { pushMode ->
getPushFoldersFlow(pushMode)
}
}
private fun getPushFoldersFlow(folderMode: FolderMode): Flow<List<RemoteFolder>> {
val messageStore = messageStoreManager.getMessageStore(account)
return callbackFlow {
send(getPushFolders(folderMode))
val listener = FolderSettingsChangedListener {
launch {
send(getPushFolders(folderMode))
}
}
messageStore.addFolderSettingsChangedListener(listener)
awaitClose {
messageStore.removeFolderSettingsChangedListener(listener)
}
}.buffer(capacity = Channel.CONFLATED)
.distinctUntilChanged()
.flowOn(ioDispatcher)
}
private fun getPushFolders(folderMode: FolderMode): List<RemoteFolder> {
if (folderMode == FolderMode.NONE) return emptyList()
return getRemoteFolderDetails()
.asSequence()
.filter { folderDetails ->
val pushClass = folderDetails.effectivePushClass
when (folderMode) {
FolderMode.NONE -> false
FolderMode.ALL -> true
FolderMode.FIRST_CLASS -> pushClass == FolderClass.FIRST_CLASS
FolderMode.FIRST_AND_SECOND_CLASS -> {
pushClass == FolderClass.FIRST_CLASS || pushClass == FolderClass.SECOND_CLASS
}
FolderMode.NOT_SECOND_CLASS -> pushClass != FolderClass.SECOND_CLASS
}
}
.map { folderDetails -> folderDetails.folder }
.toList()
}
fun getFolderServerId(folderId: Long): String? {
val messageStore = messageStoreManager.getMessageStore(account)
return messageStore.getFolder(folderId) { folder ->
@ -162,6 +227,31 @@ class FolderRepository(
RemoteFolderType.SPAM -> FolderType.SPAM
RemoteFolderType.ARCHIVE -> FolderType.ARCHIVE
}
private fun Account.getFolderPushModeFlow(): Flow<FolderMode> {
val account = this@getFolderPushModeFlow
return callbackFlow {
send(account.folderPushMode)
val listener = AccountsChangeListener {
launch {
send(account.folderPushMode)
}
}
accountManager.addOnAccountsChangeListener(listener)
awaitClose {
accountManager.removeOnAccountsChangeListener(listener)
}
}.distinctUntilChanged()
.flowOn(ioDispatcher)
}
private val RemoteFolderDetails.effectivePushClass: FolderClass
get() = if (pushClass == FolderClass.INHERITED) effectiveSyncClass else pushClass
private val RemoteFolderDetails.effectiveSyncClass: FolderClass
get() = if (syncClass == FolderClass.INHERITED) displayClass else syncClass
}
data class Folder(val id: Long, val name: String, val type: FolderType, val isLocalOnly: Boolean)

View file

@ -1,7 +1,13 @@
package com.fsck.k9.mailstore
import com.fsck.k9.Account
import com.fsck.k9.preferences.AccountManager
class FolderRepositoryManager(private val messageStoreManager: MessageStoreManager) {
fun getFolderRepository(account: Account) = FolderRepository(messageStoreManager, account)
class FolderRepositoryManager(
private val messageStoreManager: MessageStoreManager,
private val accountManager: AccountManager
) {
fun getFolderRepository(account: Account): FolderRepository {
return FolderRepository(messageStoreManager, accountManager, account)
}
}

View file

@ -6,7 +6,7 @@ import com.fsck.k9.message.extractors.MessagePreviewCreator
import org.koin.dsl.module
val mailStoreModule = module {
single { FolderRepositoryManager(messageStoreManager = get()) }
single { FolderRepositoryManager(messageStoreManager = get(), accountManager = get()) }
single { MessageViewInfoExtractorFactory(get(), get(), get()) }
single { StorageManager.getInstance(get()) }
single { SearchStatusManager() }

View file

@ -0,0 +1,61 @@
package com.fsck.k9.mailstore
import com.fsck.k9.mail.FolderClass
import java.util.concurrent.CopyOnWriteArraySet
class ListenableMessageStore(private val messageStore: MessageStore) : MessageStore by messageStore {
private val folderSettingsListener = CopyOnWriteArraySet<FolderSettingsChangedListener>()
override fun createFolders(folders: List<CreateFolderInfo>) {
messageStore.createFolders(folders)
notifyFolderSettingsChanged()
}
override fun deleteFolders(folderServerIds: List<String>) {
messageStore.deleteFolders(folderServerIds)
notifyFolderSettingsChanged()
}
override fun updateFolderSettings(folderDetails: FolderDetails) {
messageStore.updateFolderSettings(folderDetails)
notifyFolderSettingsChanged()
}
override fun setIncludeInUnifiedInbox(folderId: Long, includeInUnifiedInbox: Boolean) {
messageStore.setIncludeInUnifiedInbox(folderId, includeInUnifiedInbox)
notifyFolderSettingsChanged()
}
override fun setDisplayClass(folderId: Long, folderClass: FolderClass) {
messageStore.setDisplayClass(folderId, folderClass)
notifyFolderSettingsChanged()
}
override fun setSyncClass(folderId: Long, folderClass: FolderClass) {
messageStore.setSyncClass(folderId, folderClass)
notifyFolderSettingsChanged()
}
override fun setNotificationClass(folderId: Long, folderClass: FolderClass) {
messageStore.setNotificationClass(folderId, folderClass)
notifyFolderSettingsChanged()
}
fun addFolderSettingsChangedListener(listener: FolderSettingsChangedListener) {
folderSettingsListener.add(listener)
}
fun removeFolderSettingsChangedListener(listener: FolderSettingsChangedListener) {
folderSettingsListener.remove(listener)
}
private fun notifyFolderSettingsChanged() {
for (listener in folderSettingsListener) {
listener.onFolderSettingsChanged()
}
}
}
fun interface FolderSettingsChangedListener {
fun onFolderSettingsChanged()
}

View file

@ -3,5 +3,5 @@ package com.fsck.k9.mailstore
import com.fsck.k9.Account
interface MessageStoreFactory {
fun create(account: Account): MessageStore
fun create(account: Account): ListenableMessageStore
}

View file

@ -5,7 +5,7 @@ import com.fsck.k9.preferences.AccountManager
import java.util.concurrent.ConcurrentHashMap
class MessageStoreManager(private val accountManager: AccountManager, private val messageStoreFactory: MessageStoreFactory) {
private val messageStores = ConcurrentHashMap<String, MessageStore>()
private val messageStores = ConcurrentHashMap<String, ListenableMessageStore>()
init {
accountManager.addAccountRemovedListener { account ->
@ -13,12 +13,12 @@ class MessageStoreManager(private val accountManager: AccountManager, private va
}
}
fun getMessageStore(accountUuid: String): MessageStore {
fun getMessageStore(accountUuid: String): ListenableMessageStore {
val account = accountManager.getAccount(accountUuid) ?: error("Account not found: $accountUuid")
return getMessageStore(account)
}
fun getMessageStore(account: Account): MessageStore {
fun getMessageStore(account: Account): ListenableMessageStore {
return messageStores.getOrPut(account.uuid) { messageStoreFactory.create(account) }
}

View file

@ -2,9 +2,12 @@ package com.fsck.k9.preferences
import com.fsck.k9.Account
import com.fsck.k9.AccountRemovedListener
import com.fsck.k9.AccountsChangeListener
interface AccountManager {
fun getAccount(accountUuid: String): Account?
fun addAccountRemovedListener(listener: AccountRemovedListener)
fun moveAccount(account: Account, newPosition: Int)
fun addOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener)
fun removeOnAccountsChangeListener(accountsChangeListener: AccountsChangeListener)
}

View file

@ -14,8 +14,8 @@ import org.mockito.kotlin.whenever
class MessageStoreManagerTest {
private val account = Account("00000000-0000-4000-0000-000000000000")
private val messageStore1 = mock<MessageStore>(name = "messageStore1")
private val messageStore2 = mock<MessageStore>(name = "messageStore2")
private val messageStore1 = mock<ListenableMessageStore>(name = "messageStore1")
private val messageStore2 = mock<ListenableMessageStore>(name = "messageStore2")
private val messageStoreFactory = mock<MessageStoreFactory> {
on { create(account) } doReturn messageStore1 doReturn messageStore2
}

View file

@ -1,8 +1,8 @@
package com.fsck.k9.storage.messages
import com.fsck.k9.Account
import com.fsck.k9.mailstore.ListenableMessageStore
import com.fsck.k9.mailstore.LocalStoreProvider
import com.fsck.k9.mailstore.MessageStore
import com.fsck.k9.mailstore.MessageStoreFactory
import com.fsck.k9.mailstore.NotifierMessageStore
import com.fsck.k9.mailstore.StorageManager
@ -13,9 +13,10 @@ class K9MessageStoreFactory(
private val storageManager: StorageManager,
private val basicPartInfoExtractor: BasicPartInfoExtractor
) : MessageStoreFactory {
override fun create(account: Account): MessageStore {
override fun create(account: Account): ListenableMessageStore {
val localStore = localStoreProvider.getInstance(account)
val messageStore = K9MessageStore(localStore.database, storageManager, basicPartInfoExtractor, account.uuid)
return NotifierMessageStore(messageStore, localStore)
val notifierMessageStore = NotifierMessageStore(messageStore, localStore)
return ListenableMessageStore(notifierMessageStore)
}
}

View file

@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
buildscript {
ext {
buildConfig = [
@ -86,6 +88,12 @@ subprojects {
}
}
tasks.withType(KotlinCompile) {
kotlinOptions {
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}
}
apply plugin: 'org.jlleitschuh.gradle.ktlint'
ktlint {
version = versions.ktlint