Use sqldelight for direct db calls in MangaPresenter (#7366)

This commit is contained in:
AntsyLich 2022-06-27 01:54:34 +06:00 committed by GitHub
parent 61a44101a2
commit 04f0ca7846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 274 additions and 58 deletions

View File

@ -3,6 +3,7 @@ package eu.kanade.data.track
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.track.model.Track import eu.kanade.domain.track.model.Track
import eu.kanade.domain.track.repository.TrackRepository import eu.kanade.domain.track.repository.TrackRepository
import kotlinx.coroutines.flow.Flow
class TrackRepositoryImpl( class TrackRepositoryImpl(
private val handler: DatabaseHandler, private val handler: DatabaseHandler,
@ -14,11 +15,45 @@ class TrackRepositoryImpl(
} }
} }
override suspend fun subscribeTracksByMangaId(mangaId: Long): Flow<List<Track>> {
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<Track>) { override suspend fun insertAll(tracks: List<Track>) {
handler.await(inTransaction = true) { handler.await(inTransaction = true) {
tracks.forEach { mangaTrack -> tracks.forEach { mangaTrack ->
manga_syncQueries.insert( manga_syncQueries.insert(
mangaId = mangaTrack.id, mangaId = mangaTrack.mangaId,
syncId = mangaTrack.syncId, syncId = mangaTrack.syncId,
remoteId = mangaTrack.remoteId, remoteId = mangaTrack.remoteId,
libraryId = mangaTrack.libraryId, libraryId = mangaTrack.libraryId,

View File

@ -15,6 +15,7 @@ import eu.kanade.domain.category.repository.CategoryRepository
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.ShouldUpdateDbChapter import eu.kanade.domain.chapter.interactor.ShouldUpdateDbChapter
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource 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.interactor.UpdateChapter
import eu.kanade.domain.chapter.repository.ChapterRepository import eu.kanade.domain.chapter.repository.ChapterRepository
import eu.kanade.domain.extension.interactor.GetExtensionLanguages 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.ToggleSourcePin
import eu.kanade.domain.source.interactor.UpsertSourceData import eu.kanade.domain.source.interactor.UpsertSourceData
import eu.kanade.domain.source.repository.SourceRepository 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.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.repository.TrackRepository import eu.kanade.domain.track.repository.TrackRepository
@ -77,6 +79,7 @@ class DomainModule : InjektModule {
addFactory { MoveMangaToCategories(get()) } addFactory { MoveMangaToCategories(get()) }
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) } addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { DeleteTrack(get()) }
addFactory { GetTracks(get()) } addFactory { GetTracks(get()) }
addFactory { InsertTrack(get()) } addFactory { InsertTrack(get()) }
@ -85,6 +88,7 @@ class DomainModule : InjektModule {
addFactory { UpdateChapter(get()) } addFactory { UpdateChapter(get()) }
addFactory { ShouldUpdateDbChapter() } addFactory { ShouldUpdateDbChapter() }
addFactory { SyncChaptersWithSource(get(), get(), get(), get()) } addFactory { SyncChaptersWithSource(get(), get(), get(), get()) }
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) } addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { DeleteHistoryTable(get()) } addFactory { DeleteHistoryTable(get()) }

View File

@ -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<Chapter>,
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)
}
}
}

View File

@ -4,7 +4,9 @@ import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.repository.MangaRepository 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 { suspend fun awaitSetDownloadedFilter(manga: Manga, flag: Long): Boolean {
return mangaRepository.update( return mangaRepository.update(

View File

@ -1,5 +1,6 @@
package eu.kanade.domain.manga.model package eu.kanade.domain.manga.model
import eu.kanade.data.listOfStringsAdapter
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
@ -143,7 +144,7 @@ fun TriStateFilter.toTriStateGroupState(): ExtendedNavigationView.Item.TriStateG
} }
// TODO: Remove when all deps are migrated // 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.id = id
it.favorite = favorite it.favorite = favorite
it.last_update = lastUpdate it.last_update = lastUpdate
@ -151,7 +152,15 @@ fun Manga.toDbManga(): DbManga = DbManga.create(url, title, source).also {
it.viewer_flags = viewerFlags.toInt() it.viewer_flags = viewerFlags.toInt()
it.chapter_flags = chapterFlags.toInt() it.chapter_flags = chapterFlags.toInt()
it.cover_last_modified = coverLastModified 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.thumbnail_url = thumbnailUrl
it.initialized = initialized
} }
fun Manga.toMangaInfo(): MangaInfo = MangaInfo( fun Manga.toMangaInfo(): MangaInfo = MangaInfo(

View File

@ -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)
}
}
}

View File

@ -3,6 +3,7 @@ package eu.kanade.domain.track.interactor
import eu.kanade.domain.track.model.Track import eu.kanade.domain.track.model.Track
import eu.kanade.domain.track.repository.TrackRepository import eu.kanade.domain.track.repository.TrackRepository
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
class GetTracks( class GetTracks(
@ -17,4 +18,8 @@ class GetTracks(
emptyList() emptyList()
} }
} }
suspend fun subscribe(mangaId: Long): Flow<List<Track>> {
return trackRepository.subscribeTracksByMangaId(mangaId)
}
} }

