mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 22:39:25 +00:00
Compare commits
156 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
4eb1013446 | |||
be1b2563ec | |||
1c9c2f1e70 | |||
52dfded741 | |||
0a115e5cf4 | |||
c349af999b | |||
9a5709a34d | |||
28d311160b | |||
1ca7081a40 | |||
5ce8ebe82c | |||
a7a88b29b9 | |||
cd7b982385 | |||
ee59100075 | |||
f808ac58ef | |||
f33ada5396 | |||
984d781f2f | |||
dd33e1e8bc | |||
2950de29e5 | |||
50a8799f9d | |||
bdb0ce6fc7 | |||
18ec2bca96 | |||
937ef48794 | |||
6331f13e9a | |||
5213a2ff8e | |||
087d7452fd | |||
703094c924 | |||
eea645c865 | |||
324832a189 | |||
d55d735c51 | |||
e3ff1b9609 | |||
70c31966ca | |||
0e4188882f | |||
6bf0ce92ba | |||
d51bdc5086 | |||
f04f262cee | |||
0f172055ef | |||
e5dd4363f1 | |||
a3a48bbaac | |||
5e716fb9a8 | |||
11a36153cc | |||
8bee354f04 | |||
f7dd2b5ce7 | |||
8ca10c00bb | |||
905c7e8eda | |||
d4c5e849bf | |||
8250a2a021 | |||
01b3df7b8c | |||
daa6e4aff5 | |||
23bcb26a58 | |||
e55f60c30b | |||
0d0c16e16d | |||
540d5cce7c | |||
a548b00979 | |||
4b7ca6d565 | |||
0473fa238c | |||
cfc7119697 | |||
22a6520d3e | |||
fb25e91191 | |||
c116b270b6 | |||
aa2d598689 | |||
5ef3bb746b | |||
037616e271 | |||
abbea906f1 | |||
9132e216c9 | |||
12a7e3c4af | |||
b40c093917 | |||
7ac12455c8 | |||
5043eec7a2 | |||
cf31f53e01 | |||
cd22d76fa7 | |||
c8759843f7 | |||
781bbcc012 | |||
6da29c0686 | |||
fcdb6fc45a | |||
e785a99bd7 | |||
121e513fdd | |||
9cf01ab54f | |||
c655107681 | |||
91a5af6a9a | |||
86e74c0a6f | |||
d8f01f21a0 | |||
9fb8626d8c | |||
1c52e04cdb | |||
798128256e | |||
72c2df47fd | |||
c9c6d4c0c1 | |||
2f4f9f3003 | |||
22e8f8e5d6 | |||
04cf8c3d9a | |||
52198be543 | |||
80fd5a489b | |||
b187043ee1 | |||
8965752055 | |||
b796620267 | |||
62df81bb4e | |||
0a8e0f6178 | |||
f705020aaa | |||
78bd3b9853 | |||
992b283597 | |||
8fc1ff1d59 | |||
3bbde61f39 | |||
8d955c4b9d | |||
18593c530b | |||
78903cd4eb | |||
eaa143f7d7 | |||
bcb0e42fa2 | |||
8eed435302 | |||
0e4a63057f | |||
1af5faa440 |
@@ -1,8 +1,8 @@
|
||||
|
||||
name: Publish package to GitHub Packages
|
||||
name: Build
|
||||
on: [push]
|
||||
jobs:
|
||||
publishing:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -20,9 +20,9 @@ jobs:
|
||||
mv gradle.properties.tmp gradle.properties
|
||||
- name: Build
|
||||
run: ./gradlew build
|
||||
- name: Publish
|
||||
continue-on-error: true
|
||||
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
|
||||
env:
|
||||
GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
# - name: Publish
|
||||
# continue-on-error: true
|
||||
# run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
|
||||
# env:
|
||||
# GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||
# GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
2
.github/workflows/dokka_push.yml
vendored
2
.github/workflows/dokka_push.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
|
||||
- name: Build
|
||||
run: ./gradlew dokkaHtml
|
||||
run: ./gradlew build && ./gradlew dokkaHtml
|
||||
- name: Publish KDocs
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
|
221
CHANGELOG.md
221
CHANGELOG.md
@@ -1,5 +1,226 @@
|
||||
# Changelog
|
||||
|
||||
## 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`:
|
||||
* `Serialization`: `1.4.0-RC` -> `1.4.0`
|
||||
* `Compose`: `1.2.0-alpha01-dev753` -> `1.2.0-alpha01-dev755`
|
||||
|
||||
## 0.12.1
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `2.0.3` -> `2.1.0`
|
||||
|
||||
## 0.12.0
|
||||
|
||||
**OLD DEPRECATIONS HAVE BEEN REMOVED**
|
||||
|
||||
**MINIMAL ANDROID API HAS BEEN ENLARGED UP TO API 21 (Android 5.0)**
|
||||
|
||||
* `Versions`
|
||||
* `Kotlin`: `1.6.21` -> `1.7.0`
|
||||
* `Coroutines`: `1.6.3` -> `1.6.4`
|
||||
* `Exposed`: `0.38.2` -> `0.39.2`
|
||||
* `Compose`: `1.2.0-alpha01-dev729` -> `1.2.0-alpha01-dev753`
|
||||
* `Klock`: `2.7.0` -> `3.0.0`
|
||||
* `uuid`: `0.4.1` -> `0.5.0`
|
||||
* `Android Core KTX`: `1.7.0` -> `1.8.0`
|
||||
* `Android AppCompat`: `1.4.1` -> `1.4.2`
|
||||
* `Ktor`:
|
||||
* All previously standard functions related to work with binary data by default have been deprecated
|
||||
|
||||
## 0.11.14
|
||||
|
||||
* `Pagination`:
|
||||
* `PaginationResult` got new field `objectsNumber` which by default is a times between `pagesNumber` and `size`
|
||||
|
||||
## 0.11.13
|
||||
|
||||
* `Versions`:
|
||||
* `Coroutines`: `1.6.3` -> `1.6.4`
|
||||
* `Compose`: `1.2.0-alpha01-dev629` -> `1.2.0-alpha01-dev731`
|
||||
|
||||
## 0.11.12
|
||||
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* `JVM`:
|
||||
* Fixes in `ReadFileKeyValueRepo` methods (`values`/`keys`)
|
||||
|
||||
## 0.11.11
|
||||
|
||||
* `Crypto`:
|
||||
* `hmacSha256` has been deprecated
|
||||
* `Ktor`:
|
||||
* `Client`:
|
||||
* `BodyPair` has been deprecated
|
||||
* `Repos`:
|
||||
* `Cache`:
|
||||
* New interface `CacheRepo`
|
||||
* New interface `FullCacheRepo`
|
||||
* `actualize*` methods inside of full cache repos now open for overriding
|
||||
|
||||
## 0.11.10
|
||||
|
||||
* `Repos`:
|
||||
* `Cache`:
|
||||
* `KVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache`
|
||||
* `SimpleKVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache`
|
||||
* New `KVCache` subtype - `FullKVCache`
|
||||
* Add `Full*` variants of standard repos
|
||||
* Add `cached`/`caching` (for write repos) extensions for all standard types of repos
|
||||
|
||||
## 0.11.9
|
||||
|
||||
* `Versions`
|
||||
* `Coroutines`: `1.6.1` -> `1.6.3`
|
||||
* `Ktor`: `2.0.2` -> `2.0.3`
|
||||
* `Compose`: `1.2.0-alpha01-dev686` -> `1.2.0-alpha01-dev729`
|
||||
|
||||
## 0.11.8
|
||||
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* Fixes in `FileKeyValueRepo`
|
||||
|
||||
## 0.11.7
|
||||
|
||||
* `Common`:
|
||||
* New abstractions `SimpleMapper` and `SimpleSuspendableMapper`
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* Add mappers for `CRUDRepo`
|
||||
|
||||
## 0.11.6
|
||||
|
||||
* `FSM`:
|
||||
* `Common`
|
||||
* Several fixes related to the jobs handling
|
||||
|
||||
## 0.11.5
|
||||
|
||||
* `Coroutines`:
|
||||
* `Compose`:
|
||||
* Add extension `StateFlow#asMutableComposeListState` and `StateFlow#asComposeList`
|
||||
* Add extension `StateFlow#asMutableComposeState`/`StateFlow#asComposeState`
|
||||
|
||||
## 0.11.4
|
||||
|
||||
**THIS VERSION HAS BEEN BROKEN, DO NOT USE IT**
|
||||
|
||||
## 0.11.3
|
||||
|
||||
* `Ktor`:
|
||||
* Support of `WebSockets` has been improved
|
||||
* `Client`:
|
||||
* New extensions: `HttpClient#openBaseWebSocketFlow`, `HttpClient#openWebSocketFlow`, `HttpClient#openSecureWebSocketFlow`
|
||||
|
||||
## 0.11.2
|
||||
|
||||
* `Ktor`:
|
||||
* Support of `WebSockets` has been improved and added fixes inside of clients
|
||||
|
||||
## 0.11.1
|
||||
|
||||
* `Repos`
|
||||
* `Ktor`
|
||||
* In `configureReadKeyValueRepoRoutes` and `configureReadKeyValuesRepoRoutes` configurators fixed requiring of `reversed` property
|
||||
|
||||
## 0.11.0
|
||||
|
||||
* `Versions`
|
||||
* `UUID`: `0.4.0` -> `0.4.1`
|
||||
* `Ktor`
|
||||
* `Client`:
|
||||
* New extension fun `HttpResponse#throwOnUnsuccess`
|
||||
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
|
||||
* `Server`:
|
||||
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
|
||||
* `Repos`
|
||||
* `Ktor`:
|
||||
* Fully rewritten work with all declared repositories
|
||||
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
|
||||
|
||||
## 0.10.8
|
||||
|
||||
* `Common`
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
@file:Suppress("unused", "NOTHING_TO_INLINE")
|
||||
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.serialization.*
|
||||
@@ -21,11 +23,10 @@ import kotlinx.serialization.encoding.*
|
||||
sealed interface Either<T1, T2> {
|
||||
val optionalT1: Optional<T1>
|
||||
val optionalT2: Optional<T2>
|
||||
@Deprecated("Use optionalT1 instead", ReplaceWith("optionalT1"))
|
||||
val t1: T1?
|
||||
|
||||
val t1OrNull: T1?
|
||||
get() = optionalT1.dataOrNull()
|
||||
@Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
|
||||
val t2: T2?
|
||||
val t2OrNull: T2?
|
||||
get() = optionalT2.dataOrNull()
|
||||
}
|
||||
|
||||
@@ -33,7 +34,7 @@ class EitherSerializer<T1, T2>(
|
||||
t1Serializer: KSerializer<T1>,
|
||||
t2Serializer: KSerializer<T2>,
|
||||
) : KSerializer<Either<T1, T2>> {
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
|
||||
override val descriptor: SerialDescriptor = buildSerialDescriptor(
|
||||
"TypedSerializer",
|
||||
SerialKind.CONTEXTUAL
|
||||
@@ -96,7 +97,7 @@ class EitherSerializer<T1, T2>(
|
||||
*/
|
||||
@Serializable
|
||||
data class EitherFirst<T1, T2>(
|
||||
override val t1: T1
|
||||
val t1: T1
|
||||
) : Either<T1, T2> {
|
||||
override val optionalT1: Optional<T1> = t1.optional
|
||||
override val optionalT2: Optional<T2> = Optional.absent()
|
||||
@@ -107,7 +108,7 @@ data class EitherFirst<T1, T2>(
|
||||
*/
|
||||
@Serializable
|
||||
data class EitherSecond<T1, T2>(
|
||||
override val t2: T2
|
||||
val t2: T2
|
||||
) : Either<T1, T2> {
|
||||
override val optionalT1: Optional<T1> = Optional.absent()
|
||||
override val optionalT2: Optional<T2> = t2.optional
|
||||
|
@@ -0,0 +1,53 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
interface SimpleMapper<T1, T2> {
|
||||
fun convertToT1(from: T2): T1
|
||||
fun convertToT2(from: T1): T2
|
||||
}
|
||||
|
||||
@JvmName("convertFromT2")
|
||||
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T2) = convertToT1(from)
|
||||
@JvmName("convertFromT1")
|
||||
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T1) = convertToT2(from)
|
||||
|
||||
class SimpleMapperImpl<T1, T2>(
|
||||
private val t1: (T2) -> T1,
|
||||
private val t2: (T1) -> T2,
|
||||
) : SimpleMapper<T1, T2> {
|
||||
override fun convertToT1(from: T2): T1 = t1.invoke(from)
|
||||
|
||||
override fun convertToT2(from: T1): T2 = t2.invoke(from)
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T1, T2> simpleMapper(
|
||||
noinline t1: (T2) -> T1,
|
||||
noinline t2: (T1) -> T2,
|
||||
) = SimpleMapperImpl(t1, t2)
|
||||
|
||||
interface SimpleSuspendableMapper<T1, T2> {
|
||||
suspend fun convertToT1(from: T2): T1
|
||||
suspend fun convertToT2(from: T1): T2
|
||||
}
|
||||
|
||||
@JvmName("convertFromT2")
|
||||
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T2) = convertToT1(from)
|
||||
@JvmName("convertFromT1")
|
||||
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T1) = convertToT2(from)
|
||||
|
||||
class SimpleSuspendableMapperImpl<T1, T2>(
|
||||
private val t1: suspend (T2) -> T1,
|
||||
private val t2: suspend (T1) -> T2,
|
||||
) : SimpleSuspendableMapper<T1, T2> {
|
||||
override suspend fun convertToT1(from: T2): T1 = t1.invoke(from)
|
||||
|
||||
override suspend fun convertToT2(from: T1): T2 = t2.invoke(from)
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T1, T2> simpleSuspendableMapper(
|
||||
noinline t1: suspend (T2) -> T1,
|
||||
noinline t2: suspend (T1) -> T2,
|
||||
) = SimpleSuspendableMapperImpl(t1, t2)
|
@@ -41,10 +41,18 @@ data class Optional<T> internal constructor(
|
||||
inline val <T> T.optional
|
||||
get() = Optional.presented(this)
|
||||
|
||||
inline val <T : Any> T?.optionalOrAbsentIfNull
|
||||
get() = if (this == null) {
|
||||
Optional.absent<T>()
|
||||
} else {
|
||||
Optional.presented(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||
*/
|
||||
inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
|
||||
@OptIn(Warning::class)
|
||||
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
|
||||
}
|
||||
|
||||
@@ -52,6 +60,7 @@ inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply
|
||||
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||
*/
|
||||
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
|
||||
@OptIn(Warning::class)
|
||||
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
|
||||
}
|
||||
|
||||
@@ -59,6 +68,7 @@ inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
|
||||
* Will call [block] when data absent ([Optional.dataPresented] == false)
|
||||
*/
|
||||
inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
|
||||
@OptIn(Warning::class)
|
||||
if (!dataPresented) { block() }
|
||||
}
|
||||
|
||||
@@ -66,27 +76,22 @@ inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
|
||||
* Will call [block] when data presented ([Optional.dataPresented] == true)
|
||||
*/
|
||||
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
|
||||
if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
|
||||
@OptIn(Warning::class)
|
||||
if (!dataPresented) { block() } else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
|
||||
*/
|
||||
fun <T> Optional<T>.dataOrNull() = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
|
||||
fun <T> Optional<T>.dataOrNull() = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
|
||||
|
||||
/**
|
||||
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise
|
||||
*/
|
||||
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
|
||||
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
|
||||
|
||||
|
||||
/**
|
||||
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
||||
*/
|
||||
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
||||
|
||||
/**
|
||||
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
|
||||
*/
|
||||
@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
|
||||
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
||||
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
|
||||
|
@@ -0,0 +1,26 @@
|
||||
package dev.inmo.micro_utils.coroutines.compose
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import dev.inmo.micro_utils.common.applyDiff
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
|
||||
scope: CoroutineScope
|
||||
): SnapshotStateList<T> {
|
||||
val state = mutableStateListOf<T>()
|
||||
subscribeSafelyWithoutExceptions(scope) {
|
||||
state.applyDiff(it)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified T> Flow<List<T>>.asComposeList(
|
||||
scope: CoroutineScope
|
||||
): List<T> = asMutableComposeListState(scope)
|
||||
|
@@ -0,0 +1,35 @@
|
||||
package dev.inmo.micro_utils.coroutines.compose
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
fun <T> Flow<T>.asMutableComposeState(
|
||||
initial: T,
|
||||
scope: CoroutineScope
|
||||
): MutableState<T> {
|
||||
val state = mutableStateOf(initial)
|
||||
subscribeSafelyWithoutExceptions(scope) { state.value = it }
|
||||
return state
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> StateFlow<T>.asMutableComposeState(
|
||||
scope: CoroutineScope
|
||||
): MutableState<T> = asMutableComposeState(value, scope)
|
||||
|
||||
fun <T> Flow<T>.asComposeState(
|
||||
initial: T,
|
||||
scope: CoroutineScope
|
||||
): State<T> {
|
||||
val state = asMutableComposeState(initial, scope)
|
||||
return derivedStateOf { state.value }
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> StateFlow<T>.asComposeState(
|
||||
scope: CoroutineScope
|
||||
): State<T> = asComposeState(value, scope)
|
||||
|
@@ -1,28 +1,27 @@
|
||||
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,
|
||||
markerFactory: suspend (T) -> Any? = { null },
|
||||
block: suspend (T) -> Unit
|
||||
): Channel<T> {
|
||||
val channel = Channel<T>(channelCapacity)
|
||||
launch {
|
||||
for (data in channel) {
|
||||
block(data)
|
||||
}
|
||||
}
|
||||
channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
|
||||
return channel
|
||||
}
|
||||
|
||||
inline fun <T> CoroutineScope.safeActor(
|
||||
channelCapacity: Int = Channel.UNLIMITED,
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
noinline markerFactory: suspend (T) -> Any? = { null },
|
||||
crossinline block: suspend (T) -> Unit
|
||||
): Channel<T> = actor(
|
||||
channelCapacity
|
||||
channelCapacity,
|
||||
markerFactory
|
||||
) {
|
||||
safely(onException) {
|
||||
block(it)
|
||||
|
@@ -1,3 +0,0 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
expect fun SourceString.hmacSha256(key: String): String
|
@@ -1,12 +0,0 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
import kotlin.test.*
|
||||
|
||||
class HmacSHA256 {
|
||||
@Test
|
||||
fun testSimpleHmacSHA256Message() {
|
||||
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
val resultSha = text.hmacSha256("Example")
|
||||
assertEquals("5a481d59329ef862b158eedc95392ebb22492ba3014661a3379d8201db992484", resultSha)
|
||||
}
|
||||
}
|
@@ -7,7 +7,3 @@ external interface CryptoJs {
|
||||
@JsModule("crypto-js")
|
||||
@JsNonModule
|
||||
external val CryptoJS: CryptoJs
|
||||
|
||||
actual fun SourceString.hmacSha256(key: String): String {
|
||||
return CryptoJS.asDynamic().HmacSHA256(this, key).toString().unsafeCast<String>()
|
||||
}
|
||||
|
@@ -1,13 +0,0 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
actual fun SourceString.hmacSha256(key: String): String {
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
|
||||
val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
|
||||
mac.init(secretKey)
|
||||
|
||||
return mac.doFinal(toByteArray()).hex()
|
||||
}
|
@@ -47,24 +47,11 @@ fun <I : O, O : State> CheckableHandlerHolder(
|
||||
}
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
|
||||
fun <I : O, O : State> StateHandlerHolder(
|
||||
inputKlass: KClass<I>,
|
||||
strict: Boolean = false,
|
||||
delegateTo: StatesHandler<I, O>
|
||||
) = CheckableHandlerHolder(inputKlass, strict, delegateTo)
|
||||
|
||||
inline fun <reified I : O, O : State> CheckableHandlerHolder(
|
||||
strict: Boolean = false,
|
||||
delegateTo: StatesHandler<I, O>
|
||||
) = CheckableHandlerHolder(I::class, strict, delegateTo)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
|
||||
inline fun <reified I : O, O : State> StateHandlerHolder(
|
||||
strict: Boolean = false,
|
||||
delegateTo: StatesHandler<I, O>
|
||||
) = CheckableHandlerHolder(strict, delegateTo)
|
||||
|
||||
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
|
||||
strict: Boolean = true
|
||||
) = CheckableHandlerHolder<I, O>(
|
||||
|
@@ -6,6 +6,7 @@ import dev.inmo.micro_utils.coroutines.*
|
||||
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
|
||||
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
@@ -85,10 +86,10 @@ open class DefaultStatesMachine <T: State>(
|
||||
|
||||
protected open suspend fun performUpdate(state: T) {
|
||||
val newState = launchStateHandling(state, handlers)
|
||||
if (newState != null) {
|
||||
statesManager.update(state, newState)
|
||||
} else {
|
||||
if (newState == null) {
|
||||
statesManager.endChain(state)
|
||||
} else {
|
||||
statesManager.update(state, newState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +119,7 @@ open class DefaultStatesMachine <T: State>(
|
||||
* [StatesManager.endChain].
|
||||
*/
|
||||
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
|
||||
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) {
|
||||
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||
}
|
||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||
@@ -134,10 +135,6 @@ open class DefaultStatesMachine <T: State>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statesManager.getActiveStates().forEach {
|
||||
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -52,6 +52,7 @@ open class DefaultUpdatableStatesMachine<T : State>(
|
||||
statesJobs.remove(
|
||||
jobsStates[job] ?: return@withLock
|
||||
)
|
||||
jobsStates.remove(job)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,9 +68,6 @@ open class DefaultUpdatableStatesMachine<T : State>(
|
||||
*/
|
||||
protected open suspend fun shouldReplaceJob(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
|
||||
|
||||
@Deprecated("Overwrite shouldReplaceJob instead")
|
||||
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = shouldReplaceJob(previous, new)
|
||||
|
||||
override suspend fun updateChain(currentState: T, newState: T) {
|
||||
statesManager.update(currentState, newState)
|
||||
}
|
||||
|
@@ -1,17 +0,0 @@
|
||||
package dev.inmo.micro_utils.fsm.common.managers
|
||||
|
||||
import dev.inmo.micro_utils.fsm.common.State
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Creates [DefaultStatesManager] with [InMemoryDefaultStatesManagerRepo]
|
||||
*
|
||||
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
||||
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
||||
* new state by using [endChain] with that state
|
||||
*/
|
||||
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
|
||||
fun <T: State> InMemoryStatesManager(
|
||||
onStartContextsConflictResolver: suspend (old: T, new: T) -> Boolean = { _, _ -> true },
|
||||
onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||
) = DefaultStatesManager(onStartContextsConflictResolver = onStartContextsConflictResolver, onUpdateContextsConflictResolver = onUpdateContextsConflictResolver)
|
@@ -1,7 +1,6 @@
|
||||
import dev.inmo.micro_utils.fsm.common.*
|
||||
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
|
||||
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
|
||||
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
sealed interface TrafficLightState : State {
|
||||
|
@@ -21,7 +21,7 @@ if (new File(projectDir, "secret.gradle").exists()) {
|
||||
owner "InsanusMokrassar"
|
||||
repo "MicroUtils"
|
||||
|
||||
tagName "${project.version}"
|
||||
tagName "v${project.version}"
|
||||
releaseName "${project.version}"
|
||||
targetCommitish "${project.version}"
|
||||
|
||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.10.8
|
||||
android_code_version=123
|
||||
version=0.12.13
|
||||
android_code_version=152
|
||||
|
@@ -1,57 +1,68 @@
|
||||
[versions]
|
||||
|
||||
kt = "1.6.21"
|
||||
kt-serialization = "1.3.3"
|
||||
kt-coroutines = "1.6.1"
|
||||
kt = "1.7.10"
|
||||
kt-serialization = "1.4.0"
|
||||
kt-coroutines = "1.6.4"
|
||||
|
||||
jb-compose = "1.2.0-alpha01-dev686"
|
||||
jb-exposed = "0.38.2"
|
||||
jb-dokka = "1.6.21"
|
||||
jb-compose = "1.2.0-alpha01-dev774"
|
||||
jb-exposed = "0.39.2"
|
||||
jb-dokka = "1.7.10"
|
||||
|
||||
klock = "2.7.0"
|
||||
uuid = "0.4.0"
|
||||
klock = "3.1.0"
|
||||
uuid = "0.5.0"
|
||||
|
||||
ktor = "2.0.2"
|
||||
ktor = "2.1.1"
|
||||
|
||||
gh-release = "2.3.7"
|
||||
gh-release = "2.4.1"
|
||||
|
||||
android-gradle = "7.0.4"
|
||||
koin = "3.2.0"
|
||||
|
||||
android-gradle = "7.2.2"
|
||||
dexcount = "3.1.0"
|
||||
|
||||
android-coreKtx = "1.7.0"
|
||||
android-coreKtx = "1.8.0"
|
||||
android-recyclerView = "1.2.1"
|
||||
android-appCompat = "1.4.1"
|
||||
android-espresso = "3.3.0"
|
||||
android-test = "1.1.2"
|
||||
android-appCompat = "1.4.2"
|
||||
android-espresso = "3.4.0"
|
||||
android-test = "1.1.3"
|
||||
|
||||
android-props-minSdk = "19"
|
||||
android-props-minSdk = "21"
|
||||
android-props-compileSdk = "32"
|
||||
android-props-buildTools = "32.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" }
|
||||
|
||||
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
|
||||
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
|
||||
kt-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kt-coroutines" }
|
||||
|
||||
|
||||
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
|
||||
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
||||
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
||||
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
|
||||
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" }
|
||||
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
||||
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
||||
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
|
||||
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
|
||||
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
|
||||
ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" }
|
||||
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" }
|
||||
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
|
||||
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
|
||||
|
||||
|
||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
|
||||
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
|
||||
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
|
||||
|
||||
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.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>()
|
@@ -19,6 +19,7 @@ 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 },
|
||||
@@ -65,6 +66,7 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
* @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>,
|
||||
|
@@ -0,0 +1,15 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import io.ktor.client.plugins.ClientRequestException
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.http.isSuccess
|
||||
|
||||
inline fun HttpResponse.throwOnUnsuccess(
|
||||
unsuccessMessage: () -> String
|
||||
) {
|
||||
if (status.isSuccess()) {
|
||||
return
|
||||
}
|
||||
|
||||
throw ClientRequestException(this, unsuccessMessage())
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.Warning
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.pluginOrNull
|
||||
import io.ktor.client.plugins.websocket.*
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.http.URLProtocol
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
|
||||
inline fun <reified T : Any> openBaseWebSocketFlow(
|
||||
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
|
||||
): Flow<T> {
|
||||
return channelFlow {
|
||||
do {
|
||||
val reconnect = runCatchingSafely {
|
||||
webSocketSessionRequest()
|
||||
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
|
||||
*/
|
||||
inline fun <reified T : Any> HttpClient.openWebSocketFlow(
|
||||
url: String,
|
||||
useSecureConnection: Boolean,
|
||||
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||
): Flow<T> {
|
||||
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
||||
|
||||
return openBaseWebSocketFlow<T>(checkReconnection) {
|
||||
val block: suspend DefaultClientWebSocketSession.() -> Unit = {
|
||||
while (isActive) {
|
||||
send(receiveDeserialized<T>())
|
||||
}
|
||||
}
|
||||
|
||||
if (useSecureConnection) {
|
||||
wss(url, requestBuilder, block)
|
||||
} else {
|
||||
ws(url, requestBuilder, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||
*/
|
||||
inline fun <reified T : Any> HttpClient.openWebSocketFlow(
|
||||
url: String,
|
||||
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||
): Flow<T> = openWebSocketFlow(url, false, checkReconnection, requestBuilder)
|
||||
|
||||
/**
|
||||
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||
*/
|
||||
inline fun <reified T : Any> HttpClient.openSecureWebSocketFlow(
|
||||
url: String,
|
||||
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||
): Flow<T> = openWebSocketFlow(url, true, checkReconnection, requestBuilder)
|
||||
|
||||
/**
|
||||
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||
*/
|
||||
inline fun <reified T : Any> HttpClient.createStandardWebsocketFlow(
|
||||
url: String,
|
||||
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||
): Flow<T> = openWebSocketFlow(url, checkReconnection, requestBuilder)
|
@@ -12,8 +12,7 @@ import io.ktor.http.*
|
||||
import io.ktor.utils.io.core.ByteReadPacket
|
||||
import kotlinx.serialization.*
|
||||
|
||||
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
||||
|
||||
@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
|
||||
@@ -33,7 +32,7 @@ class UnifiedRequester(
|
||||
|
||||
suspend fun <BodyType, ResultType> unipost(
|
||||
url: String,
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
|
||||
|
||||
@@ -52,7 +51,7 @@ class UnifiedRequester(
|
||||
url: String,
|
||||
filename: String,
|
||||
inputProvider: InputProvider,
|
||||
otherData: BodyPair<BodyType>,
|
||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
@@ -75,7 +74,7 @@ class UnifiedRequester(
|
||||
suspend fun <BodyType, ResultType> unimultipart(
|
||||
url: String,
|
||||
mppFile: MPPFile,
|
||||
otherData: BodyPair<BodyType>,
|
||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
@@ -99,8 +98,10 @@ class UnifiedRequester(
|
||||
) = 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>,
|
||||
@@ -110,6 +111,7 @@ suspend fun <ResultType> HttpClient.uniget(
|
||||
}
|
||||
|
||||
|
||||
@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
|
||||
@@ -118,9 +120,10 @@ fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
|
||||
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: BodyPair<BodyType>,
|
||||
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = post(url) {
|
||||
@@ -131,6 +134,7 @@ suspend fun <BodyType, ResultType> HttpClient.unipost(
|
||||
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,
|
||||
@@ -159,10 +163,11 @@ suspend fun <ResultType> HttpClient.unimultipart(
|
||||
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: BodyPair<BodyType>,
|
||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
inputProvider: InputProvider,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
@@ -196,6 +201,7 @@ suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||
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,
|
||||
@@ -217,10 +223,11 @@ suspend fun <ResultType> HttpClient.unimultipart(
|
||||
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: BodyPair<BodyType>,
|
||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
|
@@ -4,7 +4,10 @@ import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.ktor.common.TemporalFileId
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.coroutines.*
|
||||
import org.w3c.dom.mediasource.ENDED
|
||||
import org.w3c.dom.mediasource.ReadyState
|
||||
import org.w3c.xhr.*
|
||||
import org.w3c.xhr.XMLHttpRequest.Companion.DONE
|
||||
|
||||
suspend fun tempUpload(
|
||||
fullTempUploadDraftPath: String,
|
||||
@@ -12,7 +15,7 @@ suspend fun tempUpload(
|
||||
onUpload: (Long, Long) -> Unit
|
||||
): TemporalFileId {
|
||||
val formData = FormData()
|
||||
val answer = CompletableDeferred<TemporalFileId>()
|
||||
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
|
||||
|
||||
formData.append(
|
||||
"data",
|
||||
@@ -37,17 +40,15 @@ suspend fun tempUpload(
|
||||
request.open("POST", fullTempUploadDraftPath, true)
|
||||
request.send(formData)
|
||||
|
||||
val handle = currentCoroutineContext().job.invokeOnCompletion {
|
||||
answer.invokeOnCompletion {
|
||||
runCatching {
|
||||
if (request.readyState != DONE) {
|
||||
request.abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
answer.await()
|
||||
}.also {
|
||||
handle.dispose()
|
||||
}.getOrThrow()
|
||||
return answer.await()
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,15 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.response.responseType
|
||||
import io.ktor.util.InternalAPI
|
||||
import io.ktor.util.reflect.TypeInfo
|
||||
|
||||
@InternalAPI
|
||||
suspend fun <T : Any> ApplicationCall.respond(
|
||||
message: T,
|
||||
typeInfo: TypeInfo
|
||||
) {
|
||||
response.responseType = typeInfo
|
||||
response.pipeline.execute(this, message as Any)
|
||||
}
|
@@ -12,6 +12,7 @@ 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>,
|
||||
@@ -32,6 +33,7 @@ fun <T> Route.includeWebsocketHandling(
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This method will be removed soon")
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
|
@@ -0,0 +1,31 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.http.URLProtocol
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.application.pluginOrNull
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.application
|
||||
import io.ktor.server.websocket.*
|
||||
import io.ktor.websocket.send
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
inline fun <reified T : Any> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
protocol: URLProtocol? = null,
|
||||
noinline dataMapper: suspend WebSocketServerSession.(T) -> T? = { it }
|
||||
) {
|
||||
application.apply {
|
||||
pluginOrNull(WebSockets) ?: install(WebSockets)
|
||||
}
|
||||
webSocket(suburl, protocol ?.name) {
|
||||
safely {
|
||||
flow.collect {
|
||||
sendSerialized(dataMapper(it) ?: return@collect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -19,6 +19,7 @@ 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
|
||||
@@ -97,6 +98,7 @@ class UnifiedRouter(
|
||||
|
||||
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
|
||||
@@ -107,6 +109,7 @@ suspend fun <T> ApplicationCall.unianswer(
|
||||
)
|
||||
}
|
||||
|
||||
@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 {
|
||||
@@ -119,6 +122,7 @@ suspend fun <T> ApplicationCall.uniload(
|
||||
suspend fun ApplicationCall.uniloadMultipart(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||
) = safely {
|
||||
val multipartData = receiveMultipart()
|
||||
@@ -135,16 +139,19 @@ suspend fun ApplicationCall.uniloadMultipart(
|
||||
}
|
||||
}
|
||||
is PartData.BinaryItem -> onBinaryContent(it)
|
||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -157,6 +164,7 @@ suspend fun <T> ApplicationCall.uniloadMultipart(
|
||||
onCustomFileItem(it)
|
||||
}
|
||||
},
|
||||
onBinaryChannelItem,
|
||||
onBinaryContent
|
||||
)
|
||||
|
||||
@@ -164,10 +172,12 @@ suspend fun <T> ApplicationCall.uniloadMultipart(
|
||||
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()
|
||||
@@ -204,6 +214,7 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
|
||||
}
|
||||
}
|
||||
is PartData.BinaryItem -> onBinaryContent(it)
|
||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +225,7 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
|
||||
suspend fun ApplicationCall.uniloadMultipartFile(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||
) = safely {
|
||||
val multipartData = receiveMultipart()
|
||||
@@ -247,6 +259,7 @@ suspend fun ApplicationCall.uniloadMultipartFile(
|
||||
}
|
||||
}
|
||||
is PartData.BinaryItem -> onBinaryContent(it)
|
||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,6 +286,7 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
|
||||
}
|
||||
}
|
||||
|
||||
@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>
|
||||
@@ -283,6 +297,7 @@ fun <T> ApplicationCall.decodeUrlQueryValue(
|
||||
)
|
||||
}
|
||||
|
||||
@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>
|
||||
|
@@ -13,6 +13,7 @@ import io.ktor.http.content.streamProvider
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.request.receiveMultipart
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.post
|
||||
import kotlinx.coroutines.*
|
||||
@@ -111,7 +112,7 @@ class TemporalFilesRoutingConfigurator(
|
||||
temporalFilesMutex.withLock {
|
||||
temporalFilesMap[fileId] = file
|
||||
}
|
||||
call.respond(fileId.string)
|
||||
call.respondText(fileId.string)
|
||||
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
|
||||
} ?: call.respond(HttpStatusCode.BadRequest)
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -1,23 +1,68 @@
|
||||
package dev.inmo.micro_utils.pagination
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.*
|
||||
import kotlin.math.ceil
|
||||
|
||||
/**
|
||||
* @param page Current page number
|
||||
* @param size Current page size. It can be greater than size of [results]
|
||||
* @param results Result objects
|
||||
* @param objectsNumber Count of all objects across all pages
|
||||
*/
|
||||
@Serializable
|
||||
data class PaginationResult<T>(
|
||||
override val page: Int,
|
||||
val pagesNumber: Int,
|
||||
override val size: Int,
|
||||
val results: List<T>,
|
||||
override val size: Int
|
||||
) : Pagination
|
||||
val objectsNumber: Long
|
||||
) : Pagination {
|
||||
/**
|
||||
* Amount of pages for current pagination
|
||||
*/
|
||||
@EncodeDefault
|
||||
val pagesNumber: Int = ceil(objectsNumber / size.toFloat()).toInt()
|
||||
|
||||
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0)
|
||||
constructor(
|
||||
page: Int,
|
||||
results: List<T>,
|
||||
pagesNumber: Int,
|
||||
size: Int
|
||||
) : this(
|
||||
page,
|
||||
size,
|
||||
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
|
||||
*/
|
||||
fun <I, O> PaginationResult<I>.changeResultsUnchecked(
|
||||
data: List<O>
|
||||
): PaginationResult<O> = PaginationResult(page, pagesNumber, data, size)
|
||||
): PaginationResult<O> = PaginationResult(page, size, data, objectsNumber)
|
||||
/**
|
||||
* @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality
|
||||
*/
|
||||
@@ -33,12 +78,9 @@ fun <T> List<T>.createPaginationResult(
|
||||
commonObjectsNumber: Long
|
||||
) = PaginationResult(
|
||||
pagination.page,
|
||||
calculatePagesNumber(
|
||||
commonObjectsNumber,
|
||||
pagination.size
|
||||
),
|
||||
pagination.size,
|
||||
this,
|
||||
pagination.size
|
||||
commonObjectsNumber
|
||||
)
|
||||
|
||||
fun <T> List<T>.createPaginationResult(
|
||||
@@ -46,12 +88,9 @@ fun <T> List<T>.createPaginationResult(
|
||||
commonObjectsNumber: Long
|
||||
) = PaginationResult(
|
||||
calculatePage(firstIndex, size),
|
||||
calculatePagesNumber(
|
||||
commonObjectsNumber,
|
||||
size
|
||||
),
|
||||
size,
|
||||
this,
|
||||
size
|
||||
commonObjectsNumber
|
||||
)
|
||||
|
||||
fun <T> Pair<Long, List<T>>.createPaginationResult(
|
||||
|
@@ -26,6 +26,10 @@ inline fun Pagination.nextPage() =
|
||||
size
|
||||
)
|
||||
|
||||
/**
|
||||
* @param page Current page number
|
||||
* @param size Current page size
|
||||
*/
|
||||
@Serializable
|
||||
data class SimplePagination(
|
||||
override val page: Int,
|
||||
|
@@ -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(
|
||||
|
@@ -1,16 +1,16 @@
|
||||
package dev.inmo.micro_utils.repos.cache
|
||||
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
protected val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
protected val kvCache: KVCache<IdType, ObjectType>,
|
||||
protected val idGetter: (ObjectType) -> IdType
|
||||
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo {
|
||||
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||
protected open val idGetter: (ObjectType) -> IdType
|
||||
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CacheRepo {
|
||||
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
|
||||
kvCache.set(id, it)
|
||||
})
|
||||
@@ -18,8 +18,71 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||
kvCache: KVCache<IdType, ObjectType>,
|
||||
idGetter: (ObjectType) -> IdType
|
||||
) = ReadCRUDCacheRepo(this, kvCache, idGetter)
|
||||
|
||||
open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
protected open val parentRepo: WriteCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
protected open val idGetter: (ObjectType) -> IdType
|
||||
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CacheRepo {
|
||||
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
|
||||
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)
|
||||
|
||||
override suspend fun deleteById(ids: List<IdType>) = parentRepo.deleteById(ids)
|
||||
|
||||
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
||||
val updated = parentRepo.update(values)
|
||||
|
||||
kvCache.unset(values.map { it.id })
|
||||
kvCache.set(updated.associateBy { idGetter(it) })
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
||||
return parentRepo.update(id, value) ?.also {
|
||||
kvCache.unset(id)
|
||||
kvCache.set(idGetter(it), it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||
val created = parentRepo.create(values)
|
||||
|
||||
kvCache.set(
|
||||
created.associateBy { idGetter(it) }
|
||||
)
|
||||
|
||||
return created
|
||||
}
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
|
||||
kvCache: KVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope,
|
||||
idGetter: (ObjectType) -> IdType
|
||||
) = WriteCRUDCacheRepo(this, kvCache, scope, idGetter)
|
||||
|
||||
|
||||
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
kvCache: KVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
idGetter: (ObjectType) -> IdType
|
||||
@@ -27,8 +90,17 @@ open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
parentRepo,
|
||||
kvCache,
|
||||
idGetter
|
||||
), CRUDRepo<ObjectType, IdType, InputValueType>, WriteCRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
|
||||
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
}
|
||||
),
|
||||
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
|
||||
parentRepo,
|
||||
kvCache,
|
||||
scope,
|
||||
idGetter
|
||||
),
|
||||
CRUDRepo<ObjectType, IdType, InputValueType>
|
||||
|
||||
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
||||
kvCache: KVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope,
|
||||
idGetter: (ObjectType) -> IdType
|
||||
) = CRUDCacheRepo(this, kvCache, scope, idGetter)
|
||||
|
3
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CacheRepo.kt
vendored
Normal file
3
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/CacheRepo.kt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.repos.cache
|
||||
|
||||
interface CacheRepo
|
@@ -1,41 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.cache
|
||||
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
interface KVCache<K, V> : KeyValueRepo<K, V>
|
||||
|
||||
open class SimpleKVCache<K, V>(
|
||||
protected val cachedValuesCount: Int,
|
||||
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||
) : KVCache<K, V>, KeyValueRepo<K, V> by kvParent {
|
||||
protected open val cacheStack = ArrayList<K>(cachedValuesCount)
|
||||
protected val syncMutex = Mutex()
|
||||
|
||||
protected suspend fun makeUnset(toUnset: List<K>) {
|
||||
cacheStack.removeAll(toUnset)
|
||||
kvParent.unset(toUnset)
|
||||
}
|
||||
|
||||
override suspend fun set(toSet: Map<K, V>) {
|
||||
syncMutex.withLock {
|
||||
if (toSet.size > cachedValuesCount) {
|
||||
cacheStack.clear()
|
||||
|
||||
kvParent.unset(getAllWithNextPaging { kvParent.keys(it) })
|
||||
val keysToInclude = toSet.keys.drop(toSet.size - cachedValuesCount)
|
||||
|
||||
cacheStack.addAll(keysToInclude)
|
||||
kvParent.set(keysToInclude.associateWith { toSet.getValue(it) })
|
||||
} else {
|
||||
makeUnset(cacheStack.take(toSet.size))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unset(toUnset: List<K>) {
|
||||
syncMutex.withLock { makeUnset(toUnset) }
|
||||
}
|
||||
}
|
@@ -1,25 +1,44 @@
|
||||
package dev.inmo.micro_utils.repos.cache
|
||||
|
||||
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
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class ReadKeyValueCacheRepo<Key,Value>(
|
||||
protected val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||
protected val kvCache: KVCache<Key, Value>,
|
||||
) : ReadKeyValueRepo<Key,Value> by parentRepo {
|
||||
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||
protected open val kvCache: KVCache<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(
|
||||
kvCache: KVCache<Key, Value>
|
||||
) = ReadKeyValueCacheRepo(this, kvCache)
|
||||
|
||||
open class KeyValueCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValueRepo<Key, Value>,
|
||||
kvCache: KVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo {
|
||||
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CacheRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
}
|
||||
|
||||
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||
kvCache: KVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) = KeyValueCacheRepo(this, kvCache, scope)
|
||||
|
@@ -1,42 +1,64 @@
|
||||
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
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||
protected val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||
protected val kvCache: KVCache<Key, List<Value>>
|
||||
) : ReadKeyValuesRepo<Key,Value> by parentRepo {
|
||||
protected open val parentRepo: ReadKeyValuesRepo<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)
|
||||
}
|
||||
|
||||
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||
kvCache: KVCache<Key, List<Value>>
|
||||
) = ReadKeyValuesCacheRepo(this, kvCache)
|
||||
|
||||
open class KeyValuesCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValuesRepo<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 {
|
||||
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)
|
||||
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
|
||||
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(
|
||||
kvCache: KVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) = KeyValuesCacheRepo(this, kvCache, scope)
|
||||
|
8
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/FullKVCache.kt
vendored
Normal file
8
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/FullKVCache.kt
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package dev.inmo.micro_utils.repos.cache.cache
|
||||
|
||||
/**
|
||||
* This interface declares that current type of [KVCache] will contains all the data all the time of its life
|
||||
*/
|
||||
interface FullKVCache<K, V> : KVCache<K, V> {
|
||||
companion object
|
||||
}
|
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/KVCache.kt
vendored
Normal file
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/KVCache.kt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.cache.cache
|
||||
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
|
||||
interface KVCache<K, V> : KeyValueRepo<K, V> {
|
||||
companion object
|
||||
}
|
28
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleFullKVCache.kt
vendored
Normal file
28
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleFullKVCache.kt
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package dev.inmo.micro_utils.repos.cache.cache
|
||||
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.MapKeyValueRepo
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class SimpleFullKVCache<K, V>(
|
||||
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||
) : FullKVCache<K, V>, KeyValueRepo<K, V> by kvParent {
|
||||
protected val syncMutex = Mutex()
|
||||
|
||||
override suspend fun set(toSet: Map<K, V>) {
|
||||
syncMutex.withLock {
|
||||
kvParent.set(toSet)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unset(toUnset: List<K>) {
|
||||
syncMutex.withLock {
|
||||
kvParent.unset(toUnset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <K, V> FullKVCache(
|
||||
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||
) = SimpleFullKVCache<K, V>(kvParent)
|
45
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleKVCache.kt
vendored
Normal file
45
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/cache/SimpleKVCache.kt
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package dev.inmo.micro_utils.repos.cache.cache
|
||||
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class SimpleKVCache<K, V>(
|
||||
protected val cachedValuesCount: Int,
|
||||
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||
) : KVCache<K, V>, KeyValueRepo<K, V> by kvParent {
|
||||
protected open val cacheQueue = ArrayDeque<K>(cachedValuesCount)
|
||||
protected val syncMutex = Mutex()
|
||||
|
||||
protected suspend fun makeUnset(toUnset: List<K>) {
|
||||
cacheQueue.removeAll(toUnset)
|
||||
kvParent.unset(toUnset)
|
||||
}
|
||||
|
||||
override suspend fun set(toSet: Map<K, V>) {
|
||||
syncMutex.withLock {
|
||||
for ((k, v) in toSet) {
|
||||
if (cacheQueue.size >= cachedValuesCount) {
|
||||
cacheQueue.removeFirstOrNull() ?.let {
|
||||
kvParent.unset(it)
|
||||
}
|
||||
}
|
||||
do {
|
||||
val removed = cacheQueue.remove(k)
|
||||
} while (removed)
|
||||
cacheQueue.addLast(k)
|
||||
kvParent.set(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unset(toUnset: List<K>) {
|
||||
syncMutex.withLock { makeUnset(toUnset) }
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <K, V> KVCache(
|
||||
cachedValuesCount: Int,
|
||||
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||
) = SimpleKVCache<K, V>(cachedValuesCount, kvParent)
|
95
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullCRUDCacheRepo.kt
vendored
Normal file
95
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullCRUDCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package dev.inmo.micro_utils.repos.cache.full
|
||||
|
||||
import dev.inmo.micro_utils.common.*
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.cache.*
|
||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
protected open val kvCache: FullKVCache<IdType, ObjectType>,
|
||||
protected open val idGetter: (ObjectType) -> IdType
|
||||
) : ReadCRUDRepo<ObjectType, IdType>, FullCacheRepo {
|
||||
protected inline fun <T> doOrTakeAndActualize(
|
||||
action: FullKVCache<IdType, ObjectType>.() -> Optional<T>,
|
||||
actionElse: ReadCRUDRepo<ObjectType, IdType>.() -> T,
|
||||
actualize: FullKVCache<IdType, ObjectType>.(T) -> Unit
|
||||
): T {
|
||||
kvCache.action().onPresented {
|
||||
return it
|
||||
}.onAbsent {
|
||||
return parentRepo.actionElse().also {
|
||||
kvCache.actualize(it)
|
||||
}
|
||||
}
|
||||
error("The result should be returned above")
|
||||
}
|
||||
|
||||
protected open suspend fun actualizeAll() {
|
||||
kvCache.clear()
|
||||
doForAllWithNextPaging {
|
||||
parentRepo.getByPagination(it).also {
|
||||
kvCache.set(it.results.associateBy { idGetter(it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
|
||||
{ values(pagination).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||
{ getByPagination(pagination) },
|
||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = doOrTakeAndActualize(
|
||||
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
|
||||
{ count() },
|
||||
{ if (it != 0L) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun contains(id: IdType): Boolean = doOrTakeAndActualize(
|
||||
{ contains(id).takeIf { it }.optionalOrAbsentIfNull },
|
||||
{ contains(id) },
|
||||
{ if (it) parentRepo.getById(id) ?.let { set(id, it) } }
|
||||
)
|
||||
|
||||
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize(
|
||||
{ get(id) ?.optional ?: Optional.absent() },
|
||||
{ getById(id) },
|
||||
{ it ?.let { set(idGetter(it), it) } }
|
||||
)
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||
kvCache: FullKVCache<IdType, ObjectType>,
|
||||
idGetter: (ObjectType) -> IdType
|
||||
) = FullReadCRUDCacheRepo(this, kvCache, idGetter)
|
||||
|
||||
open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
kvCache: FullKVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
idGetter: (ObjectType) -> IdType
|
||||
) : FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
parentRepo,
|
||||
kvCache,
|
||||
idGetter
|
||||
),
|
||||
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
|
||||
parentRepo,
|
||||
kvCache,
|
||||
scope,
|
||||
idGetter
|
||||
),
|
||||
CRUDRepo<ObjectType, IdType, InputValueType>
|
||||
|
||||
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
||||
kvCache: FullKVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
idGetter: (ObjectType) -> IdType
|
||||
) = FullCRUDCacheRepo(this, kvCache, scope, idGetter)
|
5
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullCacheRepo.kt
vendored
Normal file
5
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.micro_utils.repos.cache.full
|
||||
|
||||
import dev.inmo.micro_utils.repos.cache.CacheRepo
|
||||
|
||||
interface FullCacheRepo : CacheRepo
|
104
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt
vendored
Normal file
104
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValueCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
package dev.inmo.micro_utils.repos.cache.full
|
||||
|
||||
import dev.inmo.micro_utils.common.*
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
open class FullReadKeyValueCacheRepo<Key,Value>(
|
||||
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||
protected open val kvCache: FullKVCache<Key, Value>,
|
||||
) : ReadKeyValueRepo<Key, Value>, FullCacheRepo {
|
||||
protected inline fun <T> doOrTakeAndActualize(
|
||||
action: FullKVCache<Key, Value>.() -> Optional<T>,
|
||||
actionElse: ReadKeyValueRepo<Key, Value>.() -> T,
|
||||
actualize: FullKVCache<Key, Value>.(T) -> Unit
|
||||
): T {
|
||||
kvCache.action().onPresented {
|
||||
return it
|
||||
}.onAbsent {
|
||||
return parentRepo.actionElse().also {
|
||||
kvCache.actualize(it)
|
||||
}
|
||||
}
|
||||
error("The result should be returned above")
|
||||
}
|
||||
protected open suspend fun actualizeAll() {
|
||||
kvCache.clear()
|
||||
kvCache.set(parentRepo.getAll { keys(it) }.toMap())
|
||||
}
|
||||
|
||||
override suspend fun get(k: Key): Value? = doOrTakeAndActualize(
|
||||
{ get(k) ?.optional ?: Optional.absent() },
|
||||
{ get(k) },
|
||||
{ set(k, it ?: return@doOrTakeAndActualize) }
|
||||
)
|
||||
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = doOrTakeAndActualize(
|
||||
{ values(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||
{ values(pagination, reversed) },
|
||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = doOrTakeAndActualize(
|
||||
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
|
||||
{ count() },
|
||||
{ if (it != 0L) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = doOrTakeAndActualize(
|
||||
{ contains(key).takeIf { it }.optionalOrAbsentIfNull },
|
||||
{ contains(key) },
|
||||
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
|
||||
)
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
|
||||
{ keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||
{ keys(pagination, reversed) },
|
||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
|
||||
{ keys(v, pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||
{ parentRepo.keys(v, pagination, reversed) },
|
||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||
)
|
||||
}
|
||||
|
||||
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||
kvCache: FullKVCache<Key, Value>
|
||||
) = FullReadKeyValueCacheRepo(this, kvCache)
|
||||
|
||||
open class FullWriteKeyValueCacheRepo<Key,Value>(
|
||||
protected open val parentRepo: WriteKeyValueRepo<Key, Value>,
|
||||
protected open val kvCache: FullKVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
}
|
||||
|
||||
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
||||
kvCache: FullKVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
|
||||
|
||||
open class FullKeyValueCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValueRepo<Key, Value>,
|
||||
kvCache: FullKVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
|
||||
KeyValueRepo<Key,Value>,
|
||||
ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(parentRepo, kvCache) {
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
|
||||
}
|
||||
|
||||
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||
kvCache: FullKVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) = FullKeyValueCacheRepo(this, kvCache, scope)
|
154
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt
vendored
Normal file
154
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/full/FullKeyValuesCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
package dev.inmo.micro_utils.repos.cache.full
|
||||
|
||||
import dev.inmo.micro_utils.common.*
|
||||
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.FullKVCache
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
open class FullReadKeyValuesCacheRepo<Key,Value>(
|
||||
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
||||
) : ReadKeyValuesRepo<Key, Value>, FullCacheRepo {
|
||||
protected inline fun <T> doOrTakeAndActualize(
|
||||
action: FullKVCache<Key, List<Value>>.() -> Optional<T>,
|
||||
actionElse: ReadKeyValuesRepo<Key, Value>.() -> T,
|
||||
actualize: FullKVCache<Key, List<Value>>.(T) -> Unit
|
||||
): T {
|
||||
kvCache.action().onPresented {
|
||||
return it
|
||||
}.onAbsent {
|
||||
return parentRepo.actionElse().also {
|
||||
kvCache.actualize(it)
|
||||
}
|
||||
}
|
||||
error("The result should be returned above")
|
||||
}
|
||||
|
||||
protected open suspend fun actualizeKey(k: Key) {
|
||||
kvCache.set(k, parentRepo.getAll(k))
|
||||
}
|
||||
|
||||
protected open suspend fun actualizeAll() {
|
||||
doAllWithCurrentPaging { kvCache.keys(it).also { kvCache.unset(it.results) } }
|
||||
kvCache.set(parentRepo.getAll())
|
||||
}
|
||||
|
||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||
return doOrTakeAndActualize(
|
||||
{
|
||||
get(k) ?.paginate(
|
||||
pagination.let { if (reversed) it.reverse(count(k)) else it }
|
||||
) ?.let {
|
||||
if (reversed) it.copy(results = it.results.reversed()) else it
|
||||
}.optionalOrAbsentIfNull
|
||||
},
|
||||
{ get(k, pagination, reversed) },
|
||||
{ actualizeKey(k) }
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||
return doOrTakeAndActualize(
|
||||
{
|
||||
kvCache.keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull
|
||||
},
|
||||
{ parentRepo.keys(pagination, reversed) },
|
||||
{ actualizeAll() }
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun count(): Long = doOrTakeAndActualize(
|
||||
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
|
||||
{ count() },
|
||||
{ if (it != 0L) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun count(k: Key): Long = doOrTakeAndActualize(
|
||||
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
|
||||
{ count() },
|
||||
{ if (it != 0L) actualizeKey(k) }
|
||||
)
|
||||
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = doOrTakeAndActualize(
|
||||
{ get(k) ?.contains(v).takeIf { it == true }.optionalOrAbsentIfNull },
|
||||
{ contains(k, v) },
|
||||
{ if (it) actualizeKey(k) }
|
||||
)
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = doOrTakeAndActualize(
|
||||
{ contains(k).takeIf { it }.optionalOrAbsentIfNull },
|
||||
{ contains(k) },
|
||||
{ if (it) actualizeKey(k) }
|
||||
)
|
||||
|
||||
override suspend fun keys(
|
||||
v: Value,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = doOrTakeAndActualize(
|
||||
{
|
||||
val keys = getAllWithNextPaging { keys(it) }.filter { get(it) ?.contains(v) == true }.optionallyReverse(reversed)
|
||||
if (keys.isNotEmpty()) {
|
||||
keys.paginate(pagination.optionallyReverse(keys.size, reversed)).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull
|
||||
} else {
|
||||
Optional.absent()
|
||||
}
|
||||
},
|
||||
{ parentRepo.keys(v, pagination, reversed) },
|
||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||
kvCache: FullKVCache<Key, List<Value>>
|
||||
) = FullReadKeyValuesCacheRepo(this, kvCache)
|
||||
|
||||
open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
||||
protected open val parentRepo: WriteKeyValuesRepo<Key, Value>,
|
||||
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
|
||||
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)
|
||||
}
|
||||
|
||||
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
||||
kvCache: FullKVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
|
||||
|
||||
open class FullKeyValuesCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValuesRepo<Key, Value>,
|
||||
kvCache: FullKVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
|
||||
KeyValuesRepo<Key, Value>,
|
||||
ReadKeyValuesRepo<Key, Value> by FullReadKeyValuesCacheRepo(parentRepo, kvCache) {
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
doAllWithCurrentPaging {
|
||||
keys(v, it).also {
|
||||
remove(it.results.associateWith { listOf(v) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> KeyValuesRepo<Key, Value>.caching(
|
||||
kvCache: FullKVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) = FullKeyValuesCacheRepo(this, kvCache, scope)
|
@@ -1,11 +1,10 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithCurrentPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
interface ReadKeyValuesRepo<Key, Value> : Repo {
|
||||
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
@@ -36,9 +35,9 @@ interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
|
||||
typealias ReadOneToManyKeyValueRepo<Key,Value> = ReadKeyValuesRepo<Key, Value>
|
||||
|
||||
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
interface WriteKeyValuesRepo<Key, Value> : Repo {
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
val onValueRemoved: Flow<Pair<Key, Value>>
|
||||
val onDataCleared: Flow<Key>
|
||||
@@ -55,41 +54,41 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
add(toSet)
|
||||
}
|
||||
}
|
||||
typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
|
||||
typealias WriteOneToManyKeyValueRepo<Key,Value> = WriteKeyValuesRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
) = add(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add(
|
||||
vararg keysAndValues: Pair<Key, List<Value>>
|
||||
) = add(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
|
||||
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add(
|
||||
k: Key, v: List<Value>
|
||||
) = add(mapOf(k to v))
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
|
||||
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add(
|
||||
k: Key, vararg v: Value
|
||||
) = add(k, v.toList())
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
|
||||
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
) = set(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
|
||||
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set(
|
||||
vararg keysAndValues: Pair<Key, List<Value>>
|
||||
) = set(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
||||
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set(
|
||||
k: Key, v: List<Value>
|
||||
) = set(mapOf(k to v))
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
||||
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set(
|
||||
k: Key, vararg v: Value
|
||||
) = set(k, v.toList())
|
||||
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> {
|
||||
interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyValuesRepo<Key, Value> {
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
doWithPagination {
|
||||
val keysResult = keys(v, it)
|
||||
@@ -102,22 +101,29 @@ interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Val
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
|
||||
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
class DelegateBasedKeyValuesRepo<Key, Value>(
|
||||
readDelegate: ReadKeyValuesRepo<Key, Value>,
|
||||
writeDelegate: WriteKeyValuesRepo<Key, Value>
|
||||
) : KeyValuesRepo<Key, Value>,
|
||||
ReadKeyValuesRepo<Key, Value> by readDelegate,
|
||||
WriteKeyValuesRepo<Key, Value> by writeDelegate
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
) = remove(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
|
||||
vararg keysAndValues: Pair<Key, List<Value>>
|
||||
) = remove(keysAndValues.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
|
||||
k: Key,
|
||||
v: List<Value>
|
||||
) = remove(mapOf(k to v))
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
|
||||
k: Key,
|
||||
vararg v: Value
|
||||
) = remove(k, v.toList())
|
@@ -1,22 +1,51 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.common.*
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||
val keyMapper: SimpleSuspendableMapper<FromKey, ToKey>
|
||||
get() = simpleSuspendableMapper(
|
||||
{ it.toInnerKey() },
|
||||
{ it.toOutKey() }
|
||||
)
|
||||
val valueMapper: SimpleSuspendableMapper<FromValue, ToValue>
|
||||
get() = simpleSuspendableMapper(
|
||||
{ it.toInnerValue() },
|
||||
{ it.toOutValue() }
|
||||
)
|
||||
|
||||
suspend fun FromKey.toOutKey() = this as ToKey
|
||||
suspend fun FromValue.toOutValue() = this as ToValue
|
||||
|
||||
suspend fun ToKey.toInnerKey() = this as FromKey
|
||||
suspend fun ToValue.toInnerValue() = this as FromValue
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
) = object : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||
class SimpleMapperRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val keyFromToTo: suspend FromKey.() -> ToKey,
|
||||
private val valueFromToTo: suspend FromValue.() -> ToValue,
|
||||
private val keyToToFrom: suspend ToKey.() -> FromKey,
|
||||
private val valueToToFrom: suspend ToValue.() -> FromValue
|
||||
) : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
|
||||
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
|
||||
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
|
||||
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
|
||||
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
|
||||
}
|
||||
|
||||
operator fun <FromKey, FromValue, ToKey, ToValue> MapperRepo.Companion.invoke(
|
||||
keyFromToTo: suspend FromKey.() -> ToKey,
|
||||
valueFromToTo: suspend FromValue.() -> ToValue,
|
||||
keyToToFrom: suspend ToKey.() -> FromKey,
|
||||
valueToToFrom: suspend ToValue.() -> FromValue
|
||||
) = SimpleMapperRepo(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
) = MapperRepo(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
|
@@ -4,13 +4,13 @@ import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
|
||||
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
|
||||
suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType>
|
||||
suspend fun getById(id: IdType): ObjectType?
|
||||
suspend fun contains(id: IdType): Boolean
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
|
||||
typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType>
|
||||
|
||||
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
|
||||
val <IdType> UpdatedValuePair<IdType, *>.id
|
||||
@@ -18,7 +18,7 @@ val <IdType> UpdatedValuePair<IdType, *>.id
|
||||
val <ValueType> UpdatedValuePair<*, ValueType>.value
|
||||
get() = second
|
||||
|
||||
interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
||||
interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
||||
val newObjectsFlow: Flow<ObjectType>
|
||||
val updatedObjectsFlow: Flow<ObjectType>
|
||||
val deletedObjectsIdsFlow: Flow<IdType>
|
||||
@@ -28,18 +28,25 @@ interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
||||
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
|
||||
suspend fun deleteById(ids: List<IdType>)
|
||||
}
|
||||
typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
typealias WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> = WriteCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
|
||||
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
|
||||
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.create(
|
||||
vararg values: InputValueType
|
||||
): List<ObjectType> = create(values.toList())
|
||||
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.update(
|
||||
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.update(
|
||||
vararg values: UpdatedValuePair<IdType, InputValueType>
|
||||
): List<ObjectType> = update(values.toList())
|
||||
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.deleteById(
|
||||
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.deleteById(
|
||||
vararg ids: IdType
|
||||
) = deleteById(ids.toList())
|
||||
|
||||
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
|
||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
interface CRUDRepo<ObjectType, IdType, InputValueType> : ReadCRUDRepo<ObjectType, IdType>,
|
||||
WriteCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
typealias StandardCRUDRepo<ObjectType, IdType, InputValueType> = CRUDRepo<ObjectType, IdType, InputValueType>
|
||||
|
||||
class DelegateBasedCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
readDelegate: ReadCRUDRepo<ObjectType, IdType>,
|
||||
writeDelegate: WriteCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
) : CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
ReadCRUDRepo<ObjectType, IdType> by readDelegate,
|
||||
WriteCRUDRepo<ObjectType, IdType, InputValueType> by writeDelegate
|
||||
|
@@ -4,7 +4,7 @@ import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
||||
interface ReadKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun get(k: Key): Value?
|
||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
@@ -12,9 +12,9 @@ interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun contains(key: Key): Boolean
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
|
||||
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value>
|
||||
|
||||
interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
||||
interface WriteKeyValueRepo<Key, Value> : Repo {
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
val onValueRemoved: Flow<Key>
|
||||
|
||||
@@ -22,25 +22,25 @@ interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun unset(toUnset: List<Key>)
|
||||
suspend fun unsetWithValues(toUnset: List<Value>)
|
||||
}
|
||||
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
|
||||
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
vararg toSet: Pair<Key, Value>
|
||||
) = set(toSet.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
k: Key, v: Value
|
||||
) = set(k to v)
|
||||
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
|
||||
vararg k: Key
|
||||
) = unset(k.toList())
|
||||
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithValues(
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
|
||||
vararg v: Value
|
||||
) = unsetWithValues(v.toList())
|
||||
|
||||
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> {
|
||||
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> {
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
||||
doAllWithCurrentPaging {
|
||||
keys(v, it).also {
|
||||
@@ -48,5 +48,16 @@ interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clear() {
|
||||
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
|
||||
}
|
||||
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>
|
||||
}
|
||||
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
|
||||
|
||||
class DelegateBasedKeyValueRepo<Key, Value>(
|
||||
readDelegate: ReadKeyValueRepo<Key, Value>,
|
||||
writeDelegate: WriteKeyValueRepo<Key, Value>
|
||||
) : KeyValueRepo<Key, Value>,
|
||||
ReadKeyValueRepo<Key, Value> by readDelegate,
|
||||
WriteKeyValueRepo<Key, Value> by writeDelegate
|
||||
|
@@ -0,0 +1,130 @@
|
||||
package dev.inmo.micro_utils.repos.mappers
|
||||
|
||||
import dev.inmo.micro_utils.common.*
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>(
|
||||
private val to: ReadCRUDRepo<ToRegistered, ToId>,
|
||||
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>
|
||||
) : ReadCRUDRepo<FromRegistered, FromId>, MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper {
|
||||
override suspend fun getByPagination(
|
||||
pagination: Pagination
|
||||
): PaginationResult<FromRegistered> = to.getByPagination(
|
||||
pagination
|
||||
).let {
|
||||
it.changeResultsUnchecked(
|
||||
it.results.map { it.toInnerValue() }
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun count(): Long = to.count()
|
||||
|
||||
override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey())
|
||||
|
||||
override suspend fun getById(id: FromId): FromRegistered? = to.getById(
|
||||
id.toOutKey()
|
||||
) ?.toInnerValue()
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): ReadCRUDRepo<FromValue, FromKey> = MapperReadCRUDRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadCRUDRepo<FromValue, FromKey> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperWriteCRUDRepo<FromId, FromRegistered, FromInput, ToId, ToRegistered, ToInput>(
|
||||
private val to: WriteCRUDRepo<ToRegistered, ToId, ToInput>,
|
||||
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>,
|
||||
inputMapper: SimpleSuspendableMapper<FromInput, ToInput>
|
||||
) : WriteCRUDRepo<FromRegistered, FromId, FromInput>,
|
||||
MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper,
|
||||
SimpleSuspendableMapper<FromInput, ToInput> by inputMapper {
|
||||
override val newObjectsFlow: Flow<FromRegistered> = to.newObjectsFlow.map { it.toInnerValue() }
|
||||
override val updatedObjectsFlow: Flow<FromRegistered> = to.updatedObjectsFlow.map { it.toInnerValue() }
|
||||
override val deletedObjectsIdsFlow: Flow<FromId> = to.deletedObjectsIdsFlow.map { it.toInnerKey() }
|
||||
|
||||
override suspend fun deleteById(ids: List<FromId>) = to.deleteById(ids.map { it.toOutKey() })
|
||||
|
||||
override suspend fun update(
|
||||
values: List<UpdatedValuePair<FromId, FromInput>>
|
||||
): List<FromRegistered> = to.update(
|
||||
values.map {
|
||||
it.first.toOutKey() to convert(it.second)
|
||||
}
|
||||
).map { it.toInnerValue() }
|
||||
|
||||
override suspend fun update(
|
||||
id: FromId,
|
||||
value: FromInput
|
||||
): FromRegistered? = to.update(id.toOutKey(), convert(value)) ?.toInnerValue()
|
||||
|
||||
override suspend fun create(values: List<FromInput>): List<FromRegistered> = to.create(
|
||||
values.map {
|
||||
convert(it)
|
||||
}
|
||||
).map {
|
||||
it.toInnerValue()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, FromInput, ToKey, ToValue, ToInput> WriteCRUDRepo<ToValue, ToKey, ToInput>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>,
|
||||
simpleSuspendableMapper: SimpleSuspendableMapper<FromInput, ToInput>
|
||||
): WriteCRUDRepo<FromValue, FromKey, FromInput> = MapperWriteCRUDRepo(this, mapper, simpleSuspendableMapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified FromInput, reified ToKey, reified ToValue, reified ToInput> WriteCRUDRepo<ToValue, ToKey, ToInput>.withMapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline inputFromToTo: suspend FromInput.() -> ToInput = { this as ToInput },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
noinline inputToToFrom: suspend ToInput.() -> FromInput = { this as FromInput },
|
||||
): WriteCRUDRepo<FromValue, FromKey, FromInput> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom),
|
||||
simpleSuspendableMapper({ inputToToFrom(it) }, { inputFromToTo(it) })
|
||||
)
|
||||
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
open class MapperCRUDRepo<FromId, FromRegistered, FromInput, ToId, ToRegistered, ToInput>(
|
||||
private val to: CRUDRepo<ToRegistered, ToId, ToInput>,
|
||||
private val mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>,
|
||||
private val inputMapper: SimpleSuspendableMapper<FromInput, ToInput>
|
||||
) : CRUDRepo<FromRegistered, FromId, FromInput>,
|
||||
MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper,
|
||||
ReadCRUDRepo<FromRegistered, FromId> by MapperReadCRUDRepo(to, mapper),
|
||||
WriteCRUDRepo<FromRegistered, FromId, FromInput> by MapperWriteCRUDRepo(to, mapper, inputMapper)
|
||||
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, FromInput, ToKey, ToValue, ToInput> CRUDRepo<ToValue, ToKey, ToInput>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>,
|
||||
simpleSuspendableMapper: SimpleSuspendableMapper<FromInput, ToInput>
|
||||
): CRUDRepo<FromValue, FromKey, FromInput> = MapperCRUDRepo(this, mapper, simpleSuspendableMapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified FromInput, reified ToKey, reified ToValue, reified ToInput> CRUDRepo<ToValue, ToKey, ToInput>.withMapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline inputFromToTo: suspend FromInput.() -> ToInput = { this as ToInput },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
noinline inputToToFrom: suspend ToInput.() -> FromInput = { this as FromInput },
|
||||
): CRUDRepo<FromValue, FromKey, FromInput> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom),
|
||||
simpleSuspendableMapper({ inputToToFrom(it) }, { inputFromToTo(it) })
|
||||
)
|
@@ -1,15 +1,14 @@
|
||||
package dev.inmo.micro_utils.repos.mappers
|
||||
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadStandardKeyValueRepo<ToKey, ToValue>,
|
||||
open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : ReadStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
) : ReadKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
override suspend fun get(k: FromKey): FromValue? = to.get(
|
||||
k.toOutKey()
|
||||
) ?.toInnerValue()
|
||||
@@ -21,11 +20,8 @@ open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerValue() },
|
||||
it.size
|
||||
it.changeResultsUnchecked(
|
||||
it.results.map { it.toInnerValue() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,11 +32,8 @@ open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
it.changeResultsUnchecked(
|
||||
it.results.map { it.toInnerKey() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,11 +46,8 @@ open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
it.changeResultsUnchecked(
|
||||
it.results.map { it.toInnerKey() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -69,24 +59,24 @@ open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
|
||||
): ReadKeyValueRepo<FromKey, FromValue> = MapperReadKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
|
||||
open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : WriteStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
) : WriteKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
||||
k.toInnerKey() to v.toInnerValue()
|
||||
}
|
||||
@@ -112,40 +102,40 @@ open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
|
||||
): WriteKeyValueRepo<FromKey, FromValue> = MapperWriteKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: StandardKeyValueRepo<ToKey, ToValue>,
|
||||
open class MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: KeyValueRepo<ToKey, ToValue>,
|
||||
private val mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : StandardKeyValueRepo<FromKey, FromValue>,
|
||||
) : KeyValueRepo<FromKey, FromValue>,
|
||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
|
||||
WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
|
||||
ReadKeyValueRepo<FromKey, FromValue> by MapperReadKeyValueRepo(to, mapper),
|
||||
WriteKeyValueRepo<FromKey, FromValue> by MapperWriteKeyValueRepo(to, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
|
||||
): KeyValueRepo<FromKey, FromValue> = MapperKeyValueRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): KeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
@@ -1,15 +1,14 @@
|
||||
package dev.inmo.micro_utils.repos.mappers
|
||||
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadOneToManyKeyValueRepo<ToKey, ToValue>,
|
||||
open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadKeyValuesRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : ReadOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
) : ReadKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
override suspend fun get(
|
||||
k: FromKey,
|
||||
pagination: Pagination,
|
||||
@@ -19,11 +18,8 @@ open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerValue() },
|
||||
it.size
|
||||
it.changeResultsUnchecked(
|
||||
it.results.map { it.toInnerValue() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,11 +30,8 @@ open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
it.changeResultsUnchecked(
|
||||
it.results.map { it.toInnerKey() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,11 +44,8 @@ open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
pagination,
|
||||
reversed
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.map { it.toInnerKey() },
|
||||
it.size
|
||||
it.changeResultsUnchecked(
|
||||
it.results.map { it.toInnerKey() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,24 +57,24 @@ open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
|
||||
): ReadKeyValuesRepo<FromKey, FromValue> = MapperReadKeyValuesRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): ReadKeyValuesRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
|
||||
open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteKeyValuesRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : WriteOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
) : WriteKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
|
||||
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
|
||||
k.toInnerKey() to v.toInnerValue()
|
||||
}
|
||||
@@ -118,40 +108,40 @@ open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
|
||||
): WriteKeyValuesRepo<FromKey, FromValue> = MapperWriteKeyValuesRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): WriteKeyValuesRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
|
||||
open class MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: KeyValuesRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
) : OneToManyKeyValueRepo<FromKey, FromValue>,
|
||||
) : KeyValuesRepo<FromKey, FromValue>,
|
||||
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
|
||||
ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
|
||||
WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
|
||||
ReadKeyValuesRepo<FromKey, FromValue> by MapperReadKeyValuesRepo(to, mapper),
|
||||
WriteKeyValuesRepo<FromKey, FromValue> by MapperWriteKeyValuesRepo(to, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
|
||||
): KeyValuesRepo<FromKey, FromValue> = MapperKeyValuesRepo(this, mapper)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
|
||||
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
|
||||
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper(
|
||||
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
|
||||
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
|
||||
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
|
||||
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
|
||||
): KeyValuesRepo<FromKey, FromValue> = withMapper(
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
@@ -1,11 +1,10 @@
|
||||
package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
|
||||
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
|
||||
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
||||
): List<T> = getAllWithNextPaging {
|
||||
|
@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll(
|
||||
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
): List<Pair<Key, Value>> = getAllWithNextPaging {
|
||||
|
@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll(
|
||||
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
): List<Pair<Key, List<Value>>> = getAllWithNextPaging {
|
||||
|
@@ -1,10 +1,10 @@
|
||||
package dev.inmo.micro_utils.repos.versions
|
||||
|
||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.set
|
||||
|
||||
class KeyValueBasedVersionsRepoProxy<T>(
|
||||
private val keyValueStore: StandardKeyValueRepo<String, Int>,
|
||||
private val keyValueStore: KeyValueRepo<String, Int>,
|
||||
override val database: T
|
||||
) : StandardVersionsRepoProxy<T> {
|
||||
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)
|
||||
|
@@ -13,9 +13,9 @@ import java.nio.file.StandardWatchEventKinds.*
|
||||
private inline val String.isAbsolute
|
||||
get() = startsWith(File.separator)
|
||||
|
||||
class FileReadStandardKeyValueRepo(
|
||||
class FileReadKeyValueRepo(
|
||||
private val folder: File
|
||||
) : ReadStandardKeyValueRepo<String, File> {
|
||||
) : ReadKeyValueRepo<String, File> {
|
||||
init {
|
||||
folder.mkdirs()
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class FileReadStandardKeyValueRepo(
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
|
||||
val count = count()
|
||||
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
|
||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
|
||||
if (reversed) {
|
||||
filesPaths.reverse()
|
||||
}
|
||||
@@ -44,7 +44,7 @@ class FileReadStandardKeyValueRepo(
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
|
||||
val count = count()
|
||||
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
|
||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
|
||||
if (reversed) {
|
||||
filesPaths.reverse()
|
||||
}
|
||||
@@ -83,17 +83,19 @@ class FileReadStandardKeyValueRepo(
|
||||
* Files watching will not correctly works on Android with version of API lower than API 26
|
||||
*/
|
||||
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
||||
class FileWriteStandardKeyValueRepo(
|
||||
class FileWriteKeyValueRepo(
|
||||
private val folder: File,
|
||||
filesChangedProcessingScope: CoroutineScope? = null
|
||||
) : WriteStandardKeyValueRepo<String, File> {
|
||||
) : WriteKeyValueRepo<String, File> {
|
||||
private val _onNewValue = MutableSharedFlow<Pair<String, File>>()
|
||||
override val onNewValue: Flow<Pair<String, File>> = _onNewValue.asSharedFlow()
|
||||
private val _onValueRemoved = MutableSharedFlow<String>()
|
||||
override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow()
|
||||
|
||||
init {
|
||||
folder.mkdirs()
|
||||
if (!folder.mkdirs() && !folder.exists()) {
|
||||
error("Unable to create folder ${folder.absolutePath}")
|
||||
}
|
||||
filesChangedProcessingScope ?.let {
|
||||
it.launch {
|
||||
try {
|
||||
@@ -138,15 +140,17 @@ class FileWriteStandardKeyValueRepo(
|
||||
}
|
||||
|
||||
override suspend fun set(toSet: Map<String, File>) {
|
||||
supervisorScope {
|
||||
val scope = CoroutineScope(currentCoroutineContext())
|
||||
toSet.map { (filename, fileSource) ->
|
||||
launch {
|
||||
scope.launch {
|
||||
val file = File(folder, filename)
|
||||
|
||||
file.delete()
|
||||
fileSource.copyTo(file, overwrite = true)
|
||||
_onNewValue.emit(filename to file)
|
||||
if (!file.exists()) {
|
||||
error("Can't create file $file with new content")
|
||||
}
|
||||
_onNewValue.emit(filename to file)
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
@@ -176,10 +180,10 @@ class FileWriteStandardKeyValueRepo(
|
||||
|
||||
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
class FileStandardKeyValueRepo(
|
||||
class FileKeyValueRepo(
|
||||
folder: File,
|
||||
filesChangedProcessingScope: CoroutineScope? = null
|
||||
) : StandardKeyValueRepo<String, File>,
|
||||
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
|
||||
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder) {
|
||||
) : KeyValueRepo<String, File>,
|
||||
WriteKeyValueRepo<String, File> by FileWriteKeyValueRepo(folder, filesChangedProcessingScope),
|
||||
ReadKeyValueRepo<String, File> by FileReadKeyValueRepo(folder) {
|
||||
}
|
@@ -12,7 +12,7 @@ val <T> T.asId: String
|
||||
|
||||
abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
|
||||
protected val helper: StandardSQLHelper
|
||||
) : ReadStandardCRUDRepo<ObjectType, IdType> {
|
||||
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||
protected abstract val tableName: String
|
||||
protected abstract val idColumnName: String
|
||||
protected abstract suspend fun Cursor.toObject(): ObjectType
|
||||
|
@@ -9,9 +9,9 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
||||
helper: StandardSQLHelper,
|
||||
replyInFlows: Int = 0,
|
||||
extraBufferCapacityInFlows: Int = 64
|
||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
|
||||
StandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||
CRUDRepo<ObjectType, IdType, InputValueType> {
|
||||
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
|
||||
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
|
||||
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyInFlows, extraBufferCapacityInFlows)
|
||||
|
@@ -3,11 +3,10 @@ package dev.inmo.micro_utils.repos.keyvalue
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
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.pagination.utils.paginate
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
private val cache = HashMap<String, KeyValueStore<*>>()
|
||||
@@ -15,10 +14,10 @@ private val cache = HashMap<String, KeyValueStore<*>>()
|
||||
fun <T : Any> Context.keyValueStore(
|
||||
name: String = "default",
|
||||
cacheValues: Boolean = false
|
||||
): StandardKeyValueRepo<String, T> {
|
||||
): 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>
|
||||
}
|
||||
|
||||
@@ -26,7 +25,7 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
c: Context,
|
||||
preferencesName: String,
|
||||
useCache: Boolean = false
|
||||
) : SharedPreferences.OnSharedPreferenceChangeListener, StandardKeyValueRepo<String, T> {
|
||||
) : SharedPreferences.OnSharedPreferenceChangeListener, KeyValueRepo<String, T> {
|
||||
private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
|
||||
|
||||
private val cachedData = if (useCache) {
|
||||
@@ -72,14 +71,11 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
return sharedPreferences.all.values.paginate(
|
||||
resultPagination
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.changeResultsUnchecked(
|
||||
it.results.map {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
it as T
|
||||
}.let { if (reversed) it.reversed() else it },
|
||||
it.size
|
||||
}.let { if (reversed) it.reversed() else it }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -89,11 +85,8 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
return sharedPreferences.all.keys.paginate(
|
||||
resultPagination
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.let { if (reversed) it.reversed() else it },
|
||||
it.size
|
||||
it.changeResultsUnchecked(
|
||||
it.results.let { if (reversed) it.reversed() else it }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -103,11 +96,8 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
return sharedPreferences.all.mapNotNull { (k, value) -> if (value == v) k else null }.paginate(
|
||||
resultPagination
|
||||
).let {
|
||||
PaginationResult(
|
||||
it.page,
|
||||
it.pagesNumber,
|
||||
it.results.let { if (reversed) it.reversed() else it },
|
||||
it.size
|
||||
it.changeResultsUnchecked(
|
||||
it.results.let { if (reversed) it.reversed() else it }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -159,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(
|
||||
@@ -166,3 +164,5 @@ inline fun <T : Any> SharedPreferencesKeyValueRepo(
|
||||
name: String = "default",
|
||||
cacheValues: Boolean = false
|
||||
) = context.keyValueStore<T>(name, cacheValues)
|
||||
|
||||
typealias KeyValueSPRepo<T> = KeyValueStore<T>
|
||||
|
@@ -9,7 +9,6 @@ import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@@ -26,7 +25,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
private val keyFromString: String.() -> Key,
|
||||
private val valueFromString: String.() -> Value,
|
||||
private val helper: SQLiteOpenHelper
|
||||
) : OneToManyKeyValueRepo<Key, Value> {
|
||||
) : KeyValuesRepo<Key, Value> {
|
||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
|
@@ -4,13 +4,11 @@ package dev.inmo.micro_utils.repos.versions
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import androidx.core.content.contentValuesOf
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.keyvalue.keyValueStore
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/**
|
||||
* Will create [VersionsRepo] based on [T], but versions will be stored in [StandardKeyValueRepo]
|
||||
* Will create [VersionsRepo] based on [T], but versions will be stored in [KeyValueRepo]
|
||||
*
|
||||
* @receiver Will be used to create [KeyValueBasedVersionsRepoProxy] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
||||
*
|
||||
@@ -26,9 +24,9 @@ inline fun <T> Context.versionsKeyValueRepo(
|
||||
)
|
||||
)
|
||||
/**
|
||||
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
|
||||
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [KeyValueRepo]
|
||||
*
|
||||
* @receiver Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
||||
* @receiver Will be used to create [KeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
||||
*
|
||||
* @see [keyValueStore]
|
||||
*/
|
||||
@@ -37,9 +35,9 @@ inline fun Context.versionsKeyValueRepoForSQL(
|
||||
) = versionsKeyValueRepo(database)
|
||||
|
||||
/**
|
||||
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
|
||||
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [KeyValueRepo]
|
||||
*
|
||||
* @param context Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
||||
* @param context Will be used to create [KeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
|
||||
*
|
||||
* @see [keyValueStore]
|
||||
*/
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package dev.inmo.micro_utils.repos.exposed
|
||||
|
||||
import dev.inmo.micro_utils.repos.StandardCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.CRUDRepo
|
||||
|
||||
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
flowsChannelsSize: Int = 0,
|
||||
@@ -11,4 +11,4 @@ abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
tableName
|
||||
),
|
||||
ExposedCRUDRepo<ObjectType, IdType>,
|
||||
StandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
CRUDRepo<ObjectType, IdType, InputValueType>
|
||||
|
@@ -1,14 +1,14 @@
|
||||
package dev.inmo.micro_utils.repos.exposed
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
|
||||
tableName: String
|
||||
) :
|
||||
ReadStandardCRUDRepo<ObjectType, IdType>,
|
||||
ReadCRUDRepo<ObjectType, IdType>,
|
||||
ExposedCRUDRepo<ObjectType, IdType>,
|
||||
Table(tableName)
|
||||
{
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.exposed
|
||||
|
||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
@@ -15,24 +15,17 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
) :
|
||||
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
|
||||
ExposedCRUDRepo<ObjectType, IdType>,
|
||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
WriteCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
{
|
||||
protected val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
||||
protected val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
|
||||
protected val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize)
|
||||
@Deprecated("Renamed", ReplaceWith("_newObjectsFlow"))
|
||||
protected val newObjectsChannel = _newObjectsFlow
|
||||
@Deprecated("Renamed", ReplaceWith("_updatedObjectsFlow"))
|
||||
protected val updateObjectsChannel = _updatedObjectsFlow
|
||||
@Deprecated("Renamed", ReplaceWith("_deletedObjectsIdsFlow"))
|
||||
protected val deleteObjectsIdsChannel = _deletedObjectsIdsFlow
|
||||
|
||||
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
||||
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.exposed.keyvalue
|
||||
|
||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||
import dev.inmo.micro_utils.repos.exposed.initTable
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
@@ -13,7 +11,7 @@ open class ExposedKeyValueRepo<Key, Value>(
|
||||
keyColumnAllocator: ColumnAllocator<Key>,
|
||||
valueColumnAllocator: ColumnAllocator<Value>,
|
||||
tableName: String? = null
|
||||
) : StandardKeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
|
||||
) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
|
||||
database,
|
||||
keyColumnAllocator,
|
||||
valueColumnAllocator,
|
||||
|
@@ -1,8 +1,9 @@
|
||||
package dev.inmo.micro_utils.repos.exposed.keyvalue
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||
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
|
||||
|
||||
@@ -11,7 +12,7 @@ open class ExposedReadKeyValueRepo<Key, Value>(
|
||||
keyColumnAllocator: ColumnAllocator<Key>,
|
||||
valueColumnAllocator: ColumnAllocator<Value>,
|
||||
tableName: String? = null
|
||||
) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
||||
@@ -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 {
|
||||
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()
|
||||
}
|
||||
}
|
@@ -1,18 +1,18 @@
|
||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||
|
||||
import dev.inmo.micro_utils.repos.OneToManyKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
typealias ExposedKeyValuesRepo<Key, Value> = ExposedOneToManyKeyValueRepo<Key, Value>
|
||||
open class ExposedOneToManyKeyValueRepo<Key, Value>(
|
||||
typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value>
|
||||
open class ExposedKeyValuesRepo<Key, Value>(
|
||||
database: Database,
|
||||
keyColumnAllocator: ColumnAllocator<Key>,
|
||||
valueColumnAllocator: ColumnAllocator<Value>,
|
||||
tableName: String? = null
|
||||
) : OneToManyKeyValueRepo<Key, Value>, ExposedReadOneToManyKeyValueRepo<Key, Value>(
|
||||
) : KeyValuesRepo<Key, Value>, ExposedReadKeyValuesRepo<Key, Value>(
|
||||
database,
|
||||
keyColumnAllocator,
|
||||
valueColumnAllocator,
|
@@ -1,20 +1,19 @@
|
||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.*
|
||||
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedReadKeyValueRepo
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
typealias ExposedReadKeyValuesRepo<Key, Value> = ExposedReadOneToManyKeyValueRepo<Key, Value>
|
||||
typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value>
|
||||
|
||||
open class ExposedReadOneToManyKeyValueRepo<Key, Value>(
|
||||
open class ExposedReadKeyValuesRepo<Key, Value>(
|
||||
override val database: Database,
|
||||
keyColumnAllocator: ColumnAllocator<Key>,
|
||||
valueColumnAllocator: ColumnAllocator<Value>,
|
||||
tableName: String? = null
|
||||
) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||
|
@@ -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,7 +5,7 @@ import kotlinx.coroutines.flow.*
|
||||
|
||||
class ReadMapCRUDRepo<ObjectType, IdType>(
|
||||
private val map: Map<IdType, ObjectType> = emptyMap()
|
||||
) : ReadStandardCRUDRepo<ObjectType, IdType> {
|
||||
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> {
|
||||
return map.keys.drop(pagination.firstIndex).take(pagination.size).mapNotNull {
|
||||
map[it]
|
||||
@@ -24,7 +24,7 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
|
||||
|
||||
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||
) : WriteCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||
protected val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
||||
protected val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
||||
@@ -68,25 +68,30 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
|
||||
abstract class MapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
map: MutableMap<IdType, ObjectType>
|
||||
) : StandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
ReadStandardCRUDRepo<ObjectType, IdType> by ReadMapCRUDRepo(map),
|
||||
) : CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
ReadCRUDRepo<ObjectType, IdType> by ReadMapCRUDRepo(map),
|
||||
WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(map)
|
||||
|
||||
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
||||
map: MutableMap<IdType, ObjectType>,
|
||||
updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||
createCallback: suspend (newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||
) = object : MapCRUDRepo<ObjectType, IdType, InputValueType>(map) {
|
||||
override suspend fun updateObject(
|
||||
newValue: InputValueType,
|
||||
id: IdType,
|
||||
old: ObjectType
|
||||
): ObjectType = updateCallback(newValue, id, old)
|
||||
): ObjectType = map.updateCallback(newValue, id, old)
|
||||
|
||||
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = createCallback(newValue)
|
||||
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = map.createCallback(newValue)
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
||||
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||
) = MapCRUDRepo(mutableMapOf(), updateCallback, createCallback)
|
||||
|
||||
fun <ObjectType, IdType, InputValueType> MutableMap<IdType, ObjectType>.asCrudRepo(
|
||||
updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||
createCallback: suspend (newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||
) = MapCRUDRepo(this, updateCallback, createCallback)
|
||||
|
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
class ReadMapKeyValueRepo<Key, Value>(
|
||||
protected val map: Map<Key, Value> = emptyMap()
|
||||
) : ReadStandardKeyValueRepo<Key, Value> {
|
||||
) : ReadKeyValueRepo<Key, Value> {
|
||||
override suspend fun get(k: Key): Value? = map[k]
|
||||
|
||||
override suspend fun values(
|
||||
@@ -58,7 +58,7 @@ class ReadMapKeyValueRepo<Key, Value>(
|
||||
|
||||
class WriteMapKeyValueRepo<Key, Value>(
|
||||
private val map: MutableMap<Key, Value> = mutableMapOf()
|
||||
) : WriteStandardKeyValueRepo<Key, Value> {
|
||||
) : WriteKeyValueRepo<Key, Value> {
|
||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
override val onNewValue: Flow<Pair<Key, Value>>
|
||||
get() = _onNewValue
|
||||
@@ -78,19 +78,20 @@ class WriteMapKeyValueRepo<Key, Value>(
|
||||
}
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||
map.forEach {
|
||||
if (it.value in toUnset) {
|
||||
map.remove(it.key)
|
||||
_onValueRemoved.emit(it.key)
|
||||
}
|
||||
map.mapNotNull { (k, v) ->
|
||||
k.takeIf { v in toUnset }
|
||||
}.forEach {
|
||||
map.remove(it)
|
||||
_onValueRemoved.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
class MapKeyValueRepo<Key, Value>(
|
||||
private val map: MutableMap<Key, Value> = mutableMapOf()
|
||||
) : StandardKeyValueRepo<Key, Value>,
|
||||
ReadStandardKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map),
|
||||
WriteStandardKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map)
|
||||
) : KeyValueRepo<Key, Value>,
|
||||
ReadKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map),
|
||||
WriteKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map)
|
||||
|
||||
fun <K, V> MutableMap<K, V>.asKeyValueRepo(): StandardKeyValueRepo<K, V> = MapKeyValueRepo(this)
|
||||
fun <K, V> MutableMap<K, V>.asKeyValueRepo(): KeyValueRepo<K, V> = MapKeyValueRepo(this)
|
||||
|
@@ -5,9 +5,9 @@ import dev.inmo.micro_utils.pagination.utils.paginate
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class MapReadOneToManyKeyValueRepo<Key, Value>(
|
||||
class MapReadKeyValuesRepo<Key, Value>(
|
||||
private val map: Map<Key, List<Value>> = emptyMap()
|
||||
) : ReadOneToManyKeyValueRepo<Key, Value> {
|
||||
) : ReadKeyValuesRepo<Key, Value> {
|
||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||
val list = map[k] ?: return emptyPaginationResult()
|
||||
|
||||
@@ -53,9 +53,9 @@ class MapReadOneToManyKeyValueRepo<Key, Value>(
|
||||
override suspend fun count(): Long = map.size.toLong()
|
||||
}
|
||||
|
||||
class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
||||
class MapWriteKeyValuesRepo<Key, Value>(
|
||||
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
||||
) : WriteOneToManyKeyValueRepo<Key, Value> {
|
||||
) : WriteKeyValuesRepo<Key, Value> {
|
||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
@@ -80,6 +80,10 @@ class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
||||
_onValueRemoved.emit(k to v)
|
||||
}
|
||||
}
|
||||
if (map[k] ?.isEmpty() == true) {
|
||||
map.remove(k)
|
||||
_onDataCleared.emit(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,12 +98,13 @@ class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
class MapOneToManyKeyValueRepo<Key, Value>(
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
class MapKeyValuesRepo<Key, Value>(
|
||||
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
||||
) : OneToManyKeyValueRepo<Key, Value>,
|
||||
ReadOneToManyKeyValueRepo<Key, Value> by MapReadOneToManyKeyValueRepo(map),
|
||||
WriteOneToManyKeyValueRepo<Key, Value> by MapWriteOneToManyKeyValueRepo(map)
|
||||
) : KeyValuesRepo<Key, Value>,
|
||||
ReadKeyValuesRepo<Key, Value> by MapReadKeyValuesRepo(map),
|
||||
WriteKeyValuesRepo<Key, Value> by MapWriteKeyValuesRepo(map)
|
||||
|
||||
fun <K, V> MutableMap<K, List<V>>.asOneToManyKeyValueRepo(): OneToManyKeyValueRepo<K, V> = MapOneToManyKeyValueRepo(
|
||||
fun <K, V> MutableMap<K, List<V>>.asKeyValuesRepo(): KeyValuesRepo<K, V> = MapKeyValuesRepo(
|
||||
map { (k, v) -> k to v.toMutableList() }.toMap().toMutableMap()
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user