Compare commits

...

80 Commits

Author SHA1 Message Date
1ac50e9959 update several dependencies and fill changelog 2022-10-30 21:45:19 +06:00
6adfbe3a96 Update exposed and revert several dependencies updates 2022-10-24 23:52:04 +06:00
59f36e62e9 update dependencies 2022-10-22 14:25:46 +06:00
54af116009 start 0.13.2 2022-10-22 14:14:12 +06:00
38fbec8e3b Merge pull request #200 from InsanusMokrassar/0.13.1
0.13.1
2022-10-17 15:42:13 +06:00
babbfc55e4 update default of AbstractExposedWriteCRUDRepo 2022-10-17 15:31:27 +06:00
2511e18d69 AbstractExposedWriteCRUDRepo#createAndInsertId now is optional and returns nullable value 2022-10-17 14:42:53 +06:00
29658c70a0 start 0.13.1 2022-10-17 14:35:16 +06:00
96311ee43d Merge pull request #199 from InsanusMokrassar/0.13.0
0.13.0
2022-10-13 16:50:50 +06:00
bd33b09052 A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED 2022-10-11 13:24:39 +06:00
8055003b47 deprecations removing 2022-10-11 12:58:29 +06:00
1257492f85 changes in insert/update exposed methods and rewriting of read keyvalue(s) exposed repos to be extending abstract key value(s) repos 2022-10-11 12:55:25 +06:00
1107b7f4ef start 0.13.0 2022-10-11 12:07:49 +06:00
a1a1171240 start 0.12.18 2022-10-04 15:36:43 +06:00
46c02e5df1 Merge pull request #198 from InsanusMokrassar/0.12.17
0.12.17
2022-10-01 21:59:42 +06:00
2e9efc57de update dependencies 2022-10-01 21:56:47 +06:00
acecadef17 start 0.12.17 2022-10-01 21:41:34 +06:00
19394b5e69 Merge pull request #197 from InsanusMokrassar/0.12.16
0.12.16
2022-09-26 02:16:17 +06:00
de999e197f fix of setOnHierarchyChangeListenerRecursively 2022-09-26 01:10:10 +06:00
9d95687d3c FlowOnHierarchyChangeListener 2022-09-26 01:02:12 +06:00
aa9dfb4ab8 start 0.12.16 2022-09-26 00:47:32 +06:00
9c5b44efb3 Merge pull request #196 from InsanusMokrassar/0.12.15
0.12.15
2022-09-24 01:04:01 +06:00
ac587a67e6 findViewsByTag 2022-09-23 16:43:33 +06:00
59428140a8 applyDiff: Diff 2022-09-23 16:05:32 +06:00
60bdb59d71 fix version change 2022-09-23 12:41:57 +06:00
be52871de8 flows extensions 2022-09-23 12:41:07 +06:00
b7934cf357 flows extensions 2022-09-23 12:33:00 +06:00
dbfbeef90a start 0.12.15 2022-09-23 12:12:15 +06:00
00943c9cdf add docs for Diff class 2022-09-23 12:10:24 +06:00
8745c6a16a Merge pull request #195 from InsanusMokrassar/0.12.14
0.12.14
2022-09-23 00:19:12 +06:00
433ba4b58f argumentOrNull/argumentOrThrow 2022-09-22 23:39:08 +06:00
d40376e524 Update CHANGELOG.md 2022-09-22 21:51:40 +06:00
a2982f88f5 Update libs.versions.toml 2022-09-21 21:17:34 +06:00
1642f7abd9 improvements in AwaitFirst 2022-09-21 18:00:39 +06:00
a10d2184ff start 0.12.14 2022-09-21 17:53:57 +06:00
522435f096 Merge pull request #194 from InsanusMokrassar/0.12.13
make actor not async
2022-09-15 01:43:21 +06:00
79b30290c0 fix build 2022-09-15 01:19:05 +06:00
f8b8626859 make actor not async 2022-09-15 01:11:20 +06:00
b061b85a08 Merge pull request #193 from InsanusMokrassar/0.12.13
0.12.13
2022-09-14 23:27:59 +06:00
3870db1c88 fix of #155 and build 2022-09-14 22:47:57 +06:00
1be1070eb4 fix of #160 2022-09-14 22:32:00 +06:00
2696e663cf add RandomQualifier 2022-09-14 22:21:30 +06:00
1e1f7df86d create koin module 2022-09-14 22:14:20 +06:00
1d8ded8fd3 start 0.12.13 2022-09-14 21:42:29 +06:00
197825123a Merge pull request #192 from InsanusMokrassar/0.12.12
0.12.12
2022-09-13 22:08:52 +06:00
422b2e6db1 improve SkeletonAnimation 2022-09-13 22:01:02 +06:00
1973e0b5bf SkeletonAnimation 2022-09-13 01:30:36 +06:00
8258cf93a9 start 0.12.12 2022-09-12 21:03:07 +06:00
1d49bd5947 Merge pull request #191 from InsanusMokrassar/0.12.11
0.12.11
2022-09-08 23:46:55 +06:00
44317d1519 start 0.12.11 and add values in key value cache repo 2022-09-08 22:38:24 +06:00
48e08fcc69 Merge pull request #190 from InsanusMokrassar/0.12.10
0.12.10
2022-09-07 23:54:33 +06:00
1a3ce6e623 hotfix 2022-09-07 23:47:13 +06:00
fb122f3e70 0.12.10 2022-09-07 23:45:59 +06:00
7cca6039cc Merge pull request #189 from InsanusMokrassar/0.12.9
fix of cache filling without clearing of values
2022-09-07 22:16:50 +06:00
118e3dba39 fix of cache filling without clearing of values 2022-09-07 21:56:41 +06:00
87070710fa Merge pull request #188 from InsanusMokrassar/0.12.9
update korlibs
2022-09-07 21:14:26 +06:00
38499c3d4a update korlibs 2022-09-07 21:09:57 +06:00
5d754d968b Merge pull request #187 from InsanusMokrassar/0.12.9
0.12.9
2022-09-07 20:26:46 +06:00
d543d436bc Fixes in key values cache 2022-09-07 20:01:13 +06:00
12b54f99af update gradle wrapper 2022-09-07 19:49:29 +06:00
2a95d7e643 start 0.12.9 2022-09-07 19:48:59 +06:00
e3d3cacfa4 Merge pull request #186 from InsanusMokrassar/0.12.8
0.12.8
2022-09-07 00:57:17 +06:00
4b13491a0e Update CHANGELOG.md 2022-09-07 00:56:48 +06:00
85d516d1e9 Update libs.versions.toml 2022-09-06 23:44:02 +06:00
ac58b6a7e3 bodyOrNull/respondOrNoContent 2022-09-06 21:16:41 +06:00
2cc6126765 start 0.12.8 2022-09-06 21:15:01 +06:00
f94b085850 Merge pull request #185 from InsanusMokrassar/0.12.7
0.12.7
2022-09-05 18:35:56 +06:00
c9822a491b WriteCRUDCacheRepo subscribtions and changeResultsUnchecked(Pagination) 2022-09-05 15:07:33 +06:00
23b2d60295 start 0.12.7 2022-09-05 14:20:58 +06:00
f4bc9eed39 Merge pull request #184 from InsanusMokrassar/0.12.6
0.12.6
2022-08-31 04:10:33 +06:00
e310f188b0 fixed absence of image/* in known mime types 2022-08-31 03:01:06 +06:00
6c1571188c start 0.12.6 2022-08-31 02:59:29 +06:00
945d2fa284 Merge pull request #183 from InsanusMokrassar/0.12.5
0.12.5
2022-08-30 17:38:57 +06:00
020095f1ff Update CHANGELOG.md 2022-08-30 17:37:59 +06:00
b165a76e62 fixes in selectPaginated 2022-08-30 15:20:45 +06:00
03c8830672 start 0.12.5 2022-08-30 15:17:09 +06:00
38448da89b Merge pull request #182 from InsanusMokrassar/0.12.4
0.12.4
2022-08-23 13:02:19 +06:00
2ade5aff91 update dependencies 2022-08-23 12:57:16 +06:00
29bf6e80ec start 0.12.4 2022-08-23 12:55:52 +06:00
027e927e1b Merge pull request #181 from InsanusMokrassar/0.12.3
0.12.3
2022-08-22 01:44:17 +06:00
60 changed files with 915 additions and 1530 deletions

View File

@@ -1,5 +1,141 @@
# Changelog
## 0.13.2
* `Versions`:
* `Klock`: `3.1.0` -> `3.3.0`
* `Ktor`: `2.1.2` -> `2.1.3`
## 0.13.1
* `Repos`:
* `Exposed`:
* `AbstractExposedWriteCRUDRepo#createAndInsertId` now is optional and returns nullable value
## 0.13.0
**ALL DEPRECATIONS HAVE BEEN REMOVED**
**A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED**
* `Repos`:
* `Exposed`:
* `AbstractExposedWriteCRUDRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `createAndInsertId`
* Old `update` method has been deprecated and not recommended to override anymore in realizations
* Old `insert` method now is `open` instead of `abstract` and can be omitted
* `AbstractExposedKeyValueRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `insertKey`
* Old `update` method has been deprecated and not recommended to override anymore
* Old `insert` method now is `open` instead of `abstract` and can be omitted in realizations
## 0.12.17
* `Versions`:
* `JB Compose`: `1.2.0-alpha01-dev774` -> `1.2.0-beta02`
* `Ktor`: `2.1.1` -> `2.1.2`
* `Koin`: `3.2.0` -> `3.2.2`
## 0.12.16
* `Coroutines`:
* `Android`:
* Add class `FlowOnHierarchyChangeListener`
* Add `ViewGroup#setOnHierarchyChangeListenerRecursively(OnHierarchyChangeListener)`
## 0.12.15
* `Common`:
* `applyDiff` will return `Diff` object since this release
* `Android`:
* New functions/extensions `findViewsByTag` and `findViewsByTagInActivity`
* `Coroutines`:
* Add `Flow` extensions `flatMap`, `flatMapNotNull` and `flatten`
* Add `Flow` extensions `takeNotNull` and `filterNotNull`
## 0.12.14
* `Versions`:
* `Android CoreKTX`: `1.8.0` -> `1.9.0`
* `Android AppCompat`: `1.4.2` -> `1.5.1`
* Android Compile SDK: 32 -> 33
* Android Build Tools: 32.0.0 -> 33.0.0
* `Common`:
* `Android`:
* Add `argumentOrNull`/`argumentOrThrow` delegates for fragments
* `Coroutines`:
* Rewrite `awaitFirstWithDeferred` onto `CompletableDeferred` instead of coroutines suspending
## 0.12.13
* `Coroutines`:
* Add opportunity to use markers in actors (solution of [#160](https://github.com/InsanusMokrassar/MicroUtils/issues/160))
* `Koin`:
* Module inited :)
* `Repos`:
* `Android`:
* Add typealias `KeyValueSPRepo` and opportunity to create shared preferences `KeyValue` repo with `KeyValueStore(...)` (fix of [#155](https://github.com/InsanusMokrassar/MicroUtils/issues/155))
## 0.12.12
* `Common`:
* `Compose`:
* `JS`:
* Add `SkeletonAnimation` stylesheet
## 0.12.11
* `Repos`:
* `Cache`:
* Override `KeyValue` cache method `values`
## 0.12.10
* `Repos`:
* `Cache`:
* Hotfix in key values `get`
## 0.12.9
* `Versions`:
* `Klock`: `3.0.0` -> `3.1.0`
* `Repos`:
* `Cache`:
* Fixes in key values cache
## 0.12.8
* `Versions`:
* `Ktor`: `2.1.0` -> `2.1.1`
* `Compose`: `1.2.0-alpha01-dev764` -> `1.2.0-alpha01-dev774`
* `Ktor`:
* `Client`:
* New extension `HttpClient#bodyOrNull` which returns `null` in case when server responded with `No Content` (204)
* `Server`:
* New extension `ApplicationCall#respondOrNoContent` which responds `No Content` (204) when passed data is null
## 0.12.7
* `Repos`:
* `Cache`:
* Force `WriteCRUDCacheRepo` to subscribe on new and updated objects of parent repo
* `Pagination`:
* New function `changeResultsUnchecked(Pagination)`
## 0.12.6
* `MimeeTypes>`:
* Fixed absence of `image/*` in known mime types
## 0.12.5
* `Repos`:
* `Exposed`:
* Fixes in `paginate` extensions
## 0.12.4
* `Versions`:
* `Kotlin`: `1.7.0` -> `1.7.10`
* `Compose`: `1.2.0-alpha01-dev755` -> `1.2.0-alpha01-dev764`
## 0.12.3
* `Repos`:

View File

@@ -16,6 +16,7 @@ kotlin {
androidMain {
dependencies {
api project(":micro_utils.coroutines")
api libs.android.fragment
}
}
}

View File

@@ -0,0 +1,43 @@
package dev.inmo.micro_utils.common.compose
import org.jetbrains.compose.web.css.*
object SkeletonAnimation : StyleSheet() {
val skeletonKeyFrames: CSSNamedKeyframes by keyframes {
to { backgroundPosition("-20% 0") }
}
fun CSSBuilder.includeSkeletonStyle(
duration: CSSSizeValue<out CSSUnitTime> = 2.s,
timingFunction: AnimationTimingFunction = AnimationTimingFunction.EaseInOut,
iterationCount: Int? = null,
direction: AnimationDirection = AnimationDirection.Normal,
keyFrames: CSSNamedKeyframes = skeletonKeyFrames,
hideChildren: Boolean = true,
hideText: Boolean = hideChildren
) {
backgroundImage("linear-gradient(110deg, rgb(236, 236, 236) 40%, rgb(245, 245, 245) 50%, rgb(236, 236, 236) 65%)")
backgroundSize("200% 100%")
backgroundPosition("180% 0")
animation(keyFrames) {
duration(duration)
timingFunction(timingFunction)
iterationCount(iterationCount)
direction(direction)
}
if (hideText) {
property("color", "${Color.transparent} !important")
}
if (hideChildren) {
child(self, universal) style {
property("visibility", "hidden")
}
}
}
val skeleton by style {
includeSkeletonStyle()
}
}

View File

@@ -14,6 +14,14 @@ private inline fun <T> getObject(
/**
* Diff object which contains information about differences between two [Iterable]s
*
* See tests for more info
*
* @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection
* @param added The object which appear in new collection only. Indexes here show the index in the new collection
* @param replaced Pair of old-new changes. First object has been presented in the old collection on its
* [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in
* case when some value has been replaced after adds or removes in original collection the object index will be changed
*
* @see calculateDiff
*/
data class Diff<T> internal constructor(
@@ -165,7 +173,7 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
strictComparison: Boolean = false
) = calculateDiff(source, strictComparison).let {
): Diff<T> = calculateDiff(source, strictComparison).also {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}

