mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 22:39:25 +00:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
19857930a4 | |||
d0dbe3ed2f | |||
8b7e78b63a | |||
92a4ecb523 | |||
6a5ad4d728 | |||
be4aa8daac | |||
b5eac37782 | |||
b1ad3c5a39 | |||
ba16bad029 | |||
ca8ae4cd72 | |||
53d35d74b3 | |||
49c139e235 | |||
caf9c821f3 | |||
ca4c6db96f | |||
6b2298c752 | |||
a1bf43def9 | |||
15e9254e00 | |||
afe5a72c6f | |||
750a8b9ecf | |||
27fc3f93e0 | |||
8166d4b99b | |||
b61d2ae2eb | |||
4790fe0aea | |||
bc37b11cee | |||
223fed910f | |||
b85ab7b061 | |||
888dc299c9 | |||
e113dc28ed | |||
31e55d2307 | |||
e90645f248 | |||
4bb7ba2571 | |||
8d31c25bf8 | |||
c7ee1c28b2 | |||
99b09c8b28 | |||
a328c4425a | |||
c0f61ca896 | |||
86e70c0961 | |||
d87a3a039f | |||
6279a2c40a |
55
CHANGELOG.md
55
CHANGELOG.md
@@ -1,7 +1,62 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.9.24
|
||||||
|
|
||||||
|
* `Ktor`:
|
||||||
|
* `Common`:
|
||||||
|
* New extension fun `MPPFile#input`
|
||||||
|
|
||||||
|
## 0.9.23
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Exposed`:
|
||||||
|
* New property `ExposedRepo#selectAll` to retrieve all the rows in the table
|
||||||
|
|
||||||
|
## 0.9.22
|
||||||
|
|
||||||
|
* `Ktor`:
|
||||||
|
* `Server`:
|
||||||
|
* Now `createKtorServer` fun is fully customizable
|
||||||
|
|
||||||
|
## 0.9.21
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Exposed`:
|
||||||
|
* fixes in `AbstractExposedWriteCRUDRepo`
|
||||||
|
|
||||||
|
## 0.9.20
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Common`:
|
||||||
|
* Fixes in `OneToManyAndroidRepo`
|
||||||
|
* New `CursorIterator`
|
||||||
|
|
||||||
|
## 0.9.19
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Coroutines`: `1.6.0` -> `1.6.1`
|
||||||
|
* `Repos`:
|
||||||
|
* `Exposed`:
|
||||||
|
* Fixes in `ExposedStandardVersionsRepoProxy`
|
||||||
|
|
||||||
|
## 0.9.18
|
||||||
|
|
||||||
|
* `Common`
|
||||||
|
* New extensions for `Element`: `Element#onActionOutside` and `Element#onClickOutside`
|
||||||
|
|
||||||
|
## 0.9.17
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* New extensions `Element#onVisibilityChanged`, `Element#onVisible` and `Element#onInvisible`
|
||||||
|
* `Coroutines`:
|
||||||
|
* New extension `Element.visibilityFlow()`
|
||||||
|
* `FSM`:
|
||||||
|
* Now it is possible to resolve conflicts on `startChain`
|
||||||
|
|
||||||
## 0.9.16
|
## 0.9.16
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Klock`: `2.6.3` -> `2.7.0`
|
||||||
* `Common`:
|
* `Common`:
|
||||||
* New extension `Node#onRemoved`
|
* New extension `Node#onRemoved`
|
||||||
* `Compose`:
|
* `Compose`:
|
||||||
|
@@ -51,7 +51,6 @@ 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)
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
|
||||||
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
|
||||||
@@ -83,7 +82,6 @@ class EitherSerializer<T1, T2>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
|
||||||
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) {
|
||||||
|
@@ -3,7 +3,7 @@ package dev.inmo.micro_utils.common
|
|||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.*
|
||||||
|
|
||||||
fun Node.onRemoved(block: () -> Unit) {
|
fun Node.onRemoved(block: () -> Unit): MutationObserver {
|
||||||
lateinit var observer: MutationObserver
|
lateinit var observer: MutationObserver
|
||||||
|
|
||||||
observer = MutationObserver { _, _ ->
|
observer = MutationObserver { _, _ ->
|
||||||
@@ -18,4 +18,44 @@ fun Node.onRemoved(block: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
observer.observe(document, MutationObserverInit(childList = true, subtree = true))
|
observer.observe(document, MutationObserverInit(childList = true, subtree = true))
|
||||||
|
return observer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Element.onVisibilityChanged(block: IntersectionObserverEntry.(Float, IntersectionObserver) -> Unit): IntersectionObserver {
|
||||||
|
var previousIntersectionRatio = -1f
|
||||||
|
val observer = IntersectionObserver { entries, observer ->
|
||||||
|
entries.forEach {
|
||||||
|
if (previousIntersectionRatio != it.intersectionRatio) {
|
||||||
|
previousIntersectionRatio = it.intersectionRatio.toFloat()
|
||||||
|
it.block(previousIntersectionRatio, observer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observer.observe(this)
|
||||||
|
return observer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Element.onVisible(block: Element.(IntersectionObserver) -> Unit) {
|
||||||
|
var previous = -1f
|
||||||
|
onVisibilityChanged { intersectionRatio, observer ->
|
||||||
|
if (previous != intersectionRatio) {
|
||||||
|
if (intersectionRatio > 0 && previous == 0f) {
|
||||||
|
block(observer)
|
||||||
|
}
|
||||||
|
previous = intersectionRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Element.onInvisible(block: Element.(IntersectionObserver) -> Unit): IntersectionObserver {
|
||||||
|
var previous = -1f
|
||||||
|
return onVisibilityChanged { intersectionRatio, observer ->
|
||||||
|
if (previous != intersectionRatio) {
|
||||||
|
if (intersectionRatio == 0f && previous != 0f) {
|
||||||
|
block(observer)
|
||||||
|
}
|
||||||
|
previous = intersectionRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.browser.document
|
||||||
|
import org.w3c.dom.*
|
||||||
|
import org.w3c.dom.events.Event
|
||||||
|
import org.w3c.dom.events.EventListener
|
||||||
|
|
||||||
|
fun Element.onActionOutside(type: String, options: dynamic = null, callback: (Event) -> Unit): EventListener {
|
||||||
|
lateinit var observer: MutationObserver
|
||||||
|
val listener = EventListener {
|
||||||
|
val elementsToCheck = mutableListOf<Element>(this@onActionOutside)
|
||||||
|
while (it.target != this@onActionOutside && elementsToCheck.isNotEmpty()) {
|
||||||
|
val childrenGettingElement = elementsToCheck.removeFirst()
|
||||||
|
for (i in 0 until childrenGettingElement.childElementCount) {
|
||||||
|
elementsToCheck.add(childrenGettingElement.children[i] ?: continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (elementsToCheck.isEmpty()) {
|
||||||
|
callback(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options == null) {
|
||||||
|
document.addEventListener(type, listener)
|
||||||
|
} else {
|
||||||
|
document.addEventListener(type, listener, options)
|
||||||
|
}
|
||||||
|
observer = onRemoved {
|
||||||
|
if (options == null) {
|
||||||
|
document.removeEventListener(type, listener)
|
||||||
|
} else {
|
||||||
|
document.removeEventListener(type, listener, options)
|
||||||
|
}
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
return listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Element.onClickOutside(options: dynamic = null, callback: (Event) -> Unit) = onActionOutside("click", options, callback)
|
@@ -0,0 +1,28 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.onRemoved
|
||||||
|
import dev.inmo.micro_utils.common.onVisibilityChanged
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
|
||||||
|
fun Element.visibilityFlow(): Flow<Boolean> = channelFlow {
|
||||||
|
var previousData: Boolean? = null
|
||||||
|
|
||||||
|
val observer = onVisibilityChanged { intersectionRatio, _ ->
|
||||||
|
val currentData = intersectionRatio > 0
|
||||||
|
if (currentData != previousData) {
|
||||||
|
trySend(currentData)
|
||||||
|
}
|
||||||
|
previousData = currentData
|
||||||
|
}
|
||||||
|
|
||||||
|
val removeObserver = onRemoved {
|
||||||
|
observer.disconnect()
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeOnClose {
|
||||||
|
observer.disconnect()
|
||||||
|
removeObserver.disconnect()
|
||||||
|
}
|
||||||
|
}
|
@@ -39,13 +39,17 @@ interface DefaultStatesManagerRepo<T : State> {
|
|||||||
* @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE
|
* @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE
|
||||||
* any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default, will be used
|
* any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default, will be used
|
||||||
* [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter
|
* [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter
|
||||||
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
* @param onStartContextsConflictResolver Receive current [State] and the state passed with [startChain]. In case when
|
||||||
|
* this callback will return true, currently placed on the [State.context] [State] will be replaced by new state
|
||||||
|
* with [endChain] with current state
|
||||||
|
* @param onUpdateContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
||||||
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
||||||
* new state by using [endChain] with that state
|
* new state by using [endChain] with that state
|
||||||
*/
|
*/
|
||||||
open class DefaultStatesManager<T : State>(
|
open class DefaultStatesManager<T : State>(
|
||||||
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
||||||
protected val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true },
|
||||||
|
protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||||
) : StatesManager<T> {
|
) : StatesManager<T> {
|
||||||
protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
||||||
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
|
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
|
||||||
@@ -56,6 +60,14 @@ open class DefaultStatesManager<T : State>(
|
|||||||
|
|
||||||
protected val mapMutex = Mutex()
|
protected val mapMutex = Mutex()
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
repo: DefaultStatesManagerRepo<T>,
|
||||||
|
onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean
|
||||||
|
) : this (
|
||||||
|
repo,
|
||||||
|
onUpdateContextsConflictResolver = onContextsConflictResolver
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun update(old: T, new: T) = mapMutex.withLock {
|
override suspend fun update(old: T, new: T) = mapMutex.withLock {
|
||||||
val stateByOldContext: T? = repo.getContextState(old.context)
|
val stateByOldContext: T? = repo.getContextState(old.context)
|
||||||
when {
|
when {
|
||||||
@@ -67,7 +79,7 @@ open class DefaultStatesManager<T : State>(
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val stateOnNewOneContext = repo.getContextState(new.context)
|
val stateOnNewOneContext = repo.getContextState(new.context)
|
||||||
if (stateOnNewOneContext == null || onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
||||||
stateOnNewOneContext ?.let { endChainWithoutLock(it) }
|
stateOnNewOneContext ?.let { endChainWithoutLock(it) }
|
||||||
repo.removeState(old)
|
repo.removeState(old)
|
||||||
repo.set(new)
|
repo.set(new)
|
||||||
@@ -78,7 +90,11 @@ open class DefaultStatesManager<T : State>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun startChain(state: T) = mapMutex.withLock {
|
override suspend fun startChain(state: T) = mapMutex.withLock {
|
||||||
if (!repo.contains(state.context)) {
|
val stateOnContext = repo.getContextState(state.context)
|
||||||
|
if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) {
|
||||||
|
stateOnContext ?.let {
|
||||||
|
endChainWithoutLock(it)
|
||||||
|
}
|
||||||
repo.set(state)
|
repo.set(state)
|
||||||
_onStartChain.emit(state)
|
_onStartChain.emit(state)
|
||||||
}
|
}
|
||||||
|
@@ -12,5 +12,6 @@ import kotlinx.coroutines.flow.*
|
|||||||
*/
|
*/
|
||||||
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
|
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
|
||||||
fun <T: State> InMemoryStatesManager(
|
fun <T: State> InMemoryStatesManager(
|
||||||
onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
onStartContextsConflictResolver: suspend (old: T, new: T) -> Boolean = { _, _ -> true },
|
||||||
) = DefaultStatesManager(onContextsConflictResolver = onContextsConflictResolver)
|
onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||||
|
) = DefaultStatesManager(onStartContextsConflictResolver = onStartContextsConflictResolver, onUpdateContextsConflictResolver = onUpdateContextsConflictResolver)
|
||||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.9.16
|
version=0.9.24
|
||||||
android_code_version=106
|
android_code_version=114
|
||||||
|
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
kt = "1.6.10"
|
kt = "1.6.10"
|
||||||
kt-serialization = "1.3.2"
|
kt-serialization = "1.3.2"
|
||||||
kt-coroutines = "1.6.0"
|
kt-coroutines = "1.6.1"
|
||||||
|
|
||||||
jb-compose = "1.1.1"
|
jb-compose = "1.1.1"
|
||||||
jb-exposed = "0.37.3"
|
jb-exposed = "0.37.3"
|
||||||
jb-dokka = "1.6.10"
|
jb-dokka = "1.6.10"
|
||||||
|
|
||||||
klock = "2.6.3"
|
klock = "2.7.0"
|
||||||
uuid = "0.4.0"
|
uuid = "0.4.0"
|
||||||
|
|
||||||
ktor = "1.6.8"
|
ktor = "1.6.8"
|
||||||
@@ -39,6 +39,7 @@ kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", vers
|
|||||||
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
|
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
|
||||||
|
|
||||||
|
|
||||||
|
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
|
||||||
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||||
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
|
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
|
||||||
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
|
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
|
||||||
|
@@ -14,6 +14,7 @@ kotlin {
|
|||||||
api libs.kt.serialization.cbor
|
api libs.kt.serialization.cbor
|
||||||
api libs.klock
|
api libs.klock
|
||||||
api libs.uuid
|
api libs.uuid
|
||||||
|
api libs.ktor.io
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import io.ktor.utils.io.core.Input
|
||||||
|
|
||||||
|
expect fun MPPFile.input(): Input
|
@@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import io.ktor.utils.io.core.ByteReadPacket
|
||||||
|
import io.ktor.utils.io.core.Input
|
||||||
|
|
||||||
|
actual fun MPPFile.input(): Input = ByteReadPacket(readBytes())
|
@@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import io.ktor.utils.io.core.Input
|
||||||
|
import io.ktor.utils.io.streams.asInput
|
||||||
|
|
||||||
|
actual fun MPPFile.input(): Input = inputStream().asInput()
|
@@ -3,6 +3,7 @@ package dev.inmo.micro_utils.ktor.server
|
|||||||
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
|
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
|
||||||
import io.ktor.application.Application
|
import io.ktor.application.Application
|
||||||
import io.ktor.server.cio.CIO
|
import io.ktor.server.cio.CIO
|
||||||
|
import io.ktor.server.cio.CIOApplicationEngine
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@@ -10,17 +11,21 @@ fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configurati
|
|||||||
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
|
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
|
||||||
host: String = "localhost",
|
host: String = "localhost",
|
||||||
port: Int = Random.nextInt(1024, 65535),
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
|
additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||||
|
additionalConfigurationConfigurator: TConfiguration.() -> Unit = {},
|
||||||
block: Application.() -> Unit
|
block: Application.() -> Unit
|
||||||
): TEngine {
|
): TEngine = embeddedServer(
|
||||||
val env = applicationEngineEnvironment {
|
engine,
|
||||||
|
applicationEngineEnvironment {
|
||||||
module(block)
|
module(block)
|
||||||
connector {
|
connector {
|
||||||
this@connector.host = host
|
this.host = host
|
||||||
this@connector.port = port
|
this.port = port
|
||||||
}
|
}
|
||||||
}
|
additionalEngineEnvironmentConfigurator()
|
||||||
return embeddedServer(engine, env)
|
},
|
||||||
}
|
additionalConfigurationConfigurator
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create server with [CIO] server engine without starting of it
|
* Create server with [CIO] server engine without starting of it
|
||||||
@@ -30,18 +35,31 @@ fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configurati
|
|||||||
fun createKtorServer(
|
fun createKtorServer(
|
||||||
host: String = "localhost",
|
host: String = "localhost",
|
||||||
port: Int = Random.nextInt(1024, 65535),
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
|
additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||||
|
additionalConfigurationConfigurator: CIOApplicationEngine.Configuration.() -> Unit = {},
|
||||||
block: Application.() -> Unit
|
block: Application.() -> Unit
|
||||||
): ApplicationEngine = createKtorServer(CIO, host, port, block)
|
): CIOApplicationEngine = createKtorServer(
|
||||||
|
CIO,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
additionalEngineEnvironmentConfigurator,
|
||||||
|
additionalConfigurationConfigurator,
|
||||||
|
block
|
||||||
|
)
|
||||||
|
|
||||||
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> createKtorServer(
|
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> createKtorServer(
|
||||||
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
|
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
|
||||||
host: String = "localhost",
|
host: String = "localhost",
|
||||||
port: Int = Random.nextInt(1024, 65535),
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
|
additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||||
|
additionalConfigurationConfigurator: TConfiguration.() -> Unit = {},
|
||||||
configurators: List<KtorApplicationConfigurator>
|
configurators: List<KtorApplicationConfigurator>
|
||||||
): TEngine = createKtorServer(
|
): TEngine = createKtorServer(
|
||||||
engine,
|
engine,
|
||||||
host,
|
host,
|
||||||
port
|
port,
|
||||||
|
additionalEngineEnvironmentConfigurator,
|
||||||
|
additionalConfigurationConfigurator
|
||||||
) {
|
) {
|
||||||
configurators.forEach { it.apply { configure() } }
|
configurators.forEach { it.apply { configure() } }
|
||||||
}
|
}
|
||||||
@@ -54,5 +72,7 @@ fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configurati
|
|||||||
fun createKtorServer(
|
fun createKtorServer(
|
||||||
host: String = "localhost",
|
host: String = "localhost",
|
||||||
port: Int = Random.nextInt(1024, 65535),
|
port: Int = Random.nextInt(1024, 65535),
|
||||||
configurators: List<KtorApplicationConfigurator>
|
configurators: List<KtorApplicationConfigurator>,
|
||||||
): ApplicationEngine = createKtorServer(CIO, host, port, configurators)
|
additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||||
|
additionalConfigurationConfigurator: CIOApplicationEngine.Configuration.() -> Unit = {},
|
||||||
|
): ApplicationEngine = createKtorServer(CIO, host, port, additionalEngineEnvironmentConfigurator, additionalConfigurationConfigurator, configurators)
|
||||||
|
@@ -6,7 +6,7 @@ import dev.inmo.micro_utils.pagination.*
|
|||||||
* Example:
|
* Example:
|
||||||
*
|
*
|
||||||
* * `|__f__l_______________________|` will be transformed to `|_______________________f__l__|`
|
* * `|__f__l_______________________|` will be transformed to `|_______________________f__l__|`
|
||||||
* * `|__f__l_|` will be transformed to `|__f__l_|`
|
* * `|__f__l_|` will be transformed to `|_f__l__|`
|
||||||
*
|
*
|
||||||
* @return Reversed version of this [Pagination]
|
* @return Reversed version of this [Pagination]
|
||||||
*/
|
*/
|
||||||
|
@@ -13,12 +13,12 @@ interface VersionsRepo<T> : Repo {
|
|||||||
* By default, instance of this interface will check that version of table with name [tableName] is less than
|
* By default, instance of this interface will check that version of table with name [tableName] is less than
|
||||||
* [version] or is absent
|
* [version] or is absent
|
||||||
*
|
*
|
||||||
* * In case if [tableName] didn't found, will be called [onCreate] and version of table will be set up to [version]
|
* In case if [tableName] didn't found, will be called [onCreate]. Then in case if [tableName] have version less
|
||||||
* * In case if [tableName] have version less than parameter [version], it will increase version one-by-one
|
* than parameter [version] or null, it will increase version one-by-one until database version will be equal to
|
||||||
* until database version will be equal to [version]
|
* [version]
|
||||||
*
|
*
|
||||||
* @param version Current version of table
|
* @param version Current version of table
|
||||||
* @param onCreate This callback will be called in case when table have no information about table
|
* @param onCreate This callback will be called in case when repo have no information about table
|
||||||
* @param onUpdate This callback will be called after **iterative** changing of version. It is expected that parameter
|
* @param onUpdate This callback will be called after **iterative** changing of version. It is expected that parameter
|
||||||
* "to" will always be greater than "from"
|
* "to" will always be greater than "from"
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,27 @@
|
|||||||
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import android.database.Cursor
|
||||||
|
|
||||||
|
class CursorIterator(
|
||||||
|
private val c: Cursor
|
||||||
|
) : Iterator<Cursor> {
|
||||||
|
private var i = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
c.moveToFirst()
|
||||||
|
}
|
||||||
|
override fun hasNext(): Boolean {
|
||||||
|
return i < c.count
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun next(): Cursor {
|
||||||
|
i++
|
||||||
|
return if (c.moveToNext()) {
|
||||||
|
c
|
||||||
|
} else {
|
||||||
|
throw NoSuchElementException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun Cursor.iterator(): CursorIterator = CursorIterator(this)
|
@@ -143,7 +143,12 @@ class OneToManyAndroidRepo<Key, Value>(
|
|||||||
}.toLong()
|
}.toLong()
|
||||||
|
|
||||||
override suspend fun count(k: Key): Long = helper.blockingReadableTransaction {
|
override suspend fun count(k: Key): Long = helper.blockingReadableTransaction {
|
||||||
selectDistinct(tableName, columns = valueColumnArray, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = FirstPagePagination(1).limitClause()).use {
|
selectDistinct(
|
||||||
|
tableName,
|
||||||
|
columns = valueColumnArray,
|
||||||
|
selection = "$idColumnName=?",
|
||||||
|
selectionArgs = arrayOf(k.keyAsString())
|
||||||
|
).use {
|
||||||
it.count
|
it.count
|
||||||
}
|
}
|
||||||
}.toLong()
|
}.toLong()
|
||||||
|
@@ -17,13 +17,19 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
ExposedCRUDRepo<ObjectType, IdType>,
|
ExposedCRUDRepo<ObjectType, IdType>,
|
||||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
{
|
{
|
||||||
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
protected val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
||||||
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
protected val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
||||||
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize)
|
protected val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize)
|
||||||
|
@Deprecated("Renamed", ReplaceWith("_newObjectsFlow"))
|
||||||
|
protected val newObjectsChannel = _newObjectsFlow
|
||||||
|
@Deprecated("Renamed", ReplaceWith("_updatedObjectsFlow"))
|
||||||
|
protected val updateObjectsChannel = _updatedObjectsFlow
|
||||||
|
@Deprecated("Renamed", ReplaceWith("_deletedObjectsIdsFlow"))
|
||||||
|
protected val deleteObjectsIdsChannel = _deletedObjectsIdsFlow
|
||||||
|
|
||||||
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
|
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
|
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow()
|
||||||
|
|
||||||
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
|
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
|
||||||
abstract val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
abstract val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
||||||
@@ -43,7 +49,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
return transaction(db = database) {
|
return transaction(db = database) {
|
||||||
values.map { value -> createWithoutNotification(value) }
|
values.map { value -> createWithoutNotification(value) }
|
||||||
}.onEach {
|
}.onEach {
|
||||||
newObjectsChannel.emit(it)
|
_newObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +80,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
onBeforeUpdate(listOf(id to value))
|
onBeforeUpdate(listOf(id to value))
|
||||||
return updateWithoutNotification(id, value).also {
|
return updateWithoutNotification(id, value).also {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
updateObjectsChannel.emit(it)
|
_updatedObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,16 +91,25 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
values.map { (id, value) -> updateWithoutNotification(id, value) }
|
values.map { (id, value) -> updateWithoutNotification(id, value) }
|
||||||
}.filterNotNull()
|
}.filterNotNull()
|
||||||
).onEach {
|
).onEach {
|
||||||
updateObjectsChannel.emit(it)
|
_updatedObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected open suspend fun onBeforeDelete(ids: List<IdType>) {}
|
protected open suspend fun onBeforeDelete(ids: List<IdType>) {}
|
||||||
override suspend fun deleteById(ids: List<IdType>) {
|
override suspend fun deleteById(ids: List<IdType>) {
|
||||||
onBeforeDelete(ids)
|
onBeforeDelete(ids)
|
||||||
transaction(db = database) {
|
transaction(db = database) {
|
||||||
deleteWhere(null, null) {
|
val deleted = deleteWhere(null, null) {
|
||||||
selectByIds(ids)
|
selectByIds(ids)
|
||||||
}
|
}
|
||||||
|
if (deleted == ids.size) {
|
||||||
|
ids
|
||||||
|
} else {
|
||||||
|
ids.filter {
|
||||||
|
select { selectById(it) }.limit(1).none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.forEach {
|
||||||
|
_deletedObjectsIdsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed
|
package dev.inmo.micro_utils.repos.exposed
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.Repo
|
import dev.inmo.micro_utils.repos.Repo
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
||||||
interface ExposedRepo : Repo {
|
interface ExposedRepo : Repo, FieldSet {
|
||||||
val database: Database
|
val database: Database
|
||||||
|
val selectAll: Transaction.() -> Query
|
||||||
|
get() = { (this@ExposedRepo as FieldSet).selectAll() }
|
||||||
}
|
}
|
@@ -19,7 +19,7 @@ class ExposedStandardVersionsRepoProxy(
|
|||||||
override val database: Database
|
override val database: Database
|
||||||
) : StandardVersionsRepoProxy<Database>, Table("ExposedVersionsProxy"), ExposedRepo {
|
) : StandardVersionsRepoProxy<Database>, Table("ExposedVersionsProxy"), ExposedRepo {
|
||||||
val tableNameColumn = text("tableName")
|
val tableNameColumn = text("tableName")
|
||||||
val tableVersionColumn = integer("tableName")
|
val tableVersionColumn = integer("tableVersion")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initTable()
|
initTable()
|
||||||
|
Reference in New Issue
Block a user