Compare commits

..

No commits in common. "master" and "v2.2.8" have entirely different histories.

14 changed files with 57 additions and 336 deletions

View File

@ -14,6 +14,11 @@ jobs:
sed -i -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" gradle.properties sed -i -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" gradle.properties
- name: prebuild - name: prebuild
run: ./gradlew clean build run: ./gradlew clean build
- name: Publish to Gitea
continue-on-error: true
run: ./gradlew publishAllPublicationsToGiteaRepository
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
- name: Publish package - name: Publish package
continue-on-error: true continue-on-error: true
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
.idea .idea
.kotlin
out/* out/*
*.iml *.iml
target target

View File

@ -1,45 +1,5 @@
# Changelog # Changelog
## 2.6.0
* Fix of `doOnceTz` behaviour: now it will use local time as utc time to get next waking up time
## 2.5.1
* Add cleaning up of incoming template, which must remove all malformed parts from string template (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126))
* Add support of insufficient amount of arguments (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126))
* `Version`:
* `Kotlin`: `2.0.21`
* `AndroidXWork`: `2.10.0`
## 2.5.0
* `Version`:
* `Kotlin`: `2.0.20`
* `Coroutines`: `1.9.0`
* `Serialization`: `1.7.3`
## 2.4.0
* `Version`:
* `Kotlin`: `2.0.10`
* `Coroutines`: `1.8.1`
* `Serialization`: `1.7.1`
* `Klock`: `5.4.0`
* `AndroidXWork`: `2.9.1`
## 2.3.0
* `doWhile` now will guarantee that it will not call `doOnce` more than once for time
## 2.2.9
* `Version`:
* `Kotlin`: `1.9.23`
* `Serialization`: `1.6.3`
* `Klock`: `5.3.2`
* Fixes in build-in schedulers
## 2.2.8 ## 2.2.8
* `Version`: * `Version`:

View File

@ -9,13 +9,13 @@ android.useAndroidX=true
android.enableJetifier=false android.enableJetifier=false
kotlin_version=2.0.21 kotlin_version=1.9.22
kotlin_coroutines_version=1.9.0 kotlin_coroutines_version=1.8.0
kotlin_serialization_version=1.7.3 kotlin_serialization_version=1.6.2
dokka_version=1.9.20 dokka_version=1.9.10
klockVersion=5.4.0 klockVersion=5.3.1
## Github reease ## Github reease
@ -23,18 +23,18 @@ github_release_plugin_version=2.5.2
## Android ## Android
android_gradle_version=8.2.2 android_gradle_version=8.1.4
android_minSdkVersion=21 android_minSdkVersion=21
android_compileSdkVersion=35 android_compileSdkVersion=34
android_buildToolsVersion=35.0.0 android_buildToolsVersion=34.0.0
dexcount_version=4.0.0 dexcount_version=4.0.0
junit_version=4.12 junit_version=4.12
test_ext_junit_version=1.1.3 test_ext_junit_version=1.1.3
espresso_core=3.4.0 espresso_core=3.4.0
androidx_work_version=2.10.0 androidx_work_version=2.9.0
## Common ## Common
version=2.6.0 version=2.2.8
android_code_version=44 android_code_version=38

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip

View File

@ -139,14 +139,10 @@ abstract class KronSchedulerWork(
} }
throw e throw e
} }
result.checkResults( when (result) {
onFailure = { is Result.Failure -> if (prolongOnFailure) prolong()
if (prolongOnFailure) prolong() is Result.Success -> if (prolongOnSuccess) prolong()
},
onSuccess = {
if (prolongOnSuccess) prolong()
} }
)
return result return result
} }
} }

View File

@ -1,15 +0,0 @@
package dev.inmo.krontab
import android.annotation.SuppressLint
import androidx.work.ListenableWorker
@SuppressLint("RestrictedApi")
internal inline fun ListenableWorker.Result.checkResults(
onFailure: () -> Unit,
onSuccess: () -> Unit
) {
when (this) {
is ListenableWorker.Result.Failure -> onFailure()
is ListenableWorker.Result.Success -> onSuccess()
}
}

View File

@ -35,8 +35,6 @@ suspend inline fun <T> KronScheduler.doOnceLocal(block: (DateTime) -> T): T = do
/** /**
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation. * Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation.
* *
* WARNING!!! This method will use local time instead of utc
*
* WARNING!!! If you want to launch it in parallel, you must do this explicitly. * WARNING!!! If you want to launch it in parallel, you must do this explicitly.
* *
* WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediatelly * WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediatelly
@ -44,7 +42,7 @@ suspend inline fun <T> KronScheduler.doOnceLocal(block: (DateTime) -> T): T = do
suspend inline fun <T> KronScheduler.doOnceTz(noinline block: suspend (DateTimeTz) -> T): T { suspend inline fun <T> KronScheduler.doOnceTz(noinline block: suspend (DateTimeTz) -> T): T {
val time = when (this) { val time = when (this) {
is KronSchedulerTz -> nextOrNowWithOffset() is KronSchedulerTz -> nextOrNowWithOffset()
else -> nextTimeZoned() ?: DateTimeTz.nowLocal() else -> nextOrNow().local
} }
delay((time - DateTimeTz.nowLocal()).millisecondsLong) delay((time - DateTimeTz.nowLocal()).millisecondsLong)
return block(time) return block(time)
@ -73,18 +71,9 @@ suspend inline fun <T> doOnceTz(
* Will execute [block] while it will return true as a result of its calculation * Will execute [block] while it will return true as a result of its calculation
*/ */
suspend inline fun KronScheduler.doWhile(block: (DateTime) -> Boolean) { suspend inline fun KronScheduler.doWhile(block: (DateTime) -> Boolean) {
var latest: DateTime? = null
do { do {
delay(1L) delay(1L)
val result = doOnce { } while (doOnce(block))
if (latest != it) {
latest = it
block(it)
} else {
null
}
}
} while (result == null || result)
} }
/** /**
* Will execute [block] while it will return true as a result of its calculation * Will execute [block] while it will return true as a result of its calculation
@ -96,18 +85,9 @@ suspend inline fun KronScheduler.doWhileLocal(block: (DateTime) -> Boolean) = do
* Will execute [block] while it will return true as a result of its calculation * Will execute [block] while it will return true as a result of its calculation
*/ */
suspend inline fun KronScheduler.doWhileTz(noinline block: suspend (DateTimeTz) -> Boolean) { suspend inline fun KronScheduler.doWhileTz(noinline block: suspend (DateTimeTz) -> Boolean) {
var latest: DateTimeTz? = null
do { do {
delay(1L) delay(1L)
val result = doOnceTz { } while (doOnceTz(block))
if (latest != it) {
latest = it
block(it)
} else {
null
}
}
} while (result == null || result)
} }
/** /**

View File

@ -1,6 +1,5 @@
package dev.inmo.krontab package dev.inmo.krontab
import dev.inmo.krontab.internal.*
import dev.inmo.krontab.internal.CronDateTimeScheduler import dev.inmo.krontab.internal.CronDateTimeScheduler
import dev.inmo.krontab.internal.CronDateTimeSchedulerTz import dev.inmo.krontab.internal.CronDateTimeSchedulerTz
import dev.inmo.krontab.internal.createKronScheduler import dev.inmo.krontab.internal.createKronScheduler
@ -95,17 +94,7 @@ value class KrontabConfig(
var dayOfWeekParsed: Array<Byte>? = null var dayOfWeekParsed: Array<Byte>? = null
var yearParsed: Array<Int>? = null var yearParsed: Array<Int>? = null
var millisecondsParsed: Array<Short>? = null var millisecondsParsed: Array<Short>? = null
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template.split(" ").also {
.split(" ")
.filter { checkIncomingPart(it) } // filter garbage from string
.let {
if (it.size < 5) { // reconstruction in case of insufficient arguments; 5 is amount of required arguments out of latest also code
it + (it.size until 5).map { "*" }
} else {
it
}
}
.also {
listOfNotNull( listOfNotNull(
it.getOrNull(5), it.getOrNull(5),
it.getOrNull(6), it.getOrNull(6),

View File

@ -34,60 +34,35 @@ val EverySecondScheduler: KronScheduler by lazy {
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one minute * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one minute
*/ */
val EveryMinuteScheduler: KronScheduler by lazy { val EveryMinuteScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { minutes { 0 every 1 } }
seconds { at(0) }
minutes { 0 every 1 }
}
} }
/** /**
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one hour * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one hour
*/ */
val EveryHourScheduler: KronScheduler by lazy { val EveryHourScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { hours { 0 every 1 } }
seconds { at(0) }
minutes { at(0) }
hours { 0 every 1 }
}
} }
/** /**
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one day * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one day
*/ */
val EveryDayOfMonthScheduler: KronScheduler by lazy { val EveryDayOfMonthScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { dayOfMonth { 0 every 1 } }
seconds { at(0) }
minutes { at(0) }
hours { at(0) }
dayOfMonth { 0 every 1 }
}
} }
/** /**
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one month * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one month
*/ */
val EveryMonthScheduler: KronScheduler by lazy { val EveryMonthScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { months { 0 every 1 } }
seconds { at(0) }
minutes { at(0) }
hours { at(0) }
dayOfMonth { at(0) }
months { 0 every 1 }
}
} }
/** /**
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one year * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one year
*/ */
val EveryYearScheduler: KronScheduler by lazy { val EveryYearScheduler: KronScheduler by lazy {
buildSchedule { buildSchedule { years { 0 every 1 } }
seconds { at(0) }
minutes { at(0) }
hours { at(0) }
dayOfMonth { at(0) }
months { at(0) }
years { 0 every 1 }
}
} }
/** /**

View File

@ -182,24 +182,15 @@ class SchedulerBuilder(
*/ */
fun build(): KronScheduler = offset ?.let { fun build(): KronScheduler = offset ?.let {
createKronSchedulerWithOffset( createKronSchedulerWithOffset(
seconds = seconds, seconds,
minutes = minutes, minutes,
hours = hours, hours,
dayOfMonth = dayOfMonth, dayOfMonth,
month = month, month,
years = year, year,
weekDays = dayOfWeek, dayOfWeek,
offset = TimezoneOffset(it.minutes), TimezoneOffset(it.minutes),
milliseconds = milliseconds ?: millisecondsArrayDefault milliseconds ?: millisecondsArrayDefault
)
} ?: createKronScheduler(
seconds = seconds,
minutes = minutes,
hours = hours,
dayOfMonth = dayOfMonth,
month = month,
years = year,
weekDays = dayOfWeek,
milliseconds = milliseconds ?: millisecondsArrayDefault
) )
} ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek, milliseconds ?: millisecondsArrayDefault)
} }

