Refactor tracker
This commit is contained in:
@@ -1,198 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.tracker.domain
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidRule
|
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
|
||||||
import junit.framework.TestCase.*
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.koitharu.kotatsu.SampleData
|
|
||||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltAndroidTest
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class TrackerTest {
|
|
||||||
|
|
||||||
@get:Rule
|
|
||||||
var hiltRule = HiltAndroidRule(this)
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var repository: TrackingRepository
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var dataRepository: MangaDataRepository
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var tracker: Tracker
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
hiltRule.inject()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun noUpdates() = runTest {
|
|
||||||
val manga = loadManga("full.json")
|
|
||||||
tracker.deleteTrack(manga.id)
|
|
||||||
|
|
||||||
tracker.checkUpdates(manga, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(manga.id))
|
|
||||||
tracker.checkUpdates(manga, commit = true).apply {
|
|
||||||
assertTrue(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(manga.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun hasUpdates() = runTest {
|
|
||||||
val mangaFirst = loadManga("first_chapters.json")
|
|
||||||
val mangaFull = loadManga("full.json")
|
|
||||||
tracker.deleteTrack(mangaFirst.id)
|
|
||||||
|
|
||||||
tracker.checkUpdates(mangaFirst, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
tracker.checkUpdates(mangaFull, commit = true).apply {
|
|
||||||
assertTrue(isValid)
|
|
||||||
assertEquals(3, newChapters.size)
|
|
||||||
}
|
|
||||||
assertEquals(3, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
tracker.checkUpdates(mangaFull, commit = true).apply {
|
|
||||||
assertTrue(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(3, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun badIds() = runTest {
|
|
||||||
val mangaFirst = loadManga("first_chapters.json")
|
|
||||||
val mangaBad = loadManga("bad_ids.json")
|
|
||||||
tracker.deleteTrack(mangaFirst.id)
|
|
||||||
|
|
||||||
tracker.checkUpdates(mangaFirst, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
tracker.checkUpdates(mangaBad, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
tracker.checkUpdates(mangaFirst, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun badIds2() = runTest {
|
|
||||||
val mangaFirst = loadManga("first_chapters.json")
|
|
||||||
val mangaBad = loadManga("bad_ids.json")
|
|
||||||
val mangaFull = loadManga("full.json")
|
|
||||||
tracker.deleteTrack(mangaFirst.id)
|
|
||||||
|
|
||||||
tracker.checkUpdates(mangaFirst, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
tracker.checkUpdates(mangaFull, commit = true).apply {
|
|
||||||
assertTrue(isValid)
|
|
||||||
assertEquals(3, newChapters.size)
|
|
||||||
}
|
|
||||||
assertEquals(3, repository.getNewChaptersCount(mangaFull.id))
|
|
||||||
tracker.checkUpdates(mangaBad, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun fullReset() = runTest {
|
|
||||||
val mangaFull = loadManga("full.json")
|
|
||||||
val mangaFirst = loadManga("first_chapters.json")
|
|
||||||
val mangaEmpty = loadManga("empty.json")
|
|
||||||
tracker.deleteTrack(mangaFull.id)
|
|
||||||
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
|
|
||||||
tracker.checkUpdates(mangaFull, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
|
|
||||||
tracker.checkUpdates(mangaEmpty, commit = true).apply {
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
|
|
||||||
tracker.checkUpdates(mangaFirst, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
|
|
||||||
tracker.checkUpdates(mangaFull, commit = true).apply {
|
|
||||||
assertTrue(isValid)
|
|
||||||
assertEquals(3, newChapters.size)
|
|
||||||
}
|
|
||||||
assertEquals(3, repository.getNewChaptersCount(mangaFull.id))
|
|
||||||
tracker.checkUpdates(mangaEmpty, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun syncWithHistory() = runTest {
|
|
||||||
val mangaFull = loadManga("full.json")
|
|
||||||
val mangaFirst = loadManga("first_chapters.json")
|
|
||||||
tracker.deleteTrack(mangaFull.id)
|
|
||||||
|
|
||||||
tracker.checkUpdates(mangaFirst, commit = true).apply {
|
|
||||||
assertFalse(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
tracker.checkUpdates(mangaFull, commit = true).apply {
|
|
||||||
assertTrue(isValid)
|
|
||||||
assertEquals(3, newChapters.size)
|
|
||||||
}
|
|
||||||
assertEquals(3, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
|
|
||||||
var chapter = requireNotNull(mangaFull.chapters).run { get(lastIndex - 1) }
|
|
||||||
tracker.syncWithHistory(mangaFull, chapter.id)
|
|
||||||
|
|
||||||
assertEquals(1, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
|
|
||||||
chapter = requireNotNull(mangaFull.chapters).run { get(lastIndex) }
|
|
||||||
tracker.syncWithHistory(mangaFull, chapter.id)
|
|
||||||
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
|
|
||||||
tracker.checkUpdates(mangaFull, commit = true).apply {
|
|
||||||
assertTrue(isValid)
|
|
||||||
assert(newChapters.isEmpty())
|
|
||||||
}
|
|
||||||
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun loadManga(name: String): Manga {
|
|
||||||
val manga = SampleData.loadAsset("manga/$name", Manga::class)
|
|
||||||
dataRepository.storeManga(manga)
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,7 @@ import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
|||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.recoverNotNull
|
import org.koitharu.kotatsu.parsers.util.recoverNotNull
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.tracker.domain.Tracker
|
import org.koitharu.kotatsu.tracker.domain.CheckNewChaptersUseCase
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class DetailsLoadUseCase @Inject constructor(
|
|||||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
private val recoverUseCase: RecoverMangaUseCase,
|
private val recoverUseCase: RecoverMangaUseCase,
|
||||||
private val imageGetter: Html.ImageGetter,
|
private val imageGetter: Html.ImageGetter,
|
||||||
private val trackerProvider: Provider<Tracker>,
|
private val newChaptersUseCaseProvider: Provider<CheckNewChaptersUseCase>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
operator fun invoke(intent: MangaIntent): Flow<MangaDetails> = channelFlow {
|
operator fun invoke(intent: MangaIntent): Flow<MangaDetails> = channelFlow {
|
||||||
@@ -55,11 +55,32 @@ class DetailsLoadUseCase @Inject constructor(
|
|||||||
try {
|
try {
|
||||||
val details = getDetails(manga)
|
val details = getDetails(manga)
|
||||||
launch { updateTracker(details) }
|
launch { updateTracker(details) }
|
||||||
send(MangaDetails(details, local?.peek(), details.description?.parseAsHtml(withImages = false)?.trim(), false))
|
send(
|
||||||
send(MangaDetails(details, local?.await(), details.description?.parseAsHtml(withImages = true)?.trim(), true))
|
MangaDetails(
|
||||||
|
details,
|
||||||
|
local?.peek(),
|
||||||
|
details.description?.parseAsHtml(withImages = false)?.trim(),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
send(
|
||||||
|
MangaDetails(
|
||||||
|
details,
|
||||||
|
local?.await(),
|
||||||
|
details.description?.parseAsHtml(withImages = true)?.trim(),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
local?.await()?.manga?.also { localManga ->
|
local?.await()?.manga?.also { localManga ->
|
||||||
send(MangaDetails(localManga, null, localManga.description?.parseAsHtml(withImages = false)?.trim(), true))
|
send(
|
||||||
|
MangaDetails(
|
||||||
|
localManga,
|
||||||
|
null,
|
||||||
|
localManga.description?.parseAsHtml(withImages = false)?.trim(),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
)
|
||||||
} ?: close(e)
|
} ?: close(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +118,7 @@ class DetailsLoadUseCase @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateTracker(details: Manga) = runCatchingCancellable {
|
private suspend fun updateTracker(details: Manga) = runCatchingCancellable {
|
||||||
trackerProvider.get().syncWithDetails(details)
|
newChaptersUseCaseProvider.get()(details)
|
||||||
}.onFailure { e ->
|
}.onFailure { e ->
|
||||||
e.printStackTraceDebug()
|
e.printStackTraceDebug()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.history.data
|
package org.koitharu.kotatsu.history.data
|
||||||
|
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import dagger.Lazy
|
|
||||||
import dagger.Reusable
|
import dagger.Reusable
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
@@ -30,8 +29,9 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||||
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
||||||
import org.koitharu.kotatsu.scrobbling.common.domain.tryScrobble
|
import org.koitharu.kotatsu.scrobbling.common.domain.tryScrobble
|
||||||
import org.koitharu.kotatsu.tracker.domain.Tracker
|
import org.koitharu.kotatsu.tracker.domain.CheckNewChaptersUseCase
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
const val PROGRESS_NONE = -1f
|
const val PROGRESS_NONE = -1f
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class HistoryRepository @Inject constructor(
|
|||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
|
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
|
||||||
private val mangaRepository: MangaDataRepository,
|
private val mangaRepository: MangaDataRepository,
|
||||||
private val trackerLazy: Lazy<Tracker>,
|
private val newChaptersUseCaseProvider: Provider<CheckNewChaptersUseCase>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun getList(offset: Int, limit: Int): List<Manga> {
|
suspend fun getList(offset: Int, limit: Int): List<Manga> {
|
||||||
@@ -123,7 +123,7 @@ class HistoryRepository @Inject constructor(
|
|||||||
deletedAt = 0L,
|
deletedAt = 0L,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
trackerLazy.get().syncWithHistory(manga, chapterId)
|
newChaptersUseCaseProvider.get()(manga, chapterId)
|
||||||
scrobblers.forEach { it.tryScrobble(manga, chapterId) }
|
scrobblers.forEach { it.tryScrobble(manga, chapterId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,56 @@
|
|||||||
package org.koitharu.kotatsu.tracker.domain
|
package org.koitharu.kotatsu.tracker.domain
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
|
||||||
import coil.request.CachePolicy
|
import coil.request.CachePolicy
|
||||||
import dagger.Reusable
|
|
||||||
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
||||||
|
import org.koitharu.kotatsu.core.model.isLocal
|
||||||
|
import org.koitharu.kotatsu.core.parser.CachingMangaRepository
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
|
||||||
import org.koitharu.kotatsu.core.util.MultiMutex
|
import org.koitharu.kotatsu.core.util.MultiMutex
|
||||||
import org.koitharu.kotatsu.core.util.ext.toInstantOrNull
|
import org.koitharu.kotatsu.core.util.ext.toInstantOrNull
|
||||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||||
|
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
||||||
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Reusable
|
@Singleton
|
||||||
class Tracker @Inject constructor(
|
class CheckNewChaptersUseCase @Inject constructor(
|
||||||
private val repository: TrackingRepository,
|
private val repository: TrackingRepository,
|
||||||
private val historyRepository: HistoryRepository,
|
private val historyRepository: HistoryRepository,
|
||||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
|
private val localMangaRepository: LocalMangaRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val mangaMutex = MultiMutex<Long>()
|
private val mutex = MultiMutex<Long>()
|
||||||
|
|
||||||
suspend fun getTracks(limit: Int): List<MangaTracking> {
|
suspend operator fun invoke(manga: Manga): MangaUpdates = mutex.withLock(manga.id) {
|
||||||
repository.updateTracks()
|
repository.updateTracks()
|
||||||
return repository.getTracks(offset = 0, limit = limit)
|
val tracking = repository.getTrackOrNull(manga) ?: return MangaUpdates.Failure(
|
||||||
|
manga = manga,
|
||||||
|
error = null,
|
||||||
|
)
|
||||||
|
invokeImpl(tracking)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchUpdates(
|
suspend operator fun invoke(track: MangaTracking): MangaUpdates = mutex.withLock(track.manga.id) {
|
||||||
track: MangaTracking,
|
invokeImpl(track)
|
||||||
commit: Boolean
|
|
||||||
): MangaUpdates = mangaMutex.withLock(track.manga.id) {
|
|
||||||
val updates = runCatchingCancellable {
|
|
||||||
val repo = mangaRepositoryFactory.create(track.manga.source)
|
|
||||||
require(repo is ParserMangaRepository) { "Repository ${repo.javaClass.simpleName} is not supported" }
|
|
||||||
val manga = repo.getDetails(track.manga, CachePolicy.WRITE_ONLY)
|
|
||||||
compare(track, manga, getBranch(manga))
|
|
||||||
}.getOrElse { error ->
|
|
||||||
MangaUpdates.Failure(
|
|
||||||
manga = track.manga,
|
|
||||||
error = error,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (commit) {
|
|
||||||
repository.saveUpdates(updates)
|
|
||||||
}
|
|
||||||
updates
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun syncWithDetails(details: Manga) {
|
suspend operator fun invoke(manga: Manga, currentChapterId: Long) = mutex.withLock(manga.id) {
|
||||||
requireNotNull(details.chapters)
|
repository.updateTracks()
|
||||||
val track = repository.getTrackOrNull(details) ?: return
|
val details = getFullManga(manga)
|
||||||
val updates = compare(track, details, getBranch(details))
|
val chapters = details.chapters ?: return@withLock
|
||||||
repository.saveUpdates(updates)
|
val track = repository.getTrackOrNull(manga) ?: return@withLock
|
||||||
}
|
val chapterIndex = chapters.indexOfFirst { x -> x.id == currentChapterId }
|
||||||
|
|
||||||
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 lastNewChapterIndex = chapters.size - track.newChapters
|
||||||
val lastChapter = chapters.lastOrNull()
|
val lastChapter = chapters.lastOrNull()
|
||||||
val tracking = MangaTracking(
|
val tracking = MangaTracking(
|
||||||
manga = details,
|
manga = details,
|
||||||
lastChapterId = lastChapter?.id ?: NO_ID,
|
lastChapterId = lastChapter?.id ?: 0L,
|
||||||
lastCheck = Instant.now(),
|
lastCheck = Instant.now(),
|
||||||
lastChapterDate = lastChapter?.uploadDate?.toInstantOrNull() ?: track.lastChapterDate,
|
lastChapterDate = lastChapter?.uploadDate?.toInstantOrNull() ?: track.lastChapterDate,
|
||||||
newChapters = when {
|
newChapters = when {
|
||||||
@@ -79,19 +63,16 @@ class Tracker @Inject constructor(
|
|||||||
repository.mergeWith(tracking)
|
repository.mergeWith(tracking)
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
private suspend fun invokeImpl(track: MangaTracking): MangaUpdates = runCatchingCancellable {
|
||||||
suspend fun checkUpdates(manga: Manga, commit: Boolean): MangaUpdates.Success {
|
val details = getFullManga(track.manga)
|
||||||
val track = repository.getTrack(manga)
|
compare(track, details, getBranch(details))
|
||||||
val updates = compare(track, manga, getBranch(manga))
|
}.getOrElse { error ->
|
||||||
if (commit) {
|
MangaUpdates.Failure(
|
||||||
repository.saveUpdates(updates)
|
manga = track.manga,
|
||||||
}
|
error = error,
|
||||||
return updates
|
)
|
||||||
}
|
}.also { updates ->
|
||||||
|
repository.saveUpdates(updates)
|
||||||
@VisibleForTesting
|
|
||||||
suspend fun deleteTrack(mangaId: Long) = mangaMutex.withLock(mangaId) {
|
|
||||||
repository.deleteTrack(mangaId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getBranch(manga: Manga): String? {
|
private suspend fun getBranch(manga: Manga): String? {
|
||||||
@@ -99,6 +80,26 @@ class Tracker @Inject constructor(
|
|||||||
return manga.getPreferredBranch(history)
|
return manga.getPreferredBranch(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun getFullManga(manga: Manga): Manga = when {
|
||||||
|
manga.isLocal -> fetchDetails(
|
||||||
|
requireNotNull(localMangaRepository.getRemoteManga(manga)) {
|
||||||
|
"Local manga is not supported"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
manga.chapters.isNullOrEmpty() -> fetchDetails(manga)
|
||||||
|
else -> manga
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchDetails(manga: Manga): Manga {
|
||||||
|
val repo = mangaRepositoryFactory.create(manga.source)
|
||||||
|
return if (repo is CachingMangaRepository) {
|
||||||
|
repo.getDetails(manga, CachePolicy.WRITE_ONLY)
|
||||||
|
} else {
|
||||||
|
repo.getDetails(manga)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main functionality of tracker: check new chapters in [manga] comparing to the [track]
|
* The main functionality of tracker: check new chapters in [manga] comparing to the [track]
|
||||||
*/
|
*/
|
||||||
@@ -127,9 +128,4 @@ class Tracker @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
|
||||||
|
|
||||||
const val NO_ID = 0L
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.koitharu.kotatsu.tracker.domain
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetTracksUseCase @Inject constructor(
|
||||||
|
private val repository: TrackingRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(limit: Int): List<MangaTracking> {
|
||||||
|
repository.updateTracks()
|
||||||
|
return repository.getTracks(offset = 0, limit = limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,7 +85,7 @@ class TrackingRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@Deprecated("")
|
||||||
suspend fun getTrack(manga: Manga): MangaTracking {
|
suspend fun getTrack(manga: Manga): MangaTracking {
|
||||||
return getTrackOrNull(manga) ?: MangaTracking(
|
return getTrackOrNull(manga) ?: MangaTracking(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
@@ -217,7 +217,7 @@ class TrackingRepository @Inject constructor(
|
|||||||
size - ids.size
|
size - ids.size
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getOrCreateTrack(mangaId: Long): TrackEntity {
|
suspend fun getOrCreateTrack(mangaId: Long): TrackEntity {
|
||||||
return db.getTracksDao().find(mangaId) ?: TrackEntity.create(mangaId)
|
return db.getTracksDao().find(mangaId) ?: TrackEntity.create(mangaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,8 @@ sealed interface MangaUpdates {
|
|||||||
|
|
||||||
fun lastChapterDate(): Long {
|
fun lastChapterDate(): Long {
|
||||||
val lastChapter = newChapters.lastOrNull()
|
val lastChapter = newChapters.lastOrNull()
|
||||||
return if (lastChapter == null) {
|
return lastChapter?.uploadDate?.ifZero { System.currentTimeMillis() }
|
||||||
manga.chapters?.lastOrNull()?.uploadDate ?: 0L
|
?: (manga.chapters?.lastOrNull()?.uploadDate ?: 0L)
|
||||||
} else {
|
|
||||||
lastChapter.uploadDate.ifZero { System.currentTimeMillis() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
|||||||
import org.koitharu.kotatsu.parsers.util.toIntUp
|
import org.koitharu.kotatsu.parsers.util.toIntUp
|
||||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||||
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
|
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
|
||||||
import org.koitharu.kotatsu.tracker.domain.Tracker
|
import org.koitharu.kotatsu.tracker.domain.CheckNewChaptersUseCase
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.GetTracksUseCase
|
||||||
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
||||||
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
||||||
import org.koitharu.kotatsu.tracker.work.TrackerNotificationHelper.NotificationInfo
|
import org.koitharu.kotatsu.tracker.work.TrackerNotificationHelper.NotificationInfo
|
||||||
@@ -71,7 +72,8 @@ class TrackWorker @AssistedInject constructor(
|
|||||||
@Assisted workerParams: WorkerParameters,
|
@Assisted workerParams: WorkerParameters,
|
||||||
private val notificationHelper: TrackerNotificationHelper,
|
private val notificationHelper: TrackerNotificationHelper,
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
private val tracker: Tracker,
|
private val getTracksUseCase: GetTracksUseCase,
|
||||||
|
private val checkNewChaptersUseCase: CheckNewChaptersUseCase,
|
||||||
private val workManager: WorkManager,
|
private val workManager: WorkManager,
|
||||||
@TrackerLogger private val logger: FileLogger,
|
@TrackerLogger private val logger: FileLogger,
|
||||||
) : CoroutineWorker(context, workerParams) {
|
) : CoroutineWorker(context, workerParams) {
|
||||||
@@ -101,7 +103,7 @@ class TrackWorker @AssistedInject constructor(
|
|||||||
if (!settings.isTrackerEnabled) {
|
if (!settings.isTrackerEnabled) {
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
val tracks = tracker.getTracks(if (isFullRun) Int.MAX_VALUE else BATCH_SIZE)
|
val tracks = getTracksUseCase(if (isFullRun) Int.MAX_VALUE else BATCH_SIZE)
|
||||||
logger.log("Total ${tracks.size} tracks")
|
logger.log("Total ${tracks.size} tracks")
|
||||||
if (tracks.isEmpty()) {
|
if (tracks.isEmpty()) {
|
||||||
return Result.success()
|
return Result.success()
|
||||||
@@ -127,7 +129,7 @@ class TrackWorker @AssistedInject constructor(
|
|||||||
semaphore.withPermit {
|
semaphore.withPermit {
|
||||||
send(
|
send(
|
||||||
runCatchingCancellable {
|
runCatchingCancellable {
|
||||||
tracker.fetchUpdates(track, commit = true)
|
checkNewChaptersUseCase.invoke(track)
|
||||||
}.getOrElse { error ->
|
}.getOrElse { error ->
|
||||||
MangaUpdates.Failure(
|
MangaUpdates.Failure(
|
||||||
manga = track.manga,
|
manga = track.manga,
|
||||||
|
|||||||
Reference in New Issue
Block a user