Tracker improvements
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user