Compare commits

..

45 Commits
0.8.8 ... 0.9.9

Author SHA1 Message Date
b05404f828 IntersectionObserver 2022-02-24 00:26:07 +06:00
edea942874 small refactor of operatons in applyDiff 2022-02-23 23:02:31 +06:00
8a8f568b9a add applyDiff in Diff Utils and update korlibs klock version 2022-02-23 22:55:56 +06:00
4dc8d30c52 start 0.9.9 2022-02-23 22:32:05 +06:00
cb4e08e823 Merge pull request #130 from InsanusMokrassar/0.9.8
0.9.8
2022-02-19 21:44:56 +06:00
c443bf4fa0 update dependencies 2022-02-19 16:15:36 +06:00
a6982de822 start 0.9.8 2022-02-19 16:06:45 +06:00
4f1a663e75 fixes in ExposedOneToManyKeyValueRepo and includeWebsocketHandling 2022-02-19 16:06:33 +06:00
dab262d626 start 0.9.7 2022-02-19 16:06:33 +06:00
87a3f61ca6 fix in ExposedOneToManyKeyValueRepo 2022-02-07 20:43:43 +06:00
506e937a68 start 0.9.6 2022-02-07 20:39:44 +06:00
5a037c76dd Merge pull request #126 from InsanusMokrassar/0.9.5
0.9.5
2022-02-01 23:30:26 +06:00
313f622f7e Update CHANGELOG.md 2022-02-01 14:27:07 +06:00
6cba1fe1a2 Update klock 2022-02-01 14:25:24 +06:00
fd2d0e80b7 start 0.9.5 2022-02-01 14:24:12 +06:00
96ab2e8aca Merge pull request #125 from InsanusMokrassar/0.9.4
0.9.4
2022-01-18 18:58:49 +06:00
0202988cae several fixes in optionallyReverse 2022-01-17 16:11:55 +06:00
d619d59947 Either updates 2022-01-17 15:56:48 +06:00
85b3e48d18 add optionallyReverse extensions 2022-01-17 15:38:23 +06:00
7a9b7d98a1 start 0.9.4 2022-01-14 13:22:04 +06:00
b212acfcaf Merge pull request #124 from InsanusMokrassar/0.9.3
0.9.3
2022-01-14 00:46:20 +06:00
3a45e5dc70 Update CHANGELOG.md 2022-01-14 00:37:03 +06:00
73190518d5 Update gradle.properties 2022-01-14 00:35:51 +06:00
03f78180dc Merge pull request #123 from InsanusMokrassar/0.9.2
0.9.2
2022-01-13 11:22:54 +06:00
1c0b8cf842 Update CHANGELOG.md 2022-01-13 10:20:35 +06:00
a1624ea2a9 Update gradle.properties 2022-01-13 10:19:08 +06:00
23a050cf1e Merge pull request #122 from InsanusMokrassar/0.9.1
0.9.1
2022-01-08 16:12:04 +06:00
916f2f96f4 typealiases for exposed one to many 2022-01-08 14:35:28 +06:00
00cc214754 repo exposed updates 2022-01-08 14:14:44 +06:00
b2e38f72b9 start 0.9.1 2022-01-08 14:12:57 +06:00
e7107d238d Update dokka_push.yml 2021-12-30 02:55:19 +06:00
ed9ebdbd1a Merge pull request #121 from InsanusMokrassar/0.9.0
0.9.0
2021-12-30 02:52:34 +06:00
e80676d3d2 Update packages_push.yml 2021-12-29 19:43:19 +06:00
02d02fa8f2 update android tools build 2021-12-29 19:24:25 +06:00
bd783fb74f update dokka 2021-12-29 17:44:45 +06:00
50386adf70 ignore kotlin-js-store folder 2021-12-28 21:19:47 +06:00
f4ee6c2890 update exposed and adapt to new version of kotlin serialization 2021-12-28 21:17:17 +06:00
d45aef9fe5 start 0.9.0 2021-12-28 09:58:59 +06:00
a56cd3dddd Merge pull request #120 from InsanusMokrassar/0.8.9
0.8.9
2021-12-27 17:02:16 +06:00
419e7070ee more fixes to god of fixes 2021-12-27 17:02:00 +06:00
612cf40b5f small hotfix 2021-12-27 16:00:07 +06:00
8b39882e83 fixes in DefaultUpdatableStatesMachine 2021-12-27 15:57:55 +06:00
e639ae172b Fixes in uniloadMultipart 2021-12-27 15:55:05 +06:00
d0446850ae start 0.8.9 2021-12-27 15:45:44 +06:00
c48465b90b Merge pull request #119 from InsanusMokrassar/0.8.8
0.8.8
2021-12-26 22:11:04 +06:00
24 changed files with 461 additions and 71 deletions

