mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 19:41:50 +01:00
Use sqldelight for direct db calls in MangaPresenter
(#7366)
This commit is contained in:
parent
61a44101a2
commit
04f0ca7846
@ -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,
|
||||||
|
@ -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()) }
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -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>)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
.catch { logcat(LogPriority.ERROR, it) }
|
||||||
.map { tracks ->
|
.map { tracks ->
|
||||||
|
val dbTracks = tracks.map { it.toDbTrack() }
|
||||||
loggedServices.map { service ->
|
loggedServices.map { service ->
|
||||||
TrackItem(tracks.find { it.sync_id == service.id }, service)
|
TrackItem(dbTracks.find { it.sync_id == service.id }, service)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.collectLatest { trackItems ->
|
||||||
|
_trackList = trackItems
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
view?.onNextTrackers(trackItems)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doOnNext { _trackList = it }
|
|
||||||
.subscribeLatestCache(MangaController::onNextTrackers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user