mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 22:39:25 +00:00
Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
ccc1dd7857 | |||
5d450e0632 | |||
cd3838f321 |
2
.github/workflows/packages_push.yml
vendored
2
.github/workflows/packages_push.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
run: ./gradlew build
|
||||
- name: Publish
|
||||
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:
|
||||
GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
234
CHANGELOG.md
234
CHANGELOG.md
@@ -1,239 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.11.2
|
||||
|
||||
* `Ktor`:
|
||||
* Support of `WebSockets` has been improved and added fixes inside of clients
|
||||
|
||||
## 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
|
||||
|
||||
* `Repos`:
|
||||
|
@@ -10,7 +10,7 @@ kotlin {
|
||||
sourceSets {
|
||||
androidMain {
|
||||
dependencies {
|
||||
api libs.android.appCompat.resources
|
||||
api "androidx.appcompat:appcompat-resources:$appcompat_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,13 +10,13 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api libs.kt.coroutines
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
api project(":micro_utils.common")
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api libs.android.recyclerView
|
||||
api "androidx.recyclerview:recyclerview:$androidx_recycler_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
build.gradle
13
build.gradle
@@ -7,12 +7,12 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath libs.buildscript.kt.gradle
|
||||
classpath libs.buildscript.kt.serialization
|
||||
classpath libs.buildscript.jb.dokka
|
||||
classpath libs.buildscript.gh.release
|
||||
classpath libs.buildscript.android.gradle
|
||||
classpath libs.buildscript.android.dexcount
|
||||
classpath 'com.android.tools.build:gradle:7.0.4'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
|
||||
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ allprojects {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
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
|
||||
|
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 {}
|
||||
}
|
||||
}
|
||||
|
@@ -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() }
|
||||
}
|
@@ -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
|
||||
)
|
||||
|
@@ -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)
|
||||
}
|
@@ -1 +0,0 @@
|
||||
<manifest package="dev.inmo.micro_utils.common.compose"/>
|
@@ -1,5 +1,3 @@
|
||||
@file:Suppress("OPT_IN_IS_NOT_ENABLED")
|
||||
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
@RequiresOptIn(
|
||||
|
@@ -43,7 +43,6 @@ private inline fun <T> performChanges(
|
||||
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
|
||||
changedList.addAll(
|
||||
potentialChanges.take(i).mapNotNull {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
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 {
|
||||
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
|
||||
changedObjects.addAll(potentiallyChangedObjects.map {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
it as Pair<IndexedValue<T>, IndexedValue<T>>
|
||||
})
|
||||
changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
|
||||
potentiallyChangedObjects.clear()
|
||||
}
|
||||
else -> {
|
||||
@@ -157,22 +153,3 @@ inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDif
|
||||
inline fun <T> Iterable<T>.calculateStrictDiff(
|
||||
other: Iterable<T>
|
||||
) = 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)
|
||||
}
|
||||
}
|
||||
|
@@ -27,13 +27,20 @@ sealed interface Either<T1, T2> {
|
||||
@Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
|
||||
val t2: T2?
|
||||
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>(
|
||||
t1Serializer: KSerializer<T1>,
|
||||
t2Serializer: KSerializer<T2>,
|
||||
) : KSerializer<Either<T1, T2>> {
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
||||
override val descriptor: SerialDescriptor = buildSerialDescriptor(
|
||||
"TypedSerializer",
|
||||
SerialKind.CONTEXTUAL
|
||||
@@ -44,6 +51,7 @@ class EitherSerializer<T1, T2>(
|
||||
private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer)
|
||||
private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer)
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
|
||||
override fun deserialize(decoder: Decoder): Either<T1, T2> {
|
||||
return decoder.decodeStructure(descriptor) {
|
||||
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>) {
|
||||
encoder.encodeStructure(descriptor) {
|
||||
when (value) {
|
||||
|
@@ -32,7 +32,7 @@ class DiffUtilsTests {
|
||||
val withIndex = oldList.withIndex()
|
||||
|
||||
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
|
||||
for ((i, _) in withIndex) {
|
||||
for ((i, v) in withIndex) {
|
||||
if (i + count > oldList.lastIndex) {
|
||||
continue
|
||||
}
|
||||
@@ -54,8 +54,8 @@ class DiffUtilsTests {
|
||||
val oldList = (0 until 10).map { it.toString() }
|
||||
val withIndex = oldList.withIndex()
|
||||
|
||||
for (step in oldList.indices) {
|
||||
for ((i, _) in withIndex) {
|
||||
for (step in 0 until oldList.size) {
|
||||
for ((i, v) in withIndex) {
|
||||
val mutable = oldList.toMutableList()
|
||||
val changes = (
|
||||
if (step == 0) i until oldList.size else (i until oldList.size step step)
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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
|
@@ -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)
|
@@ -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()
|
||||
}
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
|
@@ -10,17 +10,12 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api libs.kt.coroutines
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.common")
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api libs.kt.coroutines.android
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
|
@@ -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)
|
@@ -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)
|
||||
}
|
@@ -1 +0,0 @@
|
||||
<manifest package="dev.inmo.micro_utils.coroutines.compose"/>
|
@@ -6,12 +6,11 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
private sealed interface AccumulatorFlowStep<T>
|
||||
private data class DataRetrievedAccumulatorFlowStep<T>(val data: T) : AccumulatorFlowStep<T>
|
||||
private data class SubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
|
||||
private data class UnsubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
|
||||
private sealed interface AccumulatorFlowStep
|
||||
private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
|
||||
private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
|
||||
private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
|
||||
|
||||
/**
|
||||
* 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 activeData = ArrayDeque<T>()
|
||||
private val dataMutex = Mutex()
|
||||
private val channelsForBroadcast = mutableListOf<Channel<T>>()
|
||||
private val channelsForBroadcast = mutableListOf<Channel<Any>>()
|
||||
private val channelsMutex = Mutex()
|
||||
private val steps = subscope.actor<AccumulatorFlowStep<T>> { step ->
|
||||
private val steps = subscope.actor<AccumulatorFlowStep> { step ->
|
||||
when (step) {
|
||||
is DataRetrievedAccumulatorFlowStep -> {
|
||||
if (activeData.firstOrNull() === step.data) {
|
||||
if (activeData.first() === step.data) {
|
||||
dataMutex.withLock {
|
||||
activeData.removeFirst()
|
||||
}
|
||||
@@ -43,7 +42,7 @@ class AccumulatorFlow<T>(
|
||||
dataMutex.withLock {
|
||||
val dataToSend = activeData.toList()
|
||||
safelyWithoutExceptions {
|
||||
dataToSend.forEach { step.channel.send(it) }
|
||||
dataToSend.forEach { step.channel.send(it as Any) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,29 +58,24 @@ class AccumulatorFlow<T>(
|
||||
channelsMutex.withLock {
|
||||
channelsForBroadcast.forEach { channel ->
|
||||
safelyWithResult {
|
||||
channel.send(it)
|
||||
channel.send(it as Any)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
val result = runCatchingSafely {
|
||||
for (data in channel) {
|
||||
val emitResult = runCatchingSafely {
|
||||
collector.emit(data)
|
||||
}
|
||||
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) {
|
||||
steps.send(DataRetrievedAccumulatorFlowStep(data))
|
||||
}
|
||||
emitResult.getOrThrow()
|
||||
for (data in channel) {
|
||||
try {
|
||||
collector.emit(data as T)
|
||||
steps.send(DataRetrievedAccumulatorFlowStep(data))
|
||||
} finally {
|
||||
channel.cancel()
|
||||
steps.send(UnsubscribeAccumulatorFlowStep(channel))
|
||||
}
|
||||
}
|
||||
channel.cancel()
|
||||
steps.send(UnsubscribeAccumulatorFlowStep(channel))
|
||||
result.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
@@ -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()
|
@@ -1,3 +0,0 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
expect fun SourceString.hmacSha256(key: String): String
|
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -7,7 +7,3 @@ external interface CryptoJs {
|
||||
@JsModule("crypto-js")
|
||||
@JsNonModule
|
||||
external val CryptoJS: CryptoJs
|
||||
|
||||
actual fun SourceString.hmacSha256(key: String): String {
|
||||
return CryptoJS.asDynamic().HmacSHA256(this, key).toString().unsafeCast<String>()
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
@@ -26,12 +26,12 @@ ext {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
||||
buildToolsVersion libs.versions.android.props.buildTools.get()
|
||||
compileSdkVersion "$android_compileSdkVersion".toInteger()
|
||||
buildToolsVersion "$android_buildToolsVersion"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion libs.versions.android.props.minSdk.get().toInteger()
|
||||
targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
||||
minSdkVersion "$android_minSdkVersion".toInteger()
|
||||
targetSdkVersion "$android_compileSdkVersion".toInteger()
|
||||
versionCode "${android_code_version}".toInteger()
|
||||
versionName "$version"
|
||||
}
|
||||
|
@@ -21,7 +21,6 @@ allprojects {
|
||||
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
||||
|
||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
||||
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
||||
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
||||
|
||||
|
@@ -3,8 +3,6 @@ package dev.inmo.micro_utils.fsm.common
|
||||
import dev.inmo.micro_utils.common.Optional
|
||||
import dev.inmo.micro_utils.common.onPresented
|
||||
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.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
@@ -15,24 +13,13 @@ import kotlinx.coroutines.sync.withLock
|
||||
* handling until [start] method will be called
|
||||
*/
|
||||
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(
|
||||
state: T,
|
||||
handlers: List<CheckableHandlerHolder<in 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(
|
||||
statesManager: StatesManager<T>,
|
||||
handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
|
||||
) = DefaultStatesMachine(statesManager, handlers, onStateHandlingErrorHandler)
|
||||
handlers: List<CheckableHandlerHolder<in T, T>>
|
||||
) = DefaultStatesMachine(statesManager, handlers)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,17 +52,12 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
|
||||
open class DefaultStatesMachine <T: State>(
|
||||
protected val statesManager: StatesManager<T>,
|
||||
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||
protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
|
||||
) : StatesMachine<T> {
|
||||
/**
|
||||
* Will call [launchStateHandling] for state handling
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -124,16 +105,6 @@ open class DefaultStatesMachine <T: State>(
|
||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||
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 {
|
||||
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||
|
@@ -1,8 +1,6 @@
|
||||
package dev.inmo.micro_utils.fsm.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.sync.withLock
|
||||
|
||||
@@ -22,23 +20,15 @@ interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
|
||||
open class DefaultUpdatableStatesMachine<T : State>(
|
||||
statesManager: StatesManager<T>,
|
||||
handlers: List<CheckableHandlerHolder<in T, T>>,
|
||||
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
|
||||
) : DefaultStatesMachine<T>(
|
||||
statesManager,
|
||||
handlers,
|
||||
onStateHandlingErrorHandler
|
||||
handlers
|
||||
), UpdatableStatesMachine<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) {
|
||||
statesJobsMutex.withLock {
|
||||
if (shouldReplaceJob(previousState, actualState)) {
|
||||
if (compare(previousState, actualState)) {
|
||||
statesJobs[actualState] ?.cancel()
|
||||
}
|
||||
val job = previousState.mapOnPresented {
|
||||
@@ -62,13 +52,7 @@ open class DefaultUpdatableStatesMachine<T : State>(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
|
||||
|
||||
override suspend fun updateChain(currentState: T, newState: T) {
|
||||
statesManager.update(currentState, newState)
|
||||
|
@@ -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
|
||||
* 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
|
||||
* @param onStartContextsConflictResolver Receive current [State] and the state passed with [startChain]. In case when
|
||||
* this callback will return true, currently placed on the [State.context] [State] will be replaced by new state
|
||||
* with [endChain] with current state
|
||||
* @param onUpdateContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
||||
* @param onContextsConflictResolver 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
|
||||
* new state by using [endChain] with that state
|
||||
*/
|
||||
open class DefaultStatesManager<T : State>(
|
||||
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
||||
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true },
|
||||
protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||
class DefaultStatesManager<T : State>(
|
||||
private val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
||||
private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||
) : 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()
|
||||
protected val _onStartChain = MutableSharedFlow<T>(0)
|
||||
private val _onStartChain = MutableSharedFlow<T>(0)
|
||||
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()
|
||||
|
||||
protected val mapMutex = Mutex()
|
||||
|
||||
constructor(
|
||||
repo: DefaultStatesManagerRepo<T>,
|
||||
onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean
|
||||
) : this (
|
||||
repo,
|
||||
onUpdateContextsConflictResolver = onContextsConflictResolver
|
||||
)
|
||||
private val mapMutex = Mutex()
|
||||
|
||||
override suspend fun update(old: T, new: T) = mapMutex.withLock {
|
||||
val stateByOldContext: T? = repo.getContextState(old.context)
|
||||
when {
|
||||
stateByOldContext != old -> return@withLock
|
||||
stateByOldContext == null || old.context == new.context -> {
|
||||
repo.removeState(old)
|
||||
repo.set(new)
|
||||
_onChainStateUpdated.emit(old to new)
|
||||
}
|
||||
else -> {
|
||||
val stateOnNewOneContext = repo.getContextState(new.context)
|
||||
if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
||||
if (stateOnNewOneContext == null || onContextsConflictResolver(old, new, stateOnNewOneContext)) {
|
||||
stateOnNewOneContext ?.let { endChainWithoutLock(it) }
|
||||
repo.removeState(old)
|
||||
repo.set(new)
|
||||
@@ -90,17 +77,13 @@ open class DefaultStatesManager<T : State>(
|
||||
}
|
||||
|
||||
override suspend fun startChain(state: T) = mapMutex.withLock {
|
||||
val stateOnContext = repo.getContextState(state.context)
|
||||
if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) {
|
||||
stateOnContext ?.let {
|
||||
endChainWithoutLock(it)
|
||||
}
|
||||
if (!repo.contains(state.context)) {
|
||||
repo.set(state)
|
||||
_onStartChain.emit(state)
|
||||
}
|
||||
}
|
||||
|
||||
protected open suspend fun endChainWithoutLock(state: T) {
|
||||
private suspend fun endChainWithoutLock(state: T) {
|
||||
if (repo.getContextState(state.context) == state) {
|
||||
repo.removeState(state)
|
||||
_onEndChain.emit(state)
|
||||
|
@@ -12,6 +12,5 @@ import kotlinx.coroutines.flow.*
|
||||
*/
|
||||
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
|
||||
fun <T: State> InMemoryStatesManager(
|
||||
onStartContextsConflictResolver: suspend (old: T, new: T) -> Boolean = { _, _ -> true },
|
||||
onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||
) = DefaultStatesManager(onStartContextsConflictResolver = onStartContextsConflictResolver, onUpdateContextsConflictResolver = onUpdateContextsConflictResolver)
|
||||
onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||
) = DefaultStatesManager(onContextsConflictResolver = onContextsConflictResolver)
|
||||
|
@@ -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>
|
||||
|
@@ -7,12 +7,43 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
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
|
||||
|
||||
crypto_js_version=4.1.1
|
||||
|
||||
# Dokka
|
||||
|
||||
dokka_version=1.6.10
|
||||
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.11.2
|
||||
android_code_version=126
|
||||
version=0.9.7
|
||||
android_code_version=97
|
||||
|
@@ -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" }
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
@@ -12,7 +12,7 @@ kotlin {
|
||||
dependencies {
|
||||
api internalProject("micro_utils.ktor.common")
|
||||
api internalProject("micro_utils.coroutines")
|
||||
api libs.ktor.client
|
||||
api "io.ktor:ktor-client-core:$ktor_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,13 @@
|
||||
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.WebSockets
|
||||
import io.ktor.client.plugins.websocket.ws
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.websocket.Frame
|
||||
import io.ktor.websocket.readBytes
|
||||
import io.ktor.client.features.websocket.ws
|
||||
import io.ktor.http.cio.websocket.Frame
|
||||
import io.ktor.http.cio.websocket.readBytes
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
|
||||
/**
|
||||
@@ -21,41 +16,42 @@ import kotlinx.serialization.DeserializationStrategy
|
||||
*/
|
||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
url: String,
|
||||
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
|
||||
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
|
||||
): Flow<T> {
|
||||
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
||||
|
||||
val correctedUrl = url.asCorrectWebSocketUrl
|
||||
|
||||
return channelFlow {
|
||||
val producerScope = this@channelFlow
|
||||
do {
|
||||
val reconnect = runCatchingSafely {
|
||||
ws(correctedUrl, requestBuilder) {
|
||||
for (received in incoming) {
|
||||
when (received) {
|
||||
is Frame.Binary -> send(conversation(received.data))
|
||||
else -> {
|
||||
close()
|
||||
return@ws
|
||||
val reconnect = try {
|
||||
safely {
|
||||
ws(correctedUrl) {
|
||||
for (received in incoming) {
|
||||
when (received) {
|
||||
is Frame.Binary -> producerScope.send(conversation(received.readBytes()))
|
||||
else -> {
|
||||
producerScope.close()
|
||||
return@ws
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkReconnection(null)
|
||||
}.getOrElse { e ->
|
||||
} catch (e: Throwable) {
|
||||
checkReconnection(e).also {
|
||||
if (!it) {
|
||||
close(e)
|
||||
producerScope.close(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (reconnect && isActive)
|
||||
|
||||
if (isActive) {
|
||||
safely {
|
||||
close()
|
||||
} while (reconnect)
|
||||
if (!producerScope.isClosedForSend) {
|
||||
safely(
|
||||
{ it.printStackTrace() }
|
||||
) {
|
||||
producerScope.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,14 +63,12 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
*/
|
||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
url: String,
|
||||
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = createStandardWebsocketFlow(
|
||||
url,
|
||||
checkReconnection,
|
||||
requestBuilder
|
||||
checkReconnection
|
||||
) {
|
||||
serialFormat.decodeDefault(deserializer, it)
|
||||
}
|
||||
|
@@ -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())
|
||||
}
|
@@ -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 : Any> 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) {
|
||||
while (isActive) {
|
||||
send(receiveDeserialized<T>())
|
||||
}
|
||||
}
|
||||
checkReconnection(null)
|
||||
}.getOrElse { e ->
|
||||
checkReconnection(e).also {
|
||||
if (!it) {
|
||||
close(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (reconnect && isActive)
|
||||
|
||||
if (isActive) {
|
||||
safely {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,10 +4,8 @@ import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.filename
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.client.statement.readBytes
|
||||
import io.ktor.http.*
|
||||
import io.ktor.utils.io.core.ByteReadPacket
|
||||
import kotlinx.serialization.*
|
||||
@@ -87,16 +85,9 @@ class UnifiedRequester(
|
||||
|
||||
fun <T> createStandardWebsocketFlow(
|
||||
url: String,
|
||||
checkReconnection: suspend (Throwable?) -> Boolean,
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
|
||||
|
||||
fun <T> createStandardWebsocketFlow(
|
||||
url: String,
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
|
||||
checkReconnection: (Throwable?) -> Boolean = { true },
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat)
|
||||
}
|
||||
|
||||
val defaultRequester = UnifiedRequester()
|
||||
@@ -105,8 +96,10 @@ suspend fun <ResultType> HttpClient.uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = get(url).let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
|
||||
) = get<StandardKtorSerialInputData>(
|
||||
url
|
||||
).let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
|
||||
|
||||
@@ -123,12 +116,10 @@ suspend fun <BodyType, ResultType> HttpClient.unipost(
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = post(url) {
|
||||
setBody(
|
||||
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
)
|
||||
) = post<StandardKtorSerialInputData>(url) {
|
||||
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
}.let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
|
||||
suspend fun <ResultType> HttpClient.unimultipart(
|
||||
@@ -141,7 +132,7 @@ suspend fun <ResultType> HttpClient.unimultipart(
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
): ResultType = submitFormWithBinaryData(
|
||||
): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>(
|
||||
url,
|
||||
formData = formData {
|
||||
append(
|
||||
@@ -157,7 +148,7 @@ suspend fun <ResultType> HttpClient.unimultipart(
|
||||
}
|
||||
) {
|
||||
requestBuilder()
|
||||
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
|
||||
}.let { serialFormat.decodeDefault(resultDeserializer, it) }
|
||||
|
||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||
url: String,
|
||||
|
@@ -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
|
||||
)
|
@@ -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)
|
@@ -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)
|
||||
}
|
@@ -11,10 +11,8 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api internalProject("micro_utils.common")
|
||||
api libs.kt.serialization.cbor
|
||||
api libs.klock
|
||||
api libs.uuid
|
||||
api libs.ktor.io
|
||||
api "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlin_serialisation_core_version"
|
||||
api "com.soywiz.korlibs.klock:klock:$klockVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
@@ -1,5 +1,3 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
|
||||
package dev.inmo.micro_utils.ktor.common
|
||||
|
||||
import kotlinx.serialization.*
|
||||
|
@@ -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)
|
@@ -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())
|
@@ -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()
|
@@ -16,11 +16,10 @@ kotlin {
|
||||
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api libs.ktor.server
|
||||
api libs.ktor.server.cio
|
||||
api libs.ktor.server.host.common
|
||||
api libs.ktor.server.websockets
|
||||
api libs.ktor.server.statusPages
|
||||
api "io.ktor:ktor-server:$ktor_version"
|
||||
api "io.ktor:ktor-server-cio:$ktor_version"
|
||||
api "io.ktor:ktor-server-host-common:$ktor_version"
|
||||
api "io.ktor:ktor-websockets:$ktor_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
@@ -2,32 +2,27 @@ 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 io.ktor.application.featureOrNull
|
||||
import io.ktor.application.install
|
||||
import io.ktor.http.cio.websocket.*
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.application
|
||||
import io.ktor.websocket.webSocket
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
@Deprecated("This method will be removed soon")
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
protocol: URLProtocol? = null,
|
||||
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
|
||||
converter: (T) -> StandardKtorSerialInputData
|
||||
) {
|
||||
application.apply {
|
||||
pluginOrNull(WebSockets) ?: install(WebSockets)
|
||||
featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets)
|
||||
}
|
||||
webSocket(suburl, protocol ?.name) {
|
||||
webSocket(suburl) {
|
||||
safely {
|
||||
flow.collect {
|
||||
converter(it) ?.let { data ->
|
||||
send(data)
|
||||
}
|
||||
send(converter(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,24 +32,10 @@ fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
protocol: URLProtocol? = null,
|
||||
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = includeWebsocketHandling(
|
||||
suburl,
|
||||
flow,
|
||||
protocol,
|
||||
converter = if (filter == null) {
|
||||
{
|
||||
serialFormat.encodeDefault(serializer, it)
|
||||
}
|
||||
} else {
|
||||
{
|
||||
if (filter(it)) {
|
||||
serialFormat.encodeDefault(serializer, it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
flow
|
||||
) {
|
||||
serialFormat.encodeDefault(serializer, it)
|
||||
}
|
||||
|
@@ -1,31 +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,
|
||||
noinline dataMapper: suspend WebSocketServerSession.(T) -> T? = { it }
|
||||
) {
|
||||
application.apply {
|
||||
pluginOrNull(WebSockets) ?: install(WebSockets)
|
||||
}
|
||||
webSocket(suburl, protocol ?.name) {
|
||||
safely {
|
||||
flow.collect {
|
||||
sendSerialized(dataMapper(it) ?: return@collect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,21 +3,25 @@ package dev.inmo.micro_utils.ktor.server
|
||||
import dev.inmo.micro_utils.common.*
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.content.*
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.request.receiveMultipart
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.response.respondBytes
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.websocket.WebSocketServerSession
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.content.PartData
|
||||
import io.ktor.http.content.forEachPart
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.request.receiveMultipart
|
||||
import io.ktor.response.respond
|
||||
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.utils.io.core.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import kotlinx.serialization.*
|
||||
import java.io.File
|
||||
import java.io.File.createTempFile
|
||||
|
||||
class UnifiedRouter(
|
||||
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
@@ -26,10 +30,8 @@ class UnifiedRouter(
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>,
|
||||
protocol: URLProtocol? = null,
|
||||
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
||||
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
|
||||
serializer: SerializationStrategy<T>
|
||||
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat)
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
|
||||
answerSerializer: SerializationStrategy<T>,
|
||||
@@ -88,11 +90,6 @@ class UnifiedRouter(
|
||||
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val default
|
||||
get() = defaultUnifiedRouter
|
||||
}
|
||||
}
|
||||
|
||||
val defaultUnifiedRouter = UnifiedRouter()
|
||||
@@ -193,9 +190,7 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
|
||||
".${name.extension}"
|
||||
).apply {
|
||||
outputStream().use { fileStream ->
|
||||
it.streamProvider().use {
|
||||
it.copyTo(fileStream)
|
||||
}
|
||||
it.provider().asStream().copyTo(fileStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,9 +232,7 @@ suspend fun ApplicationCall.uniloadMultipartFile(
|
||||
".${name.extension}"
|
||||
).apply {
|
||||
outputStream().use { fileStream ->
|
||||
it.streamProvider().use {
|
||||
it.copyTo(fileStream)
|
||||
}
|
||||
it.provider().asStream().copyTo(fileStream)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@@ -1,9 +1,8 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
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.CIOApplicationEngine
|
||||
import io.ktor.server.engine.*
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -11,21 +10,17 @@ fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configurati
|
||||
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
|
||||
host: String = "localhost",
|
||||
port: Int = Random.nextInt(1024, 65535),
|
||||
additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||
additionalConfigurationConfigurator: TConfiguration.() -> Unit = {},
|
||||
block: Application.() -> Unit
|
||||
): TEngine = embeddedServer(
|
||||
engine,
|
||||
applicationEngineEnvironment {
|
||||
): TEngine {
|
||||
val env = applicationEngineEnvironment {
|
||||
module(block)
|
||||
connector {
|
||||
this.host = host
|
||||
this.port = port
|
||||
this@connector.host = host
|
||||
this@connector.port = port
|
||||
}
|
||||
additionalEngineEnvironmentConfigurator()
|
||||
},
|
||||
additionalConfigurationConfigurator
|
||||
)
|
||||
}
|
||||
return embeddedServer(engine, env)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create server with [CIO] server engine without starting of it
|
||||
@@ -35,31 +30,18 @@ fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configurati
|
||||
fun createKtorServer(
|
||||
host: String = "localhost",
|
||||
port: Int = Random.nextInt(1024, 65535),
|
||||
additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||
additionalConfigurationConfigurator: CIOApplicationEngine.Configuration.() -> Unit = {},
|
||||
block: Application.() -> Unit
|
||||
): CIOApplicationEngine = createKtorServer(
|
||||
CIO,
|
||||
host,
|
||||
port,
|
||||
additionalEngineEnvironmentConfigurator,
|
||||
additionalConfigurationConfigurator,
|
||||
block
|
||||
)
|
||||
): ApplicationEngine = createKtorServer(CIO, host, port, block)
|
||||
|
||||
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> createKtorServer(
|
||||
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
|
||||
host: String = "localhost",
|
||||
port: Int = Random.nextInt(1024, 65535),
|
||||
additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||
additionalConfigurationConfigurator: TConfiguration.() -> Unit = {},
|
||||
configurators: List<KtorApplicationConfigurator>
|
||||
): TEngine = createKtorServer(
|
||||
engine,
|
||||
host,
|
||||
port,
|
||||
additionalEngineEnvironmentConfigurator,
|
||||
additionalConfigurationConfigurator
|
||||
port
|
||||
) {
|
||||
configurators.forEach { it.apply { configure() } }
|
||||
}
|
||||
@@ -72,7 +54,5 @@ fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configurati
|
||||
fun createKtorServer(
|
||||
host: String = "localhost",
|
||||
port: Int = Random.nextInt(1024, 65535),
|
||||
configurators: List<KtorApplicationConfigurator>,
|
||||
additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||
additionalConfigurationConfigurator: CIOApplicationEngine.Configuration.() -> Unit = {},
|
||||
): ApplicationEngine = createKtorServer(CIO, host, port, additionalEngineEnvironmentConfigurator, additionalConfigurationConfigurator, configurators)
|
||||
configurators: List<KtorApplicationConfigurator>
|
||||
): ApplicationEngine = createKtorServer(CIO, host, port, configurators)
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -1,15 +1,14 @@
|
||||
package dev.inmo.micro_utils.ktor.server.configurators
|
||||
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.plugins.cachingheaders.CachingHeaders
|
||||
import io.ktor.server.plugins.cachingheaders.CachingHeadersConfig
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.install
|
||||
import io.ktor.features.CachingHeaders
|
||||
import kotlinx.serialization.Contextual
|
||||
|
||||
data class ApplicationCachingHeadersConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
fun interface Element { operator fun CachingHeadersConfig.invoke() }
|
||||
fun interface Element { operator fun CachingHeaders.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(CachingHeaders) {
|
||||
|
@@ -1,9 +1,8 @@
|
||||
package dev.inmo.micro_utils.ktor.server.configurators
|
||||
|
||||
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator.Element
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.Routing
|
||||
import io.ktor.application.*
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.Routing
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -19,7 +18,7 @@ class ApplicationRoutingConfigurator(
|
||||
}
|
||||
|
||||
override fun Application.configure() {
|
||||
pluginOrNull(Routing) ?.apply {
|
||||
featureOrNull(Routing) ?.apply {
|
||||
rootInstaller.apply { invoke() }
|
||||
} ?: install(Routing) {
|
||||
rootInstaller.apply { invoke() }
|
||||
|
@@ -1,15 +1,14 @@
|
||||
package dev.inmo.micro_utils.ktor.server.configurators
|
||||
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.sessions.Sessions
|
||||
import io.ktor.server.sessions.SessionsConfig
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.install
|
||||
import io.ktor.sessions.Sessions
|
||||
import kotlinx.serialization.Contextual
|
||||
|
||||
class ApplicationSessionsConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
fun interface Element { operator fun SessionsConfig.invoke() }
|
||||
fun interface Element { operator fun Sessions.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(Sessions) {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package dev.inmo.micro_utils.ktor.server.configurators
|
||||
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.application.Application
|
||||
|
||||
interface KtorApplicationConfigurator {
|
||||
fun Application.configure()
|
||||
|
@@ -1,15 +1,14 @@
|
||||
package dev.inmo.micro_utils.ktor.server.configurators
|
||||
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.plugins.statuspages.StatusPages
|
||||
import io.ktor.server.plugins.statuspages.StatusPagesConfig
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.install
|
||||
import io.ktor.features.StatusPages
|
||||
import kotlinx.serialization.Contextual
|
||||
|
||||
class StatusPagesConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
fun interface Element { operator fun StatusPagesConfig.invoke() }
|
||||
fun interface Element { operator fun StatusPages.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(StatusPages) {
|
||||
|
@@ -4,8 +4,8 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath libs.buildscript.kt.gradle
|
||||
classpath libs.buildscript.kt.serialization
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation libs.kt.stdlib
|
||||
implementation libs.kt.serialization
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version"
|
||||
|
||||
implementation libs.ktor.client
|
||||
implementation libs.ktor.client.java
|
||||
implementation "io.ktor:ktor-client-core:$ktor_version"
|
||||
implementation "io.ktor:ktor-client-java:$ktor_version"
|
||||
}
|
||||
|
||||
mainClassName="MainKt"
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
@@ -165,7 +164,7 @@ suspend fun main(vararg args: String) {
|
||||
|
||||
val ietfLanguageCodes = json.decodeFromString(
|
||||
ListSerializer(LanguageCode.serializer()),
|
||||
client.get(ietfLanguageCodesLink).bodyAsText()
|
||||
client.get(ietfLanguageCodesLink)
|
||||
).map {
|
||||
it.copy(
|
||||
title = it.title
|
||||
@@ -176,7 +175,7 @@ suspend fun main(vararg args: String) {
|
||||
}
|
||||
val ietfLanguageCodesWithTagsMap = json.decodeFromString(
|
||||
ListSerializer(LanguageCodeWithTag.serializer()),
|
||||
client.get(ietfLanguageCodesAdditionalTagsLink).bodyAsText()
|
||||
client.get(ietfLanguageCodesAdditionalTagsLink)
|
||||
).filter { it.withSubtag != it.tag }.groupBy { it.tag }
|
||||
|
||||
val tags = ietfLanguageCodes.map {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
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.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
@@ -15,7 +16,6 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) {
|
||||
|
||||
internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw)
|
||||
|
||||
@Suppress("OPT_IN_USAGE")
|
||||
@Serializer(MimeType::class)
|
||||
object MimeTypeSerializer : KSerializer<MimeType> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)
|
||||
|
@@ -23,7 +23,7 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
implementation libs.kt.serialization
|
||||
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version"
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
@@ -46,8 +46,8 @@ kotlin {
|
||||
androidTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-junit')
|
||||
implementation libs.android.test.junit
|
||||
implementation libs.android.espresso
|
||||
implementation "androidx.test.ext:junit:$test_ext_junit_version"
|
||||
implementation "androidx.test.espresso:espresso-core:$espresso_core"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
@@ -27,5 +27,4 @@ inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? =
|
||||
null
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()
|
||||
|
@@ -2,19 +2,19 @@ package dev.inmo.micro_utils.pagination.utils
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
|
||||
inline fun <T> doForAll(
|
||||
suspend fun <T> doForAll(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
paginationMapper: (PaginationResult<T>) -> Pagination?,
|
||||
block: (Pagination) -> PaginationResult<T>
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
) {
|
||||
doWithPagination(initialPagination) {
|
||||
block(it).let(paginationMapper)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> doForAllWithNextPaging(
|
||||
suspend fun <T> doForAllWithNextPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: (Pagination) -> PaginationResult<T>
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
) {
|
||||
doForAll(
|
||||
initialPagination,
|
||||
@@ -23,9 +23,9 @@ inline fun <T> doForAllWithNextPaging(
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <T> doAllWithCurrentPaging(
|
||||
suspend fun <T> doAllWithCurrentPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: (Pagination) -> PaginationResult<T>
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
) {
|
||||
doForAll(
|
||||
initialPagination,
|
||||
@@ -34,7 +34,7 @@ inline fun <T> doAllWithCurrentPaging(
|
||||
)
|
||||
}
|
||||
|
||||
inline fun <T> doForAllWithCurrentPaging(
|
||||
suspend fun <T> doForAllWithCurrentPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: (Pagination) -> PaginationResult<T>
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
) = doAllWithCurrentPaging(initialPagination, block)
|
||||
|
@@ -2,10 +2,10 @@ package dev.inmo.micro_utils.pagination.utils
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
|
||||
inline fun <T> getAll(
|
||||
suspend fun <T> getAll(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
paginationMapper: (PaginationResult<T>) -> Pagination?,
|
||||
block: (Pagination) -> PaginationResult<T>
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
): List<T> {
|
||||
val results = mutableListOf<T>()
|
||||
doForAll(initialPagination, paginationMapper) {
|
||||
@@ -16,45 +16,46 @@ inline fun <T> getAll(
|
||||
return results.toList()
|
||||
}
|
||||
|
||||
inline fun <T, R> R.getAllBy(
|
||||
suspend fun <T, R> R.getAllBy(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
paginationMapper: R.(PaginationResult<T>) -> Pagination?,
|
||||
block: R.(Pagination) -> PaginationResult<T>
|
||||
block: suspend R.(Pagination) -> PaginationResult<T>
|
||||
): List<T> = getAll(
|
||||
initialPagination,
|
||||
{ paginationMapper(it) },
|
||||
{ block(it) }
|
||||
)
|
||||
|
||||
inline fun <T> getAllWithNextPaging(
|
||||
suspend fun <T> getAllWithNextPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: (Pagination) -> PaginationResult<T>
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
): List<T> = getAll(
|
||||
initialPagination,
|
||||
{ it.nextPageIfNotEmpty() },
|
||||
block
|
||||
)
|
||||
|
||||
inline fun <T, R> R.getAllByWithNextPaging(
|
||||
suspend fun <T, R> R.getAllByWithNextPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: R.(Pagination) -> PaginationResult<T>
|
||||
block: suspend R.(Pagination) -> PaginationResult<T>
|
||||
): List<T> = getAllWithNextPaging(
|
||||
initialPagination,
|
||||
{ block(it) }
|
||||
)
|
||||
|
||||
inline fun <T> getAllWithCurrentPaging(
|
||||
suspend fun <T> getAllWithCurrentPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: (Pagination) -> PaginationResult<T>
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
): List<T> = getAll(
|
||||
initialPagination,
|
||||
{ it.currentPageIfNotEmpty() },
|
||||
block
|
||||
)
|
||||
|
||||
inline fun <T, R> R.getAllByWithCurrentPaging(
|
||||
suspend fun <T, R> R.getAllByWithCurrentPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: R.(Pagination) -> PaginationResult<T>
|
||||
block: suspend R.(Pagination) -> PaginationResult<T>
|
||||
): List<T> = getAllWithCurrentPaging(
|
||||
initialPagination
|
||||
) { block(it) }
|
||||
initialPagination,
|
||||
{ block(it) }
|
||||
)
|
||||
|
@@ -6,7 +6,7 @@ import dev.inmo.micro_utils.pagination.*
|
||||
* 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_|`
|
||||
*
|
||||
* @return Reversed version of this [Pagination]
|
||||
*/
|
||||
|
@@ -14,7 +14,7 @@ kotlin {
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api libs.jb.exposed
|
||||
api "org.jetbrains.exposed:exposed-core:$kotlin_exposed_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,8 +15,8 @@ kotlin {
|
||||
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api libs.ktor.server
|
||||
api libs.ktor.server.host.common
|
||||
api "io.ktor:ktor-server:$ktor_version"
|
||||
api "io.ktor:ktor-server-host-common:$ktor_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package dev.inmo.micro_utils.pagination
|
||||
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.http.Parameters
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
|
||||
val Parameters.extractPagination: Pagination
|
||||
get() = SimplePagination(
|
||||
|
@@ -4,6 +4,8 @@ import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class ReadKeyValueCacheRepo<Key,Value>(
|
||||
protected val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||
|
@@ -8,6 +8,8 @@ import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||
protected val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||
|
@@ -10,10 +10,10 @@ kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api libs.kt.coroutines
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
api internalProject("micro_utils.pagination.common")
|
||||
|
||||
api libs.uuid
|
||||
api "com.benasher44:uuid:$uuidVersion"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ kotlin {
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api libs.android.coreKtx
|
||||
api "androidx.core:core-ktx:$core_ktx_version"
|
||||
api internalProject("micro_utils.common")
|
||||
api internalProject("micro_utils.coroutines")
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||
suspend fun FromKey.toOutKey() = this as ToKey
|
||||
suspend fun FromValue.toOutValue() = this as ToValue
|
||||
|
@@ -1,10 +1,11 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithCurrentPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
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 keys(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 onValueRemoved: Flow<Pair<Key, Value>>
|
||||
val onDataCleared: Flow<Key>
|
||||
@@ -54,41 +55,41 @@ interface WriteKeyValuesRepo<Key, Value> : Repo {
|
||||
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>>>
|
||||
) = 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>>
|
||||
) = 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>
|
||||
) = 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
|
||||
) = 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>>>
|
||||
) = 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>>
|
||||
) = 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>
|
||||
) = 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
|
||||
) = 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) {
|
||||
doWithPagination {
|
||||
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>(
|
||||
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(
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
) = 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>>
|
||||
) = remove(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
k: Key,
|
||||
v: List<Value>
|
||||
) = 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,
|
||||
vararg v: Value
|
||||
) = remove(k, v.toList())
|
@@ -4,13 +4,13 @@ import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
|
||||
interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
|
||||
suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType>
|
||||
suspend fun getById(id: IdType): ObjectType?
|
||||
suspend fun contains(id: IdType): Boolean
|
||||
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>
|
||||
val <IdType> UpdatedValuePair<IdType, *>.id
|
||||
@@ -18,7 +18,7 @@ val <IdType> UpdatedValuePair<IdType, *>.id
|
||||
val <ValueType> UpdatedValuePair<*, ValueType>.value
|
||||
get() = second
|
||||
|
||||
interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
||||
interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
||||
val newObjectsFlow: Flow<ObjectType>
|
||||
val updatedObjectsFlow: Flow<ObjectType>
|
||||
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 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
|
||||
): 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>
|
||||
): 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
|
||||
) = deleteById(ids.toList())
|
||||
|
||||
interface CRUDRepo<ObjectType, IdType, InputValueType> : ReadCRUDRepo<ObjectType, IdType>,
|
||||
WriteCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
typealias StandardCRUDRepo<ObjectType, IdType, InputValueType> = CRUDRepo<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
|
||||
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
|
||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>
|
@@ -4,7 +4,7 @@ import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadKeyValueRepo<Key, Value> : Repo {
|
||||
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun get(k: Key): Value?
|
||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
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 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 onValueRemoved: Flow<Key>
|
||||
|
||||
@@ -22,25 +22,25 @@ interface WriteKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun unset(toUnset: List<Key>)
|
||||
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>
|
||||
) = 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
|
||||
) = 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
|
||||
) = unset(k.toList())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithValues(
|
||||
vararg v: Value
|
||||
) = 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 ->
|
||||
doAllWithCurrentPaging {
|
||||
keys(v, it).also {
|
||||
@@ -49,11 +49,4 @@ interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValue
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<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
|
||||
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>
|
||||
|
@@ -6,12 +6,10 @@ import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperReadKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperReadKeyValueRepo"))
|
||||
typealias MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadKeyValueRepo<ToKey, ToValue>,
|
||||
open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadStandardKeyValueRepo<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(
|
||||
k.toOutKey()
|
||||
) ?.toInnerValue()
|
||||
@@ -71,26 +69,24 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
@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>
|
||||
): ReadKeyValueRepo<FromKey, FromValue> = MapperReadKeyValueRepo(this, mapper)
|
||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
|
||||
|
||||
@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 valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperWriteKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperWriteKeyValueRepo"))
|
||||
typealias MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteKeyValueRepo<ToKey, ToValue>,
|
||||
open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteStandardKeyValueRepo<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) ->
|
||||
k.toInnerKey() to v.toInnerValue()
|
||||
}
|
||||
@@ -116,42 +112,39 @@ open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
@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>
|
||||
): WriteKeyValueRepo<FromKey, FromValue> = MapperWriteKeyValueRepo(this, mapper)
|
||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
|
||||
|
||||
@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 valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperKeyValueRepo"))
|
||||
typealias MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
open class MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: KeyValueRepo<ToKey, ToValue>,
|
||||
private val mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : KeyValueRepo<FromKey, FromValue>,
|
||||
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: StandardKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : StandardKeyValueRepo<FromKey, FromValue>,
|
||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||
ReadKeyValueRepo<FromKey, FromValue> by MapperReadKeyValueRepo(to, mapper),
|
||||
WriteKeyValueRepo<FromKey, FromValue> by MapperWriteKeyValueRepo(to, mapper)
|
||||
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
|
||||
WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
|
||||
|
||||
@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>
|
||||
): KeyValueRepo<FromKey, FromValue> = MapperKeyValueRepo(this, mapper)
|
||||
): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
|
||||
|
||||
@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 valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): KeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
@@ -6,12 +6,10 @@ import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperReadKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperReadKeyValuesRepo"))
|
||||
typealias MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadKeyValuesRepo<ToKey, ToValue>,
|
||||
open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadOneToManyKeyValueRepo<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(
|
||||
k: FromKey,
|
||||
pagination: Pagination,
|
||||
@@ -69,26 +67,24 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
@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>
|
||||
): ReadKeyValuesRepo<FromKey, FromValue> = MapperReadKeyValuesRepo(this, mapper)
|
||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
|
||||
|
||||
@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 valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadKeyValuesRepo<FromKey, FromValue> = withMapper(
|
||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperWriteKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperWriteKeyValuesRepo"))
|
||||
typealias MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteKeyValuesRepo<ToKey, ToValue>,
|
||||
open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteOneToManyKeyValueRepo<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) ->
|
||||
k.toInnerKey() to v.toInnerValue()
|
||||
}
|
||||
@@ -122,42 +118,39 @@ open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
@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>
|
||||
): WriteKeyValuesRepo<FromKey, FromValue> = MapperWriteKeyValuesRepo(this, mapper)
|
||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
|
||||
|
||||
@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 valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteKeyValuesRepo<FromKey, FromValue> = withMapper(
|
||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperKeyValuesRepo"))
|
||||
typealias MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
open class MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: KeyValuesRepo<ToKey, ToValue>,
|
||||
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : KeyValuesRepo<FromKey, FromValue>,
|
||||
) : OneToManyKeyValueRepo<FromKey, FromValue>,
|
||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||
ReadKeyValuesRepo<FromKey, FromValue> by MapperReadKeyValuesRepo(to, mapper),
|
||||
WriteKeyValuesRepo<FromKey, FromValue> by MapperWriteKeyValuesRepo(to, mapper)
|
||||
ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
|
||||
WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
|
||||
|
||||
@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>
|
||||
): KeyValuesRepo<FromKey, FromValue> = MapperKeyValuesRepo(this, mapper)
|
||||
): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
|
||||
|
||||
@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 valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): KeyValuesRepo<FromKey, FromValue> = withMapper(
|
||||
): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
@@ -1,10 +1,11 @@
|
||||
package dev.inmo.micro_utils.repos.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.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")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
||||
): List<T> = getAllWithNextPaging {
|
||||
|
@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
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")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
): List<Pair<Key, Value>> = getAllWithNextPaging {
|
||||
|
@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
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")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
): List<Pair<Key, List<Value>>> = getAllWithNextPaging {
|
||||
|
@@ -1,10 +1,10 @@
|
||||
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
|
||||
|
||||
class KeyValueBasedVersionsRepoProxy<T>(
|
||||
private val keyValueStore: KeyValueRepo<String, Int>,
|
||||
private val keyValueStore: StandardKeyValueRepo<String, Int>,
|
||||
override val database: T
|
||||
) : StandardVersionsRepoProxy<T> {
|
||||
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)
|
||||
|
@@ -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
|
||||
* [version] or is absent
|
||||
*
|
||||
* In case if [tableName] didn't found, will be called [onCreate]. Then in case if [tableName] have version less
|
||||
* than parameter [version] or null, it will increase version one-by-one until database version will be equal to
|
||||
* [version]
|
||||
* * In case if [tableName] didn't found, will be called [onCreate] and version of table will be set up to [version]
|
||||
* * In case if [tableName] have version less than parameter [version], it will increase version one-by-one
|
||||
* until database version will be equal to [version]
|
||||
*
|
||||
* @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
|
||||
* "to" will always be greater than "from"
|
||||
*/
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user