View File

@@ -0,0 +1,76 @@
package dev.inmo.micro_utils.common
import android.os.Bundle
import android.os.Parcelable
import androidx.fragment.app.Fragment
import java.io.Serializable
import kotlin.reflect.KProperty
object ArgumentPropertyNullableDelegate {
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T? {
val arguments = thisRef.arguments ?: return null
val key = property.name
return when (property.getter.returnType.classifier) {
// Scalars
String::class -> arguments.getString(key)
Boolean::class -> arguments.getBoolean(key)
Byte::class -> arguments.getByte(key)
Char::class -> arguments.getChar(key)
Double::class -> arguments.getDouble(key)
Float::class -> arguments.getFloat(key)
Int::class -> arguments.getInt(key)
Long::class -> arguments.getLong(key)
Short::class -> arguments.getShort(key)
// References
Bundle::class -> arguments.getBundle(key)
CharSequence::class -> arguments.getCharSequence(key)
Parcelable::class -> arguments.getParcelable(key)
// Scalar arrays
BooleanArray::class -> arguments.getBooleanArray(key)
ByteArray::class -> arguments.getByteArray(key)
CharArray::class -> arguments.getCharArray(key)
DoubleArray::class -> arguments.getDoubleArray(key)
FloatArray::class -> arguments.getFloatArray(key)
IntArray::class -> arguments.getIntArray(key)
LongArray::class -> arguments.getLongArray(key)
ShortArray::class -> arguments.getShortArray(key)
Array::class -> {
val componentType = property.returnType.classifier ?.javaClass ?.componentType!!
@Suppress("UNCHECKED_CAST") // Checked by reflection.
when {
Parcelable::class.java.isAssignableFrom(componentType) -> {
arguments.getParcelableArray(key)
}
String::class.java.isAssignableFrom(componentType) -> {
arguments.getStringArray(key)
}
CharSequence::class.java.isAssignableFrom(componentType) -> {
arguments.getCharSequenceArray(key)
}
Serializable::class.java.isAssignableFrom(componentType) -> {
arguments.getSerializable(key)
}
else -> {
val valueType = componentType.canonicalName
throw IllegalArgumentException(
"Illegal value array type $valueType for key \"$key\""
)
}
}
}
Serializable::class -> arguments.getSerializable(key)
else -> null
} as? T
}
}
object ArgumentPropertyNonNullableDelegate {
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T {
return ArgumentPropertyNullableDelegate.getValue<T>(thisRef, property)!!
}
}
fun argumentOrNull() = ArgumentPropertyNullableDelegate
fun argumentOrThrow() = ArgumentPropertyNonNullableDelegate

