Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 50226711 authored by Rafael Tonholo's avatar Rafael Tonholo Committed by GitHub
Browse files

Merge pull request #9237 from rafaeltonholo/refactor/7062/storage-get-string-or-null

Refactor `Storage.getString` method to provide a more idiomatic and safer way to access string values
parents e0e66788 0a69c9d5
Loading
Loading
Loading
Loading
+92 −1
Original line number Diff line number Diff line
package net.thunderbird.core.preferences

interface Storage {
    /**
     * Checks if the storage is empty.
     *
     * @return true if the storage is empty, false otherwise.
     */
    fun isEmpty(): Boolean

    /**
     * Checks if the storage contains a value for the given key.
     *
     * @param key The key to check.
     * @return true if the storage contains a value for the given key, false otherwise.
     */
    fun contains(key: String): Boolean

    /**
     * Returns a map of all key-value pairs in the storage.
     *
     * @return A map of all key-value pairs.
     */
    fun getAll(): Map<String, String>

    /**
     * Returns the boolean value for the given key.
     *
     * @param key The key to look up.
     * @param defValue The default value to return if the key is not found.
     * @return The boolean value for the given key, or the default value if the key is not found.
     */
    fun getBoolean(key: String, defValue: Boolean): Boolean

    /**
     * Returns the integer value for the given key.
     *
     * @param key The key to look up.
     * @param defValue The default value to return if the key is not found.
     * @return The integer value for the given key, or the default value if the key is not found.
     */
    fun getInt(key: String, defValue: Int): Int

    /**
     * Returns the long value for the given key.
     *
     * @param key The key to look up.
     * @param defValue The default value to return if the key is not found.
     * @return The long value for the given key, or the default value if the key is not found.
     */
    fun getLong(key: String, defValue: Long): Long

    fun getString(key: String?, defValue: String?): String
    /**
     * Returns the string value for the given key.
     *
     * @param key The key to look up.
     * @return The string value for the given key.
     * @throws NoSuchElementException if the key is not found.
     */
    @Throws(NoSuchElementException::class)
    fun getString(key: String): String

    /**
     * Returns the string value for the given key.
     *
     * @param key The key to look up.
     * @param defValue The default value to return if the key is not found.
     * @return The string value for the given key, or the default value if the key is not found.
     */
    fun getStringOrDefault(key: String, defValue: String): String

    /**
     * Returns the string value for the given key, or null if the key is not found.
     *
     * @param key The key to look up.
     * @return The string value for the given key, or null if the key is not found.
     */
    fun getStringOrNull(key: String): String?
}

/**
 * Returns the enum value for the given key, or the default value if the key is not found or the stored value
 * is not a valid enum constant.
 *
 * @param T The enum type.
 * @param key The key to look up.
 * @param default The default enum value to return if the key is not found or the value is invalid.
 * @return The enum value for the given key, or the default value.
 * @throws IllegalArgumentException if the stored string value does not match any of the enum constants.
 */
@Throws(IllegalArgumentException::class)
inline fun <reified T : Enum<T>> Storage.getEnumOrDefault(key: String, default: T): T =
    getStringOrNull(key)
        ?.let { value ->
            try {
                enumValueOf<T>(value)
            } catch (e: IllegalArgumentException) {
                throw IllegalArgumentException(
                    buildString {
                        append("Unable to convert stored key [$key] value [$value] ")
                        appendLine("to enum of type ${T::class.qualifiedName}.")
                        append("Valid values: ${enumValues<T>().joinToString()}")
                    },
                    e,
                )
            }
        }
        ?: default
