Skip to content

Commit

Permalink
Precache instanceInfo for UI
Browse files Browse the repository at this point in the history
  • Loading branch information
charlag committed Mar 5, 2024
1 parent e222396 commit cd69215
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 204 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,54 @@ import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.db.EmojisEntity
import com.keylesspalace.tusky.db.InstanceInfoEntity
import com.keylesspalace.tusky.di.ApplicationScope
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.entity.InstanceV1
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.isHttpNotFound
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

@Singleton
class InstanceInfoRepository @Inject constructor(
private val api: MastodonApi,
db: AppDatabase,
accountManager: AccountManager
private val accountManager: AccountManager,
@ApplicationScope
private val externalScope: CoroutineScope
) {

private val dao = db.instanceDao()
private val instanceName = accountManager.activeAccount!!.domain
private val instanceName
get() = accountManager.activeAccount!!.domain

/** In-memory cache for instance data, per instance domain. */
private var _instanceInfo = ConcurrentHashMap<String, InstanceInfo>()

fun precache() {
// We are avoiding some duplicate work but we are not trying too hard.
// We might request it multiple times in parallel which is not a big problem.
// We might also get the results in random order or write them twice but it's also
// not a problem.
// We are just trying to avoid 2 things:
// - fetching it when we already have it
// - fetching default value (we want to rather re-fetch if it fails)
if (_instanceInfo[instanceName] == null) {
externalScope.launch {
fetchAndPersistInstanceInfo().onSuccess { fetched ->
_instanceInfo[fetched.instance] = fetched.toInfoOrDefault()
}
}
}
}

val cachedInstanceInfoOrFallback: InstanceInfo
get() = _instanceInfo[instanceName] ?: null.toInfoOrDefault()

/**
* Returns the custom emojis of the instance.
Expand All @@ -65,48 +94,32 @@ class InstanceInfoRepository @Inject constructor(
* Will always try to fetch the most up-to-date data from the api, falls back to cache in case it is not available.
* Never throws, returns defaults of vanilla Mastodon in case of error.
*/
suspend fun getUpdatedInstanceInfoOrFallback(): InstanceInfo = withContext(Dispatchers.IO) {
fetchRemoteInstanceInfo()
.onSuccess { instanceInfoEntity ->
dao.upsert(instanceInfoEntity)
}
.getOrElse { throwable ->
Log.w(
TAG,
"failed to load instance, falling back to cache and default values",
throwable
)
dao.getInstanceInfo(instanceName)
}
.toInfoOrDefault()
}

/**
* Returns information about the instance.
* Will always try to fetch the most up-to-date data from the api, falls back to cache in case it is not available.
* Never throws, returns defaults of vanilla Mastodon in case of error.
*/
suspend fun getCachedInstanceInfoOrFallback(): InstanceInfo = withContext(Dispatchers.IO) {
dao.getInstanceInfo(instanceName)?.toInfoOrDefault()
?: fetchRemoteInstanceInfo()
.onSuccess { dao.upsert(it) }
suspend fun getUpdatedInstanceInfoOrFallback(): InstanceInfo =
withContext(Dispatchers.IO) {
fetchAndPersistInstanceInfo()
.getOrElse { throwable ->
Log.w(
TAG,
"failed to load instance, falling back to default values",
"failed to load instance, falling back to cache and default values",
throwable
)
null
dao.getInstanceInfo(instanceName)
}
.toInfoOrDefault()
}
}.toInfoOrDefault()

private suspend fun InstanceInfoRepository.fetchAndPersistInstanceInfo(): NetworkResult<InstanceInfoEntity> =
fetchRemoteInstanceInfo()
.onSuccess { instanceInfoEntity ->
dao.upsert(instanceInfoEntity)
}

private suspend fun fetchRemoteInstanceInfo(): NetworkResult<InstanceInfoEntity> {
val instance = this.instanceName
return api.getInstance()
.map { it.toEntity() }
.recover { t ->
if (t.isHttpNotFound()) {
api.getInstanceV1().map { it.toInfoEntity() }.getOrThrow()
api.getInstanceV1().map { it.toEntity(instance) }.getOrThrow()
} else {
throw t
}
Expand Down Expand Up @@ -134,7 +147,7 @@ class InstanceInfoRepository @Inject constructor(
)

private fun Instance.toEntity() = InstanceInfoEntity(
instance = instanceName,
instance = domain,
maximumTootCharacters = this.configuration?.statuses?.maxCharacters
?: DEFAULT_CHARACTER_LIMIT,
maxPollOptions = this.configuration?.polls?.maxOptions ?: DEFAULT_MAX_OPTION_COUNT,
Expand All @@ -161,7 +174,7 @@ class InstanceInfoRepository @Inject constructor(
translationEnabled = this.configuration?.translation?.enabled
)

private fun InstanceV1.toInfoEntity() =
private fun InstanceV1.toEntity(instanceName: String) =
InstanceInfoEntity(
instance = instanceName,
maximumTootCharacters = this.configuration?.statuses?.maxCharacters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class SearchViewModel @Inject constructor(
private val instanceInfoRepository: InstanceInfoRepository,
) : ViewModel() {

init {
instanceInfoRepository.precache()
}

var currentQuery: String = ""
var currentSearchFieldContent: String? = null

Expand Down Expand Up @@ -197,8 +201,8 @@ class SearchViewModel @Inject constructor(
}
}

suspend fun supportsTranslation(): Boolean =
instanceInfoRepository.getCachedInstanceInfoOrFallback().translationEnabled == true
fun supportsTranslation(): Boolean =
instanceInfoRepository.cachedInstanceInfoOrFallback.translationEnabled == true

suspend fun translate(statusViewData: StatusViewData.Concrete): NetworkResult<Unit> {
updateStatusViewData(statusViewData.copy(translation = TranslationViewData.Loading))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import at.connyduck.calladapter.networkresult.fold
import at.connyduck.calladapter.networkresult.onFailure
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
Expand Down Expand Up @@ -264,7 +265,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
bottomSheetActivity?.startActivityWithSlideInAnimation(intent)
}

private fun more(statusViewData: StatusViewData.Concrete, view: View, position: Int) = lifecycleScope.launch {
private fun more(statusViewData: StatusViewData.Concrete, view: View, position: Int) {
val status = statusViewData.status
val id = status.actionableId
val accountId = status.actionableStatus.account.id
Expand Down Expand Up @@ -328,7 +329,8 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
}

val translateItem = popup.menu.findItem(R.id.status_translate)
translateItem.isVisible = status.language != Locale.getDefault().language && viewModel.supportsTranslation()
translateItem.isVisible =
status.language != Locale.getDefault().language && viewModel.supportsTranslation()
translateItem.setTitle(if (statusViewData.translation != null) R.string.action_show_original else R.string.action_translate)

popup.setOnMenuItemClickListener { item ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
uuimport at.connyduck.calladapter.networkresult.onFailure
import at.connyduck.sparkbutton.helpers.Utils
import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import at.connyduck.calladapter.networkresult.onFailure
import com.google.android.material.snackbar.Snackbar
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.accountlist.AccountListActivity
Expand Down
Loading

0 comments on commit cd69215

Please sign in to comment.