From 04f0ca78469573707e14742226fd8f14445853d4 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Mon, 27 Jun 2022 01:54:34 +0600 Subject: [PATCH] Use sqldelight for direct db calls in `MangaPresenter` (#7366) --- .../kanade/data/track/TrackRepositoryImpl.kt | 37 ++++- .../java/eu/kanade/domain/DomainModule.kt | 4 + .../SyncChaptersWithTrackServiceTwoWay.kt | 41 +++++ .../manga/interactor/SetMangaChapterFlags.kt | 4 +- .../eu/kanade/domain/manga/model/Manga.kt | 11 +- .../domain/track/interactor/DeleteTrack.kt | 18 ++ .../domain/track/interactor/GetTracks.kt | 5 + .../domain/track/interactor/InsertTrack.kt | 8 + .../eu/kanade/domain/track/model/Track.kt | 36 ++++ .../track/repository/TrackRepository.kt | 7 + .../tachiyomi/ui/manga/MangaPresenter.kt | 157 ++++++++++++------ app/src/main/sqldelight/data/manga_sync.sq | 4 + 12 files changed, 274 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithTrackServiceTwoWay.kt create mode 100644 app/src/main/java/eu/kanade/domain/track/interactor/DeleteTrack.kt diff --git a/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt index eed6f5f77d..ac8942eb79 100644 --- a/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt @@ -3,6 +3,7 @@ package eu.kanade.data.track import eu.kanade.data.DatabaseHandler import eu.kanade.domain.track.model.Track import eu.kanade.domain.track.repository.TrackRepository +import kotlinx.coroutines.flow.Flow class TrackRepositoryImpl( private val handler: DatabaseHandler, @@ -14,11 +15,45 @@ class TrackRepositoryImpl( } } + override suspend fun subscribeTracksByMangaId(mangaId: Long): Flow> { + return handler.subscribeToList { + manga_syncQueries.getTracksByMangaId(mangaId, trackMapper) + } + } + + override suspend fun delete(mangaId: Long, syncId: Long) { + handler.await { + manga_syncQueries.delete( + mangaId = mangaId, + syncId = syncId, + ) + } + } + + override suspend fun insert(track: Track) { + handler.await { + manga_syncQueries.insert( + mangaId = track.mangaId, + syncId = track.syncId, + remoteId = track.remoteId, + libraryId = track.libraryId, + title = track.title, + lastChapterRead = track.lastChapterRead, + totalChapters = track.totalChapters, + status = track.status, + score = track.score, + remoteUrl = track.remoteUrl, + startDate = track.startDate, + finishDate = track.finishDate, + ) + } + } + override suspend fun insertAll(tracks: List) { handler.await(inTransaction = true) { tracks.forEach { mangaTrack -> manga_syncQueries.insert( - mangaId = mangaTrack.id, + mangaId = mangaTrack.mangaId, syncId = mangaTrack.syncId, remoteId = mangaTrack.remoteId, libraryId = mangaTrack.libraryId, diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index f16925ce98..3cb07b9fc1 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -15,6 +15,7 @@ import eu.kanade.domain.category.repository.CategoryRepository import eu.kanade.domain.chapter.interactor.GetChapterByMangaId import eu.kanade.domain.chapter.interactor.ShouldUpdateDbChapter import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource +import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.UpdateChapter import eu.kanade.domain.chapter.repository.ChapterRepository import eu.kanade.domain.extension.interactor.GetExtensionLanguages @@ -47,6 +48,7 @@ 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.SourceRepository +import eu.kanade.domain.track.interactor.DeleteTrack import eu.kanade.domain.track.interactor.GetTracks import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.repository.TrackRepository @@ -77,6 +79,7 @@ class DomainModule : InjektModule { addFactory { MoveMangaToCategories(get()) } addSingletonFactory { TrackRepositoryImpl(get()) } + addFactory { DeleteTrack(get()) } addFactory { GetTracks(get()) } addFactory { InsertTrack(get()) } @@ -85,6 +88,7 @@ class DomainModule : InjektModule { addFactory { UpdateChapter(get()) } addFactory { ShouldUpdateDbChapter() } addFactory { SyncChaptersWithSource(get(), get(), get(), get()) } + addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) } addSingletonFactory { HistoryRepositoryImpl(get()) } addFactory { DeleteHistoryTable(get()) } diff --git a/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithTrackServiceTwoWay.kt b/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithTrackServiceTwoWay.kt new file mode 100644 index 0000000000..bf4de35752 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithTrackServiceTwoWay.kt @@ -0,0 +1,41 @@ +package eu.kanade.domain.chapter.interactor + +import eu.kanade.domain.chapter.model.Chapter +import eu.kanade.domain.chapter.model.toChapterUpdate +import eu.kanade.domain.track.interactor.InsertTrack +import eu.kanade.domain.track.model.Track +import eu.kanade.domain.track.model.toDbTrack +import eu.kanade.tachiyomi.data.track.TrackService +import eu.kanade.tachiyomi.util.system.logcat +import logcat.LogPriority +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class SyncChaptersWithTrackServiceTwoWay( + private val updateChapter: UpdateChapter = Injekt.get(), + private val insertTrack: InsertTrack = Injekt.get(), +) { + + suspend fun await( + chapters: List, + remoteTrack: Track, + service: TrackService, + ) { + val sortedChapters = chapters.sortedBy { it.chapterNumber } + val chapterUpdates = sortedChapters + .filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read } + .map { it.copy(read = true).toChapterUpdate() } + + // only take into account continuous reading + val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F + val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble()) + + try { + service.update(updatedTrack.toDbTrack()) + updateChapter.awaitAll(chapterUpdates) + insertTrack.await(updatedTrack) + } catch (e: Throwable) { + logcat(LogPriority.WARN, e) + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/SetMangaChapterFlags.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/SetMangaChapterFlags.kt index 3cb34503f2..6362170c19 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/SetMangaChapterFlags.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/SetMangaChapterFlags.kt @@ -4,7 +4,9 @@ import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.MangaUpdate import eu.kanade.domain.manga.repository.MangaRepository -class SetMangaChapterFlags(private val mangaRepository: MangaRepository) { +class SetMangaChapterFlags( + private val mangaRepository: MangaRepository, +) { suspend fun awaitSetDownloadedFilter(manga: Manga, flag: Long): Boolean { return mangaRepository.update( 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 0873301bed..577868b907 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 @@ -1,5 +1,6 @@ package eu.kanade.domain.manga.model +import eu.kanade.data.listOfStringsAdapter import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.LocalSource @@ -143,7 +144,7 @@ fun TriStateFilter.toTriStateGroupState(): ExtendedNavigationView.Item.TriStateG } // TODO: Remove when all deps are migrated -fun Manga.toDbManga(): DbManga = DbManga.create(url, title, source).also { +fun Manga.toDbManga(): DbManga = DbManga.create(source).also { it.id = id it.favorite = favorite it.last_update = lastUpdate @@ -151,7 +152,15 @@ fun Manga.toDbManga(): DbManga = DbManga.create(url, title, source).also { it.viewer_flags = viewerFlags.toInt() it.chapter_flags = chapterFlags.toInt() it.cover_last_modified = coverLastModified + it.url = url + it.title = title + it.artist = artist + it.author = author + it.description = description + it.genre = genre?.let(listOfStringsAdapter::encode) + it.status = status.toInt() it.thumbnail_url = thumbnailUrl + it.initialized = initialized } fun Manga.toMangaInfo(): MangaInfo = MangaInfo( diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/DeleteTrack.kt b/app/src/main/java/eu/kanade/domain/track/interactor/DeleteTrack.kt new file mode 100644 index 0000000000..f7e6080899 --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/track/interactor/DeleteTrack.kt @@ -0,0 +1,18 @@ +package eu.kanade.domain.track.interactor + +import eu.kanade.domain.track.repository.TrackRepository +import eu.kanade.tachiyomi.util.system.logcat +import logcat.LogPriority + +class DeleteTrack( + private val trackRepository: TrackRepository, +) { + + suspend fun await(mangaId: Long, syncId: Long) { + try { + trackRepository.delete(mangaId, syncId) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt b/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt index ad004631d9..a48a40421b 100644 --- a/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt +++ b/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt @@ -3,6 +3,7 @@ package eu.kanade.domain.track.interactor import eu.kanade.domain.track.model.Track import eu.kanade.domain.track.repository.TrackRepository import eu.kanade.tachiyomi.util.system.logcat +import kotlinx.coroutines.flow.Flow import logcat.LogPriority class GetTracks( @@ -17,4 +18,8 @@ class GetTracks( emptyList() } } + + suspend fun subscribe(mangaId: Long): Flow> { + return trackRepository.subscribeTracksByMangaId(mangaId) + } } diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/InsertTrack.kt b/app/src/main/java/eu/kanade/domain/track/interactor/InsertTrack.kt index 27085221a3..b1add2fda5 100644 --- a/app/src/main/java/eu/kanade/domain/track/interactor/InsertTrack.kt +++ b/app/src/main/java/eu/kanade/domain/track/interactor/InsertTrack.kt @@ -9,6 +9,14 @@ class InsertTrack( private val trackRepository: TrackRepository, ) { + suspend fun await(track: Track) { + try { + trackRepository.insert(track) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + } + } + suspend fun awaitAll(tracks: List) { try { trackRepository.insertAll(tracks) diff --git a/app/src/main/java/eu/kanade/domain/track/model/Track.kt b/app/src/main/java/eu/kanade/domain/track/model/Track.kt index cc92fb4ee6..81f629024d 100644 --- a/app/src/main/java/eu/kanade/domain/track/model/Track.kt +++ b/app/src/main/java/eu/kanade/domain/track/model/Track.kt @@ -1,5 +1,7 @@ package eu.kanade.domain.track.model +import eu.kanade.tachiyomi.data.database.models.Track as DbTrack + data class Track( val id: Long, val mangaId: Long, @@ -25,3 +27,37 @@ data class Track( ) } } + +fun Track.toDbTrack(): DbTrack = DbTrack.create(syncId.toInt()).also { + it.id = id + it.manga_id = mangaId + it.media_id = remoteId + it.library_id = libraryId + it.title = title + it.last_chapter_read = lastChapterRead.toFloat() + it.total_chapters = totalChapters.toInt() + it.status = status.toInt() + it.score = score + it.tracking_url = remoteUrl + it.started_reading_date = startDate + it.finished_reading_date = finishDate +} + +fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? { + val trackId = id ?: if (idRequired.not()) -1 else return null + return Track( + id = trackId, + mangaId = manga_id, + syncId = sync_id.toLong(), + remoteId = media_id, + libraryId = library_id, + title = title, + lastChapterRead = last_chapter_read.toDouble(), + totalChapters = total_chapters.toLong(), + status = status.toLong(), + score = score, + remoteUrl = tracking_url, + startDate = started_reading_date, + finishDate = finished_reading_date, + ) +} diff --git a/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt b/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt index 9a3b0ccbf0..38207a2f2f 100644 --- a/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt +++ b/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt @@ -1,10 +1,17 @@ package eu.kanade.domain.track.repository import eu.kanade.domain.track.model.Track +import kotlinx.coroutines.flow.Flow interface TrackRepository { suspend fun getTracksByMangaId(mangaId: Long): List + suspend fun subscribeTracksByMangaId(mangaId: Long): Flow> + + suspend fun delete(mangaId: Long, syncId: Long) + + suspend fun insert(track: Track) + suspend fun insertAll(tracks: List) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index f3f463156f..f3f26901f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -2,7 +2,10 @@ package eu.kanade.tachiyomi.ui.manga import android.os.Bundle import androidx.compose.runtime.Immutable +import eu.kanade.domain.category.interactor.GetCategories +import eu.kanade.domain.category.interactor.MoveMangaToCategories import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource +import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.UpdateChapter import eu.kanade.domain.chapter.model.ChapterUpdate import eu.kanade.domain.chapter.model.toDbChapter @@ -14,11 +17,15 @@ import eu.kanade.domain.manga.model.TriStateFilter import eu.kanade.domain.manga.model.isLocal import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toMangaInfo +import eu.kanade.domain.track.interactor.DeleteTrack +import eu.kanade.domain.track.interactor.GetTracks +import eu.kanade.domain.track.interactor.InsertTrack +import eu.kanade.domain.track.model.toDbTrack +import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.toDomainChapter import eu.kanade.tachiyomi.data.download.DownloadManager @@ -34,7 +41,6 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper import eu.kanade.tachiyomi.util.chapter.getChapterSort -import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.withUIContext @@ -44,17 +50,21 @@ import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.withContext import logcat.LogPriority -import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers @@ -77,6 +87,12 @@ class MangaPresenter( private val updateChapter: UpdateChapter = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(), private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(), + private val getCategories: GetCategories = Injekt.get(), + private val deleteTrack: DeleteTrack = Injekt.get(), + private val getTracks: GetTracks = Injekt.get(), + private val moveMangaToCategories: MoveMangaToCategories = Injekt.get(), + private val insertTrack: InsertTrack = Injekt.get(), + private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(), ) : BasePresenter() { private val _state: MutableStateFlow = MutableStateFlow(MangaScreenState.Loading) @@ -107,7 +123,6 @@ class MangaPresenter( private val loggedServices by lazy { trackManager.services.filter { it.isLogged } } - private var trackSubscription: Subscription? = null private var searchTrackerJob: Job? = null private var refreshTrackersJob: Job? = null @@ -154,20 +169,15 @@ class MangaPresenter( isFromSource = isFromSource, trackingAvailable = trackManager.hasLoggedServices(), chapters = chapterItems, - ).also { - getTrackingObservable(manga) - .subscribeLatestCache( - { _, count -> updateSuccessState { it.copy(trackingCount = count) } }, - { _, error -> logcat(LogPriority.ERROR, error) }, - ) - } + ) // Update state is MangaScreenState.Success -> currentState.copy(manga = manga, chapters = chapterItems) } } - fetchTrackers() + observeTrackers() + observeTrackingCount() observeDownloads() if (!manga.initialized) { @@ -195,20 +205,6 @@ class MangaPresenter( } // Manga info - start - - private fun getTrackingObservable(manga: DomainManga): Observable { - if (!trackManager.hasLoggedServices()) { - return Observable.just(0) - } - - return db.getTracks(manga.id).asRxObservable() - .map { tracks -> - val loggedServices = trackManager.services.filter { it.isLogged }.map { it.id } - tracks.filter { it.sync_id in loggedServices } - } - .map { it.size } - } - /** * Fetch manga information from source. */ @@ -341,8 +337,8 @@ class MangaPresenter( * @return Array of category ids the manga is in, if none returns default id */ fun getMangaCategoryIds(manga: DomainManga): Array { - val categories = db.getCategoriesForManga(manga.toDbManga()).executeAsBlocking() - return categories.mapNotNull { it.id }.toTypedArray() + val categories = runBlocking { getCategories.await(manga.id) } + return categories.map { it.id.toInt() }.toTypedArray() } fun moveMangaToCategoriesAndAddToLibrary(manga: Manga, categories: List) { @@ -359,8 +355,11 @@ class MangaPresenter( * @param categories the selected categories. */ private fun moveMangaToCategories(manga: Manga, categories: List) { - val mc = categories.filter { it.id != 0 }.map { MangaCategory.create(manga, it) } - db.setMangaCategories(mc, listOf(manga)) + val mangaId = manga.id ?: return + val categoryIds = categories.mapNotNull { it.id?.toLong() } + presenterScope.launchIO { + moveMangaToCategories.await(mangaId, categoryIds) + } } /** @@ -373,6 +372,22 @@ class MangaPresenter( moveMangaToCategories(manga, listOfNotNull(category)) } + private fun observeTrackingCount() { + val manga = successState?.manga ?: return + + presenterScope.launchIO { + getTracks.subscribe(manga.id) + .catch { logcat(LogPriority.ERROR, it) } + .map { tracks -> + val loggedServicesId = loggedServices.map { it.id.toLong() } + tracks.filter { it.syncId in loggedServicesId }.size + } + .collectLatest { trackingCount -> + updateSuccessState { it.copy(trackingCount = trackingCount) } + } + } + } + // Manga info - end // Chapters list - start @@ -520,7 +535,7 @@ class MangaPresenter( val modified = chapters.filterNot { it.read == read } modified .map { ChapterUpdate(id = it.id, read = read) } - .forEach { updateChapter.await(it) } + .let { updateChapter.awaitAll(it) } if (read && preferences.removeAfterMarkedAsRead()) { deleteChapters(modified) } @@ -545,7 +560,7 @@ class MangaPresenter( chapters .filterNot { it.bookmark == bookmarked } .map { ChapterUpdate(id = it.id, bookmark = bookmarked) } - .forEach { updateChapter.await(it) } + .let { updateChapter.awaitAll(it) } } } @@ -593,6 +608,7 @@ class MangaPresenter( */ fun setUnreadFilter(state: State) { val manga = successState?.manga ?: return + val flag = when (state) { State.IGNORE -> DomainManga.SHOW_ALL State.INCLUDE -> DomainManga.CHAPTER_SHOW_UNREAD @@ -609,11 +625,13 @@ class MangaPresenter( */ fun setDownloadedFilter(state: State) { val manga = successState?.manga ?: return + val flag = when (state) { State.IGNORE -> DomainManga.SHOW_ALL State.INCLUDE -> DomainManga.CHAPTER_SHOW_DOWNLOADED State.EXCLUDE -> DomainManga.CHAPTER_SHOW_NOT_DOWNLOADED } + presenterScope.launchIO { setMangaChapterFlags.awaitSetDownloadedFilter(manga, flag) } @@ -625,11 +643,13 @@ class MangaPresenter( */ fun setBookmarkedFilter(state: State) { val manga = successState?.manga ?: return + val flag = when (state) { State.IGNORE -> DomainManga.SHOW_ALL State.INCLUDE -> DomainManga.CHAPTER_SHOW_BOOKMARKED State.EXCLUDE -> DomainManga.CHAPTER_SHOW_NOT_BOOKMARKED } + presenterScope.launchIO { setMangaChapterFlags.awaitSetBookmarkFilter(manga, flag) } @@ -641,6 +661,7 @@ class MangaPresenter( */ fun setDisplayMode(mode: Long) { val manga = successState?.manga ?: return + presenterScope.launchIO { setMangaChapterFlags.awaitSetDisplayMode(manga, mode) } @@ -652,6 +673,7 @@ class MangaPresenter( */ fun setSorting(sort: Long) { val manga = successState?.manga ?: return + presenterScope.launchIO { setMangaChapterFlags.awaitSetSortingModeOrFlipOrder(manga, sort) } @@ -661,19 +683,25 @@ class MangaPresenter( // Track sheet - start - private fun fetchTrackers() { + private fun observeTrackers() { val manga = successState?.manga ?: return - trackSubscription?.let { remove(it) } - trackSubscription = db.getTracks(manga.id) - .asRxObservable() - .map { tracks -> - loggedServices.map { service -> - TrackItem(tracks.find { it.sync_id == service.id }, service) + + presenterScope.launchIO { + getTracks.subscribe(manga.id) + .catch { logcat(LogPriority.ERROR, it) } + .map { tracks -> + val dbTracks = tracks.map { it.toDbTrack() } + loggedServices.map { service -> + TrackItem(dbTracks.find { it.sync_id == service.id }, service) + } } - } - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext { _trackList = it } - .subscribeLatestCache(MangaController::onNextTrackers) + .collectLatest { trackItems -> + _trackList = trackItems + withContext(Dispatchers.Main) { + view?.onNextTrackers(trackItems) + } + } + } } fun refreshTrackers() { @@ -682,16 +710,21 @@ class MangaPresenter( supervisorScope { try { trackList - .filter { it.track != null } .map { async { - val track = it.service.refresh(it.track!!) - db.insertTrack(track).executeAsBlocking() + val track = it.track ?: return@async null - if (it.service is EnhancedTrackService) { + val updatedTrack = it.service.refresh(track) + + val domainTrack = updatedTrack.toDomainTrack() ?: return@async null + insertTrack.await(domainTrack) + + (it.service as? EnhancedTrackService)?.let { _ -> val allChapters = successState?.chapters - ?.map { it.chapter.toDbChapter() } ?: emptyList() - syncChaptersWithTrackServiceTwoWay(db, allChapters, track, it.service) + ?.map { it.chapter } ?: emptyList() + + syncChaptersWithTrackServiceTwoWay + .await(allChapters, domainTrack, it.service) } } } @@ -727,10 +760,17 @@ class MangaPresenter( .map { it.chapter.toDbChapter() } val hasReadChapters = allChapters.any { it.read } service.bind(item, hasReadChapters) - db.insertTrack(item).executeAsBlocking() - if (service is EnhancedTrackService) { - syncChaptersWithTrackServiceTwoWay(db, allChapters, item, service) + item.toDomainTrack(idRequired = false)?.let { track -> + insertTrack.await(track) + + (service as? EnhancedTrackService)?.let { _ -> + val chapters = successState.chapters + .map { it.chapter } + + syncChaptersWithTrackServiceTwoWay + .await(chapters, track, service) + } } } catch (e: Throwable) { withUIContext { view?.applicationContext?.toast(e.message) } @@ -743,20 +783,27 @@ class MangaPresenter( fun unregisterTracking(service: TrackService) { val manga = successState?.manga ?: return - db.deleteTrackForManga(manga.toDbManga(), service).executeAsBlocking() + + presenterScope.launchIO { + deleteTrack.await(manga.id, service.id.toLong()) + } } private fun updateRemote(track: Track, service: TrackService) { launchIO { try { service.update(track) - db.insertTrack(track).executeAsBlocking() + + track.toDomainTrack(idRequired = false)?.let { + insertTrack.await(it) + } + withUIContext { view?.onTrackingRefreshDone() } } catch (e: Throwable) { withUIContext { view?.onTrackingRefreshError(e) } // Restart on error to set old values - fetchTrackers() + observeTrackers() } } } diff --git a/app/src/main/sqldelight/data/manga_sync.sq b/app/src/main/sqldelight/data/manga_sync.sq index dd34ef8577..c3276a7b81 100644 --- a/app/src/main/sqldelight/data/manga_sync.sq +++ b/app/src/main/sqldelight/data/manga_sync.sq @@ -17,6 +17,10 @@ CREATE TABLE manga_sync( ON DELETE CASCADE ); +delete: +DELETE FROM manga_sync +WHERE manga_id = :mangaId AND sync_id = :syncId; + getTracksByMangaId: SELECT * FROM manga_sync