mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
433ba4b58f | |||
d40376e524 | |||
a2982f88f5 | |||
1642f7abd9 | |||
a10d2184ff | |||
522435f096 | |||
79b30290c0 | |||
f8b8626859 | |||
b061b85a08 | |||
3870db1c88 | |||
1be1070eb4 | |||
2696e663cf | |||
1e1f7df86d | |||
1d8ded8fd3 | |||
197825123a | |||
422b2e6db1 | |||
1973e0b5bf | |||
8258cf93a9 | |||
1d49bd5947 | |||
44317d1519 | |||
48e08fcc69 | |||
1a3ce6e623 | |||
fb122f3e70 | |||
7cca6039cc | |||
118e3dba39 | |||
87070710fa | |||
38499c3d4a | |||
5d754d968b | |||
d543d436bc | |||
12b54f99af | |||
2a95d7e643 | |||
e3d3cacfa4 | |||
4b13491a0e | |||
85d516d1e9 | |||
ac58b6a7e3 | |||
2cc6126765 | |||
f94b085850 | |||
c9822a491b | |||
23b2d60295 | |||
f4bc9eed39 | |||
e310f188b0 | |||
6c1571188c | |||
945d2fa284 | |||
020095f1ff | |||
b165a76e62 | |||
03c8830672 | |||
38448da89b | |||
2ade5aff91 | |||
29bf6e80ec | |||
027e927e1b | |||
fa061f88e2 | |||
1d01b65b5f | |||
2c2b364167 | |||
7c5fc9bf7c | |||
193d22ff20 | |||
0f3b553ba2 |
93
CHANGELOG.md
93
CHANGELOG.md
@@ -1,5 +1,98 @@
|
||||
# Changelog
|
||||
|
||||
## 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`:
|
||||
* `Exposed`:
|
||||
* Add abstract exposed variants of `KeyValue` and `KeyValues` repos
|
||||
* Add new extension `Query#selectPaginated`
|
||||
|
||||
## 0.12.2
|
||||
|
||||
* `Versions`:
|
||||
|
@@ -16,6 +16,7 @@ kotlin {
|
||||
androidMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.coroutines")
|
||||
api libs.android.fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.12.2
|
||||
android_code_version=141
|
||||
version=0.12.14
|
||||
android_code_version=153
|
||||
|
@@ -1,36 +1,40 @@
|
||||
[versions]
|
||||
|
||||
kt = "1.7.0"
|
||||
kt = "1.7.10"
|
||||
kt-serialization = "1.4.0"
|
||||
kt-coroutines = "1.6.4"
|
||||
|
||||
jb-compose = "1.2.0-alpha01-dev755"
|
||||
jb-compose = "1.2.0-alpha01-dev774"
|
||||
jb-exposed = "0.39.2"
|
||||
jb-dokka = "1.7.0"
|
||||
jb-dokka = "1.7.10"
|
||||
|
||||
klock = "3.0.0"
|
||||
klock = "3.1.0"
|
||||
uuid = "0.5.0"
|
||||
|
||||
ktor = "2.1.0"
|
||||
ktor = "2.1.1"
|
||||
|
||||
gh-release = "2.4.1"
|
||||
|
||||
koin = "3.2.0"
|
||||
|
||||
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" }
|
||||
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.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
28
koin/build.gradle
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
Normal file
12
koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
Normal 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)
|
12
koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
Normal file
12
koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
Normal 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)
|
||||
|
||||
|
8
koin/src/commonMain/kotlin/GetAllDistinct.kt
Normal file
8
koin/src/commonMain/kotlin/GetAllDistinct.kt
Normal 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()
|
||||
|
8
koin/src/commonMain/kotlin/GetAny.kt
Normal file
8
koin/src/commonMain/kotlin/GetAny.kt
Normal 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()
|
||||
|
6
koin/src/commonMain/kotlin/RandomQualifier.kt
Normal file
6
koin/src/commonMain/kotlin/RandomQualifier.kt
Normal 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())
|
13
koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
Normal file
13
koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
Normal 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)
|
12
koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
Normal file
12
koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
Normal 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)
|
||||
|
27
koin/src/jvmMain/kotlin/FactoryWithBinds.kt
Normal file
27
koin/src/jvmMain/kotlin/FactoryWithBinds.kt
Normal 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())
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
30
koin/src/jvmMain/kotlin/SignleWithBinds.kt
Normal file
30
koin/src/jvmMain/kotlin/SignleWithBinds.kt
Normal 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())
|
||||
}
|
||||
|
14
koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
Normal file
14
koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
Normal 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)
|
||||
}
|
1
koin/src/main/AndroidManifest.xml
Normal file
1
koin/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.koin"/>
|
@@ -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>()
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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,
|
||||
|
@@ -48,6 +48,14 @@ data class PaginationResult<T>(
|
||||
}
|
||||
|
||||
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
|
||||
|
@@ -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(
|
||||
|
@@ -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)
|
||||
|
@@ -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(
|
||||
|
@@ -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(
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -26,7 +26,6 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow()
|
||||
|
||||
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
|
||||
abstract val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
||||
|
||||
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
|
||||
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement)
|
||||
|
@@ -0,0 +1,20 @@
|
||||
package dev.inmo.micro_utils.repos.exposed
|
||||
|
||||
import org.jetbrains.exposed.sql.*
|
||||
|
||||
interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo {
|
||||
val ResultRow.asObject: ObjectType
|
||||
val selectById: SqlExpressionBuilder.(IdType) -> Op<Boolean>
|
||||
val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
||||
get() = { list ->
|
||||
if (list.isEmpty()) {
|
||||
Op.FALSE
|
||||
} else {
|
||||
var op = selectById(list.first())
|
||||
(1 until list.size).forEach {
|
||||
op = op.or(selectById(list[it]))
|
||||
}
|
||||
op
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,4 @@ package dev.inmo.micro_utils.repos.exposed
|
||||
|
||||
import org.jetbrains.exposed.sql.*
|
||||
|
||||
interface ExposedCRUDRepo<ObjectType, IdType> : ExposedRepo {
|
||||
val ResultRow.asObject: ObjectType
|
||||
val selectById: SqlExpressionBuilder.(IdType) -> Op<Boolean>
|
||||
}
|
||||
interface ExposedCRUDRepo<ObjectType, IdType> : CommonExposedRepo<IdType, ObjectType>
|
||||
|
@@ -0,0 +1,72 @@
|
||||
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.transactions.transaction
|
||||
|
||||
abstract class AbstractExposedKeyValueRepo<Key, Value>(
|
||||
override val database: Database,
|
||||
tableName: String? = null
|
||||
) : KeyValueRepo<Key, Value>, AbstractExposedReadKeyValueRepo<Key, Value>(
|
||||
database,
|
||||
tableName
|
||||
) {
|
||||
protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
|
||||
protected val _onValueRemoved = MutableSharedFlow<Key>()
|
||||
|
||||
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>)
|
||||
|
||||
override suspend fun set(toSet: Map<Key, Value>) {
|
||||
transaction(database) {
|
||||
toSet.mapNotNull { (k, v) ->
|
||||
if (update({ selectById(k) }) { update(k, v, it) } > 0) {
|
||||
k to v
|
||||
} else {
|
||||
val inserted = insert {
|
||||
insert(k, v, it)
|
||||
}.getOrNull(keyColumn) != null
|
||||
if (inserted) {
|
||||
k to v
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}.forEach {
|
||||
_onNewValue.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unset(toUnset: List<Key>) {
|
||||
transaction(database) {
|
||||
toUnset.mapNotNull {
|
||||
if (deleteWhere { selectById(it) } > 0) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.forEach {
|
||||
_onValueRemoved.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||
transaction(database) {
|
||||
toUnset.flatMap {
|
||||
val keys = select { selectByValue(it) }.mapNotNull { it.asKey }
|
||||
deleteWhere { selectByIds(keys) }
|
||||
keys
|
||||
}
|
||||
}.distinct().forEach {
|
||||
_onValueRemoved.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
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.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
|
||||
override val database: Database,
|
||||
tableName: String? = null
|
||||
) : ReadKeyValueRepo<Key, Value>,
|
||||
CommonExposedRepo<Key, Value>,
|
||||
Table(tableName ?: "") {
|
||||
abstract val keyColumn: Column<*>
|
||||
abstract val ResultRow.asKey: Key
|
||||
abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean>
|
||||
|
||||
override suspend fun get(k: Key): Value? = transaction(database) {
|
||||
select { selectById(k) }.limit(1).firstOrNull() ?.asObject
|
||||
}
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = transaction(database) {
|
||||
select { selectById(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().selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it.asKey
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||
select { selectByValue(v) }.selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it.asKey
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
|
||||
selectAll().selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it.asObject
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ 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.transactions.transaction
|
||||
|
||||
@@ -29,24 +30,32 @@ open class ExposedReadKeyValueRepo<Key, Value>(
|
||||
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 {
|
||||
selectAll().selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
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]
|
||||
}
|
||||
select { valueColumn.eq(v) }.selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
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 {
|
||||
selectAll().selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it[valueColumn]
|
||||
}
|
||||
}.createPaginationResult(pagination, count())
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,69 @@
|
||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||
|
||||
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
abstract class AbstractExposedKeyValuesRepo<Key, Value>(
|
||||
override val database: Database,
|
||||
tableName: String? = null
|
||||
) : KeyValuesRepo<Key, Value>, AbstractExposedReadKeyValuesRepo<Key, Value>(
|
||||
database,
|
||||
tableName
|
||||
) {
|
||||
protected val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
override val onNewValue: Flow<Pair<Key, Value>>
|
||||
get() = _onNewValue.asSharedFlow()
|
||||
protected val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
override val onValueRemoved: Flow<Pair<Key, Value>>
|
||||
get() = _onValueRemoved.asSharedFlow()
|
||||
protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow()
|
||||
override val onDataCleared: Flow<Key>
|
||||
get() = _onDataCleared.asSharedFlow()
|
||||
|
||||
protected abstract fun insert(k: Key, v: Value, it: InsertStatement<Number>)
|
||||
|
||||
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||
transaction(database) {
|
||||
toAdd.keys.flatMap { k ->
|
||||
toAdd[k] ?.mapNotNull { v ->
|
||||
if (select { selectById(k).and(selectByValue(v)) }.limit(1).any()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
val insertResult = insert {
|
||||
insert(k, v, it)
|
||||
}
|
||||
if (insertResult.insertedCount > 0) {
|
||||
k to v
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
}.forEach { _onNewValue.emit(it) }
|
||||
}
|
||||
|
||||
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||
transaction(database) {
|
||||
toRemove.keys.flatMap { k ->
|
||||
toRemove[k] ?.mapNotNull { v ->
|
||||
if (deleteWhere { selectById(k).and(selectByValue(v)) } > 0 ) {
|
||||
k to v
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
}.forEach {
|
||||
_onValueRemoved.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear(k: Key) {
|
||||
transaction(database) {
|
||||
deleteWhere { selectById(k) }
|
||||
}.also { _onDataCleared.emit(k) }
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
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.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
|
||||
override val database: Database,
|
||||
tableName: String? = null
|
||||
) : ReadKeyValuesRepo<Key, Value>,
|
||||
CommonExposedRepo<Key, Value>,
|
||||
Table(tableName ?: "") {
|
||||
abstract val keyColumn: Column<*>
|
||||
abstract val ResultRow.asKey: Key
|
||||
abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean>
|
||||
|
||||
override suspend fun count(k: Key): Long = transaction(database) { select { selectById(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 { selectById(k) }.selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it.asObject
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = transaction(database) {
|
||||
selectAll().selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it.asKey
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
v: Value,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = transaction(database) {
|
||||
select { selectByValue(v) }.selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it.asKey
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = transaction(database) {
|
||||
select { selectById(k) }.limit(1).any()
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
|
||||
select { selectById(k).and(selectByValue(v)) }.limit(1).any()
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package dev.inmo.micro_utils.repos.exposed.utils
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
|
||||
fun <T> Query.selectPaginated(
|
||||
pagination: Pagination,
|
||||
orderBy: Pair<Expression<*>, SortOrder>? = null,
|
||||
createResult: (ResultRow) -> T
|
||||
): PaginationResult<T> {
|
||||
val count = count()
|
||||
val list = paginate(pagination, orderBy).map(createResult)
|
||||
return list.createPaginationResult(pagination, count)
|
||||
}
|
||||
|
||||
fun <T> Query.selectPaginated(
|
||||
pagination: Pagination,
|
||||
orderBy: Expression<*>?,
|
||||
reversed: Boolean = false,
|
||||
createResult: (ResultRow) -> T
|
||||
) = selectPaginated(pagination, orderBy ?.let { it to if (reversed) SortOrder.DESC else SortOrder.ASC }, createResult)
|
@@ -5,6 +5,7 @@ String[] includes = [
|
||||
":common:compose",
|
||||
":matrix",
|
||||
":crypto",
|
||||
":koin",
|
||||
":selector:common",
|
||||
":pagination:common",
|
||||
":pagination:exposed",
|
||||
|
Reference in New Issue
Block a user