2018-02-24 17:13:43 +01:00
|
|
|
package eu.kanade.tachiyomi.source
|
|
|
|
|
|
|
|
|
|
import android.content.Context
|
2019-04-14 18:48:59 +02:00
|
|
|
import com.elvishew.xlog.XLog
|
2018-07-08 21:51:26 +02:00
|
|
|
import eu.kanade.tachiyomi.R
|
2020-01-06 17:43:11 +01:00
|
|
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
2018-07-08 21:51:26 +02:00
|
|
|
import eu.kanade.tachiyomi.source.model.Page
|
|
|
|
|
import eu.kanade.tachiyomi.source.model.SChapter
|
|
|
|
|
import eu.kanade.tachiyomi.source.model.SManga
|
2018-02-24 17:13:43 +01:00
|
|
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
2020-01-06 17:43:11 +01:00
|
|
|
import eu.kanade.tachiyomi.source.online.all.EHentai
|
|
|
|
|
import eu.kanade.tachiyomi.source.online.all.Hitomi
|
2020-08-20 00:20:05 +02:00
|
|
|
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
2020-01-06 17:43:11 +01:00
|
|
|
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.HentaiCafe
|
|
|
|
|
import eu.kanade.tachiyomi.source.online.english.Pururin
|
|
|
|
|
import eu.kanade.tachiyomi.source.online.english.Tsumino
|
2019-04-14 18:48:59 +02:00
|
|
|
import exh.source.BlacklistedSources
|
2019-04-06 13:35:36 +02:00
|
|
|
import exh.source.DelegatedHttpSource
|
2021-01-26 06:40:57 +01:00
|
|
|
import exh.source.EH_SOURCE_ID
|
|
|
|
|
import exh.source.EIGHTMUSES_SOURCE_ID
|
|
|
|
|
import exh.source.EXH_SOURCE_ID
|
2019-04-06 13:35:36 +02:00
|
|
|
import exh.source.EnhancedHttpSource
|
2021-01-26 06:40:57 +01:00
|
|
|
import exh.source.HBROWSE_SOURCE_ID
|
|
|
|
|
import exh.source.HENTAI_CAFE_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
|
2020-05-11 01:22:10 +02:00
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.Job
|
2020-09-29 18:15:41 +02:00
|
|
|
import kotlinx.coroutines.flow.drop
|
2020-05-11 01:22:10 +02:00
|
|
|
import kotlinx.coroutines.flow.launchIn
|
2020-09-29 18:15:41 +02:00
|
|
|
import kotlinx.coroutines.flow.onEach
|
2020-01-05 20:43:07 +01:00
|
|
|
import rx.Observable
|
2020-01-06 17:43:11 +01:00
|
|
|
import uy.kohesive.injekt.injectLazy
|
2020-09-14 00:48:20 +02:00
|
|
|
import kotlin.reflect.KClass
|
2018-02-24 17:13:43 +01:00
|
|
|
|
|
|
|
|
open class SourceManager(private val context: Context) {
|
|
|
|
|
|
|
|
|
|
private val sourcesMap = mutableMapOf<Long, Source>()
|
|
|
|
|
|
2018-07-08 21:51:26 +02:00
|
|
|
private val stubSourcesMap = mutableMapOf<Long, StubSource>()
|
|
|
|
|
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY -->
|
|
|
|
|
private val prefs: PreferencesHelper by injectLazy()
|
|
|
|
|
|
2020-05-11 01:22:10 +02:00
|
|
|
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY <--
|
2020-05-11 01:22:10 +02:00
|
|
|
|
2018-02-24 17:13:43 +01:00
|
|
|
init {
|
|
|
|
|
createInternalSources().forEach { registerSource(it) }
|
2018-02-25 21:34:19 +01:00
|
|
|
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY -->
|
2020-09-29 18:15:41 +02:00
|
|
|
// 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)
|
2019-07-30 07:41:30 +02:00
|
|
|
|
|
|
|
|
registerSource(MergedSource())
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY <--
|
2018-02-24 17:13:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
open fun get(sourceKey: Long): Source? {
|
|
|
|
|
return sourcesMap[sourceKey]
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-08 21:51:26 +02:00
|
|
|
fun getOrStub(sourceKey: Long): Source {
|
|
|
|
|
return sourcesMap[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
|
|
|
|
|
StubSource(sourceKey)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-24 17:13:43 +01:00
|
|
|
fun getOnlineSources() = sourcesMap.values.filterIsInstance<HttpSource>()
|
|
|
|
|
|
2020-07-30 21:56:18 +02:00
|
|
|
fun getVisibleOnlineSources() = sourcesMap.values.filterIsInstance<HttpSource>().filter {
|
|
|
|
|
it.id !in BlacklistedSources.HIDDEN_SOURCES
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-24 17:13:43 +01:00
|
|
|
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>()
|
|
|
|
|
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY -->
|
2019-07-30 16:22:46 +02:00
|
|
|
fun getVisibleCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>().filter {
|
|
|
|
|
it.id !in BlacklistedSources.HIDDEN_SOURCES
|
|
|
|
|
}
|
2020-06-21 06:44:04 +02:00
|
|
|
|
|
|
|
|
fun getDelegatedCatalogueSources() = sourcesMap.values.filterIsInstance<EnhancedHttpSource>().mapNotNull { enhancedHttpSource ->
|
2020-06-27 05:19:09 +02:00
|
|
|
enhancedHttpSource.enhancedSource as? DelegatedHttpSource
|
2020-06-21 06:44:04 +02:00
|
|
|
}
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY <--
|
2019-07-30 16:22:46 +02:00
|
|
|
|
2020-12-12 22:15:18 +01:00
|
|
|
internal fun registerSource(source: Source) {
|
2019-04-14 18:48:59 +02:00
|
|
|
// EXH -->
|
2019-04-06 13:35:36 +02:00
|
|
|
val sourceQName = source::class.qualifiedName
|
2020-06-26 01:35:44 +02:00
|
|
|
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
|
2020-01-06 17:43:11 +01:00
|
|
|
val newSource = if (source is HttpSource && delegate != null) {
|
2020-11-25 21:57:05 +01:00
|
|
|
XLog.tag("SourceManager").d("Delegating source: %s -> %s!", sourceQName, delegate.newSourceClass.qualifiedName)
|
2020-06-26 01:35:44 +02:00
|
|
|
val enhancedSource = EnhancedHttpSource(
|
2020-01-06 17:43:11 +01:00
|
|
|
source,
|
2020-07-16 01:16:21 +02:00
|
|
|
delegate.newSourceClass.constructors.find { it.parameters.size == 2 }!!.call(source, context)
|
2019-04-06 13:35:36 +02:00
|
|
|
)
|
2021-01-26 06:40:57 +01:00
|
|
|
|
|
|
|
|
currentDelegatedSources[enhancedSource.originalSource.id] = DelegatedSource(
|
|
|
|
|
enhancedSource.originalSource.name,
|
|
|
|
|
enhancedSource.originalSource.id,
|
|
|
|
|
enhancedSource.originalSource::class.qualifiedName ?: delegate.originalSourceQualifiedClassName,
|
|
|
|
|
(enhancedSource.enhancedSource as DelegatedHttpSource)::class,
|
|
|
|
|
delegate.factory
|
|
|
|
|
)
|
2020-06-26 01:35:44 +02:00
|
|
|
enhancedSource
|
2019-04-06 13:35:36 +02:00
|
|
|
} else source
|
2019-04-14 18:48:59 +02:00
|
|
|
|
2020-01-06 17:43:11 +01:00
|
|
|
if (source.id in BlacklistedSources.BLACKLISTED_EXT_SOURCES) {
|
2020-11-25 21:57:05 +01:00
|
|
|
XLog.tag("SourceManager").d("Removing blacklisted source: (id: %s, name: %s, lang: %s)!", source.id, source.name, (source as? CatalogueSource)?.lang)
|
2019-04-14 18:48:59 +02:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// EXH <--
|
|
|
|
|
|
2020-12-12 22:15:18 +01:00
|
|
|
if (!sourcesMap.containsKey(source.id)) {
|
2019-04-06 13:35:36 +02:00
|
|
|
sourcesMap[source.id] = newSource
|
2018-02-24 17:13:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal fun unregisterSource(source: Source) {
|
|
|
|
|
sourcesMap.remove(source.id)
|
2021-01-26 06:40:57 +01:00
|
|
|
// SY -->
|
|
|
|
|
currentDelegatedSources.remove(source.id)
|
|
|
|
|
// SY <--
|
2018-02-24 17:13:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun createInternalSources(): List<Source> = listOf(
|
2020-04-25 20:24:45 +02:00
|
|
|
LocalSource(context)
|
2018-02-24 17:13:43 +01:00
|
|
|
)
|
|
|
|
|
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY -->
|
2018-02-24 17:13:43 +01:00
|
|
|
private fun createEHSources(): List<Source> {
|
|
|
|
|
val exSrcs = mutableListOf<HttpSource>(
|
2020-01-06 17:43:11 +01:00
|
|
|
EHentai(EH_SOURCE_ID, false, context)
|
2018-02-24 17:13:43 +01:00
|
|
|
)
|
2020-05-11 01:22:10 +02:00
|
|
|
if (prefs.enableExhentai().get()) {
|
2018-02-24 17:13:43 +01:00
|
|
|
exSrcs += EHentai(EXH_SOURCE_ID, true, context)
|
|
|
|
|
}
|
|
|
|
|
return exSrcs
|
|
|
|
|
}
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY <--
|
2018-07-08 21:51:26 +02:00
|
|
|
|
2021-01-10 17:11:53 +01:00
|
|
|
inner class StubSource(override val id: Long) : Source {
|
2018-07-08 21:51:26 +02:00
|
|
|
|
|
|
|
|
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(): Exception {
|
|
|
|
|
return Exception(context.getString(R.string.source_not_installed, id.toString()))
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-06 13:35:36 +02:00
|
|
|
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY -->
|
2019-04-06 13:35:36 +02:00
|
|
|
companion object {
|
2020-08-11 03:15:08 +02:00
|
|
|
private const val fillInSourceId = Long.MAX_VALUE
|
2019-04-06 13:35:36 +02:00
|
|
|
val DELEGATED_SOURCES = listOf(
|
2020-01-06 17:43:11 +01:00
|
|
|
DelegatedSource(
|
|
|
|
|
"Hentai Cafe",
|
2020-08-11 22:58:50 +02:00
|
|
|
HENTAI_CAFE_SOURCE_ID,
|
2020-01-06 17:43:11 +01:00
|
|
|
"eu.kanade.tachiyomi.extension.all.foolslide.HentaiCafe",
|
|
|
|
|
HentaiCafe::class
|
|
|
|
|
),
|
|
|
|
|
DelegatedSource(
|
|
|
|
|
"Pururin",
|
2020-08-11 22:58:50 +02:00
|
|
|
PURURIN_SOURCE_ID,
|
2020-01-06 17:43:11 +01:00
|
|
|
"eu.kanade.tachiyomi.extension.en.pururin.Pururin",
|
|
|
|
|
Pururin::class
|
|
|
|
|
),
|
|
|
|
|
DelegatedSource(
|
|
|
|
|
"Tsumino",
|
2020-08-11 22:58:50 +02:00
|
|
|
TSUMINO_SOURCE_ID,
|
2020-01-06 17:43:11 +01:00
|
|
|
"eu.kanade.tachiyomi.extension.en.tsumino.Tsumino",
|
|
|
|
|
Tsumino::class
|
2020-08-20 00:20:05 +02:00
|
|
|
),
|
2020-06-26 01:35:44 +02:00
|
|
|
DelegatedSource(
|
|
|
|
|
"MangaDex",
|
|
|
|
|
fillInSourceId,
|
|
|
|
|
"eu.kanade.tachiyomi.extension.all.mangadex",
|
|
|
|
|
MangaDex::class,
|
|
|
|
|
true
|
2020-08-20 00:20:05 +02:00
|
|
|
),
|
2020-08-02 06:50:52 +02:00
|
|
|
DelegatedSource(
|
|
|
|
|
"HBrowse",
|
2020-08-11 22:58:50 +02:00
|
|
|
HBROWSE_SOURCE_ID,
|
2020-08-02 06:50:52 +02:00
|
|
|
"eu.kanade.tachiyomi.extension.en.hbrowse.HBrowse",
|
|
|
|
|
HBrowse::class
|
2020-08-11 03:15:08 +02:00
|
|
|
),
|
|
|
|
|
DelegatedSource(
|
|
|
|
|
"8Muses",
|
2020-08-11 22:58:50 +02:00
|
|
|
EIGHTMUSES_SOURCE_ID,
|
2020-08-11 03:15:08 +02:00
|
|
|
"eu.kanade.tachiyomi.extension.all.eromuse.EroMuse",
|
|
|
|
|
EightMuses::class
|
2020-08-11 05:29:10 +02:00
|
|
|
),
|
|
|
|
|
DelegatedSource(
|
|
|
|
|
"Hitomi",
|
|
|
|
|
fillInSourceId,
|
|
|
|
|
"eu.kanade.tachiyomi.extension.all.hitomi.Hitomi",
|
|
|
|
|
Hitomi::class,
|
|
|
|
|
true
|
2020-08-11 22:58:50 +02:00
|
|
|
),
|
|
|
|
|
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
|
2020-08-12 06:19:51 +02:00
|
|
|
),
|
|
|
|
|
DelegatedSource(
|
|
|
|
|
"NHentai",
|
|
|
|
|
fillInSourceId,
|
|
|
|
|
"eu.kanade.tachiyomi.extension.all.nhentai.NHentai",
|
|
|
|
|
NHentai::class,
|
|
|
|
|
true
|
2020-08-02 06:50:52 +02:00
|
|
|
)
|
2019-08-08 19:31:47 +02:00
|
|
|
).associateBy { it.originalSourceQualifiedClassName }
|
2019-04-06 13:35:36 +02:00
|
|
|
|
2021-01-26 06:40:57 +01:00
|
|
|
val currentDelegatedSources = ListenMutableMap(mutableMapOf<Long, DelegatedSource>(), ::handleSourceLibrary)
|
2020-06-26 01:35:44 +02:00
|
|
|
|
2020-01-06 17:43:11 +01:00
|
|
|
data class DelegatedSource(
|
|
|
|
|
val sourceName: String,
|
|
|
|
|
val sourceId: Long,
|
|
|
|
|
val originalSourceQualifiedClassName: String,
|
2020-06-26 01:35:44 +02:00
|
|
|
val newSourceClass: KClass<out DelegatedHttpSource>,
|
|
|
|
|
val factory: Boolean = false
|
2020-01-06 17:43:11 +01:00
|
|
|
)
|
2019-04-06 13:35:36 +02:00
|
|
|
}
|
2021-01-26 06:40:57 +01:00
|
|
|
|
|
|
|
|
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? = get(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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY <--
|
2018-02-24 17:13:43 +01:00
|
|
|
}
|