2018-02-24 17:13:43 +01:00
|
|
|
package eu.kanade.tachiyomi.source
|
|
|
|
|
|
|
|
|
|
import android.content.Context
|
2022-08-30 19:15:34 +02:00
|
|
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
2022-06-14 15:10:40 +02:00
|
|
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
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
|
2021-06-02 02:49:26 +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.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
|
2021-03-07 06:23:23 +01:00
|
|
|
import exh.log.xLogD
|
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
|
2022-07-16 21:08:15 +02:00
|
|
|
import exh.source.MERGED_SOURCE_ID
|
2021-01-26 06:40:57 +01:00
|
|
|
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
|
2022-04-24 20:35:59 +02:00
|
|
|
import kotlinx.coroutines.flow.Flow
|
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
2024-03-16 00:51:56 +01:00
|
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.asStateFlow
|
2022-07-16 21:08:15 +02:00
|
|
|
import kotlinx.coroutines.flow.collectLatest
|
|
|
|
|
import kotlinx.coroutines.flow.combine
|
2022-04-30 17:49:02 +02:00
|
|
|
import kotlinx.coroutines.flow.map
|
2022-07-16 21:08:15 +02:00
|
|
|
import kotlinx.coroutines.launch
|
2022-06-14 15:10:40 +02:00
|
|
|
import kotlinx.coroutines.runBlocking
|
2023-03-17 01:18:41 +01:00
|
|
|
import tachiyomi.domain.UnsortedPreferences
|
2023-03-05 18:38:31 +01:00
|
|
|
import tachiyomi.domain.source.model.StubSource
|
2023-05-03 16:33:05 +02:00
|
|
|
import tachiyomi.domain.source.repository.StubSourceRepository
|
2023-03-05 18:38:31 +01:00
|
|
|
import tachiyomi.domain.source.service.SourceManager
|
2023-02-26 22:16:49 +01:00
|
|
|
import tachiyomi.source.local.LocalSource
|
|
|
|
|
import uy.kohesive.injekt.Injekt
|
|
|
|
|
import uy.kohesive.injekt.api.get
|
2020-01-06 17:43:11 +01:00
|
|
|
import uy.kohesive.injekt.injectLazy
|
2022-10-29 03:29:38 +02:00
|
|
|
import java.util.concurrent.ConcurrentHashMap
|
2022-10-30 00:39:25 +02:00
|
|
|
import kotlin.reflect.KClass
|
2018-02-24 17:13:43 +01:00
|
|
|
|
2023-03-05 18:38:31 +01:00
|
|
|
class AndroidSourceManager(
|
2022-07-16 21:08:15 +02:00
|
|
|
private val context: Context,
|
|
|
|
|
private val extensionManager: ExtensionManager,
|
2023-05-03 16:33:05 +02:00
|
|
|
private val sourceRepository: StubSourceRepository,
|
2023-03-05 18:38:31 +01:00
|
|
|
) : SourceManager {
|
2023-03-20 13:41:44 +01:00
|
|
|
|
2024-06-26 23:25:26 +02:00
|
|
|
private val _isInitialized = MutableStateFlow(false)
|
|
|
|
|
override val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
|
|
|
|
|
|
2022-08-30 19:15:34 +02:00
|
|
|
private val downloadManager: DownloadManager by injectLazy()
|
2018-02-24 17:13:43 +01:00
|
|
|
|
2022-07-16 21:08:15 +02:00
|
|
|
private val scope = CoroutineScope(Job() + Dispatchers.IO)
|
|
|
|
|
|
2022-10-30 22:40:17 +01:00
|
|
|
private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap<Long, Source>())
|
2022-06-14 15:10:40 +02:00
|
|
|
|
2022-10-29 03:29:38 +02:00
|
|
|
private val stubSourcesMap = ConcurrentHashMap<Long, StubSource>()
|
2018-07-08 21:51:26 +02:00
|
|
|
|
2023-11-05 04:28:41 +01:00
|
|
|
override val catalogueSources: Flow<List<CatalogueSource>> = sourcesMapFlow.map {
|
|
|
|
|
it.values.filterIsInstance<CatalogueSource>()
|
|
|
|
|
}
|
2022-04-24 20:35:59 +02:00
|
|
|
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY -->
|
2022-09-25 16:07:06 +02:00
|
|
|
private val preferences: UnsortedPreferences by injectLazy()
|
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 {
|
2022-07-16 21:08:15 +02:00
|
|
|
scope.launch {
|
2022-10-21 20:42:21 +02:00
|
|
|
extensionManager.installedExtensionsFlow
|
2022-07-16 21:08:15 +02:00
|
|
|
// SY -->
|
2022-09-17 17:48:24 +02:00
|
|
|
.combine(preferences.enableExhentai().changes()) { extensions, enableExhentai ->
|
2022-07-16 21:08:15 +02:00
|
|
|
extensions to enableExhentai
|
|
|
|
|
}
|
|
|
|
|
// SY <--
|
|
|
|
|
.collectLatest { (extensions, enableExhentai) ->
|
2023-02-26 22:16:49 +01:00
|
|
|
val mutableMap = ConcurrentHashMap<Long, Source>(
|
|
|
|
|
mapOf(
|
|
|
|
|
LocalSource.ID to LocalSource(
|
|
|
|
|
context,
|
|
|
|
|
Injekt.get(),
|
|
|
|
|
Injekt.get(),
|
|
|
|
|
// SY -->
|
|
|
|
|
preferences.allowLocalSourceHiddenFolders()::get,
|
|
|
|
|
// SY <--
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
).apply {
|
2022-07-16 21:08:15 +02:00
|
|
|
// SY -->
|
|
|
|
|
put(EH_SOURCE_ID, EHentai(EH_SOURCE_ID, false, context))
|
|
|
|
|
if (enableExhentai) {
|
|
|
|
|
put(EXH_SOURCE_ID, EHentai(EXH_SOURCE_ID, true, context))
|
|
|
|
|
}
|
|
|
|
|
put(MERGED_SOURCE_ID, MergedSource())
|
|
|
|
|
// SY <--
|
|
|
|
|
}
|
|
|
|
|
extensions.forEach { extension ->
|
|
|
|
|
extension.sources.mapNotNull { it.toInternalSource() }.forEach {
|
|
|
|
|
mutableMap[it.id] = it
|
2023-11-12 00:25:27 +01:00
|
|
|
registerStubSource(StubSource.from(it))
|
2022-07-16 21:08:15 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-30 22:40:17 +01:00
|
|
|
sourcesMapFlow.value = mutableMap
|
2024-03-16 00:51:56 +01:00
|
|
|
_isInitialized.value = true
|
2020-09-29 18:15:41 +02:00
|
|
|
}
|
2018-07-08 21:51:26 +02:00
|
|
|
}
|
2018-02-24 17:13:43 +01:00
|
|
|
|
2022-07-16 21:08:15 +02:00
|
|
|
scope.launch {
|
|
|
|
|
sourceRepository.subscribeAll()
|
|
|
|
|
.collectLatest { sources ->
|
|
|
|
|
val mutableMap = stubSourcesMap.toMutableMap()
|
|
|
|
|
sources.forEach {
|
2023-05-03 16:33:05 +02:00
|
|
|
mutableMap[it.id] = it
|
2022-07-16 21:08:15 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-06-21 06:44:04 +02:00
|
|
|
}
|
2019-07-30 16:22:46 +02:00
|
|
|
|
2022-07-16 21:08:15 +02:00
|
|
|
private fun Source.toInternalSource(): Source? {
|
2019-04-14 18:48:59 +02:00
|
|
|
// EXH -->
|
2022-07-16 21:08:15 +02:00
|
|
|
val sourceQName = this::class.qualifiedName
|
2023-11-05 04:28:41 +01:00
|
|
|
val factories = DELEGATED_SOURCES.entries
|
|
|
|
|
.filter { it.value.factory }
|
|
|
|
|
.map { it.value.originalSourceQualifiedClassName }
|
2020-06-26 01:35:44 +02:00
|
|
|
val delegate = if (sourceQName != null) {
|
|
|
|
|
val matched = factories.find { sourceQName.startsWith(it) }
|
|
|
|
|
if (matched != null) {
|
|
|
|
|
DELEGATED_SOURCES[matched]
|
2022-09-12 01:43:45 +02:00
|
|
|
} else {
|
|
|
|
|
DELEGATED_SOURCES[sourceQName]
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
null
|
|
|
|
|
}
|
2022-07-16 21:08:15 +02:00
|
|
|
val newSource = if (this is HttpSource && delegate != null) {
|
2021-03-07 06:23:23 +01:00
|
|
|
xLogD("Delegating source: %s -> %s!", sourceQName, delegate.newSourceClass.qualifiedName)
|
2020-06-26 01:35:44 +02:00
|
|
|
val enhancedSource = EnhancedHttpSource(
|
2022-07-16 21:08:15 +02:00
|
|
|
this,
|
|
|
|
|
delegate.newSourceClass.constructors.find { it.parameters.size == 2 }!!.call(this, 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,
|
2022-04-08 21:30:30 +02:00
|
|
|
delegate.factory,
|
2021-01-26 06:40:57 +01:00
|
|
|
)
|
2020-06-26 01:35:44 +02:00
|
|
|
enhancedSource
|
2022-09-12 01:43:45 +02:00
|
|
|
} else {
|
|
|
|
|
this
|
|
|
|
|
}
|
2019-04-14 18:48:59 +02:00
|
|
|
|
2022-07-16 21:08:15 +02:00
|
|
|
return if (id in BlacklistedSources.BLACKLISTED_EXT_SOURCES) {
|
2023-11-05 04:28:41 +01:00
|
|
|
xLogD(
|
|
|
|
|
"Removing blacklisted source: (id: %s, name: %s, lang: %s)!",
|
|
|
|
|
id,
|
|
|
|
|
name,
|
|
|
|
|
(this as? CatalogueSource)?.lang,
|
|
|
|
|
)
|
2022-07-16 21:08:15 +02:00
|
|
|
null
|
2022-09-12 01:43:45 +02:00
|
|
|
} else {
|
|
|
|
|
newSource
|
|
|
|
|
}
|
2019-04-14 18:48:59 +02:00
|
|
|
// EXH <--
|
2022-07-16 21:08:15 +02:00
|
|
|
}
|
2019-04-14 18:48:59 +02:00
|
|
|
|
2023-03-05 18:38:31 +01:00
|
|
|
override fun get(sourceKey: Long): Source? {
|
2022-10-30 22:40:17 +01:00
|
|
|
return sourcesMapFlow.value[sourceKey]
|
2018-02-24 17:13:43 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-05 18:38:31 +01:00
|
|
|
override fun getOrStub(sourceKey: Long): Source {
|
2022-10-30 22:40:17 +01:00
|
|
|
return sourcesMapFlow.value[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) {
|
2022-07-16 21:08:15 +02:00
|
|
|
runBlocking { createStubSource(sourceKey) }
|
2022-06-14 15:10:40 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-05 18:38:31 +01:00
|
|
|
override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance<HttpSource>()
|
2018-02-24 17:13:43 +01:00
|
|
|
|
2023-03-05 18:38:31 +01:00
|
|
|
override fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance<CatalogueSource>()
|
2022-04-24 20:35:59 +02:00
|
|
|
|
2023-03-05 18:38:31 +01:00
|
|
|
override fun getStubSources(): List<StubSource> {
|
2022-07-16 21:08:15 +02:00
|
|
|
val onlineSourceIds = getOnlineSources().map { it.id }
|
|
|
|
|
return stubSourcesMap.values.filterNot { it.id in onlineSourceIds }
|
|
|
|
|
}
|
2018-02-24 17:13:43 +01:00
|
|
|
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY -->
|
2023-11-05 04:28:41 +01:00
|
|
|
override fun getVisibleOnlineSources() = sourcesMapFlow.value.values
|
|
|
|
|
.filterIsInstance<HttpSource>()
|
|
|
|
|
.filter {
|
|
|
|
|
it.id !in BlacklistedSources.HIDDEN_SOURCES
|
|
|
|
|
}
|
2022-05-01 16:46:37 +02:00
|
|
|
|
2023-11-05 04:28:41 +01:00
|
|
|
override fun getVisibleCatalogueSources() = sourcesMapFlow.value.values
|
|
|
|
|
.filterIsInstance<CatalogueSource>()
|
|
|
|
|
.filter {
|
|
|
|
|
it.id !in BlacklistedSources.HIDDEN_SOURCES
|
|
|
|
|
}
|
2022-07-16 21:08:15 +02:00
|
|
|
|
2023-11-05 04:28:41 +01:00
|
|
|
fun getDelegatedCatalogueSources() = sourcesMapFlow.value.values
|
|
|
|
|
.filterIsInstance<EnhancedHttpSource>()
|
|
|
|
|
.mapNotNull { enhancedHttpSource ->
|
|
|
|
|
enhancedHttpSource.enhancedSource as? DelegatedHttpSource
|
|
|
|
|
}
|
2020-06-20 02:50:16 +02:00
|
|
|
// SY <--
|
2018-07-08 21:51:26 +02:00
|
|
|
|
2023-05-03 16:33:05 +02:00
|
|
|
private fun registerStubSource(source: StubSource) {
|
2022-07-16 21:08:15 +02:00
|
|
|
scope.launch {
|
2023-05-03 16:33:05 +02:00
|
|
|
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)
|
2022-08-30 19:15:34 +02:00
|
|
|
}
|
2022-07-16 21:08:15 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-14 15:10:40 +02:00
|
|
|
private suspend fun createStubSource(id: Long): StubSource {
|
2023-05-03 16:33:05 +02:00
|
|
|
sourceRepository.getStubSource(id)?.let {
|
|
|
|
|
return it
|
2022-06-14 15:10:40 +02:00
|
|
|
}
|
|
|
|
|
extensionManager.getSourceData(id)?.let {
|
|
|
|
|
registerStubSource(it)
|
2023-05-03 16:33:05 +02:00
|
|
|
return it
|
2022-06-14 15:10:40 +02:00
|
|
|
}
|
2023-05-14 18:24:40 +02:00
|
|
|
return StubSource(id = id, lang = "", name = "")
|
2022-06-14 15:10:40 +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(
|
|
|
|
|
"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",
|
2022-04-08 21:30:30 +02:00
|
|
|
Pururin::class,
|
2020-01-06 17:43:11 +01:00
|
|
|
),
|
|
|
|
|
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",
|
2022-04-08 21:30:30 +02:00
|
|
|
Tsumino::class,
|
2020-08-20 00:20:05 +02:00
|
|
|
),
|
2021-06-02 02:49:26 +02:00
|
|
|
DelegatedSource(
|
2020-06-26 01:35:44 +02:00
|
|
|
"MangaDex",
|
|
|
|
|
fillInSourceId,
|
|
|
|
|
"eu.kanade.tachiyomi.extension.all.mangadex",
|
|
|
|
|
MangaDex::class,
|
2022-04-08 21:30:30 +02:00
|
|
|
true,
|
2021-06-02 02:49:26 +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",
|
2022-04-08 21:30:30 +02:00
|
|
|
HBrowse::class,
|
2020-08-11 03:15:08 +02:00
|
|
|
),
|
|
|
|
|
DelegatedSource(
|
|
|
|
|
"8Muses",
|
2020-08-11 22:58:50 +02:00
|
|
|
EIGHTMUSES_SOURCE_ID,
|
2021-02-16 01:35:50 +01:00
|
|
|
"eu.kanade.tachiyomi.extension.en.eightmuses.EightMuses",
|
2022-04-08 21:30:30 +02:00
|
|
|
EightMuses::class,
|
2020-08-11 05:29:10 +02:00
|
|
|
),
|
2020-08-12 06:19:51 +02:00
|
|
|
DelegatedSource(
|
|
|
|
|
"NHentai",
|
|
|
|
|
fillInSourceId,
|
|
|
|
|
"eu.kanade.tachiyomi.extension.all.nhentai.NHentai",
|
|
|
|
|
NHentai::class,
|
2022-04-08 21:30:30 +02:00
|
|
|
true,
|
|
|
|
|
),
|
2019-08-08 19:31:47 +02:00
|
|
|
).associateBy { it.originalSourceQualifiedClassName }
|
2019-04-06 13:35:36 +02:00
|
|
|
|
2023-11-05 04:28:41 +01:00
|
|
|
val currentDelegatedSources: MutableMap<Long, DelegatedSource> =
|
|
|
|
|
ListenMutableMap(mutableMapOf(), ::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>,
|
2022-04-08 21:30:30 +02:00
|
|
|
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
|
|
|
|
2022-07-26 22:30:51 +02:00
|
|
|
private class ListenMutableMap<K, V>(
|
|
|
|
|
private val internalMap: MutableMap<K, V>,
|
|
|
|
|
private val listener: () -> Unit,
|
|
|
|
|
) : MutableMap<K, V> by internalMap {
|
2021-01-26 06:40:57 +01:00
|
|
|
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
|
|
|
}
|