+51 −54
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import net.thunderbird.core.android.account.QuoteStyle
import net.thunderbird.core.android.account.ShowPictures
import net.thunderbird.core.android.account.SortType
import net.thunderbird.core.preferences.Storage
import net.thunderbird.core.preferences.getEnumOrDefault
import net.thunderbird.feature.account.storage.legacy.ServerSettingsSerializer
import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection
import net.thunderbird.feature.notification.NotificationLight
@@ -44,14 +45,14 @@ class AccountPreferenceSerializer(
        val accountUuid = account.uuid
        with(account) {
            incomingServerSettings = serverSettingsSerializer.deserialize(
                storage.getString("$accountUuid.$INCOMING_SERVER_SETTINGS_KEY", ""),
                storage.getStringOrDefault("$accountUuid.$INCOMING_SERVER_SETTINGS_KEY", ""),
            )
            outgoingServerSettings = serverSettingsSerializer.deserialize(
                storage.getString("$accountUuid.$OUTGOING_SERVER_SETTINGS_KEY", ""),
                storage.getStringOrDefault("$accountUuid.$OUTGOING_SERVER_SETTINGS_KEY", ""),
            )
            oAuthState = storage.getString("$accountUuid.oAuthState", null)
            name = storage.getString("$accountUuid.description", null)
            alwaysBcc = storage.getString("$accountUuid.alwaysBcc", alwaysBcc)
            oAuthState = storage.getStringOrNull("$accountUuid.oAuthState")
            name = storage.getStringOrNull("$accountUuid.description")
            alwaysBcc = storage.getStringOrNull("$accountUuid.alwaysBcc") ?: alwaysBcc
            automaticCheckIntervalMinutes = storage.getInt(
                "" +
                    "$accountUuid.automaticCheckIntervalMinutes",
@@ -74,17 +75,17 @@ class AccountPreferenceSerializer(
            isNotifySync = storage.getBoolean("$accountUuid.notifyMailCheck", false)
            messagesNotificationChannelVersion = storage.getInt("$accountUuid.messagesNotificationChannelVersion", 0)
            deletePolicy = DeletePolicy.fromInt(storage.getInt("$accountUuid.deletePolicy", DeletePolicy.NEVER.setting))
            legacyInboxFolder = storage.getString("$accountUuid.inboxFolderName", null)
            importedDraftsFolder = storage.getString("$accountUuid.draftsFolderName", null)
            importedSentFolder = storage.getString("$accountUuid.sentFolderName", null)
            importedTrashFolder = storage.getString("$accountUuid.trashFolderName", null)
            importedArchiveFolder = storage.getString("$accountUuid.archiveFolderName", null)
            importedSpamFolder = storage.getString("$accountUuid.spamFolderName", null)
            legacyInboxFolder = storage.getStringOrNull("$accountUuid.inboxFolderName")
            importedDraftsFolder = storage.getStringOrNull("$accountUuid.draftsFolderName")
            importedSentFolder = storage.getStringOrNull("$accountUuid.sentFolderName")
            importedTrashFolder = storage.getStringOrNull("$accountUuid.trashFolderName")
            importedArchiveFolder = storage.getStringOrNull("$accountUuid.archiveFolderName")
            importedSpamFolder = storage.getStringOrNull("$accountUuid.spamFolderName")

            inboxFolderId = storage.getString("$accountUuid.inboxFolderId", null)?.toLongOrNull()
            outboxFolderId = storage.getString("$accountUuid.outboxFolderId", null)?.toLongOrNull()
            inboxFolderId = storage.getStringOrNull("$accountUuid.inboxFolderId")?.toLongOrNull()
            outboxFolderId = storage.getStringOrNull("$accountUuid.outboxFolderId")?.toLongOrNull()

            val draftsFolderId = storage.getString("$accountUuid.draftsFolderId", null)?.toLongOrNull()
            val draftsFolderId = storage.getStringOrNull("$accountUuid.draftsFolderId")?.toLongOrNull()
            val draftsFolderSelection = getEnumStringPref<SpecialFolderSelection>(
                storage,
                "$accountUuid.draftsFolderSelection",
@@ -92,7 +93,7 @@ class AccountPreferenceSerializer(
            )
            setDraftsFolderId(draftsFolderId, draftsFolderSelection)

            val sentFolderId = storage.getString("$accountUuid.sentFolderId", null)?.toLongOrNull()
            val sentFolderId = storage.getStringOrNull("$accountUuid.sentFolderId")?.toLongOrNull()
            val sentFolderSelection = getEnumStringPref<SpecialFolderSelection>(
                storage,
                "$accountUuid.sentFolderSelection",
@@ -100,7 +101,7 @@ class AccountPreferenceSerializer(
            )
            setSentFolderId(sentFolderId, sentFolderSelection)

            val trashFolderId = storage.getString("$accountUuid.trashFolderId", null)?.toLongOrNull()
            val trashFolderId = storage.getStringOrNull("$accountUuid.trashFolderId")?.toLongOrNull()
            val trashFolderSelection = getEnumStringPref<SpecialFolderSelection>(
                storage,
                "$accountUuid.trashFolderSelection",
@@ -108,7 +109,7 @@ class AccountPreferenceSerializer(
            )
            setTrashFolderId(trashFolderId, trashFolderSelection)

            val archiveFolderId = storage.getString("$accountUuid.archiveFolderId", null)?.toLongOrNull()
            val archiveFolderId = storage.getStringOrNull("$accountUuid.archiveFolderId")?.toLongOrNull()
            val archiveFolderSelection = getEnumStringPref<SpecialFolderSelection>(
                storage,
                "$accountUuid.archiveFolderSelection",
@@ -116,7 +117,7 @@ class AccountPreferenceSerializer(
            )
            setArchiveFolderId(archiveFolderId, archiveFolderSelection)

            val spamFolderId = storage.getString("$accountUuid.spamFolderId", null)?.toLongOrNull()
            val spamFolderId = storage.getStringOrNull("$accountUuid.spamFolderId")?.toLongOrNull()
            val spamFolderSelection = getEnumStringPref<SpecialFolderSelection>(
                storage,
                "$accountUuid.spamFolderSelection",
@@ -124,7 +125,7 @@ class AccountPreferenceSerializer(
            )
            setSpamFolderId(spamFolderId, spamFolderSelection)

            autoExpandFolderId = storage.getString("$accountUuid.autoExpandFolderId", null)?.toLongOrNull()
            autoExpandFolderId = storage.getStringOrNull("$accountUuid.autoExpandFolderId")?.toLongOrNull()

            expungePolicy = getEnumStringPref(storage, "$accountUuid.expungePolicy", Expunge.EXPUNGE_IMMEDIATELY)
            isSyncRemoteDeletions = storage.getBoolean("$accountUuid.syncRemoteDeletions", true)
@@ -143,7 +144,7 @@ class AccountPreferenceSerializer(
            }
            isMessageReadReceipt = storage.getBoolean("$accountUuid.messageReadReceipt", DEFAULT_MESSAGE_READ_RECEIPT)
            quoteStyle = getEnumStringPref<QuoteStyle>(storage, "$accountUuid.quoteStyle", DEFAULT_QUOTE_STYLE)
            quotePrefix = storage.getString("$accountUuid.quotePrefix", DEFAULT_QUOTE_PREFIX)
            quotePrefix = storage.getStringOrDefault("$accountUuid.quotePrefix", DEFAULT_QUOTE_PREFIX)
            isDefaultQuotedTextShown = storage.getBoolean(
                "$accountUuid.defaultQuotedTextShown",
                DEFAULT_QUOTED_TEXT_SHOWN,
@@ -153,7 +154,7 @@ class AccountPreferenceSerializer(
            useCompression = storage.getBoolean("$accountUuid.useCompression", true)
            isSendClientInfoEnabled = storage.getBoolean("$accountUuid.sendClientInfo", true)

            importedAutoExpandFolder = storage.getString("$accountUuid.autoExpandFolderName", null)
            importedAutoExpandFolder = storage.getStringOrNull("$accountUuid.autoExpandFolderName")

            accountNumber = storage.getInt("$accountUuid.accountNumber", UNASSIGNED_ACCOUNT_NUMBER)

@@ -168,7 +169,7 @@ class AccountPreferenceSerializer(
            updateNotificationSettings {
                NotificationSettings(
                    isRingEnabled = storage.getBoolean("$accountUuid.ring", true),
                    ringtone = storage.getString("$accountUuid.ringtone", DEFAULT_RINGTONE_URI),
                    ringtone = storage.getStringOrDefault("$accountUuid.ringtone", DEFAULT_RINGTONE_URI),
                    light = getEnumStringPref(
                        storage,
                        "$accountUuid.notificationLight",
@@ -198,7 +199,7 @@ class AccountPreferenceSerializer(
            isSignatureBeforeQuotedText = storage.getBoolean("$accountUuid.signatureBeforeQuotedText", false)
            replaceIdentities(loadIdentities(accountUuid, storage))

            openPgpProvider = storage.getString("$accountUuid.openPgpProvider", "")
            openPgpProvider = storage.getStringOrDefault("$accountUuid.openPgpProvider", "")
            openPgpKey = storage.getLong("$accountUuid.cryptoKey", NO_OPENPGP_KEY)
            isOpenPgpHideSignOnly = storage.getBoolean("$accountUuid.openPgpHideSignOnly", true)
            isOpenPgpEncryptSubject = storage.getBoolean("$accountUuid.openPgpEncryptSubject", true)
@@ -231,12 +232,12 @@ class AccountPreferenceSerializer(
        var gotOne: Boolean
        do {
            gotOne = false
            val name = storage.getString("$accountUuid.$IDENTITY_NAME_KEY.$ident", null)
            val email = storage.getString("$accountUuid.$IDENTITY_EMAIL_KEY.$ident", null)
            val name = storage.getStringOrNull("$accountUuid.$IDENTITY_NAME_KEY.$ident")
            val email = storage.getStringOrNull("$accountUuid.$IDENTITY_EMAIL_KEY.$ident")
            val signatureUse = storage.getBoolean("$accountUuid.signatureUse.$ident", false)
            val signature = storage.getString("$accountUuid.signature.$ident", null)
            val description = storage.getString("$accountUuid.$IDENTITY_DESCRIPTION_KEY.$ident", null)
            val replyTo = storage.getString("$accountUuid.replyTo.$ident", null)
            val signature = storage.getStringOrNull("$accountUuid.signature.$ident")
            val description = storage.getStringOrNull("$accountUuid.$IDENTITY_DESCRIPTION_KEY.$ident")
            val replyTo = storage.getStringOrNull("$accountUuid.replyTo.$ident")
            if (email != null) {
                val identity = Identity(
                    name = name,
@@ -253,10 +254,10 @@ class AccountPreferenceSerializer(
        } while (gotOne)

        if (newIdentities.isEmpty()) {
            val name = storage.getString("$accountUuid.name", null)
            val email = storage.getString("$accountUuid.email", null)
            val name = storage.getStringOrNull("$accountUuid.name")
            val email = storage.getStringOrNull("$accountUuid.email")
            val signatureUse = storage.getBoolean("$accountUuid.signatureUse", false)
            val signature = storage.getString("$accountUuid.signature", null)
            val signature = storage.getStringOrNull("$accountUuid.signature")
            val identity = Identity(
                name = name,
                email = email,
@@ -275,8 +276,8 @@ class AccountPreferenceSerializer(
    fun save(editor: StorageEditor, storage: Storage, account: LegacyAccount) {
        val accountUuid = account.uuid

        if (!storage.getString("accountUuids", "").contains(account.uuid)) {
            var accountUuids = storage.getString("accountUuids", "")
        if (!storage.getStringOrDefault("accountUuids", "").contains(account.uuid)) {
            var accountUuids = storage.getStringOrDefault("accountUuids", "")
            accountUuids += (if (accountUuids.isNotEmpty()) "," else "") + account.uuid
            editor.putString("accountUuids", accountUuids)
        }
@@ -391,8 +392,11 @@ class AccountPreferenceSerializer(
        val accountUuid = account.uuid

        // Get the list of account UUIDs
        val uuids =
            storage.getString("accountUuids", "").split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        val uuids = storage
            .getStringOrDefault("accountUuids", "")
            .split(",".toRegex())
            .dropLastWhile { it.isEmpty() }
            .toTypedArray()

        // Create a list of all account UUIDs excluding this account
        val newUuids = ArrayList<String>(uuids.size)
@@ -529,7 +533,7 @@ class AccountPreferenceSerializer(
        var gotOne: Boolean
        do {
            gotOne = false
            val email = storage.getString("$accountUuid.$IDENTITY_EMAIL_KEY.$identityIndex", null)
            val email = storage.getStringOrNull("$accountUuid.$IDENTITY_EMAIL_KEY.$identityIndex")
            if (email != null) {
                editor.remove("$accountUuid.$IDENTITY_NAME_KEY.$identityIndex")
                editor.remove("$accountUuid.$IDENTITY_EMAIL_KEY.$identityIndex")
@@ -544,7 +548,7 @@ class AccountPreferenceSerializer(
    }

    fun move(editor: StorageEditor, account: LegacyAccount, storage: Storage, newPosition: Int) {
        val accountUuids = storage.getString("accountUuids", "").split(",").filter { it.isNotEmpty() }
        val accountUuids = storage.getStringOrDefault("accountUuids", "").split(",").filter { it.isNotEmpty() }
        val oldPosition = accountUuids.indexOf(account.uuid)
        if (oldPosition == -1 || oldPosition == newPosition) return

@@ -558,27 +562,20 @@ class AccountPreferenceSerializer(
        editor.putString("accountUuids", newAccountUuidsString)
    }

    private fun <T : Enum<T>> getEnumStringPref(storage: Storage, key: String, defaultEnum: T): T {
        val stringPref = storage.getString(key, null)

        return if (stringPref == null) {
            defaultEnum
        } else {
            try {
                java.lang.Enum.valueOf<T>(defaultEnum.declaringJavaClass, stringPref)
    private inline fun <reified T : Enum<T>> getEnumStringPref(storage: Storage, key: String, defaultEnum: T): T {
        return try {
            storage.getEnumOrDefault<T>(key, defaultEnum)
        } catch (ex: IllegalArgumentException) {
            Timber.w(
                ex,
                    "Unable to convert preference key [%s] value [%s] to enum of type %s",
                "Unable to convert preference key [%s] to enum of type %s",
                key,
                    stringPref,
                defaultEnum.declaringJavaClass,
            )

            defaultEnum
        }
    }
    }

    companion object {
        const val ACCOUNT_DESCRIPTION_KEY = "description"
+6 −10
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ import net.thunderbird.core.android.account.SortType
import net.thunderbird.core.featureflag.FeatureFlagProvider
import net.thunderbird.core.featureflag.toFeatureFlagKey
import net.thunderbird.core.preferences.Storage
import net.thunderbird.core.preferences.getEnumOrDefault
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import timber.log.Timber
@@ -347,8 +348,8 @@ object K9 : KoinComponent {

        isQuietTimeEnabled = storage.getBoolean("quietTimeEnabled", false)
        isNotificationDuringQuietTimeEnabled = storage.getBoolean("notificationDuringQuietTimeEnabled", true)
        quietTimeStarts = storage.getString("quietTimeStarts", "21:00")
        quietTimeEnds = storage.getString("quietTimeEnds", "7:00")
        quietTimeStarts = storage.getStringOrDefault("quietTimeStarts", "21:00")
        quietTimeEnds = storage.getStringOrDefault("quietTimeEnds", "7:00")

        messageListDensity = storage.getEnum("messageListDensity", UiDensity.Default)
        isShowCorrespondentNames = storage.getBoolean("showCorrespondentNames", true)
@@ -407,7 +408,7 @@ object K9 : KoinComponent {
        pgpInlineDialogCounter = storage.getInt("pgpInlineDialogCounter", 0)
        pgpSignOnlyDialogCounter = storage.getInt("pgpSignOnlyDialogCounter", 0)

        k9Language = storage.getString("language", "")
        k9Language = storage.getStringOrDefault("language", "")

        swipeRightAction = storage.getEnum("swipeRightAction", SwipeAction.ToggleSelection)
        swipeLeftAction = storage.getEnum("swipeLeftAction", SwipeAction.ToggleRead)
@@ -510,14 +511,9 @@ object K9 : KoinComponent {

    private inline fun <reified T : Enum<T>> Storage.getEnum(key: String, defaultValue: T): T {
        return try {
            val value = getString(key, null)
            if (value != null) {
                enumValueOf(value)
            } else {
                defaultValue
            }
            getEnumOrDefault(key, defaultValue)
        } catch (e: Exception) {
            Timber.e("Couldn't read setting '%s'. Using default value instead.", key)
            Timber.e(e, "Couldn't read setting '%s'. Using default value instead.", key)
            defaultValue
        }
    }
+1 −1
Original line number Diff line number Diff line
@@ -82,7 +82,7 @@ class Preferences internal constructor(
            val accounts = mutableMapOf<String, LegacyAccount>()
            val accountsInOrder = mutableListOf<LegacyAccount>()

            val accountUuids = storage.getString("accountUuids", null)
            val accountUuids = storage.getStringOrNull("accountUuids")
            if (!accountUuids.isNullOrEmpty()) {
                accountUuids.split(",").forEach { uuid ->
                    val existingAccount = accountsMap?.get(uuid)
+1 −1
Original line number Diff line number Diff line
@@ -87,7 +87,7 @@ internal class AccountSettingsWriter(
    }

    private fun updateAccountUuids(editor: StorageEditor, accountUuid: String) {
        val oldAccountUuids = preferences.storage.getString("accountUuids", "")
        val oldAccountUuids = preferences.storage.getStringOrDefault("accountUuids", "")
            .split(',')
            .dropLastWhile { it.isEmpty() }
        val newAccountUuids = oldAccountUuids + accountUuid
Loading