Merge pull request #5234 from k9mail/BackendStorage_to_MessageStore

This commit is contained in:
cketti 2021-04-08 18:35:26 +02:00 committed by GitHub
commit 24f25ca1d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 223 additions and 60 deletions

View file

@ -19,5 +19,6 @@ interface FolderDetailsAccessor {
val displayClass: FolderClass
val notifyClass: FolderClass
val pushClass: FolderClass
val visibleLimit: Int
val messageCount: Int
}

View file

@ -4,7 +4,6 @@ import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
import androidx.core.database.getLongOrNull
import androidx.core.database.getStringOrNull
import com.fsck.k9.K9
import com.fsck.k9.backend.api.BackendFolder
import com.fsck.k9.backend.api.BackendFolder.MoreMessages
import com.fsck.k9.mail.Flag
@ -13,80 +12,43 @@ import java.util.Date
class K9BackendFolder(
private val localStore: LocalStore,
private val messageStore: MessageStore,
private val folderServerId: String
) : BackendFolder {
private val database = localStore.database
private val databaseId: String
private val folderId: Long
private val localFolder = localStore.getFolder(folderServerId)
override val name: String
override val visibleLimit: Int
init {
data class Init(val databaseId: String, val name: String, val visibleLimit: Int)
data class Init(val folderId: Long, val name: String, val visibleLimit: Int)
val init = database.query(
"folders",
arrayOf("id", "name", "visible_limit"),
"server_id = ?",
folderServerId
) { cursor ->
if (cursor.moveToFirst()) {
Init(
databaseId = cursor.getString(0),
name = cursor.getString(1),
visibleLimit = cursor.getInt(2)
)
} else {
throw IllegalStateException("Couldn't find folder $folderServerId")
}
}
val init = messageStore.getFolder(folderServerId) { folder ->
Init(
folderId = folder.id,
name = folder.name,
visibleLimit = folder.visibleLimit
)
} ?: error("Couldn't find folder $folderServerId")
databaseId = init.databaseId
databaseId = init.folderId.toString()
folderId = init.folderId
name = init.name
visibleLimit = init.visibleLimit
}
override fun getLastUid(): Long? {
return database.rawQuery("SELECT MAX(uid) FROM messages WHERE folder_id = ?", databaseId) { cursor ->
if (cursor.moveToFirst()) {
cursor.getLongOrNull(0)
} else {
null
}
}
return messageStore.getLastUid(folderId)
}
override fun getMessageServerIds(): Set<String> {
return database.rawQuery(
"SELECT uid FROM messages" +
" WHERE empty = 0 AND deleted = 0 AND folder_id = ? AND uid NOT LIKE '${K9.LOCAL_UID_PREFIX}%'" +
" ORDER BY date DESC",
databaseId
) { cursor ->
val result = mutableSetOf<String>()
while (cursor.moveToNext()) {
val uid = cursor.getString(0)
result.add(uid)
}
result
}
return messageStore.getMessageServerIds(folderId)
}
override fun getAllMessagesAndEffectiveDates(): Map<String, Long?> {
return database.rawQuery(
"SELECT uid, date FROM messages" +
" WHERE empty = 0 AND deleted = 0 AND folder_id = ? AND uid NOT LIKE '${K9.LOCAL_UID_PREFIX}%'" +
" ORDER BY date DESC",
databaseId
) { cursor ->
val result = mutableMapOf<String, Long?>()
while (cursor.moveToNext()) {
val uid = cursor.getString(0)
val date = cursor.getLongOrNull(1)
result[uid] = date
}
result
}
return messageStore.getAllMessagesAndEffectiveDates(folderId)
}
// TODO: Move implementation from LocalFolder to this class

View file

@ -13,7 +13,7 @@ class K9BackendStorage(
private val listeners: List<BackendFoldersRefreshListener>
) : BackendStorage {
override fun getFolder(folderServerId: String): BackendFolder {
return K9BackendFolder(localStore, folderServerId)
return K9BackendFolder(localStore, messageStore, folderServerId)
}
override fun getFolderServerIds(): List<String> {

View file

@ -51,11 +51,26 @@ interface MessageStore {
*/
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>
/**
* Retrieve server IDs and dates for all remote messages in the given folder.
*/
fun getAllMessagesAndEffectiveDates(folderId: Long): Map<String, Long?>
/**
* Retrieve the header fields of a message.
*/
fun getHeaders(folderId: Long, messageServerId: String): List<Header>
/**
* Get highest UID (message server ID)
*/
fun getLastUid(folderId: Long): Long?
/**
* Create folders.
*/
@ -69,6 +84,14 @@ interface MessageStore {
*/
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.
*

View file

@ -126,7 +126,7 @@ class K9BackendFolderTest : K9RobolectricTest() {
val folderServerIds = backendStorage.getFolderServerIds()
assertTrue(FOLDER_SERVER_ID in folderServerIds)
return K9BackendFolder(localStore, FOLDER_SERVER_ID)
return K9BackendFolder(localStore, messageStore, FOLDER_SERVER_ID)
}
fun createMessageInBackendFolder(messageServerId: String, flags: Set<Flag> = emptySet()) {

View file

@ -49,10 +49,22 @@ class K9MessageStore(
return retrieveMessageOperations.getMessageServerIds(messageIds)
}
override fun getMessageServerIds(folderId: Long): Set<String> {
return retrieveMessageOperations.getMessageServerIds(folderId)
}
override fun getAllMessagesAndEffectiveDates(folderId: Long): Map<String, Long?> {
return retrieveMessageOperations.getAllMessagesAndEffectiveDates(folderId)
}
override fun getHeaders(folderId: Long, messageServerId: String): List<Header> {
return retrieveMessageOperations.getHeaders(folderId, messageServerId)
}
override fun getLastUid(folderId: Long): Long? {
return retrieveMessageOperations.getLastUid(folderId)
}
override fun createFolders(folders: List<CreateFolderInfo>) {
createFolderOperations.createFolders(folders)
}
@ -61,6 +73,10 @@ class K9MessageStore(
return retrieveFolderOperations.getFolder(folderId, mapper)
}
override fun <T> getFolder(folderServerId: String, mapper: FolderMapper<T>): T? {
return retrieveFolderOperations.getFolder(folderServerId, mapper)
}
override fun <T> getFolders(excludeLocalOnly: Boolean, mapper: FolderMapper<T>): List<T> {
return retrieveFolderOperations.getFolders(excludeLocalOnly, mapper)
}

View file

@ -12,12 +12,28 @@ import com.fsck.k9.mailstore.toFolderType
internal class RetrieveFolderOperations(private val lockableDatabase: LockableDatabase) {
fun <T> getFolder(folderId: Long, mapper: FolderMapper<T>): T? {
return getFolder(
selection = "id = ?",
selectionArguments = arrayOf(folderId.toString()),
mapper = mapper
)
}
fun <T> getFolder(folderServerId: String, mapper: FolderMapper<T>): T? {
return getFolder(
selection = "server_id = ?",
selectionArguments = arrayOf(folderServerId),
mapper = mapper
)
}
private fun <T> getFolder(selection: String, selectionArguments: Array<String>, mapper: FolderMapper<T>): T? {
return lockableDatabase.execute(false) { db ->
db.query(
"folders",
FOLDER_COLUMNS,
"id = ?",
arrayOf(folderId.toString()),
selection,
selectionArguments,
null,
null,
null
@ -142,8 +158,11 @@ private class CursorFolderAccessor(val cursor: Cursor) : FolderDetailsAccessor {
override val pushClass: FolderClass
get() = FolderClass.valueOf(cursor.getString(10))
override val messageCount: Int
override val visibleLimit: Int
get() = cursor.getInt(11)
override val messageCount: Int
get() = cursor.getInt(12)
}
private val FOLDER_COLUMNS = arrayOf(
@ -157,5 +176,6 @@ private val FOLDER_COLUMNS = arrayOf(
"poll_class",
"display_class",
"notify_class",
"push_class"
"push_class",
"visible_limit"
)

View file

@ -1,5 +1,7 @@
package com.fsck.k9.storage.messages
import androidx.core.database.getLongOrNull
import com.fsck.k9.K9
import com.fsck.k9.mail.Header
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.internet.MimeHeader
@ -52,13 +54,47 @@ internal class RetrieveMessageOperations(private val lockableDatabase: LockableD
databaseIdToServerIdMapping[databaseId] = serverId
}
}
Unit
}
databaseIdToServerIdMapping
}
}
fun getMessageServerIds(folderId: Long): Set<String> {
return lockableDatabase.execute(false) { database ->
database.rawQuery(
"SELECT uid FROM messages" +
" WHERE empty = 0 AND deleted = 0 AND folder_id = ? AND uid NOT LIKE '${K9.LOCAL_UID_PREFIX}%'",
arrayOf(folderId.toString())
).use { cursor ->
val result = mutableSetOf<String>()
while (cursor.moveToNext()) {
val uid = cursor.getString(0)
result.add(uid)
}
result
}
}
}
fun getAllMessagesAndEffectiveDates(folderId: Long): Map<String, Long?> {
return lockableDatabase.execute(false) { database ->
database.rawQuery(
"SELECT uid, date FROM messages" +
" WHERE empty = 0 AND deleted = 0 AND folder_id = ? AND uid NOT LIKE '${K9.LOCAL_UID_PREFIX}%'",
arrayOf(folderId.toString())
).use { cursor ->
val result = mutableMapOf<String, Long?>()
while (cursor.moveToNext()) {
val uid = cursor.getString(0)
val date = cursor.getLongOrNull(1)
result[uid] = date
}
result
}
}
}
fun getHeaders(folderId: Long, messageServerId: String): List<Header> {
return lockableDatabase.execute(false) { database ->
database.rawQuery(
@ -80,4 +116,19 @@ internal class RetrieveMessageOperations(private val lockableDatabase: LockableD
}
}
}
fun getLastUid(folderId: Long): Long? {
return lockableDatabase.execute(false) { database ->
database.rawQuery(
"SELECT MAX(uid) FROM messages WHERE folder_id = ?",
arrayOf(folderId.toString())
).use { cursor ->
if (cursor.moveToFirst()) {
cursor.getLongOrNull(0)
} else {
null
}
}
}
}
}

View file

@ -52,6 +52,46 @@ class RetrieveFolderOperationsTest : RobolectricTest() {
assertThat(result).isNull()
}
@Test
fun `get folder by server ID`() {
val folderId = sqliteDatabase.createFolder(
name = "Folder Name",
type = "inbox",
serverId = "folder1",
isLocalOnly = false,
integrate = true,
inTopGroup = true,
displayClass = "FIRST_CLASS",
syncClass = "FIRST_CLASS",
notifyClass = "NO_CLASS",
pushClass = "NO_CLASS"
)
val result = retrieveFolderOperations.getFolder("folder1") { folder ->
assertThat(folder.id).isEqualTo(folderId)
assertThat(folder.name).isEqualTo("Folder Name")
assertThat(folder.type).isEqualTo(FolderType.INBOX)
assertThat(folder.serverId).isEqualTo("folder1")
assertThat(folder.isLocalOnly).isEqualTo(false)
assertThat(folder.isIntegrate).isEqualTo(true)
assertThat(folder.isInTopGroup).isEqualTo(true)
assertThat(folder.displayClass).isEqualTo(FolderClass.FIRST_CLASS)
assertThat(folder.syncClass).isEqualTo(FolderClass.FIRST_CLASS)
assertThat(folder.notifyClass).isEqualTo(FolderClass.NO_CLASS)
assertThat(folder.pushClass).isEqualTo(FolderClass.NO_CLASS)
true
}
assertThat(result).isTrue()
}
@Test
fun `get non-existent folder by server ID should return null`() {
val result = retrieveFolderOperations.getFolder("folder_id") { "failed" }
assertThat(result).isNull()
}
@Test
fun `get folders should return all fields`() {
val folderId = sqliteDatabase.createFolder(

View file

@ -48,6 +48,35 @@ class RetrieveMessageOperationsTest : RobolectricTest() {
)
}
@Test
fun `get all message server ids`() {
sqliteDatabase.createMessage(folderId = 1, uid = "uid1")
sqliteDatabase.createMessage(folderId = 1, uid = "K9LOCAL:1")
sqliteDatabase.createMessage(folderId = 1, uid = "uid3")
sqliteDatabase.createMessage(folderId = 1, uid = "uid4")
val messageServerIds = retrieveMessageOperations.getMessageServerIds(folderId = 1)
assertThat(messageServerIds).isEqualTo(setOf("uid1", "uid3", "uid4"))
}
@Test
fun `get all message server ids and dates`() {
sqliteDatabase.createMessage(folderId = 1, uid = "uid1", date = 23)
sqliteDatabase.createMessage(folderId = 1, uid = "K9LOCAL:1")
sqliteDatabase.createMessage(folderId = 1, uid = "uid3", date = 42)
sqliteDatabase.createMessage(folderId = 1, uid = "uid4", deleted = true)
val result = retrieveMessageOperations.getAllMessagesAndEffectiveDates(folderId = 1)
assertThat(result).isEqualTo(
mapOf(
"uid1" to 23L,
"uid3" to 42L
)
)
}
@Test
fun `get headers`() {
val messagePartId = sqliteDatabase.createMessagePart(
@ -73,4 +102,25 @@ class RetrieveMessageOperationsTest : RobolectricTest() {
)
)
}
@Test
fun `get highest message uid`() {
val folderId = sqliteDatabase.createFolder()
sqliteDatabase.createMessage(uid = "42", folderId = folderId)
sqliteDatabase.createMessage(uid = "23", folderId = folderId)
sqliteDatabase.createMessage(uid = "27", folderId = folderId)
val highestUid = retrieveMessageOperations.getLastUid(folderId)
assertThat(highestUid).isEqualTo(42)
}
@Test
fun `get highest message uid should return null if there are no messages`() {
val folderId = sqliteDatabase.createFolder()
val highestUid = retrieveMessageOperations.getLastUid(folderId)
assertThat(highestUid).isNull()
}
}