View File

@ -37,76 +37,6 @@ private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataCon
return results.map(dataConverter) return results.map(dataConverter)
} }
/**
* FSM for parsing of incoming data. If at the end of parsing it have non-null state and string is not empty, data passed check
*
* 1.
* * \\d -> 1
* * \\* -> 2
* * \\- -> 5
* * , -> 1
* * m -> 6
* * o -> 7
* * w -> 7
* 2.
* * / -> 3
* 3.
* * \\d -> 3
* * \\* -> 4
* 4.
* * , -> 1
* 5.
* * \\d -> 5
* * , -> 1
* 6.
* * s -> 7
* 7. Empty, end of parse
*/
private val checkIncomingPartTransitionsMap = listOf(
listOf(
Regex("\\d") to 0,
Regex("\\*") to 1,
Regex("-") to 4,
Regex(",") to 0,
Regex("m") to 5,
Regex("o") to 6,
Regex("w") to 6,
),
listOf(
Regex("/") to 2,
),
listOf(
Regex("\\d") to 2,
Regex("\\*") to 3,
),
listOf(
Regex(",") to 0,
),
listOf(
Regex("\\d") to 4,
Regex(",") to 0,
),
listOf(
Regex("s") to 6, // end of ms
),
listOf(), // empty state, end of parsing
)
internal fun checkIncomingPart(part: String): Boolean {
var i = 0
var state = checkIncomingPartTransitionsMap[0]
while (i < part.length) {
val char = part[i]
val nextState = state.firstNotNullOfOrNull {
it.second.takeIf { _ -> it.first.matches("$char") }
}
if (nextState == null) return false
state = checkIncomingPartTransitionsMap[nextState]
i++
}
return part.isNotEmpty()
}
internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null } internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null }
internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null } internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null }
internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() } internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() }

