mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-25 18:47:51 +02:00
More refactoring of expected next update logic
This commit is contained in:
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user