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>
This commit is contained in:
AntsyLich 2022-06-22 03:27:55 +06:00 committed by GitHub
parent c2520bff12
commit e3b1053c03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 374 additions and 155 deletions

View File

@ -48,6 +48,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 { override suspend fun checkDuplicateName(name: String): Boolean {
return handler return handler
.awaitList { categoriesQueries.getCategories() } .awaitList { categoriesQueries.getCategories() }

View File

@ -41,8 +41,27 @@ class ChapterRepositoryImpl(
} }
override suspend fun update(chapterUpdate: ChapterUpdate) { override suspend fun update(chapterUpdate: ChapterUpdate) {
try { handler.await {
handler.await { chaptersQueries.update(
chapterUpdate.mangaId,
chapterUpdate.url,
chapterUpdate.name,
chapterUpdate.scanlator,
chapterUpdate.read?.toLong(),
chapterUpdate.bookmark?.toLong(),
chapterUpdate.lastPageRead,
chapterUpdate.chapterNumber?.toDouble(),
chapterUpdate.sourceOrder,
chapterUpdate.dateFetch,
chapterUpdate.dateUpload,
chapterId = chapterUpdate.id,
)
}
}
override suspend fun updateAll(chapterUpdates: List<ChapterUpdate>) {
handler.await(inTransaction = true) {
chapterUpdates.forEach { chapterUpdate ->
chaptersQueries.update( chaptersQueries.update(
chapterUpdate.mangaId, chapterUpdate.mangaId,
chapterUpdate.url, chapterUpdate.url,
@ -58,33 +77,6 @@ class ChapterRepositoryImpl(
chapterId = chapterUpdate.id, 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(
chapterUpdate.mangaId,
chapterUpdate.url,
chapterUpdate.name,
chapterUpdate.scanlator,
chapterUpdate.read?.toLong(),
chapterUpdate.bookmark?.toLong(),
chapterUpdate.lastPageRead,
chapterUpdate.chapterNumber?.toDouble(),
chapterUpdate.sourceOrder,
chapterUpdate.dateFetch,
chapterUpdate.dateUpload,
chapterId = chapterUpdate.id,
)
}
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
} }
} }

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 { override suspend fun update(update: MangaUpdate): Boolean {
return try { return try {
handler.await { 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.history.HistoryRepositoryImpl
import eu.kanade.data.manga.MangaRepositoryImpl import eu.kanade.data.manga.MangaRepositoryImpl
import eu.kanade.data.source.SourceRepositoryImpl 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.DeleteCategory
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.InsertCategory 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.interactor.UpdateCategory
import eu.kanade.domain.category.repository.CategoryRepository import eu.kanade.domain.category.repository.CategoryRepository
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId 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.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetFavoritesBySourceId import eu.kanade.domain.manga.interactor.GetFavoritesBySourceId
import eu.kanade.domain.manga.interactor.GetMangaById 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.ResetViewerFlags
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.repository.MangaRepository 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.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.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.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addFactory import uy.kohesive.injekt.api.addFactory
@ -61,10 +67,16 @@ class DomainModule : InjektModule {
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) } addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetDuplicateLibraryManga(get()) } addFactory { GetDuplicateLibraryManga(get()) }
addFactory { GetFavoritesBySourceId(get()) } addFactory { GetFavoritesBySourceId(get()) }
addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetMangaById(get()) } addFactory { GetMangaById(get()) }
addFactory { GetNextChapter(get()) } addFactory { GetNextChapter(get()) }
addFactory { ResetViewerFlags(get()) } addFactory { ResetViewerFlags(get()) }
addFactory { UpdateManga(get()) } addFactory { UpdateManga(get()) }
addFactory { MoveMangaToCategories(get()) }
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { GetTracks(get()) }
addFactory { InsertTrack(get()) }
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) } addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { GetChapterByMangaId(get()) } addFactory { GetChapterByMangaId(get()) }

View File

@ -11,4 +11,8 @@ class GetCategories(
fun subscribe(): Flow<List<Category>> { fun subscribe(): Flow<List<Category>> {
return categoryRepository.getAll() 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 delete(categoryId: Long)
suspend fun getCategoriesForManga(mangaId: Long): List<Category>
suspend fun checkDuplicateName(name: String): Boolean 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.model.Chapter
import eu.kanade.domain.chapter.repository.ChapterRepository import eu.kanade.domain.chapter.repository.ChapterRepository
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import logcat.LogPriority import logcat.LogPriority
class GetChapterByMangaId( class GetChapterByMangaId(
@ -19,13 +17,4 @@ class GetChapterByMangaId(
emptyList() 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

@ -25,6 +25,7 @@ class SyncChaptersWithSource(
private val chapterRepository: ChapterRepository = Injekt.get(), private val chapterRepository: ChapterRepository = Injekt.get(),
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(), private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
) { ) {
@ -167,7 +168,7 @@ class SyncChaptersWithSource(
if (toChange.isNotEmpty()) { if (toChange.isNotEmpty()) {
val chapterUpdates = toChange.map { it.toChapterUpdate() } val chapterUpdates = toChange.map { it.toChapterUpdate() }
chapterRepository.updateAll(chapterUpdates) updateChapter.awaitAll(chapterUpdates)
} }
// Set this manga as updated since chapters were changed // 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.model.ChapterUpdate
import eu.kanade.domain.chapter.repository.ChapterRepository import eu.kanade.domain.chapter.repository.ChapterRepository
import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority
class UpdateChapter( class UpdateChapter(
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository,
) { ) {
suspend fun await(chapterUpdate: ChapterUpdate) { suspend fun await(chapterUpdate: ChapterUpdate) {
chapterRepository.update(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, private val mangaRepository: MangaRepository,
) { ) {
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
return mangaRepository.update(mangaUpdate)
}
suspend fun awaitUpdateFromSource( suspend fun awaitUpdateFromSource(
localManga: Manga, localManga: Manga,
remoteManga: MangaInfo, remoteManga: MangaInfo,

View File

@ -16,5 +16,7 @@ interface MangaRepository {
suspend fun resetViewerFlags(): Boolean suspend fun resetViewerFlags(): Boolean
suspend fun moveMangaToCategories(mangaId: Long, categoryIds: List<Long>)
suspend fun update(update: MangaUpdate): Boolean 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 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.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source 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. * 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 * 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 * 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 eu.kanade.tachiyomi.source.Source
import okhttp3.Dns import okhttp3.Dns
import okhttp3.OkHttpClient 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 { 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 null
} }
override fun isTrackFrom(track: Track, manga: Manga, source: Source?): Boolean = override fun isTrackFrom(track: DomainTrack, manga: DomainManga, source: Source?): Boolean =
track.tracking_url == manga.url && source?.let { accept(it) } == true 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)) { if (accept(newSource)) {
track.also { track.tracking_url = manga.url } track.copy(remoteUrl = manga.url)
} else { } else {
null null
} }

View File

@ -2,9 +2,21 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
import android.os.Bundle import android.os.Bundle
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.MoveMangaToCategories
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.toChapterUpdate
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
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.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
@ -17,8 +29,6 @@ import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchCardItem import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchCardItem
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.hasCustomCover
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
@ -31,6 +41,14 @@ import java.util.Date
class SearchPresenter( class SearchPresenter(
initialQuery: String? = "", initialQuery: String? = "",
private val manga: Manga, private val manga: Manga,
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(),
private val moveMangaToCategories: MoveMangaToCategories = Injekt.get(),
) : GlobalSearchPresenter(initialQuery) { ) : GlobalSearchPresenter(initialQuery) {
private val replacingMangaRelay = BehaviorRelay.create<Pair<Boolean, Manga?>>() private val replacingMangaRelay = BehaviorRelay.create<Pair<Boolean, Manga?>>()
@ -94,101 +112,93 @@ class SearchPresenter(
replace: Boolean, replace: Boolean,
) { ) {
val flags = preferences.migrateFlags().get() val flags = preferences.migrateFlags().get()
val migrateChapters =
MigrationFlags.hasChapters( val migrateChapters = MigrationFlags.hasChapters(flags)
flags, val migrateCategories = MigrationFlags.hasCategories(flags)
) val migrateTracks = MigrationFlags.hasTracks(flags)
val migrateCategories = val migrateCustomCover = MigrationFlags.hasCustomCover(flags)
MigrationFlags.hasCategories(
flags, val prevDomainManga = prevManga.toDomainManga() ?: return
) val domainManga = manga.toDomainManga() ?: return
val migrateTracks =
MigrationFlags.hasTracks(
flags,
)
val migrateCustomCover =
MigrationFlags.hasCustomCover(
flags,
)
try { try {
syncChaptersWithSource(sourceChapters, manga, source) syncChaptersWithSource.await(sourceChapters, domainManga, source)
} catch (e: Exception) { } catch (e: Exception) {
// Worst case, chapters won't be synced // Worst case, chapters won't be synced
} }
db.inTransaction { // Update chapters read, bookmark and dateFetch
// Update chapters read if (migrateChapters) {
if (migrateChapters) { val prevMangaChapters = getChapterByMangaId.await(prevDomainManga.id)
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking() val mangaChapters = getChapterByMangaId.await(domainManga.id)
val maxChapterRead = prevMangaChapters
.filter { it.read } val maxChapterRead = prevMangaChapters
.maxOfOrNull { it.chapter_number } ?: 0f .filter { it.read }
val dbChapters = db.getChapters(manga).executeAsBlocking() .maxOfOrNull { it.chapterNumber }
for (chapter in dbChapters) {
if (chapter.isRecognizedNumber) { val updatedMangaChapters = mangaChapters.map { mangaChapter ->
val prevChapter = prevMangaChapters var updatedChapter = mangaChapter
.find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number } if (updatedChapter.isRecognizedNumber) {
if (prevChapter != null) { val prevChapter = prevMangaChapters
chapter.date_fetch = prevChapter.date_fetch .find { it.isRecognizedNumber && it.chapterNumber == updatedChapter.chapterNumber }
chapter.bookmark = prevChapter.bookmark
} if (prevChapter != null) {
if (chapter.chapter_number <= maxChapterRead) { updatedChapter = updatedChapter.copy(
chapter.read = true dateFetch = prevChapter.dateFetch,
} bookmark = prevChapter.bookmark,
)
}
if (maxChapterRead != null && updatedChapter.chapterNumber <= maxChapterRead) {
updatedChapter = updatedChapter.copy(read = true)
} }
} }
db.insertChapters(dbChapters).executeAsBlocking()
updatedChapter
} }
// Update categories val chapterUpdates = updatedMangaChapters.map { it.toChapterUpdate() }
if (migrateCategories) { updateChapter.awaitAll(chapterUpdates)
val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
val mangaCategories = categories.map { MangaCategory.create(manga, it) }
db.setMangaCategories(mangaCategories, listOf(manga))
}
// Update track
if (migrateTracks) {
val tracksToUpdate = db.getTracks(prevManga.id).executeAsBlocking().mapNotNull { track ->
track.id = null
track.manga_id = manga.id!!
val service = enhancedServices
.firstOrNull { it.isTrackFrom(track, prevManga, prevSource) }
if (service != null) service.migrateTrack(track, manga, source)
else track
}
db.insertTracks(tracksToUpdate).executeAsBlocking()
}
// Update favorite status
if (replace) {
prevManga.favorite = false
db.updateMangaFavorite(prevManga).executeAsBlocking()
}
manga.favorite = true
// Update reading preferences
manga.chapter_flags = prevManga.chapter_flags
manga.viewer_flags = prevManga.viewer_flags
// Update date added
if (replace) {
manga.date_added = prevManga.date_added
prevManga.date_added = 0
} else {
manga.date_added = Date().time
}
// Update custom cover
if (migrateCustomCover) {
coverCache.setCustomCoverToCache(manga, coverCache.getCustomCoverFile(prevManga.id).inputStream())
}
// SearchPresenter#networkToLocalManga may have updated the manga title,
// so ensure db gets updated title too
db.insertManga(manga).executeAsBlocking()
} }
// Update categories
if (migrateCategories) {
val categoryIds = getCategories.await(prevDomainManga.id).map { it.id }
moveMangaToCategories.await(domainManga.id, categoryIds)
}
// Update track
if (migrateTracks) {
val tracks = getTracks.await(prevDomainManga.id).mapNotNull { track ->
val updatedTrack = track.copy(mangaId = domainManga.id)
val service = enhancedServices
.firstOrNull { it.isTrackFrom(updatedTrack, prevDomainManga, prevSource) }
if (service != null) service.migrateTrack(updatedTrack, domainManga, source)
else track
}
insertTrack.awaitAll(tracks)
}
if (replace) {
updateManga.await(MangaUpdate(prevDomainManga.id, favorite = false, dateAdded = 0))
}
// Update custom cover (recheck if custom cover exists)
if (migrateCustomCover && prevDomainManga.hasCustomCover()) {
@Suppress("BlockingMethodInNonBlockingContext")
coverCache.setCustomCoverToCache(domainManga.toDbManga(), coverCache.getCustomCoverFile(prevDomainManga.id).inputStream())
}
updateManga.await(
MangaUpdate(
id = domainManga.id,
favorite = true,
chapterFlags = prevDomainManga.chapterFlags,
viewerFlags = prevDomainManga.viewerFlags,
dateAdded = if (replace) prevDomainManga.dateAdded else Date().time,
),
)
} }
} }

View File

@ -3,10 +3,12 @@ package eu.kanade.tachiyomi.ui.manga
import android.os.Bundle import android.os.Bundle
import com.jakewharton.rxrelay.PublishRelay import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga 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.toDbManga
import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
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
@ -14,6 +16,7 @@ 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.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
@ -23,7 +26,6 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.toSChapter import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
@ -34,7 +36,6 @@ import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
@ -64,9 +65,10 @@ class MangaPresenter(
private val trackManager: TrackManager = Injekt.get(), private val trackManager: TrackManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(), private val coverCache: CoverCache = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getMangaWithChapters: GetMangaWithChapters = Injekt.get(),
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
) : BasePresenter<MangaController>() { ) : BasePresenter<MangaController>() {
/** /**
@ -118,7 +120,6 @@ class MangaPresenter(
} }
// Manga info - start // Manga info - start
getMangaObservable() getMangaObservable()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache({ view, manga -> view.onNextMangaInfo(manga, source) }) .subscribeLatestCache({ view, manga -> view.onNextMangaInfo(manga, source) })
@ -147,9 +148,9 @@ class MangaPresenter(
// Keeps subscribed to changes and sends the list of chapters to the relay. // Keeps subscribed to changes and sends the list of chapters to the relay.
presenterScope.launchIO { presenterScope.launchIO {
manga.id?.let { mangaId -> manga.id?.let { mangaId ->
getChapterByMangaId.subscribe(mangaId) getMangaWithChapters.subscribe(mangaId)
.collectLatest { domainChapters -> .collectLatest { (_, chapters) ->
val chapterItems = domainChapters.map { it.toDbChapter().toModel() } val chapterItems = chapters.map { it.toDbChapter().toModel() }
setDownloadedChapters(chapterItems) setDownloadedChapters(chapterItems)
this@MangaPresenter.allChapters = chapterItems this@MangaPresenter.allChapters = chapterItems
observeDownloads() observeDownloads()
@ -168,7 +169,6 @@ class MangaPresenter(
} }
// Manga info - start // Manga info - start
private fun getMangaObservable(): Observable<Manga> { private fun getMangaObservable(): Observable<Manga> {
return db.getManga(manga.url, manga.source).asRxObservable() return db.getManga(manga.url, manga.source).asRxObservable()
} }
@ -193,16 +193,11 @@ class MangaPresenter(
if (fetchMangaJob?.isActive == true) return if (fetchMangaJob?.isActive == true) return
fetchMangaJob = presenterScope.launchIO { fetchMangaJob = presenterScope.launchIO {
try { try {
val networkManga = source.getMangaDetails(manga.toMangaInfo()) manga.toDomainManga()?.let { domainManga ->
val sManga = networkManga.toSManga() val networkManga = source.getMangaDetails(domainManga.toMangaInfo())
manga.prepUpdateCover(coverCache, sManga, manualFetch)
manga.copyFrom(sManga) updateManga.awaitUpdateFromSource(domainManga, networkManga, manualFetch, coverCache)
if (!manga.favorite) {
// if the manga isn't a favorite, set its title from source and update in db
manga.title = sManga.title
} }
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
withUIContext { view?.onFetchMangaInfoDone() } withUIContext { view?.onFetchMangaInfoDone() }
} catch (e: Throwable) { } catch (e: Throwable) {