Transfer scrobbling information within migration #930

This commit is contained in:
Koitharu
2024-06-15 13:49:57 +03:00
parent c01d80f7da
commit a379604974
4 changed files with 164 additions and 116 deletions

View File

@@ -12,136 +12,184 @@ import org.koitharu.kotatsu.history.data.toMangaHistory
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
import org.koitharu.kotatsu.tracker.data.TrackEntity import org.koitharu.kotatsu.tracker.data.TrackEntity
import javax.inject.Inject import javax.inject.Inject
class MigrateUseCase @Inject constructor( class MigrateUseCase
private val mangaRepositoryFactory: MangaRepository.Factory, @Inject
private val mangaDataRepository: MangaDataRepository, constructor(
private val database: MangaDatabase, private val mangaRepositoryFactory: MangaRepository.Factory,
private val progressUpdateUseCase: ProgressUpdateUseCase, private val mangaDataRepository: MangaDataRepository,
) { private val database: MangaDatabase,
private val progressUpdateUseCase: ProgressUpdateUseCase,
suspend operator fun invoke(oldManga: Manga, newManga: Manga) { private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
val oldDetails = if (oldManga.chapters.isNullOrEmpty()) { ) {
runCatchingCancellable { suspend operator fun invoke(
mangaRepositoryFactory.create(oldManga.source).getDetails(oldManga) oldManga: Manga,
}.getOrDefault(oldManga) newManga: Manga,
} else { ) {
oldManga val oldDetails =
} if (oldManga.chapters.isNullOrEmpty()) {
val newDetails = if (newManga.chapters.isNullOrEmpty()) { runCatchingCancellable {
mangaRepositoryFactory.create(newManga.source).getDetails(newManga) mangaRepositoryFactory.create(oldManga.source).getDetails(oldManga)
} else { }.getOrDefault(oldManga)
newManga } else {
} oldManga
mangaDataRepository.storeManga(newDetails) }
database.withTransaction { val newDetails =
// replace favorites if (newManga.chapters.isNullOrEmpty()) {
val favoritesDao = database.getFavouritesDao() mangaRepositoryFactory.create(newManga.source).getDetails(newManga)
val oldFavourites = favoritesDao.findAllRaw(oldDetails.id) } else {
if (oldFavourites.isNotEmpty()) { newManga
favoritesDao.delete(oldManga.id) }
for (f in oldFavourites) { mangaDataRepository.storeManga(newDetails)
val e = f.copy( database.withTransaction {
mangaId = newManga.id, // replace favorites
val favoritesDao = database.getFavouritesDao()
val oldFavourites = favoritesDao.findAllRaw(oldDetails.id)
if (oldFavourites.isNotEmpty()) {
favoritesDao.delete(oldManga.id)
for (f in oldFavourites) {
val e =
f.copy(
mangaId = newManga.id,
)
favoritesDao.upsert(e)
}
}
// replace history
val historyDao = database.getHistoryDao()
val oldHistory = historyDao.find(oldDetails.id)
val newHistory =
if (oldHistory != null) {
val newHistory = makeNewHistory(oldDetails, newDetails, oldHistory)
historyDao.delete(oldDetails.id)
historyDao.upsert(newHistory)
newHistory
} else {
null
}
// track
val tracksDao = database.getTracksDao()
val oldTrack = tracksDao.find(oldDetails.id)
if (oldTrack != null) {
val lastChapter = newDetails.chapters?.lastOrNull()
val newTrack =
TrackEntity(
mangaId = newDetails.id,
lastChapterId = lastChapter?.id ?: 0L,
newChapters = 0,
lastCheckTime = System.currentTimeMillis(),
lastChapterDate = lastChapter?.uploadDate ?: 0L,
lastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,
lastError = null,
)
tracksDao.delete(oldDetails.id)
tracksDao.upsert(newTrack)
}
// scrobbling
for (scrobbler in scrobblers) {
if (!scrobbler.isEnabled) {
continue
}
val prevInfo = scrobbler.getScrobblingInfoOrNull(oldDetails.id) ?: continue
scrobbler.unregisterScrobbling(oldDetails.id)
scrobbler.linkManga(newDetails.id, prevInfo.targetId)
scrobbler.updateScrobblingInfo(
mangaId = newDetails.id,
rating = prevInfo.rating,
status =
prevInfo.status ?: when {
newHistory == null -> ScrobblingStatus.PLANNED
newHistory.percent == 1f -> ScrobblingStatus.COMPLETED
else -> ScrobblingStatus.READING
},
comment = prevInfo.comment,
) )
favoritesDao.upsert(e) if (newHistory != null) {
scrobbler.scrobble(
manga = newDetails,
chapterId = newHistory.chapterId,
)
}
} }
} }
// replace history progressUpdateUseCase(newManga)
val historyDao = database.getHistoryDao()
val oldHistory = historyDao.find(oldDetails.id)
if (oldHistory != null) {
val newHistory = makeNewHistory(oldDetails, newDetails, oldHistory)
historyDao.delete(oldDetails.id)
historyDao.upsert(newHistory)
}
// track
val tracksDao = database.getTracksDao()
val oldTrack = tracksDao.find(oldDetails.id)
if (oldTrack != null) {
val lastChapter = newDetails.chapters?.lastOrNull()
val newTrack = TrackEntity(
mangaId = newDetails.id,
lastChapterId = lastChapter?.id ?: 0L,
newChapters = 0,
lastCheckTime = System.currentTimeMillis(),
lastChapterDate = lastChapter?.uploadDate ?: 0L,
lastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,
lastError = null,
)
tracksDao.delete(oldDetails.id)
tracksDao.upsert(newTrack)
}
} }
progressUpdateUseCase(newManga)
}
private fun makeNewHistory( private fun makeNewHistory(
oldManga: Manga, oldManga: Manga,
newManga: Manga, newManga: Manga,
history: HistoryEntity, history: HistoryEntity,
): HistoryEntity { ): HistoryEntity {
if (oldManga.chapters.isNullOrEmpty()) { // probably broken manga/source if (oldManga.chapters.isNullOrEmpty()) { // probably broken manga/source
val branch = newManga.getPreferredBranch(null) val branch = newManga.getPreferredBranch(null)
val chapters = checkNotNull(newManga.getChapters(branch)) val chapters = checkNotNull(newManga.getChapters(branch))
val currentChapter = if (history.percent in 0f..1f) { val currentChapter =
chapters[(chapters.lastIndex * history.percent).toInt()] if (history.percent in 0f..1f) {
} else { chapters[(chapters.lastIndex * history.percent).toInt()]
chapters.first() } else {
chapters.first()
}
return HistoryEntity(
mangaId = newManga.id,
createdAt = history.createdAt,
updatedAt = System.currentTimeMillis(),
chapterId = currentChapter.id,
page = history.page,
scroll = history.scroll,
percent = history.percent,
deletedAt = 0,
chaptersCount = chapters.size,
)
} }
val branch = oldManga.getPreferredBranch(history.toMangaHistory())
val oldChapters = checkNotNull(oldManga.getChapters(branch))
var index = oldChapters.indexOfFirst { it.id == history.chapterId }
if (index < 0) {
index =
if (history.percent in 0f..1f) {
(oldChapters.lastIndex * history.percent).toInt()
} else {
0
}
}
val newChapters = checkNotNull(newManga.chapters).groupBy { it.branch }
val newBranch =
if (newChapters.containsKey(branch)) {
branch
} else {
newManga.getPreferredBranch(null)
}
val newChapterId =
checkNotNull(newChapters[newBranch])
.let {
val oldChapter = oldChapters[index]
it.findByNumber(oldChapter.volume, oldChapter.number) ?: it.getOrNull(index) ?: it.last()
}.id
return HistoryEntity( return HistoryEntity(
mangaId = newManga.id, mangaId = newManga.id,
createdAt = history.createdAt, createdAt = history.createdAt,
updatedAt = System.currentTimeMillis(), updatedAt = System.currentTimeMillis(),
chapterId = currentChapter.id, chapterId = newChapterId,
page = history.page, page = history.page,
scroll = history.scroll, scroll = history.scroll,
percent = history.percent, percent = PROGRESS_NONE,
deletedAt = 0, deletedAt = 0,
chaptersCount = chapters.size, chaptersCount = checkNotNull(newChapters[newBranch]).size,
) )
} }
val branch = oldManga.getPreferredBranch(history.toMangaHistory())
val oldChapters = checkNotNull(oldManga.getChapters(branch)) private fun List<MangaChapter>.findByNumber(
var index = oldChapters.indexOfFirst { it.id == history.chapterId } volume: Int,
if (index < 0) { number: Float,
index = if (history.percent in 0f..1f) { ): MangaChapter? =
(oldChapters.lastIndex * history.percent).toInt() if (number <= 0f) {
null
} else { } else {
0 firstOrNull { it.volume == volume && it.number == number }
} }
}
val newChapters = checkNotNull(newManga.chapters).groupBy { it.branch }
val newBranch = if (newChapters.containsKey(branch)) {
branch
} else {
newManga.getPreferredBranch(null)
}
val newChapterId = checkNotNull(newChapters[newBranch]).let {
val oldChapter = oldChapters[index]
it.findByNumber(oldChapter.volume, oldChapter.number) ?: it.getOrNull(index) ?: it.last()
}.id
return HistoryEntity(
mangaId = newManga.id,
createdAt = history.createdAt,
updatedAt = System.currentTimeMillis(),
chapterId = newChapterId,
page = history.page,
scroll = history.scroll,
percent = PROGRESS_NONE,
deletedAt = 0,
chaptersCount = checkNotNull(newChapters[newBranch]).size,
)
} }
private fun List<MangaChapter>.findByNumber(volume: Int, number: Float): MangaChapter? {
return if (number <= 0f) {
null
} else {
firstOrNull { it.volume == volume && it.number == number }
}
}
}