View File

@@ -0,0 +1,61 @@
package dev.inmo.micro_utils.common
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.fragment.app.Fragment
fun findViewsByTag(viewGroup: ViewGroup, tag: Any?): List<View> {
return viewGroup.children.flatMap {
findViewsByTag(it, tag)
}.toList()
}
fun findViewsByTag(viewGroup: ViewGroup, key: Int, tag: Any?): List<View> {
return viewGroup.children.flatMap {
findViewsByTag(it, key, tag)
}.toList()
}
fun findViewsByTag(view: View, tag: Any?): List<View> {
val result = mutableListOf<View>()
if (view.tag == tag) {
result.add(view)
}
if (view is ViewGroup) {
result.addAll(findViewsByTag(view, tag))
}
return result.toList()
}
fun findViewsByTag(view: View, key: Int, tag: Any?): List<View> {
val result = mutableListOf<View>()
if (view.getTag(key) == tag) {
result.add(view)
}
if (view is ViewGroup) {
result.addAll(findViewsByTag(view, key, tag))
}
return result.toList()
}
fun Activity.findViewsByTag(tag: Any?) = rootView ?.let {
findViewsByTag(it, tag)
}
fun Activity.findViewsByTag(key: Int, tag: Any?) = rootView ?.let {
findViewsByTag(it, key, tag)
}
fun Fragment.findViewsByTag(tag: Any?) = view ?.let {
findViewsByTag(it, tag)
}
fun Fragment.findViewsByTag(key: Int, tag: Any?) = view ?.let {
findViewsByTag(it, key, tag)
}
fun Fragment.findViewsByTagInActivity(tag: Any?) = activity ?.findViewsByTag(tag)
fun Fragment.findViewsByTagInActivity(key: Int, tag: Any?) = activity ?.findViewsByTag(key, tag)

View File

@@ -0,0 +1,7 @@
package dev.inmo.micro_utils.common
import android.app.Activity
import android.view.View
val Activity.rootView: View?
get() = findViewById<View?>(android.R.id.content) ?.rootView ?: window.decorView.findViewById<View?>(android.R.id.content) ?.rootView

View File

@@ -1,19 +1,15 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.consumeAsFlow
fun <T> CoroutineScope.actor(
channelCapacity: Int = Channel.UNLIMITED,
block: suspend (T) -> Unit
): Channel<T> {
val channel = Channel<T>(channelCapacity)
launch {
for (data in channel) {
block(data)
}
}
channel.consumeAsFlow().subscribe(this, block)
return channel
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
fun <T> CoroutineScope.actorAsync(
channelCapacity: Int = Channel.UNLIMITED,
markerFactory: suspend (T) -> Any? = { null },
block: suspend (T) -> Unit
): Channel<T> {
val channel = Channel<T>(channelCapacity)
channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
return channel
}
inline fun <T> CoroutineScope.safeActorAsync(
channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline markerFactory: suspend (T) -> Any? = { null },
crossinline block: suspend (T) -> Unit
): Channel<T> = actorAsync(
channelCapacity,
markerFactory
) {
safely(onException) {
block(it)
}
}

View File

@@ -6,23 +6,19 @@ import kotlin.coroutines.*
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
scope: CoroutineScope,
cancelOnResult: Boolean = true
): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation ->
scope.launch(SupervisorJob()) {
val scope = this
forEach {
scope.launch {
continuation.resume(it to it.await())
scope.cancel()
}
): Pair<Deferred<T>, T> {
val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>()
val scope = scope.LinkedSupervisorScope()
forEach {
scope.launch {
resultDeferred.complete(it to it.await())
scope.cancel()
}
}
}.also {
if (cancelOnResult) {
forEach {
try {
it.cancel()
} catch (e: IllegalStateException) {
e.printStackTrace()
return resultDeferred.await().also {
if (cancelOnResult) {
forEach {
runCatchingSafely { it.cancel() }
}
}
}

View File

@@ -0,0 +1,39 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.*
import kotlin.js.JsName
import kotlin.jvm.JvmName
inline fun <T, R> Flow<Flow<T>>.flatMap(
crossinline mapper: suspend (T) -> R
) = flow {
collect {
it.collect {
emit(mapper(it))
}
}
}
@JsName("flatMapIterable")
@JvmName("flatMapIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMap(
crossinline mapper: suspend (T) -> R
) = map {
it.asFlow()
}.flatMap(mapper)
inline fun <T, R> Flow<Flow<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
@JsName("flatMapNotNullIterable")
@JvmName("flatMapNotNullIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
fun <T> Flow<Flow<T>>.flatten() = flatMap { it }
@JsName("flattenIterable")
@JvmName("flattenIterable")
fun <T> Flow<Iterable<T>>.flatten() = flatMap { it }

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.*
fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
fun <T> Flow<T>.filterNotNull() = takeNotNull()

View File

@@ -0,0 +1,50 @@
package dev.inmo.micro_utils.coroutines
import android.view.View
import android.view.ViewGroup
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
/**
* [kotlinx.coroutines.flow.Flow]-based [android.view.ViewGroup.OnHierarchyChangeListener]
*
* @param recursive If set, any call of [onChildViewAdded] will check if child [View] is [ViewGroup] and subscribe to this
* [ViewGroup] too
* @param [_onChildViewAdded] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewAdded] flow
* @param [_onChildViewRemoved] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewRemoved] flow
*/
class FlowOnHierarchyChangeListener(
private val recursive: Boolean = false,
private val _onChildViewAdded: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE),
private val _onChildViewRemoved: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
) : ViewGroup.OnHierarchyChangeListener {
val onChildViewAdded = _onChildViewAdded.asSharedFlow()
val onChildViewRemoved = _onChildViewRemoved.asSharedFlow()
/**
* Will emit data into [onChildViewAdded] flow. If [recursive] is true and [child] is [ViewGroup] will also
* subscribe to [child] hierarchy changes.
*
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewAdded]. That is why its default extraBufferCapacity is
* [Int.MAX_VALUE]
*/
override fun onChildViewAdded(parent: View, child: View) {
_onChildViewAdded.tryEmit(parent to child)
if (recursive && child is ViewGroup) {
child.setOnHierarchyChangeListener(this)
}
}
/**
* Just emit data into [onChildViewRemoved]
*
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewRemoved]. That is why its default extraBufferCapacity is
* [Int.MAX_VALUE]
*/
override fun onChildViewRemoved(parent: View, child: View) {
_onChildViewRemoved.tryEmit(parent to child)
}
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.coroutines
import android.view.ViewGroup
import android.view.ViewGroup.OnHierarchyChangeListener
/**
* Use [ViewGroup.setOnHierarchyChangeListener] recursively for all available [ViewGroup]s starting with [this].
* This extension DO NOT guarantee that recursive subscription will happen after this method call
*/
fun ViewGroup.setOnHierarchyChangeListenerRecursively(
listener: OnHierarchyChangeListener
) {
setOnHierarchyChangeListener(listener)
(0 until childCount).forEach {
(getChildAt(it) as? ViewGroup) ?.setOnHierarchyChangeListenerRecursively(listener)
}
}

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.12.3
android_code_version=142
version=0.13.2
android_code_version=160

View File

@@ -1,36 +1,40 @@
[versions]
kt = "1.7.0"
kt-serialization = "1.4.0"
kt = "1.7.10"
kt-serialization = "1.4.1"
kt-coroutines = "1.6.4"
jb-compose = "1.2.0-alpha01-dev755"
jb-compose = "1.2.0"
jb-exposed = "0.39.2"
jb-dokka = "1.7.0"
jb-dokka = "1.7.10"
klock = "3.0.0"
klock = "3.3.0"
uuid = "0.5.0"
ktor = "2.1.0"
ktor = "2.1.3"
gh-release = "2.4.1"
koin = "3.2.2"
android-gradle = "7.2.2"
dexcount = "3.1.0"
android-coreKtx = "1.8.0"
android-coreKtx = "1.9.0"
android-recyclerView = "1.2.1"
android-appCompat = "1.4.2"
android-appCompat = "1.5.1"
android-fragment = "1.5.3"
android-espresso = "3.4.0"
android-test = "1.1.3"
android-props-minSdk = "21"
android-props-compileSdk = "32"
android-props-buildTools = "32.0.0"
android-props-compileSdk = "33"
android-props-buildTools = "33.0.0"
[libraries]
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
kt-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", 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" }
@@ -59,6 +63,7 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
@@ -67,6 +72,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
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-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }

View File

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

28
koin/build.gradle Normal file
View File

@@ -0,0 +1,28 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api libs.koin
api libs.uuid
}
}
jvmMain {
dependencies {
api libs.kt.reflect
}
}
androidMain {
dependencies {
api libs.kt.reflect
}
}
}
}

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
/**
* Will be useful in case you need to declare some singles with one type several types, but need to separate them and do
* not care about how :)
*/
inline fun <reified T : Any> Module.factoryWithRandomQualifier(
noinline definition: Definition<T>
) = factory(RandomQualifier(), definition)

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
import org.koin.core.qualifier.StringQualifier
inline fun <reified T : Any> Module.factory(
qualifier: String,
noinline definition: Definition<T>
) = factory(StringQualifier(qualifier), definition)

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.scope.Scope
inline fun <reified T : Any> Scope.getAllDistinct() = getAll<T>().distinct()
inline fun <reified T : Any> Koin.getAllDistinct() = getAll<T>().distinct()