View File

@ -9,6 +9,14 @@ class InsertTrack(
private val trackRepository: TrackRepository, 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<Track>) { suspend fun awaitAll(tracks: List<Track>) {
try { try {
trackRepository.insertAll(tracks) trackRepository.insertAll(tracks)

View File

@ -1,5 +1,7 @@
package eu.kanade.domain.track.model package eu.kanade.domain.track.model
import eu.kanade.tachiyomi.data.database.models.Track as DbTrack
data class Track( data class Track(
val id: Long, val id: Long,
val mangaId: 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,
)
}

View File

@ -1,10 +1,17 @@
package eu.kanade.domain.track.repository package eu.kanade.domain.track.repository
import eu.kanade.domain.track.model.Track import eu.kanade.domain.track.model.Track
import kotlinx.coroutines.flow.Flow
interface TrackRepository { interface TrackRepository {
suspend fun getTracksByMangaId(mangaId: Long): List<Track> suspend fun getTracksByMangaId(mangaId: Long): List<Track>
suspend fun subscribeTracksByMangaId(mangaId: Long): Flow<List<Track>>
suspend fun delete(mangaId: Long, syncId: Long)
suspend fun insert(track: Track)
suspend fun insertAll(tracks: List<Track>) suspend fun insertAll(tracks: List<Track>)
} }

View File

@ -2,7 +2,10 @@ package eu.kanade.tachiyomi.ui.manga
import android.os.Bundle import android.os.Bundle
import androidx.compose.runtime.Immutable 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.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.chapter.interactor.UpdateChapter import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.ChapterUpdate import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter 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.isLocal
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaInfo 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.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga 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.Track
import eu.kanade.tachiyomi.data.database.models.toDomainChapter import eu.kanade.tachiyomi.data.database.models.toDomainChapter
import eu.kanade.tachiyomi.data.download.DownloadManager 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.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
import eu.kanade.tachiyomi.util.chapter.getChapterSort 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.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext 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.logcat
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
import logcat.LogPriority import logcat.LogPriority
import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
@ -77,6 +87,12 @@ class MangaPresenter(
private val updateChapter: UpdateChapter = Injekt.get(), private val updateChapter: UpdateChapter = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val syncChaptersWithSource: SyncChaptersWithSource = 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<MangaController>() { ) : BasePresenter<MangaController>() {
private val _state: MutableStateFlow<MangaScreenState> = MutableStateFlow(MangaScreenState.Loading) private val _state: MutableStateFlow<MangaScreenState> = MutableStateFlow(MangaScreenState.Loading)
@ -107,7 +123,6 @@ class MangaPresenter(
private val loggedServices by lazy { trackManager.services.filter { it.isLogged } } private val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
private var trackSubscription: Subscription? = null
private var searchTrackerJob: Job? = null private var searchTrackerJob: Job? = null
private var refreshTrackersJob: Job? = null private var refreshTrackersJob: Job? = null
@ -154,20 +169,15 @@ class MangaPresenter(
isFromSource = isFromSource, isFromSource = isFromSource,
trackingAvailable = trackManager.hasLoggedServices(), trackingAvailable = trackManager.hasLoggedServices(),
chapters = chapterItems, chapters = chapterItems,
).also { )
getTrackingObservable(manga)
.subscribeLatestCache(
{ _, count -> updateSuccessState { it.copy(trackingCount = count) } },
{ _, error -> logcat(LogPriority.ERROR, error) },
)
}
// Update state // Update state
is MangaScreenState.Success -> currentState.copy(manga = manga, chapters = chapterItems) is MangaScreenState.Success -> currentState.copy(manga = manga, chapters = chapterItems)
} }
} }
fetchTrackers() observeTrackers()
observeTrackingCount()
observeDownloads() observeDownloads()
if (!manga.initialized) { if (!manga.initialized) {
@ -195,20 +205,6 @@ class MangaPresenter(
} }
// Manga info - start // Manga info - start
private fun getTrackingObservable(manga: DomainManga): Observable<Int> {
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. * 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 * @return Array of category ids the manga is in, if none returns default id
*/ */
fun getMangaCategoryIds(manga: DomainManga): Array<Int> { fun getMangaCategoryIds(manga: DomainManga): Array<Int> {
val categories = db.getCategoriesForManga(manga.toDbManga()).executeAsBlocking() val categories = runBlocking { getCategories.await(manga.id) }
return categories.mapNotNull { it.id }.toTypedArray() return categories.map { it.id.toInt() }.toTypedArray()
} }
fun moveMangaToCategoriesAndAddToLibrary(manga: Manga, categories: List<Category>) { fun moveMangaToCategoriesAndAddToLibrary(manga: Manga, categories: List<Category>) {
@ -359,8 +355,11 @@ class MangaPresenter(
* @param categories the selected categories. * @param categories the selected categories.
*/ */
private fun moveMangaToCategories(manga: Manga, categories: List<Category>) { private fun moveMangaToCategories(manga: Manga, categories: List<Category>) {
val mc = categories.filter { it.id != 0 }.map { MangaCategory.create(manga, it) } val mangaId = manga.id ?: return
db.setMangaCategories(mc, listOf(manga)) val categoryIds = categories.mapNotNull { it.id?.toLong() }
presenterScope.launchIO {
moveMangaToCategories.await(mangaId, categoryIds)
}
} }
/** /**
@ -373,6 +372,22 @@ class MangaPresenter(
moveMangaToCategories(manga, listOfNotNull(category)) 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 // Manga info - end
// Chapters list - start // Chapters list - start
@ -520,7 +535,7 @@ class MangaPresenter(
val modified = chapters.filterNot { it.read == read } val modified = chapters.filterNot { it.read == read }
modified modified
.map { ChapterUpdate(id = it.id, read = read) } .map { ChapterUpdate(id = it.id, read = read) }
.forEach { updateChapter.await(it) } .let { updateChapter.awaitAll(it) }
if (read && preferences.removeAfterMarkedAsRead()) { if (read && preferences.removeAfterMarkedAsRead()) {
deleteChapters(modified) deleteChapters(modified)
} }
@ -545,7 +560,7 @@ class MangaPresenter(
chapters chapters
.filterNot { it.bookmark == bookmarked } .filterNot { it.bookmark == bookmarked }
.map { ChapterUpdate(id = it.id, 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) { fun setUnreadFilter(state: State) {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
val flag = when (state) { val flag = when (state) {
State.IGNORE -> DomainManga.SHOW_ALL State.IGNORE -> DomainManga.SHOW_ALL
State.INCLUDE -> DomainManga.CHAPTER_SHOW_UNREAD State.INCLUDE -> DomainManga.CHAPTER_SHOW_UNREAD
@ -609,11 +625,13 @@ class MangaPresenter(
*/ */
fun setDownloadedFilter(state: State) { fun setDownloadedFilter(state: State) {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
val flag = when (state) { val flag = when (state) {
State.IGNORE -> DomainManga.SHOW_ALL State.IGNORE -> DomainManga.SHOW_ALL
State.INCLUDE -> DomainManga.CHAPTER_SHOW_DOWNLOADED State.INCLUDE -> DomainManga.CHAPTER_SHOW_DOWNLOADED
State.EXCLUDE -> DomainManga.CHAPTER_SHOW_NOT_DOWNLOADED State.EXCLUDE -> DomainManga.CHAPTER_SHOW_NOT_DOWNLOADED
} }
presenterScope.launchIO { presenterScope.launchIO {
setMangaChapterFlags.awaitSetDownloadedFilter(manga, flag) setMangaChapterFlags.awaitSetDownloadedFilter(manga, flag)
} }
@ -625,11 +643,13 @@ class MangaPresenter(
*/ */
fun setBookmarkedFilter(state: State) { fun setBookmarkedFilter(state: State) {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
val flag = when (state) { val flag = when (state) {
State.IGNORE -> DomainManga.SHOW_ALL State.IGNORE -> DomainManga.SHOW_ALL
State.INCLUDE -> DomainManga.CHAPTER_SHOW_BOOKMARKED State.INCLUDE -> DomainManga.CHAPTER_SHOW_BOOKMARKED
State.EXCLUDE -> DomainManga.CHAPTER_SHOW_NOT_BOOKMARKED State.EXCLUDE -> DomainManga.CHAPTER_SHOW_NOT_BOOKMARKED
} }
presenterScope.launchIO { presenterScope.launchIO {
setMangaChapterFlags.awaitSetBookmarkFilter(manga, flag) setMangaChapterFlags.awaitSetBookmarkFilter(manga, flag)
} }
@ -641,6 +661,7 @@ class MangaPresenter(
*/ */
fun setDisplayMode(mode: Long) { fun setDisplayMode(mode: Long) {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
presenterScope.launchIO { presenterScope.launchIO {
setMangaChapterFlags.awaitSetDisplayMode(manga, mode) setMangaChapterFlags.awaitSetDisplayMode(manga, mode)
} }
@ -652,6 +673,7 @@ class MangaPresenter(
*/ */
fun setSorting(sort: Long) { fun setSorting(sort: Long) {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
presenterScope.launchIO { presenterScope.launchIO {
setMangaChapterFlags.awaitSetSortingModeOrFlipOrder(manga, sort) setMangaChapterFlags.awaitSetSortingModeOrFlipOrder(manga, sort)
} }
@ -661,19 +683,25 @@ class MangaPresenter(
// Track sheet - start // Track sheet - start
private fun fetchTrackers() { private fun observeTrackers() {
val manga = successState?.manga ?: return val manga = successState?.manga ?: return
trackSubscription?.let { remove(it) }
trackSubscription = db.getTracks(manga.id) presenterScope.launchIO {
.asRxObservable() getTracks.subscribe(manga.id)
.map { tracks -> .catch { logcat(LogPriority.ERROR, it) }
loggedServices.map { service -> .map { tracks ->
TrackItem(tracks.find { it.sync_id == service.id }, service) val dbTracks = tracks.map { it.toDbTrack() }
loggedServices.map { service ->
TrackItem(dbTracks.find { it.sync_id == service.id }, service)
}
} }
} .collectLatest { trackItems ->
.observeOn(AndroidSchedulers.mainThread()) _trackList = trackItems
.doOnNext { _trackList = it } withContext(Dispatchers.Main) {
.subscribeLatestCache(MangaController::onNextTrackers) view?.onNextTrackers(trackItems)
}
}
}
} }
fun refreshTrackers() { fun refreshTrackers() {
@ -682,16 +710,21 @@ class MangaPresenter(
supervisorScope { supervisorScope {
try { try {
trackList trackList
.filter { it.track != null }
.map { .map {
async { async {
val track = it.service.refresh(it.track!!) val track = it.track ?: return@async null
db.insertTrack(track).executeAsBlocking()
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 val allChapters = successState?.chapters
?.map { it.chapter.toDbChapter() } ?: emptyList() ?.map { it.chapter } ?: emptyList()
syncChaptersWithTrackServiceTwoWay(db, allChapters, track, it.service)
syncChaptersWithTrackServiceTwoWay
.await(allChapters, domainTrack, it.service)
} }
} }
} }
@ -727,10 +760,17 @@ class MangaPresenter(
.map { it.chapter.toDbChapter() } .map { it.chapter.toDbChapter() }
val hasReadChapters = allChapters.any { it.read } val hasReadChapters = allChapters.any { it.read }
service.bind(item, hasReadChapters) service.bind(item, hasReadChapters)
db.insertTrack(item).executeAsBlocking()
if (service is EnhancedTrackService) { item.toDomainTrack(idRequired = false)?.let { track ->
syncChaptersWithTrackServiceTwoWay(db, allChapters, item, service) insertTrack.await(track)
(service as? EnhancedTrackService)?.let { _ ->
val chapters = successState.chapters
.map { it.chapter }
syncChaptersWithTrackServiceTwoWay
.await(chapters, track, service)
}
} }
} catch (e: Throwable) { } catch (e: Throwable) {
withUIContext { view?.applicationContext?.toast(e.message) } withUIContext { view?.applicationContext?.toast(e.message) }
@ -743,20 +783,27 @@ class MangaPresenter(
fun unregisterTracking(service: TrackService) { fun unregisterTracking(service: TrackService) {
val manga = successState?.manga ?: return 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) { private fun updateRemote(track: Track, service: TrackService) {
launchIO { launchIO {
try { try {
service.update(track) service.update(track)
db.insertTrack(track).executeAsBlocking()
track.toDomainTrack(idRequired = false)?.let {
insertTrack.await(it)
}
withUIContext { view?.onTrackingRefreshDone() } withUIContext { view?.onTrackingRefreshDone() }
} catch (e: Throwable) { } catch (e: Throwable) {
withUIContext { view?.onTrackingRefreshError(e) } withUIContext { view?.onTrackingRefreshError(e) }
// Restart on error to set old values // Restart on error to set old values
fetchTrackers() observeTrackers()
} }
} }
} }

View File

@ -17,6 +17,10 @@ CREATE TABLE manga_sync(
ON DELETE CASCADE ON DELETE CASCADE
); );
delete:
DELETE FROM manga_sync
WHERE manga_id = :mangaId AND sync_id = :syncId;
getTracksByMangaId: getTracksByMangaId:
SELECT * SELECT *
FROM manga_sync FROM manga_sync