View File

@@ -162,7 +162,7 @@ class DetailsViewModel @Inject constructor(
val onMangaRemoved = MutableEventFlow<Manga>() val onMangaRemoved = MutableEventFlow<Manga>()
val isScrobblingAvailable: Boolean val isScrobblingAvailable: Boolean
get() = scrobblers.any { it.isAvailable } get() = scrobblers.any { it.isEnabled }
val scrobblingInfo: StateFlow<List<ScrobblingInfo>> = interactor.observeScrobblingInfo(mangaId) val scrobblingInfo: StateFlow<List<ScrobblingInfo>> = interactor.observeScrobblingInfo(mangaId)
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList()) .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
@@ -393,7 +393,7 @@ class DetailsViewModel @Inject constructor(
private fun getScrobbler(index: Int): Scrobbler? { private fun getScrobbler(index: Int): Scrobbler? {
val info = scrobblingInfo.value.getOrNull(index) val info = scrobblingInfo.value.getOrNull(index)
val scrobbler = if (info != null) { val scrobbler = if (info != null) {
scrobblers.find { it.scrobblerService == info.scrobbler && it.isAvailable } scrobblers.find { it.scrobblerService == info.scrobbler && it.isEnabled }
} else { } else {
null null
} }

View File

@@ -51,7 +51,7 @@ abstract class Scrobbler(
} }
} }
val isAvailable: Boolean val isEnabled: Boolean
get() = repository.isAuthorized get() = repository.isAuthorized
suspend fun authorize(authCode: String): ScrobblerUser { suspend fun authorize(authCode: String): ScrobblerUser {

View File

@@ -42,7 +42,7 @@ class ScrobblingSelectorViewModel @Inject constructor(
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga
val availableScrobblers = scrobblers.filter { it.isAvailable } val availableScrobblers = scrobblers.filter { it.isEnabled }
val selectedScrobblerIndex = MutableStateFlow(0) val selectedScrobblerIndex = MutableStateFlow(0)