Loading app/core/src/main/java/com/fsck/k9/mailstore/K9BackendFolder.kt +3 −82 Original line number Diff line number Diff line Loading @@ -12,9 +12,9 @@ import java.util.Date import com.fsck.k9.mailstore.MoreMessages as StoreMoreMessages class K9BackendFolder( private val localStore: LocalStore, localStore: LocalStore, private val messageStore: MessageStore, private val folderServerId: String folderServerId: String ) : BackendFolder { private val database = localStore.database private val databaseId: String Loading Loading @@ -88,40 +88,7 @@ class K9BackendFolder( } override fun setMessageFlag(messageServerId: String, flag: Flag, value: Boolean) { when (flag) { Flag.DELETED -> database.setMessagesBoolean(messageServerId, "deleted", value) Flag.SEEN -> database.setMessagesBoolean(messageServerId, "read", value) Flag.FLAGGED -> database.setMessagesBoolean(messageServerId, "flagged", value) Flag.ANSWERED -> database.setMessagesBoolean(messageServerId, "answered", value) Flag.FORWARDED -> database.setMessagesBoolean(messageServerId, "forwarded", value) else -> { val flagsColumnValue = database.getString( table = "messages", column = "flags", selection = "folder_id = ? AND uid = ?", selectionArgs = *arrayOf(databaseId, messageServerId) ) ?: "" val flags = flagsColumnValue.split(',').toMutableSet() if (value) { flags.add(flag.toString()) } else { flags.remove(flag.toString()) } val serializedFlags = flags.joinToString(separator = ",") database.setString( table = "messages", column = "flags", selection = "folder_id = ? AND uid = ?", selectionArgs = *arrayOf(databaseId, messageServerId), value = serializedFlags ) } } localStore.notifyChange() messageStore.setMessageFlag(folderId, messageServerId, flag, value) } // TODO: Move implementation from LocalFolder to this class Loading Loading @@ -220,52 +187,6 @@ class K9BackendFolder( } } private fun LockableDatabase.getString( table: String = "folders", column: String, selection: String = "id = ?", vararg selectionArgs: String = arrayOf(databaseId) ): String? { return execute(false) { db -> val cursor = db.query(table, arrayOf(column), selection, selectionArgs, null, null, null) cursor.use { if (it.moveToFirst()) { it.getStringOrNull(0) } else { throw IllegalStateException("Couldn't find value for column $table.$column") } } } } private fun LockableDatabase.setString( table: String = "folders", column: String, value: String?, selection: String = "id = ?", vararg selectionArgs: String = arrayOf(databaseId) ) { execute(false) { db -> val contentValues = ContentValues().apply { put(column, value) } db.update(table, contentValues, selection, selectionArgs) } } private fun LockableDatabase.setMessagesBoolean( messageServerId: String, column: String, value: Boolean ) { execute(false) { db -> val contentValues = ContentValues().apply { put(column, if (value) 1 else 0) } db.update("messages", contentValues, "folder_id = ? AND uid = ?", arrayOf(databaseId, messageServerId)) } } private fun StoreMoreMessages.toMoreMessages(): MoreMessages = when (this) { StoreMoreMessages.UNKNOWN -> MoreMessages.UNKNOWN StoreMoreMessages.FALSE -> MoreMessages.FALSE Loading app/core/src/main/java/com/fsck/k9/mailstore/MessageStore.kt +5 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,11 @@ interface MessageStore { */ 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) /** * Retrieve the server ID for a given message. */ Loading app/storage/src/main/java/com/fsck/k9/storage/messages/FlagMessageOperations.kt +67 −0 Original line number Diff line number Diff line package com.fsck.k9.storage.messages import android.content.ContentValues import android.database.sqlite.SQLiteDatabase import com.fsck.k9.mail.Flag import com.fsck.k9.mailstore.LockableDatabase Loading @@ -18,6 +19,17 @@ internal class FlagMessageOperations(private val lockableDatabase: LockableDatab } } fun setMessageFlag(folderId: Long, messageServerId: String, flag: Flag, set: Boolean) { when (flag) { Flag.DELETED -> setBoolean(folderId, messageServerId, "deleted", set) Flag.SEEN -> setBoolean(folderId, messageServerId, "read", set) Flag.FLAGGED -> setBoolean(folderId, messageServerId, "flagged", set) Flag.ANSWERED -> setBoolean(folderId, messageServerId, "answered", set) Flag.FORWARDED -> setBoolean(folderId, messageServerId, "forwarded", set) else -> rebuildFlagsColumnValue(folderId, messageServerId, flag, set) } } private fun setSpecialFlags(messageIds: Collection<Long>, flag: Flag, set: Boolean) { val columnName = when (flag) { Flag.SEEN -> "read" Loading Loading @@ -45,4 +57,59 @@ internal class FlagMessageOperations(private val lockableDatabase: LockableDatab private fun rebuildFlagsColumnValue(messageIds: Collection<Long>, flag: Flag, set: Boolean) { throw UnsupportedOperationException("not implemented") } private fun rebuildFlagsColumnValue(folderId: Long, messageServerId: String, flag: Flag, set: Boolean) { lockableDatabase.execute(true) { database -> val oldFlags = database.readFlagsColumn(folderId, messageServerId) val newFlags = if (set) oldFlags + flag else oldFlags - flag val newFlagsString = newFlags.joinToString(separator = ",") val values = ContentValues().apply { put("flags", newFlagsString) } database.update( "messages", values, "folder_id = ? AND uid = ?", arrayOf(folderId.toString(), messageServerId) ) } } private fun SQLiteDatabase.readFlagsColumn(folderId: Long, messageServerId: String): Set<Flag> { return query( "messages", arrayOf("flags"), "folder_id = ? AND uid = ?", arrayOf(folderId.toString(), messageServerId), null, null, null ).use { cursor -> if (!cursor.moveToFirst()) error("Message not found $folderId:$messageServerId") if (!cursor.isNull(0)) { cursor.getString(0).split(',').map { flagString -> Flag.valueOf(flagString) }.toSet() } else { emptySet() } } } private fun setBoolean(folderId: Long, messageServerId: String, columnName: String, value: Boolean) { lockableDatabase.execute(false) { database -> val values = ContentValues().apply { put(columnName, if (value) 1 else 0) } database.update( "messages", values, "folder_id = ? AND uid = ?", arrayOf(folderId.toString(), messageServerId) ) } } } app/storage/src/main/java/com/fsck/k9/storage/messages/K9MessageStore.kt +5 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,11 @@ class K9MessageStore( localStore.notifyChange() } override fun setMessageFlag(folderId: Long, messageServerId: String, flag: Flag, set: Boolean) { flagMessageOperations.setMessageFlag(folderId, messageServerId, flag, set) localStore.notifyChange() } override fun getMessageServerId(messageId: Long): String { return retrieveMessageOperations.getMessageServerId(messageId) } Loading app/storage/src/test/java/com/fsck/k9/storage/messages/FlagMessageOperationsTest.kt +41 −0 Original line number Diff line number Diff line Loading @@ -53,4 +53,45 @@ class FlagMessageOperationsTest : RobolectricTest() { assertThat(otherMessages).hasSize(1) assertThat(otherMessages.all { it.read == 0 }).isTrue() } @Test fun `mark message as read`() { sqliteDatabase.createMessage(folderId = 1, uid = "uid1", read = false) flagMessageOperations.setMessageFlag(folderId = 1, messageServerId = "uid1", Flag.SEEN, true) val message = sqliteDatabase.readMessages().first() assertThat(message.read).isEqualTo(1) } @Test fun `mark message as unread`() { sqliteDatabase.createMessage(folderId = 1, uid = "uid1", read = true) flagMessageOperations.setMessageFlag(folderId = 1, messageServerId = "uid1", Flag.SEEN, false) val message = sqliteDatabase.readMessages().first() assertThat(message.read).isEqualTo(0) } @Test fun `mark message as X_DOWNLOADED_FULL`() { sqliteDatabase.createMessage(folderId = 1, uid = "uid1", flags = "X_SUBJECT_DECRYPTED") flagMessageOperations.setMessageFlag(folderId = 1, messageServerId = "uid1", Flag.X_DOWNLOADED_FULL, true) val message = sqliteDatabase.readMessages().first() val readFlags = message.flags!!.split(',').toSet() assertThat(readFlags).isEqualTo(setOf("X_SUBJECT_DECRYPTED", "X_DOWNLOADED_FULL")) } @Test fun `remove X_DOWNLOADED_FULL flag`() { sqliteDatabase.createMessage(folderId = 1, uid = "uid1", flags = "X_DOWNLOADED_FULL") flagMessageOperations.setMessageFlag(folderId = 1, messageServerId = "uid1", Flag.X_DOWNLOADED_FULL, false) val message = sqliteDatabase.readMessages().first() assertThat(message.flags).isEqualTo("") } } Loading
app/core/src/main/java/com/fsck/k9/mailstore/K9BackendFolder.kt +3 −82 Original line number Diff line number Diff line Loading @@ -12,9 +12,9 @@ import java.util.Date import com.fsck.k9.mailstore.MoreMessages as StoreMoreMessages class K9BackendFolder( private val localStore: LocalStore, localStore: LocalStore, private val messageStore: MessageStore, private val folderServerId: String folderServerId: String ) : BackendFolder { private val database = localStore.database private val databaseId: String Loading Loading @@ -88,40 +88,7 @@ class K9BackendFolder( } override fun setMessageFlag(messageServerId: String, flag: Flag, value: Boolean) { when (flag) { Flag.DELETED -> database.setMessagesBoolean(messageServerId, "deleted", value) Flag.SEEN -> database.setMessagesBoolean(messageServerId, "read", value) Flag.FLAGGED -> database.setMessagesBoolean(messageServerId, "flagged", value) Flag.ANSWERED -> database.setMessagesBoolean(messageServerId, "answered", value) Flag.FORWARDED -> database.setMessagesBoolean(messageServerId, "forwarded", value) else -> { val flagsColumnValue = database.getString( table = "messages", column = "flags", selection = "folder_id = ? AND uid = ?", selectionArgs = *arrayOf(databaseId, messageServerId) ) ?: "" val flags = flagsColumnValue.split(',').toMutableSet() if (value) { flags.add(flag.toString()) } else { flags.remove(flag.toString()) } val serializedFlags = flags.joinToString(separator = ",") database.setString( table = "messages", column = "flags", selection = "folder_id = ? AND uid = ?", selectionArgs = *arrayOf(databaseId, messageServerId), value = serializedFlags ) } } localStore.notifyChange() messageStore.setMessageFlag(folderId, messageServerId, flag, value) } // TODO: Move implementation from LocalFolder to this class Loading Loading @@ -220,52 +187,6 @@ class K9BackendFolder( } } private fun LockableDatabase.getString( table: String = "folders", column: String, selection: String = "id = ?", vararg selectionArgs: String = arrayOf(databaseId) ): String? { return execute(false) { db -> val cursor = db.query(table, arrayOf(column), selection, selectionArgs, null, null, null) cursor.use { if (it.moveToFirst()) { it.getStringOrNull(0) } else { throw IllegalStateException("Couldn't find value for column $table.$column") } } } } private fun LockableDatabase.setString( table: String = "folders", column: String, value: String?, selection: String = "id = ?", vararg selectionArgs: String = arrayOf(databaseId) ) { execute(false) { db -> val contentValues = ContentValues().apply { put(column, value) } db.update(table, contentValues, selection, selectionArgs) } } private fun LockableDatabase.setMessagesBoolean( messageServerId: String, column: String, value: Boolean ) { execute(false) { db -> val contentValues = ContentValues().apply { put(column, if (value) 1 else 0) } db.update("messages", contentValues, "folder_id = ? AND uid = ?", arrayOf(databaseId, messageServerId)) } } private fun StoreMoreMessages.toMoreMessages(): MoreMessages = when (this) { StoreMoreMessages.UNKNOWN -> MoreMessages.UNKNOWN StoreMoreMessages.FALSE -> MoreMessages.FALSE Loading
app/core/src/main/java/com/fsck/k9/mailstore/MessageStore.kt +5 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,11 @@ interface MessageStore { */ 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) /** * Retrieve the server ID for a given message. */ Loading
app/storage/src/main/java/com/fsck/k9/storage/messages/FlagMessageOperations.kt +67 −0 Original line number Diff line number Diff line package com.fsck.k9.storage.messages import android.content.ContentValues import android.database.sqlite.SQLiteDatabase import com.fsck.k9.mail.Flag import com.fsck.k9.mailstore.LockableDatabase Loading @@ -18,6 +19,17 @@ internal class FlagMessageOperations(private val lockableDatabase: LockableDatab } } fun setMessageFlag(folderId: Long, messageServerId: String, flag: Flag, set: Boolean) { when (flag) { Flag.DELETED -> setBoolean(folderId, messageServerId, "deleted", set) Flag.SEEN -> setBoolean(folderId, messageServerId, "read", set) Flag.FLAGGED -> setBoolean(folderId, messageServerId, "flagged", set) Flag.ANSWERED -> setBoolean(folderId, messageServerId, "answered", set) Flag.FORWARDED -> setBoolean(folderId, messageServerId, "forwarded", set) else -> rebuildFlagsColumnValue(folderId, messageServerId, flag, set) } } private fun setSpecialFlags(messageIds: Collection<Long>, flag: Flag, set: Boolean) { val columnName = when (flag) { Flag.SEEN -> "read" Loading Loading @@ -45,4 +57,59 @@ internal class FlagMessageOperations(private val lockableDatabase: LockableDatab private fun rebuildFlagsColumnValue(messageIds: Collection<Long>, flag: Flag, set: Boolean) { throw UnsupportedOperationException("not implemented") } private fun rebuildFlagsColumnValue(folderId: Long, messageServerId: String, flag: Flag, set: Boolean) { lockableDatabase.execute(true) { database -> val oldFlags = database.readFlagsColumn(folderId, messageServerId) val newFlags = if (set) oldFlags + flag else oldFlags - flag val newFlagsString = newFlags.joinToString(separator = ",") val values = ContentValues().apply { put("flags", newFlagsString) } database.update( "messages", values, "folder_id = ? AND uid = ?", arrayOf(folderId.toString(), messageServerId) ) } } private fun SQLiteDatabase.readFlagsColumn(folderId: Long, messageServerId: String): Set<Flag> { return query( "messages", arrayOf("flags"), "folder_id = ? AND uid = ?", arrayOf(folderId.toString(), messageServerId), null, null, null ).use { cursor -> if (!cursor.moveToFirst()) error("Message not found $folderId:$messageServerId") if (!cursor.isNull(0)) { cursor.getString(0).split(',').map { flagString -> Flag.valueOf(flagString) }.toSet() } else { emptySet() } } } private fun setBoolean(folderId: Long, messageServerId: String, columnName: String, value: Boolean) { lockableDatabase.execute(false) { database -> val values = ContentValues().apply { put(columnName, if (value) 1 else 0) } database.update( "messages", values, "folder_id = ? AND uid = ?", arrayOf(folderId.toString(), messageServerId) ) } } }
app/storage/src/main/java/com/fsck/k9/storage/messages/K9MessageStore.kt +5 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,11 @@ class K9MessageStore( localStore.notifyChange() } override fun setMessageFlag(folderId: Long, messageServerId: String, flag: Flag, set: Boolean) { flagMessageOperations.setMessageFlag(folderId, messageServerId, flag, set) localStore.notifyChange() } override fun getMessageServerId(messageId: Long): String { return retrieveMessageOperations.getMessageServerId(messageId) } Loading
app/storage/src/test/java/com/fsck/k9/storage/messages/FlagMessageOperationsTest.kt +41 −0 Original line number Diff line number Diff line Loading @@ -53,4 +53,45 @@ class FlagMessageOperationsTest : RobolectricTest() { assertThat(otherMessages).hasSize(1) assertThat(otherMessages.all { it.read == 0 }).isTrue() } @Test fun `mark message as read`() { sqliteDatabase.createMessage(folderId = 1, uid = "uid1", read = false) flagMessageOperations.setMessageFlag(folderId = 1, messageServerId = "uid1", Flag.SEEN, true) val message = sqliteDatabase.readMessages().first() assertThat(message.read).isEqualTo(1) } @Test fun `mark message as unread`() { sqliteDatabase.createMessage(folderId = 1, uid = "uid1", read = true) flagMessageOperations.setMessageFlag(folderId = 1, messageServerId = "uid1", Flag.SEEN, false) val message = sqliteDatabase.readMessages().first() assertThat(message.read).isEqualTo(0) } @Test fun `mark message as X_DOWNLOADED_FULL`() { sqliteDatabase.createMessage(folderId = 1, uid = "uid1", flags = "X_SUBJECT_DECRYPTED") flagMessageOperations.setMessageFlag(folderId = 1, messageServerId = "uid1", Flag.X_DOWNLOADED_FULL, true) val message = sqliteDatabase.readMessages().first() val readFlags = message.flags!!.split(',').toSet() assertThat(readFlags).isEqualTo(setOf("X_SUBJECT_DECRYPTED", "X_DOWNLOADED_FULL")) } @Test fun `remove X_DOWNLOADED_FULL flag`() { sqliteDatabase.createMessage(folderId = 1, uid = "uid1", flags = "X_DOWNLOADED_FULL") flagMessageOperations.setMessageFlag(folderId = 1, messageServerId = "uid1", Flag.X_DOWNLOADED_FULL, false) val message = sqliteDatabase.readMessages().first() assertThat(message.flags).isEqualTo("") } }