arkon 6618f848ea Unify NSFW flagging for sources/extensions
Since multisource extensions are no longer a thing, we now simply rely on the flag at the extension level, i.e. the per-Source/SourceFactory `@Nsfw` annotation is no longer checked.
We'll have to remove all of the annotation usages from the existing sources, which will also effectively break the setting for older versions of the app.

(cherry picked from commit a2cf210a524894e5c1d1f062e4813086b46777a4)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionPresenter.kt
2021-09-24 12:47:29 -04:00

310 lines
11 KiB
Kotlin
Executable File

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.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import rx.Observable
import uy.kohesive.injekt.injectLazy
import kotlin.reflect.KClass
open class SourceManager(private val context: Context) {
private val sourcesMap = mutableMapOf<Long, Source>()
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
// 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<HttpSource>()
fun getVisibleOnlineSources() = sourcesMap.values.filterIsInstance<HttpSource>().filter {
it.id !in BlacklistedSources.HIDDEN_SOURCES
}
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>()
// SY -->
fun getVisibleCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>().filter {
it.id !in BlacklistedSources.HIDDEN_SOURCES
}
fun getDelegatedCatalogueSources() = sourcesMap.values.filterIsInstance<EnhancedHttpSource>().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
}
}
internal fun unregisterSource(source: Source) {
sourcesMap.remove(source.id)
// SY -->
currentDelegatedSources.remove(source.id)
// SY <--
}
private fun createInternalSources(): List<Source> = listOf(
LocalSource(context)
)
// SY -->
private fun createEHSources(): List<Source> {
val sources = listOf<HttpSource>(
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<SManga> {
return Observable.error(getSourceNotInstalledException())
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return Observable.error(getSourceNotInstalledException())
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
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<Long, DelegatedSource>(), ::handleSourceLibrary)
data class DelegatedSource(
val sourceName: String,
val sourceId: Long,
val originalSourceQualifiedClassName: String,
val newSourceClass: KClass<out DelegatedHttpSource>,
val factory: Boolean = false
)
}
class ListenMutableMap<K, V>(private val internalMap: MutableMap<K, V>, val listener: () -> Unit) : MutableMap<K, V> {
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<MutableMap.MutableEntry<K, V>>
get() = internalMap.entries
override val keys: MutableSet<K>
get() = internalMap.keys
override val values: MutableCollection<V>
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<out K, V>) {
internalMap.putAll(from)
listener()
}
override fun remove(key: K): V? {
val removeResult = internalMap.remove(key)
if (removeResult != null) {
listener()
}
return removeResult
}
}
// SY <--
}