View File

@@ -0,0 +1,8 @@
package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.scope.Scope
inline fun <reified T : Any> Scope.getAny() = getAll<T>().first()
inline fun <reified T : Any> Koin.getAny() = getAll<T>().first()

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.koin
import com.benasher44.uuid.uuid4
import org.koin.core.qualifier.StringQualifier
fun RandomQualifier(randomFun: () -> String = { uuid4().toString() }) = StringQualifier(randomFun())

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
/**
* Will be useful in case you need to declare some singles with one type several types, but need to separate them and do
* not care about how :)
*/
inline fun <reified T : Any> Module.singleWithRandomQualifier(
createdAtStart: Boolean = false,
noinline definition: Definition<T>
) = single(RandomQualifier(), createdAtStart, definition)

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
import org.koin.core.qualifier.StringQualifier
inline fun <reified T : Any> Module.single(
qualifier: String,
createdAtStart: Boolean = false,
noinline definition: Definition<T>
) = single(StringQualifier(qualifier), createdAtStart, definition)

View File

@@ -0,0 +1,27 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.binds
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: Qualifier? = null,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}
inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: String,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import kotlin.reflect.KClass
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return factoryWithBinds(RandomQualifier(), bindFilter, definition)
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.binds
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
inline fun <reified T : Any> Module.singleWithBinds(
qualifier: Qualifier? = null,
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}
inline fun <reified T : Any> Module.singleWithBinds(
qualifier: String,
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}

View File

@@ -0,0 +1,14 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import kotlin.reflect.KClass
inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds(
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
}

View File

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

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.ktor.client
import io.ktor.client.call.body
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpStatusCode
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf {
status == HttpStatusCode.OK
} ?.body<T>()

View File

@@ -1,82 +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.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 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
*/
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow {
do {
val reconnect = runCatchingSafely {
ws(correctedUrl, requestBuilder) {
for (received in incoming) {
when (received) {
is Frame.Binary -> send(conversation(received.data))
else -> {
close()
return@ws
}
}
}
}
checkReconnection(null)
}.getOrElse { e ->
checkReconnection(e).also {
if (!it) {
close(e)
}
}
}
} while (reconnect && isActive)
if (isActive) {
safely {
close()
}
}
}
}
/**
* @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
*/
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
deserializer: DeserializationStrategy<T>,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(
url,
checkReconnection,
requestBuilder
) {
serialFormat.decodeDefault(deserializer, it)
}

View File

@@ -1,260 +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.*
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.*
@Deprecated("This class will be removed soon. It is now recommended to use built-in ktor features instead")
class UnifiedRequester(
val client: HttpClient = HttpClient(),
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) {
suspend fun <ResultType> uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>
): ResultType = client.uniget(url, resultDeserializer, serialFormat)
fun <T> encodeUrlQueryValue(
serializationStrategy: SerializationStrategy<T>,
value: T
) = serializationStrategy.encodeUrlQueryValue(
value,
serialFormat
)
suspend fun <BodyType, ResultType> unipost(
url: String,
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
mppFile: MPPFile,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
fun <T> createStandardWebsocketFlow(
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)
}
@Deprecated("This property will be removed soon. It is now recommended to use built-in ktor features instead")
val defaultRequester = UnifiedRequester()
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = get(url).let {
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
value: T,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = serialFormat.encodeHex(
this,
value
)
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unipost(
url: String,
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = post(url) {
setBody(
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
)
}.let {
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = submitFormWithBinaryData(
url,
formData = formData {
append(
"bytes",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, mimetype)
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
}
) {
requestBuilder()
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
filename: String,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
filename,
inputProvider,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile.filename.string,
mppFile.inputProvider(),
resultDeserializer,
mimetype,
additionalParametersBuilder,
dataHeadersBuilder,
requestBuilder,
serialFormat
)
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)

View File

@@ -9,11 +9,3 @@ expect suspend fun HttpClient.tempUpload(
file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
): TemporalFileId
suspend fun UnifiedRequester.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
): TemporalFileId = client.tempUpload(
fullTempUploadDraftPath, file, onUpload
)

View File

@@ -1,61 +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
@Deprecated("This method will be removed soon")
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
protocol: URLProtocol? = null,
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
) {
application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets)
}
webSocket(suburl, protocol ?.name) {
safely {
flow.collect {
converter(it) ?.let { data ->
send(data)
}
}
}
}
}
@Deprecated("This method will be removed soon")
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
protocol: URLProtocol? = null,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(
suburl,
flow,
protocol,
converter = if (filter == null) {
{
serialFormat.encodeDefault(serializer, it)
}
} else {
{
if (filter(it)) {
serialFormat.encodeDefault(serializer, it)
} else {
null
}
}
}
)

View File

@@ -0,0 +1,15 @@
package dev.inmo.micro_utils.ktor.server
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond
suspend inline fun <reified T : Any> ApplicationCall.respondOrNoContent(
data: T?
) {
if (data == null) {
respond(HttpStatusCode.NoContent)
} else {
respond(data)
}
}

View File

@@ -19,106 +19,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
@Deprecated("This class method will be removed soon. It is now recommended to use built-in ktor features instead")
class UnifiedRouter(
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) {
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)
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T
) {
call.respondBytes (
serialFormat.encodeDefault(answerSerializer, answer),
serialFormatContentType
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
deserializer: DeserializationStrategy<T>
) = safely {
serialFormat.decodeDefault(
deserializer,
call.receive()
)
}
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
field: String
) = call.parameters[field].also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
field: String
) = call.request.queryParameters[field]
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
field: String,
deserializer: DeserializationStrategy<T>
) = getQueryParameter(field) ?.let {
serialFormat.decodeHex(
deserializer,
it
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
field: String,
deserializer: DeserializationStrategy<T>
) = decodeUrlQueryValue(field, deserializer).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
companion object {
val default
get() = defaultUnifiedRouter
}
}
val defaultUnifiedRouter = UnifiedRouter()
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T
) {
respondBytes (
standardKtorSerialFormat.encodeDefault(answerSerializer, answer),
standardKtorSerialFormatContentType
)
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniload(
deserializer: DeserializationStrategy<T>
) = safely {
standardKtorSerialFormat.decodeDefault(
deserializer,
receive()
)
}
suspend fun ApplicationCall.uniloadMultipart(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
@@ -146,82 +46,6 @@ suspend fun ApplicationCall.uniloadMultipart(
resultInput ?: error("Bytes has not been received")
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniloadMultipart(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
): Pair<Input, T> {
var data: Optional<T>? = null
val resultInput = uniloadMultipart(
onFormItem,
{
if (it.name == "data") {
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
} else {
onCustomFileItem(it)
}
},
onBinaryChannelItem,
onBinaryContent
)
val completeData = data ?: error("Data has not been received")
return resultInput to (completeData.dataOrNull().let { it as T })
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniloadMultipartFile(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
var resultInput: MPPFile? = null
var data: Optional<T>? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
when (it.name) {
"bytes" -> {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile(
name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.streamProvider().use {
it.copyTo(fileStream)
}
}
}
}
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
else -> onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
}
}
val completeData = data ?: error("Data has not been received")
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
}
suspend fun ApplicationCall.uniloadMultipartFile(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
@@ -285,24 +109,3 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
fun <T> ApplicationCall.decodeUrlQueryValue(
field: String,
deserializer: DeserializationStrategy<T>
) = getQueryParameter(field) ?.let {
standardKtorSerialFormat.decodeHex(
deserializer,
it
)
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError(
field: String,
deserializer: DeserializationStrategy<T>
) = decodeUrlQueryValue(field, deserializer).also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}

View File

@@ -26,7 +26,6 @@ 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 {
@@ -80,42 +79,40 @@ class TemporalFilesRoutingConfigurator(
override fun Route.invoke() {
post(subpath) {
unifiedRouter.apply {
val multipart = call.receiveMultipart()
val multipart = call.receiveMultipart()
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
var part = multipart.readPart()
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
var part = multipart.readPart()
while (part != null) {
if (part is PartData.FileItem) {
break
}
part = multipart.readPart()
while (part != null) {
if (part is PartData.FileItem) {
break
}
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)
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)
}
}

View File

@@ -2017,6 +2017,7 @@ internal val knownMimeTypes: Set<MimeType> = setOf(
KnownMimeTypes.Chemical.XCml,
KnownMimeTypes.Chemical.XCsml,
KnownMimeTypes.Chemical.XXyz,
KnownMimeTypes.Image.Any,
KnownMimeTypes.Image.Bmp,
KnownMimeTypes.Image.Cgm,
KnownMimeTypes.Image.G3fax,

View File

@@ -33,21 +33,17 @@ data class PaginationResult<T>(
results,
(pagesNumber * size).toLong()
)
@Deprecated("Replace with The other order of incoming parameters or objectsCount parameter")
constructor(
page: Int,
pagesNumber: Int,
results: List<T>,
size: Int
) : this(
page,
results,
pagesNumber,
size
)
}
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L)
fun <T> emptyPaginationResult(
basePagination: Pagination
) = PaginationResult<T>(
basePagination.page,
basePagination.size,
emptyList(),
0L
)
/**
* @return New [PaginationResult] with [data] without checking of data sizes equality

View File

@@ -4,11 +4,7 @@ import org.jetbrains.exposed.sql.*
fun Query.paginate(with: Pagination, orderBy: Pair<Expression<*>, SortOrder>? = null) = limit(
with.size,
(if (orderBy ?.second == SortOrder.DESC) {
with.lastIndex
} else {
with.firstIndex
}).toLong()
with.firstIndex.toLong()
).let {
if (orderBy != null) {
it.orderBy(

View File

@@ -33,6 +33,14 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach {
kvCache.set(idGetter(it), it)
}.launchIn(scope)
val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach {
kvCache.set(idGetter(it), it)
}.launchIn(scope)
val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach {
kvCache.unset(it)
}.launchIn(scope)

View File

@@ -1,7 +1,6 @@
package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import kotlinx.coroutines.CoroutineScope
@@ -14,6 +13,16 @@ open class ReadKeyValueCacheRepo<Key,Value>(
) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo {
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return keys(pagination, reversed).let {
it.changeResultsUnchecked(
it.results.mapNotNull {
get(it)
}
)
}
}
}
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(

View File

@@ -1,9 +1,7 @@
package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.utils.paginate
import dev.inmo.micro_utils.pagination.utils.reverse
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.*
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import kotlinx.coroutines.CoroutineScope
@@ -15,18 +13,22 @@ open class ReadKeyValuesCacheRepo<Key,Value>(
protected open val kvCache: KVCache<Key, List<Value>>
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return kvCache.get(k) ?.paginate(
pagination.let { if (reversed) it.reverse(count(k)) else it }
) ?.let {
if (reversed) it.copy(results = it.results.reversed()) else it
} ?: parentRepo.get(k, pagination, reversed)
return getAll(k, reversed).paginate(
pagination
)
}
override suspend fun getAll(k: Key, reversed: Boolean): List<Value> {
return kvCache.get(k) ?.let {
if (reversed) it.reversed() else it
} ?: parentRepo.getAll(k, reversed)
} ?: parentRepo.getAll(k, reversed).also {
kvCache.set(k, it)
}
}
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: parentRepo.contains(k, v)
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: (parentRepo.contains(k, v).also {
if (it) {
kvCache.unset(k) // clear as invalid
}
})
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
}
@@ -39,9 +41,21 @@ open class KeyValuesCacheRepo<Key,Value>(
kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
kvCache.set(
k,
kvCache.get(k) ?.plus(v) ?: return@onEach
)
}.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { (k, v) ->
kvCache.set(
k,
kvCache.get(k) ?.minus(v) ?: return@onEach
)
}.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
kvCache.unset(it)
}.launchIn(scope)
}
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(

View File

@@ -25,6 +25,9 @@ open class SimpleKVCache<K, V>(
kvParent.unset(it)
}
}
do {
val removed = cacheQueue.remove(k)
} while (removed)
cacheQueue.addLast(k)
kvParent.set(k, v)
}

View File

@@ -17,7 +17,7 @@ fun <T : Any> Context.keyValueStore(
): KeyValueRepo<String, T> {
@Suppress("UNCHECKED_CAST")
return cache.getOrPut(name) {
KeyValueStore<T>(this, name, cacheValues)
KeyValueStore<T>(c = this, preferencesName = name, useCache = cacheValues)
} as KeyValueStore<T>
}
@@ -149,6 +149,14 @@ class KeyValueStore<T : Any> internal constructor (
_onValueRemovedFlow.emit(it)
}
}
companion object {
operator fun <T : Any> invoke(
context: Context,
name: String = "default",
cacheValues: Boolean = false
) = context.keyValueStore<T>(name, cacheValues)
}
}
inline fun <T : Any> SharedPreferencesKeyValueRepo(
@@ -156,3 +164,5 @@ inline fun <T : Any> SharedPreferencesKeyValueRepo(
name: String = "default",
cacheValues: Boolean = false
) = context.keyValueStore<T>(name, cacheValues)
typealias KeyValueSPRepo<T> = KeyValueStore<T>

View File

@@ -4,9 +4,9 @@ import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteCRUDRepo
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateStatement
import org.jetbrains.exposed.sql.statements.*
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.Objects
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 0,
@@ -27,10 +27,47 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement)
/**
* @param id Can be null only if [createAndInsertId] have returned null (it can be useful when you have
* autoincrement identifier)
* @param it Will be [UpdateStatement] when it is called from [update] method or [InsertStatement] from the [create]
* one. Anyway, it is main method where you should put the logic of table filling by [value] data
*
* @see createAndInsertId
*/
protected abstract fun update(id: IdType?, value: InputValueType, it: UpdateBuilder<Int>)
/**
* Override this method to interact with [it] ([InsertStatement]) and put there new id with [IdType].
*
* By default, have null value due to the fact that in the most cases users have [autoIncrement]ing id columns
*
* @return In case when id for the model has been created new [IdType] should be returned
*/
protected open fun createAndInsertId(value: InputValueType, it: InsertStatement<Number>): IdType? = null
protected open fun insert(value: InputValueType, it: InsertStatement<Number>) {
val id = createAndInsertId(value, it)
update(id, value, it as UpdateBuilder<Int>)
}
@Deprecated(
"Replace its \"it\" parameter type with \"UpdateBuilder<Int>\" to actualize method signature. Method with current signature will be removed soon and do not recommended to override anymore"
)
protected open fun update(id: IdType, value: InputValueType, it: UpdateStatement) = update(
id,
value,
it as UpdateBuilder<Int>
)
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
/**
* Use this method to do the something with [values]. You may change and output values in that list and return
* changed list of pairs
*/
protected open suspend fun onAfterCreate(
values: List<Pair<InputValueType, ObjectType>>
): List<ObjectType> = values.map { it.second }
private fun createWithoutNotification(value: InputValueType): ObjectType {
return transaction(database) {
insert { insert(value, it) }.asObject(value)
@@ -40,13 +77,18 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
onBeforeCreate(values)
return transaction(db = database) {
values.map { value -> createWithoutNotification(value) }
values.map { value -> value to createWithoutNotification(value) }
}.let {
onAfterCreate(it)
}.onEach {
_newObjectsFlow.emit(it)
}
}
protected open suspend fun onBeforeUpdate(value: List<UpdatedValuePair<IdType, InputValueType>>) {}
protected open suspend fun onAfterUpdate(
value: List<UpdatedValuePair<InputValueType, ObjectType>>
): List<ObjectType> = value.map { it.second }
private fun updateWithoutNotification(id: IdType, value: InputValueType): ObjectType? {
return transaction(db = database) {
update(
@@ -54,7 +96,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
selectById(this, id)
}
) {
update(id, value, it)
update(id, value, it as UpdateBuilder<Int>)
}
}.let {
if (it > 0) {
@@ -71,7 +113,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
onBeforeUpdate(listOf(id to value))
return updateWithoutNotification(id, value).also {
return updateWithoutNotification(id, value).let {
onAfterUpdate(listOf(value to (it ?: return@let emptyList())))
}.firstOrNull().also {
if (it != null) {
_updatedObjectsFlow.emit(it)
}
@@ -81,9 +125,11 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
onBeforeUpdate(values)
return (
transaction(db = database) {
values.map { (id, value) -> updateWithoutNotification(id, value) }
}.filterNotNull()
).onEach {
values.mapNotNull { (id, value) -> value to (updateWithoutNotification(id, value) ?: return@mapNotNull null) }
}
).let {
onAfterUpdate(it)
}.onEach {
_updatedObjectsFlow.emit(it)
}
}

View File

@@ -3,8 +3,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.repos.KeyValueRepo
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateStatement
import org.jetbrains.exposed.sql.statements.*
import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedKeyValueRepo<Key, Value>(
@@ -20,13 +19,27 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
protected abstract fun update(k: Key, v: Value, it: UpdateStatement)
protected abstract fun insert(k: Key, v: Value, it: InsertStatement<Number>)
protected abstract fun update(k: Key, v: Value, it: UpdateBuilder<Int>)
protected abstract fun insertKey(k: Key, v: Value, it: InsertStatement<Number>)
protected open fun insert(k: Key, v: Value, it: InsertStatement<Number>) {
insertKey(k, v, it)
update(k, v, it as UpdateBuilder<Int>)
}
@Deprecated(
"Replace its \"it\" parameter type with \"UpdateBuilder<Int>\" to actualize method signature. Method with current signature will be removed soon and do not recommended to override anymore"
)
protected open fun update(k: Key, v: Value, it: UpdateStatement) = update(
k,
v,
it as UpdateBuilder<Int>
)
override suspend fun set(toSet: Map<Key, Value>) {
transaction(database) {
toSet.mapNotNull { (k, v) ->
if (update({ selectById(k) }) { update(k, v, it) } > 0) {
if (update({ selectById(k) }) { update(k, v, it as UpdateBuilder<Int>) } > 0) {
k to v
} else {
val inserted = insert {

View File

@@ -3,50 +3,29 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.*
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
import org.jetbrains.exposed.sql.statements.UpdateBuilder
import org.jetbrains.exposed.sql.transactions.transaction
open class ExposedReadKeyValueRepo<Key, Value>(
override val database: Database,
database: Database,
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
val keyColumn: Column<Key> = keyColumnAllocator()
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValueRepo<Key, Value>(database, tableName) {
override val keyColumn: Column<Key> = keyColumnAllocator()
val valueColumn: Column<Value> = valueColumnAllocator()
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
override val ResultRow.asKey: Key
get() = get(keyColumn)
override val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
override val ResultRow.asObject: Value
get() = get(valueColumn)
override val selectById: SqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
override val primaryKey: Table.PrimaryKey
get() = PrimaryKey(keyColumn, valueColumn)
init { initTable() }
override suspend fun get(k: Key): Value? = transaction(database) {
select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn)
}
override suspend fun contains(key: Key): Boolean = transaction(database) {
select { keyColumn.eq(key) }.limit(1).any()
}
override suspend fun count(): Long = transaction(database) { selectAll().count() }
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
selectAll().paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
it[keyColumn]
}
}.createPaginationResult(pagination, count())
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
select { valueColumn.eq(v) }.let {
it.count() to it.paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
it[keyColumn]
}
}
}.let { (count, list) ->
list.createPaginationResult(pagination, count)
}
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
selectAll().paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
it[valueColumn]
}
}.createPaginationResult(pagination, count())
}

View File

@@ -1,10 +1,8 @@
package dev.inmo.micro_utils.repos.exposed.onetomany
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.exposed.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value>
@@ -13,54 +11,15 @@ open class ExposedReadKeyValuesRepo<Key, Value>(
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
val keyColumn: Column<Key> = keyColumnAllocator()
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValuesRepo<Key, Value>(database, tableName) {
override val keyColumn: Column<Key> = keyColumnAllocator()
override val ResultRow.asKey: Key
get() = get(keyColumn)
override val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
override val ResultRow.asObject: Value
get() = get(valueColumn)
override val selectById: SqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
val valueColumn: Column<Value> = valueColumnAllocator()
init { initTable() }
override suspend fun count(k: Key): Long = transaction(database) { select { keyColumn.eq(k) }.count() }
override suspend fun count(): Long = transaction(database) { selectAll().count() }
override suspend fun get(
k: Key,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Value> = transaction(database) {
select { keyColumn.eq(k) }.paginate(pagination, keyColumn, reversed).map { it[valueColumn] }
}.createPaginationResult(
pagination,
count(k)
)
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
selectAll().paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
}.createPaginationResult(
pagination,
count()
)
override suspend fun keys(
v: Value,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = transaction(database) {
select { valueColumn.eq(v) }.let {
it.count() to it.paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
}
}.let { (count, list) ->
list.createPaginationResult(
pagination,
count
)
}
override suspend fun contains(k: Key): Boolean = transaction(database) {
select { keyColumn.eq(k) }.limit(1).any()
}
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).any()
}
}

View File

@@ -1,83 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.server.crud
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.extractPagination
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.countRouting
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.http.ContentType
import io.ktor.server.application.call
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes(
originalRepo: ReadCRUDRepo<ObjectType, IdType>,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
idsSerializer: KSerializer<IdType>,
unifiedRouter: UnifiedRouter
) {
val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
get(getByPaginationRouting) {
unifiedRouter.apply {
val pagination = call.request.queryParameters.extractPagination
unianswer(
paginationResultSerializer,
originalRepo.getByPagination(pagination)
)
}
}
get(getByIdRouting) {
unifiedRouter.apply {
val id = decodeUrlQueryValueOrSendError(
"id",
idsSerializer
) ?: return@get
unianswer(
objectsNullableSerializer,
originalRepo.getById(id)
)
}
}
get(containsRouting) {
unifiedRouter.apply {
val id = decodeUrlQueryValueOrSendError(
"id",
idsSerializer
) ?: return@get
unianswer(
Boolean.serializer(),
originalRepo.contains(id)
)
}
}
get(countRouting) {
unifiedRouter.apply {
unianswer(
Long.serializer(),
originalRepo.count()
)
}
}
}
inline fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes(
originalRepo: ReadCRUDRepo<ObjectType, IdType>,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
idsSerializer: KSerializer<IdType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) = configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType))

View File

@@ -1,39 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.server.crud
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
import dev.inmo.micro_utils.repos.CRUDRepo
import io.ktor.http.ContentType
import io.ktor.server.routing.Route
import io.ktor.server.routing.route
import kotlinx.serialization.KSerializer
fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes(
baseSubpart: String,
originalRepo: CRUDRepo<ObjectType, IdType, InputValue>,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
inputsSerializer: KSerializer<InputValue>,
idsSerializer: KSerializer<IdType>,
unifiedRouter: UnifiedRouter
) {
route(baseSubpart) {
configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, unifiedRouter)
configureWriteCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, unifiedRouter)
}
}
fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes(
baseSubpart: String,
originalRepo: CRUDRepo<ObjectType, IdType, InputValue>,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
inputsSerializer: KSerializer<InputValue>,
idsSerializer: KSerializer<IdType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) = configureCRUDRepoRoutes(
baseSubpart, originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
)

View File

@@ -1,107 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.server.crud
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.repos.WriteCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.http.ContentType
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.*
fun <ObjectType, IdType, InputValue> Route.configureWriteCRUDRepoRoutes(
originalRepo: WriteCRUDRepo<ObjectType, IdType, InputValue>,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
inputsSerializer: KSerializer<InputValue>,
idsSerializer: KSerializer<IdType>,
unifiedRouter: UnifiedRouter
) {
val listObjectsSerializer = ListSerializer(objectsSerializer)
val listInputSerializer = ListSerializer(inputsSerializer)
val listIdsSerializer = ListSerializer(idsSerializer)
val inputUpdateSerializer = PairSerializer(
idsSerializer,
inputsSerializer
)
val listInputUpdateSerializer = ListSerializer(inputUpdateSerializer)
unifiedRouter.apply {
includeWebsocketHandling(
newObjectsFlowRouting,
originalRepo.newObjectsFlow,
objectsSerializer
)
includeWebsocketHandling(
updatedObjectsFlowRouting,
originalRepo.updatedObjectsFlow,
objectsSerializer
)
includeWebsocketHandling(
deletedObjectsIdsFlowRouting,
originalRepo.deletedObjectsIdsFlow,
idsSerializer
)
}
post(createRouting) {
unifiedRouter.apply {
unianswer(
listObjectsSerializer,
originalRepo.create(
uniload(listInputSerializer)
)
)
}
}
post(updateRouting) {
unifiedRouter.apply {
val (id, input) = uniload(inputUpdateSerializer)
unianswer(
objectsNullableSerializer,
originalRepo.update(
id, input
)
)
}
}
post(updateManyRouting) {
unifiedRouter.apply {
val updates = uniload(listInputUpdateSerializer)
unianswer(
listObjectsSerializer,
originalRepo.update(
updates
)
)
}
}
post(deleteByIdRouting) {
unifiedRouter.apply {
val ids = uniload(listIdsSerializer)
unianswer(
Unit.serializer(),
originalRepo.deleteById(
ids
)
)
}
}
}
fun <ObjectType, IdType, InputValue> Route.configureWriteCRUDRepoRoutes(
originalRepo: WriteCRUDRepo<ObjectType, IdType, InputValue>,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
inputsSerializer: KSerializer<InputValue>,
idsSerializer: KSerializer<IdType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) = configureWriteCRUDRepoRoutes(
originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
)

View File

@@ -1,46 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.server.key_value
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
import dev.inmo.micro_utils.repos.KeyValueRepo
import io.ktor.http.ContentType
import io.ktor.server.routing.Route
import io.ktor.server.routing.route
import kotlinx.serialization.KSerializer
fun <K, V> Route.configureKeyValueRepoRoutes(
baseSubpart: String,
originalRepo: KeyValueRepo<K, V>,
keySerializer: KSerializer<K>,
valueSerializer: KSerializer<V>,
valueNullableSerializer: KSerializer<V?>,
unifiedRouter: UnifiedRouter
) {
route(baseSubpart) {
configureReadStandartKeyValueRepoRoutes(
originalRepo,
keySerializer,
valueSerializer,
valueNullableSerializer,
unifiedRouter
)
configureWriteKeyValueRepoRoutes(
originalRepo,
keySerializer,
valueSerializer,
unifiedRouter
)
}
}
fun <K, V> Route.configureStandartKeyValueRepoRoutes(
baseSubpart: String,
originalRepo: KeyValueRepo<K, V>,
keySerializer: KSerializer<K>,
valueSerializer: KSerializer<V>,
valueNullableSerializer: KSerializer<V?>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) = configureKeyValueRepoRoutes(baseSubpart, originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType))

View File

@@ -1,107 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.server.key_value
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.extractPagination
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ktor.common.*
import dev.inmo.micro_utils.repos.ktor.common.containsRoute
import dev.inmo.micro_utils.repos.ktor.common.countRoute
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
import dev.inmo.micro_utils.repos.ktor.common.key_value.keyParameterName
import dev.inmo.micro_utils.repos.ktor.common.key_value.reversedParameterName
import io.ktor.http.ContentType
import io.ktor.server.application.call
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
fun <K, V> Route.configureReadStandartKeyValueRepoRoutes (
originalRepo: ReadKeyValueRepo<K, V>,
keySerializer: KSerializer<K>,
valueSerializer: KSerializer<V>,
valueNullableSerializer: KSerializer<V?>,
unifiedRouter: UnifiedRouter
) {
get(getRoute) {
unifiedRouter.apply {
val key = decodeUrlQueryValueOrSendError(
keyParameterName,
keySerializer
) ?: return@get
unianswer(
valueNullableSerializer,
originalRepo.get(key)
)
}
}
get(valuesRoute) {
unifiedRouter.apply {
val parination = call.request.queryParameters.extractPagination;
val reversed = decodeUrlQueryValueOrSendError(
reversedParameterName,
Boolean.serializer()
) ?: return@get
unianswer(
PaginationResult.serializer(valueSerializer),
originalRepo.values(parination, reversed)
)
}
}
get(keysRoute) {
unifiedRouter.apply {
val parination = call.request.queryParameters.extractPagination;
val reversed = decodeUrlQueryValueOrSendError(
reversedParameterName,
Boolean.serializer()
) ?: return@get
val value = decodeUrlQueryValue(valueParameterName, valueSerializer)
unianswer(
PaginationResult.serializer(keySerializer),
value?.let { originalRepo.keys(value, parination, reversed) } ?: originalRepo.keys(parination, reversed)
)
}
}
get(containsRoute) {
unifiedRouter.apply {
val key = decodeUrlQueryValueOrSendError(
keyParameterName,
keySerializer
) ?: return@get
unianswer(
Boolean.serializer(),
originalRepo.contains(key)
)
}
}
get(countRoute) {
unifiedRouter.apply {
unianswer(
Long.serializer(),
originalRepo.count()
)
}
}
}
inline fun <K, V> Route.configureReadStandartKeyValueRepoRoutes (
originalRepo: ReadKeyValueRepo<K, V>,
keySerializer: KSerializer<K>,
valueSerializer: KSerializer<V>,
valueNullableSerializer: KSerializer<V?>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) = configureReadStandartKeyValueRepoRoutes(
originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
)

View File

@@ -1,70 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.server.key_value
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
import io.ktor.http.ContentType
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.*
fun <K, V> Route.configureWriteKeyValueRepoRoutes (
originalRepo: WriteKeyValueRepo<K, V>,
keySerializer: KSerializer<K>,
valueSerializer: KSerializer<V>,
unifiedRouter: UnifiedRouter
) {
val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
val keysListSerializer = ListSerializer(keySerializer)
val valuesListSerializer = ListSerializer(valueSerializer)
unifiedRouter.apply {
includeWebsocketHandling(
onNewValueRoute,
originalRepo.onNewValue,
PairSerializer(keySerializer, valueSerializer)
)
includeWebsocketHandling(
onValueRemovedRoute,
originalRepo.onValueRemoved,
keySerializer
)
}
post(setRoute) {
unifiedRouter.apply {
val toSet = uniload(
keyValueMapSerializer
)
unianswer(Unit.serializer(), originalRepo.set(toSet))
}
}
post(unsetRoute) {
unifiedRouter.apply {
val toUnset = uniload(keysListSerializer)
unianswer(Unit.serializer(), originalRepo.unset(toUnset))
}
}
post(unsetWithValuesRoute) {
unifiedRouter.apply {
val toUnset = uniload(valuesListSerializer)
unianswer(Unit.serializer(), originalRepo.unsetWithValues(toUnset))
}
}
}
fun <K, V> Route.configureWriteStandartKeyValueRepoRoutes (
originalRepo: WriteKeyValueRepo<K, V>,
keySerializer: KSerializer<K>,
valueSerializer: KSerializer<V>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) = configureWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))

View File

@@ -1,35 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
import dev.inmo.micro_utils.repos.KeyValuesRepo
import io.ktor.http.ContentType
import io.ktor.server.routing.Route
import io.ktor.server.routing.route
import kotlinx.serialization.KSerializer
fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes(
baseSubpart: String,
originalRepo: KeyValuesRepo<Key, Value>,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
unifiedRouter: UnifiedRouter
) {
route(baseSubpart) {
configureOneToManyReadKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, unifiedRouter)
configureOneToManyWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, unifiedRouter)
}
}
fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes(
baseSubpart: String,
originalRepo: KeyValuesRepo<Key, Value>,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) = configureOneToManyKeyValueRepoRoutes(
baseSubpart, originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
)

