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

Commit 15577ff9 authored by Hasib Prince's avatar Hasib Prince
Browse files

refactor: EmailCache

parent 669e962d
Loading
Loading
Loading
Loading
Loading
+49 −35
Original line number Diff line number Diff line
/*
 * Copyright MURENA SAS 2023
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package com.fsck.k9.ui.messagelist

import android.content.Context
@@ -6,22 +22,21 @@ import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.fsck.k9.Account
import com.fsck.k9.controller.MessageReference
import com.fsck.k9.search.getAccounts
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import com.squareup.moshi.Moshi
import java.io.IOException
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import timber.log.Timber

class EmailCache constructor(private val context: Context, private val moshi: Moshi, private val gson: Gson) {
class EmailCache constructor(private val context: Context, private val gson: Gson) {
    companion object {
        private const val MAX_CACHE_SIZE = 20
        private const val preferenceDataStoreName = "emailCache"
        private val Context.emailCacheDataStore by preferencesDataStore(preferenceDataStoreName)
    }
@@ -29,27 +44,19 @@ class EmailCache constructor(private val context: Context, private val moshi: Mo
    private val MAIL_LIST_KEY = stringPreferencesKey("mail_list")
    private var isCacheShown = false

    suspend fun getCachedMails(accountList: List<Account>? = null): List<MessageListItem>? {
    suspend fun getCachedMails(): List<MessageListItem>? {
        if (isCacheShown) {
            return null
        }

        return runBlocking {
            fetchCachedMail()
        }
    }

    private suspend fun fetchCachedMail(): List<MessageListItem>? {
        val listType = object : TypeToken<List<MessageListItem>>() {}.type
        val mailListJson = context.emailCacheDataStore.data.map { it[MAIL_LIST_KEY] }.firstOrNull()
        Timber.d("email data: $mailListJson")
        return gson.fromJson(mailListJson, listType)
    }

    suspend fun saveLatestMails(mailList: List<MessageListItem>) {
//        val type = Types.newParameterizedType(List::class.java, MessageListItem::class.java)
//        val adapter = moshi.adapter<List<MessageListItem>>(type)
//        val mailListJson = adapter.toJson(mailList)
        if (isCacheShown) return

        isCacheShown = true
        val cachedMailsWithLatest = getLatestMails(mailList)

@@ -58,18 +65,7 @@ class EmailCache constructor(private val context: Context, private val moshi: Mo
        context.emailCacheDataStore.edit {
            it[MAIL_LIST_KEY] = mailListJson
        }
    }

    private suspend fun getLatestMails(mailList: List<MessageListItem>): MutableList<MessageListItem> {
        var cachedMailsWithLatest = mutableListOf<MessageListItem>()
        cachedMailsWithLatest.addAll(mailList)
        fetchCachedMail()?.let {
            cachedMailsWithLatest.addAll(it)
        }
        cachedMailsWithLatest.sortedByDescending { it.messageDate }
        val lastIndex = if (cachedMailsWithLatest.size < 40) cachedMailsWithLatest.size else 39
        cachedMailsWithLatest = cachedMailsWithLatest.subList(0, lastIndex)
        return cachedMailsWithLatest
        Timber.d("Saved latest mails in the cache")
    }

    suspend fun deleteMail(messages: List<MessageReference>) {
@@ -82,27 +78,45 @@ class EmailCache constructor(private val context: Context, private val moshi: Mo
            }
        }
    }

    private suspend fun fetchCachedMail(): List<MessageListItem>? {
        val listType = object : TypeToken<List<MessageListItem>>() {}.type
        val mailListJson = context.emailCacheDataStore.data.map { it[MAIL_LIST_KEY] }.firstOrNull()
        Timber.d("Cached email data: $mailListJson")
        return gson.fromJson(mailListJson, listType)
    }

    private suspend fun getLatestMails(mailList: List<MessageListItem>): MutableList<MessageListItem> {
        var cachedMailsWithLatest = mutableListOf<MessageListItem>()
        cachedMailsWithLatest.addAll(mailList)
        fetchCachedMail()?.let {
            cachedMailsWithLatest.addAll(it)
        }

        cachedMailsWithLatest.sortedByDescending { it.messageDate }
        val lastIndex = if (cachedMailsWithLatest.size < MAX_CACHE_SIZE) cachedMailsWithLatest.size else MAX_CACHE_SIZE
        cachedMailsWithLatest = cachedMailsWithLatest.subList(0, lastIndex)
        return cachedMailsWithLatest
    }
}

internal class CharSequenceTypeAdapter : TypeAdapter<CharSequence?>() {
    @Throws(IOException::class)
    override fun write(out: JsonWriter, value: CharSequence?) {
    override fun write(writer: JsonWriter, value: CharSequence?) {
        if (value == null) {
            out.nullValue()
            writer.nullValue()
        } else {
            // Assumes that value complies with CharSequence.toString() contract
            out.value(value.toString())
            writer.value(value.toString())
        }
    }

    @Throws(IOException::class)
    override fun read(`in`: JsonReader): CharSequence? {
        return if (`in`.peek() === JsonToken.NULL) {
            // Skip the JSON null
            `in`.skipValue()
    override fun read(reader: JsonReader): CharSequence? {
        return if (reader.peek() === JsonToken.NULL) {
            reader.skipValue()
            null
        } else {
            `in`.nextString()
            reader.nextString()
        }
    }
}
+2 −3
Original line number Diff line number Diff line
package com.fsck.k9.ui.messagelist

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
@@ -21,7 +20,7 @@ val messageListUiModule = module {
    }
    single { Moshi.Builder().add(KotlinJsonAdapterFactory()) .build() }
    single { GsonBuilder().registerTypeAdapter(CharSequence::class.java, CharSequenceTypeAdapter()).create() }
    single<EmailCache> { EmailCache(androidContext(), get(), get()) }
    single<EmailCache> { EmailCache(androidContext(), get()) }
    factory {
        MessageListLiveDataFactory(messageListLoader = get(), preferences = get(), messageListRepository = get(), emailCache = get())
    }
+2 −1
Original line number Diff line number Diff line
@@ -26,9 +26,10 @@ class MessageListLiveData(

    private fun loadMessageListAsync() {
        coroutineScope.launch(Dispatchers.Main) {
            emailCache.getCachedMails(config.search.getAccounts(preferences))?.let {
            emailCache.getCachedMails()?.let {
                value = MessageListInfo(it, true)
            }

            val messageList = withContext(Dispatchers.IO) {
                messageListLoader.getMessageList(config)
            }