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 = _isInitialized.asStateFlow() private val downloadManager: DownloadManager by injectLazy() private val scope = CoroutineScope(Job() + Dispatchers.IO) private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap()) private val stubSourcesMap = ConcurrentHashMap() override val sources: Flow> = sourcesMapFlow.map { it.values.toList() } init { scope.launch { extensionManager.isInitialized.first { it } extensionManager.installedExtensionsFlow .collectLatest { extensions -> val mutableMap = ConcurrentHashMap( 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() override fun getStubSources(): List { 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 = "") } }