View File

@@ -1,129 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.extractPagination
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
import io.ktor.http.ContentType
import io.ktor.server.application.call
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
fun <Key, Value> Route.configureOneToManyReadKeyValueRepoRoutes(
originalRepo: ReadKeyValuesRepo<Key, Value>,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
unifiedRouter: UnifiedRouter
) {
val paginationKeyResult = PaginationResult.serializer(keySerializer)
val paginationValueResult = PaginationResult.serializer(valueSerializer)
get(getRoute) {
unifiedRouter.apply {
val pagination = call.request.queryParameters.extractPagination
val key = decodeUrlQueryValueOrSendError(
keyParameterName,
keySerializer
) ?: return@get
val reversed = decodeUrlQueryValue(
reversedParameterName,
Boolean.serializer()
) ?: false
unianswer(
paginationValueResult,
originalRepo.get(key, pagination, reversed)
)
}
}
get(keysRoute) {
unifiedRouter.apply {
val pagination = call.request.queryParameters.extractPagination
val reversed = decodeUrlQueryValue(
reversedParameterName,
Boolean.serializer()
) ?: false
val value: Value? = decodeUrlQueryValue(
valueParameterName,
valueSerializer
)
unianswer(
paginationKeyResult,
value?.let { originalRepo.keys(value, pagination, reversed) } ?: originalRepo.keys(pagination, reversed)
)
}
}
get(containsByKeyRoute) {
unifiedRouter.apply {
val key = decodeUrlQueryValueOrSendError(
keyParameterName,
keySerializer
) ?: return@get
unianswer(
Boolean.serializer(),
originalRepo.contains(key)
)
}
}
get(containsByKeyValueRoute) {
unifiedRouter.apply {
val key = decodeUrlQueryValueOrSendError(
keyParameterName,
keySerializer
) ?: return@get
val value = decodeUrlQueryValueOrSendError(
valueParameterName,
valueSerializer
) ?: return@get
unianswer(
Boolean.serializer(),
originalRepo.contains(key, value)
)
}
}
get(countByKeyRoute) {
unifiedRouter.apply {
val key = decodeUrlQueryValueOrSendError(
keyParameterName,
keySerializer
) ?: return@get
unianswer(
Long.serializer(),
originalRepo.count(key)
)
}
}
get(countRoute) {
unifiedRouter.apply {
unianswer(
Long.serializer(),
originalRepo.count()
)
}
}
}
inline fun <Key, Value> Route.configureOneToManyReadKeyValueRepoRoutes(
originalRepo: ReadKeyValuesRepo<Key, Value>,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) = configureOneToManyReadKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))

