Files
mihon-bookoasis/app/src/main/java/eu/kanade/tachiyomi/source/AndroidSourceManager.kt
T
AntsyLich 5fa5f7e159 Load extensions off the main thread (#3788)
Assisted-by: Claude:claude-opus-5
2026-08-16 16:35:38 +00:00

125 lines
4.4 KiB
Kotlin

package eu.kanade.tachiyomi.source
import android.content.Context
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import tachiyomi.domain.source.model.StubSource
import tachiyomi.domain.source.repository.StubSourceRepository
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.source.local.LocalSource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.ConcurrentHashMap
class AndroidSourceManager(
private val context: Context,
private val extensionManager: ExtensionManager,
private val sourceRepository: StubSourceRepository,
) : SourceManager {
private val _isInitialized = MutableStateFlow(false)
override val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
private val downloadManager: DownloadManager by injectLazy()
private val scope = CoroutineScope(Job() + Dispatchers.IO)
private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap<Long, Source>())
private val stubSourcesMap = ConcurrentHashMap<Long, StubSource>()
override val sources: Flow<List<Source>> = sourcesMapFlow.map { it.values.toList() }
init {
scope.launch {
extensionManager.isInitialized.first { it }
extensionManager.installedExtensionsFlow
.collectLatest { extensions ->
val mutableMap = ConcurrentHashMap<Long, Source>(
mapOf(
LocalSource.ID to LocalSource(
context,
Injekt.get(),
Injekt.get(),
),
),
)
extensions.forEach { extension ->
extension.sources.forEach {
mutableMap[it.id] = it
registerStubSource(StubSource.from(it))
}
}
sourcesMapFlow.value = mutableMap
_isInitialized.value = true
}
}
scope.launch {
sourceRepository.subscribeAll()
.collectLatest { sources ->
val mutableMap = stubSourcesMap.toMutableMap()
sources.forEach {
mutableMap[it.id] = it
}
}
}
}
override fun get(sourceKey: Long): Source? {
return sourcesMapFlow.value[sourceKey]
}
override fun getOrStub(sourceKey: Long): Source {
return sourcesMapFlow.value[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
runBlocking { createStubSource(sourceKey) }
}
}
override fun getAll() = sourcesMapFlow.value.values.toList()
override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance<HttpSource>()
override fun getStubSources(): List<StubSource> {
val onlineSourceIds = getOnlineSources().map { it.id }
return stubSourcesMap.values.filterNot { it.id in onlineSourceIds }
}
private fun registerStubSource(source: StubSource) {
scope.launch {
val dbSource = sourceRepository.getStubSource(source.id)
if (dbSource == source) return@launch
sourceRepository.upsertStubSource(source.id, source.lang, source.name)
if (dbSource != null) {
downloadManager.renameSource(dbSource, source)
}
}
}
private suspend fun createStubSource(id: Long): StubSource {
sourceRepository.getStubSource(id)?.let {
return it
}
extensionManager.getSourceData(id)?.let {
registerStubSource(it)
return it
}
return StubSource(id = id, lang = "", name = "")
}
}