Tracker improvements

This commit is contained in:
Koitharu
2024-04-17 08:53:41 +03:00
parent 846c346a86
commit 7ec2e0c5cc
5 changed files with 73 additions and 30 deletions

View File

@@ -10,6 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
import okio.IOException
import org.koitharu.kotatsu.core.model.isLocal
@@ -17,6 +18,7 @@ import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.util.ext.peek
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.sanitize
import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.explore.domain.RecoverMangaUseCase
@@ -25,7 +27,9 @@ import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.recoverNotNull
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.tracker.domain.Tracker
import javax.inject.Inject
import javax.inject.Provider
class DetailsLoadUseCase @Inject constructor(
private val mangaDataRepository: MangaDataRepository,
@@ -33,6 +37,7 @@ class DetailsLoadUseCase @Inject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
private val recoverUseCase: RecoverMangaUseCase,
private val imageGetter: Html.ImageGetter,
private val trackerProvider: Provider<Tracker>,
) {
operator fun invoke(intent: MangaIntent): Flow<MangaDetails> = channelFlow {
@@ -49,6 +54,7 @@ class DetailsLoadUseCase @Inject constructor(
send(MangaDetails(manga, null, null, false))
try {
val details = getDetails(manga)
launch { updateTracker(manga) }
send(MangaDetails(details, local?.peek(), details.description?.parseAsHtml(withImages = false), false))
send(MangaDetails(details, local?.await(), details.description?.parseAsHtml(withImages = true), true))
} catch (e: IOException) {
@@ -90,4 +96,10 @@ class DetailsLoadUseCase @Inject constructor(
}
return spannable
}
private suspend fun updateTracker(details: Manga) = runCatchingCancellable {
trackerProvider.get().syncWithDetails(details)
}.onFailure { e ->
e.printStackTraceDebug()
}
}

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.history.data
import androidx.room.withTransaction
import dagger.Lazy
import dagger.Reusable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -26,7 +27,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
import org.koitharu.kotatsu.scrobbling.common.domain.tryScrobble
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.domain.Tracker
import javax.inject.Inject
const val PROGRESS_NONE = -1f
@@ -34,10 +35,10 @@ const val PROGRESS_NONE = -1f
@Reusable
class HistoryRepository @Inject constructor(
private val db: MangaDatabase,
private val trackingRepository: TrackingRepository,
private val settings: AppSettings,
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
private val mangaRepository: MangaDataRepository,
private val trackerLazy: Lazy<Tracker>,
) {
suspend fun getList(offset: Int, limit: Int): List<Manga> {
@@ -114,7 +115,7 @@ class HistoryRepository @Inject constructor(
deletedAt = 0L,
),
)
trackingRepository.syncWithHistory(manga, chapterId)
trackerLazy.get().syncWithHistory(manga, chapterId)
scrobblers.forEach { it.tryScrobble(manga, chapterId) }
}
}

View File

@@ -33,6 +33,7 @@ class TrackEntity(
const val RESULT_HAS_UPDATE = 1
const val RESULT_NO_UPDATE = 2
const val RESULT_FAILED = 3
const val RESULT_EXTERNAL_MODIFICATION = 4
fun create(mangaId: Long) = TrackEntity(
mangaId = mangaId,

View File

@@ -2,11 +2,13 @@ package org.koitharu.kotatsu.tracker.domain
import androidx.annotation.VisibleForTesting
import coil.request.CachePolicy
import dagger.Reusable
import org.koitharu.kotatsu.core.model.getPreferredBranch
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.CompositeMutex2
import org.koitharu.kotatsu.core.util.ext.toInstantOrNull
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@@ -14,10 +16,12 @@ import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
import org.koitharu.kotatsu.tracker.work.TrackingItem
import java.time.Instant
import javax.inject.Inject
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@Reusable
class Tracker @Inject constructor(
private val settings: AppSettings,
private val repository: TrackingRepository,
@@ -32,7 +36,7 @@ class Tracker @Inject constructor(
val categoryId = repository.getCategoryId(it.manga.id)
TrackingItem(
tracking = it,
channelId = if (categoryId == 0L) {
channelId = if (categoryId == NO_ID) {
channels.getHistoryChannelId()
} else {
channels.getFavouritesChannelId(categoryId)
@@ -66,6 +70,34 @@ class Tracker @Inject constructor(
return updates
}
suspend fun syncWithDetails(details: Manga) {
requireNotNull(details.chapters)
val track = repository.getTrackOrNull(details) ?: return
val updates = compare(track, details, getBranch(details))
repository.saveUpdates(updates)
}
suspend fun syncWithHistory(details: Manga, chapterId: Long) {
val chapters = requireNotNull(details.chapters)
val track = repository.getTrackOrNull(details) ?: return
val chapterIndex = chapters.indexOfFirst { x -> x.id == chapterId }
val lastNewChapterIndex = chapters.size - track.newChapters
val lastChapter = chapters.lastOrNull()
val tracking = MangaTracking(
manga = details,
lastChapterId = lastChapter?.id ?: NO_ID,
lastCheck = Instant.now(),
lastChapterDate = lastChapter?.uploadDate?.toInstantOrNull() ?: track.lastChapterDate,
newChapters = when {
track.newChapters == 0 -> 0
chapterIndex < 0 -> track.newChapters
chapterIndex >= lastNewChapterIndex -> chapters.lastIndex - chapterIndex
else -> track.newChapters
},
)
repository.mergeWith(tracking)
}
@VisibleForTesting
suspend fun checkUpdates(manga: Manga, commit: Boolean): MangaUpdates.Success {
val track = repository.getTrack(manga)
@@ -118,6 +150,7 @@ class Tracker @Inject constructor(
private companion object {
const val NO_ID = 0L
private val mangaMutex = CompositeMutex2<Long>()
suspend inline fun <T> withMangaLock(id: Long, action: () -> T): T {

View File

@@ -31,10 +31,6 @@ import javax.inject.Inject
import javax.inject.Provider
private const val NO_ID = 0L
@Deprecated("Use buckets")
private const val MAX_QUERY_IDS = 100
private const val MAX_BUCKET_SIZE = 20
private const val MAX_LOG_SIZE = 120
@Reusable
@@ -99,13 +95,23 @@ class TrackingRepository @Inject constructor(
@VisibleForTesting
suspend fun getTrack(manga: Manga): MangaTracking {
val track = db.getTracksDao().find(manga.id)
return getTrackOrNull(manga) ?: MangaTracking(
manga = manga,
lastChapterId = NO_ID,
lastCheck = null,
lastChapterDate = null,
newChapters = 0,
)
}
suspend fun getTrackOrNull(manga: Manga): MangaTracking? {
val track = db.getTracksDao().find(manga.id) ?: return null
return MangaTracking(
manga = manga,
lastChapterId = track?.lastChapterId ?: NO_ID,
lastCheck = track?.lastCheckTime?.toInstantOrNull(),
lastChapterDate = track?.lastChapterDate?.toInstantOrNull(),
newChapters = track?.newChapters ?: 0,
lastChapterId = track.lastChapterId,
lastCheck = track.lastCheckTime.toInstantOrNull(),
lastChapterDate = track.lastChapterDate.toInstantOrNull(),
newChapters = track.newChapters,
)
}
@@ -168,24 +174,14 @@ class TrackingRepository @Inject constructor(
}
}
suspend fun syncWithHistory(manga: Manga, chapterId: Long) {
val chapters = manga.chapters ?: return
val chapterIndex = chapters.indexOfFirst { x -> x.id == chapterId }
val track = getOrCreateTrack(manga.id)
val lastNewChapterIndex = chapters.size - track.newChapters
val lastChapterId = chapters.lastOrNull()?.id ?: NO_ID
suspend fun mergeWith(tracking: MangaTracking) {
val entity = TrackEntity(
mangaId = manga.id,
lastChapterId = lastChapterId,
newChapters = when {
track.newChapters == 0 -> 0
chapterIndex < 0 -> track.newChapters
chapterIndex >= lastNewChapterIndex -> chapters.lastIndex - chapterIndex
else -> track.newChapters
},
lastCheckTime = System.currentTimeMillis(),
lastChapterDate = maxOf(track.lastChapterDate, chapters.lastOrNull()?.uploadDate ?: 0L),
lastResult = track.lastResult,
mangaId = tracking.manga.id,
lastChapterId = tracking.lastChapterId,
newChapters = tracking.newChapters,
lastCheckTime = tracking.lastCheck?.toEpochMilli() ?: 0L,
lastChapterDate = tracking.lastChapterDate?.toEpochMilli() ?: 0L,
lastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,
)
db.getTracksDao().upsert(entity)
}