diff --git a/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt index f7a44ad54..44b78cd6d 100644 --- a/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt @@ -48,6 +48,12 @@ class CategoryRepositoryImpl( } } + override suspend fun getCategoriesForManga(mangaId: Long): List { + return handler.awaitList { + categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper) + } + } + override suspend fun checkDuplicateName(name: String): Boolean { return handler .awaitList { categoriesQueries.getCategories() } diff --git a/app/src/main/java/eu/kanade/data/chapter/ChapterRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/chapter/ChapterRepositoryImpl.kt index 703ae5784..cfa52dc56 100644 --- a/app/src/main/java/eu/kanade/data/chapter/ChapterRepositoryImpl.kt +++ b/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) { + 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) { - 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) } } diff --git a/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt index ddd592773..140c51af4 100644 --- a/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt @@ -42,6 +42,15 @@ class MangaRepositoryImpl( } } + override suspend fun moveMangaToCategories(mangaId: Long, categoryIds: List) { + 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 { diff --git a/app/src/main/java/eu/kanade/data/track/TrackMapper.kt b/app/src/main/java/eu/kanade/data/track/TrackMapper.kt new file mode 100644 index 000000000..d6f4bd181 --- /dev/null +++ b/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, + ) + } diff --git a/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt new file mode 100644 index 000000000..eed6f5f77 --- /dev/null +++ b/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 { + return handler.awaitList { + manga_syncQueries.getTracksByMangaId(mangaId, trackMapper) + } + } + + override suspend fun insertAll(tracks: List) { + 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, + ) + } + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index 8c329b1f7..8d061eb1c 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/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 { 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 { TrackRepositoryImpl(get()) } + addFactory { GetTracks(get()) } + addFactory { InsertTrack(get()) } addSingletonFactory { ChapterRepositoryImpl(get()) } addFactory { GetChapterByMangaId(get()) } diff --git a/app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt b/app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt index 6b0d5400e..ab1f5f240 100644 --- a/app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt +++ b/app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt @@ -11,4 +11,8 @@ class GetCategories( fun subscribe(): Flow> { return categoryRepository.getAll() } + + suspend fun await(mangaId: Long): List { + return categoryRepository.getCategoriesForManga(mangaId) + } } diff --git a/app/src/main/java/eu/kanade/domain/category/interactor/MoveMangaToCategories.kt b/app/src/main/java/eu/kanade/domain/category/interactor/MoveMangaToCategories.kt new file mode 100644 index 000000000..567ec133b --- /dev/null +++ b/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) { + try { + mangaRepository.moveMangaToCategories(mangaId, categoryIds) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt b/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt index 7d2256bf8..231cd81f8 100644 --- a/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt +++ b/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 + suspend fun checkDuplicateName(name: String): Boolean } diff --git a/app/src/main/java/eu/kanade/domain/chapter/interactor/GetChapterByMangaId.kt b/app/src/main/java/eu/kanade/domain/chapter/interactor/GetChapterByMangaId.kt index 3582a5676..b695388e7 100644 --- a/app/src/main/java/eu/kanade/domain/chapter/interactor/GetChapterByMangaId.kt +++ b/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> { - return try { - chapterRepository.getChapterByMangaIdAsFlow(mangaId) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - flowOf(emptyList()) - } - } } diff --git a/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithSource.kt b/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithSource.kt index 858d26584..23a1d8185 100644 --- a/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithSource.kt +++ b/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 diff --git a/app/src/main/java/eu/kanade/domain/chapter/interactor/UpdateChapter.kt b/app/src/main/java/eu/kanade/domain/chapter/interactor/UpdateChapter.kt index 2d9d0d052..5dfa7a822 100644 --- a/app/src/main/java/eu/kanade/domain/chapter/interactor/UpdateChapter.kt +++ b/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) { + try { + chapterRepository.updateAll(chapterUpdates) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + } } } diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/GetMangaWithChapters.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/GetMangaWithChapters.kt new file mode 100644 index 000000000..1c6426470 --- /dev/null +++ b/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>> { + return combine( + mangaRepository.subscribeMangaById(id), + chapterRepository.getChapterByMangaIdAsFlow(id), + ) { manga, chapters -> + Pair(manga, chapters) + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt index f52c4bfeb..b18d1f222 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt +++ b/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, diff --git a/app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt b/app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt index e0ec74b0b..b72e9bba0 100644 --- a/app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt +++ b/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) + suspend fun update(update: MangaUpdate): Boolean } diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt b/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt new file mode 100644 index 000000000..ad004631d --- /dev/null +++ b/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 { + return try { + trackRepository.getTracksByMangaId(mangaId) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + emptyList() + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/InsertTrack.kt b/app/src/main/java/eu/kanade/domain/track/interactor/InsertTrack.kt new file mode 100644 index 000000000..27085221a --- /dev/null +++ b/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) { + try { + trackRepository.insertAll(tracks) + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/track/model/Track.kt b/app/src/main/java/eu/kanade/domain/track/model/Track.kt new file mode 100644 index 000000000..cc92fb4ee --- /dev/null +++ b/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, + ) + } +} diff --git a/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt b/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt new file mode 100644 index 000000000..9a3b0ccbf --- /dev/null +++ b/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 + + suspend fun insertAll(tracks: List) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/EnhancedTrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/EnhancedTrackService.kt index 08060f3da..4c47ba67c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/EnhancedTrackService.kt +++ b/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? } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt index b9cb86518..409865d81 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt +++ b/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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt index 9b6b87330..2095d5a41 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt +++ b/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>() @@ -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() + + updatedChapter } - // Update categories - if (migrateCategories) { - 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() + val chapterUpdates = updatedMangaChapters.map { it.toChapterUpdate() } + updateChapter.awaitAll(chapterUpdates) } + + // 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, + ), + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 5768f6846..f819d87c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/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() { /** @@ -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) this@MangaPresenter.allChapters = chapterItems observeDownloads() @@ -168,7 +169,6 @@ class MangaPresenter( } // Manga info - start - private fun getMangaObservable(): Observable { 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) {