View File

@ -1,38 +0,0 @@
package dev.inmo.krontab.utils
import dev.inmo.krontab.*
import korlibs.time.*
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
class BuildersTest {
@Test
fun presetsWorksCorrectly() {
val data = mapOf(
EverySecondScheduler to { it: DateTime -> if (it.milliseconds > 0 ) it + 1.seconds - it.milliseconds.milliseconds else it },
EveryMinuteScheduler to { it: DateTime -> if (it.seconds > 0 || it.milliseconds > 0 ) it + 1.minutes - it.seconds.seconds - it.milliseconds.milliseconds else it },
EveryHourScheduler to { it: DateTime -> if (it.minutes > 0 || it.seconds > 0 || it.milliseconds > 0 ) it + 1.hours - it.minutes.minutes - it.seconds.seconds - it.milliseconds.milliseconds else it },
EveryDayOfMonthScheduler to { it: DateTime -> if (it.hours > 0 || it.minutes > 0 || it.seconds > 0 || it.milliseconds > 0 ) it + 1.days - it.hours.hours - it.minutes.minutes - it.seconds.seconds - it.milliseconds.milliseconds else it },
EveryMonthScheduler to { it: DateTime -> if (it.dayOfMonth > 1 || it.hours > 0 || it.minutes > 0 || it.seconds > 0 || it.milliseconds > 0 ) (it + 1.months).copy(dayOfMonth = 1, hour = 0, minute = 0, second = 0, milliseconds = 0) else it },
)
val samples = 10000
runTest {
var now = DateTime.now()
for (i in 0 until samples) {
data.forEach { (scheduler, expectCalculator) ->
val expectValue = expectCalculator(now)
val newNow = scheduler.nextOrRelative(now)
assertEquals(expectValue, newNow, "For time ${now.toStringDefault()} calculated wrong value: ${newNow.toStringDefault()} is not equal to ${expectValue.toStringDefault()}")
now = newNow
}
}
}
}
}

