package eu.kanade.tachiyomi.source import android.content.Context import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.Hitomi import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.NHentai import eu.kanade.tachiyomi.source.online.all.PervEden import eu.kanade.tachiyomi.source.online.english.EightMuses import eu.kanade.tachiyomi.source.online.english.HBrowse import eu.kanade.tachiyomi.source.online.english.Pururin import eu.kanade.tachiyomi.source.online.english.Tsumino import exh.log.xLogD import exh.source.BlacklistedSources import exh.source.DelegatedHttpSource import exh.source.EH_SOURCE_ID import exh.source.EIGHTMUSES_SOURCE_ID import exh.source.EXH_SOURCE_ID import exh.source.EnhancedHttpSource import exh.source.HBROWSE_SOURCE_ID import exh.source.PERV_EDEN_EN_SOURCE_ID import exh.source.PERV_EDEN_IT_SOURCE_ID import exh.source.PURURIN_SOURCE_ID import exh.source.TSUMINO_SOURCE_ID import exh.source.handleSourceLibrary 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.drop import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import rx.Observable import uy.kohesive.injekt.injectLazy import kotlin.reflect.KClass open class SourceManager(private val context: Context) { private val sourcesMap = mutableMapOf() private val stubSourcesMap = mutableMapOf() private val _catalogueSources: MutableStateFlow> = MutableStateFlow(listOf()) val catalogueSources: Flow> = _catalogueSources val onlineSources: Flow> = _catalogueSources.map { sources -> sources.filterIsInstance() } // SY --> private val prefs: PreferencesHelper by injectLazy() private val scope = CoroutineScope(Job() + Dispatchers.Main) // SY <-- init { createInternalSources().forEach { registerSource(it) } // SY --> // Create internal sources createEHSources().forEach { registerSource(it) } // Watch the preference and manage Exhentai prefs.enableExhentai().asFlow() .drop(1) .onEach { if (it) { registerSource(EHentai(EXH_SOURCE_ID, true, context)) } else { sourcesMap.remove(EXH_SOURCE_ID) } }.launchIn(scope) registerSource(MergedSource()) // SY <-- } open fun get(sourceKey: Long): Source? { return sourcesMap[sourceKey] } fun getOrStub(sourceKey: Long): Source { return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) { StubSource(sourceKey) } } fun getOnlineSources() = sourcesMap.values.filterIsInstance() fun getVisibleOnlineSources() = sourcesMap.values.filterIsInstance().filter { it.id !in BlacklistedSources.HIDDEN_SOURCES } fun getCatalogueSources() = sourcesMap.values.filterIsInstance() // SY --> fun getVisibleCatalogueSources() = sourcesMap.values.filterIsInstance().filter { it.id !in BlacklistedSources.HIDDEN_SOURCES } fun getDelegatedCatalogueSources() = sourcesMap.values.filterIsInstance().mapNotNull { enhancedHttpSource -> enhancedHttpSource.enhancedSource as? DelegatedHttpSource } // SY <-- internal fun registerSource(source: Source) { // EXH --> val sourceQName = source::class.qualifiedName val factories = DELEGATED_SOURCES.entries.filter { it.value.factory }.map { it.value.originalSourceQualifiedClassName } val delegate = if (sourceQName != null) { val matched = factories.find { sourceQName.startsWith(it) } if (matched != null) { DELEGATED_SOURCES[matched] } else DELEGATED_SOURCES[sourceQName] } else null val newSource = if (source is HttpSource && delegate != null) { xLogD("Delegating source: %s -> %s!", sourceQName, delegate.newSourceClass.qualifiedName) val enhancedSource = EnhancedHttpSource( source, delegate.newSourceClass.constructors.find { it.parameters.size == 2 }!!.call(source, context), ) currentDelegatedSources[enhancedSource.originalSource.id] = DelegatedSource( enhancedSource.originalSource.name, enhancedSource.originalSource.id, enhancedSource.originalSource::class.qualifiedName ?: delegate.originalSourceQualifiedClassName, (enhancedSource.enhancedSource as DelegatedHttpSource)::class, delegate.factory, ) enhancedSource } else source if (source.id in BlacklistedSources.BLACKLISTED_EXT_SOURCES) { xLogD("Removing blacklisted source: (id: %s, name: %s, lang: %s)!", source.id, source.name, (source as? CatalogueSource)?.lang) return } // EXH <-- if (!sourcesMap.containsKey(source.id)) { sourcesMap[source.id] = newSource } triggerCatalogueSources() } internal fun unregisterSource(source: Source) { sourcesMap.remove(source.id) triggerCatalogueSources() // SY --> currentDelegatedSources.remove(source.id) // SY <-- } private fun triggerCatalogueSources() { _catalogueSources.update { sourcesMap.values.filterIsInstance() } } private fun createInternalSources(): List = listOf( LocalSource(context), ) // SY --> private fun createEHSources(): List { val sources = listOf( EHentai(EH_SOURCE_ID, false, context), ) return if (prefs.enableExhentai().get()) { sources + EHentai(EXH_SOURCE_ID, true, context) } else sources } // SY <-- inner class StubSource(override val id: Long) : Source { override val name: String get() = id.toString() override fun fetchMangaDetails(manga: SManga): Observable { return Observable.error(getSourceNotInstalledException()) } override fun fetchChapterList(manga: SManga): Observable> { return Observable.error(getSourceNotInstalledException()) } override fun fetchPageList(chapter: SChapter): Observable> { return Observable.error(getSourceNotInstalledException()) } override fun toString(): String { return name } private fun getSourceNotInstalledException(): SourceNotInstalledException { return SourceNotInstalledException(id) } } inner class SourceNotInstalledException(val id: Long) : Exception(context.getString(R.string.source_not_installed, id.toString())) // SY --> companion object { private const val fillInSourceId = Long.MAX_VALUE val DELEGATED_SOURCES = listOf( DelegatedSource( "Pururin", PURURIN_SOURCE_ID, "eu.kanade.tachiyomi.extension.en.pururin.Pururin", Pururin::class, ), DelegatedSource( "Tsumino", TSUMINO_SOURCE_ID, "eu.kanade.tachiyomi.extension.en.tsumino.Tsumino", Tsumino::class, ), DelegatedSource( "MangaDex", fillInSourceId, "eu.kanade.tachiyomi.extension.all.mangadex", MangaDex::class, true, ), DelegatedSource( "HBrowse", HBROWSE_SOURCE_ID, "eu.kanade.tachiyomi.extension.en.hbrowse.HBrowse", HBrowse::class, ), DelegatedSource( "8Muses", EIGHTMUSES_SOURCE_ID, "eu.kanade.tachiyomi.extension.en.eightmuses.EightMuses", EightMuses::class, ), DelegatedSource( "Hitomi", fillInSourceId, "eu.kanade.tachiyomi.extension.all.hitomi.Hitomi", Hitomi::class, true, ), DelegatedSource( "PervEden English", PERV_EDEN_EN_SOURCE_ID, "eu.kanade.tachiyomi.extension.en.perveden.Perveden", PervEden::class, ), DelegatedSource( "PervEden Italian", PERV_EDEN_IT_SOURCE_ID, "eu.kanade.tachiyomi.extension.it.perveden.Perveden", PervEden::class, ), DelegatedSource( "NHentai", fillInSourceId, "eu.kanade.tachiyomi.extension.all.nhentai.NHentai", NHentai::class, true, ), ).associateBy { it.originalSourceQualifiedClassName } val currentDelegatedSources = ListenMutableMap(mutableMapOf(), ::handleSourceLibrary) data class DelegatedSource( val sourceName: String, val sourceId: Long, val originalSourceQualifiedClassName: String, val newSourceClass: KClass, val factory: Boolean = false, ) } class ListenMutableMap(private val internalMap: MutableMap, val listener: () -> Unit) : MutableMap { override val size: Int get() = internalMap.size override fun containsKey(key: K): Boolean = internalMap.containsKey(key) override fun containsValue(value: V): Boolean = internalMap.containsValue(value) override fun get(key: K): V? = internalMap[key] override fun isEmpty(): Boolean = internalMap.isEmpty() override val entries: MutableSet> get() = internalMap.entries override val keys: MutableSet get() = internalMap.keys override val values: MutableCollection get() = internalMap.values override fun clear() { val clearResult = internalMap.clear() listener() return clearResult } override fun put(key: K, value: V): V? { val putResult = internalMap.put(key, value) if (putResult == null) { listener() } return putResult } override fun putAll(from: Map) { internalMap.putAll(from) listener() } override fun remove(key: K): V? { val removeResult = internalMap.remove(key) if (removeResult != null) { listener() } return removeResult } } // SY <-- }