mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
160 Commits
Author | SHA1 | Date | |
---|---|---|---|
64ee899b84 | |||
e0e0c1658b | |||
2c586f667c | |||
64164ef6c1 | |||
22343c0731 | |||
f4ec1a4c60 | |||
c1c33cceb1 | |||
a3e975b2ba | |||
06e705a687 | |||
d56eb6c867 | |||
9cbca864e3 | |||
abb4378694 | |||
0eb698d9a4 | |||
15ea9f2093 | |||
d47aca0923 | |||
1ac50e9959 | |||
6adfbe3a96 | |||
59f36e62e9 | |||
54af116009 | |||
38fbec8e3b | |||
babbfc55e4 | |||
2511e18d69 | |||
29658c70a0 | |||
96311ee43d | |||
bd33b09052 | |||
8055003b47 | |||
1257492f85 | |||
1107b7f4ef | |||
a1a1171240 | |||
46c02e5df1 | |||
2e9efc57de | |||
acecadef17 | |||
19394b5e69 | |||
de999e197f | |||
9d95687d3c | |||
aa9dfb4ab8 | |||
9c5b44efb3 | |||
ac587a67e6 | |||
59428140a8 | |||
60bdb59d71 | |||
be52871de8 | |||
b7934cf357 | |||
dbfbeef90a | |||
00943c9cdf | |||
8745c6a16a | |||
433ba4b58f | |||
d40376e524 | |||
a2982f88f5 | |||
1642f7abd9 | |||
a10d2184ff | |||
522435f096 | |||
79b30290c0 | |||
f8b8626859 | |||
b061b85a08 | |||
3870db1c88 | |||
1be1070eb4 | |||
2696e663cf | |||
1e1f7df86d | |||
1d8ded8fd3 | |||
197825123a | |||
422b2e6db1 | |||
1973e0b5bf | |||
8258cf93a9 | |||
1d49bd5947 | |||
44317d1519 | |||
48e08fcc69 | |||
1a3ce6e623 | |||
fb122f3e70 | |||
7cca6039cc | |||
118e3dba39 | |||
87070710fa | |||
38499c3d4a | |||
5d754d968b | |||
d543d436bc | |||
12b54f99af | |||
2a95d7e643 | |||
e3d3cacfa4 | |||
4b13491a0e | |||
85d516d1e9 | |||
ac58b6a7e3 | |||
2cc6126765 | |||
f94b085850 | |||
c9822a491b | |||
23b2d60295 | |||
f4bc9eed39 | |||
e310f188b0 | |||
6c1571188c | |||
945d2fa284 | |||
020095f1ff | |||
b165a76e62 | |||
03c8830672 | |||
38448da89b | |||
2ade5aff91 | |||
29bf6e80ec | |||
027e927e1b | |||
fa061f88e2 | |||
1d01b65b5f | |||
2c2b364167 | |||
7c5fc9bf7c | |||
193d22ff20 | |||
0f3b553ba2 | |||
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 |
@@ -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:
|
||||
|
236
CHANGELOG.md
236
CHANGELOG.md
@@ -1,5 +1,241 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.1
|
||||
|
||||
* `Versions`:
|
||||
* `Klock`: `3.3.1` -> `3.4.0`
|
||||
* `UUID`: `0.5.0` -> `0.6.0`
|
||||
|
||||
## 0.14.0
|
||||
|
||||
**ALL DEPRECATIONS HAVE BEEN REMOVED**
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.7.10` -> `1.7.20`
|
||||
* `Klock`: `3.3.0` -> `3.3.1`
|
||||
* `Compose`: `1.2.0` -> `1.2.1`
|
||||
* `Exposed`: `0.39.2` -> `0.40.1`
|
||||
|
||||
## 0.13.2
|
||||
|
||||
* `Versions`:
|
||||
* `Klock`: `3.1.0` -> `3.3.0`
|
||||
* `Ktor`: `2.1.2` -> `2.1.3`
|
||||
|
||||
## 0.13.1
|
||||
|
||||
* `Repos`:
|
||||
* `Exposed`:
|
||||
* `AbstractExposedWriteCRUDRepo#createAndInsertId` now is optional and returns nullable value
|
||||
|
||||
## 0.13.0
|
||||
|
||||
**ALL DEPRECATIONS HAVE BEEN REMOVED**
|
||||
**A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED**
|
||||
|
||||
* `Repos`:
|
||||
* `Exposed`:
|
||||
* `AbstractExposedWriteCRUDRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `createAndInsertId`
|
||||
* Old `update` method has been deprecated and not recommended to override anymore in realizations
|
||||
* Old `insert` method now is `open` instead of `abstract` and can be omitted
|
||||
* `AbstractExposedKeyValueRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `insertKey`
|
||||
* Old `update` method has been deprecated and not recommended to override anymore
|
||||
* Old `insert` method now is `open` instead of `abstract` and can be omitted in realizations
|
||||
|
||||
## 0.12.17
|
||||
|
||||
* `Versions`:
|
||||
* `JB Compose`: `1.2.0-alpha01-dev774` -> `1.2.0-beta02`
|
||||
* `Ktor`: `2.1.1` -> `2.1.2`
|
||||
* `Koin`: `3.2.0` -> `3.2.2`
|
||||
|
||||
## 0.12.16
|
||||
|
||||
* `Coroutines`:
|
||||
* `Android`:
|
||||
* Add class `FlowOnHierarchyChangeListener`
|
||||
* Add `ViewGroup#setOnHierarchyChangeListenerRecursively(OnHierarchyChangeListener)`
|
||||
|
||||
## 0.12.15
|
||||
|
||||
* `Common`:
|
||||
* `applyDiff` will return `Diff` object since this release
|
||||
* `Android`:
|
||||
* New functions/extensions `findViewsByTag` and `findViewsByTagInActivity`
|
||||
* `Coroutines`:
|
||||
* Add `Flow` extensions `flatMap`, `flatMapNotNull` and `flatten`
|
||||
* Add `Flow` extensions `takeNotNull` and `filterNotNull`
|
||||
|
||||
## 0.12.14
|
||||
|
||||
* `Versions`:
|
||||
* `Android CoreKTX`: `1.8.0` -> `1.9.0`
|
||||
* `Android AppCompat`: `1.4.2` -> `1.5.1`
|
||||
* Android Compile SDK: 32 -> 33
|
||||
* Android Build Tools: 32.0.0 -> 33.0.0
|
||||
* `Common`:
|
||||
* `Android`:
|
||||
* Add `argumentOrNull`/`argumentOrThrow` delegates for fragments
|
||||
* `Coroutines`:
|
||||
* Rewrite `awaitFirstWithDeferred` onto `CompletableDeferred` instead of coroutines suspending
|
||||
|
||||
## 0.12.13
|
||||
|
||||
* `Coroutines`:
|
||||
* Add opportunity to use markers in actors (solution of [#160](https://github.com/InsanusMokrassar/MicroUtils/issues/160))
|
||||
* `Koin`:
|
||||
* Module inited :)
|
||||
* `Repos`:
|
||||
* `Android`:
|
||||
* Add typealias `KeyValueSPRepo` and opportunity to create shared preferences `KeyValue` repo with `KeyValueStore(...)` (fix of [#155](https://github.com/InsanusMokrassar/MicroUtils/issues/155))
|
||||
|
||||
## 0.12.12
|
||||
|
||||
* `Common`:
|
||||
* `Compose`:
|
||||
* `JS`:
|
||||
* Add `SkeletonAnimation` stylesheet
|
||||
|
||||
## 0.12.11
|
||||
|
||||
* `Repos`:
|
||||
* `Cache`:
|
||||
* Override `KeyValue` cache method `values`
|
||||
|
||||
## 0.12.10
|
||||
|
||||
* `Repos`:
|
||||
* `Cache`:
|
||||
* Hotfix in key values `get`
|
||||
|
||||
## 0.12.9
|
||||
|
||||
* `Versions`:
|
||||
* `Klock`: `3.0.0` -> `3.1.0`
|
||||
* `Repos`:
|
||||
* `Cache`:
|
||||
* Fixes in key values cache
|
||||
|
||||
## 0.12.8
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `2.1.0` -> `2.1.1`
|
||||
* `Compose`: `1.2.0-alpha01-dev764` -> `1.2.0-alpha01-dev774`
|
||||
* `Ktor`:
|
||||
* `Client`:
|
||||
* New extension `HttpClient#bodyOrNull` which returns `null` in case when server responded with `No Content` (204)
|
||||
* `Server`:
|
||||
* New extension `ApplicationCall#respondOrNoContent` which responds `No Content` (204) when passed data is null
|
||||
|
||||
## 0.12.7
|
||||
|
||||
* `Repos`:
|
||||
* `Cache`:
|
||||
* Force `WriteCRUDCacheRepo` to subscribe on new and updated objects of parent repo
|
||||
* `Pagination`:
|
||||
* New function `changeResultsUnchecked(Pagination)`
|
||||
|
||||
## 0.12.6
|
||||
|
||||
* `MimeeTypes>`:
|
||||
* Fixed absence of `image/*` in known mime types
|
||||
|
||||
## 0.12.5
|
||||
|
||||
* `Repos`:
|
||||
* `Exposed`:
|
||||
* Fixes in `paginate` extensions
|
||||
|
||||
## 0.12.4
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.7.0` -> `1.7.10`
|
||||
* `Compose`: `1.2.0-alpha01-dev755` -> `1.2.0-alpha01-dev764`
|
||||
|
||||
## 0.12.3
|
||||
|
||||
* `Repos`:
|
||||
* `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`:
|
||||
|
@@ -16,6 +16,7 @@ kotlin {
|
||||
androidMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.coroutines")
|
||||
api libs.android.fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,43 @@
|
||||
package dev.inmo.micro_utils.common.compose
|
||||
|
||||
import org.jetbrains.compose.web.css.*
|
||||
|
||||
object SkeletonAnimation : StyleSheet() {
|
||||
val skeletonKeyFrames: CSSNamedKeyframes by keyframes {
|
||||
to { backgroundPosition("-20% 0") }
|
||||
}
|
||||
|
||||
fun CSSBuilder.includeSkeletonStyle(
|
||||
duration: CSSSizeValue<out CSSUnitTime> = 2.s,
|
||||
timingFunction: AnimationTimingFunction = AnimationTimingFunction.EaseInOut,
|
||||
iterationCount: Int? = null,
|
||||
direction: AnimationDirection = AnimationDirection.Normal,
|
||||
keyFrames: CSSNamedKeyframes = skeletonKeyFrames,
|
||||
hideChildren: Boolean = true,
|
||||
hideText: Boolean = hideChildren
|
||||
) {
|
||||
backgroundImage("linear-gradient(110deg, rgb(236, 236, 236) 40%, rgb(245, 245, 245) 50%, rgb(236, 236, 236) 65%)")
|
||||
backgroundSize("200% 100%")
|
||||
backgroundPosition("180% 0")
|
||||
animation(keyFrames) {
|
||||
duration(duration)
|
||||
timingFunction(timingFunction)
|
||||
iterationCount(iterationCount)
|
||||
direction(direction)
|
||||
}
|
||||
if (hideText) {
|
||||
property("color", "${Color.transparent} !important")
|
||||
}
|
||||
|
||||
if (hideChildren) {
|
||||
child(self, universal) style {
|
||||
property("visibility", "hidden")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val skeleton by style {
|
||||
includeSkeletonStyle()
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,14 @@ private inline fun <T> getObject(
|
||||
/**
|
||||
* Diff object which contains information about differences between two [Iterable]s
|
||||
*
|
||||
* See tests for more info
|
||||
*
|
||||
* @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection
|
||||
* @param added The object which appear in new collection only. Indexes here show the index in the new collection
|
||||
* @param replaced Pair of old-new changes. First object has been presented in the old collection on its
|
||||
* [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in
|
||||
* case when some value has been replaced after adds or removes in original collection the object index will be changed
|
||||
*
|
||||
* @see calculateDiff
|
||||
*/
|
||||
data class Diff<T> internal constructor(
|
||||
@@ -165,7 +173,7 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
|
||||
fun <T> MutableList<T>.applyDiff(
|
||||
source: Iterable<T>,
|
||||
strictComparison: Boolean = false
|
||||
) = calculateDiff(source, strictComparison).let {
|
||||
): Diff<T> = calculateDiff(source, strictComparison).also {
|
||||
for (i in it.removed.indices.sortedDescending()) {
|
||||
removeAt(it.removed[i].index)
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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,76 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import androidx.fragment.app.Fragment
|
||||
import java.io.Serializable
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
object ArgumentPropertyNullableDelegate {
|
||||
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T? {
|
||||
val arguments = thisRef.arguments ?: return null
|
||||
val key = property.name
|
||||
return when (property.getter.returnType.classifier) {
|
||||
// Scalars
|
||||
String::class -> arguments.getString(key)
|
||||
Boolean::class -> arguments.getBoolean(key)
|
||||
Byte::class -> arguments.getByte(key)
|
||||
Char::class -> arguments.getChar(key)
|
||||
Double::class -> arguments.getDouble(key)
|
||||
Float::class -> arguments.getFloat(key)
|
||||
Int::class -> arguments.getInt(key)
|
||||
Long::class -> arguments.getLong(key)
|
||||
Short::class -> arguments.getShort(key)
|
||||
|
||||
// References
|
||||
Bundle::class -> arguments.getBundle(key)
|
||||
CharSequence::class -> arguments.getCharSequence(key)
|
||||
Parcelable::class -> arguments.getParcelable(key)
|
||||
|
||||
// Scalar arrays
|
||||
BooleanArray::class -> arguments.getBooleanArray(key)
|
||||
ByteArray::class -> arguments.getByteArray(key)
|
||||
CharArray::class -> arguments.getCharArray(key)
|
||||
DoubleArray::class -> arguments.getDoubleArray(key)
|
||||
FloatArray::class -> arguments.getFloatArray(key)
|
||||
IntArray::class -> arguments.getIntArray(key)
|
||||
LongArray::class -> arguments.getLongArray(key)
|
||||
ShortArray::class -> arguments.getShortArray(key)
|
||||
Array::class -> {
|
||||
val componentType = property.returnType.classifier ?.javaClass ?.componentType!!
|
||||
@Suppress("UNCHECKED_CAST") // Checked by reflection.
|
||||
when {
|
||||
Parcelable::class.java.isAssignableFrom(componentType) -> {
|
||||
arguments.getParcelableArray(key)
|
||||
}
|
||||
String::class.java.isAssignableFrom(componentType) -> {
|
||||
arguments.getStringArray(key)
|
||||
}
|
||||
CharSequence::class.java.isAssignableFrom(componentType) -> {
|
||||
arguments.getCharSequenceArray(key)
|
||||
}
|
||||
Serializable::class.java.isAssignableFrom(componentType) -> {
|
||||
arguments.getSerializable(key)
|
||||
}
|
||||
else -> {
|
||||
val valueType = componentType.canonicalName
|
||||
throw IllegalArgumentException(
|
||||
"Illegal value array type $valueType for key \"$key\""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Serializable::class -> arguments.getSerializable(key)
|
||||
else -> null
|
||||
} as? T
|
||||
}
|
||||
}
|
||||
|
||||
object ArgumentPropertyNonNullableDelegate {
|
||||
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T {
|
||||
return ArgumentPropertyNullableDelegate.getValue<T>(thisRef, property)!!
|
||||
}
|
||||
}
|
||||
|
||||
fun argumentOrNull() = ArgumentPropertyNullableDelegate
|
||||
fun argumentOrThrow() = ArgumentPropertyNonNullableDelegate
|
@@ -0,0 +1,61 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
fun findViewsByTag(viewGroup: ViewGroup, tag: Any?): List<View> {
|
||||
return viewGroup.children.flatMap {
|
||||
findViewsByTag(it, tag)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
fun findViewsByTag(viewGroup: ViewGroup, key: Int, tag: Any?): List<View> {
|
||||
return viewGroup.children.flatMap {
|
||||
findViewsByTag(it, key, tag)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
fun findViewsByTag(view: View, tag: Any?): List<View> {
|
||||
val result = mutableListOf<View>()
|
||||
if (view.tag == tag) {
|
||||
result.add(view)
|
||||
}
|
||||
if (view is ViewGroup) {
|
||||
result.addAll(findViewsByTag(view, tag))
|
||||
}
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
fun findViewsByTag(view: View, key: Int, tag: Any?): List<View> {
|
||||
val result = mutableListOf<View>()
|
||||
if (view.getTag(key) == tag) {
|
||||
result.add(view)
|
||||
}
|
||||
if (view is ViewGroup) {
|
||||
result.addAll(findViewsByTag(view, key, tag))
|
||||
}
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
fun Activity.findViewsByTag(tag: Any?) = rootView ?.let {
|
||||
findViewsByTag(it, tag)
|
||||
}
|
||||
|
||||
fun Activity.findViewsByTag(key: Int, tag: Any?) = rootView ?.let {
|
||||
findViewsByTag(it, key, tag)
|
||||
}
|
||||
|
||||
fun Fragment.findViewsByTag(tag: Any?) = view ?.let {
|
||||
findViewsByTag(it, tag)
|
||||
}
|
||||
|
||||
fun Fragment.findViewsByTag(key: Int, tag: Any?) = view ?.let {
|
||||
findViewsByTag(it, key, tag)
|
||||
}
|
||||
|
||||
fun Fragment.findViewsByTagInActivity(tag: Any?) = activity ?.findViewsByTag(tag)
|
||||
|
||||
fun Fragment.findViewsByTagInActivity(key: Int, tag: Any?) = activity ?.findViewsByTag(key, tag)
|
@@ -0,0 +1,7 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
|
||||
val Activity.rootView: View?
|
||||
get() = findViewById<View?>(android.R.id.content) ?.rootView ?: window.decorView.findViewById<View?>(android.R.id.content) ?.rootView
|
@@ -1,19 +1,15 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
|
||||
fun <T> CoroutineScope.actor(
|
||||
channelCapacity: Int = Channel.UNLIMITED,
|
||||
block: suspend (T) -> Unit
|
||||
): Channel<T> {
|
||||
val channel = Channel<T>(channelCapacity)
|
||||
launch {
|
||||
for (data in channel) {
|
||||
block(data)
|
||||
}
|
||||
}
|
||||
channel.consumeAsFlow().subscribe(this, block)
|
||||
return channel
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,30 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
|
||||
fun <T> CoroutineScope.actorAsync(
|
||||
channelCapacity: Int = Channel.UNLIMITED,
|
||||
markerFactory: suspend (T) -> Any? = { null },
|
||||
block: suspend (T) -> Unit
|
||||
): Channel<T> {
|
||||
val channel = Channel<T>(channelCapacity)
|
||||
channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
|
||||
return channel
|
||||
}
|
||||
|
||||
inline fun <T> CoroutineScope.safeActorAsync(
|
||||
channelCapacity: Int = Channel.UNLIMITED,
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
noinline markerFactory: suspend (T) -> Any? = { null },
|
||||
crossinline block: suspend (T) -> Unit
|
||||
): Channel<T> = actorAsync(
|
||||
channelCapacity,
|
||||
markerFactory
|
||||
) {
|
||||
safely(onException) {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
@@ -6,23 +6,19 @@ import kotlin.coroutines.*
|
||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
|
||||
scope: CoroutineScope,
|
||||
cancelOnResult: Boolean = true
|
||||
): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation ->
|
||||
scope.launch(SupervisorJob()) {
|
||||
val scope = this
|
||||
forEach {
|
||||
scope.launch {
|
||||
continuation.resume(it to it.await())
|
||||
scope.cancel()
|
||||
}
|
||||
): Pair<Deferred<T>, T> {
|
||||
val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>()
|
||||
val scope = scope.LinkedSupervisorScope()
|
||||
forEach {
|
||||
scope.launch {
|
||||
resultDeferred.complete(it to it.await())
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
if (cancelOnResult) {
|
||||
forEach {
|
||||
try {
|
||||
it.cancel()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
return resultDeferred.await().also {
|
||||
if (cancelOnResult) {
|
||||
forEach {
|
||||
runCatchingSafely { it.cancel() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,39 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
inline fun <T, R> Flow<Flow<T>>.flatMap(
|
||||
crossinline mapper: suspend (T) -> R
|
||||
) = flow {
|
||||
collect {
|
||||
it.collect {
|
||||
emit(mapper(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsName("flatMapIterable")
|
||||
@JvmName("flatMapIterable")
|
||||
inline fun <T, R> Flow<Iterable<T>>.flatMap(
|
||||
crossinline mapper: suspend (T) -> R
|
||||
) = map {
|
||||
it.asFlow()
|
||||
}.flatMap(mapper)
|
||||
|
||||
inline fun <T, R> Flow<Flow<T>>.flatMapNotNull(
|
||||
crossinline mapper: suspend (T) -> R
|
||||
) = flatMap(mapper).takeNotNull()
|
||||
|
||||
@JsName("flatMapNotNullIterable")
|
||||
@JvmName("flatMapNotNullIterable")
|
||||
inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull(
|
||||
crossinline mapper: suspend (T) -> R
|
||||
) = flatMap(mapper).takeNotNull()
|
||||
|
||||
fun <T> Flow<Flow<T>>.flatten() = flatMap { it }
|
||||
|
||||
@JsName("flattenIterable")
|
||||
@JvmName("flattenIterable")
|
||||
fun <T> Flow<Iterable<T>>.flatten() = flatMap { it }
|
@@ -0,0 +1,6 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
|
||||
fun <T> Flow<T>.filterNotNull() = takeNotNull()
|
50
coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
Normal file
50
coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
Normal file
@@ -0,0 +1,50 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
|
||||
/**
|
||||
* [kotlinx.coroutines.flow.Flow]-based [android.view.ViewGroup.OnHierarchyChangeListener]
|
||||
*
|
||||
* @param recursive If set, any call of [onChildViewAdded] will check if child [View] is [ViewGroup] and subscribe to this
|
||||
* [ViewGroup] too
|
||||
* @param [_onChildViewAdded] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewAdded] flow
|
||||
* @param [_onChildViewRemoved] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewRemoved] flow
|
||||
*/
|
||||
class FlowOnHierarchyChangeListener(
|
||||
private val recursive: Boolean = false,
|
||||
private val _onChildViewAdded: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE),
|
||||
private val _onChildViewRemoved: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
|
||||
) : ViewGroup.OnHierarchyChangeListener {
|
||||
val onChildViewAdded = _onChildViewAdded.asSharedFlow()
|
||||
val onChildViewRemoved = _onChildViewRemoved.asSharedFlow()
|
||||
|
||||
/**
|
||||
* Will emit data into [onChildViewAdded] flow. If [recursive] is true and [child] is [ViewGroup] will also
|
||||
* subscribe to [child] hierarchy changes.
|
||||
*
|
||||
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
|
||||
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewAdded]. That is why its default extraBufferCapacity is
|
||||
* [Int.MAX_VALUE]
|
||||
*/
|
||||
override fun onChildViewAdded(parent: View, child: View) {
|
||||
_onChildViewAdded.tryEmit(parent to child)
|
||||
|
||||
if (recursive && child is ViewGroup) {
|
||||
child.setOnHierarchyChangeListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Just emit data into [onChildViewRemoved]
|
||||
*
|
||||
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
|
||||
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewRemoved]. That is why its default extraBufferCapacity is
|
||||
* [Int.MAX_VALUE]
|
||||
*/
|
||||
override fun onChildViewRemoved(parent: View, child: View) {
|
||||
_onChildViewRemoved.tryEmit(parent to child)
|
||||
}
|
||||
}
|
17
coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
Normal file
17
coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.OnHierarchyChangeListener
|
||||
|
||||
/**
|
||||
* Use [ViewGroup.setOnHierarchyChangeListener] recursively for all available [ViewGroup]s starting with [this].
|
||||
* This extension DO NOT guarantee that recursive subscription will happen after this method call
|
||||
*/
|
||||
fun ViewGroup.setOnHierarchyChangeListenerRecursively(
|
||||
listener: OnHierarchyChangeListener
|
||||
) {
|
||||
setOnHierarchyChangeListener(listener)
|
||||
(0 until childCount).forEach {
|
||||
(getChildAt(it) as? ViewGroup) ?.setOnHierarchyChangeListenerRecursively(listener)
|
||||
}
|
||||
}
|
@@ -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>(
|
||||
|
@@ -68,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.11.8
|
||||
android_code_version=132
|
||||
version=0.14.1
|
||||
android_code_version=162
|
||||
|
@@ -1,36 +1,40 @@
|
||||
[versions]
|
||||
|
||||
kt = "1.6.21"
|
||||
kt-serialization = "1.3.3"
|
||||
kt-coroutines = "1.6.1"
|
||||
kt = "1.7.20"
|
||||
kt-serialization = "1.4.1"
|
||||
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.1"
|
||||
jb-exposed = "0.40.1"
|
||||
jb-dokka = "1.7.20"
|
||||
|
||||
klock = "2.7.0"
|
||||
uuid = "0.4.1"
|
||||
klock = "3.4.0"
|
||||
uuid = "0.6.0"
|
||||
|
||||
ktor = "2.0.2"
|
||||
ktor = "2.1.3"
|
||||
|
||||
gh-release = "2.3.7"
|
||||
gh-release = "2.4.1"
|
||||
|
||||
android-gradle = "7.0.4"
|
||||
koin = "3.2.2"
|
||||
|
||||
android-gradle = "7.2.2"
|
||||
dexcount = "3.1.0"
|
||||
|
||||
android-coreKtx = "1.7.0"
|
||||
android-coreKtx = "1.9.0"
|
||||
android-recyclerView = "1.2.1"
|
||||
android-appCompat = "1.4.1"
|
||||
android-espresso = "3.3.0"
|
||||
android-test = "1.1.2"
|
||||
android-appCompat = "1.5.1"
|
||||
android-fragment = "1.5.3"
|
||||
android-espresso = "3.4.0"
|
||||
android-test = "1.1.3"
|
||||
|
||||
android-props-minSdk = "19"
|
||||
android-props-compileSdk = "32"
|
||||
android-props-buildTools = "32.0.0"
|
||||
android-props-minSdk = "21"
|
||||
android-props-compileSdk = "33"
|
||||
android-props-buildTools = "33.0.0"
|
||||
|
||||
[libraries]
|
||||
|
||||
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
|
||||
kt-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kt" }
|
||||
|
||||
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
|
||||
kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" }
|
||||
@@ -59,6 +63,7 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
|
||||
|
||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
|
||||
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
|
||||
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
|
||||
|
||||
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
|
||||
@@ -67,6 +72,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
|
||||
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
|
||||
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
|
||||
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
|
||||
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
|
||||
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
|
||||
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
|
||||
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.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>()
|
@@ -1,80 +0,0 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.pluginOrNull
|
||||
import io.ktor.client.plugins.websocket.WebSockets
|
||||
import io.ktor.client.plugins.websocket.ws
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.websocket.Frame
|
||||
import io.ktor.websocket.readBytes
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
|
||||
/**
|
||||
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||
*/
|
||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
url: String,
|
||||
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
|
||||
): Flow<T> {
|
||||
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
||||
|
||||
val correctedUrl = url.asCorrectWebSocketUrl
|
||||
|
||||
return channelFlow {
|
||||
do {
|
||||
val reconnect = runCatchingSafely {
|
||||
ws(correctedUrl, requestBuilder) {
|
||||
for (received in incoming) {
|
||||
when (received) {
|
||||
is Frame.Binary -> send(conversation(received.data))
|
||||
else -> {
|
||||
close()
|
||||
return@ws
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkReconnection(null)
|
||||
}.getOrElse { e ->
|
||||
checkReconnection(e).also {
|
||||
if (!it) {
|
||||
close(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (reconnect && isActive)
|
||||
|
||||
if (isActive) {
|
||||
safely {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||
*/
|
||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
url: String,
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
) = createStandardWebsocketFlow(
|
||||
url,
|
||||
checkReconnection,
|
||||
requestBuilder
|
||||
) {
|
||||
serialFormat.decodeDefault(deserializer, it)
|
||||
}
|
@@ -1,253 +0,0 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.filename
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.client.statement.readBytes
|
||||
import io.ktor.http.*
|
||||
import io.ktor.utils.io.core.ByteReadPacket
|
||||
import kotlinx.serialization.*
|
||||
|
||||
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
||||
|
||||
class UnifiedRequester(
|
||||
val client: HttpClient = HttpClient(),
|
||||
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) {
|
||||
suspend fun <ResultType> uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
): ResultType = client.uniget(url, resultDeserializer, serialFormat)
|
||||
|
||||
fun <T> encodeUrlQueryValue(
|
||||
serializationStrategy: SerializationStrategy<T>,
|
||||
value: T
|
||||
) = serializationStrategy.encodeUrlQueryValue(
|
||||
value,
|
||||
serialFormat
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> unipost(
|
||||
url: String,
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
|
||||
|
||||
suspend fun <ResultType> unimultipart(
|
||||
url: String,
|
||||
filename: String,
|
||||
inputProvider: InputProvider,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
||||
|
||||
suspend fun <BodyType, ResultType> unimultipart(
|
||||
url: String,
|
||||
filename: String,
|
||||
inputProvider: InputProvider,
|
||||
otherData: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
||||
|
||||
suspend fun <ResultType> unimultipart(
|
||||
url: String,
|
||||
mppFile: MPPFile,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||
): ResultType = client.unimultipart(
|
||||
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> unimultipart(
|
||||
url: String,
|
||||
mppFile: MPPFile,
|
||||
otherData: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||
): ResultType = client.unimultipart(
|
||||
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
||||
)
|
||||
|
||||
fun <T> createStandardWebsocketFlow(
|
||||
url: String,
|
||||
checkReconnection: suspend (Throwable?) -> Boolean,
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
|
||||
|
||||
fun <T> createStandardWebsocketFlow(
|
||||
url: String,
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
|
||||
}
|
||||
|
||||
val defaultRequester = UnifiedRequester()
|
||||
|
||||
suspend fun <ResultType> HttpClient.uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = get(url).let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
|
||||
}
|
||||
|
||||
|
||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
|
||||
value: T,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = serialFormat.encodeHex(
|
||||
this,
|
||||
value
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> HttpClient.unipost(
|
||||
url: String,
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = post(url) {
|
||||
setBody(
|
||||
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
)
|
||||
}.let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
|
||||
}
|
||||
|
||||
suspend fun <ResultType> HttpClient.unimultipart(
|
||||
url: String,
|
||||
filename: String,
|
||||
inputProvider: InputProvider,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
): ResultType = submitFormWithBinaryData(
|
||||
url,
|
||||
formData = formData {
|
||||
append(
|
||||
"bytes",
|
||||
inputProvider,
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, mimetype)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
|
||||
dataHeadersBuilder()
|
||||
}
|
||||
)
|
||||
additionalParametersBuilder()
|
||||
}
|
||||
) {
|
||||
requestBuilder()
|
||||
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
|
||||
|
||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||
url: String,
|
||||
filename: String,
|
||||
otherData: BodyPair<BodyType>,
|
||||
inputProvider: InputProvider,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
): ResultType = unimultipart(
|
||||
url,
|
||||
filename,
|
||||
inputProvider,
|
||||
resultDeserializer,
|
||||
mimetype,
|
||||
additionalParametersBuilder = {
|
||||
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
||||
append(
|
||||
"data",
|
||||
InputProvider(serialized.size.toLong()) {
|
||||
ByteReadPacket(serialized)
|
||||
},
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
||||
dataHeadersBuilder()
|
||||
}
|
||||
)
|
||||
additionalParametersBuilder()
|
||||
},
|
||||
dataHeadersBuilder,
|
||||
requestBuilder,
|
||||
serialFormat
|
||||
)
|
||||
|
||||
suspend fun <ResultType> HttpClient.unimultipart(
|
||||
url: String,
|
||||
mppFile: MPPFile,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
): ResultType = unimultipart(
|
||||
url,
|
||||
mppFile.filename.string,
|
||||
mppFile.inputProvider(),
|
||||
resultDeserializer,
|
||||
mimetype,
|
||||
additionalParametersBuilder,
|
||||
dataHeadersBuilder,
|
||||
requestBuilder,
|
||||
serialFormat
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||
url: String,
|
||||
mppFile: MPPFile,
|
||||
otherData: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
): ResultType = unimultipart(
|
||||
url,
|
||||
mppFile,
|
||||
resultDeserializer,
|
||||
mimetype,
|
||||
additionalParametersBuilder = {
|
||||
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
||||
append(
|
||||
"data",
|
||||
InputProvider(serialized.size.toLong()) {
|
||||
ByteReadPacket(serialized)
|
||||
},
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
||||
dataHeadersBuilder()
|
||||
}
|
||||
)
|
||||
additionalParametersBuilder()
|
||||
},
|
||||
dataHeadersBuilder,
|
||||
requestBuilder,
|
||||
serialFormat
|
||||
)
|
@@ -9,11 +9,3 @@ expect suspend fun HttpClient.tempUpload(
|
||||
file: MPPFile,
|
||||
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
|
||||
): TemporalFileId
|
||||
|
||||
suspend fun UnifiedRequester.tempUpload(
|
||||
fullTempUploadDraftPath: String,
|
||||
file: MPPFile,
|
||||
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
|
||||
): TemporalFileId = client.tempUpload(
|
||||
fullTempUploadDraftPath, file, onUpload
|
||||
)
|
||||
|
@@ -1,60 +0,0 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.http.URLProtocol
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.application.pluginOrNull
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.application
|
||||
import io.ktor.server.websocket.*
|
||||
import io.ktor.websocket.send
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
@Deprecated("This method will be removed soon")
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
protocol: URLProtocol? = null,
|
||||
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
|
||||
) {
|
||||
application.apply {
|
||||
pluginOrNull(WebSockets) ?: install(WebSockets)
|
||||
}
|
||||
webSocket(suburl, protocol ?.name) {
|
||||
safely {
|
||||
flow.collect {
|
||||
converter(it) ?.let { data ->
|
||||
send(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
protocol: URLProtocol? = null,
|
||||
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
||||
) = includeWebsocketHandling(
|
||||
suburl,
|
||||
flow,
|
||||
protocol,
|
||||
converter = if (filter == null) {
|
||||
{
|
||||
serialFormat.encodeDefault(serializer, it)
|
||||
}
|
||||
} else {
|
||||
{
|
||||
if (filter(it)) {
|
||||
serialFormat.encodeDefault(serializer, it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
@@ -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,106 +19,10 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
class UnifiedRouter(
|
||||
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) {
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>,
|
||||
protocol: URLProtocol? = null,
|
||||
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
||||
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
|
||||
answerSerializer: SerializationStrategy<T>,
|
||||
answer: T
|
||||
) {
|
||||
call.respondBytes (
|
||||
serialFormat.encodeDefault(answerSerializer, answer),
|
||||
serialFormatContentType
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = safely {
|
||||
serialFormat.decodeDefault(
|
||||
deserializer,
|
||||
call.receive()
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
|
||||
field: String
|
||||
) = call.parameters[field].also {
|
||||
if (it == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
|
||||
field: String
|
||||
) = call.request.queryParameters[field]
|
||||
|
||||
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
|
||||
field: String
|
||||
) = getQueryParameter(field).also {
|
||||
if (it == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = getQueryParameter(field) ?.let {
|
||||
serialFormat.decodeHex(
|
||||
deserializer,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = decodeUrlQueryValue(field, deserializer).also {
|
||||
if (it == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val default
|
||||
get() = defaultUnifiedRouter
|
||||
}
|
||||
}
|
||||
|
||||
val defaultUnifiedRouter = UnifiedRouter()
|
||||
|
||||
suspend fun <T> ApplicationCall.unianswer(
|
||||
answerSerializer: SerializationStrategy<T>,
|
||||
answer: T
|
||||
) {
|
||||
respondBytes (
|
||||
standardKtorSerialFormat.encodeDefault(answerSerializer, answer),
|
||||
standardKtorSerialFormatContentType
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> ApplicationCall.uniload(
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = safely {
|
||||
standardKtorSerialFormat.decodeDefault(
|
||||
deserializer,
|
||||
receive()
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun ApplicationCall.uniloadMultipart(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||
) = safely {
|
||||
val multipartData = receiveMultipart()
|
||||
@@ -135,85 +39,17 @@ suspend fun ApplicationCall.uniloadMultipart(
|
||||
}
|
||||
}
|
||||
is PartData.BinaryItem -> onBinaryContent(it)
|
||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
resultInput ?: error("Bytes has not been received")
|
||||
}
|
||||
|
||||
suspend fun <T> ApplicationCall.uniloadMultipart(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||
): Pair<Input, T> {
|
||||
var data: Optional<T>? = null
|
||||
val resultInput = uniloadMultipart(
|
||||
onFormItem,
|
||||
{
|
||||
if (it.name == "data") {
|
||||
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
||||
} else {
|
||||
onCustomFileItem(it)
|
||||
}
|
||||
},
|
||||
onBinaryContent
|
||||
)
|
||||
|
||||
val completeData = data ?: error("Data has not been received")
|
||||
return resultInput to (completeData.dataOrNull().let { it as T })
|
||||
}
|
||||
|
||||
suspend fun <T> ApplicationCall.uniloadMultipartFile(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||
) = safely {
|
||||
val multipartData = receiveMultipart()
|
||||
|
||||
var resultInput: MPPFile? = null
|
||||
var data: Optional<T>? = null
|
||||
|
||||
multipartData.forEachPart {
|
||||
when (it) {
|
||||
is PartData.FormItem -> onFormItem(it)
|
||||
is PartData.FileItem -> {
|
||||
when (it.name) {
|
||||
"bytes" -> {
|
||||
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
|
||||
resultInput = MPPFile.createTempFile(
|
||||
name.nameWithoutExtension.let {
|
||||
var resultName = it
|
||||
while (resultName.length < 3) {
|
||||
resultName += "_"
|
||||
}
|
||||
resultName
|
||||
},
|
||||
".${name.extension}"
|
||||
).apply {
|
||||
outputStream().use { fileStream ->
|
||||
it.streamProvider().use {
|
||||
it.copyTo(fileStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
||||
else -> onCustomFileItem(it)
|
||||
}
|
||||
}
|
||||
is PartData.BinaryItem -> onBinaryContent(it)
|
||||
}
|
||||
}
|
||||
|
||||
val completeData = data ?: error("Data has not been received")
|
||||
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
|
||||
}
|
||||
|
||||
suspend fun ApplicationCall.uniloadMultipartFile(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||
) = safely {
|
||||
val multipartData = receiveMultipart()
|
||||
@@ -247,6 +83,7 @@ suspend fun ApplicationCall.uniloadMultipartFile(
|
||||
}
|
||||
}
|
||||
is PartData.BinaryItem -> onBinaryContent(it)
|
||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,22 +109,3 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
|
||||
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> ApplicationCall.decodeUrlQueryValue(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = getQueryParameter(field) ?.let {
|
||||
standardKtorSerialFormat.decodeHex(
|
||||
deserializer,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = decodeUrlQueryValue(field, deserializer).also {
|
||||
if (it == null) {
|
||||
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,6 @@ import java.nio.file.attribute.FileTime
|
||||
|
||||
class TemporalFilesRoutingConfigurator(
|
||||
private val subpath: String = DefaultTemporalFilesSubPath,
|
||||
private val unifiedRouter: UnifiedRouter = UnifiedRouter.default,
|
||||
private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer
|
||||
) : ApplicationRoutingConfigurator.Element {
|
||||
interface TemporalFilesUtilizer {
|
||||
@@ -80,42 +79,40 @@ class TemporalFilesRoutingConfigurator(
|
||||
|
||||
override fun Route.invoke() {
|
||||
post(subpath) {
|
||||
unifiedRouter.apply {
|
||||
val multipart = call.receiveMultipart()
|
||||
val multipart = call.receiveMultipart()
|
||||
|
||||
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
|
||||
var part = multipart.readPart()
|
||||
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
|
||||
var part = multipart.readPart()
|
||||
|
||||
while (part != null) {
|
||||
if (part is PartData.FileItem) {
|
||||
break
|
||||
}
|
||||
part = multipart.readPart()
|
||||
while (part != null) {
|
||||
if (part is PartData.FileItem) {
|
||||
break
|
||||
}
|
||||
|
||||
part ?.let {
|
||||
if (it is PartData.FileItem) {
|
||||
val fileId = TemporalFileId(uuid4().toString())
|
||||
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
|
||||
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
|
||||
outputStream().use { outputStream ->
|
||||
it.streamProvider().use {
|
||||
it.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
deleteOnExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo ?.also { (fileId, file) ->
|
||||
temporalFilesMutex.withLock {
|
||||
temporalFilesMap[fileId] = file
|
||||
}
|
||||
call.respondText(fileId.string)
|
||||
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
|
||||
} ?: call.respond(HttpStatusCode.BadRequest)
|
||||
part = multipart.readPart()
|
||||
}
|
||||
|
||||
part ?.let {
|
||||
if (it is PartData.FileItem) {
|
||||
val fileId = TemporalFileId(uuid4().toString())
|
||||
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
|
||||
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
|
||||
outputStream().use { outputStream ->
|
||||
it.streamProvider().use {
|
||||
it.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
deleteOnExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo ?.also { (fileId, file) ->
|
||||
temporalFilesMutex.withLock {
|
||||
temporalFilesMap[fileId] = file
|
||||
}
|
||||
call.respondText(fileId.string)
|
||||
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
|
||||
} ?: call.respond(HttpStatusCode.BadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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,56 @@
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
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 +66,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 +76,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(
|
||||
|
2
repos/cache/build.gradle
vendored
2
repos/cache/build.gradle
vendored
@@ -15,4 +15,4 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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,23 +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.*
|
||||
|
||||
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,40 +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.*
|
||||
|
||||
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)
|
@@ -0,0 +1,138 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Read part of [KeyValueRepo]
|
||||
*
|
||||
* @param Key This type will be used as key in all operations related to searches of data
|
||||
* @param Value This type will be used as returning data in most "get" operations
|
||||
*/
|
||||
interface ReadKeyValueRepo<Key, Value> : Repo {
|
||||
/**
|
||||
* @return Result [Value] in case when it is presented in repo by its [k] or null otherwise
|
||||
*/
|
||||
suspend fun get(k: Key): Value?
|
||||
|
||||
/**
|
||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
||||
* ascending sort for [Key]s
|
||||
*/
|
||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
|
||||
/**
|
||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
||||
* ascending sort for [Key]s
|
||||
*/
|
||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
|
||||
/**
|
||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
||||
* ascending sort for [Key]s
|
||||
*
|
||||
* @param v This value should be used to exclude from search the items with different [Value]s
|
||||
*/
|
||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
|
||||
/**
|
||||
* @return true if [key] is presented in current collection or false otherwise
|
||||
*/
|
||||
suspend fun contains(key: Key): Boolean
|
||||
|
||||
/**
|
||||
* @return count of all collection objects
|
||||
*/
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value>
|
||||
|
||||
/**
|
||||
* Write part of [KeyValueRepo]
|
||||
*
|
||||
* @param Key This type will be used as key in all operations related to changes of data
|
||||
* @param Value This type will be used as incoming data in most operations
|
||||
*/
|
||||
interface WriteKeyValueRepo<Key, Value> : Repo {
|
||||
/**
|
||||
* This flow must emit data each time when data by [Key] has been changed with [set] method or in any other way
|
||||
* excluding cases of data removing
|
||||
*
|
||||
* @see onValueRemoved
|
||||
*/
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
|
||||
/**
|
||||
* This flow must emit data each time when data by [Key] has been removed with [unset]/[unsetWithValues] methods or
|
||||
* in any other way
|
||||
*
|
||||
* @see onNewValue
|
||||
*/
|
||||
val onValueRemoved: Flow<Key>
|
||||
|
||||
/**
|
||||
* Will set as batch [toSet] data in current repo. Must pass the data which were successfully updated in repo to
|
||||
* [onNewValue]
|
||||
*/
|
||||
suspend fun set(toSet: Map<Key, Value>)
|
||||
/**
|
||||
* Will unset as batch data with keys from [toUnset]. Must pass the [Key]s which were successfully removed in repo to
|
||||
* [onValueRemoved]
|
||||
*/
|
||||
suspend fun unset(toUnset: List<Key>)
|
||||
/**
|
||||
* Will unset as batch data with values from [toUnset]. Must pass the [Key]s which were successfully removed in repo
|
||||
* to [onValueRemoved]
|
||||
*/
|
||||
suspend fun unsetWithValues(toUnset: List<Value>)
|
||||
}
|
||||
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
vararg toSet: Pair<Key, Value>
|
||||
) = set(toSet.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
k: Key, v: Value
|
||||
) = set(k to v)
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
|
||||
vararg k: Key
|
||||
) = unset(k.toList())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
|
||||
vararg v: Value
|
||||
) = unsetWithValues(v.toList())
|
||||
|
||||
/**
|
||||
* Full version of standard key-value repository with all set/unset/clear/get methods
|
||||
*/
|
||||
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> {
|
||||
/**
|
||||
* By default, will walk throw all the [keys] with [Value]s from [toUnset] and run [doAllWithCurrentPaging] with
|
||||
* [unset] of found data [Key]s
|
||||
*/
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
||||
doAllWithCurrentPaging {
|
||||
keys(v, it).also {
|
||||
unset(it.results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, will remove all the data of current repo using [doAllWithCurrentPaging], [keys] and [unset]
|
||||
*/
|
||||
suspend fun clear() {
|
||||
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
|
||||
}
|
||||
}
|
||||
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
|
@@ -1,59 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
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>
|
||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun contains(key: Key): Boolean
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value>
|
||||
|
||||
interface WriteKeyValueRepo<Key, Value> : Repo {
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
val onValueRemoved: Flow<Key>
|
||||
|
||||
suspend fun set(toSet: Map<Key, Value>)
|
||||
suspend fun unset(toUnset: List<Key>)
|
||||
suspend fun unsetWithValues(toUnset: List<Value>)
|
||||
}
|
||||
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
vararg toSet: Pair<Key, Value>
|
||||
) = set(toSet.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
k: Key, v: Value
|
||||
) = set(k to v)
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
|
||||
vararg k: Key
|
||||
) = unset(k.toList())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
|
||||
vararg v: Value
|
||||
) = unsetWithValues(v.toList())
|
||||
|
||||
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 {
|
||||
unset(it.results)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
@@ -1,13 +1,10 @@
|
||||
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
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperReadKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperReadKeyValueRepo"))
|
||||
typealias MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@@ -23,11 +20,8 @@ open class MapperReadKeyValueRepo<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() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -38,11 +32,8 @@ open class MapperReadKeyValueRepo<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() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -55,11 +46,8 @@ open class MapperReadKeyValueRepo<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() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,8 +73,6 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperWriteKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperWriteKeyValueRepo"))
|
||||
typealias MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteKeyValueRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@@ -130,8 +116,6 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperKeyValueRepo", "dev.inmo.micro_utils.repos.mappers.MapperKeyValueRepo"))
|
||||
typealias MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
open class MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: KeyValueRepo<ToKey, ToValue>,
|
||||
|
@@ -1,13 +1,10 @@
|
||||
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
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperReadKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperReadKeyValuesRepo"))
|
||||
typealias MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: ReadKeyValuesRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@@ -21,11 +18,8 @@ open class MapperReadKeyValuesRepo<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 +30,8 @@ open class MapperReadKeyValuesRepo<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 +44,8 @@ open class MapperReadKeyValuesRepo<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() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -83,8 +71,6 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperWriteKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperWriteKeyValuesRepo"))
|
||||
typealias MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: WriteKeyValuesRepo<ToKey, ToValue>,
|
||||
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@@ -136,8 +122,6 @@ inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue>
|
||||
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapperKeyValuesRepo", "dev.inmo.micro_utils.repos.mappers.MapperKeyValuesRepo"))
|
||||
typealias MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue> = MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
open class MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
private val to: KeyValuesRepo<ToKey, ToValue>,
|
||||
|
@@ -13,9 +13,6 @@ import java.nio.file.StandardWatchEventKinds.*
|
||||
private inline val String.isAbsolute
|
||||
get() = startsWith(File.separator)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("FileReadKeyValueRepo", "dev.inmo.micro_utils.repos.FileReadKeyValueRepo"))
|
||||
typealias FileReadStandardKeyValueRepo = FileReadKeyValueRepo
|
||||
|
||||
class FileReadKeyValueRepo(
|
||||
private val folder: File
|
||||
) : ReadKeyValueRepo<String, File> {
|
||||
@@ -34,7 +31,7 @@ class FileReadKeyValueRepo(
|
||||
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()
|
||||
}
|
||||
@@ -47,7 +44,7 @@ class FileReadKeyValueRepo(
|
||||
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()
|
||||
}
|
||||
@@ -82,9 +79,6 @@ class FileReadKeyValueRepo(
|
||||
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
|
||||
}
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("FileWriteKeyValueRepo", "dev.inmo.micro_utils.repos.FileWriteKeyValueRepo"))
|
||||
typealias FileWriteStandardKeyValueRepo = FileWriteKeyValueRepo
|
||||
|
||||
/**
|
||||
* Files watching will not correctly works on Android with version of API lower than API 26
|
||||
*/
|
||||
@@ -184,9 +178,6 @@ class FileWriteKeyValueRepo(
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("FileKeyValueRepo", "dev.inmo.micro_utils.repos.FileKeyValueRepo"))
|
||||
typealias FileStandardKeyValueRepo = FileKeyValueRepo
|
||||
|
||||
@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 FileKeyValueRepo(
|
||||
|
@@ -3,8 +3,7 @@ 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.KeyValueRepo
|
||||
@@ -18,7 +17,7 @@ fun <T : Any> Context.keyValueStore(
|
||||
): KeyValueRepo<String, T> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return cache.getOrPut(name) {
|
||||
KeyValueStore<T>(this, name, cacheValues)
|
||||
KeyValueStore<T>(c = this, preferencesName = name, useCache = cacheValues)
|
||||
} as KeyValueStore<T>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
@@ -4,9 +4,9 @@ import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
||||
import org.jetbrains.exposed.sql.statements.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.util.Objects
|
||||
|
||||
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
flowsChannelsSize: Int = 0,
|
||||
@@ -20,24 +20,46 @@ abstract class AbstractExposedWriteCRUDRepo<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)
|
||||
/**
|
||||
* @param id Can be null only if [createAndInsertId] have returned null (it can be useful when you have
|
||||
* autoincrement identifier)
|
||||
* @param it Will be [UpdateStatement] when it is called from [update] method or [InsertStatement] from the [create]
|
||||
* one. Anyway, it is main method where you should put the logic of table filling by [value] data
|
||||
*
|
||||
* @see createAndInsertId
|
||||
*/
|
||||
protected abstract fun update(id: IdType?, value: InputValueType, it: UpdateBuilder<Int>)
|
||||
|
||||
/**
|
||||
* Override this method to interact with [it] ([InsertStatement]) and put there new id with [IdType].
|
||||
*
|
||||
* By default, have null value due to the fact that in the most cases users have [autoIncrement]ing id columns
|
||||
*
|
||||
* @return In case when id for the model has been created new [IdType] should be returned
|
||||
*/
|
||||
protected open fun createAndInsertId(value: InputValueType, it: InsertStatement<Number>): IdType? = null
|
||||
|
||||
protected open fun insert(value: InputValueType, it: InsertStatement<Number>) {
|
||||
val id = createAndInsertId(value, it)
|
||||
update(id, value, it as UpdateBuilder<Int>)
|
||||
}
|
||||
|
||||
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
|
||||
|
||||
/**
|
||||
* Use this method to do the something with [values]. You may change and output values in that list and return
|
||||
* changed list of pairs
|
||||
*/
|
||||
protected open suspend fun onAfterCreate(
|
||||
values: List<Pair<InputValueType, ObjectType>>
|
||||
): List<ObjectType> = values.map { it.second }
|
||||
private fun createWithoutNotification(value: InputValueType): ObjectType {
|
||||
return transaction(database) {
|
||||
insert { insert(value, it) }.asObject(value)
|
||||
@@ -47,13 +69,18 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||
onBeforeCreate(values)
|
||||
return transaction(db = database) {
|
||||
values.map { value -> createWithoutNotification(value) }
|
||||
values.map { value -> value to createWithoutNotification(value) }
|
||||
}.let {
|
||||
onAfterCreate(it)
|
||||
}.onEach {
|
||||
_newObjectsFlow.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
protected open suspend fun onBeforeUpdate(value: List<UpdatedValuePair<IdType, InputValueType>>) {}
|
||||
protected open suspend fun onAfterUpdate(
|
||||
value: List<UpdatedValuePair<InputValueType, ObjectType>>
|
||||
): List<ObjectType> = value.map { it.second }
|
||||
private fun updateWithoutNotification(id: IdType, value: InputValueType): ObjectType? {
|
||||
return transaction(db = database) {
|
||||
update(
|
||||
@@ -61,7 +88,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
selectById(this, id)
|
||||
}
|
||||
) {
|
||||
update(id, value, it)
|
||||
update(id, value, it as UpdateBuilder<Int>)
|
||||
}
|
||||
}.let {
|
||||
if (it > 0) {
|
||||
@@ -78,7 +105,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
|
||||
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
||||
onBeforeUpdate(listOf(id to value))
|
||||
return updateWithoutNotification(id, value).also {
|
||||
return updateWithoutNotification(id, value).let {
|
||||
onAfterUpdate(listOf(value to (it ?: return@let emptyList())))
|
||||
}.firstOrNull().also {
|
||||
if (it != null) {
|
||||
_updatedObjectsFlow.emit(it)
|
||||
}
|
||||
@@ -88,9 +117,11 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
onBeforeUpdate(values)
|
||||
return (
|
||||
transaction(db = database) {
|
||||
values.map { (id, value) -> updateWithoutNotification(id, value) }
|
||||
}.filterNotNull()
|
||||
).onEach {
|
||||
values.mapNotNull { (id, value) -> value to (updateWithoutNotification(id, value) ?: return@mapNotNull null) }
|
||||
}
|
||||
).let {
|
||||
onAfterUpdate(it)
|
||||
}.onEach {
|
||||
_updatedObjectsFlow.emit(it)
|
||||
}
|
||||
}
|
||||
@@ -98,9 +129,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
override suspend fun deleteById(ids: List<IdType>) {
|
||||
onBeforeDelete(ids)
|
||||
transaction(db = database) {
|
||||
val deleted = deleteWhere(null, null) {
|
||||
selectByIds(ids)
|
||||
}
|
||||
val deleted = deleteWhere(null, null) { selectByIds(it, ids) }
|
||||
if (deleted == ids.size) {
|
||||
ids
|
||||
} else {
|
||||
|
@@ -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: ISqlExpressionBuilder.(IdType) -> Op<Boolean>
|
||||
val selectByIds: ISqlExpressionBuilder.(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,76 @@
|
||||
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.*
|
||||
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: UpdateBuilder<Int>)
|
||||
protected abstract fun insertKey(k: Key, v: Value, it: InsertStatement<Number>)
|
||||
|
||||
protected open fun insert(k: Key, v: Value, it: InsertStatement<Number>) {
|
||||
insertKey(k, v, it)
|
||||
update(k, v, it as UpdateBuilder<Int>)
|
||||
}
|
||||
|
||||
override suspend fun set(toSet: Map<Key, Value>) {
|
||||
transaction(database) {
|
||||
toSet.mapNotNull { (k, v) ->
|
||||
if (update({ selectById(k) }) { update(k, v, it as UpdateBuilder<Int>) } > 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 { item ->
|
||||
if (deleteWhere { selectById(it, item) } > 0) {
|
||||
item
|
||||
} 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(it, 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: ISqlExpressionBuilder.(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
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,6 +4,8 @@ import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
open class ExposedKeyValueRepo<Key, Value>(
|
||||
@@ -47,9 +49,9 @@ open class ExposedKeyValueRepo<Key, Value>(
|
||||
|
||||
override suspend fun unset(toUnset: List<Key>) {
|
||||
transaction(database) {
|
||||
toUnset.mapNotNull {
|
||||
if (deleteWhere { keyColumn.eq(it) } > 0) {
|
||||
it
|
||||
toUnset.mapNotNull { item ->
|
||||
if (deleteWhere { keyColumn.eq(item) } > 0) {
|
||||
item
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@@ -3,50 +3,29 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.*
|
||||
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import org.jetbrains.exposed.sql.statements.UpdateBuilder
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
open class ExposedReadKeyValueRepo<Key, Value>(
|
||||
override val database: Database,
|
||||
database: Database,
|
||||
keyColumnAllocator: ColumnAllocator<Key>,
|
||||
valueColumnAllocator: ColumnAllocator<Value>,
|
||||
tableName: String? = null
|
||||
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValueRepo<Key, Value>(database, tableName) {
|
||||
|
||||
override val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
||||
override val ResultRow.asKey: Key
|
||||
get() = get(keyColumn)
|
||||
override val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
|
||||
override val ResultRow.asObject: Value
|
||||
get() = get(valueColumn)
|
||||
override val selectById: ISqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
|
||||
override val primaryKey: Table.PrimaryKey
|
||||
get() = PrimaryKey(keyColumn, valueColumn)
|
||||
|
||||
init { initTable() }
|
||||
|
||||
override suspend fun get(k: Key): Value? = transaction(database) {
|
||||
select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn)
|
||||
}
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = transaction(database) {
|
||||
select { keyColumn.eq(key) }.limit(1).any()
|
||||
}
|
||||
|
||||
override suspend fun count(): Long = transaction(database) { selectAll().count() }
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||
selectAll().paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
|
||||
it[keyColumn]
|
||||
}
|
||||
}.createPaginationResult(pagination, count())
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||
select { valueColumn.eq(v) }.let {
|
||||
it.count() to it.paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
|
||||
it[keyColumn]
|
||||
}
|
||||
}
|
||||
}.let { (count, list) ->
|
||||
list.createPaginationResult(pagination, count)
|
||||
}
|
||||
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
|
||||
selectAll().paginate(pagination, keyColumn to if (reversed) SortOrder.DESC else SortOrder.ASC).map {
|
||||
it[valueColumn]
|
||||
}
|
||||
}.createPaginationResult(pagination, count())
|
||||
}
|
||||
|
@@ -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(it, k).and(SqlExpressionBuilder.selectByValue(v)) } > 0 ) {
|
||||
k to v
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
}.forEach {
|
||||
_onValueRemoved.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear(k: Key) {
|
||||
transaction(database) {
|
||||
deleteWhere { selectById(it, 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: ISqlExpressionBuilder.(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()
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ 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.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value>
|
||||
|
@@ -1,10 +1,8 @@
|
||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value>
|
||||
|
||||
@@ -13,54 +11,15 @@ open class ExposedReadKeyValuesRepo<Key, Value>(
|
||||
keyColumnAllocator: ColumnAllocator<Key>,
|
||||
valueColumnAllocator: ColumnAllocator<Value>,
|
||||
tableName: String? = null
|
||||
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValuesRepo<Key, Value>(database, tableName) {
|
||||
override val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
override val ResultRow.asKey: Key
|
||||
get() = get(keyColumn)
|
||||
override val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
|
||||
override val ResultRow.asObject: Value
|
||||
get() = get(valueColumn)
|
||||
override val selectById: ISqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
|
||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||
|
||||
init { initTable() }
|
||||
|
||||
override suspend fun count(k: Key): Long = transaction(database) { select { keyColumn.eq(k) }.count() }
|
||||
|
||||
override suspend fun count(): Long = transaction(database) { selectAll().count() }
|
||||
|
||||
override suspend fun get(
|
||||
k: Key,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Value> = transaction(database) {
|
||||
select { keyColumn.eq(k) }.paginate(pagination, keyColumn, reversed).map { it[valueColumn] }
|
||||
}.createPaginationResult(
|
||||
pagination,
|
||||
count(k)
|
||||
)
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||
selectAll().paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
|
||||
}.createPaginationResult(
|
||||
pagination,
|
||||
count()
|
||||
)
|
||||
|
||||
override suspend fun keys(
|
||||
v: Value,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = transaction(database) {
|
||||
select { valueColumn.eq(v) }.let {
|
||||
it.count() to it.paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
|
||||
}
|
||||
}.let { (count, list) ->
|
||||
list.createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = transaction(database) {
|
||||
select { keyColumn.eq(k) }.limit(1).any()
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
|
||||
select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).any()
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
@@ -87,6 +87,7 @@ class WriteMapKeyValueRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
class MapKeyValueRepo<Key, Value>(
|
||||
private val map: MutableMap<Key, Value> = mutableMapOf()
|
||||
) : KeyValueRepo<Key, Value>,
|
||||
|
@@ -5,8 +5,6 @@ import dev.inmo.micro_utils.pagination.utils.paginate
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapReadKeyValuesRepo", "dev.inmo.micro_utils.repos.MapReadKeyValuesRepo"))
|
||||
typealias MapReadOneToManyKeyValueRepo<Key, Value> = MapReadKeyValuesRepo<Key, Value>
|
||||
class MapReadKeyValuesRepo<Key, Value>(
|
||||
private val map: Map<Key, List<Value>> = emptyMap()
|
||||
) : ReadKeyValuesRepo<Key, Value> {
|
||||
@@ -55,8 +53,6 @@ class MapReadKeyValuesRepo<Key, Value>(
|
||||
override suspend fun count(): Long = map.size.toLong()
|
||||
}
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapWriteKeyValuesRepo", "dev.inmo.micro_utils.repos.MapWriteKeyValuesRepo"))
|
||||
typealias MapWriteOneToManyKeyValueRepo<Key, Value> = MapWriteKeyValuesRepo<Key, Value>
|
||||
class MapWriteKeyValuesRepo<Key, Value>(
|
||||
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
||||
) : WriteKeyValuesRepo<Key, Value> {
|
||||
@@ -102,8 +98,7 @@ class MapWriteKeyValuesRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("MapKeyValuesRepo", "dev.inmo.micro_utils.repos.MapKeyValuesRepo"))
|
||||
typealias MapOneToManyKeyValueRepo1<Key, Value> = MapKeyValuesRepo<Key, Value>
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
class MapKeyValuesRepo<Key, Value>(
|
||||
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
|
||||
) : KeyValuesRepo<Key, Value>,
|
||||
@@ -113,6 +108,3 @@ class MapKeyValuesRepo<Key, Value>(
|
||||
fun <K, V> MutableMap<K, List<V>>.asKeyValuesRepo(): KeyValuesRepo<K, V> = MapKeyValuesRepo(
|
||||
map { (k, v) -> k to v.toMutableList() }.toMap().toMutableMap()
|
||||
)
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("asKeyValuesRepo", "dev.inmo.micro_utils.repos.asKeyValuesRepo"))
|
||||
fun <K, V> MutableMap<K, List<V>>.asOneToManyKeyValueRepo(): KeyValuesRepo<K, V> = asKeyValuesRepo()
|
||||
|
@@ -1,65 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.*
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.countRouting
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.idParameterName
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
@Deprecated("Use KtorReadCRUDRepoClient instead")
|
||||
class KtorReadStandardCrudRepo<ObjectType, IdType> (
|
||||
private val baseUrl: String,
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val objectsSerializer: KSerializer<ObjectType>,
|
||||
private val objectsSerializerNullable: KSerializer<ObjectType?>,
|
||||
private val idsSerializer: KSerializer<IdType>
|
||||
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||
private val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
|
||||
|
||||
constructor(
|
||||
baseUrl: String,
|
||||
client: HttpClient,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsSerializerNullable: KSerializer<ObjectType?>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) : this (
|
||||
baseUrl, UnifiedRequester(client, serialFormat), objectsSerializer, objectsSerializerNullable, idsSerializer
|
||||
)
|
||||
|
||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = unifiedRequester.uniget(
|
||||
buildStandardUrl(baseUrl, getByPaginationRouting, pagination.asUrlQueryParts),
|
||||
paginationResultSerializer
|
||||
)
|
||||
|
||||
override suspend fun getById(id: IdType): ObjectType? = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
getByIdRouting,
|
||||
idParameterName to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
|
||||
),
|
||||
objectsSerializerNullable
|
||||
)
|
||||
|
||||
override suspend fun contains(id: IdType): Boolean = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
containsRouting,
|
||||
idParameterName to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
|
||||
),
|
||||
Boolean.serializer()
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
countRouting
|
||||
),
|
||||
Long.serializer()
|
||||
)
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.KSerializer
|
||||
|
||||
@Deprecated("Use KtorCRUDRepoClient instead")
|
||||
class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
|
||||
baseUrl: String,
|
||||
baseSubpart: String,
|
||||
unifiedRequester: UnifiedRequester,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>
|
||||
) : CRUDRepo<ObjectType, IdType, InputValue>,
|
||||
ReadCRUDRepo<ObjectType, IdType> by KtorReadStandardCrudRepo(
|
||||
"$baseUrl/$baseSubpart",
|
||||
unifiedRequester,
|
||||
objectsSerializer,
|
||||
objectsNullableSerializer,
|
||||
idsSerializer
|
||||
),
|
||||
WriteCRUDRepo<ObjectType, IdType, InputValue> by KtorWriteStandardCrudRepo(
|
||||
"$baseUrl/$baseSubpart",
|
||||
unifiedRequester,
|
||||
objectsSerializer,
|
||||
objectsNullableSerializer,
|
||||
inputsSerializer,
|
||||
idsSerializer
|
||||
) {
|
||||
constructor(
|
||||
baseUrl: String,
|
||||
baseSubpart: String,
|
||||
client: HttpClient,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) : this(
|
||||
baseUrl, baseSubpart, UnifiedRequester(client, serialFormat), objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer
|
||||
)
|
||||
}
|
@@ -1,80 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.*
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.*
|
||||
|
||||
@Deprecated("Use KtorWriteCRUDRepoClient instead")
|
||||
class KtorWriteStandardCrudRepo<ObjectType, IdType, InputValue> (
|
||||
private val baseUrl: String,
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val objectsSerializer: KSerializer<ObjectType>,
|
||||
private val objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
private val inputsSerializer: KSerializer<InputValue>,
|
||||
private val idsSerializer: KSerializer<IdType>
|
||||
) : WriteCRUDRepo<ObjectType, IdType, InputValue> {
|
||||
private val listObjectsSerializer = ListSerializer(objectsSerializer)
|
||||
private val listInputSerializer = ListSerializer(inputsSerializer)
|
||||
private val listIdsSerializer = ListSerializer(idsSerializer)
|
||||
private val inputUpdateSerializer = PairSerializer(
|
||||
idsSerializer,
|
||||
inputsSerializer
|
||||
)
|
||||
private val listInputUpdateSerializer = ListSerializer(inputUpdateSerializer)
|
||||
|
||||
constructor(
|
||||
baseUrl: String,
|
||||
client: HttpClient,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsSerializerNullable: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) : this (
|
||||
baseUrl, UnifiedRequester(client, serialFormat), objectsSerializer, objectsSerializerNullable, inputsSerializer, idsSerializer
|
||||
)
|
||||
|
||||
override val newObjectsFlow: Flow<ObjectType> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, newObjectsFlowRouting),
|
||||
deserializer = objectsSerializer
|
||||
)
|
||||
override val updatedObjectsFlow: Flow<ObjectType> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
|
||||
deserializer = objectsSerializer
|
||||
)
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
|
||||
deserializer = idsSerializer
|
||||
)
|
||||
|
||||
|
||||
override suspend fun create(values: List<InputValue>): List<ObjectType> = unifiedRequester.unipost(
|
||||
buildStandardUrl(baseUrl, createRouting),
|
||||
BodyPair(listInputSerializer, values),
|
||||
listObjectsSerializer
|
||||
)
|
||||
|
||||
override suspend fun update(id: IdType, value: InputValue): ObjectType? = unifiedRequester.unipost(
|
||||
buildStandardUrl(baseUrl, updateRouting),
|
||||
BodyPair(inputUpdateSerializer, id to value),
|
||||
objectsNullableSerializer
|
||||
)
|
||||
|
||||
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValue>>): List<ObjectType> = unifiedRequester.unipost(
|
||||
buildStandardUrl(baseUrl, updateManyRouting),
|
||||
BodyPair(listInputUpdateSerializer, values),
|
||||
listObjectsSerializer
|
||||
)
|
||||
|
||||
override suspend fun deleteById(ids: List<IdType>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(baseUrl, deleteByIdRouting),
|
||||
BodyPair(listIdsSerializer, ids),
|
||||
Unit.serializer()
|
||||
)
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.*
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.containsRoute
|
||||
import dev.inmo.micro_utils.repos.ktor.common.countRoute
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.keyParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.reversedParameterName
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
@Deprecated("Replaced with KtorReadKeyValueRepoClient")
|
||||
class KtorReadStandardKeyValueRepo<Key, Value> (
|
||||
private val baseUrl: String,
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>,
|
||||
private val valueNullableSerializer: KSerializer<Value?>
|
||||
) : ReadKeyValueRepo<Key, Value> {
|
||||
constructor(
|
||||
baseUrl: String,
|
||||
client: HttpClient,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
valueNullableSerializer: KSerializer<Value?>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) : this (
|
||||
baseUrl, UnifiedRequester(client, serialFormat), keySerializer, valueSerializer, valueNullableSerializer
|
||||
)
|
||||
|
||||
override suspend fun get(k: Key): Value? = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
getRoute,
|
||||
mapOf(
|
||||
keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, k)
|
||||
)
|
||||
),
|
||||
valueNullableSerializer
|
||||
)
|
||||
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
valuesRoute,
|
||||
mapOf(
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
PaginationResult.serializer(valueSerializer)
|
||||
)
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
PaginationResult.serializer(keySerializer)
|
||||
)
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
valueParameterName to unifiedRequester.encodeUrlQueryValue(valueSerializer, v),
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
PaginationResult.serializer(keySerializer)
|
||||
)
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
containsRoute,
|
||||
mapOf(
|
||||
keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, key)
|
||||
),
|
||||
),
|
||||
Boolean.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
countRoute,
|
||||
),
|
||||
Long.serializer()
|
||||
)
|
||||
}
|
@@ -1,42 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.*
|
||||
|
||||
@Deprecated("Replaced with KtorKeyValueRepoClient")
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
class KtorStandartKeyValueRepo<K, V> (
|
||||
baseUrl: String,
|
||||
baseSubpart: String,
|
||||
unifiedRequester: UnifiedRequester,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>
|
||||
) : KeyValueRepo<K, V>,
|
||||
ReadKeyValueRepo<K, V> by KtorReadStandardKeyValueRepo(
|
||||
"$baseUrl/$baseSubpart",
|
||||
unifiedRequester,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
valueNullableSerializer
|
||||
),
|
||||
WriteKeyValueRepo<K, V> by KtorWriteStandardKeyValueRepo(
|
||||
"$baseUrl/$baseSubpart",
|
||||
unifiedRequester,
|
||||
keySerializer,
|
||||
valueSerializer
|
||||
) {
|
||||
constructor(
|
||||
baseUrl: String,
|
||||
baseSubpart: String,
|
||||
client: HttpClient = HttpClient(),
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) : this(baseUrl, baseSubpart, UnifiedRequester(client, serialFormat), keySerializer, valueSerializer, valueNullableSerializer)
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.*
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.*
|
||||
|
||||
@Deprecated("Replaced with KtorWriteKeyValueRepoClient")
|
||||
class KtorWriteStandardKeyValueRepo<K, V> (
|
||||
private var baseUrl: String,
|
||||
private var unifiedRequester: UnifiedRequester,
|
||||
private var keySerializer: KSerializer<K>,
|
||||
private var valueSerializer: KSerializer<V>,
|
||||
) : WriteKeyValueRepo<K, V> {
|
||||
private val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
||||
private val keysListSerializer = ListSerializer(keySerializer)
|
||||
private val valuesListSerializer = ListSerializer(valueSerializer)
|
||||
|
||||
constructor(
|
||||
baseUrl: String,
|
||||
client: HttpClient,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) : this (
|
||||
baseUrl, UnifiedRequester(client, serialFormat), keySerializer, valueSerializer
|
||||
)
|
||||
|
||||
override val onNewValue: Flow<Pair<K, V>> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
deserializer = PairSerializer(keySerializer, valueSerializer)
|
||||
)
|
||||
|
||||
override val onValueRemoved: Flow<K> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
deserializer = keySerializer
|
||||
)
|
||||
|
||||
override suspend fun set(toSet: Map<K, V>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
setRoute
|
||||
),
|
||||
BodyPair(keyValueMapSerializer, toSet),
|
||||
Unit.serializer()
|
||||
)
|
||||
|
||||
override suspend fun unset(toUnset: List<K>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
unsetRoute,
|
||||
),
|
||||
BodyPair(keysListSerializer, toUnset),
|
||||
Unit.serializer()
|
||||
)
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<V>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
unsetWithValuesRoute,
|
||||
),
|
||||
BodyPair(valuesListSerializer, toUnset),
|
||||
Unit.serializer()
|
||||
)
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.KSerializer
|
||||
|
||||
@Deprecated("Should be replaced with KtorKeyValuesRepoClient")
|
||||
class KtorOneToManyKeyValueRepo<Key, Value>(
|
||||
baseUrl: String,
|
||||
baseSubpart: String,
|
||||
unifiedRequester: UnifiedRequester,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
) : KeyValuesRepo<Key, Value>,
|
||||
ReadKeyValuesRepo<Key, Value> by KtorReadOneToManyKeyValueRepo<Key, Value> (
|
||||
"$baseUrl/$baseSubpart",
|
||||
unifiedRequester,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
),
|
||||
WriteKeyValuesRepo<Key, Value> by KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
"$baseUrl/$baseSubpart",
|
||||
unifiedRequester,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
) {
|
||||
constructor(
|
||||
baseUrl: String,
|
||||
baseSubpart: String,
|
||||
client: HttpClient,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) : this (baseUrl, baseSubpart, UnifiedRequester(client, serialFormat), keySerializer, valueSerializer)
|
||||
}
|
@@ -1,108 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.*
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
@Deprecated("Should be replaced with KtorReadKeyValuesRepoClient")
|
||||
class KtorReadOneToManyKeyValueRepo<Key, Value> (
|
||||
private val baseUrl: String,
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>
|
||||
) : ReadKeyValuesRepo<Key, Value> {
|
||||
private val paginationValueResultSerializer = PaginationResult.serializer(valueSerializer)
|
||||
private val paginationKeyResultSerializer = PaginationResult.serializer(keySerializer)
|
||||
|
||||
constructor(
|
||||
baseUrl: String,
|
||||
client: HttpClient,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) : this (baseUrl, UnifiedRequester(client, serialFormat), keySerializer, valueSerializer)
|
||||
|
||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
getRoute,
|
||||
mapOf(
|
||||
keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, k),
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
paginationValueResultSerializer
|
||||
)
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
paginationKeyResultSerializer
|
||||
)
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
valueParameterName to unifiedRequester.encodeUrlQueryValue(valueSerializer, v),
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
paginationKeyResultSerializer
|
||||
)
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
containsByKeyRoute,
|
||||
mapOf(keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, k))
|
||||
),
|
||||
Boolean.serializer()
|
||||
)
|
||||
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
containsByKeyValueRoute,
|
||||
mapOf(
|
||||
keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, k),
|
||||
valueParameterName to unifiedRequester.encodeUrlQueryValue(valueSerializer, v),
|
||||
)
|
||||
),
|
||||
Boolean.serializer()
|
||||
)
|
||||
|
||||
override suspend fun count(k: Key): Long = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
countByKeyRoute,
|
||||
mapOf(
|
||||
keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, k)
|
||||
)
|
||||
),
|
||||
Long.serializer()
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
countRoute,
|
||||
),
|
||||
Long.serializer()
|
||||
)
|
||||
|
||||
}
|
@@ -1,88 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.*
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.*
|
||||
|
||||
@Deprecated("Should be replaced with KtorWriteKeyValuesRepoClient")
|
||||
class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
private val baseUrl: String,
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>
|
||||
) : WriteKeyValuesRepo<Key, Value> {
|
||||
private val keyValueSerializer = PairSerializer(keySerializer, valueSerializer)
|
||||
private val keyValueMapSerializer = MapSerializer(keySerializer, ListSerializer(valueSerializer))
|
||||
|
||||
constructor(
|
||||
baseUrl: String,
|
||||
client: HttpClient,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) : this (
|
||||
baseUrl, UnifiedRequester(client, serialFormat), keySerializer, valueSerializer
|
||||
)
|
||||
|
||||
override val onNewValue: Flow<Pair<Key, Value>> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onNewValueRoute),
|
||||
deserializer = keyValueSerializer
|
||||
)
|
||||
override val onValueRemoved: Flow<Pair<Key, Value>> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
deserializer = keyValueSerializer
|
||||
)
|
||||
override val onDataCleared: Flow<Key> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onDataClearedRoute),
|
||||
deserializer = keySerializer
|
||||
)
|
||||
|
||||
override suspend fun remove(toRemove: Map<Key, List<Value>>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
removeRoute,
|
||||
),
|
||||
BodyPair(keyValueMapSerializer, toRemove),
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun add(toAdd: Map<Key, List<Value>>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
addRoute,
|
||||
),
|
||||
BodyPair(keyValueMapSerializer, toAdd),
|
||||
Unit.serializer(),
|
||||
)
|
||||
override suspend fun clear(k: Key) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
clearRoute,
|
||||
),
|
||||
BodyPair(keySerializer, k),
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun clearWithValue(v: Value) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
clearWithValueRoute,
|
||||
),
|
||||
BodyPair(valueSerializer, v),
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun set(toSet: Map<Key, List<Value>>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
setRoute,
|
||||
),
|
||||
BodyPair(keyValueMapSerializer, toSet),
|
||||
Unit.serializer(),
|
||||
)
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.extractPagination
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.countRouting
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.get
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes(
|
||||
originalRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
|
||||
|
||||
get(getByPaginationRouting) {
|
||||
unifiedRouter.apply {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
|
||||
unianswer(
|
||||
paginationResultSerializer,
|
||||
originalRepo.getByPagination(pagination)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(getByIdRouting) {
|
||||
unifiedRouter.apply {
|
||||
val id = decodeUrlQueryValueOrSendError(
|
||||
"id",
|
||||
idsSerializer
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
objectsNullableSerializer,
|
||||
originalRepo.getById(id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(containsRouting) {
|
||||
unifiedRouter.apply {
|
||||
val id = decodeUrlQueryValueOrSendError(
|
||||
"id",
|
||||
idsSerializer
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(countRouting) {
|
||||
unifiedRouter.apply {
|
||||
unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes(
|
||||
originalRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
@@ -1,39 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
||||
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
|
||||
import dev.inmo.micro_utils.repos.CRUDRepo
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.route
|
||||
import kotlinx.serialization.KSerializer
|
||||
|
||||
fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: CRUDRepo<ObjectType, IdType, InputValue>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
route(baseSubpart) {
|
||||
configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, unifiedRouter)
|
||||
configureWriteCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, unifiedRouter)
|
||||
}
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: CRUDRepo<ObjectType, IdType, InputValue>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureCRUDRepoRoutes(
|
||||
baseSubpart, originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user