View File

@@ -1,103 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
import io.ktor.http.ContentType
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.*
fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
originalRepo: WriteKeyValuesRepo<Key, Value>,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
unifiedRouter: UnifiedRouter
) {
val keyValueSerializer = PairSerializer(keySerializer, valueSerializer)
val keyValueMapSerializer = MapSerializer(keySerializer, ListSerializer(valueSerializer))
unifiedRouter.apply {
includeWebsocketHandling(
onNewValueRoute,
originalRepo.onNewValue,
keyValueSerializer
)
includeWebsocketHandling(
onValueRemovedRoute,
originalRepo.onValueRemoved,
keyValueSerializer
)
includeWebsocketHandling(
onDataClearedRoute,
originalRepo.onDataCleared,
keySerializer
)
}
post(addRoute) {
unifiedRouter.apply {
val obj = uniload(keyValueMapSerializer)
unianswer(
Unit.serializer(),
originalRepo.add(obj)
)
}
}
post(removeRoute) {
unifiedRouter.apply {
val obj = uniload(keyValueMapSerializer)
unianswer(
Unit.serializer(),
originalRepo.remove(obj),
)
}
}
post(clearRoute) {
unifiedRouter.apply {
val key = uniload(keySerializer)
unianswer(
Unit.serializer(),
originalRepo.clear(key),
)
}
}
post(clearWithValueRoute) {
unifiedRouter.apply {
val v = uniload(valueSerializer)
unianswer(
Unit.serializer(),
originalRepo.clearWithValue(v),
)
}
}
post(setRoute) {
unifiedRouter.apply {
val obj = uniload(keyValueMapSerializer)
unianswer(
Unit.serializer(),
originalRepo.set(obj)
)
}
}
}
fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
originalRepo: WriteKeyValuesRepo<Key, Value>,
keySerializer: KSerializer<Key>,
valueSerializer: KSerializer<Value>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) = configureOneToManyWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))

View File

@@ -5,6 +5,7 @@ String[] includes = [
":common:compose",
":matrix",
":crypto",
":koin",
":selector:common",
":pagination:common",
":pagination:exposed",