Move CursorExtensions to :core:android:common module

This commit is contained in:
Wolf Montwé 2023-03-09 12:07:45 +01:00
parent 3974688b63
commit 3ed19f0011
No known key found for this signature in database
GPG key ID: 6D45B21512ACBF72
18 changed files with 201 additions and 85 deletions

View file

@ -1,41 +0,0 @@
package com.fsck.k9.helper
import android.database.Cursor
fun <T> Cursor.map(block: (Cursor) -> T): List<T> {
return List(count) { index ->
moveToPosition(index)
block(this)
}
}
fun Cursor.getStringOrNull(columnName: String): String? {
val columnIndex = getColumnIndex(columnName)
return if (isNull(columnIndex)) null else getString(columnIndex)
}
fun Cursor.getIntOrNull(columnName: String): Int? {
val columnIndex = getColumnIndex(columnName)
return if (isNull(columnIndex)) null else getInt(columnIndex)
}
fun Cursor.getLongOrNull(columnName: String): Long? {
val columnIndex = getColumnIndex(columnName)
return if (isNull(columnIndex)) null else getLong(columnIndex)
}
fun Cursor.getStringOrThrow(columnName: String): String {
return getStringOrNull(columnName) ?: error("Column $columnName must not be null")
}
fun Cursor.getIntOrThrow(columnName: String): Int {
return getIntOrNull(columnName) ?: error("Column $columnName must not be null")
}
fun Cursor.getLongOrThrow(columnName: String): Long {
return getLongOrNull(columnName) ?: error("Column $columnName must not be null")
}
fun Cursor.getBoolean(columnIndex: Int): Boolean {
return getString(columnIndex).toBoolean()
}

View file

@ -1,10 +1,10 @@
package com.fsck.k9.mailstore package com.fsck.k9.mailstore
import android.content.ContentValues import android.content.ContentValues
import com.fsck.k9.helper.getIntOrThrow import app.k9mail.core.android.common.database.getIntOrThrow
import com.fsck.k9.helper.getLongOrThrow import app.k9mail.core.android.common.database.getLongOrThrow
import com.fsck.k9.helper.getStringOrNull import app.k9mail.core.android.common.database.getStringOrNull
import com.fsck.k9.helper.getStringOrThrow import app.k9mail.core.android.common.database.getStringOrThrow
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
class OutboxStateRepository( class OutboxStateRepository(

View file

@ -2,10 +2,10 @@ package com.fsck.k9.storage.messages
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import app.k9mail.core.android.common.database.getIntOrNull
import app.k9mail.core.android.common.database.getLongOrNull
import app.k9mail.core.android.common.database.getStringOrNull
import com.fsck.k9.K9 import com.fsck.k9.K9
import com.fsck.k9.helper.getIntOrNull
import com.fsck.k9.helper.getLongOrNull
import com.fsck.k9.helper.getStringOrNull
import com.fsck.k9.mailstore.LockableDatabase import com.fsck.k9.mailstore.LockableDatabase
import java.util.UUID import java.util.UUID
import timber.log.Timber import timber.log.Timber

View file

@ -2,8 +2,8 @@ package com.fsck.k9.storage.messages
import android.database.Cursor import android.database.Cursor
import androidx.core.database.getLongOrNull import androidx.core.database.getLongOrNull
import app.k9mail.core.android.common.database.map
import com.fsck.k9.Account.FolderMode import com.fsck.k9.Account.FolderMode
import com.fsck.k9.helper.map
import com.fsck.k9.mail.FolderClass import com.fsck.k9.mail.FolderClass
import com.fsck.k9.mail.FolderType import com.fsck.k9.mail.FolderType
import com.fsck.k9.mailstore.FolderDetailsAccessor import com.fsck.k9.mailstore.FolderDetailsAccessor

View file

@ -2,6 +2,7 @@ package com.fsck.k9.storage.migrations
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import app.k9mail.core.android.common.database.map
import com.fsck.k9.controller.MessagingControllerCommands.PendingAppend import com.fsck.k9.controller.MessagingControllerCommands.PendingAppend
import com.fsck.k9.controller.MessagingControllerCommands.PendingCommand import com.fsck.k9.controller.MessagingControllerCommands.PendingCommand
import com.fsck.k9.controller.MessagingControllerCommands.PendingDelete import com.fsck.k9.controller.MessagingControllerCommands.PendingDelete
@ -11,7 +12,6 @@ import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveAndMarkAsRe
import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy
import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag
import com.fsck.k9.controller.PendingCommandSerializer import com.fsck.k9.controller.PendingCommandSerializer
import com.fsck.k9.helper.map
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import timber.log.Timber import timber.log.Timber

View file

@ -2,8 +2,8 @@ package com.fsck.k9.storage.migrations
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import app.k9mail.core.android.common.database.map
import com.fsck.k9.Account import com.fsck.k9.Account
import com.fsck.k9.helper.map
import com.fsck.k9.mailstore.MigrationsHelper import com.fsck.k9.mailstore.MigrationsHelper
import com.fsck.k9.preferences.Protocols import com.fsck.k9.preferences.Protocols
import timber.log.Timber import timber.log.Timber

View file

@ -3,7 +3,7 @@ package com.fsck.k9.storage.migrations
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import androidx.core.database.getLongOrNull import androidx.core.database.getLongOrNull
import com.fsck.k9.helper.map import app.k9mail.core.android.common.database.map
import com.fsck.k9.mailstore.MigrationsHelper import com.fsck.k9.mailstore.MigrationsHelper
private const val EXTRA_HIGHEST_KNOWN_UID = "imapHighestKnownUid" private const val EXTRA_HIGHEST_KNOWN_UID = "imapHighestKnownUid"

View file

@ -3,7 +3,7 @@ package com.fsck.k9.storage.migrations
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import com.fsck.k9.helper.map import app.k9mail.core.android.common.database.map
/** /**
* Write the address fields to use ASCII 1 instead of ASCII 0 as separator. * Write the address fields to use ASCII 1 instead of ASCII 0 as separator.

View file

@ -2,10 +2,10 @@ package com.fsck.k9.storage.messages
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import com.fsck.k9.helper.getIntOrNull import app.k9mail.core.android.common.database.getIntOrNull
import com.fsck.k9.helper.getLongOrNull import app.k9mail.core.android.common.database.getLongOrNull
import com.fsck.k9.helper.getStringOrNull import app.k9mail.core.android.common.database.getStringOrNull
import com.fsck.k9.helper.map import app.k9mail.core.android.common.database.map
fun SQLiteDatabase.createFolder( fun SQLiteDatabase.createFolder(
name: String = "irrelevant", name: String = "irrelevant",

View file

@ -2,9 +2,9 @@ package com.fsck.k9.storage.messages
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import com.fsck.k9.helper.getLongOrNull import app.k9mail.core.android.common.database.getLongOrNull
import com.fsck.k9.helper.getStringOrNull import app.k9mail.core.android.common.database.getStringOrNull
import com.fsck.k9.helper.map import app.k9mail.core.android.common.database.map
fun SQLiteDatabase.createExtraValue( fun SQLiteDatabase.createExtraValue(
name: String = "irrelevant", name: String = "irrelevant",

View file

@ -2,10 +2,10 @@ package com.fsck.k9.storage.messages
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import com.fsck.k9.helper.getIntOrNull import app.k9mail.core.android.common.database.getIntOrNull
import com.fsck.k9.helper.getLongOrNull import app.k9mail.core.android.common.database.getLongOrNull
import com.fsck.k9.helper.getStringOrNull import app.k9mail.core.android.common.database.getStringOrNull
import com.fsck.k9.helper.map import app.k9mail.core.android.common.database.map
import com.fsck.k9.mailstore.DatabasePreviewType import com.fsck.k9.mailstore.DatabasePreviewType
import com.fsck.k9.mailstore.LockableDatabase import com.fsck.k9.mailstore.LockableDatabase
import com.fsck.k9.mailstore.MigrationsHelper import com.fsck.k9.mailstore.MigrationsHelper

View file

@ -3,10 +3,10 @@ package com.fsck.k9.storage.messages
import android.content.ContentValues import android.content.ContentValues
import android.database.Cursor import android.database.Cursor
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import com.fsck.k9.helper.getIntOrNull import app.k9mail.core.android.common.database.getIntOrNull
import com.fsck.k9.helper.getLongOrNull import app.k9mail.core.android.common.database.getLongOrNull
import com.fsck.k9.helper.getStringOrNull import app.k9mail.core.android.common.database.getStringOrNull
import com.fsck.k9.helper.map import app.k9mail.core.android.common.database.map
fun SQLiteDatabase.createMessagePart( fun SQLiteDatabase.createMessagePart(
type: Int = MessagePartType.UNKNOWN, type: Int = MessagePartType.UNKNOWN,

View file

@ -2,8 +2,8 @@ package com.fsck.k9.storage.messages
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import com.fsck.k9.helper.getLongOrNull import app.k9mail.core.android.common.database.getLongOrNull
import com.fsck.k9.helper.map import app.k9mail.core.android.common.database.map
fun SQLiteDatabase.createThread( fun SQLiteDatabase.createThread(
messageId: Long, messageId: Long,

View file

@ -2,9 +2,9 @@ package com.fsck.k9.storage.notifications
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import com.fsck.k9.helper.getIntOrNull import app.k9mail.core.android.common.database.getIntOrNull
import com.fsck.k9.helper.getLongOrNull import app.k9mail.core.android.common.database.getLongOrNull
import com.fsck.k9.helper.map import app.k9mail.core.android.common.database.map
fun SQLiteDatabase.createNotification( fun SQLiteDatabase.createNotification(
messageId: Long, messageId: Long,

View file

@ -9,8 +9,8 @@ import android.net.Uri
import android.provider.ContactsContract import android.provider.ContactsContract
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import app.k9mail.core.android.common.database.EmptyCursor import app.k9mail.core.android.common.database.EmptyCursor
import app.k9mail.core.android.common.database.getLongValue import app.k9mail.core.android.common.database.getLongOrThrow
import app.k9mail.core.android.common.database.getStringValue import app.k9mail.core.android.common.database.getStringOrNull
import app.k9mail.core.common.mail.EmailAddress import app.k9mail.core.common.mail.EmailAddress
interface ContactDataSource { interface ContactDataSource {
@ -28,13 +28,13 @@ internal class ContentResolverContactDataSource(
override fun getContactFor(emailAddress: EmailAddress): Contact? { override fun getContactFor(emailAddress: EmailAddress): Contact? {
getCursorFor(emailAddress).use { cursor -> getCursorFor(emailAddress).use { cursor ->
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
val contactId = cursor.getLongValue(ContactsContract.CommonDataKinds.Email._ID) val contactId = cursor.getLongOrThrow(ContactsContract.CommonDataKinds.Email._ID)
val lookupKey = cursor.getStringValue(ContactsContract.Contacts.LOOKUP_KEY) val lookupKey = cursor.getStringOrNull(ContactsContract.Contacts.LOOKUP_KEY)
val uri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey) val uri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey)
val name = cursor.getStringValue(ContactsContract.CommonDataKinds.Identity.DISPLAY_NAME) val name = cursor.getStringOrNull(ContactsContract.CommonDataKinds.Identity.DISPLAY_NAME)
val photoUri = cursor.getStringValue(ContactsContract.CommonDataKinds.Photo.PHOTO_URI) val photoUri = cursor.getStringOrNull(ContactsContract.CommonDataKinds.Photo.PHOTO_URI)
?.let { photoUriString -> Uri.parse(photoUriString) } ?.let { photoUriString -> Uri.parse(photoUriString) }
return Contact( return Contact(

View file

@ -2,12 +2,36 @@ package app.k9mail.core.android.common.database
import android.database.Cursor import android.database.Cursor
fun Cursor.getStringValue(key: String): String? { fun <T> Cursor.map(block: (Cursor) -> T): List<T> {
val columnIndex = getColumnIndex(key) return List(count) { index ->
return getString(columnIndex) moveToPosition(index)
block(this)
}
} }
fun Cursor.getLongValue(key: String): Long { fun Cursor.getStringOrNull(columnName: String): String? {
val columnIndex = getColumnIndex(key) val columnIndex = getColumnIndex(columnName)
return getLong(columnIndex) return if (isNull(columnIndex)) null else getString(columnIndex)
}
fun Cursor.getIntOrNull(columnName: String): Int? {
val columnIndex = getColumnIndex(columnName)
return if (isNull(columnIndex)) null else getInt(columnIndex)
}
fun Cursor.getLongOrNull(columnName: String): Long? {
val columnIndex = getColumnIndex(columnName)
return if (isNull(columnIndex)) null else getLong(columnIndex)
}
fun Cursor.getStringOrThrow(columnName: String): String {
return getStringOrNull(columnName) ?: error("Column $columnName must not be null")
}
fun Cursor.getIntOrThrow(columnName: String): Int {
return getIntOrNull(columnName) ?: error("Column $columnName must not be null")
}
fun Cursor.getLongOrThrow(columnName: String): Long {
return getLongOrNull(columnName) ?: error("Column $columnName must not be null")
} }

View file

@ -0,0 +1,100 @@
package app.k9mail.core.android.common.database
import android.database.Cursor
import android.database.MatrixCursor
import assertk.assertThat
import assertk.assertions.hasMessage
import assertk.assertions.isEqualTo
import assertk.assertions.isFailure
import assertk.assertions.isNull
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner
data class CursorExtensionsAccessTestData<T : Any>(
val name: String,
val value: T,
val access: (Cursor, String) -> T?,
val throwingAccess: (Cursor, String) -> T,
) {
override fun toString(): String = name
}
@RunWith(ParameterizedRobolectricTestRunner::class)
class CursorExtensionsKtAccessTest(data: CursorExtensionsAccessTestData<Any>) {
private val testValue = data.value
private val testAction = data.access
private val testThrowingAction = data.throwingAccess
@Test
fun `testAction should return null if column is null`() {
val cursor = MatrixCursor(arrayOf("column")).apply {
addRow(arrayOf(null))
}
val result = cursor.map { testAction(it, "column") }
assertThat(result[0]).isNull()
}
@Test
fun `testAction should return value if column is not null`() {
val cursor = MatrixCursor(arrayOf("column")).apply {
addRow(arrayOf(testValue))
}
val result = cursor.map { testAction(it, "column") }
assertThat(result[0]).isEqualTo(testValue)
}
@Test
fun `testThrowingAction should throw if column is null`() {
val cursor = MatrixCursor(arrayOf("column")).apply {
addRow(arrayOf(null))
}
assertThat {
cursor.map { testThrowingAction(it, "column") }
}.isFailure().hasMessage("Column column must not be null")
}
@Test
fun `testThrowingAction should return value if column is not null`() {
val cursor = MatrixCursor(arrayOf("column")).apply {
addRow(arrayOf(testValue))
}
val result = cursor.map { testThrowingAction(it, "column") }
assertThat(result[0]).isEqualTo(testValue)
}
companion object {
@JvmStatic
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
fun data(): Collection<CursorExtensionsAccessTestData<Any>> {
return listOf(
CursorExtensionsAccessTestData(
name = "getString",
value = "value",
access = { cursor, column -> cursor.getStringOrNull(column) },
throwingAccess = { cursor, column -> cursor.getStringOrThrow(column) },
),
CursorExtensionsAccessTestData(
name = "getInt",
value = Int.MAX_VALUE,
access = { cursor, column -> cursor.getIntOrNull(column) },
throwingAccess = { cursor, column -> cursor.getIntOrThrow(column) },
),
CursorExtensionsAccessTestData(
name = "getLong",
value = Long.MAX_VALUE,
access = { cursor, column -> cursor.getLongOrNull(column) },
throwingAccess = { cursor, column -> cursor.getLongOrThrow(column) },
),
)
}
}
}

View file

@ -0,0 +1,33 @@
package app.k9mail.core.android.common.database
import android.database.MatrixCursor
import assertk.assertThat
import assertk.assertions.isEqualTo
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class CursorExtensionsKtTest {
@Test
fun `map should return an empty list if cursor is empty`() {
val cursor = MatrixCursor(arrayOf("column"))
val result = cursor.map { it.getStringOrNull("column") }
assertThat(result).isEqualTo(emptyList<String>())
}
@Test
fun `map should return a list of mapped values`() {
val cursor = MatrixCursor(arrayOf("column")).apply {
addRow(arrayOf("value1"))
addRow(arrayOf("value2"))
}
val result = cursor.map { it.getStringOrNull("column") }
assertThat(result).isEqualTo(listOf("value1", "value2"))
}
}