View File

@ -29,57 +29,6 @@ class StringParseTest {
} }
} }
@Test @Test
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithWrongAmountOfSpaces() {
val kronScheduler = buildSchedule("*/1 * * * * ")
val flow = kronScheduler.asFlowWithoutDelays()
runTest {
val mustBeCollected = 10
var collected = 0
flow.takeWhile {
collected < mustBeCollected
}.collect {
collected++
}
assertEquals(mustBeCollected, collected)
}
}
@Test
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithGarbageInTemplate() {
val kronScheduler = buildSchedule(" sdf */1 * * * oo * ")
val flow = kronScheduler.asFlowWithoutDelays()
runTest {
val mustBeCollected = 10
var collected = 0
flow.takeWhile {
collected < mustBeCollected
}.collect {
collected++
}
assertEquals(mustBeCollected, collected)
}
}
@Test
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithInsufficientArgsInTemplate() {
val kronScheduler = buildSchedule(" sdf */1 ")
val flow = kronScheduler.asFlowWithoutDelays()
runTest {
val mustBeCollected = 10
var collected = 0
flow.takeWhile {
collected < mustBeCollected
}.collect {
collected++
}
assertEquals(mustBeCollected, collected)
}
}
@Test
fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() { fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() {
val kronScheduler = buildSchedule("*/1 * * * * 500ms") val kronScheduler = buildSchedule("*/1 * * * * 500ms")