mirror of
https://github.com/mihonapp/mihon.git
synced 2025-10-27 20:37:57 +01:00
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:
@@ -5,14 +5,12 @@ import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
const val MAX_FETCH_INTERVAL = 28
|
||||
private const val FETCH_INTERVAL_GRACE_PERIOD = 1
|
||||
|
||||
class SetFetchInterval(
|
||||
class FetchInterval(
|
||||
private val getChapterByMangaId: GetChapterByMangaId,
|
||||
) {
|
||||
|
||||
@@ -29,7 +27,7 @@ class SetFetchInterval(
|
||||
val chapters = getChapterByMangaId.await(manga.id)
|
||||
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
|
||||
chapters,
|
||||
dateTime,
|
||||
dateTime.zone,
|
||||
)
|
||||
val nextUpdate = calculateNextUpdate(manga, interval, dateTime, currentWindow)
|
||||
|
||||
@@ -42,33 +40,34 @@ class SetFetchInterval(
|
||||
|
||||
fun getWindow(dateTime: ZonedDateTime): Pair<Long, Long> {
|
||||
val today = dateTime.toLocalDate().atStartOfDay(dateTime.zone)
|
||||
val lowerBound = today.minusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong())
|
||||
val upperBound = today.plusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong())
|
||||
val lowerBound = today.minusDays(GRACE_PERIOD)
|
||||
val upperBound = today.plusDays(GRACE_PERIOD)
|
||||
return Pair(lowerBound.toEpochSecond() * 1000, upperBound.toEpochSecond() * 1000 - 1)
|
||||
}
|
||||
|
||||
internal fun calculateInterval(chapters: List<Chapter>, zonedDateTime: ZonedDateTime): Int {
|
||||
val sortedChapters = chapters
|
||||
.sortedWith(
|
||||
compareByDescending<Chapter> { it.dateUpload }.thenByDescending { it.dateFetch },
|
||||
)
|
||||
.take(50)
|
||||
|
||||
val uploadDates = sortedChapters
|
||||
internal fun calculateInterval(chapters: List<Chapter>, zone: ZoneId): Int {
|
||||
val uploadDates = chapters.asSequence()
|
||||
.filter { it.dateUpload > 0L }
|
||||
.sortedByDescending { it.dateUpload }
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone)
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
}
|
||||
.distinct()
|
||||
val fetchDates = sortedChapters
|
||||
.take(10)
|
||||
.toList()
|
||||
|
||||
val fetchDates = chapters.asSequence()
|
||||
.sortedByDescending { it.dateFetch }
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone)
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
}
|
||||
.distinct()
|
||||
.take(10)
|
||||
.toList()
|
||||
|
||||
val interval = when {
|
||||
// Enough upload date from source
|
||||
@@ -87,7 +86,7 @@ class SetFetchInterval(
|
||||
else -> 7
|
||||
}
|
||||
|
||||
return interval.coerceIn(1, MAX_FETCH_INTERVAL)
|
||||
return interval.coerceIn(1, MAX_INTERVAL)
|
||||
}
|
||||
|
||||
private fun calculateNextUpdate(
|
||||
@@ -118,7 +117,7 @@ class SetFetchInterval(
|
||||
}
|
||||
|
||||
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
|
||||
val cycle = timeSinceLatest.floorDiv(delta) + 1
|
||||
@@ -128,4 +127,10 @@ class SetFetchInterval(
|
||||
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 New Issue
Block a user