mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 22:39:25 +00:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
916f2f96f4 | |||
00cc214754 | |||
b2e38f72b9 | |||
e7107d238d | |||
ed9ebdbd1a | |||
e80676d3d2 | |||
02d02fa8f2 | |||
bd783fb74f | |||
50386adf70 | |||
f4ee6c2890 | |||
d45aef9fe5 | |||
a56cd3dddd | |||
419e7070ee | |||
612cf40b5f | |||
8b39882e83 | |||
e639ae172b | |||
d0446850ae | |||
c48465b90b | |||
f419fd03d2 | |||
494812a660 | |||
eb78f21eec | |||
4bda70268b | |||
f037ce4371 | |||
3d2196e35d | |||
a74f061b02 | |||
11ade14676 | |||
eb562d8784 | |||
1ee5b4bfd4 | |||
d97892080b | |||
6f37125724 | |||
ed1baaade7 |
6
.github/workflows/dokka_push.yml
vendored
6
.github/workflows/dokka_push.yml
vendored
@@ -10,10 +10,10 @@ 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 31.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/31.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
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew dokkaHtml
|
run: ./gradlew dokkaHtml
|
||||||
- name: Publish KDocs
|
- name: Publish KDocs
|
||||||
|
6
.github/workflows/packages_push.yml
vendored
6
.github/workflows/packages_push.yml
vendored
@@ -8,10 +8,10 @@ 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 31.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/31.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
|
||||||
- name: Rewrite version
|
- name: Rewrite version
|
||||||
run: |
|
run: |
|
||||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,5 +11,6 @@ out/
|
|||||||
|
|
||||||
secret.gradle
|
secret.gradle
|
||||||
local.properties
|
local.properties
|
||||||
|
kotlin-js-store
|
||||||
|
|
||||||
publishing.sh
|
publishing.sh
|
||||||
|
51
CHANGELOG.md
51
CHANGELOG.md
@@ -1,5 +1,56 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `AppCompat`: `1.3.1` -> `1.4.0`
|
||||||
|
* Android Compile SDK: `31.0.0` -> `32.0.0`
|
||||||
|
* `FSM`:
|
||||||
|
* `DefaultStatesMachine` now is extendable
|
||||||
|
* New type `UpdatableStatesMachine` with default realization`DefaultUpdatableStatesMachine`
|
||||||
|
|
||||||
|
## 0.8.7
|
||||||
|
|
||||||
|
* `Ktor`:
|
||||||
|
* `Client`:
|
||||||
|
* `UnifiedRequester` now have no private fields
|
||||||
|
* Add preview work with multipart
|
||||||
|
* `Server`
|
||||||
|
* `UnifiedRouter` now have no private fields
|
||||||
|
* Add preview work with multipart
|
||||||
|
|
||||||
## 0.8.6
|
## 0.8.6
|
||||||
|
|
||||||
* `Common`:
|
* `Common`:
|
||||||
|
@@ -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"
|
||||||
|
@@ -32,8 +32,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 +43,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 +75,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) {
|
||||||
|
@@ -23,11 +23,12 @@ value class FileName(val string: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@PreviewFeature
|
|
||||||
expect class MPPFile
|
expect class MPPFile
|
||||||
|
|
||||||
expect val MPPFile.filename: FileName
|
expect val MPPFile.filename: FileName
|
||||||
expect val MPPFile.filesize: Long
|
expect val MPPFile.filesize: Long
|
||||||
|
expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||||
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||||
|
fun MPPFile.bytesSync() = bytesAllocatorSync()
|
||||||
suspend fun MPPFile.bytes() = bytesAllocator()
|
suspend fun MPPFile.bytes() = bytesAllocator()
|
||||||
|
|
||||||
|
@@ -21,8 +21,10 @@ import kotlinx.serialization.Serializable
|
|||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Optional<T> internal constructor(
|
data class Optional<T> internal constructor(
|
||||||
internal val data: T?,
|
@Warning("It is unsafe to use this data directly")
|
||||||
internal val dataPresented: Boolean
|
val data: T?,
|
||||||
|
@Warning("It is unsafe to use this data directly")
|
||||||
|
val dataPresented: Boolean
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
@@ -42,17 +44,31 @@ inline val <T> T.optional
|
|||||||
/**
|
/**
|
||||||
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||||
*/
|
*/
|
||||||
fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
|
inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
|
||||||
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
|
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||||
|
*/
|
||||||
|
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
|
||||||
|
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will call [block] when data absent ([Optional.dataPresented] == false)
|
* Will call [block] when data absent ([Optional.dataPresented] == false)
|
||||||
*/
|
*/
|
||||||
fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
|
inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
|
||||||
if (!dataPresented) { block() }
|
if (!dataPresented) { block() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||||
|
*/
|
||||||
|
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
|
||||||
|
if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
|
||||||
*/
|
*/
|
||||||
@@ -67,9 +83,10 @@ fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Supp
|
|||||||
/**
|
/**
|
||||||
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
||||||
*/
|
*/
|
||||||
fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
|
||||||
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
||||||
|
@@ -2,8 +2,7 @@ package dev.inmo.micro_utils.common
|
|||||||
|
|
||||||
import org.khronos.webgl.ArrayBuffer
|
import org.khronos.webgl.ArrayBuffer
|
||||||
import org.w3c.dom.ErrorEvent
|
import org.w3c.dom.ErrorEvent
|
||||||
import org.w3c.files.File
|
import org.w3c.files.*
|
||||||
import org.w3c.files.FileReader
|
|
||||||
import kotlin.js.Promise
|
import kotlin.js.Promise
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,6 +23,11 @@ fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
|
|||||||
reader.readAsArrayBuffer(this)
|
reader.readAsArrayBuffer(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun MPPFile.readBytes(): ByteArray {
|
||||||
|
val reader = FileReaderSync()
|
||||||
|
return reader.readAsArrayBuffer(this).toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await()
|
private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,5 +44,11 @@ actual val MPPFile.filesize: Long
|
|||||||
* @suppress
|
* @suppress
|
||||||
*/
|
*/
|
||||||
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
|
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
|
||||||
|
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||||
|
get() = ::readBytes
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
|
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
|
||||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||||
get() = ::dirtyReadBytes
|
get() = ::dirtyReadBytes
|
||||||
|
@@ -22,6 +22,11 @@ actual val MPPFile.filesize: Long
|
|||||||
/**
|
/**
|
||||||
* @suppress
|
* @suppress
|
||||||
*/
|
*/
|
||||||
|
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||||
|
get() = ::readBytes
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||||
get() = {
|
get() = {
|
||||||
doInIO {
|
doInIO {
|
||||||
|
@@ -10,6 +10,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api project(":micro_utils.common")
|
||||||
api project(":micro_utils.coroutines")
|
api project(":micro_utils.coroutines")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,11 @@
|
|||||||
package dev.inmo.micro_utils.fsm.common
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.common.Optional
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.common.onPresented
|
||||||
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
|
* Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
|
||||||
@@ -42,17 +45,53 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
|
* Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
|
||||||
* resolving, and uses [launchStateHandling] for [State] handling
|
* resolving, and uses [launchStateHandling] for [State] handling.
|
||||||
|
*
|
||||||
|
* This class suppose to be extended in case you wish some custom behaviour inside of [launchStateHandling], for example
|
||||||
*/
|
*/
|
||||||
class DefaultStatesMachine <T: State>(
|
open class DefaultStatesMachine <T: State>(
|
||||||
private val statesManager: StatesManager<T>,
|
protected val statesManager: StatesManager<T>,
|
||||||
private val handlers: List<CheckableHandlerHolder<in T, T>>
|
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||||
) : StatesMachine<T> {
|
) : StatesMachine<T> {
|
||||||
/**
|
/**
|
||||||
* Will call [launchStateHandling] for state handling
|
* Will call [launchStateHandling] for state handling
|
||||||
*/
|
*/
|
||||||
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
|
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This
|
||||||
|
*/
|
||||||
|
protected val statesJobs = mutableMapOf<T, Job>()
|
||||||
|
protected val statesJobsMutex = Mutex()
|
||||||
|
|
||||||
|
protected open suspend fun performUpdate(state: T) {
|
||||||
|
val newState = launchStateHandling(state, handlers)
|
||||||
|
if (newState != null) {
|
||||||
|
statesManager.update(state, newState)
|
||||||
|
} else {
|
||||||
|
statesManager.endChain(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
|
||||||
|
statesJobsMutex.withLock {
|
||||||
|
statesJobs[actualState] ?.cancel()
|
||||||
|
statesJobs[actualState] = scope.launch {
|
||||||
|
performUpdate(actualState)
|
||||||
|
}.also { job ->
|
||||||
|
job.invokeOnCompletion { _ ->
|
||||||
|
scope.launch {
|
||||||
|
statesJobsMutex.withLock {
|
||||||
|
if (statesJobs[actualState] == job) {
|
||||||
|
statesJobs.remove(actualState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launch handling of states. On [statesManager] [StatesManager.onStartChain],
|
* Launch handling of states. On [statesManager] [StatesManager.onStartChain],
|
||||||
* [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If
|
* [statesManager] [StatesManager.onChainStateUpdated] will be called lambda with performing of state. If
|
||||||
@@ -60,23 +99,15 @@ class DefaultStatesMachine <T: State>(
|
|||||||
* [StatesManager.endChain].
|
* [StatesManager.endChain].
|
||||||
*/
|
*/
|
||||||
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||||
val statePerformer: suspend (T) -> Unit = { state: T ->
|
|
||||||
val newState = launchStateHandling(state, handlers)
|
|
||||||
if (newState != null) {
|
|
||||||
statesManager.update(state, newState)
|
|
||||||
} else {
|
|
||||||
statesManager.endChain(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
||||||
launch { statePerformer(it) }
|
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||||
}
|
}
|
||||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||||
launch { statePerformer(it.second) }
|
launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
statesManager.getActiveStates().forEach {
|
statesManager.getActiveStates().forEach {
|
||||||
launch { statePerformer(it) }
|
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,60 @@
|
|||||||
|
package dev.inmo.micro_utils.fsm.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This extender of [StatesMachine] interface declare one new function [updateChain]. Realizations of this interface
|
||||||
|
* must be able to perform update of chain in internal [StatesManager]
|
||||||
|
*/
|
||||||
|
interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
|
||||||
|
/**
|
||||||
|
* Update chain with current state equal to [currentState] with [newState]. Behaviour of this update preforming
|
||||||
|
* in cases when [currentState] does not exist in [StatesManager] must be declared inside of realization of
|
||||||
|
* [StatesManager.update] function
|
||||||
|
*/
|
||||||
|
suspend fun updateChain(currentState: T, newState: T)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class DefaultUpdatableStatesMachine<T : State>(
|
||||||
|
statesManager: StatesManager<T>,
|
||||||
|
handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||||
|
) : DefaultStatesMachine<T>(
|
||||||
|
statesManager,
|
||||||
|
handlers
|
||||||
|
), UpdatableStatesMachine<T> {
|
||||||
|
protected val jobsStates = mutableMapOf<Job, T>()
|
||||||
|
|
||||||
|
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
|
||||||
|
statesJobsMutex.withLock {
|
||||||
|
if (compare(previousState, actualState)) {
|
||||||
|
statesJobs[actualState] ?.cancel()
|
||||||
|
}
|
||||||
|
val job = previousState.mapOnPresented {
|
||||||
|
statesJobs.remove(it)
|
||||||
|
} ?.takeIf { it.isActive } ?: scope.launch {
|
||||||
|
performUpdate(actualState)
|
||||||
|
}.also { job ->
|
||||||
|
job.invokeOnCompletion { _ ->
|
||||||
|
scope.launch {
|
||||||
|
statesJobsMutex.withLock {
|
||||||
|
statesJobs.remove(
|
||||||
|
jobsStates[job] ?: return@withLock
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jobsStates.remove(job)
|
||||||
|
statesJobs[actualState] = job
|
||||||
|
jobsStates[job] = actualState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
|
||||||
|
|
||||||
|
override suspend fun updateChain(currentState: T, newState: T) {
|
||||||
|
statesManager.update(currentState, newState)
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,12 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
class FSMBuilder<T : State>(
|
class FSMBuilder<T : State>(
|
||||||
var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||||
|
val fsmBuilder: (statesManager: StatesManager<T>, states: List<CheckableHandlerHolder<T, T>>) -> StatesMachine<T> = { statesManager, states ->
|
||||||
|
StatesMachine(
|
||||||
|
statesManager,
|
||||||
|
states
|
||||||
|
)
|
||||||
|
},
|
||||||
var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
|
var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
|
||||||
) {
|
) {
|
||||||
private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
|
private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
|
||||||
@@ -42,7 +48,7 @@ class FSMBuilder<T : State>(
|
|||||||
add(filter, handler)
|
add(filter, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build() = StatesMachine(
|
fun build() = fsmBuilder(
|
||||||
statesManager,
|
statesManager,
|
||||||
states.toList().let { list ->
|
states.toList().let { list ->
|
||||||
defaultStateHandler ?.let { list + it.holder { true } } ?: list
|
defaultStateHandler ?.let { list + it.holder { true } } ?: list
|
||||||
|
@@ -7,14 +7,14 @@ 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.2
|
||||||
|
|
||||||
ktor_version=1.6.5
|
ktor_version=1.6.7
|
||||||
|
|
||||||
klockVersion=2.4.8
|
klockVersion=2.4.10
|
||||||
|
|
||||||
github_release_plugin_version=2.2.12
|
github_release_plugin_version=2.2.12
|
||||||
|
|
||||||
@@ -24,12 +24,12 @@ uuidVersion=0.3.1
|
|||||||
|
|
||||||
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.3.1
|
appcompat_version=1.4.0
|
||||||
|
|
||||||
android_minSdkVersion=19
|
android_minSdkVersion=19
|
||||||
android_compileSdkVersion=31
|
android_compileSdkVersion=32
|
||||||
android_buildToolsVersion=31.0.0
|
android_buildToolsVersion=32.0.0
|
||||||
dexcount_version=3.0.0
|
dexcount_version=3.0.1
|
||||||
junit_version=4.12
|
junit_version=4.12
|
||||||
test_ext_junit_version=1.1.2
|
test_ext_junit_version=1.1.2
|
||||||
espresso_core=3.3.0
|
espresso_core=3.3.0
|
||||||
@@ -40,10 +40,10 @@ crypto_js_version=4.1.1
|
|||||||
|
|
||||||
# Dokka
|
# Dokka
|
||||||
|
|
||||||
dokka_version=1.5.31
|
dokka_version=1.6.0
|
||||||
|
|
||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.8.6
|
version=0.9.1
|
||||||
android_code_version=86
|
android_code_version=91
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import io.ktor.client.request.forms.InputProvider
|
||||||
|
|
||||||
|
expect suspend fun MPPFile.inputProvider(): InputProvider
|
@@ -1,16 +1,20 @@
|
|||||||
package dev.inmo.micro_utils.ktor.client
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import dev.inmo.micro_utils.common.filename
|
||||||
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.request.get
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.request.post
|
import io.ktor.client.request.forms.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.utils.io.core.ByteReadPacket
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
||||||
|
|
||||||
class UnifiedRequester(
|
class UnifiedRequester(
|
||||||
private val client: HttpClient = HttpClient(),
|
val client: HttpClient = HttpClient(),
|
||||||
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
) {
|
) {
|
||||||
suspend fun <ResultType> uniget(
|
suspend fun <ResultType> uniget(
|
||||||
url: String,
|
url: String,
|
||||||
@@ -31,6 +35,54 @@ class UnifiedRequester(
|
|||||||
resultDeserializer: DeserializationStrategy<ResultType>
|
resultDeserializer: DeserializationStrategy<ResultType>
|
||||||
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
|
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
|
||||||
|
|
||||||
|
suspend fun <ResultType> unimultipart(
|
||||||
|
url: String,
|
||||||
|
filename: String,
|
||||||
|
inputProvider: InputProvider,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
||||||
|
|
||||||
|
suspend fun <BodyType, ResultType> unimultipart(
|
||||||
|
url: String,
|
||||||
|
filename: String,
|
||||||
|
inputProvider: InputProvider,
|
||||||
|
otherData: BodyPair<BodyType>,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
||||||
|
|
||||||
|
suspend fun <ResultType> unimultipart(
|
||||||
|
url: String,
|
||||||
|
mppFile: MPPFile,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||||
|
): ResultType = client.unimultipart(
|
||||||
|
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun <BodyType, ResultType> unimultipart(
|
||||||
|
url: String,
|
||||||
|
mppFile: MPPFile,
|
||||||
|
otherData: BodyPair<BodyType>,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||||
|
): ResultType = client.unimultipart(
|
||||||
|
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
||||||
|
)
|
||||||
|
|
||||||
fun <T> createStandardWebsocketFlow(
|
fun <T> createStandardWebsocketFlow(
|
||||||
url: String,
|
url: String,
|
||||||
checkReconnection: (Throwable?) -> Boolean = { true },
|
checkReconnection: (Throwable?) -> Boolean = { true },
|
||||||
@@ -69,3 +121,124 @@ suspend fun <BodyType, ResultType> HttpClient.unipost(
|
|||||||
}.let {
|
}.let {
|
||||||
serialFormat.decodeDefault(resultDeserializer, it)
|
serialFormat.decodeDefault(resultDeserializer, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun <ResultType> HttpClient.unimultipart(
|
||||||
|
url: String,
|
||||||
|
filename: String,
|
||||||
|
inputProvider: InputProvider,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
|
): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>(
|
||||||
|
url,
|
||||||
|
formData = formData {
|
||||||
|
append(
|
||||||
|
"bytes",
|
||||||
|
inputProvider,
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, mimetype)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
|
||||||
|
dataHeadersBuilder()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
additionalParametersBuilder()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
requestBuilder()
|
||||||
|
}.let { serialFormat.decodeDefault(resultDeserializer, it) }
|
||||||
|
|
||||||
|
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||||
|
url: String,
|
||||||
|
filename: String,
|
||||||
|
otherData: BodyPair<BodyType>,
|
||||||
|
inputProvider: InputProvider,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
|
): ResultType = unimultipart(
|
||||||
|
url,
|
||||||
|
filename,
|
||||||
|
inputProvider,
|
||||||
|
resultDeserializer,
|
||||||
|
mimetype,
|
||||||
|
additionalParametersBuilder = {
|
||||||
|
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
||||||
|
append(
|
||||||
|
"data",
|
||||||
|
InputProvider(serialized.size.toLong()) {
|
||||||
|
ByteReadPacket(serialized)
|
||||||
|
},
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
||||||
|
dataHeadersBuilder()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
additionalParametersBuilder()
|
||||||
|
},
|
||||||
|
dataHeadersBuilder,
|
||||||
|
requestBuilder,
|
||||||
|
serialFormat
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun <ResultType> HttpClient.unimultipart(
|
||||||
|
url: String,
|
||||||
|
mppFile: MPPFile,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
|
): ResultType = unimultipart(
|
||||||
|
url,
|
||||||
|
mppFile.filename.string,
|
||||||
|
mppFile.inputProvider(),
|
||||||
|
resultDeserializer,
|
||||||
|
mimetype,
|
||||||
|
additionalParametersBuilder,
|
||||||
|
dataHeadersBuilder,
|
||||||
|
requestBuilder,
|
||||||
|
serialFormat
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||||
|
url: String,
|
||||||
|
mppFile: MPPFile,
|
||||||
|
otherData: BodyPair<BodyType>,
|
||||||
|
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||||
|
mimetype: String = "*/*",
|
||||||
|
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||||
|
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||||
|
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||||
|
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||||
|
): ResultType = unimultipart(
|
||||||
|
url,
|
||||||
|
mppFile,
|
||||||
|
resultDeserializer,
|
||||||
|
mimetype,
|
||||||
|
additionalParametersBuilder = {
|
||||||
|
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
||||||
|
append(
|
||||||
|
"data",
|
||||||
|
InputProvider(serialized.size.toLong()) {
|
||||||
|
ByteReadPacket(serialized)
|
||||||
|
},
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
||||||
|
dataHeadersBuilder()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
additionalParametersBuilder()
|
||||||
|
},
|
||||||
|
dataHeadersBuilder,
|
||||||
|
requestBuilder,
|
||||||
|
serialFormat
|
||||||
|
)
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import io.ktor.client.request.forms.InputProvider
|
||||||
|
import io.ktor.utils.io.core.ByteReadPacket
|
||||||
|
|
||||||
|
actual suspend fun MPPFile.inputProvider(): InputProvider = bytes().let {
|
||||||
|
InputProvider(it.size.toLong()) {
|
||||||
|
ByteReadPacket(it)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import io.ktor.client.request.forms.InputProvider
|
||||||
|
import io.ktor.utils.io.streams.asInput
|
||||||
|
|
||||||
|
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
|
||||||
|
inputStream().asInput()
|
||||||
|
}
|
@@ -10,6 +10,7 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api internalProject("micro_utils.common")
|
||||||
api "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlin_serialisation_core_version"
|
api "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlin_serialisation_core_version"
|
||||||
api "com.soywiz.korlibs.klock:klock:$klockVersion"
|
api "com.soywiz.korlibs.klock:klock:$klockVersion"
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +1,31 @@
|
|||||||
package dev.inmo.micro_utils.ktor.server
|
package dev.inmo.micro_utils.ktor.server
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.*
|
||||||
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.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.ContentType
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.content.PartData
|
||||||
|
import io.ktor.http.content.forEachPart
|
||||||
import io.ktor.request.receive
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.request.receiveMultipart
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.response.respondBytes
|
import io.ktor.response.respondBytes
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
|
import io.ktor.util.asStream
|
||||||
|
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 kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
import java.io.File
|
||||||
|
import java.io.File.createTempFile
|
||||||
|
|
||||||
class UnifiedRouter(
|
class UnifiedRouter(
|
||||||
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||||
private val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||||
) {
|
) {
|
||||||
fun <T> Route.includeWebsocketHandling(
|
fun <T> Route.includeWebsocketHandling(
|
||||||
suburl: String,
|
suburl: String,
|
||||||
@@ -104,6 +113,139 @@ suspend fun <T> ApplicationCall.uniload(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun ApplicationCall.uniloadMultipart(
|
||||||
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||||
|
) = safely {
|
||||||
|
val multipartData = receiveMultipart()
|
||||||
|
|
||||||
|
var resultInput: Input? = null
|
||||||
|
|
||||||
|
multipartData.forEachPart {
|
||||||
|
when (it) {
|
||||||
|
is PartData.FormItem -> onFormItem(it)
|
||||||
|
is PartData.FileItem -> {
|
||||||
|
when (it.name) {
|
||||||
|
"bytes" -> resultInput = it.provider()
|
||||||
|
else -> onCustomFileItem(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PartData.BinaryItem -> onBinaryContent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultInput ?: error("Bytes has not been received")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> ApplicationCall.uniloadMultipart(
|
||||||
|
deserializer: DeserializationStrategy<T>,
|
||||||
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||||
|
): Pair<Input, T> {
|
||||||
|
var data: Optional<T>? = null
|
||||||
|
val resultInput = uniloadMultipart(
|
||||||
|
onFormItem,
|
||||||
|
{
|
||||||
|
if (it.name == "data") {
|
||||||
|
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
||||||
|
} else {
|
||||||
|
onCustomFileItem(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBinaryContent
|
||||||
|
)
|
||||||
|
|
||||||
|
val completeData = data ?: error("Data has not been received")
|
||||||
|
return resultInput to (completeData.dataOrNull().let { it as T })
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun <T> ApplicationCall.uniloadMultipartFile(
|
||||||
|
deserializer: DeserializationStrategy<T>,
|
||||||
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||||
|
) = safely {
|
||||||
|
val multipartData = receiveMultipart()
|
||||||
|
|
||||||
|
var resultInput: MPPFile? = null
|
||||||
|
var data: Optional<T>? = null
|
||||||
|
|
||||||
|
multipartData.forEachPart {
|
||||||
|
when (it) {
|
||||||
|
is PartData.FormItem -> onFormItem(it)
|
||||||
|
is PartData.FileItem -> {
|
||||||
|
when (it.name) {
|
||||||
|
"bytes" -> {
|
||||||
|
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
|
||||||
|
resultInput = MPPFile.createTempFile(
|
||||||
|
name.nameWithoutExtension.let {
|
||||||
|
var resultName = it
|
||||||
|
while (resultName.length < 3) {
|
||||||
|
resultName += "_"
|
||||||
|
}
|
||||||
|
resultName
|
||||||
|
},
|
||||||
|
".${name.extension}"
|
||||||
|
).apply {
|
||||||
|
outputStream().use { fileStream ->
|
||||||
|
it.provider().asStream().copyTo(fileStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
||||||
|
else -> onCustomFileItem(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PartData.BinaryItem -> onBinaryContent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val completeData = data ?: error("Data has not been received")
|
||||||
|
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun ApplicationCall.uniloadMultipartFile(
|
||||||
|
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||||
|
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||||
|
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||||
|
) = safely {
|
||||||
|
val multipartData = receiveMultipart()
|
||||||
|
|
||||||
|
var resultInput: MPPFile? = null
|
||||||
|
|
||||||
|
multipartData.forEachPart {
|
||||||
|
when (it) {
|
||||||
|
is PartData.FormItem -> onFormItem(it)
|
||||||
|
is PartData.FileItem -> {
|
||||||
|
if (it.name == "bytes") {
|
||||||
|
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
|
||||||
|
resultInput = MPPFile.createTempFile(
|
||||||
|
name.nameWithoutExtension.let {
|
||||||
|
var resultName = it
|
||||||
|
while (resultName.length < 3) {
|
||||||
|
resultName += "_"
|
||||||
|
}
|
||||||
|
resultName
|
||||||
|
},
|
||||||
|
".${name.extension}"
|
||||||
|
).apply {
|
||||||
|
outputStream().use { fileStream ->
|
||||||
|
it.provider().asStream().copyTo(fileStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onCustomFileItem(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PartData.BinaryItem -> onBinaryContent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultInput ?: error("Bytes has not been received")
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun ApplicationCall.getParameterOrSendError(
|
suspend fun ApplicationCall.getParameterOrSendError(
|
||||||
field: String
|
field: String
|
||||||
) = parameters[field].also {
|
) = parameters[field].also {
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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"}}}
|
@@ -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() }
|
||||||
|
@@ -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>,
|
||||||
|
@@ -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() }
|
||||||
|
|
||||||
|
@@ -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()
|
||||||
|
@@ -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()
|
||||||
|
Reference in New Issue
Block a user