Use sqldelight in migration (#7331)

* Use sqldelight in migration

* Some more changes

Co-Authored-By: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>

* Review Changes

* Review changes 2

* Review Changes 3

* Review Changes 4

Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
(cherry picked from commit e3b1053c03da17c8c1b66f1914251707134e84a9)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
This commit is contained in:
AntsyLich 2022-06-22 03:27:55 +06:00 committed by Jobobby04
parent 9e63d7fb0b
commit 14a57b7d4d
25 changed files with 292 additions and 79 deletions

View File

@ -55,6 +55,12 @@ class CategoryRepositoryImpl(
}
}
override suspend fun getCategoriesForManga(mangaId: Long): List<Category> {
return handler.awaitList {
categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper)
}
}
override suspend fun checkDuplicateName(name: String): Boolean {
return handler
.awaitList { categoriesQueries.getCategories() }

View File

@ -41,7 +41,6 @@ class ChapterRepositoryImpl(
}
override suspend fun update(chapterUpdate: ChapterUpdate) {
try {
handler.await {
chaptersQueries.update(
chapterUpdate.mangaId,
@ -58,13 +57,9 @@ class ChapterRepositoryImpl(
chapterId = chapterUpdate.id,
)
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
override suspend fun updateAll(chapterUpdates: List<ChapterUpdate>) {
try {
handler.await(inTransaction = true) {
chapterUpdates.forEach { chapterUpdate ->
chaptersQueries.update(
@ -83,9 +78,6 @@ class ChapterRepositoryImpl(
)
}
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
override suspend fun removeChaptersWithIds(chapterIds: List<Long>) {

View File

@ -42,6 +42,15 @@ class MangaRepositoryImpl(
}
}
override suspend fun moveMangaToCategories(mangaId: Long, categoryIds: List<Long>) {
handler.await(inTransaction = true) {
mangas_categoriesQueries.deleteMangaCategoryByMangaId(mangaId)
categoryIds.map { categoryId ->
mangas_categoriesQueries.insert(mangaId, categoryId)
}
}
}
override suspend fun update(update: MangaUpdate): Boolean {
return try {
handler.await {

View File

@ -0,0 +1,22 @@
package eu.kanade.data.track
import eu.kanade.domain.track.model.Track
val trackMapper: (Long, Long, Long, Long, Long?, String, Double, Long, Long, Float, String, Long, Long) -> Track =
{ id, mangaId, syncId, remoteId, libraryId, title, lastChapterRead, totalChapters, status, score, remoteUrl, startDate, finishDate ->
Track(
id = id,
mangaId = mangaId,
syncId = syncId,
remoteId = remoteId,
libraryId = libraryId,
title = title,
lastChapterRead = lastChapterRead,
totalChapters = totalChapters,
status = status,
score = score,
remoteUrl = remoteUrl,
startDate = startDate,
finishDate = finishDate,
)
}

View File

@ -0,0 +1,37 @@
package eu.kanade.data.track
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.track.model.Track
import eu.kanade.domain.track.repository.TrackRepository
class TrackRepositoryImpl(
private val handler: DatabaseHandler,
) : TrackRepository {
override suspend fun getTracksByMangaId(mangaId: Long): List<Track> {
return handler.awaitList {
manga_syncQueries.getTracksByMangaId(mangaId, trackMapper)
}
}
override suspend fun insertAll(tracks: List<Track>) {
handler.await(inTransaction = true) {
tracks.forEach { mangaTrack ->
manga_syncQueries.insert(
mangaId = mangaTrack.id,
syncId = mangaTrack.syncId,
remoteId = mangaTrack.remoteId,
libraryId = mangaTrack.libraryId,
title = mangaTrack.title,
lastChapterRead = mangaTrack.lastChapterRead,
totalChapters = mangaTrack.totalChapters,
status = mangaTrack.status,
score = mangaTrack.score,
remoteUrl = mangaTrack.remoteUrl,
startDate = mangaTrack.startDate,
finishDate = mangaTrack.finishDate,
)
}
}
}
}

View File

@ -5,9 +5,11 @@ import eu.kanade.data.chapter.ChapterRepositoryImpl
import eu.kanade.data.history.HistoryRepositoryImpl
import eu.kanade.data.manga.MangaRepositoryImpl
import eu.kanade.data.source.SourceRepositoryImpl
import eu.kanade.data.track.TrackRepositoryImpl
import eu.kanade.domain.category.interactor.DeleteCategory
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.InsertCategory
import eu.kanade.domain.category.interactor.MoveMangaToCategories
import eu.kanade.domain.category.interactor.UpdateCategory
import eu.kanade.domain.category.repository.CategoryRepository
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
@ -29,6 +31,7 @@ import eu.kanade.domain.history.repository.HistoryRepository
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetFavoritesBySourceId
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.ResetViewerFlags
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.repository.MangaRepository
@ -43,6 +46,9 @@ 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.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.repository.TrackRepository
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addFactory
@ -61,10 +67,16 @@ class DomainModule : InjektModule {
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetDuplicateLibraryManga(get()) }
addFactory { GetFavoritesBySourceId(get()) }
addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetMangaById(get()) }
addFactory { GetNextChapter(get()) }
addFactory { ResetViewerFlags(get()) }
addFactory { UpdateManga(get()) }
addFactory { MoveMangaToCategories(get()) }
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { GetTracks(get()) }
addFactory { InsertTrack(get()) }
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { GetChapterByMangaId(get()) }

View File

@ -11,4 +11,8 @@ class GetCategories(
fun subscribe(): Flow<List<Category>> {
return categoryRepository.getAll()
}
suspend fun await(mangaId: Long): List<Category> {
return categoryRepository.getCategoriesForManga(mangaId)
}
}

View File

@ -0,0 +1,18 @@
package eu.kanade.domain.category.interactor
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority
class MoveMangaToCategories(
private val mangaRepository: MangaRepository,
) {
suspend fun await(mangaId: Long, categoryIds: List<Long>) {
try {
mangaRepository.moveMangaToCategories(mangaId, categoryIds)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
}

View File

@ -16,6 +16,8 @@ interface CategoryRepository {
suspend fun delete(categoryId: Long)
suspend fun getCategoriesForManga(mangaId: Long): List<Category>
suspend fun checkDuplicateName(name: String): Boolean
}

View File

@ -3,8 +3,6 @@ package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.repository.ChapterRepository
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import logcat.LogPriority
class GetChapterByMangaId(
@ -19,13 +17,4 @@ class GetChapterByMangaId(
emptyList()
}
}
suspend fun subscribe(mangaId: Long): Flow<List<Chapter>> {
return try {
chapterRepository.getChapterByMangaIdAsFlow(mangaId)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
flowOf(emptyList())
}
}
}

View File

@ -26,6 +26,7 @@ class SyncChaptersWithSource(
private val chapterRepository: ChapterRepository = Injekt.get(),
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
) {
@ -184,7 +185,7 @@ class SyncChaptersWithSource(
if (toChange.isNotEmpty()) {
val chapterUpdates = toChange.map { it.toChapterUpdate() }
chapterRepository.updateAll(chapterUpdates)
updateChapter.awaitAll(chapterUpdates)
}
// Set this manga as updated since chapters were changed

View File

@ -2,12 +2,26 @@ package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.repository.ChapterRepository
import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority
class UpdateChapter(
private val chapterRepository: ChapterRepository,
) {
suspend fun await(chapterUpdate: ChapterUpdate) {
try {
chapterRepository.update(chapterUpdate)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
suspend fun awaitAll(chapterUpdates: List<ChapterUpdate>) {
try {
chapterRepository.updateAll(chapterUpdates)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
}

View File

@ -0,0 +1,23 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.repository.ChapterRepository
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.repository.MangaRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
class GetMangaWithChapters(
private val mangaRepository: MangaRepository,
private val chapterRepository: ChapterRepository,
) {
suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> {
return combine(
mangaRepository.subscribeMangaById(id),
chapterRepository.getChapterByMangaIdAsFlow(id),
) { manga, chapters ->
Pair(manga, chapters)
}
}
}

View File

@ -14,6 +14,10 @@ class UpdateManga(
private val mangaRepository: MangaRepository,
) {
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
return mangaRepository.update(mangaUpdate)
}
suspend fun awaitUpdateFromSource(
localManga: Manga,
remoteManga: MangaInfo,

View File

@ -16,5 +16,7 @@ interface MangaRepository {
suspend fun resetViewerFlags(): Boolean
suspend fun moveMangaToCategories(mangaId: Long, categoryIds: List<Long>)
suspend fun update(update: MangaUpdate): Boolean
}

View File

@ -0,0 +1,20 @@
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 logcat.LogPriority
class GetTracks(
private val trackRepository: TrackRepository,
) {
suspend fun await(mangaId: Long): List<Track> {
return try {
trackRepository.getTracksByMangaId(mangaId)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
emptyList()
}
}
}

View File

@ -0,0 +1,19 @@
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 logcat.LogPriority
class InsertTrack(
private val trackRepository: TrackRepository,
) {
suspend fun awaitAll(tracks: List<Track>) {
try {
trackRepository.insertAll(tracks)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
}

View File

@ -0,0 +1,27 @@
package eu.kanade.domain.track.model
data class Track(
val id: Long,
val mangaId: Long,
val syncId: Long,
val remoteId: Long,
val libraryId: Long?,
val title: String,
val lastChapterRead: Double,
val totalChapters: Long,
val status: Long,
val score: Float,
val remoteUrl: String,
val startDate: Long,
val finishDate: Long,
) {
fun copyPersonalFrom(other: Track): Track {
return this.copy(
lastChapterRead = other.lastChapterRead,
score = other.score,
status = other.status,
startDate = other.startDate,
finishDate = other.finishDate,
)
}
}

View File

@ -0,0 +1,10 @@
package eu.kanade.domain.track.repository
import eu.kanade.domain.track.model.Track
interface TrackRepository {
suspend fun getTracksByMangaId(mangaId: Long): List<Track>
suspend fun insertAll(tracks: List<Track>)
}

View File

@ -1,9 +1,10 @@
package eu.kanade.tachiyomi.data.track
import eu.kanade.domain.track.model.Track
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source
import eu.kanade.domain.manga.model.Manga as DomainManga
/**
* An Enhanced Track Service will never prompt the user to match a manga with the remote.
@ -30,10 +31,10 @@ interface EnhancedTrackService {
/**
* Checks whether the provided source/track/manga triplet is from this TrackService
*/
fun isTrackFrom(track: Track, manga: Manga, source: Source?): Boolean
fun isTrackFrom(track: Track, manga: DomainManga, source: Source?): Boolean
/**
* Migrates the given track for the manga to the newSource, if possible
*/
fun migrateTrack(track: Track, manga: Manga, newSource: Source): Track?
fun migrateTrack(track: Track, manga: DomainManga, newSource: Source): Track?
}

View File

@ -13,6 +13,8 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source
import okhttp3.Dns
import okhttp3.OkHttpClient
import eu.kanade.domain.manga.model.Manga as DomainManga
import eu.kanade.domain.track.model.Track as DomainTrack
class Komga(private val context: Context, id: Int) : TrackService(id), EnhancedTrackService, NoLoginTrackService {
@ -105,12 +107,12 @@ class Komga(private val context: Context, id: Int) : TrackService(id), EnhancedT
null
}
override fun isTrackFrom(track: Track, manga: Manga, source: Source?): Boolean =
track.tracking_url == manga.url && source?.let { accept(it) } == true
override fun isTrackFrom(track: DomainTrack, manga: DomainManga, source: Source?): Boolean =
track.remoteUrl == manga.url && source?.let { accept(it) } == true
override fun migrateTrack(track: Track, manga: Manga, newSource: Source): Track? =
override fun migrateTrack(track: DomainTrack, manga: DomainManga, newSource: Source): DomainTrack? =
if (accept(newSource)) {
track.also { track.tracking_url = manga.url }
track.copy(remoteUrl = manga.url)
} else {
null
}

View File

@ -5,11 +5,13 @@ import android.net.Uri
import android.os.Bundle
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -31,7 +33,6 @@ import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
@ -45,7 +46,6 @@ import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.system.logcat
@ -98,12 +98,17 @@ class MangaPresenter(
private val db: DatabaseHelper = Injekt.get(),
private val trackManager: TrackManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
// SY -->
private val sourceManager: SourceManager = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
// SY <--
private val coverCache: CoverCache = Injekt.get(),
private val getMangaWithChapters: GetMangaWithChapters = Injekt.get(),
// SY -->
private val getMergedChapterByMangaId: GetMergedChapterByMangaId = Injekt.get(),
// SY <--
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getMergedChapterByMangaId: GetMergedChapterByMangaId = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
) : BasePresenter<MangaController>() {
/**
@ -183,7 +188,6 @@ class MangaPresenter(
}
// Manga info - start
getMangaObservable()
.observeOn(AndroidSchedulers.mainThread())
// SY -->
@ -232,27 +236,27 @@ class MangaPresenter(
manga.id?.let { mangaId ->
if (source is MergedSource) {
getMergedChapterByMangaId.subscribe(mangaId)
.map { source.transformMergedChapters(mangaId, it, true, dedupe) }
.map { manga to source.transformMergedChapters(mangaId, it, true, dedupe) }
} else {
getChapterByMangaId.subscribe(mangaId)
getMangaWithChapters.subscribe(mangaId)
}
.collectLatest { domainChapters ->
val chapterItems = domainChapters.map { it.toDbChapter().toModel() }
.collectLatest { (_, chapters) ->
val chapterItems = chapters.map { it.toDbChapter().toModel() }
setDownloadedChapters(chapterItems)
this@MangaPresenter.allChapters = chapterItems
observeDownloads()
// SY -->
if (domainChapters.isNotEmpty() && (source.isEhBasedSource()) && DebugToggles.ENABLE_EXH_ROOT_REDIRECT.enabled) {
if (chapters.isNotEmpty() && (source.isEhBasedSource()) && DebugToggles.ENABLE_EXH_ROOT_REDIRECT.enabled) {
// Check for gallery in library and accept manga with lowest id
// Find chapters sharing same root
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, domainChapters.map { it.toDbChapter() })
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters.map { it.toDbChapter() })
.onEach { (acceptedChain, _) ->
// Redirect if we are not the accepted root
if (manga.id != acceptedChain.manga.id && acceptedChain.manga.favorite) {
// Update if any of our chapters are not in accepted manga's chapters
xLogD("Found accepted manga %s", manga.url)
val ourChapterUrls = domainChapters.map { it.url }.toSet()
val ourChapterUrls = chapters.map { it.url }.toSet()
val acceptedChapterUrls = acceptedChain.chapters.map { it.url }.toSet()
val update = (ourChapterUrls - acceptedChapterUrls).isNotEmpty()
redirectFlow.emit(
@ -280,7 +284,6 @@ class MangaPresenter(
}
// Manga info - start
private fun getMangaObservable(): Observable<Manga> {
return db.getManga(manga.url, manga.source).asRxObservable()
}
@ -318,16 +321,11 @@ class MangaPresenter(
if (fetchMangaJob?.isActive == true) return
fetchMangaJob = presenterScope.launchIO {
try {
val networkManga = source.getMangaDetails(manga.toMangaInfo())
val sManga = networkManga.toSManga()
manga.prepUpdateCover(coverCache, sManga, manualFetch)
manga.copyFrom(sManga)
if (!manga.favorite) {
// if the manga isn't a favorite, set its title from source and update in db
manga.title = sManga.title
manga.toDomainManga()?.let { domainManga ->
val networkManga = source.getMangaDetails(domainManga.toMangaInfo())
updateManga.awaitUpdateFromSource(domainManga, networkManga, manualFetch, coverCache)
}
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
withUIContext { view?.onFetchMangaInfoDone() }
} catch (e: Throwable) {

View File

@ -245,7 +245,6 @@ class MangaFullCoverDialog : FullComposeController<MangaFullCoverDialog.MangaFul
}
}
}
}
companion object {
@ -257,4 +256,3 @@ class MangaFullCoverDialog : FullComposeController<MangaFullCoverDialog.MangaFul
private const val REQUEST_IMAGE_OPEN = 101
}
}

View File

@ -56,8 +56,10 @@ class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: Att
val title = getString(R.styleable.MaterialSpinnerView_title).orEmpty()
binding.title.text = title
val viewEntries = (getTextArray(R.styleable.MaterialSpinnerView_android_entries)
?: emptyArray()).map { it.toString() }
val viewEntries = (
getTextArray(R.styleable.MaterialSpinnerView_android_entries)
?: emptyArray()
).map { it.toString() }
entries = viewEntries
binding.details.text = viewEntries.firstOrNull().orEmpty()
}

View File

@ -23,7 +23,8 @@ SELECT
C._id AS id,
C.name,
C.sort AS `order`,
C.flags
C.flags,
C.manga_order AS `mangaOrder`
FROM categories C
JOIN mangas_categories MC
ON C._id = MC.category_id