Эх сурвалжийг харах

Use sqldelight in migration (#7331)

* Use sqldelight in migration

* Some more changes

Co-Authored-By: Ivan Iskandar <[email protected]>

* Review Changes

* Review changes 2

* Review Changes 3

* Review Changes 4

Co-authored-by: Ivan Iskandar <[email protected]>
AntsyLich 2 жил өмнө
parent
commit
e3b1053c03
23 өөрчлөгдсөн 368 нэмэгдсэн , 149 устгасан
  1. 6 0
      app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt
  2. 21 29
      app/src/main/java/eu/kanade/data/chapter/ChapterRepositoryImpl.kt
  3. 9 0
      app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt
  4. 22 0
      app/src/main/java/eu/kanade/data/track/TrackMapper.kt
  5. 37 0
      app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt
  6. 12 0
      app/src/main/java/eu/kanade/domain/DomainModule.kt
  7. 4 0
      app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt
  8. 18 0
      app/src/main/java/eu/kanade/domain/category/interactor/MoveMangaToCategories.kt
  9. 2 0
      app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt
  10. 0 11
      app/src/main/java/eu/kanade/domain/chapter/interactor/GetChapterByMangaId.kt
  11. 2 1
      app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithSource.kt
  12. 15 1
      app/src/main/java/eu/kanade/domain/chapter/interactor/UpdateChapter.kt
  13. 23 0
      app/src/main/java/eu/kanade/domain/manga/interactor/GetMangaWithChapters.kt
  14. 4 0
      app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt
  15. 2 0
      app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt
  16. 20 0
      app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt
  17. 19 0
      app/src/main/java/eu/kanade/domain/track/interactor/InsertTrack.kt
  18. 27 0
      app/src/main/java/eu/kanade/domain/track/model/Track.kt
  19. 10 0
      app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt
  20. 4 3
      app/src/main/java/eu/kanade/tachiyomi/data/track/EnhancedTrackService.kt
  21. 6 4
      app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt
  22. 92 82
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt
  23. 13 18
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt

+ 6 - 0
app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt

@@ -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 {
         return handler
             .awaitList { categoriesQueries.getCategories() }

+ 21 - 29
app/src/main/java/eu/kanade/data/chapter/ChapterRepositoryImpl.kt

@@ -41,8 +41,27 @@ class ChapterRepositoryImpl(
     }
 
     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(
                     chapterUpdate.mangaId,
                     chapterUpdate.url,
@@ -58,33 +77,6 @@ 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(
-                        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)
         }
     }
 

+ 9 - 0
app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt

@@ -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 {

+ 22 - 0
app/src/main/java/eu/kanade/data/track/TrackMapper.kt

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

+ 37 - 0
app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt

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

+ 12 - 0
app/src/main/java/eu/kanade/domain/DomainModule.kt

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

+ 4 - 0
app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt

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

+ 18 - 0
app/src/main/java/eu/kanade/domain/category/interactor/MoveMangaToCategories.kt

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

+ 2 - 0
app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt

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

+ 0 - 11
app/src/main/java/eu/kanade/domain/chapter/interactor/GetChapterByMangaId.kt

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

+ 2 - 1
app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithSource.kt

@@ -25,6 +25,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(),
 ) {
 
@@ -167,7 +168,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

+ 15 - 1
app/src/main/java/eu/kanade/domain/chapter/interactor/UpdateChapter.kt

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

+ 23 - 0
app/src/main/java/eu/kanade/domain/manga/interactor/GetMangaWithChapters.kt

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

+ 4 - 0
app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt

@@ -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,

+ 2 - 0
app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt

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

+ 20 - 0
app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt

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

+ 19 - 0
app/src/main/java/eu/kanade/domain/track/interactor/InsertTrack.kt

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

+ 27 - 0
app/src/main/java/eu/kanade/domain/track/model/Track.kt

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

+ 10 - 0
app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt

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

+ 4 - 3
app/src/main/java/eu/kanade/tachiyomi/data/track/EnhancedTrackService.kt

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

+ 6 - 4
app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt

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

+ 92 - 82
app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt

@@ -2,9 +2,21 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
 
 import android.os.Bundle
 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.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.track.EnhancedTrackService
 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.GlobalSearchItem
 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.launchUI
 import eu.kanade.tachiyomi.util.lang.withUIContext
@@ -31,6 +41,14 @@ import java.util.Date
 class SearchPresenter(
     initialQuery: String? = "",
     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) {
 
     private val replacingMangaRelay = BehaviorRelay.create<Pair<Boolean, Manga?>>()
@@ -94,101 +112,93 @@ class SearchPresenter(
         replace: Boolean,
     ) {
         val flags = preferences.migrateFlags().get()
-        val migrateChapters =
-            MigrationFlags.hasChapters(
-                flags,
-            )
-        val migrateCategories =
-            MigrationFlags.hasCategories(
-                flags,
-            )
-        val migrateTracks =
-            MigrationFlags.hasTracks(
-                flags,
-            )
-        val migrateCustomCover =
-            MigrationFlags.hasCustomCover(
-                flags,
-            )
+
+        val migrateChapters = MigrationFlags.hasChapters(flags)
+        val migrateCategories = MigrationFlags.hasCategories(flags)
+        val migrateTracks = MigrationFlags.hasTracks(flags)
+        val migrateCustomCover = MigrationFlags.hasCustomCover(flags)
+
+        val prevDomainManga = prevManga.toDomainManga() ?: return
+        val domainManga = manga.toDomainManga() ?: return
 
         try {
-            syncChaptersWithSource(sourceChapters, manga, source)
+            syncChaptersWithSource.await(sourceChapters, domainManga, source)
         } catch (e: Exception) {
             // Worst case, chapters won't be synced
         }
 
-        db.inTransaction {
-            // Update chapters read
-            if (migrateChapters) {
-                val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
-                val maxChapterRead = prevMangaChapters
-                    .filter { it.read }
-                    .maxOfOrNull { it.chapter_number } ?: 0f
-                val dbChapters = db.getChapters(manga).executeAsBlocking()
-                for (chapter in dbChapters) {
-                    if (chapter.isRecognizedNumber) {
-                        val prevChapter = prevMangaChapters
-                            .find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number }
-                        if (prevChapter != null) {
-                            chapter.date_fetch = prevChapter.date_fetch
-                            chapter.bookmark = prevChapter.bookmark
-                        }
-                        if (chapter.chapter_number <= maxChapterRead) {
-                            chapter.read = true
-                        }
+        // Update chapters read, bookmark and dateFetch
+        if (migrateChapters) {
+            val prevMangaChapters = getChapterByMangaId.await(prevDomainManga.id)
+            val mangaChapters = getChapterByMangaId.await(domainManga.id)
+
+            val maxChapterRead = prevMangaChapters
+                .filter { it.read }
+                .maxOfOrNull { it.chapterNumber }
+
+            val updatedMangaChapters = mangaChapters.map { mangaChapter ->
+                var updatedChapter = mangaChapter
+                if (updatedChapter.isRecognizedNumber) {
+                    val prevChapter = prevMangaChapters
+                        .find { it.isRecognizedNumber && it.chapterNumber == updatedChapter.chapterNumber }
+
+                    if (prevChapter != null) {
+                        updatedChapter = updatedChapter.copy(
+                            dateFetch = prevChapter.dateFetch,
+                            bookmark = prevChapter.bookmark,
+                        )
+                    }
+
+                    if (maxChapterRead != null && updatedChapter.chapterNumber <= maxChapterRead) {
+                        updatedChapter = updatedChapter.copy(read = true)
                     }
                 }
-                db.insertChapters(dbChapters).executeAsBlocking()
-            }
 
-            // Update categories
-            if (migrateCategories) {
-                val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
-                val mangaCategories = categories.map { MangaCategory.create(manga, it) }
-                db.setMangaCategories(mangaCategories, listOf(manga))
+                updatedChapter
             }
 
-            // Update track
-            if (migrateTracks) {
-                val tracksToUpdate = db.getTracks(prevManga.id).executeAsBlocking().mapNotNull { track ->
-                    track.id = null
-                    track.manga_id = manga.id!!
+            val chapterUpdates = updatedMangaChapters.map { it.toChapterUpdate() }
+            updateChapter.awaitAll(chapterUpdates)
+        }
 
-                    val service = enhancedServices
-                        .firstOrNull { it.isTrackFrom(track, prevManga, prevSource) }
-                    if (service != null) service.migrateTrack(track, manga, source)
-                    else track
-                }
-                db.insertTracks(tracksToUpdate).executeAsBlocking()
-            }
+        // Update categories
+        if (migrateCategories) {
+            val categoryIds = getCategories.await(prevDomainManga.id).map { it.id }
+            moveMangaToCategories.await(domainManga.id, categoryIds)
+        }
 
-            // 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 track
+        if (migrateTracks) {
+            val tracks = getTracks.await(prevDomainManga.id).mapNotNull { track ->
+                val updatedTrack = track.copy(mangaId = domainManga.id)
 
-            // Update custom cover
-            if (migrateCustomCover) {
-                coverCache.setCustomCoverToCache(manga, coverCache.getCustomCoverFile(prevManga.id).inputStream())
+                val service = enhancedServices
+                    .firstOrNull { it.isTrackFrom(updatedTrack, prevDomainManga, prevSource) }
+
+                if (service != null) service.migrateTrack(updatedTrack, domainManga, source)
+                else track
             }
+            insertTrack.awaitAll(tracks)
+        }
 
-            // SearchPresenter#networkToLocalManga may have updated the manga title,
-            // so ensure db gets updated title too
-            db.insertManga(manga).executeAsBlocking()
+        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,
+            ),
+        )
     }
 }

+ 13 - 18
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt

@@ -3,10 +3,12 @@ package eu.kanade.tachiyomi.ui.manga
 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.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.data.cache.CoverCache
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
 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.MangaCategory
 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.download.DownloadManager
 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.source.Source
 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.manga.chapter.ChapterItem
 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.lang.launchIO
 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
@@ -64,9 +65,10 @@ class MangaPresenter(
     private val trackManager: TrackManager = Injekt.get(),
     private val downloadManager: DownloadManager = 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 getCategories: GetCategories = Injekt.get(),
+    private val updateManga: UpdateManga = Injekt.get(),
 ) : BasePresenter<MangaController>() {
 
     /**
@@ -118,7 +120,6 @@ class MangaPresenter(
         }
 
         // Manga info - start
-
         getMangaObservable()
             .observeOn(AndroidSchedulers.mainThread())
             .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.
         presenterScope.launchIO {
             manga.id?.let { mangaId ->
-                getChapterByMangaId.subscribe(mangaId)
-                    .collectLatest { domainChapters ->
-                        val chapterItems = domainChapters.map { it.toDbChapter().toModel() }
+                getMangaWithChapters.subscribe(mangaId)
+                    .collectLatest { (_, chapters) ->
+                        val chapterItems = chapters.map { it.toDbChapter().toModel() }
                         setDownloadedChapters(chapterItems)
                         [email protected] = chapterItems
                         observeDownloads()
@@ -168,7 +169,6 @@ class MangaPresenter(
     }
 
     // Manga info - start
-
     private fun getMangaObservable(): Observable<Manga> {
         return db.getManga(manga.url, manga.source).asRxObservable()
     }
@@ -193,16 +193,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) {