From d5b4bb49b168adc5b7c3934e530571497c85a916 Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 26 Oct 2022 23:01:21 -0400 Subject: [PATCH] Refactor network to local manga logic Maybe fixes #8289 --- .../kanade/data/manga/MangaRepositoryImpl.kt | 2 +- .../java/eu/kanade/domain/DomainModule.kt | 4 +- .../domain/manga/interactor/GetManga.kt | 4 -- .../domain/manga/interactor/InsertManga.kt | 13 ------- .../manga/interactor/NetworkToLocalManga.kt | 35 +++++++++++++++++ .../eu/kanade/domain/manga/model/Manga.kt | 15 +++++++ .../migration/search/SearchPresenter.kt | 5 +-- .../source/browse/BrowseSourcePresenter.kt | 36 +++-------------- .../globalsearch/GlobalSearchPresenter.kt | 39 ++++++------------- 9 files changed, 72 insertions(+), 81 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/domain/manga/interactor/InsertManga.kt create mode 100644 app/src/main/java/eu/kanade/domain/manga/interactor/NetworkToLocalManga.kt diff --git a/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt index 9fcb8f79d..43d0be0e9 100644 --- a/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt @@ -25,7 +25,7 @@ class MangaRepositoryImpl( } override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? { - return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) } + return handler.awaitOneOrNull(inTransaction = true) { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) } } override fun getMangaByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow { diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index b4047afef..751e6f5ea 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -44,7 +44,7 @@ import eu.kanade.domain.manga.interactor.GetFavorites import eu.kanade.domain.manga.interactor.GetLibraryManga import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.GetMangaWithChapters -import eu.kanade.domain.manga.interactor.InsertManga +import eu.kanade.domain.manga.interactor.NetworkToLocalManga import eu.kanade.domain.manga.interactor.ResetViewerFlags import eu.kanade.domain.manga.interactor.SetMangaChapterFlags import eu.kanade.domain.manga.interactor.SetMangaViewerFlags @@ -98,7 +98,7 @@ class DomainModule : InjektModule { addFactory { SetMangaChapterFlags(get()) } addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) } addFactory { SetMangaViewerFlags(get()) } - addFactory { InsertManga(get()) } + addFactory { NetworkToLocalManga(get()) } addFactory { UpdateManga(get()) } addFactory { SetMangaCategories(get()) } diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/GetManga.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/GetManga.kt index e2c54b993..22518acc4 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/GetManga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/GetManga.kt @@ -23,10 +23,6 @@ class GetManga( return mangaRepository.getMangaByIdAsFlow(id) } - suspend fun await(url: String, sourceId: Long): Manga? { - return mangaRepository.getMangaByUrlAndSourceId(url, sourceId) - } - fun subscribe(url: String, sourceId: Long): Flow { return mangaRepository.getMangaByUrlAndSourceIdAsFlow(url, sourceId) } diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/InsertManga.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/InsertManga.kt deleted file mode 100644 index 2477d91f2..000000000 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/InsertManga.kt +++ /dev/null @@ -1,13 +0,0 @@ -package eu.kanade.domain.manga.interactor - -import eu.kanade.domain.manga.model.Manga -import eu.kanade.domain.manga.repository.MangaRepository - -class InsertManga( - private val mangaRepository: MangaRepository, -) { - - suspend fun await(manga: Manga): Long? { - return mangaRepository.insert(manga) - } -} diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/NetworkToLocalManga.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/NetworkToLocalManga.kt new file mode 100644 index 000000000..b8793d635 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/NetworkToLocalManga.kt @@ -0,0 +1,35 @@ +package eu.kanade.domain.manga.interactor + +import eu.kanade.domain.manga.model.Manga +import eu.kanade.domain.manga.repository.MangaRepository + +class NetworkToLocalManga( + private val mangaRepository: MangaRepository, +) { + + suspend fun await(manga: Manga, sourceId: Long): Manga { + val localManga = getManga(manga.url, sourceId) + return when { + localManga == null -> { + val id = insertManga(manga) + manga.copy(id = id!!) + } + !localManga.favorite -> { + // if the manga isn't a favorite, set its display title from source + // if it later becomes a favorite, updated title will go to db + localManga.copy(title = manga.title) + } + else -> { + localManga + } + } + } + + private suspend fun getManga(url: String, sourceId: Long): Manga? { + return mangaRepository.getMangaByUrlAndSourceId(url, sourceId) + } + + private suspend fun insertManga(manga: Manga): Long? { + return mangaRepository.insert(manga) + } +} diff --git a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt index ba577518c..57b38f4a7 100644 --- a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt @@ -232,6 +232,21 @@ fun Manga.toMangaUpdate(): MangaUpdate { ) } +fun SManga.toDomainManga(): Manga { + return Manga.create().copy( + url = url, + title = title, + artist = artist, + author = author, + description = description, + genre = getGenres(), + status = status.toLong(), + thumbnailUrl = thumbnail_url, + updateStrategy = update_strategy, + initialized = initialized, + ) +} + fun Manga.isLocal(): Boolean = source == LocalSource.ID fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt index 97ae7bc24..936814bbe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt @@ -79,11 +79,10 @@ class SearchPresenter( return GlobalSearchItem(source, results, source.id == manga.source) } - override fun networkToLocalManga(sManga: SManga, sourceId: Long): eu.kanade.tachiyomi.data.database.models.Manga { + override suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { val localManga = super.networkToLocalManga(sManga, sourceId) // For migration, displayed title should always match source rather than local DB - localManga.title = sManga.title - return localManga + return localManga.copy(title = sManga.title) } fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt index 51c9a6880..12db91691 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt @@ -28,9 +28,10 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga import eu.kanade.domain.manga.interactor.GetManga -import eu.kanade.domain.manga.interactor.InsertManga +import eu.kanade.domain.manga.interactor.NetworkToLocalManga import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.toDbManga +import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.manga.model.toMangaUpdate import eu.kanade.domain.source.interactor.GetRemoteManga import eu.kanade.domain.source.service.SourcePreferences @@ -39,8 +40,6 @@ import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.presentation.browse.BrowseSourceState import eu.kanade.presentation.browse.BrowseSourceStateImpl import eu.kanade.tachiyomi.data.cache.CoverCache -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService @@ -49,7 +48,6 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxItem import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxSectionItem @@ -97,7 +95,7 @@ open class BrowseSourcePresenter( private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(), private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(), - private val insertManga: InsertManga = Injekt.get(), + private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(), private val insertTrack: InsertTrack = Injekt.get(), private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(), @@ -131,9 +129,9 @@ open class BrowseSourcePresenter( getRemoteManga.subscribe(sourceId, currentFilter.query, currentFilter.filters) }.flow .map { - it.map { + it.map { sManga -> withIOContext { - networkToLocalManga(it, sourceId).toDomainManga()!! + networkToLocalManga.await(sManga.toDomainManga(), sourceId) } } } @@ -183,30 +181,6 @@ open class BrowseSourcePresenter( state.filters = source!!.getFilterList() } - /** - * Returns a manga from the database for the given manga from network. It creates a new entry - * if the manga is not yet in the database. - * - * @param sManga the manga from the source. - * @return a manga from the database. - */ - private suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { - var localManga = getManga.await(sManga.url, sourceId) - if (localManga == null) { - val newManga = Manga.create(sManga.url, sManga.title, sourceId) - newManga.copyFrom(sManga) - newManga.id = -1 - val id = insertManga.await(newManga.toDomainManga()!!) - val result = getManga.await(id!!) - localManga = result - } else if (!localManga.favorite) { - // if the manga isn't a favorite, set its display title from source - // if it later becomes a favorite, updated title will go to db - localManga = localManga.copy(title = sManga.title) - } - return localManga?.toDbManga()!! - } - /** * Initialize a manga. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt index 483752ca6..3aee01d46 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt @@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch import android.os.Bundle import eu.kanade.domain.base.BasePreferences -import eu.kanade.domain.manga.interactor.GetManga -import eu.kanade.domain.manga.interactor.InsertManga +import eu.kanade.domain.manga.interactor.NetworkToLocalManga import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.toDbManga +import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.manga.model.toMangaUpdate import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.data.database.models.Manga @@ -30,6 +30,7 @@ import rx.subjects.PublishSubject import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import eu.kanade.domain.manga.model.Manga as DomainManga open class GlobalSearchPresenter( private val initialQuery: String? = "", @@ -37,8 +38,7 @@ open class GlobalSearchPresenter( val sourceManager: SourceManager = Injekt.get(), val preferences: BasePreferences = Injekt.get(), val sourcePreferences: SourcePreferences = Injekt.get(), - private val getManga: GetManga = Injekt.get(), - private val insertManga: InsertManga = Injekt.get(), + private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(), ) : BasePresenter() { @@ -55,7 +55,7 @@ open class GlobalSearchPresenter( /** * Subject which fetches image of given manga. */ - private val fetchImageSubject = PublishSubject.create, Source>>() + private val fetchImageSubject = PublishSubject.create, Source>>() /** * Subscription for fetching images of manga. @@ -168,9 +168,9 @@ open class GlobalSearchPresenter( .subscribeOn(Schedulers.io()) .onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions .map { it.mangas } - .map { list -> list.map { networkToLocalManga(it, source.id) } } // Convert to local manga + .map { list -> list.map { runBlocking { networkToLocalManga(it, source.id) } } } // Convert to local manga .doOnNext { fetchImage(it, source) } // Load manga covers - .map { list -> createCatalogueSearchItem(source, list.map { GlobalSearchCardItem(it.toDomainManga()!!) }) } + .map { list -> createCatalogueSearchItem(source, list.map { GlobalSearchCardItem(it) }) } }, 5, ) @@ -208,7 +208,7 @@ open class GlobalSearchPresenter( * * @param manga the list of manga to initialize. */ - private fun fetchImage(manga: List, source: Source) { + private fun fetchImage(manga: List, source: Source) { fetchImageSubject.onNext(Pair(manga, source)) } @@ -220,9 +220,9 @@ open class GlobalSearchPresenter( fetchImageSubscription = fetchImageSubject.observeOn(Schedulers.io()) .flatMap { (first, source) -> Observable.from(first) - .filter { it.thumbnail_url == null && !it.initialized } + .filter { it.thumbnailUrl == null && !it.initialized } .map { Pair(it, source) } - .concatMap { runAsObservable { getMangaDetails(it.first, it.second) } } + .concatMap { runAsObservable { getMangaDetails(it.first.toDbManga(), it.second) } } .map { Pair(source as CatalogueSource, it) } } .onBackpressureBuffer() @@ -259,22 +259,7 @@ open class GlobalSearchPresenter( * @param sManga the manga from the source. * @return a manga from the database. */ - protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { - var localManga = runBlocking { getManga.await(sManga.url, sourceId) } - if (localManga == null) { - val newManga = Manga.create(sManga.url, sManga.title, sourceId) - newManga.copyFrom(sManga) - newManga.id = -1 - val result = runBlocking { - val id = insertManga.await(newManga.toDomainManga()!!) - getManga.await(id!!) - } - localManga = result - } else if (!localManga.favorite) { - // if the manga isn't a favorite, set its display title from source - // if it later becomes a favorite, updated title will go to db - localManga = localManga.copy(title = sManga.title) - } - return localManga!!.toDbManga() + protected open suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): DomainManga { + return networkToLocalManga.await(sManga.toDomainManga(), sourceId) } }