More refactoring of expected next update logic

This commit is contained in:
arkon
2023-07-30 19:11:20 -04:00
parent c9a1bd86b5
commit 81cd765543
14 changed files with 101 additions and 278 deletions

View File

@ -38,9 +38,6 @@ class LibraryPreferences(
),
)
fun leadingExpectedDays() = preferenceStore.getInt("pref_library_before_expect_key", 1)
fun followingExpectedDays() = preferenceStore.getInt("pref_library_after_expect_key", 1)
fun autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)

View File

@ -1,35 +1,34 @@
package tachiyomi.domain.manga.interactor
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.time.Instant
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
import kotlin.math.absoluteValue
const val MAX_GRACE_PERIOD = 28
const val MAX_FETCH_INTERVAL = 28
private const val FETCH_INTERVAL_GRACE_PERIOD = 1
class SetFetchInterval(
private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId,
) {
fun update(
suspend fun toMangaUpdateOrNull(
manga: Manga,
chapters: List<Chapter>,
zonedDateTime: ZonedDateTime,
fetchRange: Pair<Long, Long>,
dateTime: ZonedDateTime,
window: Pair<Long, Long>,
): MangaUpdate? {
val currentInterval = if (fetchRange.first == 0L && fetchRange.second == 0L) {
getCurrent(ZonedDateTime.now())
val currentWindow = if (window.first == 0L && window.second == 0L) {
getWindow(ZonedDateTime.now())
} else {
fetchRange
window
}
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(chapters, zonedDateTime)
val nextUpdate = calculateNextUpdate(manga, interval, zonedDateTime, currentInterval)
val chapters = getChapterByMangaId.await(manga.id)
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(chapters, dateTime)
val nextUpdate = calculateNextUpdate(manga, interval, dateTime, currentWindow)
return if (manga.nextUpdate == nextUpdate && manga.fetchInterval == interval) {
null
@ -38,20 +37,11 @@ class SetFetchInterval(
}
}
fun getCurrent(timeToCal: ZonedDateTime): Pair<Long, Long> {
// lead range and the following range depend on if updateOnlyExpectedPeriod set.
var followRange = 0
var leadRange = 0
if (LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.libraryUpdateMangaRestriction().get()) {
followRange = libraryPreferences.followingExpectedDays().get()
leadRange = libraryPreferences.leadingExpectedDays().get()
}
val startToday = timeToCal.toLocalDate().atStartOfDay(timeToCal.zone)
// revert math of (next_update + follow < now) become (next_update < now - follow)
// so (now - follow) become lower limit
val lowerRange = startToday.minusDays(followRange.toLong())
val higherRange = startToday.plusDays(leadRange.toLong())
return Pair(lowerRange.toEpochSecond() * 1000, higherRange.toEpochSecond() * 1000 - 1)
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 = lowerBound.plusDays(FETCH_INTERVAL_GRACE_PERIOD.toLong())
return Pair(lowerBound.toEpochSecond() * 1000, upperBound.toEpochSecond() * 1000 - 1)
}
internal fun calculateInterval(chapters: List<Chapter>, zonedDateTime: ZonedDateTime): Int {
@ -91,35 +81,41 @@ class SetFetchInterval(
// Default to 7 days
else -> 7
}
// Min 1, max 28 days
return interval.coerceIn(1, MAX_GRACE_PERIOD)
return interval.coerceIn(1, MAX_FETCH_INTERVAL)
}
private fun calculateNextUpdate(
manga: Manga,
interval: Int,
zonedDateTime: ZonedDateTime,
fetchRange: Pair<Long, Long>,
dateTime: ZonedDateTime,
window: Pair<Long, Long>,
): Long {
return if (
manga.nextUpdate !in fetchRange.first.rangeTo(fetchRange.second + 1) ||
manga.nextUpdate !in window.first.rangeTo(window.second + 1) ||
manga.fetchInterval == 0
) {
val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(manga.lastUpdate), zonedDateTime.zone).toLocalDate().atStartOfDay()
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, zonedDateTime).toInt()
val cycle = timeSinceLatest.floorDiv(interval.absoluteValue.takeIf { interval < 0 } ?: doubleInterval(interval, timeSinceLatest, doubleWhenOver = 10, maxValue = 28))
latestDate.plusDays((cycle + 1) * interval.toLong()).toEpochSecond(zonedDateTime.offset) * 1000
val latestDate = ZonedDateTime.ofInstant(Instant.ofEpochMilli(manga.lastUpdate), dateTime.zone)
.toLocalDate()
.atStartOfDay()
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, dateTime).toInt()
val cycle = timeSinceLatest.floorDiv(
interval.absoluteValue.takeIf { interval < 0 }
?: doubleInterval(interval, timeSinceLatest, doubleWhenOver = 10),
)
latestDate.plusDays((cycle + 1) * interval.toLong()).toEpochSecond(dateTime.offset) * 1000
} else {
manga.nextUpdate
}
}
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int, maxValue: Int): Int {
if (delta >= maxValue) return maxValue
val cycle = timeSinceLatest.floorDiv(delta) + 1
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int): Int {
if (delta >= MAX_FETCH_INTERVAL) return MAX_FETCH_INTERVAL
// double delta again if missed more than 9 check in new delta
val cycle = timeSinceLatest.floorDiv(delta) + 1
return if (cycle > doubleWhenOver) {
doubleInterval(delta * 2, timeSinceLatest, doubleWhenOver, maxValue)
doubleInterval(delta * 2, timeSinceLatest, doubleWhenOver)
} else {
delta
}

View File

@ -11,6 +11,7 @@ import java.time.ZonedDateTime
@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,
@ -19,14 +20,8 @@ class SetFetchIntervalTest {
private val setFetchInterval = SetFetchInterval(mockk())
private fun chapterAddTime(chapter: Chapter, duration: Duration): Chapter {
val newTime = testTime.plus(duration).toEpochSecond() * 1000
return chapter.copy(dateFetch = newTime, dateUpload = newTime)
}
// default 7 when less than 3 distinct day
@Test
fun `calculateInterval returns 7 when 1 chapters in 1 day`() {
fun `calculateInterval returns default of 7 days when less than 3 distinct days`() {
val chapters = mutableListOf<Chapter>()
(1..1).forEach {
val duration = Duration.ofHours(10)
@ -63,9 +58,8 @@ class SetFetchIntervalTest {
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 7
}
// Default 1 if interval less than 1
@Test
fun `calculateInterval returns 1 when 5 chapters in 75 hours, 3 days`() {
fun `calculateInterval returns default of 1 day when interval less than 1`() {
val chapters = mutableListOf<Chapter>()
(1..5).forEach {
val duration = Duration.ofHours(15L * it)
@ -98,9 +92,8 @@ class SetFetchIntervalTest {
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 2
}
// If interval is decimal, floor to closest integer
@Test
fun `calculateInterval returns 1 when 5 chapters in 125 hours, 5 days`() {
fun `calculateInterval returns floored value when interval is decimal`() {
val chapters = mutableListOf<Chapter>()
(1..5).forEach {
val duration = Duration.ofHours(25L * it)
@ -121,9 +114,8 @@ class SetFetchIntervalTest {
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
}
// Use fetch time if upload time not available
@Test
fun `calculateInterval returns 1 when 5 chapters in 125 hours, 5 days of dateFetch`() {
fun `calculateInterval returns interval based on fetch time if upload time not available`() {
val chapters = mutableListOf<Chapter>()
(1..5).forEach {
val duration = Duration.ofHours(25L * it)
@ -132,4 +124,9 @@ class SetFetchIntervalTest {
}
setFetchInterval.calculateInterval(chapters, testTime) shouldBe 1
}
private fun chapterAddTime(chapter: Chapter, duration: Duration): Chapter {
val newTime = testTime.plus(duration).toEpochSecond() * 1000
return chapter.copy(dateFetch = newTime, dateUpload = newTime)
}
}