mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-01 22:58:57 +01:00
Update Manga in Expected Period (#5734)
* Add Predict Interval Test * Get mangas next update and interval in library update * Get next update and interval in backup restore * Display and set intervals, nextUpdate in Manga Info * Move logic function to MangeScreen and InfoHeader Update per suggestion --------- Co-authored-by: arkon <arkon@users.noreply.github.com>
This commit is contained in:
@@ -13,111 +13,115 @@ import kotlin.math.absoluteValue
|
||||
|
||||
const val MAX_GRACE_PERIOD = 28
|
||||
|
||||
fun updateIntervalMeta(
|
||||
manga: Manga,
|
||||
chapters: List<Chapter>,
|
||||
zonedDateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
setCurrentFetchRange: Pair<Long, Long> = getCurrentFetchRange(zonedDateTime),
|
||||
): MangaUpdate? {
|
||||
val currentFetchRange = if (setCurrentFetchRange.first == 0L && setCurrentFetchRange.second == 0L) {
|
||||
getCurrentFetchRange(ZonedDateTime.now())
|
||||
} else {
|
||||
setCurrentFetchRange
|
||||
}
|
||||
val interval = manga.calculateInterval.takeIf { it < 0 } ?: calculateInterval(chapters, zonedDateTime)
|
||||
val nextUpdate = calculateNextUpdate(manga, interval, zonedDateTime, currentFetchRange)
|
||||
class SetMangaUpdateInterval(
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
) {
|
||||
|
||||
return if (manga.nextUpdate == nextUpdate && manga.calculateInterval == interval) {
|
||||
null
|
||||
} else { MangaUpdate(id = manga.id, nextUpdate = nextUpdate, calculateInterval = interval) }
|
||||
}
|
||||
|
||||
fun calculateInterval(chapters: List<Chapter>, zonedDateTime: ZonedDateTime): Int {
|
||||
val sortedChapters = chapters
|
||||
.sortedWith(compareByDescending<Chapter> { it.dateUpload }.thenByDescending { it.dateFetch })
|
||||
.take(50)
|
||||
|
||||
val uploadDates = sortedChapters
|
||||
.filter { it.dateUpload > 0L }
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
fun updateInterval(
|
||||
manga: Manga,
|
||||
chapters: List<Chapter>,
|
||||
zonedDateTime: ZonedDateTime,
|
||||
fetchRange: Pair<Long, Long>,
|
||||
): MangaUpdate? {
|
||||
val currentFetchRange = if (fetchRange.first == 0L && fetchRange.second == 0L) {
|
||||
getCurrentFetchRange(ZonedDateTime.now())
|
||||
} else {
|
||||
fetchRange
|
||||
}
|
||||
.distinct()
|
||||
val fetchDates = sortedChapters
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
val interval = manga.calculateInterval.takeIf { it < 0 } ?: calculateInterval(chapters, zonedDateTime)
|
||||
val nextUpdate = calculateNextUpdate(manga, interval, zonedDateTime, currentFetchRange)
|
||||
|
||||
return if (manga.nextUpdate == nextUpdate && manga.calculateInterval == interval) {
|
||||
null
|
||||
} else {
|
||||
MangaUpdate(id = manga.id, nextUpdate = nextUpdate, calculateInterval = interval)
|
||||
}
|
||||
.distinct()
|
||||
}
|
||||
|
||||
val newInterval = when {
|
||||
// Enough upload date from source
|
||||
uploadDates.size >= 3 -> {
|
||||
val uploadDelta = uploadDates.last().until(uploadDates.first(), ChronoUnit.DAYS)
|
||||
val uploadPeriod = uploadDates.indexOf(uploadDates.last())
|
||||
uploadDelta.floorDiv(uploadPeriod).toInt()
|
||||
fun getCurrentFetchRange(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()
|
||||
}
|
||||
// Enough fetch date from client
|
||||
fetchDates.size >= 3 -> {
|
||||
val fetchDelta = fetchDates.last().until(fetchDates.first(), ChronoUnit.DAYS)
|
||||
val uploadPeriod = fetchDates.indexOf(fetchDates.last())
|
||||
fetchDelta.floorDiv(uploadPeriod).toInt()
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
.filter { it.dateUpload > 0L }
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateUpload), zonedDateTime.zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
}
|
||||
.distinct()
|
||||
val fetchDates = sortedChapters
|
||||
.map {
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochMilli(it.dateFetch), zonedDateTime.zone)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
}
|
||||
.distinct()
|
||||
|
||||
val interval = when {
|
||||
// Enough upload date from source
|
||||
uploadDates.size >= 3 -> {
|
||||
val uploadDelta = uploadDates.last().until(uploadDates.first(), ChronoUnit.DAYS)
|
||||
val uploadPeriod = uploadDates.indexOf(uploadDates.last())
|
||||
uploadDelta.floorDiv(uploadPeriod).toInt()
|
||||
}
|
||||
// Enough fetch date from client
|
||||
fetchDates.size >= 3 -> {
|
||||
val fetchDelta = fetchDates.last().until(fetchDates.first(), ChronoUnit.DAYS)
|
||||
val uploadPeriod = fetchDates.indexOf(fetchDates.last())
|
||||
fetchDelta.floorDiv(uploadPeriod).toInt()
|
||||
}
|
||||
// Default to 7 days
|
||||
else -> 7
|
||||
}
|
||||
// Default to 7 days
|
||||
else -> 7
|
||||
// Min 1, max 28 days
|
||||
return interval.coerceIn(1, MAX_GRACE_PERIOD)
|
||||
}
|
||||
// Min 1, max 28 days
|
||||
return newInterval.coerceIn(1, MAX_GRACE_PERIOD)
|
||||
}
|
||||
|
||||
private fun calculateNextUpdate(
|
||||
manga: Manga,
|
||||
interval: Int,
|
||||
zonedDateTime: ZonedDateTime,
|
||||
currentFetchRange: Pair<Long, Long>,
|
||||
): Long {
|
||||
return if (manga.nextUpdate !in currentFetchRange.first.rangeTo(currentFetchRange.second + 1) ||
|
||||
manga.calculateInterval == 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
|
||||
} else {
|
||||
manga.nextUpdate
|
||||
private fun calculateNextUpdate(
|
||||
manga: Manga,
|
||||
interval: Int,
|
||||
zonedDateTime: ZonedDateTime,
|
||||
fetchRange: Pair<Long, Long>,
|
||||
): Long {
|
||||
return if (
|
||||
manga.nextUpdate !in fetchRange.first.rangeTo(fetchRange.second + 1) ||
|
||||
manga.calculateInterval == 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
|
||||
} 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
|
||||
// double delta again if missed more than 9 check in new delta
|
||||
return if (cycle > doubleWhenOver) {
|
||||
doubleInterval(delta * 2, timeSinceLatest, doubleWhenOver, maxValue)
|
||||
} else {
|
||||
delta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int, maxValue: Int): Int {
|
||||
if (delta >= maxValue) return maxValue
|
||||
val cycle = timeSinceLatest.floorDiv(delta) + 1
|
||||
// double delta again if missed more than 9 check in new delta
|
||||
return if (cycle > doubleWhenOver) {
|
||||
doubleInterval(delta * 2, timeSinceLatest, doubleWhenOver, maxValue)
|
||||
} else {
|
||||
delta
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentFetchRange(
|
||||
timeToCal: ZonedDateTime,
|
||||
): Pair<Long, Long> {
|
||||
val preferences: LibraryPreferences = Injekt.get()
|
||||
|
||||
// lead range and the following range depend on if updateOnlyExpectedPeriod set.
|
||||
var followRange = 0
|
||||
var leadRange = 0
|
||||
if (LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in preferences.libraryUpdateMangaRestriction().get()) {
|
||||
followRange = preferences.followingExpectedDays().get()
|
||||
leadRange = preferences.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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user