package mihon.domain.migration.usecases import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.hasCustomCover import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.track.EnhancedTracker import eu.kanade.tachiyomi.data.track.TrackerManager import kotlinx.coroutines.CancellationException import mihon.domain.migration.models.MigrationFlag import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.SetMangaCategories import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.interactor.UpdateChapter import tachiyomi.domain.chapter.model.toChapterUpdate import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaUpdate import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.track.interactor.GetTracks import tachiyomi.domain.track.interactor.InsertTrack import java.time.Instant class MigrateMangaUseCase( private val sourcePreferences: SourcePreferences, private val trackerManager: TrackerManager, private val sourceManager: SourceManager, private val downloadManager: DownloadManager, private val updateManga: UpdateManga, private val getChaptersByMangaId: GetChaptersByMangaId, private val syncChaptersWithSource: SyncChaptersWithSource, private val updateChapter: UpdateChapter, private val getCategories: GetCategories, private val setMangaCategories: SetMangaCategories, private val getTracks: GetTracks, private val insertTrack: InsertTrack, private val coverCache: CoverCache, ) { private val enhancedServices by lazy { trackerManager.trackers.filterIsInstance() } suspend operator fun invoke(current: Manga, target: Manga, replace: Boolean) { val targetSource = sourceManager.get(target.source) ?: return val currentSource = sourceManager.get(current.source) val flags = sourcePreferences.migrationFlags().get() try { val chapters = targetSource.getChapterList(target.toSManga()) try { syncChaptersWithSource.await(chapters, target, targetSource) } catch (_: Exception) { // Worst case, chapters won't be synced } // Update chapters read, bookmark and dateFetch if (MigrationFlag.CHAPTER in flags) { val prevMangaChapters = getChaptersByMangaId.await(current.id) val mangaChapters = getChaptersByMangaId.await(target.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) } } updatedChapter } val chapterUpdates = updatedMangaChapters.map { it.toChapterUpdate() } updateChapter.awaitAll(chapterUpdates) } // Update categories if (MigrationFlag.CATEGORY in flags) { val categoryIds = getCategories.await(current.id).map { it.id } setMangaCategories.await(target.id, categoryIds) } // Update track getTracks.await(current.id).mapNotNull { track -> val updatedTrack = track.copy(mangaId = target.id) val service = enhancedServices .firstOrNull { it.isTrackFrom(updatedTrack, current, currentSource) } if (service != null) { service.migrateTrack(updatedTrack, target, targetSource) } else { updatedTrack } } .takeIf { it.isNotEmpty() } ?.let { insertTrack.awaitAll(it) } // Delete downloaded if (MigrationFlag.REMOVE_DOWNLOAD in flags && currentSource != null) { downloadManager.deleteManga(current, currentSource) } // Update custom cover (recheck if custom cover exists) if (MigrationFlag.CUSTOM_COVER in flags && current.hasCustomCover()) { coverCache.setCustomCoverToCache(target, coverCache.getCustomCoverFile(current.id).inputStream()) } val currentMangaUpdate = MangaUpdate( id = current.id, favorite = false, dateAdded = 0, ) .takeIf { replace } val targetMangaUpdate = MangaUpdate( id = target.id, favorite = true, chapterFlags = current.chapterFlags, viewerFlags = current.viewerFlags, dateAdded = if (replace) current.dateAdded else Instant.now().toEpochMilli(), notes = if (MigrationFlag.NOTES in flags) current.notes else null, ) updateManga.awaitAll(listOfNotNull(currentMangaUpdate, targetMangaUpdate)) } catch (e: Throwable) { if (e is CancellationException) { throw e } } } }