From 35ec5936587799f33a264f57729cb4b75c5a0f72 Mon Sep 17 00:00:00 2001 From: Andreas Date: Sat, 16 Jul 2022 21:08:15 +0200 Subject: [PATCH] Use Flow in ExtensionManager and SourceManager (#7547) - Replace ExtensionManager relay and observable with Flow - Inverse SourceManager dependency - SourceManager observers ExtensionManager flow - Separate SourceData from SourceRepository as it created a circular dependency --- .../data/source/SourceDataRepositoryImpl.kt | 23 +++++ .../data/source/SourceRepositoryImpl.kt | 9 -- .../java/eu/kanade/domain/DomainModule.kt | 7 +- .../interactor/GetExtensionLanguages.kt | 2 +- .../interactor/GetExtensionUpdates.kt | 3 +- .../extension/interactor/GetExtensions.kt | 6 +- .../domain/source/interactor/GetSourceData.kt | 20 ---- .../source/interactor/UpsertSourceData.kt | 19 ---- .../source/repository/SourceDataRepository.kt | 12 +++ .../source/repository/SourceRepository.kt | 5 - .../java/eu/kanade/tachiyomi/AppModule.kt | 4 +- .../tachiyomi/extension/ExtensionManager.kt | 64 +++--------- .../kanade/tachiyomi/source/SourceManager.kt | 97 ++++++++++--------- app/src/main/sqldelight/data/sources.sq | 6 +- 14 files changed, 113 insertions(+), 164 deletions(-) create mode 100644 app/src/main/java/eu/kanade/data/source/SourceDataRepositoryImpl.kt delete mode 100644 app/src/main/java/eu/kanade/domain/source/interactor/GetSourceData.kt delete mode 100644 app/src/main/java/eu/kanade/domain/source/interactor/UpsertSourceData.kt create mode 100644 app/src/main/java/eu/kanade/domain/source/repository/SourceDataRepository.kt diff --git a/app/src/main/java/eu/kanade/data/source/SourceDataRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/source/SourceDataRepositoryImpl.kt new file mode 100644 index 000000000..44e68a877 --- /dev/null +++ b/app/src/main/java/eu/kanade/data/source/SourceDataRepositoryImpl.kt @@ -0,0 +1,23 @@ +package eu.kanade.data.source + +import eu.kanade.data.DatabaseHandler +import eu.kanade.domain.source.model.SourceData +import eu.kanade.domain.source.repository.SourceDataRepository +import kotlinx.coroutines.flow.Flow + +class SourceDataRepositoryImpl( + private val handler: DatabaseHandler, +) : SourceDataRepository { + + override fun subscribeAll(): Flow> { + return handler.subscribeToList { sourcesQueries.findAll(sourceDataMapper) } + } + + override suspend fun getSourceData(id: Long): SourceData? { + return handler.awaitOneOrNull { sourcesQueries.findOne(id, sourceDataMapper) } + } + + override suspend fun upsertSourceData(id: Long, lang: String, name: String) { + handler.await { sourcesQueries.upsert(id, lang, name) } + } +} diff --git a/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt index 8c4881b25..87db87325 100644 --- a/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt @@ -2,7 +2,6 @@ package eu.kanade.data.source import eu.kanade.data.DatabaseHandler import eu.kanade.domain.source.model.Source -import eu.kanade.domain.source.model.SourceData import eu.kanade.domain.source.repository.SourceRepository import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.SourceManager @@ -50,12 +49,4 @@ class SourceRepositoryImpl( } } } - - override suspend fun getSourceData(id: Long): SourceData? { - return handler.awaitOneOrNull { sourcesQueries.getSourceData(id, sourceDataMapper) } - } - - override suspend fun upsertSourceData(id: Long, lang: String, name: String) { - handler.await { sourcesQueries.upsert(id, lang, name) } - } } diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 21c583423..4775b8be3 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -4,6 +4,7 @@ import eu.kanade.data.category.CategoryRepositoryImpl import eu.kanade.data.chapter.ChapterRepositoryImpl import eu.kanade.data.history.HistoryRepositoryImpl import eu.kanade.data.manga.MangaRepositoryImpl +import eu.kanade.data.source.SourceDataRepositoryImpl import eu.kanade.data.source.SourceRepositoryImpl import eu.kanade.data.track.TrackRepositoryImpl import eu.kanade.domain.category.interactor.CreateCategoryWithName @@ -47,14 +48,13 @@ import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.repository.MangaRepository import eu.kanade.domain.source.interactor.GetEnabledSources import eu.kanade.domain.source.interactor.GetLanguagesWithSources -import eu.kanade.domain.source.interactor.GetSourceData import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.interactor.ToggleLanguage import eu.kanade.domain.source.interactor.ToggleSource import eu.kanade.domain.source.interactor.ToggleSourcePin -import eu.kanade.domain.source.interactor.UpsertSourceData +import eu.kanade.domain.source.repository.SourceDataRepository import eu.kanade.domain.source.repository.SourceRepository import eu.kanade.domain.track.interactor.DeleteTrack import eu.kanade.domain.track.interactor.GetTracks @@ -120,15 +120,14 @@ class DomainModule : InjektModule { addFactory { GetExtensionLanguages(get(), get()) } addSingletonFactory { SourceRepositoryImpl(get(), get()) } + addSingletonFactory { SourceDataRepositoryImpl(get()) } addFactory { GetEnabledSources(get(), get()) } addFactory { GetLanguagesWithSources(get(), get()) } - addFactory { GetSourceData(get()) } addFactory { GetSourcesWithFavoriteCount(get(), get()) } addFactory { GetSourcesWithNonLibraryManga(get()) } addFactory { SetMigrateSorting(get()) } addFactory { ToggleLanguage(get()) } addFactory { ToggleSource(get()) } addFactory { ToggleSourcePin(get()) } - addFactory { UpsertSourceData(get()) } } } diff --git a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt index 7bda92177..a0d61da56 100644 --- a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt +++ b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionLanguages.kt @@ -14,7 +14,7 @@ class GetExtensionLanguages( fun subscribe(): Flow> { return combine( preferences.enabledLanguages().asFlow(), - extensionManager.getAvailableExtensionsObservable().asFlow(), + extensionManager.getAvailableExtensionsFlow(), ) { enabledLanguage, availableExtensions -> availableExtensions .map { it.lang } diff --git a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionUpdates.kt b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionUpdates.kt index 96373f9b4..63a27d89e 100644 --- a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionUpdates.kt +++ b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensionUpdates.kt @@ -1,6 +1,5 @@ package eu.kanade.domain.extension.interactor -import eu.kanade.core.util.asFlow import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.model.Extension @@ -15,7 +14,7 @@ class GetExtensionUpdates( fun subscribe(): Flow> { val showNsfwSources = preferences.showNsfwSource().get() - return extensionManager.getInstalledExtensionsObservable().asFlow() + return extensionManager.getInstalledExtensionsFlow() .map { installed -> installed .filter { it.hasUpdate && (showNsfwSources || it.isNsfw.not()) } diff --git a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensions.kt b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensions.kt index cdd3ba1ab..8187eb93f 100644 --- a/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensions.kt +++ b/app/src/main/java/eu/kanade/domain/extension/interactor/GetExtensions.kt @@ -19,9 +19,9 @@ class GetExtensions( return combine( preferences.enabledLanguages().asFlow(), - extensionManager.getInstalledExtensionsObservable().asFlow(), - extensionManager.getUntrustedExtensionsObservable().asFlow(), - extensionManager.getAvailableExtensionsObservable().asFlow(), + extensionManager.getInstalledExtensionsFlow(), + extensionManager.getUntrustedExtensionsFlow(), + extensionManager.getAvailableExtensionsFlow(), ) { _activeLanguages, _installed, _untrusted, _available -> val installed = _installed diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/GetSourceData.kt b/app/src/main/java/eu/kanade/domain/source/interactor/GetSourceData.kt deleted file mode 100644 index 0cbe17608..000000000 --- a/app/src/main/java/eu/kanade/domain/source/interactor/GetSourceData.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.kanade.domain.source.interactor - -import eu.kanade.domain.source.model.SourceData -import eu.kanade.domain.source.repository.SourceRepository -import eu.kanade.tachiyomi.util.system.logcat -import logcat.LogPriority - -class GetSourceData( - private val repository: SourceRepository, -) { - - suspend fun await(id: Long): SourceData? { - return try { - repository.getSourceData(id) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - null - } - } -} diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/UpsertSourceData.kt b/app/src/main/java/eu/kanade/domain/source/interactor/UpsertSourceData.kt deleted file mode 100644 index a0f74ffda..000000000 --- a/app/src/main/java/eu/kanade/domain/source/interactor/UpsertSourceData.kt +++ /dev/null @@ -1,19 +0,0 @@ -package eu.kanade.domain.source.interactor - -import eu.kanade.domain.source.model.SourceData -import eu.kanade.domain.source.repository.SourceRepository -import eu.kanade.tachiyomi.util.system.logcat -import logcat.LogPriority - -class UpsertSourceData( - private val repository: SourceRepository, -) { - - suspend fun await(sourceData: SourceData) { - try { - repository.upsertSourceData(sourceData.id, sourceData.lang, sourceData.name) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - } - } -} diff --git a/app/src/main/java/eu/kanade/domain/source/repository/SourceDataRepository.kt b/app/src/main/java/eu/kanade/domain/source/repository/SourceDataRepository.kt new file mode 100644 index 000000000..45fe944f4 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/source/repository/SourceDataRepository.kt @@ -0,0 +1,12 @@ +package eu.kanade.domain.source.repository + +import eu.kanade.domain.source.model.SourceData +import kotlinx.coroutines.flow.Flow + +interface SourceDataRepository { + fun subscribeAll(): Flow> + + suspend fun getSourceData(id: Long): SourceData? + + suspend fun upsertSourceData(id: Long, lang: String, name: String) +} diff --git a/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt b/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt index 153ba5984..00590ca9b 100644 --- a/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt +++ b/app/src/main/java/eu/kanade/domain/source/repository/SourceRepository.kt @@ -1,7 +1,6 @@ package eu.kanade.domain.source.repository import eu.kanade.domain.source.model.Source -import eu.kanade.domain.source.model.SourceData import kotlinx.coroutines.flow.Flow import eu.kanade.tachiyomi.source.Source as LoadedSource @@ -14,8 +13,4 @@ interface SourceRepository { fun getSourcesWithFavoriteCount(): Flow>> fun getSourcesWithNonLibraryManga(): Flow>> - - suspend fun getSourceData(id: Long): SourceData? - - suspend fun upsertSourceData(id: Long, lang: String, name: String) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index 26032ce6e..3052bd6ef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt @@ -87,10 +87,10 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { NetworkHelper(app) } - addSingletonFactory { SourceManager(app).also { get().init(it) } } - addSingletonFactory { ExtensionManager(app) } + addSingletonFactory { SourceManager(app, get(), get()) } + addSingletonFactory { DownloadManager(app) } addSingletonFactory { TrackManager(app) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 9148c839c..b872987cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -14,7 +14,6 @@ import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.source.Source -import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.preference.plusAssign import eu.kanade.tachiyomi.util.system.logcat @@ -88,22 +87,23 @@ class ExtensionManager( return null } - /** - * Relay used to notify the available extensions. - */ - private val availableExtensionsRelay = BehaviorRelay.create>() - /** * List of the currently available extensions. */ var availableExtensions = emptyList() private set(value) { field = value - availableExtensionsRelay.call(value) + availableExtensionsFlow.value = field updatedInstalledExtensionsStatuses(value) setupAvailableExtensionsSourcesDataMap(value) } + private val availableExtensionsFlow = MutableStateFlow(availableExtensions) + + fun getAvailableExtensionsFlow(): StateFlow> { + return availableExtensionsFlow.asStateFlow() + } + private var availableExtensionsSourcesData: Map = mapOf() private fun setupAvailableExtensionsSourcesDataMap(extensions: List) { @@ -115,30 +115,22 @@ class ExtensionManager( fun getSourceData(id: Long) = availableExtensionsSourcesData[id] - /** - * Relay used to notify the untrusted extensions. - */ - private val untrustedExtensionsRelay = BehaviorRelay.create>() - /** * List of the currently untrusted extensions. */ var untrustedExtensions = emptyList() private set(value) { field = value - untrustedExtensionsRelay.call(value) + untrustedExtensionsFlow.value = field } - /** - * The source manager where the sources of the extensions are added. - */ - private lateinit var sourceManager: SourceManager + private val untrustedExtensionsFlow = MutableStateFlow(untrustedExtensions) - /** - * Initializes this manager with the given source manager. - */ - fun init(sourceManager: SourceManager) { - this.sourceManager = sourceManager + fun getUntrustedExtensionsFlow(): StateFlow> { + return untrustedExtensionsFlow.asStateFlow() + } + + init { initExtensions() ExtensionInstallReceiver(InstallationListener()).register(context) } @@ -152,36 +144,12 @@ class ExtensionManager( installedExtensions = extensions .filterIsInstance() .map { it.extension } - installedExtensions - .flatMap { it.sources } - .forEach { sourceManager.registerSource(it) } untrustedExtensions = extensions .filterIsInstance() .map { it.extension } } - /** - * Returns the relay of the installed extensions as an observable. - */ - fun getInstalledExtensionsObservable(): Observable> { - return installedExtensionsRelay.asObservable() - } - - /** - * Returns the relay of the available extensions as an observable. - */ - fun getAvailableExtensionsObservable(): Observable> { - return availableExtensionsRelay.asObservable() - } - - /** - * Returns the relay of the untrusted extensions as an observable. - */ - fun getUntrustedExtensionsObservable(): Observable> { - return untrustedExtensionsRelay.asObservable() - } - /** * Finds the available extensions in the [api] and updates [availableExtensions]. */ @@ -324,7 +292,6 @@ class ExtensionManager( */ private fun registerNewExtension(extension: Extension.Installed) { installedExtensions += extension - extension.sources.forEach { sourceManager.registerSource(it) } } /** @@ -338,11 +305,9 @@ class ExtensionManager( val oldExtension = mutInstalledExtensions.find { it.pkgName == extension.pkgName } if (oldExtension != null) { mutInstalledExtensions -= oldExtension - extension.sources.forEach { sourceManager.unregisterSource(it) } } mutInstalledExtensions += extension installedExtensions = mutInstalledExtensions - extension.sources.forEach { sourceManager.registerSource(it) } } /** @@ -355,7 +320,6 @@ class ExtensionManager( val installedExtension = installedExtensions.find { it.pkgName == pkgName } if (installedExtension != null) { installedExtensions -= installedExtension - installedExtension.sources.forEach { sourceManager.unregisterSource(it) } } val untrustedExtension = untrustedExtensions.find { it.pkgName == pkgName } if (untrustedExtension != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt index 18e7fbeb1..c528ae1c4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt @@ -1,42 +1,72 @@ package eu.kanade.tachiyomi.source import android.content.Context -import eu.kanade.domain.source.interactor.GetSourceData -import eu.kanade.domain.source.interactor.UpsertSourceData import eu.kanade.domain.source.model.SourceData +import eu.kanade.domain.source.repository.SourceDataRepository import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.ExtensionManager 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.util.lang.launchIO +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.collectLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import rx.Observable import tachiyomi.source.model.ChapterInfo import tachiyomi.source.model.MangaInfo -import uy.kohesive.injekt.injectLazy -class SourceManager(private val context: Context) { +class SourceManager( + private val context: Context, + private val extensionManager: ExtensionManager, + private val sourceRepository: SourceDataRepository, +) { - private val extensionManager: ExtensionManager by injectLazy() - private val getSourceData: GetSourceData by injectLazy() - private val upsertSourceData: UpsertSourceData by injectLazy() + private val scope = CoroutineScope(Job() + Dispatchers.IO) + + private var sourcesMap = emptyMap() + set(value) { + field = value + sourcesMapFlow.value = field + } + + private val sourcesMapFlow = MutableStateFlow(sourcesMap) - 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() } + val catalogueSources: Flow> = sourcesMapFlow.map { it.values.filterIsInstance() } + val onlineSources: Flow> = catalogueSources.map { sources -> sources.filterIsInstance() } init { - createInternalSources().forEach { registerSource(it) } + scope.launch { + extensionManager.getInstalledExtensionsFlow() + .collectLatest { extensions -> + val mutableMap = mutableMapOf(LocalSource.ID to LocalSource(context)) + extensions.forEach { extension -> + extension.sources.forEach { + mutableMap[it.id] = it + registerStubSource(it.toSourceData()) + } + } + sourcesMap = mutableMap + } + } + + scope.launch { + sourceRepository.subscribeAll() + .collectLatest { sources -> + val mutableMap = stubSourcesMap.toMutableMap() + sources.forEach { + mutableMap[it.id] = StubSource(it) + } + } + } } fun get(sourceKey: Long): Source? { @@ -58,44 +88,15 @@ class SourceManager(private val context: Context) { return stubSourcesMap.values.filterNot { it.id in onlineSourceIds } } - internal fun registerSource(source: Source) { - if (!sourcesMap.containsKey(source.id)) { - sourcesMap[source.id] = source - } - registerStubSource(source.toSourceData()) - triggerCatalogueSources() - } - private fun registerStubSource(sourceData: SourceData) { - launchIO { - val dbSourceData = getSourceData.await(sourceData.id) - - if (dbSourceData != sourceData) { - upsertSourceData.await(sourceData) - } - if (stubSourcesMap[sourceData.id]?.toSourceData() != sourceData) { - stubSourcesMap[sourceData.id] = StubSource(sourceData) - } + scope.launch { + val (id, lang, name) = sourceData + sourceRepository.upsertSourceData(id, lang, name) } } - internal fun unregisterSource(source: Source) { - sourcesMap.remove(source.id) - triggerCatalogueSources() - } - - private fun triggerCatalogueSources() { - _catalogueSources.update { - sourcesMap.values.filterIsInstance() - } - } - - private fun createInternalSources(): List = listOf( - LocalSource(context), - ) - private suspend fun createStubSource(id: Long): StubSource { - getSourceData.await(id)?.let { + sourceRepository.getSourceData(id)?.let { return StubSource(it) } extensionManager.getSourceData(id)?.let { diff --git a/app/src/main/sqldelight/data/sources.sq b/app/src/main/sqldelight/data/sources.sq index cbbb54c5f..aac60bb78 100644 --- a/app/src/main/sqldelight/data/sources.sq +++ b/app/src/main/sqldelight/data/sources.sq @@ -4,7 +4,11 @@ CREATE TABLE sources( name TEXT NOT NULL ); -getSourceData: +findAll: +SELECT * +FROM sources; + +findOne: SELECT * FROM sources WHERE _id = :id;