mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-18 14:59:24 +00:00
Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
aefaf4a8cc | |||
b29f37a251 | |||
88854020ac | |||
14ebe01fc6 | |||
771aed0f0f | |||
16c57fcd6a | |||
75397a7ccb | |||
b05404f828 | |||
edea942874 | |||
8a8f568b9a | |||
4dc8d30c52 | |||
cb4e08e823 | |||
c443bf4fa0 | |||
a6982de822 | |||
4f1a663e75 | |||
dab262d626 | |||
87a3f61ca6 | |||
506e937a68 | |||
5a037c76dd | |||
313f622f7e | |||
6cba1fe1a2 | |||
fd2d0e80b7 | |||
96ab2e8aca | |||
0202988cae | |||
d619d59947 | |||
85b3e48d18 | |||
7a9b7d98a1 | |||
b212acfcaf |
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,5 +1,63 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.9.10
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.5.2` -> `2.6.1`
|
||||||
|
* Ktor:
|
||||||
|
* Client:
|
||||||
|
* New function `UnifiedRequester#createStandardWebsocketFlow` without `checkReconnection` arg
|
||||||
|
* Server:
|
||||||
|
* Now it is possible to filter data in `Route#includeWebsocketHandling`
|
||||||
|
* Callback in `Route#includeWebsocketHandling` and dependent methods is `suspend` since now
|
||||||
|
* Add `URLProtocol` support in `Route#includeWebsocketHandling` and dependent methods
|
||||||
|
|
||||||
|
## 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
|
## 0.9.3
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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(
|
||||||
@@ -93,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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,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)
|
||||||
|
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
}
|
@@ -10,11 +10,11 @@ org.gradle.jvmargs=-Xmx2g
|
|||||||
kotlin_version=1.6.10
|
kotlin_version=1.6.10
|
||||||
kotlin_coroutines_version=1.6.0
|
kotlin_coroutines_version=1.6.0
|
||||||
kotlin_serialisation_core_version=1.3.2
|
kotlin_serialisation_core_version=1.3.2
|
||||||
kotlin_exposed_version=0.37.2
|
kotlin_exposed_version=0.37.3
|
||||||
|
|
||||||
ktor_version=1.6.7
|
ktor_version=1.6.7
|
||||||
|
|
||||||
klockVersion=2.4.12
|
klockVersion=2.6.1
|
||||||
|
|
||||||
github_release_plugin_version=2.2.12
|
github_release_plugin_version=2.2.12
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ uuidVersion=0.4.0
|
|||||||
|
|
||||||
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.6.0
|
dokka_version=1.6.10
|
||||||
|
|
||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.9.3
|
version=0.9.10
|
||||||
android_code_version=93
|
android_code_version=100
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||||
|
@@ -4,6 +4,7 @@ import dev.inmo.micro_utils.coroutines.safely
|
|||||||
import dev.inmo.micro_utils.ktor.common.*
|
import dev.inmo.micro_utils.ktor.common.*
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.features.websocket.ws
|
import io.ktor.client.features.websocket.ws
|
||||||
|
import io.ktor.client.request.HttpRequestBuilder
|
||||||
import io.ktor.http.cio.websocket.Frame
|
import io.ktor.http.cio.websocket.Frame
|
||||||
import io.ktor.http.cio.websocket.readBytes
|
import io.ktor.http.cio.websocket.readBytes
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -17,6 +18,7 @@ import kotlinx.serialization.DeserializationStrategy
|
|||||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||||
url: String,
|
url: String,
|
||||||
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
|
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
|
||||||
|
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
|
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
|
||||||
): Flow<T> {
|
): Flow<T> {
|
||||||
val correctedUrl = url.asCorrectWebSocketUrl
|
val correctedUrl = url.asCorrectWebSocketUrl
|
||||||
@@ -26,7 +28,7 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
|
|||||||
do {
|
do {
|
||||||
val reconnect = try {
|
val reconnect = try {
|
||||||
safely {
|
safely {
|
||||||
ws(correctedUrl) {
|
ws(correctedUrl, requestBuilder) {
|
||||||
for (received in incoming) {
|
for (received in incoming) {
|
||||||
when (received) {
|
when (received) {
|
||||||
is Frame.Binary -> producerScope.send(conversation(received.readBytes()))
|
is Frame.Binary -> producerScope.send(conversation(received.readBytes()))
|
||||||
@@ -65,10 +67,12 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
|
|||||||
url: String,
|
url: String,
|
||||||
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
|
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
|
||||||
deserializer: DeserializationStrategy<T>,
|
deserializer: DeserializationStrategy<T>,
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||||
|
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
) = createStandardWebsocketFlow(
|
) = createStandardWebsocketFlow(
|
||||||
url,
|
url,
|
||||||
checkReconnection
|
checkReconnection,
|
||||||
|
requestBuilder
|
||||||
) {
|
) {
|
||||||
serialFormat.decodeDefault(deserializer, it)
|
serialFormat.decodeDefault(deserializer, it)
|
||||||
}
|
}
|
||||||
|
@@ -85,9 +85,16 @@ class UnifiedRequester(
|
|||||||
|
|
||||||
fun <T> createStandardWebsocketFlow(
|
fun <T> createStandardWebsocketFlow(
|
||||||
url: String,
|
url: String,
|
||||||
checkReconnection: (Throwable?) -> Boolean = { true },
|
checkReconnection: (Throwable?) -> Boolean,
|
||||||
deserializer: DeserializationStrategy<T>
|
deserializer: DeserializationStrategy<T>,
|
||||||
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat)
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat, requestBuilder)
|
||||||
|
|
||||||
|
fun <T> createStandardWebsocketFlow(
|
||||||
|
url: String,
|
||||||
|
deserializer: DeserializationStrategy<T>,
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
val defaultRequester = UnifiedRequester()
|
val defaultRequester = UnifiedRequester()
|
||||||
|
@@ -2,29 +2,31 @@ 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.URLProtocol
|
||||||
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.websocket.webSocket
|
import io.ktor.routing.application
|
||||||
|
import io.ktor.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
|
protocol: URLProtocol = URLProtocol.WS,
|
||||||
|
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
|
||||||
) {
|
) {
|
||||||
webSocket(suburl) {
|
application.apply {
|
||||||
|
featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets)
|
||||||
|
}
|
||||||
|
webSocket(suburl, protocol.name) {
|
||||||
safely {
|
safely {
|
||||||
flow.collect {
|
flow.collect {
|
||||||
send(converter(it))
|
converter(it) ?.let { data ->
|
||||||
|
send(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,10 +36,24 @@ fun <T> Route.includeWebsocketHandling(
|
|||||||
suburl: String,
|
suburl: String,
|
||||||
flow: Flow<T>,
|
flow: Flow<T>,
|
||||||
serializer: SerializationStrategy<T>,
|
serializer: SerializationStrategy<T>,
|
||||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||||
|
protocol: URLProtocol = URLProtocol.WS,
|
||||||
|
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
||||||
) = includeWebsocketHandling(
|
) = includeWebsocketHandling(
|
||||||
suburl,
|
suburl,
|
||||||
flow
|
flow,
|
||||||
) {
|
protocol,
|
||||||
serialFormat.encodeDefault(serializer, it)
|
converter = if (filter == null) {
|
||||||
}
|
{
|
||||||
|
serialFormat.encodeDefault(serializer, it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
{
|
||||||
|
if (filter(it)) {
|
||||||
|
serialFormat.encodeDefault(serializer, it)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@@ -5,8 +5,7 @@ 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.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.*
|
||||||
import io.ktor.http.HttpStatusCode
|
|
||||||
import io.ktor.http.content.PartData
|
import io.ktor.http.content.PartData
|
||||||
import io.ktor.http.content.forEachPart
|
import io.ktor.http.content.forEachPart
|
||||||
import io.ktor.request.receive
|
import io.ktor.request.receive
|
||||||
@@ -18,6 +17,7 @@ import io.ktor.util.asStream
|
|||||||
import io.ktor.util.cio.writeChannel
|
import io.ktor.util.cio.writeChannel
|
||||||
import io.ktor.util.pipeline.PipelineContext
|
import io.ktor.util.pipeline.PipelineContext
|
||||||
import io.ktor.utils.io.core.*
|
import io.ktor.utils.io.core.*
|
||||||
|
import io.ktor.websocket.WebSocketServerSession
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -30,8 +30,10 @@ class UnifiedRouter(
|
|||||||
fun <T> Route.includeWebsocketHandling(
|
fun <T> Route.includeWebsocketHandling(
|
||||||
suburl: String,
|
suburl: String,
|
||||||
flow: Flow<T>,
|
flow: Flow<T>,
|
||||||
serializer: SerializationStrategy<T>
|
serializer: SerializationStrategy<T>,
|
||||||
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat)
|
protocol: URLProtocol = URLProtocol.WS,
|
||||||
|
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
||||||
|
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
|
||||||
|
|
||||||
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
|
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
|
||||||
answerSerializer: SerializationStrategy<T>,
|
answerSerializer: SerializationStrategy<T>,
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
@@ -35,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) }
|
||||||
@@ -48,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
|
||||||
|
Reference in New Issue
Block a user