Repo created
This commit is contained in:
parent
75dc487a7a
commit
39c29d175b
6317 changed files with 388324 additions and 2 deletions
20
legacy/mailstore/build.gradle.kts
Normal file
20
legacy/mailstore/build.gradle.kts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.android)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.k9mail.legacy.mailstore"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.legacy.di)
|
||||
implementation(projects.legacy.message)
|
||||
|
||||
implementation(projects.core.common)
|
||||
implementation(projects.core.android.account)
|
||||
implementation(projects.feature.mail.account.api)
|
||||
implementation(projects.feature.mail.folder.api)
|
||||
implementation(projects.feature.search.implLegacy)
|
||||
|
||||
implementation(projects.mail.common)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import com.fsck.k9.mail.FolderType
|
||||
|
||||
data class CreateFolderInfo(
|
||||
val serverId: String,
|
||||
val name: String,
|
||||
val type: FolderType,
|
||||
val settings: FolderSettings,
|
||||
)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import com.fsck.k9.mail.FolderType
|
||||
|
||||
fun interface FolderMapper<T> {
|
||||
fun map(folder: FolderDetailsAccessor): T
|
||||
}
|
||||
|
||||
interface FolderDetailsAccessor {
|
||||
val id: Long
|
||||
val serverId: String?
|
||||
val name: String
|
||||
val type: FolderType
|
||||
val isLocalOnly: Boolean
|
||||
val isInTopGroup: Boolean
|
||||
val isIntegrate: Boolean
|
||||
val isSyncEnabled: Boolean
|
||||
val isVisible: Boolean
|
||||
val isNotificationsEnabled: Boolean
|
||||
val isPushEnabled: Boolean
|
||||
val visibleLimit: Int
|
||||
val moreMessages: MoreMessages
|
||||
val lastChecked: Long?
|
||||
val unreadMessageCount: Int
|
||||
val starredMessageCount: Int
|
||||
|
||||
fun serverIdOrThrow(): String
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import app.k9mail.legacy.mailstore.FolderTypeMapper.folderTypeOf
|
||||
import app.k9mail.legacy.mailstore.RemoteFolderTypeMapper.toFolderType
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import net.thunderbird.core.android.account.LegacyAccount
|
||||
import net.thunderbird.core.common.exception.MessagingException
|
||||
import net.thunderbird.feature.mail.folder.api.Folder
|
||||
import net.thunderbird.feature.mail.folder.api.FolderDetails
|
||||
import net.thunderbird.feature.mail.folder.api.FolderType
|
||||
import net.thunderbird.feature.mail.folder.api.OutboxFolderManager
|
||||
import net.thunderbird.feature.mail.folder.api.RemoteFolder
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class FolderRepository(
|
||||
private val messageStoreManager: MessageStoreManager,
|
||||
private val outboxFolderManager: OutboxFolderManager,
|
||||
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
) {
|
||||
suspend fun getFolder(account: LegacyAccount, folderId: Long): Folder? {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
val outboxFolderId = outboxFolderManager.getOutboxFolderId(account.id)
|
||||
return messageStore.getFolder(folderId) { folder ->
|
||||
Folder(
|
||||
id = folder.id,
|
||||
name = folder.name,
|
||||
type = folder.getFolderType(account, outboxFolderId),
|
||||
isLocalOnly = folder.isLocalOnly,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getFolderDetails(account: LegacyAccount, folderId: Long): FolderDetails? {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
val outboxFolderId = outboxFolderManager.getOutboxFolderId(account.id)
|
||||
return messageStore.getFolder(folderId) { folder ->
|
||||
FolderDetails(
|
||||
folder = Folder(
|
||||
id = folder.id,
|
||||
name = folder.name,
|
||||
type = folder.getFolderType(account, outboxFolderId),
|
||||
isLocalOnly = folder.isLocalOnly,
|
||||
),
|
||||
isInTopGroup = folder.isInTopGroup,
|
||||
isIntegrate = folder.isIntegrate,
|
||||
isSyncEnabled = folder.isSyncEnabled,
|
||||
isVisible = folder.isVisible,
|
||||
isNotificationsEnabled = folder.isNotificationsEnabled,
|
||||
isPushEnabled = folder.isPushEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(MessagingException::class)
|
||||
fun getRemoteFolders(accountUuid: String): List<RemoteFolder> {
|
||||
val messageStore = messageStoreManager.getMessageStore(accountUuid)
|
||||
return messageStore.getFolders(excludeLocalOnly = true) { folder ->
|
||||
RemoteFolder(
|
||||
id = folder.id,
|
||||
serverId = folder.serverIdOrThrow(),
|
||||
name = folder.name,
|
||||
type = folder.type.toFolderType(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(MessagingException::class)
|
||||
fun getRemoteFolders(account: LegacyAccount): List<RemoteFolder> =
|
||||
getRemoteFolders(account.uuid)
|
||||
|
||||
fun getRemoteFolderDetails(account: LegacyAccount): List<RemoteFolderDetails> {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
return messageStore.getFolders(excludeLocalOnly = true) { folder ->
|
||||
RemoteFolderDetails(
|
||||
folder = RemoteFolder(
|
||||
id = folder.id,
|
||||
serverId = folder.serverIdOrThrow(),
|
||||
name = folder.name,
|
||||
type = folder.type.toFolderType(),
|
||||
),
|
||||
isInTopGroup = folder.isInTopGroup,
|
||||
isIntegrate = folder.isIntegrate,
|
||||
isSyncEnabled = folder.isSyncEnabled,
|
||||
isVisible = folder.isVisible,
|
||||
isNotificationsEnabled = folder.isNotificationsEnabled,
|
||||
isPushEnabled = folder.isPushEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPushFoldersFlow(account: LegacyAccount): Flow<List<RemoteFolder>> {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
return callbackFlow {
|
||||
send(getPushFolders(account))
|
||||
|
||||
val listener = FolderSettingsChangedListener {
|
||||
trySendBlocking(getPushFolders(account))
|
||||
}
|
||||
messageStore.addFolderSettingsChangedListener(listener)
|
||||
|
||||
awaitClose {
|
||||
messageStore.removeFolderSettingsChangedListener(listener)
|
||||
}
|
||||
}.buffer(capacity = Channel.CONFLATED)
|
||||
.distinctUntilChanged()
|
||||
.flowOn(ioDispatcher)
|
||||
}
|
||||
|
||||
private fun getPushFolders(account: LegacyAccount): List<RemoteFolder> {
|
||||
return getRemoteFolderDetails(account)
|
||||
.asSequence()
|
||||
.filter { folderDetails -> folderDetails.isPushEnabled }
|
||||
.map { folderDetails -> folderDetails.folder }
|
||||
.toList()
|
||||
}
|
||||
|
||||
fun getFolderServerId(account: LegacyAccount, folderId: Long): String? {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
return messageStore.getFolder(folderId) { folder ->
|
||||
folder.serverId
|
||||
}
|
||||
}
|
||||
|
||||
fun getFolderId(account: LegacyAccount, folderServerId: String): Long? {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
return messageStore.getFolderId(folderServerId)
|
||||
}
|
||||
|
||||
fun isFolderPresent(account: LegacyAccount, folderId: Long): Boolean {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
return messageStore.getFolder(folderId) { true } ?: false
|
||||
}
|
||||
|
||||
fun updateFolderDetails(account: LegacyAccount, folderDetails: FolderDetails) {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
messageStore.updateFolderSettings(folderDetails)
|
||||
}
|
||||
|
||||
fun setIncludeInUnifiedInbox(account: LegacyAccount, folderId: Long, includeInUnifiedInbox: Boolean) {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
messageStore.setIncludeInUnifiedInbox(folderId, includeInUnifiedInbox)
|
||||
}
|
||||
|
||||
fun setVisible(account: LegacyAccount, folderId: Long, visible: Boolean) {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
messageStore.setVisible(folderId, visible)
|
||||
}
|
||||
|
||||
fun setSyncEnabled(account: LegacyAccount, folderId: Long, enable: Boolean) {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
messageStore.setSyncEnabled(folderId, enable)
|
||||
}
|
||||
|
||||
fun setNotificationsEnabled(account: LegacyAccount, folderId: Long, enable: Boolean) {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
messageStore.setNotificationsEnabled(folderId, enable)
|
||||
}
|
||||
|
||||
fun setPushDisabled(account: LegacyAccount) {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
messageStore.setPushDisabled()
|
||||
}
|
||||
|
||||
fun hasPushEnabledFolder(account: LegacyAccount): Boolean {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
return messageStore.hasPushEnabledFolder()
|
||||
}
|
||||
|
||||
fun hasPushEnabledFolderFlow(account: LegacyAccount): Flow<Boolean> {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
return callbackFlow {
|
||||
send(hasPushEnabledFolder(account))
|
||||
|
||||
val listener = FolderSettingsChangedListener {
|
||||
trySendBlocking(hasPushEnabledFolder(account))
|
||||
}
|
||||
messageStore.addFolderSettingsChangedListener(listener)
|
||||
|
||||
awaitClose {
|
||||
messageStore.removeFolderSettingsChangedListener(listener)
|
||||
}
|
||||
}.buffer(capacity = Channel.CONFLATED)
|
||||
.distinctUntilChanged()
|
||||
.flowOn(ioDispatcher)
|
||||
}
|
||||
|
||||
private fun FolderDetailsAccessor.getFolderType(account: LegacyAccount, outboxFolderId: Long): FolderType =
|
||||
if (id == outboxFolderId) {
|
||||
FolderType.OUTBOX
|
||||
} else {
|
||||
folderTypeOf(account, id)
|
||||
}
|
||||
}
|
||||
|
||||
data class RemoteFolderDetails(
|
||||
val folder: RemoteFolder,
|
||||
val isInTopGroup: Boolean,
|
||||
val isIntegrate: Boolean,
|
||||
val isSyncEnabled: Boolean,
|
||||
val isVisible: Boolean,
|
||||
val isNotificationsEnabled: Boolean,
|
||||
val isPushEnabled: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
data class FolderSettings(
|
||||
val visibleLimit: Int,
|
||||
val isVisible: Boolean,
|
||||
val isSyncEnabled: Boolean,
|
||||
val isNotificationsEnabled: Boolean,
|
||||
val isPushEnabled: Boolean,
|
||||
val inTopGroup: Boolean,
|
||||
val integrate: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import net.thunderbird.core.android.account.LegacyAccount
|
||||
import net.thunderbird.feature.mail.folder.api.FolderType
|
||||
|
||||
object FolderTypeMapper {
|
||||
|
||||
fun folderTypeOf(account: LegacyAccount, folderId: Long) = when (folderId) {
|
||||
account.inboxFolderId -> FolderType.INBOX
|
||||
account.sentFolderId -> FolderType.SENT
|
||||
account.trashFolderId -> FolderType.TRASH
|
||||
account.draftsFolderId -> FolderType.DRAFTS
|
||||
account.archiveFolderId -> FolderType.ARCHIVE
|
||||
account.spamFolderId -> FolderType.SPAM
|
||||
else -> FolderType.REGULAR
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
import net.thunderbird.feature.mail.folder.api.FolderDetails
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class ListenableMessageStore(private val messageStore: MessageStore) : MessageStore by messageStore {
|
||||
private val folderSettingsListener = CopyOnWriteArraySet<FolderSettingsChangedListener>()
|
||||
|
||||
override fun createFolders(folders: List<CreateFolderInfo>): Set<Long> {
|
||||
return messageStore.createFolders(folders).also {
|
||||
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 setVisible(folderId: Long, visible: Boolean) {
|
||||
messageStore.setVisible(folderId, visible)
|
||||
notifyFolderSettingsChanged()
|
||||
}
|
||||
|
||||
override fun setSyncEnabled(folderId: Long, enable: Boolean) {
|
||||
messageStore.setSyncEnabled(folderId, enable)
|
||||
notifyFolderSettingsChanged()
|
||||
}
|
||||
|
||||
override fun setPushEnabled(folderId: Long, enable: Boolean) {
|
||||
messageStore.setPushEnabled(folderId, enable)
|
||||
notifyFolderSettingsChanged()
|
||||
}
|
||||
|
||||
override fun setNotificationsEnabled(folderId: Long, enable: Boolean) {
|
||||
messageStore.setNotificationsEnabled(folderId, enable)
|
||||
notifyFolderSettingsChanged()
|
||||
}
|
||||
|
||||
override fun setPushDisabled() {
|
||||
messageStore.setPushDisabled()
|
||||
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()
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
fun interface MessageListChangedListener {
|
||||
fun onMessageListChanged()
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
interface MessageListRepository {
|
||||
fun addListener(listener: MessageListChangedListener)
|
||||
fun addListener(accountUuid: String, listener: MessageListChangedListener)
|
||||
fun removeListener(listener: MessageListChangedListener)
|
||||
fun notifyMessageListChanged(accountUuid: String)
|
||||
|
||||
fun <T> getMessages(
|
||||
accountUuid: String,
|
||||
selection: String,
|
||||
selectionArgs: Array<String>,
|
||||
sortOrder: String,
|
||||
messageMapper: MessageMapper<T>,
|
||||
): List<T>
|
||||
|
||||
fun <T> getThreadedMessages(
|
||||
accountUuid: String,
|
||||
selection: String,
|
||||
selectionArgs: Array<String>,
|
||||
sortOrder: String,
|
||||
messageMapper: MessageMapper<T>,
|
||||
): List<T>
|
||||
|
||||
fun <T> getThread(
|
||||
accountUuid: String,
|
||||
threadId: Long,
|
||||
sortOrder: String,
|
||||
messageMapper: MessageMapper<T>,
|
||||
): List<T>
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import app.k9mail.legacy.message.extractors.PreviewResult
|
||||
import com.fsck.k9.mail.Address
|
||||
|
||||
fun interface MessageMapper<T> {
|
||||
fun map(message: MessageDetailsAccessor): T
|
||||
}
|
||||
|
||||
interface MessageDetailsAccessor {
|
||||
val id: Long
|
||||
val messageServerId: String
|
||||
val folderId: Long
|
||||
val fromAddresses: List<Address>
|
||||
val toAddresses: List<Address>
|
||||
val ccAddresses: List<Address>
|
||||
val messageDate: Long
|
||||
val internalDate: Long
|
||||
val subject: String?
|
||||
val preview: PreviewResult
|
||||
val isRead: Boolean
|
||||
val isStarred: Boolean
|
||||
val isAnswered: Boolean
|
||||
val isForwarded: Boolean
|
||||
val hasAttachments: Boolean
|
||||
val threadRoot: Long
|
||||
val threadCount: Int
|
||||
}
|
||||
|
|
@ -0,0 +1,373 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import com.fsck.k9.mail.Flag
|
||||
import com.fsck.k9.mail.FolderType
|
||||
import com.fsck.k9.mail.Header
|
||||
import java.util.Date
|
||||
import kotlin.jvm.Throws
|
||||
import net.thunderbird.core.common.exception.MessagingException
|
||||
import net.thunderbird.feature.mail.folder.api.FolderDetails
|
||||
import net.thunderbird.feature.search.legacy.SearchConditionTreeNode
|
||||
|
||||
/**
|
||||
* Functions for accessing and modifying locally stored messages.
|
||||
*
|
||||
* The goal is for this to gradually replace [LocalStore]. Once complete, apps will be able to provide their own
|
||||
* storage implementation.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
interface MessageStore {
|
||||
/**
|
||||
* Save a remote message in this store.
|
||||
*/
|
||||
fun saveRemoteMessage(folderId: Long, messageServerId: String, messageData: SaveMessageData)
|
||||
|
||||
/**
|
||||
* Save a local message in this store.
|
||||
*
|
||||
* @param existingMessageId The message with this ID is replaced if not `null`.
|
||||
* @return The message ID of the saved message.
|
||||
*/
|
||||
fun saveLocalMessage(folderId: Long, messageData: SaveMessageData, existingMessageId: Long? = null): Long
|
||||
|
||||
/**
|
||||
* Copy a message to another folder.
|
||||
*
|
||||
* @return The message's database ID in the destination folder.
|
||||
*/
|
||||
fun copyMessage(messageId: Long, destinationFolderId: Long): Long
|
||||
|
||||
/**
|
||||
* Copy messages to another folder.
|
||||
*
|
||||
* @return A mapping of the original message database ID to the new message database ID.
|
||||
*/
|
||||
fun copyMessages(messageIds: Collection<Long>, destinationFolderId: Long): Map<Long, Long> {
|
||||
return messageIds
|
||||
.map { messageId ->
|
||||
messageId to copyMessage(messageId, destinationFolderId)
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a message to another folder.
|
||||
*
|
||||
* @return The message's database ID in the destination folder. This will most likely be different from the
|
||||
* messageId passed to this function.
|
||||
*/
|
||||
fun moveMessage(messageId: Long, destinationFolderId: Long): Long
|
||||
|
||||
/**
|
||||
* Move messages to another folder.
|
||||
*
|
||||
* @return A mapping of the original message database ID to the new message database ID.
|
||||
*/
|
||||
fun moveMessages(messageIds: Collection<Long>, destinationFolderId: Long): Map<Long, Long> {
|
||||
return messageIds
|
||||
.map { messageId ->
|
||||
messageId to moveMessage(messageId, destinationFolderId)
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set message flags.
|
||||
*/
|
||||
fun setFlag(messageIds: Collection<Long>, flag: Flag, set: Boolean)
|
||||
|
||||
/**
|
||||
* Set or remove a flag on a message.
|
||||
*/
|
||||
fun setMessageFlag(folderId: Long, messageServerId: String, flag: Flag, set: Boolean)
|
||||
|
||||
/**
|
||||
* Set whether a message should be considered as new.
|
||||
*/
|
||||
fun setNewMessageState(folderId: Long, messageServerId: String, newMessage: Boolean)
|
||||
|
||||
/**
|
||||
* Clear the new message state for all messages.
|
||||
*/
|
||||
fun clearNewMessageState()
|
||||
|
||||
/**
|
||||
* Retrieve the server ID for a given message.
|
||||
*/
|
||||
fun getMessageServerId(messageId: Long): String?
|
||||
|
||||
/**
|
||||
* Retrieve the server IDs for the given messages.
|
||||
*
|
||||
* @return A mapping of the message database ID to the message server ID.
|
||||
*/
|
||||
fun getMessageServerIds(messageIds: Collection<Long>): Map<Long, String>
|
||||
|
||||
/**
|
||||
* Retrieve server IDs for all remote messages in the given folder.
|
||||
*/
|
||||
fun getMessageServerIds(folderId: Long): Set<String>
|
||||
|
||||
/**
|
||||
* Check if a message is present in the store.
|
||||
*/
|
||||
fun isMessagePresent(folderId: Long, messageServerId: String): Boolean
|
||||
|
||||
/**
|
||||
* Get the flags associated with a message.
|
||||
*/
|
||||
fun getMessageFlags(folderId: Long, messageServerId: String): Set<Flag>
|
||||
|
||||
/**
|
||||
* Retrieve server IDs and dates for all remote messages in the given folder.
|
||||
*/
|
||||
fun getAllMessagesAndEffectiveDates(folderId: Long): Map<String, Long?>
|
||||
|
||||
/**
|
||||
* Retrieve list of messages.
|
||||
*/
|
||||
fun <T> getMessages(
|
||||
selection: String,
|
||||
selectionArgs: Array<String>,
|
||||
sortOrder: String,
|
||||
messageMapper: MessageMapper<out T?>,
|
||||
): List<T>
|
||||
|
||||
/**
|
||||
* Retrieve threaded list of messages.
|
||||
*/
|
||||
fun <T> getThreadedMessages(
|
||||
selection: String,
|
||||
selectionArgs: Array<String>,
|
||||
sortOrder: String,
|
||||
messageMapper: MessageMapper<out T?>,
|
||||
): List<T>
|
||||
|
||||
/**
|
||||
* Retrieve list of messages in a thread.
|
||||
*/
|
||||
fun <T> getThread(threadId: Long, sortOrder: String, messageMapper: MessageMapper<out T?>): List<T>
|
||||
|
||||
/**
|
||||
* Retrieve the date of the oldest message in the given folder.
|
||||
*/
|
||||
fun getOldestMessageDate(folderId: Long): Date?
|
||||
|
||||
/**
|
||||
* Retrieve the header fields of a message.
|
||||
*/
|
||||
fun getHeaders(folderId: Long, messageServerId: String): List<Header>
|
||||
|
||||
/**
|
||||
* Retrieve selected header fields of a message.
|
||||
*/
|
||||
fun getHeaders(folderId: Long, messageServerId: String, headerNames: Set<String>): List<Header>
|
||||
|
||||
/**
|
||||
* Return the size of this message store in bytes.
|
||||
*/
|
||||
fun getSize(): Long
|
||||
|
||||
/**
|
||||
* Remove messages from the store.
|
||||
*/
|
||||
fun destroyMessages(folderId: Long, messageServerIds: Collection<String>)
|
||||
|
||||
/**
|
||||
* Create folders.
|
||||
*/
|
||||
@Throws(MessagingException::class)
|
||||
fun createFolders(folders: List<CreateFolderInfo>): Set<Long>
|
||||
|
||||
/**
|
||||
* Retrieve information about a folder.
|
||||
*
|
||||
* @param mapper A function to map the values read from the store to a domain-specific object.
|
||||
* @return The value returned by [mapper] or `null` if the folder wasn't found.
|
||||
*/
|
||||
fun <T> getFolder(folderId: Long, mapper: FolderMapper<T>): T?
|
||||
|
||||
/**
|
||||
* Retrieve information about a folder.
|
||||
*
|
||||
* @param mapper A function to map the values read from the store to a domain-specific object.
|
||||
* @return The value returned by [mapper] or `null` if the folder wasn't found.
|
||||
*/
|
||||
fun <T> getFolder(folderServerId: String, mapper: FolderMapper<T>): T?
|
||||
|
||||
/**
|
||||
* Retrieve folders.
|
||||
*
|
||||
* @param mapper A function to map the values read from the store to a domain-specific object.
|
||||
* @return A list of values returned by [mapper].
|
||||
*/
|
||||
@Throws(MessagingException::class)
|
||||
fun <T> getFolders(excludeLocalOnly: Boolean, mapper: FolderMapper<T>): List<T>
|
||||
|
||||
/**
|
||||
* Retrieve folders with their unread count.
|
||||
*
|
||||
* For the Outbox the total number of messages will be returned.
|
||||
*/
|
||||
fun <T> getDisplayFolders(includeHiddenFolders: Boolean, outboxFolderId: Long?, mapper: FolderMapper<T>): List<T>
|
||||
|
||||
/**
|
||||
* Check if all given folders are included in the Unified Inbox.
|
||||
*/
|
||||
fun areAllIncludedInUnifiedInbox(folderIds: Collection<Long>): Boolean
|
||||
|
||||
/**
|
||||
* Find a folder with the given server ID and return its store ID.
|
||||
*/
|
||||
fun getFolderId(folderServerId: String): Long?
|
||||
|
||||
/**
|
||||
* Find a folder with the given store ID and return its server ID.
|
||||
*/
|
||||
fun getFolderServerId(folderId: Long): String?
|
||||
|
||||
/**
|
||||
* Retrieve the number of messages in a folder.
|
||||
*/
|
||||
fun getMessageCount(folderId: Long): Int
|
||||
|
||||
/**
|
||||
* Retrieve the number of unread messages in a folder.
|
||||
*/
|
||||
fun getUnreadMessageCount(folderId: Long): Int
|
||||
|
||||
/**
|
||||
* Retrieve the number of unread messages matching [conditions].
|
||||
*/
|
||||
fun getUnreadMessageCount(conditions: SearchConditionTreeNode?): Int
|
||||
|
||||
/**
|
||||
* Retrieve the number of starred messages matching [conditions].
|
||||
*/
|
||||
fun getStarredMessageCount(conditions: SearchConditionTreeNode?): Int
|
||||
|
||||
/**
|
||||
* Update a folder's name and type.
|
||||
*
|
||||
* @throws MessagingException in case it fails changing the folder in the local database.
|
||||
*/
|
||||
@Throws(MessagingException::class)
|
||||
fun changeFolder(folderServerId: String, name: String, type: FolderType)
|
||||
|
||||
/**
|
||||
* Update settings of a single folder.
|
||||
*/
|
||||
fun updateFolderSettings(folderDetails: FolderDetails)
|
||||
|
||||
/**
|
||||
* Update the "integrate" setting of a folder.
|
||||
*/
|
||||
fun setIncludeInUnifiedInbox(folderId: Long, includeInUnifiedInbox: Boolean)
|
||||
|
||||
/**
|
||||
* Update the "visible" setting of a folder.
|
||||
*/
|
||||
fun setVisible(folderId: Long, visible: Boolean)
|
||||
|
||||
/**
|
||||
* Update the sync setting of a folder.
|
||||
*/
|
||||
fun setSyncEnabled(folderId: Long, enable: Boolean)
|
||||
|
||||
/**
|
||||
* Update the push setting of a folder.
|
||||
*/
|
||||
fun setPushEnabled(folderId: Long, enable: Boolean)
|
||||
|
||||
/**
|
||||
* Update the notifications setting of a folder.
|
||||
*/
|
||||
fun setNotificationsEnabled(folderId: Long, enable: Boolean)
|
||||
|
||||
/**
|
||||
* Get the 'more messages' state of a folder.
|
||||
*/
|
||||
fun hasMoreMessages(folderId: Long): MoreMessages?
|
||||
|
||||
/**
|
||||
* Update the 'more messages' state of a folder.
|
||||
*/
|
||||
fun setMoreMessages(folderId: Long, moreMessages: MoreMessages)
|
||||
|
||||
/**
|
||||
* Update the time when the folder was last checked for new messages.
|
||||
*/
|
||||
fun setLastChecked(folderId: Long, timestamp: Long)
|
||||
|
||||
/**
|
||||
* Update folder status message.
|
||||
*/
|
||||
fun setStatus(folderId: Long, status: String?)
|
||||
|
||||
/**
|
||||
* Update a folder's "visible limit" value.
|
||||
*/
|
||||
fun setVisibleLimit(folderId: Long, visibleLimit: Int)
|
||||
|
||||
/**
|
||||
* Disable the push setting of all folders.
|
||||
*/
|
||||
fun setPushDisabled()
|
||||
|
||||
/**
|
||||
* Returns `true` if there is at least one folder with the push setting enabled.
|
||||
*/
|
||||
fun hasPushEnabledFolder(): Boolean
|
||||
|
||||
/**
|
||||
* Delete folders.
|
||||
*/
|
||||
fun deleteFolders(folderServerIds: List<String>)
|
||||
|
||||
/**
|
||||
* Retrieve a string property by name.
|
||||
*
|
||||
* For everything that doesn't fit into existing structures this message store offers a generic key/value store.
|
||||
*/
|
||||
fun getExtraString(name: String): String?
|
||||
|
||||
/**
|
||||
* Create or update a string property.
|
||||
*/
|
||||
fun setExtraString(name: String, value: String)
|
||||
|
||||
/**
|
||||
* Retrieve a number property by name.
|
||||
*/
|
||||
fun getExtraNumber(name: String): Long?
|
||||
|
||||
/**
|
||||
* Create or update a number property.
|
||||
*/
|
||||
fun setExtraNumber(name: String, value: Long)
|
||||
|
||||
/**
|
||||
* Retrieve a string property associated with the given folder.
|
||||
*/
|
||||
fun getFolderExtraString(folderId: Long, name: String): String?
|
||||
|
||||
/**
|
||||
* Create or update a string property associated with the given folder.
|
||||
*/
|
||||
fun setFolderExtraString(folderId: Long, name: String, value: String?)
|
||||
|
||||
/**
|
||||
* Retrieve a number property associated with the given folder.
|
||||
*/
|
||||
fun getFolderExtraNumber(folderId: Long, name: String): Long?
|
||||
|
||||
/**
|
||||
* Create or update a number property associated with the given folder.
|
||||
*/
|
||||
fun setFolderExtraNumber(folderId: Long, name: String, value: Long)
|
||||
|
||||
/**
|
||||
* Optimize the message store with the goal of using the minimal amount of disk space.
|
||||
*/
|
||||
fun compact()
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import net.thunderbird.core.android.account.LegacyAccount
|
||||
|
||||
interface MessageStoreFactory {
|
||||
fun create(account: LegacyAccount): ListenableMessageStore
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import net.thunderbird.core.android.account.AccountManager
|
||||
import net.thunderbird.core.android.account.LegacyAccount
|
||||
|
||||
class MessageStoreManager(
|
||||
private val accountManager: AccountManager,
|
||||
private val messageStoreFactory: MessageStoreFactory,
|
||||
) {
|
||||
private val messageStores = ConcurrentHashMap<String, ListenableMessageStore>()
|
||||
|
||||
init {
|
||||
accountManager.addAccountRemovedListener { account ->
|
||||
removeMessageStore(account.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessageStore(accountUuid: String): ListenableMessageStore {
|
||||
val account = accountManager.getAccount(accountUuid) ?: error("Account not found: $accountUuid")
|
||||
return getMessageStore(account)
|
||||
}
|
||||
|
||||
fun getMessageStore(account: LegacyAccount): ListenableMessageStore {
|
||||
return messageStores.getOrPut(account.uuid) { messageStoreFactory.create(account) }
|
||||
}
|
||||
|
||||
private fun removeMessageStore(accountUuid: String) {
|
||||
messageStores.remove(accountUuid)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.legacy.mailstore;
|
||||
|
||||
|
||||
public enum MoreMessages {
|
||||
UNKNOWN("unknown"),
|
||||
FALSE("false"),
|
||||
TRUE("true");
|
||||
|
||||
private final String databaseName;
|
||||
|
||||
MoreMessages(String databaseName) {
|
||||
this.databaseName = databaseName;
|
||||
}
|
||||
|
||||
public static MoreMessages fromDatabaseName(String databaseName) {
|
||||
for (MoreMessages value : MoreMessages.values()) {
|
||||
if (value.databaseName.equals(databaseName)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown value: " + databaseName);
|
||||
}
|
||||
|
||||
public String getDatabaseName() {
|
||||
return databaseName;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import net.thunderbird.feature.mail.folder.api.FolderType
|
||||
import com.fsck.k9.mail.FolderType as RemoteFolderType
|
||||
|
||||
object RemoteFolderTypeMapper {
|
||||
|
||||
fun RemoteFolderType.toFolderType(): FolderType = when (this) {
|
||||
RemoteFolderType.REGULAR -> FolderType.REGULAR
|
||||
RemoteFolderType.INBOX -> FolderType.INBOX
|
||||
RemoteFolderType.OUTBOX -> FolderType.REGULAR // We currently don't support remote Outbox folders
|
||||
RemoteFolderType.DRAFTS -> FolderType.DRAFTS
|
||||
RemoteFolderType.SENT -> FolderType.SENT
|
||||
RemoteFolderType.TRASH -> FolderType.TRASH
|
||||
RemoteFolderType.SPAM -> FolderType.SPAM
|
||||
RemoteFolderType.ARCHIVE -> FolderType.ARCHIVE
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import app.k9mail.legacy.message.extractors.PreviewResult
|
||||
import com.fsck.k9.mail.Message
|
||||
import com.fsck.k9.mail.MessageDownloadState
|
||||
|
||||
data class SaveMessageData(
|
||||
val message: Message,
|
||||
val subject: String?,
|
||||
val date: Long,
|
||||
val internalDate: Long,
|
||||
val downloadState: MessageDownloadState,
|
||||
val attachmentCount: Int,
|
||||
val previewResult: PreviewResult,
|
||||
val textForSearchIndex: String? = null,
|
||||
val encryptionType: String?,
|
||||
)
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package app.k9mail.legacy.mailstore
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isSameInstanceAs
|
||||
import net.thunderbird.core.android.account.AccountManager
|
||||
import net.thunderbird.core.android.account.AccountRemovedListener
|
||||
import net.thunderbird.core.android.account.LegacyAccount
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.KStubbing
|
||||
import org.mockito.kotlin.argumentCaptor
|
||||
import org.mockito.kotlin.doNothing
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
class MessageStoreManagerTest {
|
||||
private val account = LegacyAccount("00000000-0000-4000-0000-000000000000")
|
||||
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
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MessageStore instance is reused`() {
|
||||
val accountManager = mock<AccountManager>()
|
||||
val messageStoreManager = MessageStoreManager(accountManager, messageStoreFactory)
|
||||
|
||||
assertThat(messageStoreManager.getMessageStore(account)).isSameInstanceAs(messageStore1)
|
||||
assertThat(messageStoreManager.getMessageStore(account)).isSameInstanceAs(messageStore1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MessageStore instance is removed when account is removed`() {
|
||||
val listenerCaptor = argumentCaptor<AccountRemovedListener>()
|
||||
val accountManager = mock<AccountManager> {
|
||||
doNothingOn { addAccountRemovedListener(listenerCaptor.capture()) }
|
||||
}
|
||||
val messageStoreManager = MessageStoreManager(accountManager, messageStoreFactory)
|
||||
|
||||
assertThat(messageStoreManager.getMessageStore(account)).isSameInstanceAs(messageStore1)
|
||||
|
||||
listenerCaptor.firstValue.onAccountRemoved(account)
|
||||
|
||||
assertThat(messageStoreManager.getMessageStore(account)).isSameInstanceAs(messageStore2)
|
||||
}
|
||||
|
||||
private fun <T : Any> KStubbing<T>.doNothingOn(block: T.() -> Any) {
|
||||
doNothing().whenever(mock).block()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package app.k9mail.legacy.mailstore;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
|
||||
public class MoreMessagesTest {
|
||||
private static final String ERROR_MESSAGE = "The return value of getDatabaseName() is used in the database and " +
|
||||
"must not be changed without data migration.";
|
||||
|
||||
|
||||
@Test
|
||||
public void UNKNOWN_getDatabaseName_shouldReturnUnknown() throws Exception {
|
||||
assertEquals(ERROR_MESSAGE, "unknown", MoreMessages.UNKNOWN.getDatabaseName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TRUE_getDatabaseName_shouldReturnTrue() throws Exception {
|
||||
assertEquals(ERROR_MESSAGE, "true", MoreMessages.TRUE.getDatabaseName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void FALSE_getDatabaseName_shouldReturnFalse() throws Exception {
|
||||
assertEquals(ERROR_MESSAGE, "false", MoreMessages.FALSE.getDatabaseName());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue