Merge pull request #5234 from k9mail/BackendStorage_to_MessageStore
This commit is contained in:
commit
24f25ca1d9
10 changed files with 223 additions and 60 deletions
|
@ -19,5 +19,6 @@ interface FolderDetailsAccessor {
|
|||
val displayClass: FolderClass
|
||||
val notifyClass: FolderClass
|
||||
val pushClass: FolderClass
|
||||
val visibleLimit: Int
|
||||
val messageCount: Int
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue