Compare commits

..

3 Commits

173 changed files with 560 additions and 4842 deletions

View File

@@ -22,7 +22,7 @@ jobs:
run: ./gradlew build run: ./gradlew build
- name: Publish - name: Publish
continue-on-error: true continue-on-error: true
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidDebugPublication -x signAndroidReleasePublication -x signKotlinMultiplatformPublication
env: env:
GITHUBPACKAGES_USER: ${{ github.actor }} GITHUBPACKAGES_USER: ${{ github.actor }}
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,234 +1,5 @@
# Changelog # Changelog
## 0.11.1
* `Repos`
* `Ktor`
* In `configureReadKeyValueRepoRoutes` and `configureReadKeyValuesRepoRoutes` configurators fixed requiring of `reversed` property
## 0.11.0
* `Versions`
* `UUID`: `0.4.0` -> `0.4.1`
* `Ktor`
* `Client`:
* New extension fun `HttpResponse#throwOnUnsuccess`
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
* `Server`:
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
* `Repos`
* `Ktor`:
* Fully rewritten work with all declared repositories
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
## 0.10.8
* `Common`
* Add `Element.isOverflow*` extension properties
## 0.10.7
* `Pagination`:
* Now it is possible to use `doForAll*` and `getForAll` functions in non suspend places
## 0.10.6
* `Versions`
* `Ktor`: `2.0.1` -> `2.0.2`
* `Common`
* `JS`:
* Add `ResizeObserver` functionality
## 0.10.5
* `Versions`
* `Compose`: `1.2.0-alpha01-dev683` -> `1.2.0-alpha01-dev686`
* `Repos`
* `Android`:
* New function `SharedPreferencesKeyValueRepo`
* `FSM`
* Add `StateHandlingErrorHandler` and opportunity to handle states handling errors
## 0.10.4
* `Versions`:
* `Serialization`: `1.3.2` -> `1.3.3`
## 0.10.3
* `Versions`:
* `Compose`: `1.2.0-alpha01-dev682` -> `1.2.0-alpha01-dev683`
* `Coroutines`:
* Fixes in `AccumulatorFlow`
## 0.10.2
* `Versions`:
* `Compose`: `1.2.0-alpha01-dev675` -> `1.2.0-alpha01-dev682`
## 0.10.1
* `Versions`:
* `Ktor`: `2.0.0` -> `2.0.1`
* `Crypto`:
* Add `hmacSha256`
* Add `hex`
## 0.10.0
* `Versions`:
* `Kotlin`: `1.6.10` -> `1.6.21`
* `Compose`: `1.1.1` -> `1.2.0-alpha01-dev675`
* `Exposed`: `0.37.3` -> `0.38.2`
* `Ktor`: `1.6.8` -> `2.0.0`
* `Dokka`: `1.6.10` -> `1.6.21`
## 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
* `Versions`:
* `Klock`: `2.6.3` -> `2.7.0`
* `Common`:
* New extension `Node#onRemoved`
* `Compose`:
* New extension `Composition#linkWithRoot` for removing of composition with root element
* `Coroutines`:
* `Compose`:
* New function `renderComposableAndLinkToContextAndRoot` with linking of composition to root element
## 0.9.15
* `FSM`:
* Rename `DefaultUpdatableStatesMachine#compare` to `DefaultUpdatableStatesMachine#shouldReplaceJob`
* `DefaultStatesManager` now is extendable
* `DefaultStatesMachine` will stop all jobs of states which was removed from `statesManager`
## 0.9.14
* `Versions`:
* `Klock`: `2.6.2` -> `2.6.3`
* `Ktor`: `1.6.7` -> `1.6.8`
* `Ktor`:
* Add temporal files uploading functionality (for clients to upload and for server to receive)
## 0.9.13
* `Versions`:
* `Compose`: `1.1.0` -> `1.1.1`
## 0.9.12
* `Common`:
* `JS`:
* New function `openLink`
* New function `selectFile`
* New function `triggerDownloadFile`
* `Compose`:
* Created :)
* `Common`:
* `DefaultDisposableEffectResult` as a default realization of `DisposableEffectResult`
* `JS`:
* `openLink` on top of `openLink` with `String` target from common
* `Coroutines`:
* `Compose`:
* `Common`:
* New extension `Flow.toMutableState`
* New extension `StateFlow.toMutableState`
* `JS`:
* New function `selectFileOrThrow` on top of `selectFile` from `common`
* New function `selectFileOrNull` on top of `selectFile` from `common`
## 0.9.11
* `Versions`:
* `Klock`: `2.6.1` -> `2.6.2`
* `Coroutines`:
* `Compose`:
* Created :)
* New extensions and function:
* `Composition#linkWithJob`
* `Composition#linkWithContext`
* `renderComposableAndLinkToContext`
## 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 ## 0.9.7
* `Repos`: * `Repos`:

View File

@@ -10,7 +10,7 @@ kotlin {
sourceSets { sourceSets {
androidMain { androidMain {
dependencies { dependencies {
api libs.android.appCompat.resources api "androidx.appcompat:appcompat-resources:$appcompat_version"
} }
} }
} }

View File

@@ -10,13 +10,13 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api libs.kt.coroutines api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
api project(":micro_utils.common") api project(":micro_utils.common")
} }
} }
androidMain { androidMain {
dependencies { dependencies {
api libs.android.recyclerView api "androidx.recyclerview:recyclerview:$androidx_recycler_version"
} }
} }
} }

View File

@@ -7,12 +7,12 @@ buildscript {
} }
dependencies { dependencies {
classpath libs.buildscript.kt.gradle classpath 'com.android.tools.build:gradle:7.0.4'
classpath libs.buildscript.kt.serialization classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath libs.buildscript.jb.dokka classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath libs.buildscript.gh.release classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
classpath libs.buildscript.android.gradle classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
classpath libs.buildscript.android.dexcount classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
} }
} }
@@ -21,7 +21,6 @@ allprojects {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
google() google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
} }
// temporal crutch until legacy tests will be stabled or legacy target will be removed // temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

@@ -1,18 +0,0 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.common")
}
}
}
}

View File

@@ -1,16 +0,0 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.DisposableEffectResult
class DefaultDisposableEffectResult(
private val onDispose: () -> Unit
) : DisposableEffectResult {
override fun dispose() {
onDispose()
}
companion object {
val DoNothing = DefaultDisposableEffectResult {}
}
}

View File

@@ -1,9 +0,0 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.Composition
import dev.inmo.micro_utils.common.onRemoved
import org.w3c.dom.Element
fun Composition.linkWithElement(element: Element) {
element.onRemoved { dispose() }
}

View File

@@ -1,10 +0,0 @@
package dev.inmo.micro_utils.common.compose
import org.jetbrains.compose.web.attributes.ATarget
fun openLink(link: String, mode: ATarget = ATarget.Blank, features: String = "") = dev.inmo.micro_utils.common.openLink(
link,
mode.targetStr,
features
)

View File

@@ -1,13 +0,0 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.*
import org.jetbrains.compose.web.dom.DOMScope
import org.w3c.dom.Element
fun <TElement : Element> renderComposableAndLinkToRoot(
root: TElement,
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
content: @Composable DOMScope<TElement>.() -> Unit
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
linkWithElement(root)
}

View File

@@ -1 +0,0 @@
<manifest package="dev.inmo.micro_utils.common.compose"/>

View File

@@ -1,5 +1,3 @@
@file:Suppress("OPT_IN_IS_NOT_ENABLED")
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
@RequiresOptIn( @RequiresOptIn(

View File

@@ -43,7 +43,6 @@ private inline fun <T> performChanges(
if (oldOneEqualToNewObject || newOneEqualToOldObject) { if (oldOneEqualToNewObject || newOneEqualToOldObject) {
changedList.addAll( changedList.addAll(
potentialChanges.take(i).mapNotNull { potentialChanges.take(i).mapNotNull {
@Suppress("UNCHECKED_CAST")
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
} }
) )
@@ -122,10 +121,7 @@ fun <T> Iterable<T>.calculateDiff(
when { when {
oldObject === newObject || (oldObject == newObject && !strictComparison) -> { oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
changedObjects.addAll(potentiallyChangedObjects.map { changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
@Suppress("UNCHECKED_CAST")
it as Pair<IndexedValue<T>, IndexedValue<T>>
})
potentiallyChangedObjects.clear() potentiallyChangedObjects.clear()
} }
else -> { else -> {
@@ -157,22 +153,3 @@ inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDif
inline fun <T> Iterable<T>.calculateStrictDiff( inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T> other: Iterable<T>
) = calculateDiff(other, strictComparison = true) ) = calculateDiff(other, strictComparison = true)
/**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
strictComparison: Boolean = false
) = calculateDiff(source, strictComparison).let {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}
it.added.forEach { (i, t) ->
add(i, t)
}
it.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}

View File

@@ -27,13 +27,20 @@ sealed interface Either<T1, T2> {
@Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2")) @Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
val t2: T2? val t2: T2?
get() = optionalT2.dataOrNull() get() = optionalT2.dataOrNull()
companion object {
fun <T1, T2> serializer(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
): KSerializer<Either<T1, T2>> = EitherSerializer(t1Serializer, t2Serializer)
}
} }
class EitherSerializer<T1, T2>( class EitherSerializer<T1, T2>(
t1Serializer: KSerializer<T1>, t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>, t2Serializer: KSerializer<T2>,
) : KSerializer<Either<T1, T2>> { ) : KSerializer<Either<T1, T2>> {
@OptIn(InternalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor( override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer", "TypedSerializer",
SerialKind.CONTEXTUAL SerialKind.CONTEXTUAL
@@ -44,6 +51,7 @@ class EitherSerializer<T1, T2>(
private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer) private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer)
private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer) private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer)
@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
@@ -75,6 +83,7 @@ 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) {

View File

@@ -32,7 +32,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) { for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) { for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) { if (i + count > oldList.lastIndex) {
continue continue
} }
@@ -54,8 +54,8 @@ 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 oldList.indices) { for (step in 0 until oldList.size) {
for ((i, _) in withIndex) { for ((i, v) in withIndex) {
val mutable = oldList.toMutableList() val mutable = oldList.toMutableList()
val changes = ( val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step) if (step == 0) i until oldList.size else (i until oldList.size step step)
@@ -73,83 +73,4 @@ 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, _) 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, _) in withIndex) {
val mutable = oldList.toMutableList()
val newList = if (step == 0) {
i until oldList.size
} else {
i until oldList.size step step
}
newList.forEach { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value
}
}
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(
mutable,
mutableOldList
)
}
}
}
} }

View File

@@ -1,61 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.document
import org.w3c.dom.*
fun Node.onRemoved(block: () -> Unit): MutationObserver {
lateinit var observer: MutationObserver
observer = MutationObserver { _, _ ->
fun checkIfRemoved(node: Node): Boolean {
return node.parentNode != document && (node.parentNode ?.let { checkIfRemoved(it) } ?: true)
}
if (checkIfRemoved(this)) {
observer.disconnect()
block()
}
}
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
}
}
}

View File

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

View File

@@ -1,12 +0,0 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.Element
inline val Element.isOverflowWidth
get() = scrollWidth > clientWidth
inline val Element.isOverflowHeight
get() = scrollHeight > clientHeight
inline val Element.isOverflow
get() = isOverflowHeight || isOverflowWidth

View File

@@ -1,38 +0,0 @@
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)

View File

@@ -1,8 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.window
fun openLink(link: String, target: String = "_blank", features: String = "") {
window.open(link, target, features) ?.focus()
}

View File

@@ -1,58 +0,0 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.*
import kotlin.js.Json
import kotlin.js.json
external class ResizeObserver(
callback: (Array<ResizeObserverEntry>, ResizeObserver) -> Unit
) {
fun observe(target: Element, options: Json = definedExternally)
fun unobserve(target: Element)
fun disconnect()
}
external interface ResizeObserverSize {
val blockSize: Float
val inlineSize: Float
}
external interface ResizeObserverEntry {
val borderBoxSize: Array<ResizeObserverSize>
val contentBoxSize: Array<ResizeObserverSize>
val devicePixelContentBoxSize: Array<ResizeObserverSize>
val contentRect: DOMRectReadOnly
val target: Element
}
fun ResizeObserver.observe(target: Element, options: ResizeObserverObserveOptions) = observe(
target,
json(
"box" to options.box ?.name
)
)
class ResizeObserverObserveOptions(
val box: Box? = null
) {
sealed interface Box {
val name: String
object Content : Box {
override val name: String
get() = "content-box"
}
object Border : Box {
override val name: String
get() = "border-box"
}
object DevicePixelContent : Box {
override val name: String
get() = "device-pixel-content-box"
}
}
}

View File

@@ -1,30 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.document
import kotlinx.dom.createElement
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
import org.w3c.files.get
fun selectFile(
inputSetup: (HTMLInputElement) -> Unit = {},
onFailure: (Throwable) -> Unit = {},
onFile: (MPPFile) -> Unit
) {
(document.createElement("input") {
(this as HTMLInputElement).apply {
type = "file"
onchange = {
runCatching {
files ?.get(0) ?: error("File must not be null")
}.onSuccess {
onFile(it)
}.onFailure {
onFailure(it)
}
}
inputSetup(this)
}
} as HTMLElement).click()
}

View File

@@ -1,14 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.document
import org.w3c.dom.HTMLAnchorElement
fun triggerDownloadFile(filename: String, fileLink: String) {
val hiddenElement = document.createElement("a") as HTMLAnchorElement
hiddenElement.href = fileLink
hiddenElement.target = "_blank"
hiddenElement.download = filename
hiddenElement.click()
}

View File

@@ -10,17 +10,12 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api libs.kt.coroutines api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
}
}
jsMain {
dependencies {
api project(":micro_utils.common")
} }
} }
androidMain { androidMain {
dependencies { dependencies {
api libs.kt.coroutines.android api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
} }
} }
} }

View File

@@ -1,20 +0,0 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api libs.kt.coroutines
api project(":micro_utils.coroutines")
api project(":micro_utils.common.compose")
}
}
}
}

View File

@@ -1,23 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
fun <T> Flow<T>.toMutableState(
initial: T,
scope: CoroutineScope
): MutableState<T> {
val state = mutableStateOf(initial)
subscribeSafelyWithoutExceptions(scope) { state.value = it }
return state
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.toMutableState(
scope: CoroutineScope
): MutableState<T> = toMutableState(value, scope)

View File

@@ -1,14 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.job
import kotlin.coroutines.CoroutineContext
fun Composition.linkWithJob(job: Job) {
job.invokeOnCompletion {
this@linkWithJob.dispose()
}
}
fun Composition.linkWithContext(coroutineContext: CoroutineContext) = linkWithJob(coroutineContext.job)

View File

@@ -1,26 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import dev.inmo.micro_utils.common.compose.linkWithElement
import kotlinx.coroutines.*
import org.jetbrains.compose.web.dom.DOMScope
import org.w3c.dom.Element
suspend fun <TElement : Element> renderComposableAndLinkToContext(
root: TElement,
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
content: @Composable DOMScope<TElement>.() -> Unit
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
linkWithContext(
currentCoroutineContext()
)
}
suspend fun <TElement : Element> renderComposableAndLinkToContextAndRoot(
root: TElement,
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
content: @Composable DOMScope<TElement>.() -> Unit
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
linkWithContext(currentCoroutineContext())
linkWithElement(root)
}

View File

@@ -1 +0,0 @@
<manifest package="dev.inmo.micro_utils.coroutines.compose"/>

View File

@@ -6,12 +6,11 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.cancellation.CancellationException
private sealed interface AccumulatorFlowStep<T> private sealed interface AccumulatorFlowStep
private data class DataRetrievedAccumulatorFlowStep<T>(val data: T) : AccumulatorFlowStep<T> private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
private data class SubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T> private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
private data class UnsubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T> private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
/** /**
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences: * This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
@@ -27,12 +26,12 @@ class AccumulatorFlow<T>(
private val subscope = scope.LinkedSupervisorScope() private val subscope = scope.LinkedSupervisorScope()
private val activeData = ArrayDeque<T>() private val activeData = ArrayDeque<T>()
private val dataMutex = Mutex() private val dataMutex = Mutex()
private val channelsForBroadcast = mutableListOf<Channel<T>>() private val channelsForBroadcast = mutableListOf<Channel<Any>>()
private val channelsMutex = Mutex() private val channelsMutex = Mutex()
private val steps = subscope.actor<AccumulatorFlowStep<T>> { step -> private val steps = subscope.actor<AccumulatorFlowStep> { step ->
when (step) { when (step) {
is DataRetrievedAccumulatorFlowStep -> { is DataRetrievedAccumulatorFlowStep -> {
if (activeData.firstOrNull() === step.data) { if (activeData.first() === step.data) {
dataMutex.withLock { dataMutex.withLock {
activeData.removeFirst() activeData.removeFirst()
} }
@@ -43,7 +42,7 @@ class AccumulatorFlow<T>(
dataMutex.withLock { dataMutex.withLock {
val dataToSend = activeData.toList() val dataToSend = activeData.toList()
safelyWithoutExceptions { safelyWithoutExceptions {
dataToSend.forEach { step.channel.send(it) } dataToSend.forEach { step.channel.send(it as Any) }
} }
} }
} }
@@ -59,29 +58,24 @@ class AccumulatorFlow<T>(
channelsMutex.withLock { channelsMutex.withLock {
channelsForBroadcast.forEach { channel -> channelsForBroadcast.forEach { channel ->
safelyWithResult { safelyWithResult {
channel.send(it) channel.send(it as Any)
} }
} }
} }
} }
override suspend fun collectSafely(collector: FlowCollector<T>) { override suspend fun collectSafely(collector: FlowCollector<T>) {
val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND) val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
steps.send(SubscribeAccumulatorFlowStep(channel)) steps.send(SubscribeAccumulatorFlowStep(channel))
val result = runCatchingSafely {
for (data in channel) { for (data in channel) {
val emitResult = runCatchingSafely { try {
collector.emit(data) collector.emit(data as T)
}
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) {
steps.send(DataRetrievedAccumulatorFlowStep(data)) steps.send(DataRetrievedAccumulatorFlowStep(data))
} } finally {
emitResult.getOrThrow()
}
}
channel.cancel() channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel)) steps.send(UnsubscribeAccumulatorFlowStep(channel))
result.getOrThrow() }
}
} }
} }

View File

@@ -1,28 +0,0 @@
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()
}
}

View File

@@ -1,42 +0,0 @@
package dev.inmo.micro_utils.coroutines
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.selectFile
import kotlinx.coroutines.CompletableDeferred
import org.w3c.dom.HTMLInputElement
suspend fun selectFileOrThrow(
inputSetup: (HTMLInputElement) -> Unit = {}
): MPPFile {
val result = CompletableDeferred<MPPFile>()
selectFile(
inputSetup,
{
result.completeExceptionally(it)
}
) {
result.complete(it)
}
return result.await()
}
suspend fun selectFileOrNull(
inputSetup: (HTMLInputElement) -> Unit = {},
onFailure: (Throwable) -> Unit = {}
): MPPFile? {
val result = CompletableDeferred<MPPFile?>()
selectFile(
inputSetup,
{
result.complete(null)
onFailure(it)
}
) {
result.complete(it)
}
return result.await()
}

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.crypto
val HEX_ARRAY = "0123456789abcdef".toCharArray()
fun SourceBytes.hex(): String {
val hexChars = CharArray(size * 2)
for (j in indices) {
val v: Int = this[j].toInt() and 0xFF
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
}
return hexChars.concatToString()
}
fun SourceString.hex(): String = encodeToByteArray().hex()

View File

@@ -1,3 +0,0 @@
package dev.inmo.micro_utils.crypto
expect fun SourceString.hmacSha256(key: String): String

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.crypto
import kotlin.test.*
class Hex {
@Test
fun testSimpleHmacSHA256Message() {
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
val resultHex = text.hex()
assertEquals(
"4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742c2073656420646f20656975736d6f642074656d706f7220696e6369646964756e74207574206c61626f726520657420646f6c6f7265206d61676e6120616c697175612e20557420656e696d206164206d696e696d2076656e69616d2c2071756973206e6f737472756420657865726369746174696f6e20756c6c616d636f206c61626f726973206e69736920757420616c697175697020657820656120636f6d6d6f646f20636f6e7365717561742e2044756973206175746520697275726520646f6c6f7220696e20726570726568656e646572697420696e20766f6c7570746174652076656c697420657373652063696c6c756d20646f6c6f726520657520667567696174206e756c6c612070617269617475722e204578636570746575722073696e74206f6363616563617420637570696461746174206e6f6e2070726f6964656e742c2073756e7420696e2063756c706120717569206f666669636961206465736572756e74206d6f6c6c697420616e696d20696420657374206c61626f72756d2e",
resultHex
)
}
}

View File

@@ -1,12 +0,0 @@
package dev.inmo.micro_utils.crypto
import kotlin.test.*
class HmacSHA256 {
@Test
fun testSimpleHmacSHA256Message() {
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
val resultSha = text.hmacSha256("Example")
assertEquals("5a481d59329ef862b158eedc95392ebb22492ba3014661a3379d8201db992484", resultSha)
}
}

View File

@@ -7,7 +7,3 @@ external interface CryptoJs {
@JsModule("crypto-js") @JsModule("crypto-js")
@JsNonModule @JsNonModule
external val CryptoJS: CryptoJs external val CryptoJS: CryptoJs
actual fun SourceString.hmacSha256(key: String): String {
return CryptoJS.asDynamic().HmacSHA256(this, key).toString().unsafeCast<String>()
}

View File

@@ -1,13 +0,0 @@
package dev.inmo.micro_utils.crypto
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
actual fun SourceString.hmacSha256(key: String): String {
val mac = Mac.getInstance("HmacSHA256")
val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
mac.init(secretKey)
return mac.doFinal(toByteArray()).hex()
}

View File

@@ -26,12 +26,12 @@ ext {
} }
android { android {
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger() compileSdkVersion "$android_compileSdkVersion".toInteger()
buildToolsVersion libs.versions.android.props.buildTools.get() buildToolsVersion "$android_buildToolsVersion"
defaultConfig { defaultConfig {
minSdkVersion libs.versions.android.props.minSdk.get().toInteger() minSdkVersion "$android_minSdkVersion".toInteger()
targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger() targetSdkVersion "$android_compileSdkVersion".toInteger()
versionCode "${android_code_version}".toInteger() versionCode "${android_code_version}".toInteger()
versionName "$version" versionName "$version"
} }

View File

@@ -21,7 +21,6 @@ allprojects {
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true" releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"

View File

@@ -3,8 +3,6 @@ package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.Optional import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.common.onPresented import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.* import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@@ -15,24 +13,13 @@ import kotlinx.coroutines.sync.withLock
* handling until [start] method will be called * handling until [start] method will be called
*/ */
interface StatesMachine<T : State> : StatesHandler<T, T> { interface StatesMachine<T : State> : StatesHandler<T, T> {
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T>
): T? {
return runCatchingSafely {
handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}.getOrElse {
onStateHandlingErrorHandler(state, it)
}
}
suspend fun launchStateHandling( suspend fun launchStateHandling(
state: T, state: T,
handlers: List<CheckableHandlerHolder<in T, T>> handlers: List<CheckableHandlerHolder<in T, T>>
): T? { ): T? {
return launchStateHandling(state, handlers, defaultStateHandlingErrorHandler()) return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
} }
/** /**
@@ -51,9 +38,8 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
*/ */
operator fun <T: State> invoke( operator fun <T: State> invoke(
statesManager: StatesManager<T>, statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>, handlers: List<CheckableHandlerHolder<in T, T>>
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler() ) = DefaultStatesMachine(statesManager, handlers)
) = DefaultStatesMachine(statesManager, handlers, onStateHandlingErrorHandler)
} }
} }
@@ -66,17 +52,12 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
open class DefaultStatesMachine <T: State>( open class DefaultStatesMachine <T: State>(
protected val statesManager: StatesManager<T>, protected val statesManager: StatesManager<T>,
protected val handlers: List<CheckableHandlerHolder<in T, T>>, protected val handlers: List<CheckableHandlerHolder<in T, T>>,
protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : 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)
override suspend fun launchStateHandling(state: T, handlers: List<CheckableHandlerHolder<in T, T>>): T? {
return launchStateHandling(state, handlers, onStateHandlingErrorHandler)
}
/** /**
* This * This
*/ */
@@ -124,16 +105,6 @@ open class DefaultStatesMachine <T: State>(
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) } launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
} }
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) { removedState ->
launch {
statesJobsMutex.withLock {
val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState }
if (stateInMap === removedState) {
statesJobs[stateInMap] ?.cancel()
}
}
}
}
statesManager.getActiveStates().forEach { statesManager.getActiveStates().forEach {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) } launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }

View File

@@ -1,8 +1,6 @@
package dev.inmo.micro_utils.fsm.common package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.* import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@@ -22,23 +20,15 @@ interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
open class DefaultUpdatableStatesMachine<T : State>( open class DefaultUpdatableStatesMachine<T : State>(
statesManager: StatesManager<T>, statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>, handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : DefaultStatesMachine<T>( ) : DefaultStatesMachine<T>(
statesManager, statesManager,
handlers, handlers
onStateHandlingErrorHandler
), UpdatableStatesMachine<T> { ), UpdatableStatesMachine<T> {
protected val jobsStates = mutableMapOf<Job, T>() protected val jobsStates = mutableMapOf<Job, T>()
/**
* Realization of this update will use the [Job] of [previousState] in [statesJobs] and [jobsStates] if
* [previousState] is [Optional.presented] and [shouldReplaceJob] has returned true for [previousState] and [actualState]. In
* other words, [Job] of [previousState] WILL NOT be replaced with the new one if they are "equal". Equality of
* states is solved in [shouldReplaceJob] and can be rewritten in subclasses
*/
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) { override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock { statesJobsMutex.withLock {
if (shouldReplaceJob(previousState, actualState)) { if (compare(previousState, actualState)) {
statesJobs[actualState] ?.cancel() statesJobs[actualState] ?.cancel()
} }
val job = previousState.mapOnPresented { val job = previousState.mapOnPresented {
@@ -62,13 +52,7 @@ open class DefaultUpdatableStatesMachine<T : State>(
} }
} }
/** protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
* Compare if [previous] potentially lead to the same behaviour with [new]
*/
protected open suspend fun shouldReplaceJob(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
@Deprecated("Overwrite shouldReplaceJob instead")
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = shouldReplaceJob(previous, new)
override suspend fun updateChain(currentState: T, newState: T) { override suspend fun updateChain(currentState: T, newState: T) {
statesManager.update(currentState, newState) statesManager.update(currentState, newState)

View File

@@ -37,49 +37,36 @@ 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 onStartContextsConflictResolver Receive current [State] and the state passed with [startChain]. In case when * @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* 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>( class DefaultStatesManager<T : State>(
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(), private val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true }, private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: 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) private 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()
protected val _onStartChain = MutableSharedFlow<T>(0) private val _onStartChain = MutableSharedFlow<T>(0)
override val onStartChain: Flow<T> = _onStartChain.asSharedFlow() override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
protected val _onEndChain = MutableSharedFlow<T>(0) private val _onEndChain = MutableSharedFlow<T>(0)
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow() override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
protected val mapMutex = Mutex() private 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 {
stateByOldContext != old -> return@withLock stateByOldContext != old -> return@withLock
stateByOldContext == null || old.context == new.context -> { stateByOldContext == null || old.context == new.context -> {
repo.removeState(old)
repo.set(new) repo.set(new)
_onChainStateUpdated.emit(old to new) _onChainStateUpdated.emit(old to new)
} }
else -> { else -> {
val stateOnNewOneContext = repo.getContextState(new.context) val stateOnNewOneContext = repo.getContextState(new.context)
if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) { if (stateOnNewOneContext == null || onContextsConflictResolver(old, new, stateOnNewOneContext)) {
stateOnNewOneContext ?.let { endChainWithoutLock(it) } stateOnNewOneContext ?.let { endChainWithoutLock(it) }
repo.removeState(old) repo.removeState(old)
repo.set(new) repo.set(new)
@@ -90,17 +77,13 @@ open class DefaultStatesManager<T : State>(
} }
override suspend fun startChain(state: T) = mapMutex.withLock { override suspend fun startChain(state: T) = mapMutex.withLock {
val stateOnContext = repo.getContextState(state.context) if (!repo.contains(state.context)) {
if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) {
stateOnContext ?.let {
endChainWithoutLock(it)
}
repo.set(state) repo.set(state)
_onStartChain.emit(state) _onStartChain.emit(state)
} }
} }
protected open suspend fun endChainWithoutLock(state: T) { private suspend fun endChainWithoutLock(state: T) {
if (repo.getContextState(state.context) == state) { if (repo.getContextState(state.context) == state) {
repo.removeState(state) repo.removeState(state)
_onEndChain.emit(state) _onEndChain.emit(state)

View File

@@ -12,6 +12,5 @@ import kotlinx.coroutines.flow.*
*/ */
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager")) @Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
fun <T: State> InMemoryStatesManager( fun <T: State> InMemoryStatesManager(
onStartContextsConflictResolver: suspend (old: T, new: T) -> Boolean = { _, _ -> true }, onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true } ) = DefaultStatesManager(onContextsConflictResolver = onContextsConflictResolver)
) = DefaultStatesManager(onStartContextsConflictResolver = onStartContextsConflictResolver, onUpdateContextsConflictResolver = onUpdateContextsConflictResolver)

View File

@@ -1,6 +0,0 @@
package dev.inmo.micro_utils.fsm.common.utils
typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T?
val DefaultStateHandlingErrorHandler: StateHandlingErrorHandler<*> = { _, _ -> null }
inline fun <T> defaultStateHandlingErrorHandler(): StateHandlingErrorHandler<T> = DefaultStateHandlingErrorHandler as StateHandlingErrorHandler<T>

View File

@@ -7,12 +7,43 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx2g
kotlin_version=1.6.10
kotlin_coroutines_version=1.6.0
kotlin_serialisation_core_version=1.3.2
kotlin_exposed_version=0.37.2
ktor_version=1.6.7
klockVersion=2.4.13
github_release_plugin_version=2.2.12
uuidVersion=0.4.0
# ANDROID
core_ktx_version=1.7.0
androidx_recycler_version=1.2.1
appcompat_version=1.4.0
android_minSdkVersion=19
android_compileSdkVersion=32
android_buildToolsVersion=32.0.0
dexcount_version=3.0.1
junit_version=4.12
test_ext_junit_version=1.1.2
espresso_core=3.3.0
# JS NPM # JS NPM
crypto_js_version=4.1.1 crypto_js_version=4.1.1
# Dokka
dokka_version=1.6.10
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.11.1 version=0.9.7
android_code_version=125 android_code_version=97

View File

@@ -1,87 +0,0 @@
[versions]
kt = "1.6.21"
kt-serialization = "1.3.3"
kt-coroutines = "1.6.1"
jb-compose = "1.2.0-alpha01-dev686"
jb-exposed = "0.38.2"
jb-dokka = "1.6.21"
klock = "2.7.0"
uuid = "0.4.1"
ktor = "2.0.2"
gh-release = "2.3.7"
android-gradle = "7.0.4"
dexcount = "3.1.0"
android-coreKtx = "1.7.0"
android-recyclerView = "1.2.1"
android-appCompat = "1.4.1"
android-espresso = "3.3.0"
android-test = "1.1.2"
android-props-minSdk = "19"
android-props-compileSdk = "32"
android-props-buildTools = "32.0.0"
[libraries]
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" }
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
kt-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kt-coroutines" }
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" }
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" }
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gradle-plugin", version.ref = "dexcount" }
[plugins]
jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" }

View File

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

View File

@@ -12,7 +12,7 @@ kotlin {
dependencies { dependencies {
api internalProject("micro_utils.ktor.common") api internalProject("micro_utils.ktor.common")
api internalProject("micro_utils.coroutines") api internalProject("micro_utils.coroutines")
api libs.ktor.client api "io.ktor:ktor-client-core:$ktor_version"
} }
} }
} }

View File

@@ -1,18 +1,13 @@
package dev.inmo.micro_utils.ktor.client package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.coroutines.runCatchingSafely
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.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull import io.ktor.client.features.websocket.ws
import io.ktor.client.plugins.websocket.WebSockets import io.ktor.http.cio.websocket.Frame
import io.ktor.client.plugins.websocket.ws import io.ktor.http.cio.websocket.readBytes
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.websocket.Frame
import io.ktor.websocket.readBytes
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
/** /**
@@ -21,41 +16,42 @@ import kotlinx.serialization.DeserializationStrategy
*/ */
inline fun <T> HttpClient.createStandardWebsocketFlow( inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String, url: String,
crossinline checkReconnection: suspend (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> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow { return channelFlow {
val producerScope = this@channelFlow
do { do {
val reconnect = runCatchingSafely { val reconnect = try {
ws(correctedUrl, requestBuilder) { safely {
ws(correctedUrl) {
for (received in incoming) { for (received in incoming) {
when (received) { when (received) {
is Frame.Binary -> send(conversation(received.data)) is Frame.Binary -> producerScope.send(conversation(received.readBytes()))
else -> { else -> {
close() producerScope.close()
return@ws return@ws
} }
} }
} }
} }
}
checkReconnection(null) checkReconnection(null)
}.getOrElse { e -> } catch (e: Throwable) {
checkReconnection(e).also { checkReconnection(e).also {
if (!it) { if (!it) {
close(e) producerScope.close(e)
} }
} }
} }
} while (reconnect && isActive) } while (reconnect)
if (!producerScope.isClosedForSend) {
if (isActive) { safely(
safely { { it.printStackTrace() }
close() ) {
producerScope.close()
} }
} }
} }
@@ -67,14 +63,12 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
*/ */
inline fun <T> HttpClient.createStandardWebsocketFlow( inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String, url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, 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)
} }

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.statement.HttpResponse
import io.ktor.http.isSuccess
inline fun HttpResponse.throwOnUnsuccess(
unsuccessMessage: () -> String
) {
if (status.isSuccess()) {
return
}
throw ClientRequestException(this, unsuccessMessage())
}

View File

@@ -1,55 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull
import io.ktor.client.plugins.websocket.*
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.websocket.Frame
import io.ktor.websocket.readBytes
import io.ktor.websocket.serialization.sendSerializedBase
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
import kotlinx.serialization.DeserializationStrategy
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T> HttpClient.createStandardWebsocketFlow(
url: String,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow {
do {
val reconnect = runCatchingSafely {
ws(correctedUrl, requestBuilder) {
for (received in incoming) {
sendSerialized(received.data)
}
}
checkReconnection(null)
}.getOrElse { e ->
checkReconnection(e).also {
if (!it) {
close(e)
}
}
}
} while (reconnect && isActive)
if (isActive) {
safely {
close()
}
}
}
}

View File

@@ -4,10 +4,8 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename 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.call.body
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.request.forms.* import io.ktor.client.request.forms.*
import io.ktor.client.statement.readBytes
import io.ktor.http.* import io.ktor.http.*
import io.ktor.utils.io.core.ByteReadPacket import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.serialization.* import kotlinx.serialization.*
@@ -87,16 +85,9 @@ class UnifiedRequester(
fun <T> createStandardWebsocketFlow( fun <T> createStandardWebsocketFlow(
url: String, url: String,
checkReconnection: suspend (Throwable?) -> Boolean, checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>
requestBuilder: HttpRequestBuilder.() -> Unit = {}, ) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat)
) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
fun <T> createStandardWebsocketFlow(
url: String,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
} }
val defaultRequester = UnifiedRequester() val defaultRequester = UnifiedRequester()
@@ -105,8 +96,10 @@ suspend fun <ResultType> HttpClient.uniget(
url: String, url: String,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = get(url).let { ) = get<StandardKtorSerialInputData>(
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
} }
@@ -123,12 +116,10 @@ suspend fun <BodyType, ResultType> HttpClient.unipost(
bodyInfo: BodyPair<BodyType>, bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>, resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = post(url) { ) = post<StandardKtorSerialInputData>(url) {
setBody( body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
)
}.let { }.let {
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) serialFormat.decodeDefault(resultDeserializer, it)
} }
suspend fun <ResultType> HttpClient.unimultipart( suspend fun <ResultType> HttpClient.unimultipart(
@@ -141,7 +132,7 @@ suspend fun <ResultType> HttpClient.unimultipart(
dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}, requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = submitFormWithBinaryData( ): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>(
url, url,
formData = formData { formData = formData {
append( append(
@@ -157,7 +148,7 @@ suspend fun <ResultType> HttpClient.unimultipart(
} }
) { ) {
requestBuilder() requestBuilder()
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) } }.let { serialFormat.decodeDefault(resultDeserializer, it) }
suspend fun <BodyType, ResultType> HttpClient.unimultipart( suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String, url: String,

View File

@@ -1,19 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
expect suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
): TemporalFileId
suspend fun UnifiedRequester.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
): TemporalFileId = client.tempUpload(
fullTempUploadDraftPath, file, onUpload
)

View File

@@ -1,58 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import io.ktor.client.HttpClient
import kotlinx.coroutines.*
import org.w3c.xhr.*
suspend fun tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (Long, Long) -> Unit
): TemporalFileId {
val formData = FormData()
val answer = CompletableDeferred<TemporalFileId>()
formData.append(
"data",
file
)
val request = XMLHttpRequest()
request.responseType = XMLHttpRequestResponseType.TEXT
request.upload.onprogress = {
onUpload(it.loaded.toLong(), it.total.toLong())
}
request.onload = {
if (request.status == 200.toShort()) {
answer.complete(TemporalFileId(request.responseText))
} else {
answer.completeExceptionally(Exception("Something went wrong: $it"))
}
}
request.onerror = {
answer.completeExceptionally(Exception("Something went wrong: $it"))
}
request.open("POST", fullTempUploadDraftPath, true)
request.send(formData)
val handle = currentCoroutineContext().job.invokeOnCompletion {
runCatching {
request.abort()
}
}
return runCatching {
answer.await()
}.also {
handle.dispose()
}.getOrThrow()
}
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)

View File

@@ -1,40 +0,0 @@
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.TemporalFileId
import io.ktor.client.HttpClient
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import java.net.URLConnection
internal val MPPFile.mimeType: String
get() = URLConnection.getFileNameMap().getContentTypeFor(filename.name) ?: "*/*"
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (Long, Long) -> Unit
): TemporalFileId {
val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData(
fullTempUploadDraftPath,
formData = formData {
append(
"data",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, file.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
}
)
}
) {
onUpload(onUpload)
}.bodyAsText()
return TemporalFileId(fileId)
}

View File

@@ -11,10 +11,8 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
api internalProject("micro_utils.common") api internalProject("micro_utils.common")
api libs.kt.serialization.cbor api "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlin_serialisation_core_version"
api libs.klock api "com.soywiz.korlibs.klock:klock:$klockVersion"
api libs.uuid
api libs.ktor.io
} }
} }
} }

View File

@@ -1,6 +0,0 @@
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

View File

@@ -1,5 +1,3 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.ktor.common package dev.inmo.micro_utils.ktor.common
import kotlinx.serialization.* import kotlinx.serialization.*

View File

@@ -1,10 +0,0 @@
package dev.inmo.micro_utils.ktor.common
import kotlin.jvm.JvmInline
import kotlinx.serialization.Serializable
const val DefaultTemporalFilesSubPath = "temp_upload"
@Serializable
@JvmInline
value class TemporalFileId(val string: String)

View File

@@ -1,7 +0,0 @@
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())

View File

@@ -1,7 +0,0 @@
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()

View File

@@ -16,11 +16,10 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
api libs.ktor.server api "io.ktor:ktor-server:$ktor_version"
api libs.ktor.server.cio api "io.ktor:ktor-server-cio:$ktor_version"
api libs.ktor.server.host.common api "io.ktor:ktor-server-host-common:$ktor_version"
api libs.ktor.server.websockets api "io.ktor:ktor-websockets:$ktor_version"
api libs.ktor.server.statusPages
} }
} }
} }

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.responseType
import io.ktor.util.InternalAPI
import io.ktor.util.reflect.TypeInfo
@InternalAPI
suspend fun <T : Any> ApplicationCall.respond(
message: T,
typeInfo: TypeInfo
) {
response.responseType = typeInfo
response.pipeline.execute(this, message as Any)
}

View File

@@ -2,31 +2,27 @@ 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.http.URLProtocol import io.ktor.application.featureOrNull
import io.ktor.server.application.install import io.ktor.application.install
import io.ktor.server.application.pluginOrNull import io.ktor.http.cio.websocket.*
import io.ktor.server.routing.Route import io.ktor.routing.Route
import io.ktor.server.routing.application import io.ktor.routing.application
import io.ktor.server.websocket.* import io.ktor.websocket.webSocket
import io.ktor.websocket.send
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
fun <T> Route.includeWebsocketHandling( fun <T> Route.includeWebsocketHandling(
suburl: String, suburl: String,
flow: Flow<T>, flow: Flow<T>,
protocol: URLProtocol? = null, converter: (T) -> StandardKtorSerialInputData
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
) { ) {
application.apply { application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets) featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets)
} }
webSocket(suburl, protocol ?.name) { webSocket(suburl) {
safely { safely {
flow.collect { flow.collect {
converter(it) ?.let { data -> send(converter(it))
send(data)
}
} }
} }
} }
@@ -36,24 +32,10 @@ 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? = null,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling( ) = includeWebsocketHandling(
suburl, suburl,
flow, flow
protocol, ) {
converter = if (filter == null) {
{
serialFormat.encodeDefault(serializer, it) serialFormat.encodeDefault(serializer, it)
} }
} else {
{
if (filter(it)) {
serialFormat.encodeDefault(serializer, it)
} else {
null
}
}
}
)

View File

@@ -1,30 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.application.pluginOrNull
import io.ktor.server.routing.Route
import io.ktor.server.routing.application
import io.ktor.server.websocket.*
import io.ktor.websocket.send
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy
inline fun <reified T : Any> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
protocol: URLProtocol? = null
) {
application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets)
}
webSocket(suburl, protocol ?.name) {
safely {
flow.collect {
sendSerialized(it)
}
}
}
}

View File

@@ -3,21 +3,25 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.* 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.http.* import io.ktor.application.ApplicationCall
import io.ktor.http.content.* import io.ktor.application.call
import io.ktor.server.application.ApplicationCall import io.ktor.http.ContentType
import io.ktor.server.application.call import io.ktor.http.HttpStatusCode
import io.ktor.server.request.receive import io.ktor.http.content.PartData
import io.ktor.server.request.receiveMultipart import io.ktor.http.content.forEachPart
import io.ktor.server.response.respond import io.ktor.request.receive
import io.ktor.server.response.respondBytes import io.ktor.request.receiveMultipart
import io.ktor.server.routing.Route import io.ktor.response.respond
import io.ktor.server.websocket.WebSocketServerSession import io.ktor.response.respondBytes
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 io.ktor.utils.io.core.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.*
import kotlinx.serialization.SerializationStrategy import java.io.File
import java.io.File.createTempFile
class UnifiedRouter( class UnifiedRouter(
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
@@ -26,10 +30,8 @@ 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>
protocol: URLProtocol? = null, ) = includeWebsocketHandling(suburl, flow, serializer, serialFormat)
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>,
@@ -88,11 +90,6 @@ class UnifiedRouter(
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
} }
} }
companion object {
val default
get() = defaultUnifiedRouter
}
} }
val defaultUnifiedRouter = UnifiedRouter() val defaultUnifiedRouter = UnifiedRouter()
@@ -193,9 +190,7 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
".${name.extension}" ".${name.extension}"
).apply { ).apply {
outputStream().use { fileStream -> outputStream().use { fileStream ->
it.streamProvider().use { it.provider().asStream().copyTo(fileStream)
it.copyTo(fileStream)
}
} }
} }
} }
@@ -237,9 +232,7 @@ suspend fun ApplicationCall.uniloadMultipartFile(
".${name.extension}" ".${name.extension}"
).apply { ).apply {
outputStream().use { fileStream -> outputStream().use { fileStream ->
it.streamProvider().use { it.provider().asStream().copyTo(fileStream)
it.copyTo(fileStream)
}
} }
} }
} else { } else {

View File

@@ -1,9 +1,8 @@
package dev.inmo.micro_utils.ktor.server 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.server.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
@@ -11,21 +10,17 @@ 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 = embeddedServer( ): TEngine {
engine, val env = applicationEngineEnvironment {
applicationEngineEnvironment {
module(block) module(block)
connector { connector {
this.host = host this@connector.host = host
this.port = port this@connector.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
@@ -35,31 +30,18 @@ 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
): CIOApplicationEngine = createKtorServer( ): ApplicationEngine = createKtorServer(CIO, host, port, block)
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() } }
} }
@@ -72,7 +54,5 @@ 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>
additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, ): ApplicationEngine = createKtorServer(CIO, host, port, configurators)
additionalConfigurationConfigurator: CIOApplicationEngine.Configuration.() -> Unit = {},
): ApplicationEngine = createKtorServer(CIO, host, port, additionalEngineEnvironmentConfigurator, additionalConfigurationConfigurator, configurators)

View File

@@ -1,133 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.common.FileName
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.common.DefaultTemporalFilesSubPath
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.streamProvider
import io.ktor.server.application.call
import io.ktor.server.request.receiveMultipart
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
import java.nio.file.Files
import java.nio.file.attribute.FileTime
class TemporalFilesRoutingConfigurator(
private val subpath: String = DefaultTemporalFilesSubPath,
private val unifiedRouter: UnifiedRouter = UnifiedRouter.default,
private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer
) : ApplicationRoutingConfigurator.Element {
interface TemporalFilesUtilizer {
fun start(filesMap: MutableMap<TemporalFileId, MPPFile>, filesMutex: Mutex, onNewFileFlow: Flow<TemporalFileId>): Job
companion object : TemporalFilesUtilizer {
class ByTimerUtilizer(
private val removeMillis: Long,
private val scope: CoroutineScope
) : TemporalFilesUtilizer {
override fun start(
filesMap: MutableMap<TemporalFileId, MPPFile>,
filesMutex: Mutex,
onNewFileFlow: Flow<TemporalFileId>
): Job = scope.launchSafelyWithoutExceptions {
while (isActive) {
val filesWithCreationInfo = filesMap.mapNotNull { (fileId, file) ->
fileId to ((Files.getAttribute(file.toPath(), "creationTime") as? FileTime) ?.toMillis() ?: return@mapNotNull null)
}
if (filesWithCreationInfo.isEmpty()) {
delay(removeMillis)
continue
}
var min = filesWithCreationInfo.first()
for (fileWithCreationInfo in filesWithCreationInfo) {
if (fileWithCreationInfo.second < min.second) {
min = fileWithCreationInfo
}
}
delay(System.currentTimeMillis() - (min.second + removeMillis))
filesMutex.withLock {
filesMap.remove(min.first)
} ?.delete()
}
}
}
override fun start(
filesMap: MutableMap<TemporalFileId, MPPFile>,
filesMutex: Mutex,
onNewFileFlow: Flow<TemporalFileId>
): Job = Job()
}
}
private val temporalFilesMap = mutableMapOf<TemporalFileId, MPPFile>()
private val temporalFilesMutex = Mutex()
private val filesFlow = MutableSharedFlow<TemporalFileId>()
val utilizerJob = temporalFilesUtilizer.start(temporalFilesMap, temporalFilesMutex, filesFlow.asSharedFlow())
override fun Route.invoke() {
post(subpath) {
unifiedRouter.apply {
val multipart = call.receiveMultipart()
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
var part = multipart.readPart()
while (part != null) {
if (part is PartData.FileItem) {
break
}
part = multipart.readPart()
}
part ?.let {
if (it is PartData.FileItem) {
val fileId = TemporalFileId(uuid4().toString())
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
outputStream().use { outputStream ->
it.streamProvider().use {
it.copyTo(outputStream)
}
}
deleteOnExit()
}
}
}
fileInfo ?.also { (fileId, file) ->
temporalFilesMutex.withLock {
temporalFilesMap[fileId] = file
}
call.respondText(fileId.string)
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
} ?: call.respond(HttpStatusCode.BadRequest)
}
}
}
suspend fun removeTemporalFile(temporalFileId: TemporalFileId) {
temporalFilesMutex.withLock {
temporalFilesMap.remove(temporalFileId)
}
}
fun getTemporalFile(temporalFileId: TemporalFileId) = temporalFilesMap[temporalFileId]
suspend fun getAndRemoveTemporalFile(temporalFileId: TemporalFileId) = temporalFilesMutex.withLock {
temporalFilesMap.remove(temporalFileId)
}
}

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application import io.ktor.application.Application
import io.ktor.server.application.install import io.ktor.application.install
import io.ktor.server.plugins.cachingheaders.CachingHeaders import io.ktor.features.CachingHeaders
import io.ktor.server.plugins.cachingheaders.CachingHeadersConfig
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
data class ApplicationCachingHeadersConfigurator( data class ApplicationCachingHeadersConfigurator(
private val elements: List<@Contextual Element> private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator { ) : KtorApplicationConfigurator {
fun interface Element { operator fun CachingHeadersConfig.invoke() } fun interface Element { operator fun CachingHeaders.Configuration.invoke() }
override fun Application.configure() { override fun Application.configure() {
install(CachingHeaders) { install(CachingHeaders) {

View File

@@ -1,9 +1,8 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator.Element import io.ktor.application.*
import io.ktor.server.application.* import io.ktor.routing.Route
import io.ktor.server.routing.Route import io.ktor.routing.Routing
import io.ktor.server.routing.Routing
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -19,7 +18,7 @@ class ApplicationRoutingConfigurator(
} }
override fun Application.configure() { override fun Application.configure() {
pluginOrNull(Routing) ?.apply { featureOrNull(Routing) ?.apply {
rootInstaller.apply { invoke() } rootInstaller.apply { invoke() }
} ?: install(Routing) { } ?: install(Routing) {
rootInstaller.apply { invoke() } rootInstaller.apply { invoke() }

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application import io.ktor.application.Application
import io.ktor.server.application.install import io.ktor.application.install
import io.ktor.server.sessions.Sessions import io.ktor.sessions.Sessions
import io.ktor.server.sessions.SessionsConfig
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
class ApplicationSessionsConfigurator( class ApplicationSessionsConfigurator(
private val elements: List<@Contextual Element> private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator { ) : KtorApplicationConfigurator {
fun interface Element { operator fun SessionsConfig.invoke() } fun interface Element { operator fun Sessions.Configuration.invoke() }
override fun Application.configure() { override fun Application.configure() {
install(Sessions) { install(Sessions) {

View File

@@ -1,6 +1,6 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application import io.ktor.application.Application
interface KtorApplicationConfigurator { interface KtorApplicationConfigurator {
fun Application.configure() fun Application.configure()

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application import io.ktor.application.Application
import io.ktor.server.application.install import io.ktor.application.install
import io.ktor.server.plugins.statuspages.StatusPages import io.ktor.features.StatusPages
import io.ktor.server.plugins.statuspages.StatusPagesConfig
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
class StatusPagesConfigurator( class StatusPagesConfigurator(
private val elements: List<@Contextual Element> private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator { ) : KtorApplicationConfigurator {
fun interface Element { operator fun StatusPagesConfig.invoke() } fun interface Element { operator fun StatusPages.Configuration.invoke() }
override fun Application.configure() { override fun Application.configure() {
install(StatusPages) { install(StatusPages) {

View File

@@ -4,8 +4,8 @@ buildscript {
} }
dependencies { dependencies {
classpath libs.buildscript.kt.gradle classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath libs.buildscript.kt.serialization classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
} }
} }
@@ -16,11 +16,11 @@ plugins {
} }
dependencies { dependencies {
implementation libs.kt.stdlib implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation libs.kt.serialization implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version"
implementation libs.ktor.client implementation "io.ktor:ktor-client-core:$ktor_version"
implementation libs.ktor.client.java implementation "io.ktor:ktor-client-java:$ktor_version"
} }
mainClassName="MainKt" mainClassName="MainKt"

View File

@@ -1,6 +1,5 @@
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.ListSerializer
@@ -165,7 +164,7 @@ suspend fun main(vararg args: String) {
val ietfLanguageCodes = json.decodeFromString( val ietfLanguageCodes = json.decodeFromString(
ListSerializer(LanguageCode.serializer()), ListSerializer(LanguageCode.serializer()),
client.get(ietfLanguageCodesLink).bodyAsText() client.get(ietfLanguageCodesLink)
).map { ).map {
it.copy( it.copy(
title = it.title title = it.title
@@ -176,7 +175,7 @@ suspend fun main(vararg args: String) {
} }
val ietfLanguageCodesWithTagsMap = json.decodeFromString( val ietfLanguageCodesWithTagsMap = json.decodeFromString(
ListSerializer(LanguageCodeWithTag.serializer()), ListSerializer(LanguageCodeWithTag.serializer()),
client.get(ietfLanguageCodesAdditionalTagsLink).bodyAsText() client.get(ietfLanguageCodesAdditionalTagsLink)
).filter { it.withSubtag != it.tag }.groupBy { it.tag } ).filter { it.withSubtag != it.tag }.groupBy { it.tag }
val tags = ietfLanguageCodes.map { val tags = ietfLanguageCodes.map {

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.mime_types package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.* import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.* import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
@@ -15,7 +16,6 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) {
internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw) internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw)
@Suppress("OPT_IN_USAGE")
@Serializer(MimeType::class) @Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> { object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING) override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)

View File

@@ -23,7 +23,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
implementation kotlin('stdlib') implementation kotlin('stdlib')
implementation libs.kt.serialization api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version"
} }
} }
commonTest { commonTest {
@@ -46,8 +46,8 @@ kotlin {
androidTest { androidTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation libs.android.test.junit implementation "androidx.test.ext:junit:$test_ext_junit_version"
implementation libs.android.espresso implementation "androidx.test.espresso:espresso-core:$espresso_core"
} }
} }
} }

View File

@@ -1,72 +0,0 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js (IR) {
browser()
nodejs()
}
android {
publishAllLibraryVariants()
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
implementation libs.kt.serialization
implementation compose.runtime
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation compose.desktop.currentOs
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
jsMain {
dependencies {
implementation compose.web.core
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
androidTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
}
}
apply from: "$defaultAndroidSettingsPresetPath"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -27,5 +27,4 @@ inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? =
null null
} }
@Suppress("NOTHING_TO_INLINE")
inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty() inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()

View File

@@ -2,19 +2,19 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
inline fun <T> doForAll( suspend fun <T> doForAll(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?, paginationMapper: (PaginationResult<T>) -> Pagination?,
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
) { ) {
doWithPagination(initialPagination) { doWithPagination(initialPagination) {
block(it).let(paginationMapper) block(it).let(paginationMapper)
} }
} }
inline fun <T> doForAllWithNextPaging( suspend fun <T> doForAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
) { ) {
doForAll( doForAll(
initialPagination, initialPagination,
@@ -23,9 +23,9 @@ inline fun <T> doForAllWithNextPaging(
) )
} }
inline fun <T> doAllWithCurrentPaging( suspend fun <T> doAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
) { ) {
doForAll( doForAll(
initialPagination, initialPagination,
@@ -34,7 +34,7 @@ inline fun <T> doAllWithCurrentPaging(
) )
} }
inline fun <T> doForAllWithCurrentPaging( suspend fun <T> doForAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
) = doAllWithCurrentPaging(initialPagination, block) ) = doAllWithCurrentPaging(initialPagination, block)

View File

@@ -2,10 +2,10 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
inline fun <T> getAll( suspend fun <T> getAll(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?, paginationMapper: (PaginationResult<T>) -> Pagination?,
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
): List<T> { ): List<T> {
val results = mutableListOf<T>() val results = mutableListOf<T>()
doForAll(initialPagination, paginationMapper) { doForAll(initialPagination, paginationMapper) {
@@ -16,45 +16,46 @@ inline fun <T> getAll(
return results.toList() return results.toList()
} }
inline fun <T, R> R.getAllBy( suspend fun <T, R> R.getAllBy(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
paginationMapper: R.(PaginationResult<T>) -> Pagination?, paginationMapper: R.(PaginationResult<T>) -> Pagination?,
block: R.(Pagination) -> PaginationResult<T> block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAll( ): List<T> = getAll(
initialPagination, initialPagination,
{ paginationMapper(it) }, { paginationMapper(it) },
{ block(it) } { block(it) }
) )
inline fun <T> getAllWithNextPaging( suspend fun <T> getAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
): List<T> = getAll( ): List<T> = getAll(
initialPagination, initialPagination,
{ it.nextPageIfNotEmpty() }, { it.nextPageIfNotEmpty() },
block block
) )
inline fun <T, R> R.getAllByWithNextPaging( suspend fun <T, R> R.getAllByWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T> block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithNextPaging( ): List<T> = getAllWithNextPaging(
initialPagination, initialPagination,
{ block(it) } { block(it) }
) )
inline fun <T> getAllWithCurrentPaging( suspend fun <T> getAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
): List<T> = getAll( ): List<T> = getAll(
initialPagination, initialPagination,
{ it.currentPageIfNotEmpty() }, { it.currentPageIfNotEmpty() },
block block
) )
inline fun <T, R> R.getAllByWithCurrentPaging( suspend fun <T, R> R.getAllByWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T> block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithCurrentPaging( ): List<T> = getAllWithCurrentPaging(
initialPagination initialPagination,
) { block(it) } { block(it) }
)

View File

@@ -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]
*/ */

View File

@@ -14,7 +14,7 @@ kotlin {
} }
jvmMain { jvmMain {
dependencies { dependencies {
api libs.jb.exposed api "org.jetbrains.exposed:exposed-core:$kotlin_exposed_version"
} }
} }
} }

View File

@@ -15,8 +15,8 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
api libs.ktor.server api "io.ktor:ktor-server:$ktor_version"
api libs.ktor.server.host.common api "io.ktor:ktor-server-host-common:$ktor_version"
} }
} }
} }

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.pagination package dev.inmo.micro_utils.pagination
import io.ktor.application.ApplicationCall
import io.ktor.http.Parameters import io.ktor.http.Parameters
import io.ktor.server.application.ApplicationCall
val Parameters.extractPagination: Pagination val Parameters.extractPagination: Pagination
get() = SimplePagination( get() = SimplePagination(

View File

@@ -4,6 +4,8 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class ReadKeyValueCacheRepo<Key,Value>( open class ReadKeyValueCacheRepo<Key,Value>(
protected val parentRepo: ReadKeyValueRepo<Key, Value>, protected val parentRepo: ReadKeyValueRepo<Key, Value>,

View File

@@ -8,6 +8,8 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class ReadKeyValuesCacheRepo<Key,Value>( open class ReadKeyValuesCacheRepo<Key,Value>(
protected val parentRepo: ReadKeyValuesRepo<Key, Value>, protected val parentRepo: ReadKeyValuesRepo<Key, Value>,

View File

@@ -10,10 +10,10 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api libs.kt.coroutines api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
api internalProject("micro_utils.pagination.common") api internalProject("micro_utils.pagination.common")
api libs.uuid api "com.benasher44:uuid:$uuidVersion"
} }
} }
@@ -24,7 +24,7 @@ kotlin {
} }
androidMain { androidMain {
dependencies { dependencies {
api libs.android.coreKtx api "androidx.core:core-ktx:$core_ktx_version"
api internalProject("micro_utils.common") api internalProject("micro_utils.common")
api internalProject("micro_utils.coroutines") api internalProject("micro_utils.coroutines")
} }

View File

@@ -1,6 +1,5 @@
package dev.inmo.micro_utils.repos package dev.inmo.micro_utils.repos
@Suppress("UNCHECKED_CAST")
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> { interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
suspend fun FromKey.toOutKey() = this as ToKey suspend fun FromKey.toOutKey() = this as ToKey
suspend fun FromValue.toOutValue() = this as ToValue suspend fun FromValue.toOutValue() = this as ToValue

View File

@@ -1,10 +1,11 @@
package dev.inmo.micro_utils.repos package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithCurrentPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface ReadKeyValuesRepo<Key, Value> : Repo { interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value> suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
@@ -35,9 +36,9 @@ interface ReadKeyValuesRepo<Key, Value> : Repo {
} }
} }
} }
typealias ReadOneToManyKeyValueRepo<Key,Value> = ReadKeyValuesRepo<Key, Value> typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
interface WriteKeyValuesRepo<Key, Value> : Repo { interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>> val onNewValue: Flow<Pair<Key, Value>>
val onValueRemoved: Flow<Pair<Key, Value>> val onValueRemoved: Flow<Pair<Key, Value>>
val onDataCleared: Flow<Key> val onDataCleared: Flow<Key>
@@ -54,41 +55,41 @@ interface WriteKeyValuesRepo<Key, Value> : Repo {
add(toSet) add(toSet)
} }
} }
typealias WriteOneToManyKeyValueRepo<Key,Value> = WriteKeyValuesRepo<Key, Value> typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add( suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
keysAndValues: List<Pair<Key, List<Value>>> keysAndValues: List<Pair<Key, List<Value>>>
) = add(keysAndValues.toMap()) ) = add(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add( suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
vararg keysAndValues: Pair<Key, List<Value>> vararg keysAndValues: Pair<Key, List<Value>>
) = add(keysAndValues.toMap()) ) = add(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
k: Key, v: List<Value> k: Key, v: List<Value>
) = add(mapOf(k to v)) ) = add(mapOf(k to v))
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
k: Key, vararg v: Value k: Key, vararg v: Value
) = add(k, v.toList()) ) = add(k, v.toList())
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set( suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
keysAndValues: List<Pair<Key, List<Value>>> keysAndValues: List<Pair<Key, List<Value>>>
) = set(keysAndValues.toMap()) ) = set(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set( suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
vararg keysAndValues: Pair<Key, List<Value>> vararg keysAndValues: Pair<Key, List<Value>>
) = set(keysAndValues.toMap()) ) = set(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, v: List<Value> k: Key, v: List<Value>
) = set(mapOf(k to v)) ) = set(mapOf(k to v))
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, vararg v: Value k: Key, vararg v: Value
) = set(k, v.toList()) ) = set(k, v.toList())
interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyValuesRepo<Key, Value> { interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> {
override suspend fun clearWithValue(v: Value) { override suspend fun clearWithValue(v: Value) {
doWithPagination { doWithPagination {
val keysResult = keys(v, it) val keysResult = keys(v, it)
@@ -101,29 +102,22 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
} }
} }
} }
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value> typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
class DelegateBasedKeyValuesRepo<Key, Value>( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
readDelegate: ReadKeyValuesRepo<Key, Value>,
writeDelegate: WriteKeyValuesRepo<Key, Value>
) : KeyValuesRepo<Key, Value>,
ReadKeyValuesRepo<Key, Value> by readDelegate,
WriteKeyValuesRepo<Key, Value> by writeDelegate
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
keysAndValues: List<Pair<Key, List<Value>>> keysAndValues: List<Pair<Key, List<Value>>>
) = remove(keysAndValues.toMap()) ) = remove(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
vararg keysAndValues: Pair<Key, List<Value>> vararg keysAndValues: Pair<Key, List<Value>>
) = remove(keysAndValues.toMap()) ) = remove(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key, k: Key,
v: List<Value> v: List<Value>
) = remove(mapOf(k to v)) ) = remove(mapOf(k to v))
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove( suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key, k: Key,
vararg v: Value vararg v: Value
) = remove(k, v.toList()) ) = remove(k, v.toList())

View File

@@ -4,13 +4,13 @@ import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.PaginationResult
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface ReadCRUDRepo<ObjectType, IdType> : Repo { interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType>
suspend fun getById(id: IdType): ObjectType? suspend fun getById(id: IdType): ObjectType?
suspend fun contains(id: IdType): Boolean suspend fun contains(id: IdType): Boolean
suspend fun count(): Long suspend fun count(): Long
} }
typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType> typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType> typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
val <IdType> UpdatedValuePair<IdType, *>.id val <IdType> UpdatedValuePair<IdType, *>.id
@@ -18,7 +18,7 @@ val <IdType> UpdatedValuePair<IdType, *>.id
val <ValueType> UpdatedValuePair<*, ValueType>.value val <ValueType> UpdatedValuePair<*, ValueType>.value
get() = second get() = second
interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo { interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
val newObjectsFlow: Flow<ObjectType> val newObjectsFlow: Flow<ObjectType>
val updatedObjectsFlow: Flow<ObjectType> val updatedObjectsFlow: Flow<ObjectType>
val deletedObjectsIdsFlow: Flow<IdType> val deletedObjectsIdsFlow: Flow<IdType>
@@ -28,25 +28,18 @@ interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
suspend fun deleteById(ids: List<IdType>) suspend fun deleteById(ids: List<IdType>)
} }
typealias WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> = WriteCRUDRepo<ObjectType, IdType, InputValueType> typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.create( suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
vararg values: InputValueType vararg values: InputValueType
): List<ObjectType> = create(values.toList()) ): List<ObjectType> = create(values.toList())
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.update( suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.update(
vararg values: UpdatedValuePair<IdType, InputValueType> vararg values: UpdatedValuePair<IdType, InputValueType>
): List<ObjectType> = update(values.toList()) ): List<ObjectType> = update(values.toList())
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.deleteById( suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.deleteById(
vararg ids: IdType vararg ids: IdType
) = deleteById(ids.toList()) ) = deleteById(ids.toList())
interface CRUDRepo<ObjectType, IdType, InputValueType> : ReadCRUDRepo<ObjectType, IdType>, interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
WriteCRUDRepo<ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
typealias StandardCRUDRepo<ObjectType, IdType, InputValueType> = CRUDRepo<ObjectType, IdType, InputValueType> typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>
class DelegateBasedCRUDRepo<ObjectType, IdType, InputValueType>(
readDelegate: ReadCRUDRepo<ObjectType, IdType>,
writeDelegate: WriteCRUDRepo<ObjectType, IdType, InputValueType>
) : CRUDRepo<ObjectType, IdType, InputValueType>,
ReadCRUDRepo<ObjectType, IdType> by readDelegate,
WriteCRUDRepo<ObjectType, IdType, InputValueType> by writeDelegate

View File

@@ -4,7 +4,7 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface ReadKeyValueRepo<Key, Value> : Repo { interface ReadStandardKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key): Value? suspend fun get(k: Key): Value?
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value> suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
@@ -12,9 +12,9 @@ interface ReadKeyValueRepo<Key, Value> : Repo {
suspend fun contains(key: Key): Boolean suspend fun contains(key: Key): Boolean
suspend fun count(): Long suspend fun count(): Long
} }
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value> typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
interface WriteKeyValueRepo<Key, Value> : Repo { interface WriteStandardKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>> val onNewValue: Flow<Pair<Key, Value>>
val onValueRemoved: Flow<Key> val onValueRemoved: Flow<Key>
@@ -22,25 +22,25 @@ interface WriteKeyValueRepo<Key, Value> : Repo {
suspend fun unset(toUnset: List<Key>) suspend fun unset(toUnset: List<Key>)
suspend fun unsetWithValues(toUnset: List<Value>) suspend fun unsetWithValues(toUnset: List<Value>)
} }
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value> typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set( suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value> vararg toSet: Pair<Key, Value>
) = set(toSet.toMap()) ) = set(toSet.toMap())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set( suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
k: Key, v: Value k: Key, v: Value
) = set(k to v) ) = set(k to v)
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset( suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
vararg k: Key vararg k: Key
) = unset(k.toList()) ) = unset(k.toList())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues( suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithValues(
vararg v: Value vararg v: Value
) = unsetWithValues(v.toList()) ) = unsetWithValues(v.toList())
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> { interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> {
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v -> override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
doAllWithCurrentPaging { doAllWithCurrentPaging {
keys(v, it).also { keys(v, it).also {
@@ -49,11 +49,4 @@ interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValue
} }
} }
} }
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value> typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>
class DelegateBasedKeyValueRepo<Key, Value>(
readDelegate: ReadKeyValueRepo<Key, Value>,
writeDelegate: WriteKeyValueRepo<Key, Value>
) : KeyValueRepo<Key, Value>,
ReadKeyValueRepo<Key, Value> by readDelegate,
WriteKeyValueRepo<Key, Value> by writeDelegate

View File

@@ -6,12 +6,10 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@Deprecated("Renamed", ReplaceWith("MapperReadKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperReadKeyValueRepo")) open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
typealias MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue> private val to: ReadStandardKeyValueRepo<ToKey, ToValue>,
open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper { ) : ReadStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override suspend fun get(k: FromKey): FromValue? = to.get( override suspend fun get(k: FromKey): FromValue? = to.get(
k.toOutKey() k.toOutKey()
) ?.toInnerValue() ) ?.toInnerValue()
@@ -71,26 +69,24 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadKeyValueRepo<FromKey, FromValue> = MapperReadKeyValueRepo(this, mapper) ): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadKeyValueRepo<FromKey, FromValue> = withMapper( ): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@Deprecated("Renamed", ReplaceWith("MapperWriteKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperWriteKeyValueRepo")) open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
typealias MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue> private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper { ) : WriteStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) -> override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
k.toInnerKey() to v.toInnerValue() k.toInnerKey() to v.toInnerValue()
} }
@@ -116,42 +112,39 @@ open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteKeyValueRepo<FromKey, FromValue> = MapperWriteKeyValueRepo(this, mapper) ): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteKeyValueRepo<FromKey, FromValue> = withMapper( ): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@Deprecated("Renamed", ReplaceWith("MapperKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperKeyValueRepo")) open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
typealias MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue> private val to: StandardKeyValueRepo<ToKey, ToValue>,
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
open class MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>( ) : StandardKeyValueRepo<FromKey, FromValue>,
private val to: KeyValueRepo<ToKey, ToValue>,
private val mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : KeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadKeyValueRepo<FromKey, FromValue> by MapperReadKeyValueRepo(to, mapper), ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
WriteKeyValueRepo<FromKey, FromValue> by MapperWriteKeyValueRepo(to, mapper) WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValueRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): KeyValueRepo<FromKey, FromValue> = MapperKeyValueRepo(this, mapper) ): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValueRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): KeyValueRepo<FromKey, FromValue> = withMapper( ): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )

View File

@@ -6,12 +6,10 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@Deprecated("Renamed", ReplaceWith("MapperReadKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperReadKeyValuesRepo")) open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
typealias MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue> private val to: ReadOneToManyKeyValueRepo<ToKey, ToValue>,
open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadKeyValuesRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper { ) : ReadOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override suspend fun get( override suspend fun get(
k: FromKey, k: FromKey,
pagination: Pagination, pagination: Pagination,
@@ -69,26 +67,24 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadKeyValuesRepo<FromKey, FromValue> = MapperReadKeyValuesRepo(this, mapper) ): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadKeyValuesRepo<FromKey, FromValue> = withMapper( ): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@Deprecated("Renamed", ReplaceWith("MapperWriteKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperWriteKeyValuesRepo")) open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
typealias MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue> private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteKeyValuesRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper { ) : WriteOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) -> override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
k.toInnerKey() to v.toInnerValue() k.toInnerKey() to v.toInnerValue()
} }
@@ -122,42 +118,39 @@ open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteKeyValuesRepo<FromKey, FromValue> = MapperWriteKeyValuesRepo(this, mapper) ): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteKeyValuesRepo<FromKey, FromValue> = withMapper( ): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )
@Deprecated("Renamed", ReplaceWith("MapperKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperKeyValuesRepo")) open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
typealias MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue> private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
open class MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: KeyValuesRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : KeyValuesRepo<FromKey, FromValue>, ) : OneToManyKeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadKeyValuesRepo<FromKey, FromValue> by MapperReadKeyValuesRepo(to, mapper), ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
WriteKeyValuesRepo<FromKey, FromValue> by MapperWriteKeyValuesRepo(to, mapper) WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue> mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): KeyValuesRepo<FromKey, FromValue> = MapperKeyValuesRepo(this, mapper) ): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper( inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey }, crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue }, crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey }, crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue }, crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): KeyValuesRepo<FromKey, FromValue> = withMapper( ): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom) mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
) )

View File

@@ -1,10 +1,11 @@
package dev.inmo.micro_utils.repos.pagination package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadCRUDRepo import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll( suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T> crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithNextPaging { ): List<T> = getAllWithNextPaging {

View File

@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadKeyValueRepo import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll( suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, Value>> = getAllWithNextPaging { ): List<Pair<Key, Value>> = getAllWithNextPaging {

View File

@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll( suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, List<Value>>> = getAllWithNextPaging { ): List<Pair<Key, List<Value>>> = getAllWithNextPaging {

View File

@@ -1,10 +1,10 @@
package dev.inmo.micro_utils.repos.versions package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import dev.inmo.micro_utils.repos.set import dev.inmo.micro_utils.repos.set
class KeyValueBasedVersionsRepoProxy<T>( class KeyValueBasedVersionsRepoProxy<T>(
private val keyValueStore: KeyValueRepo<String, Int>, private val keyValueStore: StandardKeyValueRepo<String, Int>,
override val database: T override val database: T
) : StandardVersionsRepoProxy<T> { ) : StandardVersionsRepoProxy<T> {
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName) override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)

View File

@@ -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]. Then in case if [tableName] have version less * * In case if [tableName] didn't found, will be called [onCreate] and version of table will be set up to [version]
* than parameter [version] or null, it will increase version one-by-one until database version will be equal to * * In case if [tableName] have version less than parameter [version], it will increase version one-by-one
* [version] * until database version will be equal to [version]
* *
* @param version Current version of table * @param version Current version of table
* @param onCreate This callback will be called in case when repo have no information about table * @param onCreate This callback will be called in case when table 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"
*/ */

Some files were not shown because too many files have changed in this diff Show More