View File

@@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 11
- name: Fix android 32.0.0 dx - name: Fix android 32.0.0 dx
continue-on-error: true continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar

View File

@@ -8,7 +8,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 11
- name: Fix android 32.0.0 dx - name: Fix android 32.0.0 dx
continue-on-error: true continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar

1
.gitignore vendored
View File

@@ -11,5 +11,6 @@ out/
secret.gradle secret.gradle
local.properties local.properties
kotlin-js-store
publishing.sh publishing.sh

View File

@@ -1,5 +1,93 @@
# Changelog # Changelog
## 0.9.9
* `Versions`:
* `Klock`: `2.5.1` -> `2.5.2`
* `Common`:
* Add new diff tool - `applyDiff`
* Implementation of `IntersectionObserver` in JS part (copypaste of [this](https://youtrack.jetbrains.com/issue/KT-43157#focus=Comments-27-4498582.0-0) comment)
## 0.9.8
* `Versions`:
* `Exposed`: `0.37.2` -> `0.37.3`
* `Klock`: `2.4.13` -> `2.5.1`
* `AppCompat`: `1.4.0` -> `1.4.1`
## 0.9.7
* `Repos`:
* `Exposed`:
* Fix in `ExposedOneToManyKeyValueRepo` - now it will not use `insertIgnore`
* `Ktor`:
* `Server`:
* `Route#includeWebsocketHandling` now will check that `WebSockets` feature and install it if not
## 0.9.6
* `Repos`:
* `Exposed`:
* Fix in `ExposedOneToManyKeyValueRepo` - now it will not use `deleteIgnoreWhere`
## 0.9.5
* `Versions`:
* `Klock`: `2.4.12` -> `2.4.13`
## 0.9.4
* `Pagination`:
* `Common`:
* Add several `optionallyReverse` functions
* `Common`:
* Changes in `Either`:
* Now `Either` uses `optionalT1` and `optionalT2` as main properties
* `Either#t1` and `Either#t2` are deprecated
* New extensions `Either#mapOnFirst` and `Either#mapOnSecond`
## 0.9.3
* `Versions`:
* `UUID`: `0.3.1` -> `0.4.0`
## 0.9.2
* `Versions`:
* `Klock`: `2.4.10` -> `2.4.12`
## 0.9.1
* `Repos`:
* `Exposed`:
* Default realizations of standard interfaces for exposed DB are using public fields for now:
* `ExposedReadKeyValueRepo`
* `ExposedReadOneToManyKeyValueRepo`
* `ExposedStandardVersionsRepoProxy`
* New typealiases for one to many exposed realizations:
* `ExposedReadKeyValuesRepo`
* `ExposedKeyValuesRepo`
## 0.9.0
* `Versions`:
* `Kotlin`: `1.5.31` -> `1.6.10`
* `Coroutines`: `1.5.2` -> `1.6.0`
* `Serialization`: `1.3.1` -> `1.3.2`
* `Exposed`: `0.36.2` -> `0.37.2`
* `Ktor`: `1.6.5` -> `1.6.7`
* `Klock`: `2.4.8` -> `2.4.10`
## 0.8.9
* `Ktor`:
* `Server`:
* Fixes in `uniloadMultipart`
* `Client`:
* Fixes in `unimultipart`
* `FSM`:
* Fixes in `DefaultUpdatableStatesMachine`
## 0.8.8 ## 0.8.8
* `Versions`: * `Versions`:

View File

@@ -7,7 +7,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.3' classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version" classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"

View File

@@ -153,3 +153,22 @@ inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDif
inline fun <T> Iterable<T>.calculateStrictDiff( inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T> other: Iterable<T>
) = calculateDiff(other, strictComparison = true) ) = calculateDiff(other, strictComparison = true)
/**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
strictComparison: Boolean = false
) = calculateDiff(source, strictComparison).let {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}
it.added.forEach { (i, t) ->
add(i, t)
}
it.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}

View File

@@ -6,7 +6,7 @@ import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.* import kotlinx.serialization.encoding.*
/** /**
* Realization of this interface will contains at least one not null - [t1] or [t2] * Realization of this interface will contains at least one not null - [optionalT1] or [optionalT2]
* *
* @see EitherFirst * @see EitherFirst
* @see EitherSecond * @see EitherSecond
@@ -14,11 +14,19 @@ import kotlinx.serialization.encoding.*
* @see Either.Companion.second * @see Either.Companion.second
* @see Either.onFirst * @see Either.onFirst
* @see Either.onSecond * @see Either.onSecond
* @see Either.mapOnFirst
* @see Either.mapOnSecond
*/ */
@Serializable(EitherSerializer::class) @Serializable(EitherSerializer::class)
sealed interface Either<T1, T2> { sealed interface Either<T1, T2> {
val optionalT1: Optional<T1>
val optionalT2: Optional<T2>
@Deprecated("Use optionalT1 instead", ReplaceWith("optionalT1"))
val t1: T1? val t1: T1?
get() = optionalT1.dataOrNull()
@Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
val t2: T2? val t2: T2?
get() = optionalT2.dataOrNull()
companion object { companion object {
fun <T1, T2> serializer( fun <T1, T2> serializer(
@@ -32,8 +40,7 @@ class EitherSerializer<T1, T2>(
t1Serializer: KSerializer<T1>, t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>, t2Serializer: KSerializer<T2>,
) : KSerializer<Either<T1, T2>> { ) : KSerializer<Either<T1, T2>> {
@ExperimentalSerializationApi @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
@InternalSerializationApi
override val descriptor: SerialDescriptor = buildSerialDescriptor( override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer", "TypedSerializer",
SerialKind.CONTEXTUAL SerialKind.CONTEXTUAL
@@ -44,8 +51,7 @@ class EitherSerializer<T1, T2>(
private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer) private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer)
private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer) private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer)
@ExperimentalSerializationApi @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
@InternalSerializationApi
override fun deserialize(decoder: Decoder): Either<T1, T2> { override fun deserialize(decoder: Decoder): Either<T1, T2> {
return decoder.decodeStructure(descriptor) { return decoder.decodeStructure(descriptor) {
var type: String? = null var type: String? = null
@@ -77,8 +83,7 @@ class EitherSerializer<T1, T2>(
} }
@ExperimentalSerializationApi @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: Either<T1, T2>) { override fun serialize(encoder: Encoder, value: Either<T1, T2>) {
encoder.encodeStructure(descriptor) { encoder.encodeStructure(descriptor) {
when (value) { when (value) {
@@ -96,25 +101,25 @@ class EitherSerializer<T1, T2>(
} }
/** /**
* This type [Either] will always have not nullable [t1] * This type [Either] will always have not nullable [optionalT1]
*/ */
@Serializable @Serializable
data class EitherFirst<T1, T2>( data class EitherFirst<T1, T2>(
override val t1: T1 override val t1: T1
) : Either<T1, T2> { ) : Either<T1, T2> {
override val t2: T2? override val optionalT1: Optional<T1> = t1.optional
get() = null override val optionalT2: Optional<T2> = Optional.absent()
} }
/** /**
* This type [Either] will always have not nullable [t2] * This type [Either] will always have not nullable [optionalT2]
*/ */
@Serializable @Serializable
data class EitherSecond<T1, T2>( data class EitherSecond<T1, T2>(
override val t2: T2 override val t2: T2
) : Either<T1, T2> { ) : Either<T1, T2> {
override val t1: T1? override val optionalT1: Optional<T1> = Optional.absent()
get() = null override val optionalT2: Optional<T2> = t2.optional
} }
/** /**
@@ -127,23 +132,35 @@ inline fun <T1, T2> Either.Companion.first(t1: T1): Either<T1, T2> = EitherFirst
inline fun <T1, T2> Either.Companion.second(t2: T2): Either<T1, T2> = EitherSecond(t2) inline fun <T1, T2> Either.Companion.second(t2: T2): Either<T1, T2> = EitherSecond(t2)
/** /**
* Will call [block] in case when [Either.t1] of [this] is not null * Will call [block] in case when [this] is [EitherFirst]
*/ */
inline fun <T1, T2, E : Either<T1, T2>> E.onFirst(block: (T1) -> Unit): E { inline fun <T1, T2, E : Either<T1, T2>> E.onFirst(block: (T1) -> Unit): E {
val t1 = t1 optionalT1.onPresented(block)
t1 ?.let(block)
return this return this
} }
/** /**
* Will call [block] in case when [Either.t2] of [this] is not null * Will call [block] in case when [this] is [EitherSecond]
*/ */
inline fun <T1, T2, E : Either<T1, T2>> E.onSecond(block: (T2) -> Unit): E { inline fun <T1, T2, E : Either<T1, T2>> E.onSecond(block: (T2) -> Unit): E {
val t2 = t2 optionalT2.onPresented(block)
t2 ?.let(block)
return this return this
} }
/**
* @return Result of [block] if [this] is [EitherFirst]
*/
inline fun <T1, R> Either<T1, *>.mapOnFirst(block: (T1) -> R): R? {
return optionalT1.mapOnPresented(block)
}
/**
* @return Result of [block] if [this] is [EitherSecond]
*/
inline fun <T2, R> Either<*, T2>.mapOnSecond(block: (T2) -> R): R? {
return optionalT2.mapOnPresented(block)
}
inline fun <reified T1, reified T2> Any.either() = when (this) { inline fun <reified T1, reified T2> Any.either() = when (this) {
is T1 -> Either.first<T1, T2>(this) is T1 -> Either.first<T1, T2>(this)
is T2 -> Either.second<T1, T2>(this) is T2 -> Either.second<T1, T2>(this)

View File

@@ -54,7 +54,7 @@ class DiffUtilsTests {
val oldList = (0 until 10).map { it.toString() } val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (step in 0 until oldList.size) { for (step in oldList.indices) {
for ((i, v) in withIndex) { for ((i, v) in withIndex) {
val mutable = oldList.toMutableList() val mutable = oldList.toMutableList()
val changes = ( val changes = (
@@ -73,4 +73,78 @@ class DiffUtilsTests {
} }
} }
} }
@Test
fun testThatSimpleRemoveApplyWorks() {
val oldList = (0 until 10).toList()
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
val removedSublist = oldList.subList(i, i + count)
val mutableOldList = oldList.toMutableList()
val targetList = oldList - removedSublist
mutableOldList.applyDiff(targetList)
assertEquals(
targetList,
mutableOldList
)
}
}
}
@Test
fun testThatSimpleAddApplyWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
val addedSublist = oldList.subList(i, i + count).map { "added$it" }
val mutable = oldList.toMutableList()
mutable.addAll(i, addedSublist)
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(
mutable,
mutableOldList
)
}
}
}
@Test
fun testThatSimpleChangesApplyWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (step in oldList.indices) {
for ((i, v) in withIndex) {
val mutable = oldList.toMutableList()
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step)
).map { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value
}
}
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(
mutable,
mutableOldList
)
}
}
}
} }

View File

@@ -0,0 +1,124 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.DOMRectReadOnly
import org.w3c.dom.Element
external interface IntersectionObserverOptions {
/**
* An Element or Document object which is an ancestor of the intended target, whose bounding rectangle will be
* considered the viewport. Any part of the target not visible in the visible area of the root is not considered
* visible.
*/
var root: Element?
/**
* A string which specifies a set of offsets to add to the root's bounding_box when calculating intersections,
* effectively shrinking or growing the root for calculation purposes. The syntax is approximately the same as that
* for the CSS margin property; see The root element and root margin in Intersection Observer API for more
* information on how the margin works and the syntax. The default is "0px 0px 0px 0px".
*/
var rootMargin: String?
/**
* Either a single number or an array of numbers between 0.0 and 1.0, specifying a ratio of intersection area to
* total bounding box area for the observed target. A value of 0.0 means that even a single visible pixel counts as
* the target being visible. 1.0 means that the entire target element is visible. See Thresholds in Intersection
* Observer API for a more in-depth description of how thresholds are used. The default is a threshold of 0.0.
*/
var threshold: Array<Number>?
}
fun IntersectionObserverOptions(
block: IntersectionObserverOptions.() -> Unit = {}
): IntersectionObserverOptions = js("{}").unsafeCast<IntersectionObserverOptions>().apply(block)
external interface IntersectionObserverEntry {
/**
* Returns the bounds rectangle of the target element as a DOMRectReadOnly. The bounds are computed as described in
* the documentation for Element.getBoundingClientRect().
*/
val boundingClientRect: DOMRectReadOnly
/**
* Returns the ratio of the intersectionRect to the boundingClientRect.
*/
val intersectionRatio: Number
/**
* Returns a DOMRectReadOnly representing the target's visible area.
*/
val intersectionRect: DOMRectReadOnly
/**
* A Boolean value which is true if the target element intersects with the intersection observer's root. If this is
* true, then, the IntersectionObserverEntry describes a transition into a state of intersection; if it's false,
* then you know the transition is from intersecting to not-intersecting.
*/
val isIntersecting: Boolean
/**
* Returns a DOMRectReadOnly for the intersection observer's root.
*/
val rootBounds: DOMRectReadOnly
/**
* The Element whose intersection with the root changed.
*/
val target: Element
/**
* A DOMHighResTimeStamp indicating the time at which the intersection was recorded, relative to the
* IntersectionObserver's time origin.
*/
val time: Double
}
typealias IntersectionObserverCallback = (entries: Array<IntersectionObserverEntry>, observer: IntersectionObserver) -> Unit
/**
* This is just an implementation from [this commentary](https://youtrack.jetbrains.com/issue/KT-43157#focus=Comments-27-4498582.0-0)
* of Kotlin JS issue related to the absence of [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver)
*/
external class IntersectionObserver(callback: IntersectionObserverCallback) {
constructor(callback: IntersectionObserverCallback, options: IntersectionObserverOptions)
/**
* The Element or Document whose bounds are used as the bounding box when testing for intersection. If no root value
* was passed to the constructor or its value is null, the top-level document's viewport is used.
*/
val root: Element
/**
* An offset rectangle applied to the root's bounding box when calculating intersections, effectively shrinking or
* growing the root for calculation purposes. The value returned by this property may not be the same as the one
* specified when calling the constructor as it may be changed to match internal requirements. Each offset can be
* expressed in pixels (px) or as a percentage (%). The default is "0px 0px 0px 0px".
*/
val rootMargin: String
/**
* A list of thresholds, sorted in increasing numeric order, where each threshold is a ratio of intersection area to
* bounding box area of an observed target. Notifications for a target are generated when any of the thresholds are
* crossed for that target. If no value was passed to the constructor, 0 is used.
*/
val thresholds: Array<Number>
/**
* Stops the IntersectionObserver object from observing any target.
*/
fun disconnect()
/**
* Tells the IntersectionObserver a target element to observe.
*/
fun observe(targetElement: Element)
/**
* Returns an array of IntersectionObserverEntry objects for all observed targets.
*/
fun takeRecords(): Array<IntersectionObserverEntry>
/**
* Tells the IntersectionObserver to stop observing a particular target element.
*/
fun unobserve(targetElement: Element)
}

View File

@@ -28,12 +28,12 @@ open class DefaultUpdatableStatesMachine<T : State>(
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) { override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock { statesJobsMutex.withLock {
if (previousState.dataOrNull() != actualState) { if (compare(previousState, actualState)) {
statesJobs[actualState] ?.cancel() statesJobs[actualState] ?.cancel()
} }
val job = previousState.mapOnPresented { val job = previousState.mapOnPresented {
statesJobs.remove(it) statesJobs.remove(it)
} ?: scope.launch { } ?.takeIf { it.isActive } ?: scope.launch {
performUpdate(actualState) performUpdate(actualState)
}.also { job -> }.also { job ->
job.invokeOnCompletion { _ -> job.invokeOnCompletion { _ ->
@@ -52,6 +52,8 @@ open class DefaultUpdatableStatesMachine<T : State>(
} }
} }
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
override suspend fun updateChain(currentState: T, newState: T) { override suspend fun updateChain(currentState: T, newState: T) {
statesManager.update(currentState, newState) statesManager.update(currentState, newState)
} }

View File

@@ -7,24 +7,24 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx2g
kotlin_version=1.5.31 kotlin_version=1.6.10
kotlin_coroutines_version=1.5.2 kotlin_coroutines_version=1.6.0
kotlin_serialisation_core_version=1.3.1 kotlin_serialisation_core_version=1.3.2
kotlin_exposed_version=0.36.2 kotlin_exposed_version=0.37.3
ktor_version=1.6.5 ktor_version=1.6.7
klockVersion=2.4.8 klockVersion=2.5.2
github_release_plugin_version=2.2.12 github_release_plugin_version=2.2.12
uuidVersion=0.3.1 uuidVersion=0.4.0
# ANDROID # ANDROID
core_ktx_version=1.7.0 core_ktx_version=1.7.0
androidx_recycler_version=1.2.1 androidx_recycler_version=1.2.1
appcompat_version=1.4.0 appcompat_version=1.4.1
android_minSdkVersion=19 android_minSdkVersion=19
android_compileSdkVersion=32 android_compileSdkVersion=32
@@ -40,10 +40,10 @@ crypto_js_version=4.1.1
# Dokka # Dokka
dokka_version=1.5.31 dokka_version=1.6.10
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.8.8 version=0.9.9
android_code_version=88 android_code_version=99

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -140,7 +140,7 @@ suspend fun <ResultType> HttpClient.unimultipart(
inputProvider, inputProvider,
Headers.build { Headers.build {
append(HttpHeaders.ContentType, mimetype) append(HttpHeaders.ContentType, mimetype)
append(HttpHeaders.ContentDisposition, "filename=$filename") append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
dataHeadersBuilder() dataHeadersBuilder()
} }
) )

View File

@@ -2,25 +2,23 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.featureOrNull
import io.ktor.application.install
import io.ktor.http.cio.websocket.* import io.ktor.http.cio.websocket.*
import io.ktor.routing.Route import io.ktor.routing.Route
import io.ktor.routing.application
import io.ktor.websocket.webSocket import io.ktor.websocket.webSocket
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
private suspend fun DefaultWebSocketSession.checkReceivedAndCloseIfExists() {
if (incoming.tryReceive() != null) {
close()
throw CorrectCloseException
}
}
fun <T> Route.includeWebsocketHandling( fun <T> Route.includeWebsocketHandling(
suburl: String, suburl: String,
flow: Flow<T>, flow: Flow<T>,
converter: (T) -> StandardKtorSerialInputData converter: (T) -> StandardKtorSerialInputData
) { ) {
application.apply {
featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets)
}
webSocket(suburl) { webSocket(suburl) {
safely { safely {
flow.collect { flow.collect {

View File

@@ -180,7 +180,13 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
"bytes" -> { "bytes" -> {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part")) val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile( resultInput = MPPFile.createTempFile(
name.nameWithoutExtension, name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}" ".${name.extension}"
).apply { ).apply {
outputStream().use { fileStream -> outputStream().use { fileStream ->
@@ -216,7 +222,13 @@ suspend fun ApplicationCall.uniloadMultipartFile(
if (it.name == "bytes") { if (it.name == "bytes") {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part")) val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile( resultInput = MPPFile.createTempFile(
name.nameWithoutExtension, name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}" ".${name.extension}"
).apply { ).apply {
outputStream().use { fileStream -> outputStream().use { fileStream ->

View File

@@ -38,3 +38,31 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
size.toLong() size.toLong()
) )
} }
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
is List<T> -> optionallyReverse(reverse)
is Set<T> -> optionallyReverse(reverse)
else -> if (reverse) {
reversed()
} else {
this
}
}
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
reversed()
} else {
this
}
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
reversed().toSet()
} else {
this
}
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
Array(size) {
get(lastIndex - it)
}
} else {
this
}

View File

@@ -26,3 +26,15 @@ fun Pagination.reverse(datasetSize: Long): SimplePagination {
* Shortcut for [reverse] * Shortcut for [reverse]
*/ */
fun Pagination.reverse(objectsCount: Int) = reverse(objectsCount.toLong()) fun Pagination.reverse(objectsCount: Int) = reverse(objectsCount.toLong())
fun Pagination.optionallyReverse(objectsCount: Int, reverse: Boolean) = if (reverse) {
reverse(objectsCount)
} else {
this
}
fun Pagination.optionallyReverse(objectsCount: Long, reverse: Boolean) = if (reverse) {
reverse(objectsCount)
} else {
this
}

View File

@@ -1,5 +1,4 @@
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'signing'
task javadocsJar(type: Jar) { task javadocsJar(type: Jar) {
classifier = 'javadoc' classifier = 'javadoc'
@@ -69,8 +68,19 @@ publishing {
} }
} }
} }
signing { if (project.hasProperty("signing.gnupg.keyName")) {
useGpgCmd() apply plugin: 'signing'
sign publishing.publications
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
}
}
} }

View File

@@ -1 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","includeGpgSigning":true,"developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}]}} {"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}

View File

@@ -12,8 +12,8 @@ open class ExposedReadKeyValueRepo<Key, Value>(
valueColumnAllocator: ColumnAllocator<Value>, valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null tableName: String? = null
) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") { ) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
protected val keyColumn: Column<Key> = keyColumnAllocator() val keyColumn: Column<Key> = keyColumnAllocator()
protected val valueColumn: Column<Value> = valueColumnAllocator() val valueColumn: Column<Value> = valueColumnAllocator()
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn) override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
init { initTable() } init { initTable() }

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedKeyValuesRepo<Key, Value> = ExposedOneToManyKeyValueRepo<Key, Value>
open class ExposedOneToManyKeyValueRepo<Key, Value>( open class ExposedOneToManyKeyValueRepo<Key, Value>(
database: Database, database: Database,
keyColumnAllocator: ColumnAllocator<Key>, keyColumnAllocator: ColumnAllocator<Key>,
@@ -34,10 +35,15 @@ open class ExposedOneToManyKeyValueRepo<Key, Value>(
if (select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) { if (select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) {
return@mapNotNull null return@mapNotNull null
} }
insertIgnore { val insertResult = insert {
it[keyColumn] = k it[keyColumn] = k
it[valueColumn] = v it[valueColumn] = v
}.getOrNull(keyColumn) ?.let { k to v } }
if (insertResult.insertedCount > 0) {
k to v
} else {
null
}
} ?: emptyList() } ?: emptyList()
} }
}.forEach { _onNewValue.emit(it) } }.forEach { _onNewValue.emit(it) }
@@ -47,7 +53,7 @@ open class ExposedOneToManyKeyValueRepo<Key, Value>(
transaction(database) { transaction(database) {
toRemove.keys.flatMap { k -> toRemove.keys.flatMap { k ->
toRemove[k] ?.mapNotNull { v -> toRemove[k] ?.mapNotNull { v ->
if (deleteIgnoreWhere { keyColumn.eq(k).and(valueColumn.eq(v)) } > 0 ) { if (deleteWhere { keyColumn.eq(k).and(valueColumn.eq(v)) } > 0 ) {
k to v k to v
} else { } else {
null null

View File

@@ -3,17 +3,20 @@ package dev.inmo.micro_utils.repos.exposed.onetomany
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.* import dev.inmo.micro_utils.repos.exposed.*
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedReadKeyValueRepo
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedReadKeyValuesRepo<Key, Value> = ExposedReadOneToManyKeyValueRepo<Key, Value>
open class ExposedReadOneToManyKeyValueRepo<Key, Value>( open class ExposedReadOneToManyKeyValueRepo<Key, Value>(
override val database: Database, override val database: Database,
keyColumnAllocator: ColumnAllocator<Key>, keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>, valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null tableName: String? = null
) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") { ) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
protected val keyColumn: Column<Key> = keyColumnAllocator() val keyColumn: Column<Key> = keyColumnAllocator()
protected val valueColumn: Column<Value> = valueColumnAllocator() val valueColumn: Column<Value> = valueColumnAllocator()
init { initTable() } init { initTable() }

View File

@@ -18,8 +18,8 @@ inline fun versionsRepo(database: Database): VersionsRepo<Database> = StandardVe
class ExposedStandardVersionsRepoProxy( class ExposedStandardVersionsRepoProxy(
override val database: Database override val database: Database
) : StandardVersionsRepoProxy<Database>, Table("ExposedVersionsProxy"), ExposedRepo { ) : StandardVersionsRepoProxy<Database>, Table("ExposedVersionsProxy"), ExposedRepo {
private val tableNameColumn = text("tableName") val tableNameColumn = text("tableName")
private val tableVersionColumn = integer("tableName") val tableVersionColumn = integer("tableName")
init { init {
initTable() initTable()

View File

@@ -11,8 +11,7 @@ open class TypedSerializer<T : Any>(
presetSerializers: Map<String, KSerializer<out T>> = emptyMap(), presetSerializers: Map<String, KSerializer<out T>> = emptyMap(),
) : KSerializer<T> { ) : KSerializer<T> {
protected val serializers = presetSerializers.toMutableMap() protected val serializers = presetSerializers.toMutableMap()
@ExperimentalSerializationApi @OptIn(InternalSerializationApi::class)
@InternalSerializationApi
override val descriptor: SerialDescriptor = buildSerialDescriptor( override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer", "TypedSerializer",
SerialKind.CONTEXTUAL SerialKind.CONTEXTUAL
@@ -21,8 +20,7 @@ open class TypedSerializer<T : Any>(
element("value", ContextualSerializer(kClass).descriptor) element("value", ContextualSerializer(kClass).descriptor)
} }
@ExperimentalSerializationApi @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
@InternalSerializationApi
override fun deserialize(decoder: Decoder): T { override fun deserialize(decoder: Decoder): T {
return decoder.decodeStructure(descriptor) { return decoder.decodeStructure(descriptor) {
var type: String? = null var type: String? = null
@@ -46,14 +44,12 @@ open class TypedSerializer<T : Any>(
} }
} }
@ExperimentalSerializationApi @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
@InternalSerializationApi
protected open fun <O: T> CompositeEncoder.encode(value: O) { protected open fun <O: T> CompositeEncoder.encode(value: O) {
encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer<O>, value) encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer<O>, value)
} }
@ExperimentalSerializationApi @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: T) { override fun serialize(encoder: Encoder, value: T) {
encoder.encodeStructure(descriptor) { encoder.encodeStructure(descriptor) {
val valueSerializer = value::class.serializer() val valueSerializer = value::class.serializer()