Clean up fetch interval tests a bit
Also limit the dates we look at to most recent 10 distinct dates only. Closes #9930
This commit is contained in:
parent
e5f83d0c6e
commit
6663abebaf
8 changed files with 167 additions and 139 deletions
|
@ -50,6 +50,7 @@ import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
||||||
import tachiyomi.domain.history.interactor.RemoveHistory
|
import tachiyomi.domain.history.interactor.RemoveHistory
|
||||||
import tachiyomi.domain.history.interactor.UpsertHistory
|
import tachiyomi.domain.history.interactor.UpsertHistory
|
||||||
import tachiyomi.domain.history.repository.HistoryRepository
|
import tachiyomi.domain.history.repository.HistoryRepository
|
||||||
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||||
|
@ -57,7 +58,6 @@ import tachiyomi.domain.manga.interactor.GetManga
|
||||||
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
|
||||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||||
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
|
||||||
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||||
|
@ -102,7 +102,7 @@ class DomainModule : InjektModule {
|
||||||
addFactory { GetNextChapters(get(), get(), get()) }
|
addFactory { GetNextChapters(get(), get(), get()) }
|
||||||
addFactory { ResetViewerFlags(get()) }
|
addFactory { ResetViewerFlags(get()) }
|
||||||
addFactory { SetMangaChapterFlags(get()) }
|
addFactory { SetMangaChapterFlags(get()) }
|
||||||
addFactory { SetFetchInterval(get()) }
|
addFactory { FetchInterval(get()) }
|
||||||
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
addFactory { SetMangaDefaultChapterFlags(get(), get(), get()) }
|
||||||
addFactory { SetMangaViewerFlags(get()) }
|
addFactory { SetMangaViewerFlags(get()) }
|
||||||
addFactory { NetworkToLocalManga(get()) }
|
addFactory { NetworkToLocalManga(get()) }
|
||||||
|
|
|
@ -3,7 +3,7 @@ package eu.kanade.domain.manga.interactor
|
||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.manga.repository.MangaRepository
|
import tachiyomi.domain.manga.repository.MangaRepository
|
||||||
|
@ -15,7 +15,7 @@ import java.util.Date
|
||||||
|
|
||||||
class UpdateManga(
|
class UpdateManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
private val setFetchInterval: SetFetchInterval,
|
private val fetchInterval: FetchInterval,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
||||||
|
@ -79,9 +79,9 @@ class UpdateManga(
|
||||||
suspend fun awaitUpdateFetchInterval(
|
suspend fun awaitUpdateFetchInterval(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||||
window: Pair<Long, Long> = setFetchInterval.getWindow(dateTime),
|
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return setFetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
|
return fetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
|
||||||
?.let { mangaRepository.update(it) }
|
?.let { mangaRepository.update(it) }
|
||||||
?: false
|
?: false
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import tachiyomi.domain.manga.interactor.MAX_FETCH_INTERVAL
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -67,7 +67,7 @@ fun SetIntervalDialog(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
val size = DpSize(width = maxWidth / 2, height = 128.dp)
|
val size = DpSize(width = maxWidth / 2, height = 128.dp)
|
||||||
val items = (0..MAX_FETCH_INTERVAL).map {
|
val items = (0..FetchInterval.MAX_INTERVAL).map {
|
||||||
if (it == 0) {
|
if (it == 0) {
|
||||||
stringResource(R.string.label_default)
|
stringResource(R.string.label_default)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.chapter.repository.ChapterRepository
|
import tachiyomi.domain.chapter.repository.ChapterRepository
|
||||||
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.track.model.Track
|
import tachiyomi.domain.track.model.Track
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
@ -31,10 +31,10 @@ class BackupRestorer(
|
||||||
) {
|
) {
|
||||||
private val updateManga: UpdateManga = Injekt.get()
|
private val updateManga: UpdateManga = Injekt.get()
|
||||||
private val chapterRepository: ChapterRepository = Injekt.get()
|
private val chapterRepository: ChapterRepository = Injekt.get()
|
||||||
private val setFetchInterval: SetFetchInterval = Injekt.get()
|
private val fetchInterval: FetchInterval = Injekt.get()
|
||||||
|
|
||||||
private var now = ZonedDateTime.now()
|
private var now = ZonedDateTime.now()
|
||||||
private var currentFetchWindow = setFetchInterval.getWindow(now)
|
private var currentFetchWindow = fetchInterval.getWindow(now)
|
||||||
|
|
||||||
private var backupManager = BackupManager(context)
|
private var backupManager = BackupManager(context)
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ class BackupRestorer(
|
||||||
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
||||||
sourceMapping = backupMaps.associate { it.sourceId to it.name }
|
sourceMapping = backupMaps.associate { it.sourceId to it.name }
|
||||||
now = ZonedDateTime.now()
|
now = ZonedDateTime.now()
|
||||||
currentFetchWindow = setFetchInterval.getWindow(now)
|
currentFetchWindow = fetchInterval.getWindow(now)
|
||||||
|
|
||||||
return coroutineScope {
|
return coroutineScope {
|
||||||
// Restore individual manga
|
// Restore individual manga
|
||||||
|
|
|
@ -59,9 +59,9 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_HAS_U
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_READ
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_OUTSIDE_RELEASE_PERIOD
|
||||||
|
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
import tachiyomi.domain.manga.interactor.SetFetchInterval
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||||
import tachiyomi.domain.source.model.SourceNotInstalledException
|
import tachiyomi.domain.source.model.SourceNotInstalledException
|
||||||
|
@ -90,7 +90,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
private val getCategories: GetCategories = Injekt.get()
|
private val getCategories: GetCategories = Injekt.get()
|
||||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
|
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
|
||||||
private val refreshTracks: RefreshTracks = Injekt.get()
|
private val refreshTracks: RefreshTracks = Injekt.get()
|
||||||
private val setFetchInterval: SetFetchInterval = Injekt.get()
|
private val fetchInterval: FetchInterval = Injekt.get()
|
||||||
|
|
||||||
private val notifier = LibraryUpdateNotifier(context)
|
private val notifier = LibraryUpdateNotifier(context)
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||||
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
|
||||||
val hasDownloads = AtomicBoolean(false)
|
val hasDownloads = AtomicBoolean(false)
|
||||||
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
|
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
|
||||||
val fetchWindow = setFetchInterval.getWindow(ZonedDateTime.now())
|
val fetchWindow = fetchInterval.getWindow(ZonedDateTime.now())
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
mangaToUpdate.groupBy { it.manga.source }.values
|
mangaToUpdate.groupBy { it.manga.source }.values
|
||||||
|
|
|
@ -5,14 +5,12 @@ import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.manga.model.MangaUpdate
|
import tachiyomi.domain.manga.model.MangaUpdate
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import java.time.ZoneId
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
const val MAX_FETCH_INTERVAL = 28
|
class FetchInterval(
|
||||||
private const val FETCH_INTERVAL_GRACE_PERIOD = 1
|
|
||||||
|
|
||||||
class SetFetchInterval(
|
|
||||||
private val getChapterByMangaId: GetChapterByMangaId,
|
private val getChapterByMangaId: GetChapterByMangaId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -29,7 +27,7 @@ class SetFetchInterval(
|
||||||
val chapters = getChapterByMangaId.await(manga.id)
|
val chapters = getChapterByMangaId.await(manga.id)
|
||||||
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
||||||
chapters,
|
chapters,
|
||||||
dateTime,
|
dateTime.zone,
|
||||||
)
|
)
|
||||||
val nextUpdate = calculateNextUpdate(manga, interval, dateTime, currentWindow)
|
val nextUpdate = calculateNextUpdate(manga, interval, dateTime, currentWindow)
|
||||||
|
|
||||||
|
@ -42,33 +40,34 @@ class SetFetchInterval(
|
||||||
|
|
||||||
fun getWindow(dateTime: ZonedDateTime): Pair<Long, Long> {
|
fun getWindow(dateTime: ZonedDateTime): Pair<Long, Long> {
|
||||||
val today = dateTime.toLocalDate().atStartOfDay(dateTime.zone)
|
val today = dateTime.toLocalDate().atStartOfDay(dateTime.zone)
|
||||||
val lowerBound = today.minusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong())
|
val lowerBound = today.minusDays(GRACE_PERIOD)
|
||||||
val upperBound = today.plusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong())
|
val upperBound = today.plusDays(GRACE_PERIOD)
|
||||||
return Pair(lowerBound.toEpochSecond() * 1000, upperBound.toEpochSecond() * 1000 - 1)
|
return Pair(lowerBound.toEpochSecond() * 1000, upperBound.toEpochSecond() * 1000 - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun calculateInterval(chapters: List<Chapter>, zonedDateTime: ZonedDateTime): Int {
|
internal fun calculateInterval(chapters: List<Chapter>, zone: ZoneId): Int {
|
||||||
val sortedChapters = chapters
|
val uploadDates = chapters.asSequence()
|
||||||
.sortedWith(
|
|
||||||
compareByDescending<Chapter> { it.dateUpload }.thenByDescending { it.dateFetch },
|
|
||||||
)
|
|
||||||
.take(50)
|
|
||||||
|
|
||||||
val uploadDates = sortedChapters
|
|
||||||
.filter { it.dateUpload > 0L }
|
.filter { it.dateUpload > 0L }
|
||||||
|
.sortedByDescending { it.dateUpload }
|
||||||
.map {
|
.map {
|
||||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone)
|
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zone)
|
||||||
.toLocalDate()
|
.toLocalDate()
|
||||||
.atStartOfDay()
|
.atStartOfDay()
|
||||||
}
|
}
|
||||||
.distinct()
|
.distinct()
|
||||||
val fetchDates = sortedChapters
|
.take(10)
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
val fetchDates = chapters.asSequence()
|
||||||
|
.sortedByDescending { it.dateFetch }
|
||||||
.map {
|
.map {
|
||||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone)
|
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zone)
|
||||||
.toLocalDate()
|
.toLocalDate()
|
||||||
.atStartOfDay()
|
.atStartOfDay()
|
||||||
}
|
}
|
||||||
.distinct()
|
.distinct()
|
||||||
|
.take(10)
|
||||||
|
.toList()
|
||||||
|
|
||||||
val interval = when {
|
val interval = when {
|
||||||
// Enough upload date from source
|
// Enough upload date from source
|
||||||
|
@ -87,7 +86,7 @@ class SetFetchInterval(
|
||||||
else -> 7
|
else -> 7
|
||||||
}
|
}
|
||||||
|
|
||||||
return interval.coerceIn(1, MAX_FETCH_INTERVAL)
|
return interval.coerceIn(1, MAX_INTERVAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculateNextUpdate(
|
private fun calculateNextUpdate(
|
||||||
|
@ -118,7 +117,7 @@ class SetFetchInterval(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int): Int {
|
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int): Int {
|
||||||
if (delta >= MAX_FETCH_INTERVAL) return MAX_FETCH_INTERVAL
|
if (delta >= MAX_INTERVAL) return MAX_INTERVAL
|
||||||
|
|
||||||
// double delta again if missed more than 9 check in new delta
|
// double delta again if missed more than 9 check in new delta
|
||||||
val cycle = timeSinceLatest.floorDiv(delta) + 1
|
val cycle = timeSinceLatest.floorDiv(delta) + 1
|
||||||
|
@ -128,4 +127,10 @@ class SetFetchInterval(
|
||||||
delta
|
delta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MAX_INTERVAL = 28
|
||||||
|
|
||||||
|
private const val GRACE_PERIOD = 1L
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package tachiyomi.domain.manga.interactor
|
||||||
|
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.parallel.Execution
|
||||||
|
import org.junit.jupiter.api.parallel.ExecutionMode
|
||||||
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.days
|
||||||
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
import kotlin.time.DurationUnit
|
||||||
|
import kotlin.time.toDuration
|
||||||
|
import kotlin.time.toJavaDuration
|
||||||
|
|
||||||
|
@Execution(ExecutionMode.CONCURRENT)
|
||||||
|
class FetchIntervalTest {
|
||||||
|
|
||||||
|
private val testTime = ZonedDateTime.parse("2020-01-01T00:00:00Z")
|
||||||
|
private val testZoneId = ZoneOffset.UTC
|
||||||
|
private var chapter = Chapter.create().copy(
|
||||||
|
dateFetch = testTime.toEpochSecond() * 1000,
|
||||||
|
dateUpload = testTime.toEpochSecond() * 1000,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val fetchInterval = FetchInterval(mockk())
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns default interval of 7 days when not enough distinct days`() {
|
||||||
|
val chaptersWithUploadDate = (1..50).map {
|
||||||
|
chapterWithTime(chapter, 1.days)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chaptersWithUploadDate, testZoneId) shouldBe 7
|
||||||
|
|
||||||
|
val chaptersWithoutUploadDate = chaptersWithUploadDate.map {
|
||||||
|
it.copy(dateUpload = 0L)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chaptersWithoutUploadDate, testZoneId) shouldBe 7
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns interval based on more recent chapters`() {
|
||||||
|
val oldChapters = (1..5).map {
|
||||||
|
chapterWithTime(chapter, (it * 7).days) // Would have interval of 7 days
|
||||||
|
}
|
||||||
|
val newChapters = (1..10).map {
|
||||||
|
chapterWithTime(chapter, oldChapters.lastUploadDate() + it.days)
|
||||||
|
}
|
||||||
|
|
||||||
|
val chapters = oldChapters + newChapters
|
||||||
|
|
||||||
|
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns interval of 7 days when multiple chapters in 1 day`() {
|
||||||
|
val chapters = (1..10).map {
|
||||||
|
chapterWithTime(chapter, 10.hours)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 7
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns interval of 7 days when multiple chapters in 2 days`() {
|
||||||
|
val chapters = (1..2).map {
|
||||||
|
chapterWithTime(chapter, 1.days)
|
||||||
|
} + (1..5).map {
|
||||||
|
chapterWithTime(chapter, 2.days)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 7
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns interval of 1 day when chapters are released every 1 day`() {
|
||||||
|
val chapters = (1..20).map {
|
||||||
|
chapterWithTime(chapter, it.days)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns interval of 1 day when delta is less than 1 day`() {
|
||||||
|
val chapters = (1..20).map {
|
||||||
|
chapterWithTime(chapter, (15 * it).hours)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns interval of 2 days when chapters are released every 2 days`() {
|
||||||
|
val chapters = (1..20).map {
|
||||||
|
chapterWithTime(chapter, (2 * it).days)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns interval with floored value when interval is decimal`() {
|
||||||
|
val chaptersWithUploadDate = (1..5).map {
|
||||||
|
chapterWithTime(chapter, (25 * it).hours)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chaptersWithUploadDate, testZoneId) shouldBe 1
|
||||||
|
|
||||||
|
val chaptersWithoutUploadDate = chaptersWithUploadDate.map {
|
||||||
|
it.copy(dateUpload = 0L)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chaptersWithoutUploadDate, testZoneId) shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `returns interval of 1 day when chapters are released just below every 2 days`() {
|
||||||
|
val chapters = (1..20).map {
|
||||||
|
chapterWithTime(chapter, (43 * it).hours)
|
||||||
|
}
|
||||||
|
fetchInterval.calculateInterval(chapters, testZoneId) shouldBe 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chapterWithTime(chapter: Chapter, duration: Duration): Chapter {
|
||||||
|
val newTime = testTime.plus(duration.toJavaDuration()).toEpochSecond() * 1000
|
||||||
|
return chapter.copy(dateFetch = newTime, dateUpload = newTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<Chapter>.lastUploadDate() =
|
||||||
|
last().dateUpload.toDuration(DurationUnit.MILLISECONDS)
|
||||||
|
}
|
|
@ -1,104 +0,0 @@
|
||||||
package tachiyomi.domain.manga.interactor
|
|
||||||
|
|
||||||
import io.kotest.matchers.shouldBe
|
|
||||||
import io.mockk.mockk
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.parallel.Execution
|
|
||||||
import org.junit.jupiter.api.parallel.ExecutionMode
|
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.hours
|
|
||||||
import kotlin.time.toJavaDuration
|
|
||||||
|
|
||||||
@Execution(ExecutionMode.CONCURRENT)
|
|
||||||
class SetFetchIntervalTest {
|
|
||||||
|
|
||||||
private val testTime = ZonedDateTime.parse("2020-01-01T00:00:00Z")
|
|
||||||
private var chapter = Chapter.create().copy(
|
|
||||||
dateFetch = testTime.toEpochSecond() * 1000,
|
|
||||||
dateUpload = testTime.toEpochSecond() * 1000,
|
|
||||||
)
|
|
||||||
|
|
||||||
private val setFetchInterval = SetFetchInterval(mockk())
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calculateInterval returns default of 7 days when less than 3 distinct days`() {
|
|
||||||
val chapters = (1..2).map {
|
|
||||||
chapterWithTime(chapter, 10.hours)
|
|
||||||
}
|
|
||||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calculateInterval returns 7 when 5 chapters in 1 day`() {
|
|
||||||
val chapters = (1..5).map {
|
|
||||||
chapterWithTime(chapter, 10.hours)
|
|
||||||
}
|
|
||||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calculateInterval returns 7 when 7 chapters in 48 hours, 2 day`() {
|
|
||||||
val chapters = (1..2).map {
|
|
||||||
chapterWithTime(chapter, 24.hours)
|
|
||||||
} + (1..5).map {
|
|
||||||
chapterWithTime(chapter, 48.hours)
|
|
||||||
}
|
|
||||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calculateInterval returns default of 1 day when interval less than 1`() {
|
|
||||||
val chapters = (1..5).map {
|
|
||||||
chapterWithTime(chapter, (15 * it).hours)
|
|
||||||
}
|
|
||||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal interval calculation
|
|
||||||
@Test
|
|
||||||
fun `calculateInterval returns 1 when 5 chapters in 120 hours, 5 days`() {
|
|
||||||
val chapters = (1..5).map {
|
|
||||||
chapterWithTime(chapter, (24 * it).hours)
|
|
||||||
}
|
|
||||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calculateInterval returns 2 when 5 chapters in 240 hours, 10 days`() {
|
|
||||||
val chapters = (1..5).map {
|
|
||||||
chapterWithTime(chapter, (48 * it).hours)
|
|
||||||
}
|
|
||||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 2
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calculateInterval returns floored value when interval is decimal`() {
|
|
||||||
val chapters = (1..5).map {
|
|
||||||
chapterWithTime(chapter, (25 * it).hours)
|
|
||||||
}
|
|
||||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calculateInterval returns 1 when 5 chapters in 215 hours, 5 days`() {
|
|
||||||
val chapters = (1..5).map {
|
|
||||||
chapterWithTime(chapter, (43 * it).hours)
|
|
||||||
}
|
|
||||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calculateInterval returns interval based on fetch time if upload time not available`() {
|
|
||||||
val chapters = (1..5).map {
|
|
||||||
chapterWithTime(chapter, (25 * it).hours).copy(dateUpload = 0L)
|
|
||||||
}
|
|
||||||
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun chapterWithTime(chapter: Chapter, duration: Duration): Chapter {
|
|
||||||
val newTime = testTime.plus(duration.toJavaDuration()).toEpochSecond() * 1000
|
|
||||||
return chapter.copy(dateFetch = newTime, dateUpload = newTime)
|
|
||||||
}
|
|
||||||
}
|
|
Reference in a new issue