Read extension stores once at startup

Refreshing stores now fetches them all first and writes them in one transaction.
This commit is contained in:
AntsyLich
2026-10-11 13:43:34 +06:00
parent 260e31cd2c
commit 8066b8dfc9
3 changed files with 46 additions and 16 deletions
@@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.util.storage.copyAndSetReadOnlyTo
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.LogPriority import logcat.LogPriority
import mihon.app.di.appGraph import mihon.app.di.appGraph
@@ -115,7 +116,7 @@ internal object ExtensionLoader {
): List<Extension.Installed> { ): List<Extension.Installed> {
val trustExtension = context.appGraph.trustExtension val trustExtension = context.appGraph.trustExtension
val sourcePreferences = context.appGraph.sourcePreferences val sourcePreferences = context.appGraph.sourcePreferences
val stores = context.appGraph.extensionStoreRepository.getAll() val stores = context.appGraph.extensionStoreRepository.getAllAsFlow().first()
val enabledContentWarnings = sourcePreferences.enabledContentWarnings.get() val enabledContentWarnings = sourcePreferences.enabledContentWarnings.get()
val applyContentWarningsToInstalled = sourcePreferences.applyContentWarningsToInstalled.get() val applyContentWarningsToInstalled = sourcePreferences.applyContentWarningsToInstalled.get()
+1
View File
@@ -32,6 +32,7 @@ dependencies {
implementation(projects.sourceApi) implementation(projects.sourceApi)
implementation(projects.domain) implementation(projects.domain)
implementation(projects.core.common) implementation(projects.core.common)
implementation(projects.core.metro)
implementation(libs.metro.runtime) implementation(libs.metro.runtime)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
@@ -7,11 +7,18 @@ import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.SingleIn
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.WhileSubscribed
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import logcat.LogPriority import logcat.LogPriority
import mihon.core.metro.AppCoroutineScope
import mihon.data.extension.service.ExtensionStoreService import mihon.data.extension.service.ExtensionStoreService
import mihon.domain.extension.model.ExtensionStore import mihon.domain.extension.model.ExtensionStore
import mihon.domain.extension.repository.ExtensionStoreRepository import mihon.domain.extension.repository.ExtensionStoreRepository
@@ -19,14 +26,33 @@ import tachiyomi.core.common.util.system.logcat
import tachiyomi.data.Database import tachiyomi.data.Database
import tachiyomi.data.subscribeToList import tachiyomi.data.subscribeToList
import tachiyomi.data.subscribeToOne import tachiyomi.data.subscribeToOne
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@Inject @Inject
@SingleIn(AppScope::class) @SingleIn(AppScope::class)
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class ExtensionStoreRepositoryImpl( class ExtensionStoreRepositoryImpl(
@AppCoroutineScope scope: CoroutineScope,
private val service: ExtensionStoreService, private val service: ExtensionStoreService,
private val database: Database, private val database: Database,
) : ExtensionStoreRepository { ) : ExtensionStoreRepository {
private val stores = database.extension_storeQueries.getAll(::extensionStoreMapper)
.subscribeToList()
.catch {
logcat(LogPriority.ERROR, it) { "Failed to query extension stores" }
emit(emptyList())
}
.shareIn(
scope = scope,
started = SharingStarted.WhileSubscribed(
stopTimeout = 5.seconds,
replayExpiration = Duration.ZERO,
),
replay = 1,
)
override suspend fun insert(indexUrl: String): Result<Unit> { override suspend fun insert(indexUrl: String): Result<Unit> {
return service.fetch(indexUrl).mapCatching { upsert(it) } return service.fetch(indexUrl).mapCatching { upsert(it) }
} }
@@ -46,24 +72,26 @@ class ExtensionStoreRepositoryImpl(
override suspend fun refreshAll() { override suspend fun refreshAll() {
try { try {
database.extension_storeQueries.getAll().awaitAsList().forEach { store -> val refreshed = stores.first().mapNotNull { store ->
service.fetch(store.index_url) service.fetch(store.indexUrl)
.mapCatching {
database.transaction {
if (!database.extension_storeQueries.contains(store.index_url).awaitAsOne()) {
return@transaction
}
upsert(it)
if (store.index_url != it.indexUrl) {
database.extension_storeQueries.delete(store.index_url)
}
}
}
.onFailure { .onFailure {
logcat(LogPriority.ERROR, it) { logcat(LogPriority.ERROR, it) {
"Failed to refresh extension store '${store.name} (${store.index_url})'" "Failed to refresh extension store '${store.name} (${store.indexUrl})'"
} }
} }
.getOrNull()
?.let { store.indexUrl to it }
}
database.transaction {
refreshed.forEach { (indexUrl, store) ->
if (!database.extension_storeQueries.contains(indexUrl).awaitAsOne()) {
return@forEach
}
upsert(store)
if (indexUrl != store.indexUrl) {
database.extension_storeQueries.delete(indexUrl)
}
}
} }
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
@@ -109,7 +137,7 @@ class ExtensionStoreRepositoryImpl(
} }
override fun getAllAsFlow(): Flow<List<ExtensionStore>> { override fun getAllAsFlow(): Flow<List<ExtensionStore>> {
return database.extension_storeQueries.getAll(::extensionStoreMapper).subscribeToList() return stores
} }
override fun getCountAsFlow(): Flow<Long> { override fun getCountAsFlow(): Flow<Long> {