mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
117 Commits
Author | SHA1 | Date | |
---|---|---|---|
b26a4f24d4 | |||
498ec673dc | |||
5c67ab6aec | |||
f78359b5d7 | |||
dbce612cb2 | |||
4322ffdb0a | |||
123b848d74 | |||
b120fbd2b1 | |||
d41c3c2de4 | |||
a3cabd7a9e | |||
3dde486126 | |||
5978122dda | |||
4b4806ce34 | |||
987c6cc709 | |||
012c7e9bdf | |||
d06bb265e5 | |||
e2fb8bf21e | |||
60dea2a518 | |||
3877f49278 | |||
a299a5b505 | |||
a03f7201d2 | |||
94e26ee8a0 | |||
2d8ad47083 | |||
e9aec238a1 | |||
844c33166c | |||
cfe9f2159f | |||
61bccd5f48 | |||
e5a608a315 | |||
9b4f35eea1 | |||
602ae2385b | |||
48a4246aac | |||
a4020cb484 | |||
d074f29b82 | |||
a67bef6853 | |||
14e666f18d | |||
cce914091c | |||
d7cd3db8e2 | |||
0d8f844314 | |||
abd0cb2031 | |||
79ef03ed0c | |||
a3087cb650 | |||
9acb9af338 | |||
0c9283eb87 | |||
493d838201 | |||
c0523469fa | |||
4f00eaa6d4 | |||
9706524572 | |||
3174b84367 | |||
6cccd5ff6c | |||
972857268d | |||
f3cd92cc5e | |||
502a49644c | |||
51719b5868 | |||
17821bd094 | |||
6a89ffaa8a | |||
d5a8d0f4d4 | |||
9739bd871e | |||
632d2545d4 | |||
ecfa273a81 | |||
a36828116e | |||
14aa9ca26c | |||
069e51f2ff | |||
a15cbdfb1a | |||
4af8114eda | |||
90dc84e900 | |||
67c595b440 | |||
830b7aee56 | |||
1890608cb3 | |||
bd396959a9 | |||
d5fe19f0a5 | |||
46bfb09415 | |||
a60cb596d1 | |||
6f9d5e2d5f | |||
80bc226ee1 | |||
12e37184e1 | |||
25e9345d02 | |||
ccc4d030c3 | |||
90c0817b6d | |||
527f7bbafe | |||
765a32729f | |||
de783f77a2 | |||
de4c8d104c | |||
88c8c28f45 | |||
57b36826d1 | |||
f81a2f309b | |||
5fc760f4a5 | |||
091cb38339 | |||
3ae9b3e576 | |||
b03b4cbeec | |||
f2c1b3c76a | |||
f3bec34882 | |||
d6aa9fe9c2 | |||
2d5304a770 | |||
88f2c16c82 | |||
490c318d1c | |||
8beaf61a08 | |||
8b61c984eb | |||
e38094df58 | |||
c25e3f5867 | |||
f78e81d175 | |||
3837ae237d | |||
2b6ef8b4ff | |||
6cc0eefb3e | |||
ab11e28bf7 | |||
26d5f5a5f5 | |||
74ae91cba6 | |||
70509c7edd | |||
5a69bd6c63 | |||
091bb1394f | |||
b82c3864a0 | |||
49ee38a936 | |||
c201866c51 | |||
023f841fb5 | |||
76102e9ab3 | |||
b2fc5e2a4d | |||
55aacb8753 | |||
8702846216 |
16
.github/workflows/gradle_build.yml
vendored
Normal file
16
.github/workflows/gradle_build.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Build
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
186
CHANGELOG.md
186
CHANGELOG.md
@@ -1,5 +1,191 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.27
|
||||
|
||||
## 0.4.26
|
||||
|
||||
* `Versions`:
|
||||
* `Serialization`: `1.0.1` -> `1.1.0`
|
||||
|
||||
## 0.4.25
|
||||
|
||||
* `Matrix`:
|
||||
* Subproject has been created
|
||||
|
||||
## 0.4.24
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.4.21` -> `1.4.30`
|
||||
* `Klock`: `2.0.4` -> `2.0.6`
|
||||
* `Coroutines`:
|
||||
* New class `DoWithFirstBuilder`
|
||||
* Several new extensions like `firstOf`/`first`/`invokeOnFirstOf`
|
||||
|
||||
## 0.4.23
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `1.5.0` -> `1.5.1`
|
||||
* `Serialization`
|
||||
* `Base64`
|
||||
* New serializer `Base64BytesToFromStringSerializer` has been added
|
||||
|
||||
## 0.4.22
|
||||
|
||||
* `Versions`:
|
||||
* `Exposed`: `0.28.1` -> `0.29.1`
|
||||
* `Klock`: `2.0.2` -> `2.0.4`
|
||||
|
||||
## 0.4.21
|
||||
|
||||
* `Common`
|
||||
* `JS`
|
||||
* Extension `DataView#toByteArray` has been added
|
||||
* Extension `ArrayBuffer#toByteArray` has been added
|
||||
* Extension `ByteArray#toDataView` has been added
|
||||
* Extension `ByteArray#toArrayBuffer` has been added
|
||||
* `Coroutines`
|
||||
* `JS`
|
||||
* Extension `Blob#toByteArray` has been added
|
||||
* `Crypto`
|
||||
* Subproject has been created
|
||||
* `Serialization`
|
||||
* `Base64`
|
||||
* Currently, it is not depended on `common` project and use `crypto` instead
|
||||
|
||||
## 0.4.20
|
||||
|
||||
* `Serialization`
|
||||
* `Encapsulator`:
|
||||
* Has been created
|
||||
|
||||
## 0.4.19
|
||||
|
||||
* `Coroutines`:
|
||||
* New extension `Iterable<Deferred>#awaitFirstWithDeferred` has been added to identify which of `Deferred`s was
|
||||
normally completed
|
||||
* New extensions `Iterable<Deferred<T>>#invokeOnFirst` and `Iterable<DeferredAction<*, O>>.invokeFirstOf` have been
|
||||
added
|
||||
|
||||
## 0.4.18
|
||||
|
||||
* `Coroutines`:
|
||||
* New extension `Iterable<Deferred>#awaitFirst` has been added
|
||||
* `Serialization`
|
||||
* `Base 64`
|
||||
* New `Base64ByteArraySerializer` has been added
|
||||
|
||||
## 0.4.17
|
||||
|
||||
* `Common`
|
||||
* Functionality for decode/encode base 64 to/from `ByteArray`/`String` have been added
|
||||
* `Serialization`
|
||||
* `Base 64`
|
||||
* Project has been initiated
|
||||
|
||||
## 0.4.16
|
||||
|
||||
* `Coroutines`:
|
||||
* `safely`:
|
||||
* New `safelyWithoutExceptions` function may accept `onException` parameter with nullable result
|
||||
* Old `safelyWithoutExceptions` now using `defaultSafelyWithoutExceptionHandler` to handle exceptions "like in
|
||||
`safely`", but it is expected that `defaultSafelyWithoutExceptionHandler` will not throw any exception
|
||||
|
||||
## 0.4.15
|
||||
|
||||
* `Coroutines`:
|
||||
* `safely`:
|
||||
* `SafelyExceptionHandlerKey` has been deprecated
|
||||
* `SafelyExceptionHandler` has been deprecated
|
||||
* `ContextSafelyExceptionHandlerKey` has been added
|
||||
* `ContextSafelyExceptionHandler` has been added
|
||||
* `safelyWithContextExceptionHandler` has been added
|
||||
|
||||
## 0.4.14
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.4.20` -> `1.4.21`
|
||||
* `Ktor`: `1.4.3` -> `1.5.0`
|
||||
* `Klock`: `2.0.1` -> `2.0.2`
|
||||
* `Coroutines`:
|
||||
* Add global variable `defaultSafelyExceptionHandler`
|
||||
* Add `SafelyExceptionHandlerKey` and `SafelyExceptionHandler` classes to be able to overwrite
|
||||
`defaultSafelyExceptionHandler` using context of coroutine
|
||||
|
||||
## 0.4.13
|
||||
|
||||
* `Common`
|
||||
* `Android`
|
||||
* Add expand/collapse functionality for horizontal expand/collapse
|
||||
|
||||
## 0.4.12
|
||||
|
||||
* `Coroutines`
|
||||
* `JVM`
|
||||
* Update `launchSynchronously` signature
|
||||
* `Selector`
|
||||
* Project created
|
||||
|
||||
## 0.4.11
|
||||
|
||||
* `Common`
|
||||
* Add `clamp` function
|
||||
|
||||
## 0.4.10
|
||||
|
||||
* `Versions`:
|
||||
* `Klock`: `2.0.0` -> `2.0.1`
|
||||
* `Repo`
|
||||
* Repo `WriteStandardKeyValueRepo` got new method `unsetWithValues`
|
||||
|
||||
## 0.4.9
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `1.4.2` -> `1.4.3`
|
||||
* `Coroutines`:
|
||||
* `launchSynchronously` has been added in JVM
|
||||
* `Repo`
|
||||
* `Common`
|
||||
* In repos different usages of `BroadcastChannel`s has been replaced with `MutableSharedFlow`
|
||||
* `Exposed`
|
||||
* `asObject` open fun has been added in CRUD realization
|
||||
|
||||
## 0.4.8
|
||||
|
||||
* `Versions`:
|
||||
* `Coroutines`: `1.4.1` -> `1.4.2`
|
||||
* `UUID`: `0.2.2` -> `0.2.3`
|
||||
* `Pagination`
|
||||
* Add `PaginatedIterable` and `PaginatedIterator`
|
||||
|
||||
## 0.4.7
|
||||
|
||||
* `Ktor`
|
||||
* `Client`
|
||||
* New class `UnifiedRequester`
|
||||
* `Server`
|
||||
* New class `UnifiedRouter`
|
||||
* `Repos`
|
||||
* `Ktor`
|
||||
* `Client`
|
||||
* Rewriting of all clients on new `UnifiedRequester`
|
||||
* `Server`
|
||||
* Rewriting of all clients on new `UnifiedRouter`
|
||||
|
||||
## 0.4.6
|
||||
|
||||
* `Common`
|
||||
* New annotation `Warning` has been added
|
||||
* `Pagination`
|
||||
* `Common`
|
||||
* `Pagination` got new extension: `Pagination#isFirstPage`
|
||||
* `Coroutines`:
|
||||
* New extension `FlowCollector#invoke` has been added
|
||||
* `Repos`
|
||||
* `Common`
|
||||
* `JVM` (and `Android` since `Android API 26`):
|
||||
* `FileStandardKeyValueRepo` has been added
|
||||
* Add several `typealias`es for each type of repos
|
||||
|
||||
## 0.4.5
|
||||
|
||||
* `Android`
|
||||
|
36
README.md
36
README.md
@@ -1 +1,37 @@
|
||||
# MicroUtils
|
||||
|
||||
This is a library with collection of tools for working in Kotlin environment. First of all, this library collection is oriented to use next technologies:
|
||||
|
||||
* [`Kotlin Coroutines`](https://github.com/Kotlin/kotlinx.coroutines)
|
||||
* [`Kotlin Serialization`](https://github.com/Kotlin/kotlinx.serialization)
|
||||
* [`Kotlin Exposed`](https://github.com/JetBrains/Exposed)
|
||||
* [`Ktor`](https://ktor.io)
|
||||
|
||||
<details>
|
||||
<summary> <b>Android environment</b> </summary>
|
||||
|
||||
You always can look at the <a href="https://github.com/InsanusMokrassar/MicroUtils/blob/master/gradle.properties#L24-L34">properties file</a> to get information about current project dependencies, compile and build tools for `Android` target.
|
||||
|
||||
</details>
|
||||
|
||||
## Projects
|
||||
|
||||
* `common` contains common tools for platform which usually are absent out-of-the-box when you starting project
|
||||
* `selector` contains tools to use `Selector` interface with things like `RecyclerView` in android or other selection needs
|
||||
* `coroutines` is a module for `Kotlin Coroutines` with different things like subscribing on flows (`onEach` + `launchIn` shortcut :) )
|
||||
* `ktor` is a set of modules for `client`s and `server`s
|
||||
* `mime_types` is NOT lightweight set of `MimeType`s with a lot of different objected and serializable (with `Kotlin Serialization`) mime types
|
||||
* `pagination` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for lightweight pagination
|
||||
* `serialization` is a collection of projects with serializers for `kotlinx.serialization`
|
||||
* `repos` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for `KeyValue`/`OneToMany`/`CRUD` repos created to be able to exclude some heavy dependencies when you need some simple and lightweight typical repositories
|
||||
|
||||
## Complex modules structure
|
||||
|
||||
Most of complex modules are built with next hierarchy:
|
||||
|
||||
* `common` submodule for `API` things which are common for all platforms
|
||||
* `exposed` submodule contains realizations for exposed tables
|
||||
* `ktor` submodule is usually unavailable directly, because it contains its own submodules for clients and servers
|
||||
* `common` part contains routes which are common for clients and servers
|
||||
* `client` submodule contains clients which are usually using `UnifiedRequester` to make requests using routes from `ktor/common` module and some internal logic of requests
|
||||
* `server` submodule (in most cases `JVM`-only) contains some extensions for `Route` instances which usually will give opportunity to proxy internet requests from `ktor/client` realization to some proxy object
|
||||
|
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-cayman
|
@@ -17,3 +17,21 @@ package dev.inmo.micro_utils.common
|
||||
AnnotationTarget.TYPE_PARAMETER
|
||||
)
|
||||
annotation class PreviewFeature
|
||||
|
||||
@RequiresOptIn(
|
||||
"This thing is marked as warned. See message of warn to get more info",
|
||||
RequiresOptIn.Level.WARNING
|
||||
)
|
||||
@Target(
|
||||
AnnotationTarget.CLASS,
|
||||
AnnotationTarget.CONSTRUCTOR,
|
||||
AnnotationTarget.FIELD,
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.PROPERTY_GETTER,
|
||||
AnnotationTarget.PROPERTY_SETTER,
|
||||
AnnotationTarget.FUNCTION,
|
||||
AnnotationTarget.TYPE,
|
||||
AnnotationTarget.TYPEALIAS,
|
||||
AnnotationTarget.TYPE_PARAMETER
|
||||
)
|
||||
annotation class Warning(val message: String)
|
||||
|
@@ -0,0 +1,89 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlin.experimental.and
|
||||
|
||||
private const val BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
private const val BASE64_MASK: Byte = 0x3f
|
||||
private const val BASE64_PAD = '='
|
||||
private val BASE64_INVERSE_ALPHABET = IntArray(256) {
|
||||
BASE64_ALPHABET.indexOf(it.toChar())
|
||||
}
|
||||
|
||||
internal fun Int.toBase64(): Char = BASE64_ALPHABET[this]
|
||||
internal fun Byte.fromBase64(): Byte = BASE64_INVERSE_ALPHABET[toInt() and 0xff].toByte() and BASE64_MASK
|
||||
|
||||
const val replacedInCrypto = "Replaced in crypto subproject"
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("EncodedBase64String", "dev.inmo.micro_utils.crypto"))
|
||||
typealias EncodedBase64String = String
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("EncodedByteArray", "dev.inmo.micro_utils.crypto"))
|
||||
typealias EncodedByteArray = ByteArray
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64String()", "dev.inmo.micro_utils.crypto"))
|
||||
fun String.encodeBase64String(): EncodedBase64String = encodeToByteArray().encodeBase64String()
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64()", "dev.inmo.micro_utils.crypto"))
|
||||
fun String.encodeBase64(): EncodedByteArray = encodeToByteArray().encodeBase64()
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64String()", "dev.inmo.micro_utils.crypto"))
|
||||
fun ByteArray.encodeBase64String(): EncodedBase64String = buildString {
|
||||
var i = 0
|
||||
while (this@encodeBase64String.size > i) {
|
||||
val read = kotlin.math.min(3, this@encodeBase64String.size - i)
|
||||
val data = ByteArray(3) {
|
||||
if (it < read) {
|
||||
this@encodeBase64String[it + i]
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
val padSize = (data.size - read) * 8 / 6
|
||||
val chunk = ((data[0].toInt() and 0xFF) shl 16) or
|
||||
((data[1].toInt() and 0xFF) shl 8) or
|
||||
(data[2].toInt() and 0xFF)
|
||||
|
||||
for (index in data.size downTo padSize) {
|
||||
val char = (chunk shr (6 * index)) and BASE64_MASK.toInt()
|
||||
append(char.toBase64())
|
||||
}
|
||||
|
||||
repeat(padSize) { append(BASE64_PAD) }
|
||||
|
||||
i += read
|
||||
}
|
||||
}
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("encodeBase64()", "dev.inmo.micro_utils.crypto"))
|
||||
fun ByteArray.encodeBase64(): EncodedByteArray = encodeBase64String().encodeToByteArray()
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64()", "dev.inmo.micro_utils.crypto"))
|
||||
fun EncodedBase64String.decodeBase64() = dropLastWhile { it == BASE64_PAD }.encodeToByteArray().decodeBase64()
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64String()", "dev.inmo.micro_utils.crypto"))
|
||||
fun EncodedBase64String.decodeBase64String() = decodeBase64().decodeToString()
|
||||
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64()", "dev.inmo.micro_utils.crypto"))
|
||||
fun EncodedByteArray.decodeBase64(): ByteArray {
|
||||
val result = mutableListOf<Byte>()
|
||||
val data = ByteArray(4)
|
||||
(0 until size step 4).forEach { i ->
|
||||
var read = 0
|
||||
for (j in 0 until 4) {
|
||||
if (j + i < size) {
|
||||
data[j] = get(j + i)
|
||||
read++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
val chunk = data.foldIndexed(0) { index, result, current ->
|
||||
result or (current.fromBase64().toInt() shl ((3 - index) * 6))
|
||||
}
|
||||
for (index in data.size - 2 downTo (data.size - read)) {
|
||||
val origin = (chunk shr (8 * index)) and 0xff
|
||||
result.add(origin.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
return result.toByteArray()
|
||||
}
|
||||
@Deprecated(replacedInCrypto, ReplaceWith("decodeBase64String()", "dev.inmo.micro_utils.crypto"))
|
||||
fun EncodedByteArray.decodeBase64String() = decodeBase64().decodeToString()
|
@@ -0,0 +1,10 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T : Comparable<T>> T.clamp(min: T, max: T): T {
|
||||
return when {
|
||||
this < min -> min
|
||||
this > max -> max
|
||||
else -> this
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
|
||||
class Base64Text {
|
||||
|
||||
val testText = """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.
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"""
|
||||
val base64Text = """TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLgpTZWQgdXQgcGVyc3BpY2lhdGlzIHVuZGUgb21uaXMgaXN0ZSBuYXR1cyBlcnJvciBzaXQgdm9sdXB0YXRlbSBhY2N1c2FudGl1bSBkb2xvcmVtcXVlIGxhdWRhbnRpdW0sIHRvdGFtIHJlbSBhcGVyaWFtLCBlYXF1ZSBpcHNhIHF1YWUgYWIgaWxsbyBpbnZlbnRvcmUgdmVyaXRhdGlzIGV0IHF1YXNpIGFyY2hpdGVjdG8gYmVhdGFlIHZpdGFlIGRpY3RhIHN1bnQgZXhwbGljYWJvLiBOZW1vIGVuaW0gaXBzYW0gdm9sdXB0YXRlbSBxdWlhIHZvbHVwdGFzIHNpdCBhc3Blcm5hdHVyIGF1dCBvZGl0IGF1dCBmdWdpdCwgc2VkIHF1aWEgY29uc2VxdXVudHVyIG1hZ25pIGRvbG9yZXMgZW9zIHF1aSByYXRpb25lIHZvbHVwdGF0ZW0gc2VxdWkgbmVzY2l1bnQuIE5lcXVlIHBvcnJvIHF1aXNxdWFtIGVzdCwgcXVpIGRvbG9yZW0gaXBzdW0gcXVpYSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIsIGFkaXBpc2NpIHZlbGl0LCBzZWQgcXVpYSBub24gbnVtcXVhbSBlaXVzIG1vZGkgdGVtcG9yYSBpbmNpZHVudCB1dCBsYWJvcmUgZXQgZG9sb3JlIG1hZ25hbSBhbGlxdWFtIHF1YWVyYXQgdm9sdXB0YXRlbS4gVXQgZW5pbSBhZCBtaW5pbWEgdmVuaWFtLCBxdWlzIG5vc3RydW0gZXhlcmNpdGF0aW9uZW0gdWxsYW0gY29ycG9yaXMgc3VzY2lwaXQgbGFib3Jpb3NhbSwgbmlzaSB1dCBhbGlxdWlkIGV4IGVhIGNvbW1vZGkgY29uc2VxdWF0dXI/IFF1aXMgYXV0ZW0gdmVsIGV1bSBpdXJlIHJlcHJlaGVuZGVyaXQgcXVpIGluIGVhIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIHF1YW0gbmloaWwgbW9sZXN0aWFlIGNvbnNlcXVhdHVyLCB2ZWwgaWxsdW0gcXVpIGRvbG9yZW0gZXVtIGZ1Z2lhdCBxdW8gdm9sdXB0YXMgbnVsbGEgcGFyaWF0dXI/"""
|
||||
|
||||
@Test
|
||||
fun decodeEncode() {
|
||||
val encoded = testText.encodeBase64String()
|
||||
assertEquals(base64Text, encoded)
|
||||
val decoded = encoded.decodeBase64String()
|
||||
assertEquals(testText, decoded)
|
||||
}
|
||||
|
||||
val urlTestText = "https://example.com?isitexample=1"
|
||||
val urlTestBase64Text = "aHR0cHM6Ly9leGFtcGxlLmNvbT9pc2l0ZXhhbXBsZT0x"
|
||||
|
||||
@Test
|
||||
fun decodeEncodeUrl() {
|
||||
val encoded = urlTestText.encodeBase64String()
|
||||
assertEquals(urlTestBase64Text, encoded)
|
||||
val decoded = encoded.decodeBase64String()
|
||||
assertEquals(urlTestText, decoded)
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import org.khronos.webgl.*
|
||||
|
||||
fun DataView.toByteArray() = ByteArray(this.byteLength) {
|
||||
getInt8(it)
|
||||
}
|
||||
|
||||
fun ArrayBuffer.toByteArray() = Int8Array(this) as ByteArray
|
||||
|
||||
fun ByteArray.toDataView() = DataView(ArrayBuffer(size)).also {
|
||||
forEachIndexed { i, byte -> it.setInt8(i, byte) }
|
||||
}
|
||||
|
||||
fun ByteArray.toArrayBuffer() = toDataView().buffer
|
@@ -5,23 +5,44 @@ import android.view.ViewGroup
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.Transformation
|
||||
|
||||
@PreviewFeature
|
||||
fun View.expand(
|
||||
private fun View.performExpand(
|
||||
duration: Long = 500,
|
||||
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
onMeasured: View.() -> Unit,
|
||||
onPerformAnimation: View.(interpolatedTime: Float, t: Transformation?) -> Unit
|
||||
) {
|
||||
measure(targetWidth, targetHeight)
|
||||
val measuredHeight: Int = measuredHeight
|
||||
layoutParams.height = 0
|
||||
visibility = View.VISIBLE
|
||||
onMeasured()
|
||||
show()
|
||||
val a: Animation = object : Animation() {
|
||||
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
|
||||
super.applyTransformation(interpolatedTime, t)
|
||||
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
|
||||
onPerformAnimation(interpolatedTime, t)
|
||||
requestLayout()
|
||||
}
|
||||
|
||||
override fun willChangeBounds(): Boolean = true
|
||||
}
|
||||
|
||||
a.duration = duration
|
||||
startAnimation(a)
|
||||
}
|
||||
|
||||
private fun View.performCollapse(
|
||||
duration: Long = 500,
|
||||
onPerformAnimation: View.(interpolatedTime: Float, t: Transformation?) -> Unit
|
||||
) {
|
||||
val a: Animation = object : Animation() {
|
||||
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
|
||||
if (interpolatedTime == 1f) {
|
||||
gone()
|
||||
} else {
|
||||
onPerformAnimation(interpolatedTime, t)
|
||||
requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override fun willChangeBounds(): Boolean {
|
||||
return true
|
||||
}
|
||||
@@ -32,27 +53,58 @@ fun View.expand(
|
||||
startAnimation(a)
|
||||
}
|
||||
|
||||
@PreviewFeature
|
||||
fun View.expand(
|
||||
duration: Long = 500,
|
||||
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
) {
|
||||
var measuredHeight = 0
|
||||
performExpand(
|
||||
duration,
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
{
|
||||
measuredHeight = this.measuredHeight
|
||||
}
|
||||
) { interpolatedTime, _ ->
|
||||
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewFeature
|
||||
fun View.expandHorizontally(
|
||||
duration: Long = 500,
|
||||
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
) {
|
||||
var measuredWidth = 0
|
||||
performExpand(
|
||||
duration,
|
||||
targetWidth,
|
||||
targetHeight,
|
||||
{
|
||||
measuredWidth = this.measuredWidth
|
||||
}
|
||||
) { interpolatedTime, _ ->
|
||||
layoutParams.width = if (interpolatedTime == 1f) targetWidth else (measuredWidth * interpolatedTime).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewFeature
|
||||
fun View.collapse(duration: Long = 500) {
|
||||
val initialHeight: Int = measuredHeight
|
||||
val a: Animation = object : Animation() {
|
||||
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
|
||||
if (interpolatedTime == 1f) {
|
||||
visibility = View.GONE
|
||||
} else {
|
||||
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
|
||||
requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override fun willChangeBounds(): Boolean {
|
||||
return true
|
||||
}
|
||||
performCollapse(duration) { interpolatedTime, _ ->
|
||||
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
a.duration = duration
|
||||
|
||||
startAnimation(a)
|
||||
@PreviewFeature
|
||||
fun View.collapseHorizontally(duration: Long = 500) {
|
||||
val initialWidth: Int = measuredWidth
|
||||
performCollapse(duration) { interpolatedTime, _ ->
|
||||
layoutParams.width = initialWidth - (initialWidth * interpolatedTime).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewFeature
|
||||
@@ -74,3 +126,15 @@ fun View.toggleExpandState(duration: Long = 500): Boolean = if (isCollapsed) {
|
||||
collapse(duration)
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true in case of expanding
|
||||
*/
|
||||
@PreviewFeature
|
||||
fun View.toggleExpandHorizontallyState(duration: Long = 500): Boolean = if (isCollapsed) {
|
||||
expandHorizontally(duration)
|
||||
true
|
||||
} else {
|
||||
collapseHorizontally(duration)
|
||||
false
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ fun <T> CoroutineScope.actor(
|
||||
|
||||
inline fun <T> CoroutineScope.safeActor(
|
||||
channelCapacity: Int = Channel.UNLIMITED,
|
||||
noinline onException: ExceptionHandler<Unit> = {},
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
crossinline block: suspend (T) -> Unit
|
||||
): Channel<T> = actor(
|
||||
channelCapacity
|
||||
|
@@ -0,0 +1,37 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
if (cancelOnResult) {
|
||||
forEach {
|
||||
try {
|
||||
it.cancel()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
|
||||
scope: CoroutineScope,
|
||||
cancelOnResult: Boolean = true
|
||||
): T = awaitFirstWithDeferred(scope, cancelOnResult).second
|
||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
|
||||
cancelOthers: Boolean = true
|
||||
): T = awaitFirst(CoroutineScope(coroutineContext), cancelOthers)
|
@@ -0,0 +1,85 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
class DeferredAction<T, O>(
|
||||
val deferred: Deferred<T>,
|
||||
val callback: suspend (T) -> O
|
||||
) {
|
||||
suspend operator fun invoke() = callback(deferred.await())
|
||||
}
|
||||
|
||||
class DoWithFirstBuilder<T>(
|
||||
private val scope: CoroutineScope
|
||||
) {
|
||||
private val deferreds = mutableListOf<Deferred<T>>()
|
||||
operator fun plus(block: suspend CoroutineScope.() -> T) {
|
||||
deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block))
|
||||
}
|
||||
inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block)
|
||||
inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block)
|
||||
|
||||
fun build() = deferreds.toList()
|
||||
}
|
||||
|
||||
fun <T, O> Deferred<T>.buildAction(callback: suspend (T) -> O) = DeferredAction(this, callback)
|
||||
|
||||
suspend fun <O> Iterable<DeferredAction<*, O>>.invokeFirstOf(
|
||||
scope: CoroutineScope,
|
||||
cancelOnResult: Boolean = true
|
||||
): O {
|
||||
return map { it.deferred }.awaitFirstWithDeferred(scope, cancelOnResult).let { result ->
|
||||
first { it.deferred == result.first }.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <O> invokeFirstOf(
|
||||
scope: CoroutineScope,
|
||||
vararg variants: DeferredAction<*, O>,
|
||||
cancelOnResult: Boolean = true
|
||||
): O = variants.toList().invokeFirstOf(scope, cancelOnResult)
|
||||
|
||||
suspend fun <T, O> Iterable<Deferred<T>>.invokeOnFirst(
|
||||
scope: CoroutineScope,
|
||||
cancelOnResult: Boolean = true,
|
||||
callback: suspend (T) -> O
|
||||
): O = map { it.buildAction(callback) }.invokeFirstOf(scope, cancelOnResult)
|
||||
|
||||
suspend fun <T, O> CoroutineScope.invokeOnFirstOf(
|
||||
cancelOnResult: Boolean = true,
|
||||
block: DoWithFirstBuilder<T>.() -> Unit,
|
||||
callback: suspend (T) -> O
|
||||
) = firstOf(
|
||||
DoWithFirstBuilder<T>(this).apply(block).build(),
|
||||
cancelOnResult
|
||||
).let { callback(it) }
|
||||
|
||||
suspend fun <T, O> invokeOnFirst(
|
||||
scope: CoroutineScope,
|
||||
vararg variants: Deferred<T>,
|
||||
cancelOnResult: Boolean = true,
|
||||
callback: suspend (T) -> O
|
||||
): O = variants.toList().invokeOnFirst(scope, cancelOnResult, callback)
|
||||
|
||||
suspend fun <T> CoroutineScope.firstOf(
|
||||
variants: Iterable<Deferred<T>>,
|
||||
cancelOnResult: Boolean = true
|
||||
) = variants.invokeOnFirst(this, cancelOnResult) { it }
|
||||
|
||||
suspend fun <T> CoroutineScope.firstOf(
|
||||
cancelOnResult: Boolean = true,
|
||||
block: DoWithFirstBuilder<T>.() -> Unit
|
||||
) = firstOf(
|
||||
DoWithFirstBuilder<T>(this).apply(block).build(),
|
||||
cancelOnResult
|
||||
)
|
||||
|
||||
suspend fun <T> CoroutineScope.firstOf(
|
||||
vararg variants: Deferred<T>,
|
||||
cancelOnResult: Boolean = true
|
||||
) = firstOf(variants.toList(), cancelOnResult)
|
||||
|
||||
suspend fun <T> List<Deferred<T>>.first(
|
||||
scope: CoroutineScope,
|
||||
cancelOnResult: Boolean = true
|
||||
) = scope.firstOf(this, cancelOnResult)
|
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
|
||||
suspend inline operator fun <T> FlowCollector<T>.invoke(value: T) = emit(value)
|
@@ -16,7 +16,7 @@ inline fun <T> Flow<T>.subscribe(scope: CoroutineScope, noinline block: suspend
|
||||
*/
|
||||
inline fun <T> Flow<T>.subscribeSafely(
|
||||
scope: CoroutineScope,
|
||||
noinline onException: ExceptionHandler<Unit> = { throw it },
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend (T) -> Unit
|
||||
) = subscribe(scope) {
|
||||
safely(onException) {
|
||||
|
@@ -1,30 +1,156 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
typealias ExceptionHandler<T> = suspend (Throwable) -> T
|
||||
|
||||
/**
|
||||
* This instance will be used in all calls of [safely] where exception handler has not been passed
|
||||
*/
|
||||
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
|
||||
|
||||
/**
|
||||
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
|
||||
*/
|
||||
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
|
||||
try {
|
||||
defaultSafelyExceptionHandler(it)
|
||||
} catch (e: Throwable) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for [SafelyExceptionHandler] which can be used in [CoroutineContext.get] to get current default
|
||||
* [SafelyExceptionHandler]
|
||||
*/
|
||||
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
|
||||
class SafelyExceptionHandlerKey<T> : CoroutineContext.Key<SafelyExceptionHandler<T>>
|
||||
|
||||
/**
|
||||
* Shortcut for creating instance of [SafelyExceptionHandlerKey]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
|
||||
inline fun <T> safelyExceptionHandlerKey() = SafelyExceptionHandlerKey<T>()
|
||||
|
||||
/**
|
||||
* Wrapper for [ExceptionHandler] which can be used in [CoroutineContext] to set local (for [CoroutineContext]) default
|
||||
* [ExceptionHandler]. To get it use [CoroutineContext.get] with key [SafelyExceptionHandlerKey]
|
||||
*
|
||||
* @see SafelyExceptionHandlerKey
|
||||
* @see ExceptionHandler
|
||||
*/
|
||||
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandler", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
|
||||
class SafelyExceptionHandler<T>(
|
||||
val handler: ExceptionHandler<T>
|
||||
) : CoroutineContext.Element {
|
||||
override val key: CoroutineContext.Key<*> = safelyExceptionHandlerKey<T>()
|
||||
}
|
||||
|
||||
/**
|
||||
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
|
||||
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
|
||||
*
|
||||
* @see safelyWithContextExceptionHandler
|
||||
* @see ContextSafelyExceptionHandler
|
||||
*/
|
||||
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
|
||||
|
||||
/**
|
||||
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
|
||||
*
|
||||
* @see safelyWithContextExceptionHandler
|
||||
* @see ContextSafelyExceptionHandlerKey
|
||||
*/
|
||||
class ContextSafelyExceptionHandler(
|
||||
val handler: ExceptionHandler<Unit>
|
||||
) : CoroutineContext.Element {
|
||||
override val key: CoroutineContext.Key<*>
|
||||
get() = ContextSafelyExceptionHandlerKey
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
|
||||
* exists
|
||||
*
|
||||
* @see ContextSafelyExceptionHandler
|
||||
* @see ContextSafelyExceptionHandlerKey
|
||||
*/
|
||||
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
|
||||
|
||||
/**
|
||||
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
|
||||
* already contains [ContextSafelyExceptionHandler], [ContextSafelyExceptionHandler.handler] will be used BEFORE
|
||||
* [contextExceptionHandler] in case of exception.
|
||||
*
|
||||
* After all, will be called [withContext] method with created [ContextSafelyExceptionHandler] and block which will call
|
||||
* [safely] method with [safelyExceptionHandler] as onException parameter and [block] as execution block
|
||||
*/
|
||||
suspend fun <T> safelyWithContextExceptionHandler(
|
||||
contextExceptionHandler: ExceptionHandler<Unit>,
|
||||
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
block: suspend CoroutineScope.() -> T
|
||||
): T {
|
||||
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
|
||||
ContextSafelyExceptionHandler {
|
||||
oldHandler(it)
|
||||
contextExceptionHandler(it)
|
||||
}
|
||||
} ?: ContextSafelyExceptionHandler(contextExceptionHandler)
|
||||
return withContext(contextSafelyExceptionHandler) {
|
||||
safely(safelyExceptionHandler, block)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
|
||||
*
|
||||
* Priorities of [ExceptionHandler]s:
|
||||
*
|
||||
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
|
||||
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
|
||||
* * [defaultSafelyExceptionHandler]
|
||||
*
|
||||
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
|
||||
* exception will be available for catching
|
||||
*
|
||||
* @see defaultSafelyExceptionHandler
|
||||
* @see safelyWithoutExceptions
|
||||
* @see safelyWithContextExceptionHandler
|
||||
*/
|
||||
suspend inline fun <T> safely(
|
||||
noinline onException: ExceptionHandler<T> = { throw it },
|
||||
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T {
|
||||
return try {
|
||||
supervisorScope(block)
|
||||
} catch (e: Throwable) {
|
||||
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
|
||||
onException(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for [safely] without exception handler (instead of this you will receive null as a result)
|
||||
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
|
||||
* result from exception (instead of throwing it)
|
||||
*/
|
||||
suspend inline fun <T> safelyWithoutExceptions(
|
||||
noinline onException: ExceptionHandler<T?>,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T? = safely(onException, block)
|
||||
|
||||
/**
|
||||
* Shortcut for [safely] without exception handler (instead of this you will always receive null as a result)
|
||||
*/
|
||||
suspend inline fun <T> safelyWithoutExceptions(
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T? = safely({ null }, block)
|
||||
): T? = safelyWithoutExceptions(
|
||||
{
|
||||
defaultSafelyWithoutExceptionHandler.invoke(it)
|
||||
null
|
||||
},
|
||||
block
|
||||
)
|
||||
|
@@ -0,0 +1,10 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.await
|
||||
import org.khronos.webgl.Int8Array
|
||||
import org.w3c.fetch.Response
|
||||
import org.w3c.files.Blob
|
||||
|
||||
suspend fun Blob.toByteArray() = Int8Array(
|
||||
Response(this).arrayBuffer().await()
|
||||
) as ByteArray
|
@@ -0,0 +1,30 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T {
|
||||
var throwable: Throwable? = null
|
||||
var result: T? = null
|
||||
val objectToSynchronize = java.lang.Object()
|
||||
val launchCallback = {
|
||||
launch {
|
||||
safely(
|
||||
{
|
||||
throwable = it
|
||||
}
|
||||
) {
|
||||
result = block()
|
||||
}
|
||||
synchronized(objectToSynchronize) {
|
||||
objectToSynchronize.notifyAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized(objectToSynchronize) {
|
||||
launchCallback()
|
||||
objectToSynchronize.wait()
|
||||
}
|
||||
throw throwable ?: return result!!
|
||||
}
|
||||
|
||||
fun <T> launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block)
|
@@ -0,0 +1,34 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class AwaitFirstTests {
|
||||
private fun CoroutineScope.createTestDeferred(value: Int, wait: Long = 100000) = async(start = CoroutineStart.LAZY) { delay(wait); value }
|
||||
@Test
|
||||
fun testThatAwaitFirstIsWorkingCorrectly() {
|
||||
val baseScope = CoroutineScope(Dispatchers.Default)
|
||||
val resultDeferred = baseScope.createTestDeferred(-1, 0)
|
||||
val deferreds = listOf(
|
||||
baseScope.async { createTestDeferred(0) },
|
||||
baseScope.async { createTestDeferred(1) },
|
||||
baseScope.async { createTestDeferred(2) },
|
||||
resultDeferred
|
||||
)
|
||||
val controlJob = baseScope.launch {
|
||||
delay(1000000)
|
||||
}
|
||||
val result = baseScope.launchSynchronously {
|
||||
val result = deferreds.awaitFirst(baseScope)
|
||||
|
||||
assertTrue(baseScope.isActive)
|
||||
assertTrue(controlJob.isActive)
|
||||
|
||||
result
|
||||
}
|
||||
assertEquals(baseScope.launchSynchronously { resultDeferred.await() }, result)
|
||||
assertTrue(deferreds.all { it == resultDeferred || it.isCancelled })
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.asDeferred
|
||||
import dev.inmo.micro_utils.coroutines.launchSynchronously
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.test.*
|
||||
|
||||
class DoWithFirstTests {
|
||||
@Test
|
||||
fun testHandleOneOf() {
|
||||
val scope = CoroutineScope(Dispatchers.Default)
|
||||
val happenedDeferreds = mutableListOf<Int>()
|
||||
val deferredWhichMustHappen = (-1).asDeferred
|
||||
scope.launchSynchronously {
|
||||
scope.launch {
|
||||
((0 until 100).map {
|
||||
DeferredAction(
|
||||
scope.async { delay(10000); it },
|
||||
happenedDeferreds::add
|
||||
)
|
||||
} + DeferredAction(deferredWhichMustHappen, happenedDeferreds::add)).invokeFirstOf(scope)
|
||||
}.join()
|
||||
}
|
||||
assertEquals(1, happenedDeferreds.size)
|
||||
assertEquals(scope.launchSynchronously { deferredWhichMustHappen.await() }, happenedDeferreds.first())
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.test.Test
|
||||
|
||||
class HandleSafelyCoroutineContextTest {
|
||||
@Test
|
||||
fun testHandleSafelyCoroutineContext() {
|
||||
val scope = CoroutineScope(Dispatchers.Default)
|
||||
var contextHandlerHappen = false
|
||||
var localHandlerHappen = false
|
||||
var defaultHandlerHappen = false
|
||||
defaultSafelyExceptionHandler = {
|
||||
defaultHandlerHappen = true
|
||||
throw it
|
||||
}
|
||||
val contextHandler: ExceptionHandler<Unit> = {
|
||||
contextHandlerHappen = true
|
||||
}
|
||||
val checkJob = scope.launch {
|
||||
safelyWithContextExceptionHandler(contextHandler) {
|
||||
safely(
|
||||
{
|
||||
localHandlerHappen = true
|
||||
}
|
||||
) {
|
||||
error("That must happen :)")
|
||||
}
|
||||
println(coroutineContext)
|
||||
error("That must happen too:)")
|
||||
}
|
||||
}
|
||||
launchSynchronously { checkJob.join() }
|
||||
assert(contextHandlerHappen)
|
||||
assert(localHandlerHappen)
|
||||
assert(defaultHandlerHappen)
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class LaunchSynchronouslyTest {
|
||||
@Test
|
||||
fun testRunInCoroutine() {
|
||||
(0 .. 10000).forEach {
|
||||
assertEquals(it, launchSynchronously { it })
|
||||
}
|
||||
}
|
||||
}
|
30
crypto/build.gradle
Normal file
30
crypto/build.gradle
Normal file
@@ -0,0 +1,30 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.common")
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
api npm("crypto-js", "$crypto_js_version")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += [ "src/jvmMain/kotlin" ]
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
import kotlin.experimental.and
|
||||
|
||||
private const val BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
private const val BASE64_MASK: Byte = 0x3f
|
||||
private const val BASE64_PAD = '='
|
||||
private val BASE64_INVERSE_ALPHABET = IntArray(256) {
|
||||
BASE64_ALPHABET.indexOf(it.toChar())
|
||||
}
|
||||
|
||||
internal fun Int.toBase64(): Char = BASE64_ALPHABET[this]
|
||||
internal fun Byte.fromBase64(): Byte = BASE64_INVERSE_ALPHABET[toInt() and 0xff].toByte() and BASE64_MASK
|
||||
|
||||
typealias EncodedBase64String = String
|
||||
typealias EncodedByteArray = ByteArray
|
||||
|
||||
fun SourceString.encodeBase64String(): EncodedBase64String = encodeToByteArray().encodeBase64String()
|
||||
fun SourceString.encodeBase64(): EncodedByteArray = encodeToByteArray().encodeBase64()
|
||||
|
||||
fun SourceBytes.encodeBase64String(): EncodedBase64String = buildString {
|
||||
var i = 0
|
||||
while (this@encodeBase64String.size > i) {
|
||||
val read = kotlin.math.min(3, this@encodeBase64String.size - i)
|
||||
val data = ByteArray(3) {
|
||||
if (it < read) {
|
||||
this@encodeBase64String[it + i]
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
val padSize = (data.size - read) * 8 / 6
|
||||
val chunk = ((data[0].toInt() and 0xFF) shl 16) or
|
||||
((data[1].toInt() and 0xFF) shl 8) or
|
||||
(data[2].toInt() and 0xFF)
|
||||
|
||||
for (index in data.size downTo padSize) {
|
||||
val char = (chunk shr (6 * index)) and BASE64_MASK.toInt()
|
||||
append(char.toBase64())
|
||||
}
|
||||
|
||||
repeat(padSize) { append(BASE64_PAD) }
|
||||
|
||||
i += read
|
||||
}
|
||||
}
|
||||
fun SourceBytes.encodeBase64(): EncodedByteArray = encodeBase64String().encodeToByteArray()
|
||||
|
||||
fun EncodedBase64String.decodeBase64(): SourceBytes = dropLastWhile { it == BASE64_PAD }.encodeToByteArray().decodeBase64()
|
||||
fun EncodedBase64String.decodeBase64String(): SourceString = decodeBase64().decodeToString()
|
||||
|
||||
fun EncodedByteArray.decodeBase64(): SourceBytes {
|
||||
val result = mutableListOf<Byte>()
|
||||
val data = ByteArray(4)
|
||||
(0 until size step 4).forEach { i ->
|
||||
var read = 0
|
||||
for (j in 0 until 4) {
|
||||
if (j + i < size) {
|
||||
data[j] = get(j + i)
|
||||
read++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
val chunk = data.foldIndexed(0) { index, result, current ->
|
||||
result or (current.fromBase64().toInt() shl ((3 - index) * 6))
|
||||
}
|
||||
for (index in data.size - 2 downTo (data.size - read)) {
|
||||
val origin = (chunk shr (8 * index)) and 0xff
|
||||
result.add(origin.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
return result.toByteArray()
|
||||
}
|
||||
fun EncodedByteArray.decodeBase64String(): SourceString = decodeBase64().decodeToString()
|
@@ -0,0 +1,11 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
/**
|
||||
* This typealias shows that corresponding [ByteArray] is an raw data
|
||||
*/
|
||||
typealias SourceBytes = ByteArray
|
||||
/**
|
||||
* This typealias shows that corresponding [String] is an raw data
|
||||
*/
|
||||
typealias SourceString = String
|
||||
|
@@ -0,0 +1,6 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
typealias MD5 = String
|
||||
|
||||
expect fun SourceBytes.md5(): MD5
|
||||
fun SourceString.md5(): MD5 = encodeToByteArray().md5()
|
@@ -0,0 +1,32 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
|
||||
class Base64Text {
|
||||
|
||||
val testText = """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.
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"""
|
||||
val base64Text = """TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLgpTZWQgdXQgcGVyc3BpY2lhdGlzIHVuZGUgb21uaXMgaXN0ZSBuYXR1cyBlcnJvciBzaXQgdm9sdXB0YXRlbSBhY2N1c2FudGl1bSBkb2xvcmVtcXVlIGxhdWRhbnRpdW0sIHRvdGFtIHJlbSBhcGVyaWFtLCBlYXF1ZSBpcHNhIHF1YWUgYWIgaWxsbyBpbnZlbnRvcmUgdmVyaXRhdGlzIGV0IHF1YXNpIGFyY2hpdGVjdG8gYmVhdGFlIHZpdGFlIGRpY3RhIHN1bnQgZXhwbGljYWJvLiBOZW1vIGVuaW0gaXBzYW0gdm9sdXB0YXRlbSBxdWlhIHZvbHVwdGFzIHNpdCBhc3Blcm5hdHVyIGF1dCBvZGl0IGF1dCBmdWdpdCwgc2VkIHF1aWEgY29uc2VxdXVudHVyIG1hZ25pIGRvbG9yZXMgZW9zIHF1aSByYXRpb25lIHZvbHVwdGF0ZW0gc2VxdWkgbmVzY2l1bnQuIE5lcXVlIHBvcnJvIHF1aXNxdWFtIGVzdCwgcXVpIGRvbG9yZW0gaXBzdW0gcXVpYSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIsIGFkaXBpc2NpIHZlbGl0LCBzZWQgcXVpYSBub24gbnVtcXVhbSBlaXVzIG1vZGkgdGVtcG9yYSBpbmNpZHVudCB1dCBsYWJvcmUgZXQgZG9sb3JlIG1hZ25hbSBhbGlxdWFtIHF1YWVyYXQgdm9sdXB0YXRlbS4gVXQgZW5pbSBhZCBtaW5pbWEgdmVuaWFtLCBxdWlzIG5vc3RydW0gZXhlcmNpdGF0aW9uZW0gdWxsYW0gY29ycG9yaXMgc3VzY2lwaXQgbGFib3Jpb3NhbSwgbmlzaSB1dCBhbGlxdWlkIGV4IGVhIGNvbW1vZGkgY29uc2VxdWF0dXI/IFF1aXMgYXV0ZW0gdmVsIGV1bSBpdXJlIHJlcHJlaGVuZGVyaXQgcXVpIGluIGVhIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIHF1YW0gbmloaWwgbW9sZXN0aWFlIGNvbnNlcXVhdHVyLCB2ZWwgaWxsdW0gcXVpIGRvbG9yZW0gZXVtIGZ1Z2lhdCBxdW8gdm9sdXB0YXMgbnVsbGEgcGFyaWF0dXI/"""
|
||||
|
||||
@Test
|
||||
fun decodeEncode() {
|
||||
val encoded = testText.encodeBase64String()
|
||||
assertEquals(base64Text, encoded)
|
||||
val decoded = encoded.decodeBase64String()
|
||||
assertEquals(testText, decoded)
|
||||
}
|
||||
|
||||
val urlTestText = "https://example.com?isitexample=1"
|
||||
val urlTestBase64Text = "aHR0cHM6Ly9leGFtcGxlLmNvbT9pc2l0ZXhhbXBsZT0x"
|
||||
|
||||
@Test
|
||||
fun decodeEncodeUrl() {
|
||||
val encoded = urlTestText.encodeBase64String()
|
||||
assertEquals(urlTestBase64Text, encoded)
|
||||
val decoded = encoded.decodeBase64String()
|
||||
assertEquals(urlTestText, decoded)
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class MD5Test {
|
||||
val source = "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 expectedMD5 = "db89bb5ceab87f9c0fcc2ab36c189c2c"
|
||||
@Test
|
||||
fun testMD5() {
|
||||
var i = 0
|
||||
source.md5().forEach {
|
||||
assertEquals(expectedMD5[i], it)
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
external interface CryptoJs {
|
||||
fun MD5(data: String): String
|
||||
}
|
||||
|
||||
@JsModule("crypto-js")
|
||||
@JsNonModule
|
||||
external val CryptoJS: CryptoJs
|
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())
|
@@ -0,0 +1,9 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.security.MessageDigest
|
||||
|
||||
actual fun SourceBytes.md5(): MD5 = BigInteger(
|
||||
1,
|
||||
MessageDigest.getInstance("MD5").digest(this)
|
||||
).toString(16)
|
1
crypto/src/main/AndroidManifest.xml
Normal file
1
crypto/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.crypto"/>
|
@@ -1,3 +1,5 @@
|
||||
apply plugin: 'com.getkeepsafe.dexcount'
|
||||
|
||||
android {
|
||||
compileSdkVersion "$android_compileSdkVersion".toInteger()
|
||||
buildToolsVersion "$android_buildToolsVersion"
|
||||
|
@@ -5,20 +5,19 @@ kotlin.incremental=true
|
||||
kotlin.incremental.js=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
|
||||
kotlin_version=1.4.20
|
||||
kotlin_coroutines_version=1.4.1
|
||||
kotlin_serialisation_core_version=1.0.1
|
||||
kotlin_exposed_version=0.28.1
|
||||
kotlin_version=1.4.30
|
||||
kotlin_coroutines_version=1.4.2
|
||||
kotlin_serialisation_core_version=1.1.0
|
||||
kotlin_exposed_version=0.29.1
|
||||
|
||||
ktor_version=1.4.2
|
||||
ktor_version=1.5.1
|
||||
|
||||
klockVersion=2.0.0
|
||||
klockVersion=2.0.6
|
||||
|
||||
github_release_plugin_version=2.2.12
|
||||
|
||||
uuidVersion=0.2.2
|
||||
uuidVersion=0.2.3
|
||||
|
||||
# ANDROID
|
||||
|
||||
@@ -29,17 +28,21 @@ appcompat_version=1.2.0
|
||||
android_minSdkVersion=19
|
||||
android_compileSdkVersion=30
|
||||
android_buildToolsVersion=30.0.2
|
||||
dexcount_version=2.0.0-RC1
|
||||
dexcount_version=2.0.0
|
||||
junit_version=4.12
|
||||
test_ext_junit_version=1.1.2
|
||||
espresso_core=3.3.0
|
||||
|
||||
# JS NPM
|
||||
|
||||
crypto_js_version=4.0.0
|
||||
|
||||
# Dokka
|
||||
|
||||
dokka_version=1.4.0
|
||||
dokka_version=1.4.20
|
||||
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.4.5
|
||||
android_code_version=9
|
||||
version=0.4.27
|
||||
android_code_version=31
|
||||
|
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-6.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@@ -25,7 +25,7 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
val producerScope = this@channelFlow
|
||||
do {
|
||||
val reconnect = try {
|
||||
safely ({ throw it }) {
|
||||
safely {
|
||||
ws(correctedUrl) {
|
||||
for (received in incoming) {
|
||||
when (received) {
|
||||
@@ -64,10 +64,11 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
url: String,
|
||||
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
|
||||
deserializer: DeserializationStrategy<T>
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = createStandardWebsocketFlow(
|
||||
url,
|
||||
checkReconnection
|
||||
) {
|
||||
standardKtorSerialFormat.decodeDefault(deserializer, it)
|
||||
serialFormat.decodeDefault(deserializer, it)
|
||||
}
|
||||
|
@@ -4,32 +4,61 @@ import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.post
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import kotlinx.serialization.*
|
||||
|
||||
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
|
||||
|
||||
class UnifiedRequester(
|
||||
private val client: HttpClient = HttpClient(),
|
||||
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) {
|
||||
suspend fun <ResultType> uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
): ResultType = client.get<StandardKtorSerialInputData>(
|
||||
url
|
||||
).let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
|
||||
|
||||
fun <T> encodeUrlQueryValue(
|
||||
serializationStrategy: SerializationStrategy<T>,
|
||||
value: T
|
||||
) = serialFormat.encodeHex(
|
||||
serializationStrategy,
|
||||
value
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> unipost(
|
||||
url: String,
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = client.post<StandardKtorSerialInputData>(url) {
|
||||
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
}.let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
|
||||
fun <T> createStandardWebsocketFlow(
|
||||
url: String,
|
||||
checkReconnection: (Throwable?) -> Boolean = { true },
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat)
|
||||
}
|
||||
|
||||
val defaultRequester = UnifiedRequester()
|
||||
|
||||
suspend fun <ResultType> HttpClient.uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = get<StandardKtorSerialInputData>(
|
||||
url
|
||||
).let {
|
||||
standardKtorSerialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
) = defaultRequester.uniget(url, resultDeserializer)
|
||||
|
||||
|
||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = standardKtorSerialFormat.encodeHex(
|
||||
this,
|
||||
value
|
||||
)
|
||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(value: T) = defaultRequester.encodeUrlQueryValue(this, value)
|
||||
|
||||
suspend fun <BodyType, ResultType> HttpClient.unipost(
|
||||
url: String,
|
||||
bodyInfo: BodyPair<BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = post<StandardKtorSerialInputData>(url) {
|
||||
body = standardKtorSerialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
}.let {
|
||||
standardKtorSerialFormat.decodeDefault(resultDeserializer, it)
|
||||
}
|
||||
) = defaultRequester.unipost(url, bodyInfo, resultDeserializer)
|
||||
|
@@ -3,16 +3,28 @@ package dev.inmo.micro_utils.ktor.common
|
||||
typealias QueryParam = Pair<String, String?>
|
||||
typealias QueryParams = Map<String, String?>
|
||||
|
||||
/**
|
||||
* Create query part which includes key=value pairs separated with &
|
||||
*/
|
||||
val QueryParams.asUrlQuery: String
|
||||
get() = keys.joinToString("&") { "${it}${get(it) ?.let { value -> "=$value" }}" }
|
||||
|
||||
/**
|
||||
* Create query part which includes key=value pairs separated with &
|
||||
*/
|
||||
val List<QueryParam>.asUrlQuery: String
|
||||
get() = joinToString("&") { (key, value) -> "${key}${value ?.let { _ -> "=$value" }}" }
|
||||
|
||||
/**
|
||||
* Create query part which includes key=value pairs separated with & and attach to receiver
|
||||
*/
|
||||
fun String.includeQueryParams(
|
||||
queryParams: QueryParams
|
||||
): String = "$this${if(queryParams.isNotEmpty()) "${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}" else ""}"
|
||||
|
||||
/**
|
||||
* Create query part which includes key=value pairs separated with & and attach to receiver
|
||||
*/
|
||||
fun String.includeQueryParams(
|
||||
queryParams: List<QueryParam>
|
||||
): String = "$this${if (contains("?")) "&" else "?"}${queryParams.asUrlQuery}"
|
||||
|
@@ -33,10 +33,11 @@ fun <T> Route.includeWebsocketHandling(
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>
|
||||
serializer: SerializationStrategy<T>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = includeWebsocketHandling(
|
||||
suburl,
|
||||
flow
|
||||
) {
|
||||
standardKtorSerialFormat.encodeDefault(serializer, it)
|
||||
serialFormat.encodeDefault(serializer, it)
|
||||
}
|
||||
|
@@ -3,12 +3,87 @@ package dev.inmo.micro_utils.ktor.server
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondBytes
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.util.pipeline.PipelineContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.*
|
||||
|
||||
class UnifiedRouter(
|
||||
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
private val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) {
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>
|
||||
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val defaultUnifiedRouter = UnifiedRouter()
|
||||
|
||||
suspend fun <T> ApplicationCall.unianswer(
|
||||
answerSerializer: SerializationStrategy<T>,
|
||||
|
7
matrix/build.gradle
Normal file
7
matrix/build.gradle
Normal file
@@ -0,0 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.matrix
|
||||
|
||||
typealias Matrix<T> = List<Row<T>>
|
@@ -0,0 +1,13 @@
|
||||
package dev.inmo.micro_utils.matrix
|
||||
|
||||
class MatrixBuilder<T> {
|
||||
private val mutMatrix: MutableList<List<T>> = ArrayList()
|
||||
val matrix: Matrix<T>
|
||||
get() = mutMatrix
|
||||
|
||||
fun row(t: List<T>) = mutMatrix.add(t)
|
||||
operator fun List<T>.unaryPlus() = row(this)
|
||||
}
|
||||
|
||||
fun <T> MatrixBuilder<T>.row(block: RowBuilder<T>.() -> Unit) = +RowBuilder<T>().also(block).row
|
||||
fun <T> MatrixBuilder<T>.row(vararg elements: T) = +elements.toList()
|
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.matrix
|
||||
|
||||
fun <T> matrix(block: MatrixBuilder<T>.() -> Unit): Matrix<T> {
|
||||
return MatrixBuilder<T>().also(block).matrix
|
||||
}
|
||||
|
||||
fun <T> flatMatrix(block: RowBuilder<T>.() -> Unit): Matrix<T> {
|
||||
return MatrixBuilder<T>().apply {
|
||||
row(block)
|
||||
}.matrix
|
||||
}
|
||||
|
||||
fun <T> flatMatrix(vararg elements: T): Matrix<T> {
|
||||
return MatrixBuilder<T>().apply {
|
||||
row { elements.forEach { +it } }
|
||||
}.matrix
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.matrix
|
||||
|
||||
typealias Row<T> = List<T>
|
@@ -0,0 +1,14 @@
|
||||
package dev.inmo.micro_utils.matrix
|
||||
|
||||
class RowBuilder<T> {
|
||||
private val mutRow: MutableList<T> = ArrayList()
|
||||
val row: Row<T>
|
||||
get() = mutRow
|
||||
|
||||
fun column(t: T) = mutRow.add(t)
|
||||
operator fun T.unaryPlus() = column(this)
|
||||
}
|
||||
|
||||
fun <T> row(block: RowBuilder<T>.() -> Unit): List<T> = RowBuilder<T>().also(block).row
|
||||
fun <T> RowBuilder<T>.columns(elements: List<T>) = elements.forEach(::column)
|
||||
fun <T> RowBuilder<T>.columns(vararg elements: T) = elements.forEach(::column)
|
@@ -0,0 +1,45 @@
|
||||
package dev.inmo.micro_utils.matrix
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class SimpleTest {
|
||||
@Test
|
||||
fun simpleTest() {
|
||||
val expected = listOf(
|
||||
listOf(1, 2, 3),
|
||||
listOf(4, 5, 6)
|
||||
)
|
||||
|
||||
val fromMatrixWithVarargs = matrix<Int> {
|
||||
row(1, 2, 3)
|
||||
row(4, 5, 6)
|
||||
}
|
||||
|
||||
val fromMatrixWithColumnsVarargs = matrix<Int> {
|
||||
row {
|
||||
columns(1, 2, 3)
|
||||
}
|
||||
row {
|
||||
columns(4, 5, 6)
|
||||
}
|
||||
}
|
||||
|
||||
val fromMatrixWithSimpleAdd = matrix<Int> {
|
||||
row {
|
||||
column(1)
|
||||
column(2)
|
||||
column(3)
|
||||
}
|
||||
row {
|
||||
column(4)
|
||||
column(5)
|
||||
column(6)
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(expected, fromMatrixWithVarargs)
|
||||
assertEquals(expected, fromMatrixWithColumnsVarargs)
|
||||
assertEquals(expected, fromMatrixWithSimpleAdd)
|
||||
}
|
||||
}
|
1
matrix/src/main/AndroidManifest.xml
Normal file
1
matrix/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.matrix"/>
|
@@ -4,7 +4,9 @@ project.group = "$group"
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
jvm {
|
||||
compilations.main.kotlinOptions.useIR = true
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
|
@@ -4,7 +4,9 @@ project.group = "$group"
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
jvm {
|
||||
compilations.main.kotlinOptions.useIR = true
|
||||
}
|
||||
js (BOTH) {
|
||||
browser()
|
||||
nodejs()
|
||||
|
@@ -22,6 +22,12 @@ interface Pagination {
|
||||
val size: Int
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical shortcut for comparison that page is 0
|
||||
*/
|
||||
inline val Pagination.isFirstPage
|
||||
get() = page == 0
|
||||
|
||||
/**
|
||||
* First number in index of objects. It can be used as offset for databases or other data sources
|
||||
*/
|
||||
|
@@ -0,0 +1,41 @@
|
||||
package dev.inmo.micro_utils.pagination.utils
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
|
||||
class PaginatedIterator<T>(
|
||||
pageSize: Int,
|
||||
private val countGetter: () -> Long,
|
||||
private val paginationResultGetter: Pagination.() -> PaginationResult<T>
|
||||
) : Iterator<T> {
|
||||
private var pagination = FirstPagePagination(pageSize)
|
||||
private val currentStack = mutableListOf<T>()
|
||||
override fun hasNext(): Boolean = currentStack.isNotEmpty() || (countGetter() < pagination.lastIndexExclusive)
|
||||
|
||||
override fun next(): T {
|
||||
if (currentStack.isEmpty()) {
|
||||
val resultPagination = paginationResultGetter.invoke(pagination)
|
||||
currentStack.addAll(resultPagination.results)
|
||||
require(currentStack.isNotEmpty()) { "There is no elements left" }
|
||||
pagination = resultPagination.nextPage()
|
||||
}
|
||||
return currentStack.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
class PaginatedIterable<T>(
|
||||
private val pageSize: Int,
|
||||
private val countGetter: () -> Long,
|
||||
private val paginationResultGetter: Pagination.() -> PaginationResult<T>
|
||||
) : Iterable<T> {
|
||||
override fun iterator(): Iterator<T> = PaginatedIterator(pageSize, countGetter, paginationResultGetter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will make iterable using incoming [countGetter] and [paginationResultGetter]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> makeIterable(
|
||||
noinline countGetter: () -> Long,
|
||||
pageSize: Int = defaultMediumPageSize,
|
||||
noinline paginationResultGetter: Pagination.() -> PaginationResult<T>
|
||||
): Iterable<T> = PaginatedIterable(pageSize, countGetter, paginationResultGetter)
|
@@ -1 +1 @@
|
||||
{"bintrayConfig":{"repo":"MicroUtils","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/MicroUtils","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]}}
|
||||
{"bintrayConfig":{"repo":"MicroUtils","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/MicroUtils","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror/src/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror","vcsUrl":"ssh://git@git.inmo.dev:8322/InsanusMokrassar/MicroUtils_mirror.git","includeGpgSigning":true,"publishToMavenCentral":true,"developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}]}}
|
@@ -1,27 +1,16 @@
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
task javadocsJar(type: Jar) {
|
||||
classifier = 'javadoc'
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
project.publishing.publications.all {
|
||||
// rename artifacts
|
||||
groupId "${project.group}"
|
||||
if (it.name.contains('kotlinMultiplatform')) {
|
||||
artifactId = "${project.name}"
|
||||
} else {
|
||||
artifactId = "${project.name}-$name"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications.all {
|
||||
artifact javadocsJar
|
||||
|
||||
pom {
|
||||
description = ""
|
||||
description = "It is set of projects with micro tools for avoiding of routines coding"
|
||||
name = "${project.name}"
|
||||
url = "https://git.inmo.dev/InsanusMokrassar/MicroUtils_mirror"
|
||||
|
||||
@@ -66,7 +55,22 @@ publishing {
|
||||
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
|
||||
}
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
credentials {
|
||||
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign publishing.publications
|
||||
}
|
||||
|
@@ -17,6 +17,11 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
jvmMain {
|
||||
dependencies {
|
||||
api internalProject("micro_utils.common")
|
||||
}
|
||||
}
|
||||
androidMain {
|
||||
dependencies {
|
||||
api "androidx.core:core-ktx:$core_ktx_version"
|
||||
|
@@ -36,6 +36,7 @@ interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
@@ -53,6 +54,7 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
add(toSet)
|
||||
}
|
||||
}
|
||||
typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
@@ -87,6 +89,7 @@ suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
||||
) = set(k, v.toList())
|
||||
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
keysAndValues: List<Pair<Key, List<Value>>>
|
||||
|
@@ -10,6 +10,7 @@ interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
|
||||
suspend fun contains(id: IdType): Boolean
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
|
||||
|
||||
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
|
||||
val <IdType> UpdatedValuePair<IdType, *>.id
|
||||
@@ -27,6 +28,7 @@ interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
|
||||
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
|
||||
suspend fun deleteById(ids: List<IdType>)
|
||||
}
|
||||
typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
|
||||
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
|
||||
vararg values: InputValueType
|
||||
@@ -40,3 +42,4 @@ suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectTyp
|
||||
|
||||
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
|
||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
|
||||
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>
|
@@ -1,7 +1,6 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
||||
@@ -12,6 +11,7 @@ interface ReadStandardKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun contains(key: Key): Boolean
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
|
||||
|
||||
interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
@@ -19,7 +19,9 @@ interface WriteStandardKeyValueRepo<Key, Value> : Repo {
|
||||
|
||||
suspend fun set(toSet: Map<Key, Value>)
|
||||
suspend fun unset(toUnset: List<Key>)
|
||||
suspend fun unsetWithValues(toUnset: List<Value>)
|
||||
}
|
||||
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
|
||||
vararg toSet: Pair<Key, Value>
|
||||
@@ -33,4 +35,17 @@ suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
|
||||
vararg k: Key
|
||||
) = unset(k.toList())
|
||||
|
||||
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
|
||||
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithValues(
|
||||
vararg v: Value
|
||||
) = unsetWithValues(v.toList())
|
||||
|
||||
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> {
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
||||
doWithPagination {
|
||||
keys(v, it).also {
|
||||
unset(it.results)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>
|
||||
|
@@ -105,6 +105,10 @@ open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
k.toOutKey()
|
||||
}
|
||||
)
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<FromValue>) = to.unsetWithValues(
|
||||
toUnset.map { it.toOutValue() }
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
|
@@ -0,0 +1,183 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.common.Warning
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardWatchEventKinds.*
|
||||
|
||||
private inline val String.isAbsolute
|
||||
get() = startsWith(File.separator)
|
||||
|
||||
class FileReadStandardKeyValueRepo(
|
||||
private val folder: File
|
||||
) : ReadStandardKeyValueRepo<String, File> {
|
||||
init {
|
||||
folder.mkdirs()
|
||||
}
|
||||
|
||||
override suspend fun get(k: String): File? {
|
||||
val file = File(folder, k)
|
||||
if (file.exists()) {
|
||||
return file
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
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()
|
||||
if (reversed) {
|
||||
filesPaths.reverse()
|
||||
}
|
||||
return filesPaths.map { File(folder, it) }.createPaginationResult(
|
||||
resultPagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
if (reversed) {
|
||||
filesPaths.reverse()
|
||||
}
|
||||
return filesPaths.toList().createPaginationResult(
|
||||
resultPagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun keys(
|
||||
v: File,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<String> {
|
||||
val resultPagination = if (reversed) pagination.reverse(1L) else pagination
|
||||
return if (resultPagination.isFirstPage) {
|
||||
val fileSubpath = v.absolutePath.removePrefix(folder.absolutePath)
|
||||
if (fileSubpath == v.absolutePath) {
|
||||
emptyList()
|
||||
} else {
|
||||
listOf(fileSubpath)
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}.createPaginationResult(resultPagination, 1L)
|
||||
}
|
||||
|
||||
override suspend fun contains(key: String): Boolean {
|
||||
return File(folder, key).exists()
|
||||
}
|
||||
|
||||
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
|
||||
}
|
||||
|
||||
/**
|
||||
* Files watching will not correctly works on Android with version of API lower than API 26
|
||||
*/
|
||||
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
||||
class FileWriteStandardKeyValueRepo(
|
||||
private val folder: File,
|
||||
filesChangedProcessingScope: CoroutineScope? = null
|
||||
) : WriteStandardKeyValueRepo<String, File> {
|
||||
private val _onNewValue = MutableSharedFlow<Pair<String, File>>()
|
||||
override val onNewValue: Flow<Pair<String, File>> = _onNewValue.asSharedFlow()
|
||||
private val _onValueRemoved = MutableSharedFlow<String>()
|
||||
override val onValueRemoved: Flow<String> = _onValueRemoved.asSharedFlow()
|
||||
|
||||
init {
|
||||
folder.mkdirs()
|
||||
filesChangedProcessingScope ?.let {
|
||||
it.launch {
|
||||
try {
|
||||
val folderPath = folder.toPath()
|
||||
while (isActive) {
|
||||
val key = try {
|
||||
folderPath.register(FileSystems.getDefault().newWatchService(), ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE)
|
||||
} catch (e: Exception) {
|
||||
// add verbose way to show that file watching is not working
|
||||
return@launch
|
||||
}
|
||||
|
||||
for (event in key.pollEvents()) {
|
||||
val relativeFilePath = (event.context() as? Path) ?: continue
|
||||
val file = relativeFilePath.toFile()
|
||||
val relativePath = file.toRelativeString(folder)
|
||||
|
||||
when (event.kind()) {
|
||||
ENTRY_CREATE, ENTRY_MODIFY -> {
|
||||
launch {
|
||||
_onNewValue.emit(relativePath to file)
|
||||
}
|
||||
}
|
||||
ENTRY_DELETE -> {
|
||||
launch {
|
||||
_onValueRemoved.emit(relativePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key.isValid || folder.exists()) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// add verbose way to notify that this functionality is disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun set(toSet: Map<String, File>) {
|
||||
supervisorScope {
|
||||
toSet.map { (filename, fileSource) ->
|
||||
launch {
|
||||
val file = File(folder, filename)
|
||||
|
||||
file.delete()
|
||||
fileSource.copyTo(file, overwrite = true)
|
||||
_onNewValue.emit(filename to file)
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
|
||||
override suspend fun unset(toUnset: List<String>) {
|
||||
toUnset.forEach {
|
||||
val file = File(folder, it)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
_onValueRemoved.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<File>) {
|
||||
val keys = toUnset.mapNotNull { v ->
|
||||
val key = v.absolutePath.removePrefix(folder.absolutePath)
|
||||
if (key != v.absolutePath) {
|
||||
key
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
unset(keys)
|
||||
}
|
||||
}
|
||||
|
||||
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
|
||||
class FileStandardKeyValueRepo(
|
||||
folder: File,
|
||||
filesChangedProcessingScope: CoroutineScope? = null
|
||||
) : StandardKeyValueRepo<String, File>,
|
||||
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
|
||||
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder)
|
@@ -10,21 +10,6 @@ import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
private data class CallbackContinuationPair<T> (
|
||||
val callback: suspend SQLiteDatabase.() -> T,
|
||||
val continuation: Continuation<T>
|
||||
) {
|
||||
suspend fun SQLiteDatabase.execute() {
|
||||
safely(
|
||||
{
|
||||
continuation.resumeWithException(it)
|
||||
}
|
||||
) {
|
||||
continuation.resume(callback())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StandardSQLHelper(
|
||||
context: Context,
|
||||
name: String,
|
||||
|
@@ -3,20 +3,18 @@ package dev.inmo.micro_utils.repos.crud
|
||||
import android.content.ContentValues
|
||||
import dev.inmo.micro_utils.common.mapNotNullA
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
helper: StandardSQLHelper
|
||||
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper) {
|
||||
protected val newObjectsChannel = BroadcastChannel<ObjectType>(64)
|
||||
protected val updateObjectsChannel = BroadcastChannel<ObjectType>(64)
|
||||
protected val deleteObjectsIdsChannel = BroadcastChannel<IdType>(64)
|
||||
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asFlow()
|
||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asFlow()
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asFlow()
|
||||
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
||||
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64)
|
||||
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(64)
|
||||
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
|
||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
|
||||
|
||||
protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
|
||||
|
||||
@@ -42,7 +40,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
||||
}
|
||||
}.also {
|
||||
it.forEach {
|
||||
newObjectsChannel.send(it)
|
||||
newObjectsChannel.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +57,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
||||
}
|
||||
}
|
||||
deleted.forEach {
|
||||
deleteObjectsIdsChannel.send(it)
|
||||
deleteObjectsIdsChannel.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +74,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
||||
}
|
||||
}
|
||||
return getById(id) ?.also {
|
||||
updateObjectsChannel.send(it)
|
||||
updateObjectsChannel.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +93,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
|
||||
getById(it.first)
|
||||
}.also {
|
||||
it.forEach {
|
||||
updateObjectsChannel.send(it)
|
||||
updateObjectsChannel.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,10 +8,7 @@ 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.repos.StandardKeyValueRepo
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
private val cache = HashMap<String, KeyValueStore<*>>()
|
||||
|
||||
@@ -37,11 +34,11 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
null
|
||||
}
|
||||
|
||||
private val onNewValueChannel = BroadcastChannel<Pair<String, T>>(Channel.BUFFERED)
|
||||
private val onValueRemovedChannel = BroadcastChannel<String>(Channel.BUFFERED)
|
||||
private val onNewValueChannel = MutableSharedFlow<Pair<String, T>>()
|
||||
private val _onValueRemovedFlow = MutableSharedFlow<String>()
|
||||
|
||||
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asFlow()
|
||||
override val onValueRemoved: Flow<String> = onValueRemovedChannel.asFlow()
|
||||
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asSharedFlow()
|
||||
override val onValueRemoved: Flow<String> = _onValueRemovedFlow.asSharedFlow()
|
||||
|
||||
init {
|
||||
cachedData ?.let {
|
||||
@@ -131,7 +128,7 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
}
|
||||
}
|
||||
toSet.forEach { (k, v) ->
|
||||
onNewValueChannel.send(k to v)
|
||||
onNewValueChannel.emit(k to v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +136,18 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
sharedPreferences.edit {
|
||||
toUnset.forEach { remove(it) }
|
||||
}
|
||||
toUnset.forEach { onValueRemovedChannel.send(it) }
|
||||
toUnset.forEach { _onValueRemovedFlow.emit(it) }
|
||||
}
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<T>) {
|
||||
val keysToRemove = sharedPreferences.all.mapNotNull { if (it.value in toUnset) it.key else null }
|
||||
sharedPreferences.edit {
|
||||
keysToRemove.map {
|
||||
remove(it)
|
||||
}
|
||||
}
|
||||
keysToRemove.forEach {
|
||||
_onValueRemovedFlow.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -32,11 +32,11 @@ class AndroidSQLStandardVersionsRepoProxy(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getTableVersion(table: String): Int? = database.writableTransaction {
|
||||
override suspend fun getTableVersion(tableName: String): Int? = database.writableTransaction {
|
||||
select(
|
||||
tableName,
|
||||
this@AndroidSQLStandardVersionsRepoProxy.tableName,
|
||||
selection = "$tableNameColumnName=?",
|
||||
selectionArgs = arrayOf(table),
|
||||
selectionArgs = arrayOf(tableName),
|
||||
limit = limitClause(1)
|
||||
).use {
|
||||
if (it.moveToFirst()) {
|
||||
@@ -47,16 +47,16 @@ class AndroidSQLStandardVersionsRepoProxy(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateTableVersion(table: String, version: Int) {
|
||||
override suspend fun updateTableVersion(tableName: String, version: Int) {
|
||||
database.writableTransaction {
|
||||
val updated = update(
|
||||
tableName,
|
||||
this@AndroidSQLStandardVersionsRepoProxy.tableName,
|
||||
contentValuesOf(tableVersionColumnName to version),
|
||||
"$tableNameColumnName=?",
|
||||
arrayOf(table)
|
||||
arrayOf(tableName)
|
||||
) > 0
|
||||
if (!updated) {
|
||||
insert(tableName, null, contentValuesOf(tableNameColumnName to table, tableVersionColumnName to version))
|
||||
insert(this@AndroidSQLStandardVersionsRepoProxy.tableName, null, contentValuesOf(tableNameColumnName to tableName, tableVersionColumnName to version))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ package dev.inmo.micro_utils.repos.exposed
|
||||
|
||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
|
||||
import kotlinx.coroutines.channels.BroadcastChannel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
@@ -25,8 +24,10 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
|
||||
|
||||
@Deprecated("Will be removed in near major update. Override open fun with the same name instead")
|
||||
abstract val InsertStatement<Number>.asObject: ObjectType
|
||||
abstract val selectByIds: SqlExpressionBuilder.(List<out IdType>) -> Op<Boolean>
|
||||
protected open fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType = asObject
|
||||
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)
|
||||
@@ -34,7 +35,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
|
||||
private fun createWithoutNotification(value: InputValueType): ObjectType {
|
||||
return transaction(database) {
|
||||
insert { insert(value, it) }.asObject
|
||||
insert { insert(value, it) }.asObject(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +43,8 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
onBeforeCreate(values)
|
||||
return transaction(db = database) {
|
||||
values.map { value -> createWithoutNotification(value) }
|
||||
}.also {
|
||||
it.forEach {
|
||||
newObjectsChannel.emit(it)
|
||||
}
|
||||
}.onEach {
|
||||
newObjectsChannel.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +60,11 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
}
|
||||
}.let {
|
||||
if (it > 0) {
|
||||
select {
|
||||
selectById(this, id)
|
||||
}.limit(1).firstOrNull() ?.asObject
|
||||
transaction(db = database) {
|
||||
select {
|
||||
selectById(this, id)
|
||||
}.limit(1).firstOrNull() ?.asObject
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -83,13 +84,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
return (
|
||||
transaction(db = database) {
|
||||
values.map { (id, value) -> updateWithoutNotification(id, value) }
|
||||
}.filter {
|
||||
it != null
|
||||
} as List<ObjectType>
|
||||
).also {
|
||||
it.forEach {
|
||||
updateObjectsChannel.emit(it)
|
||||
}
|
||||
}.filterNotNull()
|
||||
).onEach {
|
||||
updateObjectsChannel.emit(it)
|
||||
}
|
||||
}
|
||||
protected open suspend fun onBeforeDelete(ids: List<IdType>) {}
|
||||
|
@@ -60,4 +60,16 @@ open class ExposedKeyValueRepo<Key, Value>(
|
||||
_onValueRemoved.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||
transaction(database) {
|
||||
toUnset.flatMap {
|
||||
val keys = select { valueColumn.eq(it) }.mapNotNull { it[keyColumn] }
|
||||
deleteWhere { keyColumn.inList(keys) }
|
||||
keys
|
||||
}
|
||||
}.distinct().forEach {
|
||||
_onValueRemoved.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -76,6 +76,15 @@ class WriteMapKeyValueRepo<Key, Value>(
|
||||
map.remove(k) ?.also { _ -> _onValueRemoved.emit(k) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||
map.forEach {
|
||||
if (it.value in toUnset) {
|
||||
map.remove(it.key)
|
||||
_onValueRemoved.emit(it.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MapKeyValueRepo<Key, Value>(
|
||||
|
@@ -1,8 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.encodeUrlQueryValue
|
||||
import dev.inmo.micro_utils.ktor.client.uniget
|
||||
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||
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.ReadStandardCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
@@ -12,41 +11,52 @@ import kotlinx.serialization.builtins.serializer
|
||||
|
||||
class KtorReadStandardCrudRepo<ObjectType, IdType> (
|
||||
private val baseUrl: String,
|
||||
private val client: HttpClient = HttpClient(),
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val objectsSerializer: KSerializer<ObjectType>,
|
||||
private val objectsSerializerNullable: KSerializer<ObjectType?>,
|
||||
private val idsSerializer: KSerializer<IdType>
|
||||
) : ReadStandardCRUDRepo<ObjectType, IdType> {
|
||||
private val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
|
||||
|
||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = client.uniget(
|
||||
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? = client.uniget(
|
||||
override suspend fun getById(id: IdType): ObjectType? = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
getByIdRouting,
|
||||
mapOf(
|
||||
"id" to idsSerializer.encodeUrlQueryValue(id)
|
||||
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
|
||||
)
|
||||
),
|
||||
objectsSerializerNullable
|
||||
)
|
||||
|
||||
override suspend fun contains(id: IdType): Boolean = client.uniget(
|
||||
override suspend fun contains(id: IdType): Boolean = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
containsRouting,
|
||||
mapOf(
|
||||
"id" to idsSerializer.encodeUrlQueryValue(id)
|
||||
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
|
||||
)
|
||||
),
|
||||
Boolean.serializer()
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = client.uniget(
|
||||
override suspend fun count(): Long = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
countRouting
|
||||
|
@@ -1,5 +1,8 @@
|
||||
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
|
||||
@@ -7,7 +10,7 @@ import kotlinx.serialization.KSerializer
|
||||
class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
|
||||
baseUrl: String,
|
||||
baseSubpart: String,
|
||||
client: HttpClient,
|
||||
unifiedRequester: UnifiedRequester,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
@@ -15,16 +18,29 @@ class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
|
||||
) : StandardCRUDRepo<ObjectType, IdType, InputValue>,
|
||||
ReadStandardCRUDRepo<ObjectType, IdType> by KtorReadStandardCrudRepo(
|
||||
"$baseUrl/$baseSubpart",
|
||||
client,
|
||||
unifiedRequester,
|
||||
objectsSerializer,
|
||||
objectsNullableSerializer,
|
||||
idsSerializer
|
||||
),
|
||||
WriteStandardCRUDRepo<ObjectType, IdType, InputValue> by KtorWriteStandardCrudRepo(
|
||||
"$baseUrl/$baseSubpart",
|
||||
client,
|
||||
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,7 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.*
|
||||
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
@@ -12,7 +12,7 @@ import kotlinx.serialization.builtins.*
|
||||
|
||||
class KtorWriteStandardCrudRepo<ObjectType, IdType, InputValue> (
|
||||
private val baseUrl: String,
|
||||
private val client: HttpClient = HttpClient(),
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val objectsSerializer: KSerializer<ObjectType>,
|
||||
private val objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
private val inputsSerializer: KSerializer<InputValue>,
|
||||
@@ -27,39 +27,51 @@ class KtorWriteStandardCrudRepo<ObjectType, IdType, InputValue> (
|
||||
)
|
||||
private val listInputUpdateSerializer = ListSerializer(inputUpdateSerializer)
|
||||
|
||||
override val newObjectsFlow: Flow<ObjectType> = client.createStandardWebsocketFlow(
|
||||
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> = client.createStandardWebsocketFlow(
|
||||
override val updatedObjectsFlow: Flow<ObjectType> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
|
||||
deserializer = objectsSerializer
|
||||
)
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> = client.createStandardWebsocketFlow(
|
||||
override val deletedObjectsIdsFlow: Flow<IdType> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
|
||||
deserializer = idsSerializer
|
||||
)
|
||||
|
||||
|
||||
override suspend fun create(values: List<InputValue>): List<ObjectType> = client.unipost(
|
||||
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? = client.unipost(
|
||||
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> = client.unipost(
|
||||
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>) = client.unipost(
|
||||
override suspend fun deleteById(ids: List<IdType>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(baseUrl, deleteByIdRouting),
|
||||
BodyPair(listIdsSerializer, ids),
|
||||
Unit.serializer()
|
||||
|
@@ -1,80 +1,90 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.encodeUrlQueryValue
|
||||
import dev.inmo.micro_utils.ktor.client.uniget
|
||||
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||
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.ReadStandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
class KtorReadStandardKeyValueRepo<Key, Value> (
|
||||
private var baseUrl: String,
|
||||
private var client: HttpClient = HttpClient(),
|
||||
private var keySerializer: KSerializer<Key>,
|
||||
private var valueSerializer: KSerializer<Value>,
|
||||
private var valueNullableSerializer: KSerializer<Value?>,
|
||||
private val baseUrl: String,
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>,
|
||||
private val valueNullableSerializer: KSerializer<Value?>
|
||||
) : ReadStandardKeyValueRepo<Key, Value> {
|
||||
override suspend fun get(k: Key): Value? = client.uniget(
|
||||
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 keySerializer.encodeUrlQueryValue(k)
|
||||
keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, k)
|
||||
)
|
||||
),
|
||||
valueNullableSerializer
|
||||
)
|
||||
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = client.uniget(
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
valuesRoute,
|
||||
mapOf(
|
||||
reversedParameterName to Boolean.serializer().encodeUrlQueryValue(reversed)
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
PaginationResult.serializer(valueSerializer)
|
||||
)
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = client.uniget(
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
reversedParameterName to Boolean.serializer().encodeUrlQueryValue(reversed)
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
PaginationResult.serializer(keySerializer)
|
||||
)
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = client.uniget(
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
valueParameterName to valueSerializer.encodeUrlQueryValue(v),
|
||||
reversedParameterName to Boolean.serializer().encodeUrlQueryValue(reversed)
|
||||
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 = client.uniget(
|
||||
override suspend fun contains(key: Key): Boolean = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
containsRoute,
|
||||
mapOf(
|
||||
keyParameterName to keySerializer.encodeUrlQueryValue(key)
|
||||
keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, key)
|
||||
),
|
||||
),
|
||||
Boolean.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = client.uniget(
|
||||
override suspend fun count(): Long = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
countRoute,
|
||||
|
@@ -1,27 +1,41 @@
|
||||
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.KSerializer
|
||||
import kotlinx.serialization.*
|
||||
|
||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||
class KtorStandartKeyValueRepo<K, V> (
|
||||
baseUrl: String,
|
||||
baseSubpart: String,
|
||||
client: HttpClient = HttpClient(),
|
||||
unifiedRequester: UnifiedRequester,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>
|
||||
) : StandardKeyValueRepo<K, V>,
|
||||
ReadStandardKeyValueRepo<K, V> by KtorReadStandardKeyValueRepo(
|
||||
"$baseUrl/$baseSubpart",
|
||||
client,
|
||||
unifiedRequester,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
valueNullableSerializer
|
||||
),
|
||||
WriteStandardKeyValueRepo<K, V> by KtorWriteStandardKeyValueRepo(
|
||||
"$baseUrl/$baseSubpart",
|
||||
client,
|
||||
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,7 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.*
|
||||
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.WriteStandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import io.ktor.client.HttpClient
|
||||
@@ -11,23 +11,35 @@ import kotlinx.serialization.builtins.*
|
||||
|
||||
class KtorWriteStandardKeyValueRepo<K, V> (
|
||||
private var baseUrl: String,
|
||||
private var client: HttpClient = HttpClient(),
|
||||
private var unifiedRequester: UnifiedRequester,
|
||||
private var keySerializer: KSerializer<K>,
|
||||
private var valueSerializer: KSerializer<V>,
|
||||
) : WriteStandardKeyValueRepo<K, V> {
|
||||
private val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
||||
private val keysListSerializer = ListSerializer(keySerializer)
|
||||
override val onNewValue: Flow<Pair<K, V>> = client.createStandardWebsocketFlow(
|
||||
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> = client.createStandardWebsocketFlow(
|
||||
override val onValueRemoved: Flow<K> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
deserializer = keySerializer
|
||||
)
|
||||
|
||||
override suspend fun set(toSet: Map<K, V>) = client.unipost(
|
||||
override suspend fun set(toSet: Map<K, V>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
setRoute
|
||||
@@ -36,7 +48,7 @@ class KtorWriteStandardKeyValueRepo<K, V> (
|
||||
Unit.serializer()
|
||||
)
|
||||
|
||||
override suspend fun unset(toUnset: List<K>) = client.unipost(
|
||||
override suspend fun unset(toUnset: List<K>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
unsetRoute,
|
||||
@@ -44,4 +56,13 @@ class KtorWriteStandardKeyValueRepo<K, V> (
|
||||
BodyPair(keysListSerializer, toUnset),
|
||||
Unit.serializer()
|
||||
)
|
||||
|
||||
override suspend fun unsetWithValues(toUnset: List<V>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
unsetWithValuesRoute,
|
||||
),
|
||||
BodyPair(valuesListSerializer, toUnset),
|
||||
Unit.serializer()
|
||||
)
|
||||
}
|
||||
|
@@ -1,5 +1,8 @@
|
||||
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
|
||||
@@ -7,19 +10,28 @@ import kotlinx.serialization.KSerializer
|
||||
class KtorOneToManyKeyValueRepo<Key, Value>(
|
||||
baseUrl: String,
|
||||
baseSubpart: String,
|
||||
client: HttpClient,
|
||||
unifiedRequester: UnifiedRequester,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
) : OneToManyKeyValueRepo<Key, Value>,
|
||||
ReadOneToManyKeyValueRepo<Key, Value> by KtorReadOneToManyKeyValueRepo<Key, Value> (
|
||||
"$baseUrl/$baseSubpart",
|
||||
client,
|
||||
unifiedRequester,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
),
|
||||
WriteOneToManyKeyValueRepo<Key, Value> by KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
"$baseUrl/$baseSubpart",
|
||||
client,
|
||||
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,8 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.client.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.client.encodeUrlQueryValue
|
||||
import dev.inmo.micro_utils.ktor.client.uniget
|
||||
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
|
||||
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.ReadOneToManyKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
||||
@@ -15,81 +14,89 @@ import kotlinx.serialization.builtins.serializer
|
||||
|
||||
class KtorReadOneToManyKeyValueRepo<Key, Value> (
|
||||
private val baseUrl: String,
|
||||
private val client: HttpClient = HttpClient(),
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>,
|
||||
private val valueSerializer: KSerializer<Value>
|
||||
) : ReadOneToManyKeyValueRepo<Key, Value> {
|
||||
private val paginationValueResultSerializer = PaginationResult.serializer(valueSerializer)
|
||||
private val paginationKeyResultSerializer = PaginationResult.serializer(keySerializer)
|
||||
|
||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> = client.uniget(
|
||||
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 keySerializer.encodeUrlQueryValue(k),
|
||||
reversedParameterName to Boolean.serializer().encodeUrlQueryValue(reversed)
|
||||
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> = client.uniget(
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
reversedParameterName to Boolean.serializer().encodeUrlQueryValue(reversed)
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
paginationKeyResultSerializer
|
||||
)
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = client.uniget(
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
keysRoute,
|
||||
mapOf(
|
||||
valueParameterName to valueSerializer.encodeUrlQueryValue(v),
|
||||
reversedParameterName to Boolean.serializer().encodeUrlQueryValue(reversed)
|
||||
valueParameterName to unifiedRequester.encodeUrlQueryValue(valueSerializer, v),
|
||||
reversedParameterName to unifiedRequester.encodeUrlQueryValue(Boolean.serializer(), reversed)
|
||||
) + pagination.asUrlQueryParts
|
||||
),
|
||||
paginationKeyResultSerializer
|
||||
)
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = client.uniget(
|
||||
override suspend fun contains(k: Key): Boolean = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
containsByKeyRoute,
|
||||
mapOf(keyParameterName to keySerializer.encodeUrlQueryValue(k))
|
||||
mapOf(keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, k))
|
||||
),
|
||||
Boolean.serializer()
|
||||
)
|
||||
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = client.uniget(
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
containsByKeyValueRoute,
|
||||
mapOf(
|
||||
keyParameterName to keySerializer.encodeUrlQueryValue(k),
|
||||
valueParameterName to valueSerializer.encodeUrlQueryValue(v),
|
||||
keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, k),
|
||||
valueParameterName to unifiedRequester.encodeUrlQueryValue(valueSerializer, v),
|
||||
)
|
||||
),
|
||||
Boolean.serializer()
|
||||
)
|
||||
|
||||
override suspend fun count(k: Key): Long = client.uniget(
|
||||
override suspend fun count(k: Key): Long = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
countByKeyRoute,
|
||||
mapOf(
|
||||
keyParameterName to keySerializer.encodeUrlQueryValue(k)
|
||||
keyParameterName to unifiedRequester.encodeUrlQueryValue(keySerializer, k)
|
||||
)
|
||||
),
|
||||
Long.serializer()
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = client.uniget(
|
||||
override suspend fun count(): Long = unifiedRequester.uniget(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
countRoute,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
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.buildStandardUrl
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||
import io.ktor.client.HttpClient
|
||||
@@ -11,26 +11,37 @@ import kotlinx.serialization.builtins.*
|
||||
|
||||
class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
private val baseUrl: String,
|
||||
private val client: HttpClient = HttpClient(),
|
||||
private val unifiedRequester: UnifiedRequester,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>
|
||||
) : WriteOneToManyKeyValueRepo<Key, Value> {
|
||||
private val keyValueSerializer = PairSerializer(keySerializer, valueSerializer)
|
||||
private val keyValueMapSerializer = MapSerializer(keySerializer, ListSerializer(valueSerializer))
|
||||
override val onNewValue: Flow<Pair<Key, Value>> = client.createStandardWebsocketFlow(
|
||||
|
||||
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>> = client.createStandardWebsocketFlow(
|
||||
override val onValueRemoved: Flow<Pair<Key, Value>> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onValueRemovedRoute),
|
||||
deserializer = keyValueSerializer
|
||||
)
|
||||
override val onDataCleared: Flow<Key> = client.createStandardWebsocketFlow(
|
||||
override val onDataCleared: Flow<Key> = unifiedRequester.createStandardWebsocketFlow(
|
||||
buildStandardUrl(baseUrl, onDataClearedRoute),
|
||||
deserializer = keySerializer
|
||||
)
|
||||
|
||||
override suspend fun remove(toRemove: Map<Key, List<Value>>) = client.unipost(
|
||||
override suspend fun remove(toRemove: Map<Key, List<Value>>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
removeRoute,
|
||||
@@ -39,7 +50,7 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun add(toAdd: Map<Key, List<Value>>) = client.unipost(
|
||||
override suspend fun add(toAdd: Map<Key, List<Value>>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
addRoute,
|
||||
@@ -47,7 +58,7 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
BodyPair(keyValueMapSerializer, toAdd),
|
||||
Unit.serializer(),
|
||||
)
|
||||
override suspend fun clear(k: Key) = client.unipost(
|
||||
override suspend fun clear(k: Key) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
clearRoute,
|
||||
@@ -56,7 +67,7 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun set(toSet: Map<Key, List<Value>>) = client.unipost(
|
||||
override suspend fun set(toSet: Map<Key, List<Value>>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
setRoute,
|
||||
|
@@ -10,3 +10,4 @@ const val onNewValueRoute = "onNewValue"
|
||||
const val onValueRemovedRoute = "onValueRemoved"
|
||||
const val setRoute = "set"
|
||||
const val unsetRoute = "unset"
|
||||
const val unsetWithValuesRoute = "unsetWithValues"
|
@@ -1,12 +1,14 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.server.decodeUrlQueryValueOrSendError
|
||||
import dev.inmo.micro_utils.ktor.server.unianswer
|
||||
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.ReadStandardCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import kotlinx.serialization.KSerializer
|
||||
@@ -16,47 +18,65 @@ fun <ObjectType, IdType> Route.configureReadStandardCrudRepoRoutes(
|
||||
originalRepo: ReadStandardCRUDRepo<ObjectType, IdType>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
idsSerializer: KSerializer<IdType>
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
|
||||
|
||||
get(getByPaginationRouting) {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
unifiedRouter.apply {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
|
||||
call.unianswer(
|
||||
paginationResultSerializer,
|
||||
originalRepo.getByPagination(pagination)
|
||||
)
|
||||
unianswer(
|
||||
paginationResultSerializer,
|
||||
originalRepo.getByPagination(pagination)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(getByIdRouting) {
|
||||
val id = call.decodeUrlQueryValueOrSendError(
|
||||
"id",
|
||||
idsSerializer
|
||||
) ?: return@get
|
||||
unifiedRouter.apply {
|
||||
val id = decodeUrlQueryValueOrSendError(
|
||||
"id",
|
||||
idsSerializer
|
||||
) ?: return@get
|
||||
|
||||
call.unianswer(
|
||||
objectsNullableSerializer,
|
||||
originalRepo.getById(id)
|
||||
)
|
||||
unianswer(
|
||||
objectsNullableSerializer,
|
||||
originalRepo.getById(id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(containsRouting) {
|
||||
val id = call.decodeUrlQueryValueOrSendError(
|
||||
"id",
|
||||
idsSerializer
|
||||
) ?: return@get
|
||||
unifiedRouter.apply {
|
||||
val id = decodeUrlQueryValueOrSendError(
|
||||
"id",
|
||||
idsSerializer
|
||||
) ?: return@get
|
||||
|
||||
call.unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(id)
|
||||
)
|
||||
unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(countRouting) {
|
||||
call.unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
unifiedRouter.apply {
|
||||
unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <ObjectType, IdType> Route.configureReadStandardCrudRepoRoutes(
|
||||
originalRepo: ReadStandardCRUDRepo<ObjectType, IdType>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureReadStandardCrudRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
||||
|
@@ -1,6 +1,11 @@
|
||||
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.StandardCRUDRepo
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.route
|
||||
import kotlinx.serialization.KSerializer
|
||||
@@ -11,10 +16,24 @@ fun <ObjectType, IdType, InputValue> Route.configureStandardCrudRepoRoutes(
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
route(baseSubpart) {
|
||||
configureReadStandardCrudRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer)
|
||||
configureWriteStandardCrudRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer)
|
||||
configureReadStandardCrudRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, unifiedRouter)
|
||||
configureWriteStandardCrudRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, unifiedRouter)
|
||||
}
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType, InputValue> Route.configureStandardCrudRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: StandardCRUDRepo<ObjectType, IdType, InputValue>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureStandardCrudRepoRoutes(
|
||||
baseSubpart, originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
||||
)
|
||||
|
@@ -1,9 +1,12 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.post
|
||||
import kotlinx.serialization.KSerializer
|
||||
@@ -14,7 +17,8 @@ fun <ObjectType, IdType, InputValue> Route.configureWriteStandardCrudRepoRoutes(
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val listObjectsSerializer = ListSerializer(objectsSerializer)
|
||||
val listInputSerializer = ListSerializer(inputsSerializer)
|
||||
@@ -25,58 +29,80 @@ fun <ObjectType, IdType, InputValue> Route.configureWriteStandardCrudRepoRoutes(
|
||||
)
|
||||
val listInputUpdateSerializer = ListSerializer(inputUpdateSerializer)
|
||||
|
||||
includeWebsocketHandling(
|
||||
newObjectsFlowRouting,
|
||||
originalRepo.newObjectsFlow,
|
||||
objectsSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
updatedObjectsFlowRouting,
|
||||
originalRepo.updatedObjectsFlow,
|
||||
objectsSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
deletedObjectsIdsFlowRouting,
|
||||
originalRepo.deletedObjectsIdsFlow,
|
||||
idsSerializer
|
||||
)
|
||||
unifiedRouter.apply {
|
||||
includeWebsocketHandling(
|
||||
newObjectsFlowRouting,
|
||||
originalRepo.newObjectsFlow,
|
||||
objectsSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
updatedObjectsFlowRouting,
|
||||
originalRepo.updatedObjectsFlow,
|
||||
objectsSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
deletedObjectsIdsFlowRouting,
|
||||
originalRepo.deletedObjectsIdsFlow,
|
||||
idsSerializer
|
||||
)
|
||||
}
|
||||
|
||||
post(createRouting) {
|
||||
call.unianswer(
|
||||
listObjectsSerializer,
|
||||
originalRepo.create(
|
||||
call.uniload(listInputSerializer)
|
||||
unifiedRouter.apply {
|
||||
unianswer(
|
||||
listObjectsSerializer,
|
||||
originalRepo.create(
|
||||
uniload(listInputSerializer)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(updateRouting) {
|
||||
val (id, input) = call.uniload(inputUpdateSerializer)
|
||||
call.unianswer(
|
||||
objectsNullableSerializer,
|
||||
originalRepo.update(
|
||||
id, input
|
||||
unifiedRouter.apply {
|
||||
val (id, input) = uniload(inputUpdateSerializer)
|
||||
unianswer(
|
||||
objectsNullableSerializer,
|
||||
originalRepo.update(
|
||||
id, input
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(updateManyRouting) {
|
||||
val updates = call.uniload(listInputUpdateSerializer)
|
||||
call.unianswer(
|
||||
listObjectsSerializer,
|
||||
originalRepo.update(
|
||||
updates
|
||||
unifiedRouter.apply {
|
||||
val updates = uniload(listInputUpdateSerializer)
|
||||
unianswer(
|
||||
listObjectsSerializer,
|
||||
originalRepo.update(
|
||||
updates
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(deleteByIdRouting) {
|
||||
val ids = call.uniload(listIdsSerializer)
|
||||
call.unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.deleteById(
|
||||
ids
|
||||
unifiedRouter.apply {
|
||||
val ids = uniload(listIdsSerializer)
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.deleteById(
|
||||
ids
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType, InputValue> Route.configureWriteStandardCrudRepoRoutes(
|
||||
originalRepo: WriteStandardCRUDRepo<ObjectType, IdType, InputValue>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureWriteStandardCrudRepoRoutes(
|
||||
originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
||||
)
|
||||
|
@@ -1,16 +1,22 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
||||
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
|
||||
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.route
|
||||
import kotlinx.serialization.KSerializer
|
||||
|
||||
fun <K, V> Route.configureStandartKeyValueRepoRoutes(
|
||||
fun <K, V> Route.configureStandardKeyValueRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: StandardKeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
route(baseSubpart) {
|
||||
configureReadStandartKeyValueRepoRoutes(
|
||||
@@ -18,11 +24,23 @@ fun <K, V> Route.configureStandartKeyValueRepoRoutes(
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
valueNullableSerializer,
|
||||
unifiedRouter
|
||||
)
|
||||
configureWriteStandartKeyValueRepoRoutes(
|
||||
configureWriteStandardKeyValueRepoRoutes(
|
||||
originalRepo,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
unifiedRouter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun <K, V> Route.configureStandartKeyValueRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: StandardKeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureStandardKeyValueRepoRoutes(baseSubpart, originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
@@ -1,5 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.extractPagination
|
||||
@@ -7,6 +9,7 @@ import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import kotlinx.serialization.KSerializer
|
||||
@@ -17,62 +20,84 @@ fun <K, V> Route.configureReadStandartKeyValueRepoRoutes (
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
get(getRoute) {
|
||||
val key = call.decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
|
||||
call.unianswer(
|
||||
valueNullableSerializer,
|
||||
originalRepo.get(key)
|
||||
)
|
||||
unianswer(
|
||||
valueNullableSerializer,
|
||||
originalRepo.get(key)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(valuesRoute) {
|
||||
val parination = call.request.queryParameters.extractPagination;
|
||||
val reversed = call.decodeUrlQueryValueOrSendError(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: return@get
|
||||
unifiedRouter.apply {
|
||||
val parination = call.request.queryParameters.extractPagination;
|
||||
val reversed = decodeUrlQueryValueOrSendError(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: return@get
|
||||
|
||||
call.unianswer(
|
||||
PaginationResult.serializer(valueSerializer),
|
||||
originalRepo.values(parination, reversed)
|
||||
)
|
||||
unianswer(
|
||||
PaginationResult.serializer(valueSerializer),
|
||||
originalRepo.values(parination, reversed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(keysRoute) {
|
||||
val parination = call.request.queryParameters.extractPagination;
|
||||
val reversed = call.decodeUrlQueryValueOrSendError(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: return@get
|
||||
val value = call.decodeUrlQueryValue(valueParameterName, valueSerializer)
|
||||
unifiedRouter.apply {
|
||||
val parination = call.request.queryParameters.extractPagination;
|
||||
val reversed = decodeUrlQueryValueOrSendError(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: return@get
|
||||
val value = decodeUrlQueryValue(valueParameterName, valueSerializer)
|
||||
|
||||
call.unianswer(
|
||||
PaginationResult.serializer(keySerializer),
|
||||
value ?.let { originalRepo.keys(value, parination, reversed) } ?: originalRepo.keys(parination, reversed)
|
||||
)
|
||||
unianswer(
|
||||
PaginationResult.serializer(keySerializer),
|
||||
value?.let { originalRepo.keys(value, parination, reversed) } ?: originalRepo.keys(parination, reversed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(containsRoute) {
|
||||
val key = call.decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
|
||||
call.unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(key)
|
||||
)
|
||||
unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(key)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(countRoute) {
|
||||
call.unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
unifiedRouter.apply {
|
||||
unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <K, V> Route.configureReadStandartKeyValueRepoRoutes (
|
||||
originalRepo: ReadStandardKeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureReadStandartKeyValueRepoRoutes(
|
||||
originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
||||
)
|
||||
|
@@ -1,44 +1,70 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.repos.WriteStandardKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.post
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.*
|
||||
|
||||
fun <K, V> Route.configureWriteStandardKeyValueRepoRoutes (
|
||||
originalRepo: WriteStandardKeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
||||
val keysListSerializer = ListSerializer(keySerializer)
|
||||
val valuesListSerializer = ListSerializer(valueSerializer)
|
||||
unifiedRouter.apply {
|
||||
includeWebsocketHandling(
|
||||
onNewValueRoute,
|
||||
originalRepo.onNewValue,
|
||||
PairSerializer(keySerializer, valueSerializer)
|
||||
)
|
||||
|
||||
includeWebsocketHandling(
|
||||
onValueRemovedRoute,
|
||||
originalRepo.onValueRemoved,
|
||||
keySerializer
|
||||
)
|
||||
}
|
||||
|
||||
post(setRoute) {
|
||||
unifiedRouter.apply {
|
||||
val toSet = uniload(
|
||||
keyValueMapSerializer
|
||||
)
|
||||
|
||||
unianswer(Unit.serializer(), originalRepo.set(toSet))
|
||||
}
|
||||
}
|
||||
|
||||
post(unsetRoute) {
|
||||
unifiedRouter.apply {
|
||||
val toUnset = uniload(keysListSerializer)
|
||||
|
||||
unianswer(Unit.serializer(), originalRepo.unset(toUnset))
|
||||
}
|
||||
}
|
||||
|
||||
post(unsetWithValuesRoute) {
|
||||
unifiedRouter.apply {
|
||||
val toUnset = uniload(valuesListSerializer)
|
||||
|
||||
unianswer(Unit.serializer(), originalRepo.unsetWithValues(toUnset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <K, V> Route.configureWriteStandartKeyValueRepoRoutes (
|
||||
originalRepo: WriteStandardKeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
) {
|
||||
val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
||||
val keysListSerializer = ListSerializer(keySerializer)
|
||||
includeWebsocketHandling(
|
||||
onNewValueRoute,
|
||||
originalRepo.onNewValue,
|
||||
PairSerializer(keySerializer, valueSerializer)
|
||||
)
|
||||
|
||||
includeWebsocketHandling(
|
||||
onValueRemovedRoute,
|
||||
originalRepo.onValueRemoved,
|
||||
keySerializer
|
||||
)
|
||||
|
||||
post(setRoute) {
|
||||
val toSet = call.uniload(
|
||||
keyValueMapSerializer
|
||||
)
|
||||
|
||||
call.unianswer(Unit.serializer(), originalRepo.set(toSet))
|
||||
}
|
||||
|
||||
post(unsetRoute) {
|
||||
val toUnset = call.uniload(keysListSerializer)
|
||||
|
||||
call.unianswer(Unit.serializer(), originalRepo.unset(toUnset))
|
||||
}
|
||||
}
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureWriteStandardKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
||||
|
@@ -1,6 +1,11 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
||||
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
|
||||
import dev.inmo.micro_utils.repos.OneToManyKeyValueRepo
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.route
|
||||
import kotlinx.serialization.KSerializer
|
||||
@@ -9,10 +14,22 @@ fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: OneToManyKeyValueRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSealizer: KSerializer<Value>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
route(baseSubpart) {
|
||||
configureOneToManyReadKeyValueRepoRoutes(originalRepo, keySerializer, valueSealizer)
|
||||
configureOneToManyWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSealizer)
|
||||
configureOneToManyReadKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, unifiedRouter)
|
||||
configureOneToManyWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, unifiedRouter)
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: OneToManyKeyValueRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureOneToManyKeyValueRepoRoutes(
|
||||
baseSubpart, originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
||||
)
|
@@ -1,5 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.extractPagination
|
||||
@@ -10,6 +12,7 @@ import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import kotlinx.serialization.KSerializer
|
||||
@@ -18,89 +21,110 @@ import kotlinx.serialization.builtins.serializer
|
||||
fun <Key, Value> Route.configureOneToManyReadKeyValueRepoRoutes(
|
||||
originalRepo: ReadOneToManyKeyValueRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSealizer: KSerializer<Value>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val paginationKeyResult = PaginationResult.serializer(keySerializer)
|
||||
val paginationValueResult = PaginationResult.serializer(valueSealizer)
|
||||
val paginationValueResult = PaginationResult.serializer(valueSerializer)
|
||||
|
||||
get(getRoute) {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
val key = call.decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
val reversed = call.decodeUrlQueryValue(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: false
|
||||
unifiedRouter.apply {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
val reversed = decodeUrlQueryValue(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: false
|
||||
|
||||
call.unianswer(
|
||||
paginationValueResult,
|
||||
originalRepo.get(key, pagination, reversed)
|
||||
)
|
||||
unianswer(
|
||||
paginationValueResult,
|
||||
originalRepo.get(key, pagination, reversed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(keysRoute) {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
val reversed = call.decodeUrlQueryValue(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: false
|
||||
val value: Value? = call.decodeUrlQueryValue(
|
||||
valueParameterName,
|
||||
valueSealizer
|
||||
)
|
||||
unifiedRouter.apply {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
val reversed = decodeUrlQueryValue(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: false
|
||||
val value: Value? = decodeUrlQueryValue(
|
||||
valueParameterName,
|
||||
valueSerializer
|
||||
)
|
||||
|
||||
call.unianswer(
|
||||
paginationKeyResult,
|
||||
value ?.let { originalRepo.keys(value, pagination, reversed) } ?: originalRepo.keys(pagination, reversed)
|
||||
)
|
||||
unianswer(
|
||||
paginationKeyResult,
|
||||
value?.let { originalRepo.keys(value, pagination, reversed) } ?: originalRepo.keys(pagination, reversed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(containsByKeyRoute) {
|
||||
val key = call.decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
|
||||
call.unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(key)
|
||||
)
|
||||
unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(key)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(containsByKeyValueRoute) {
|
||||
val key = call.decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
val value = call.decodeUrlQueryValueOrSendError(
|
||||
valueParameterName,
|
||||
valueSealizer
|
||||
) ?: return@get
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
val value = decodeUrlQueryValueOrSendError(
|
||||
valueParameterName,
|
||||
valueSerializer
|
||||
) ?: return@get
|
||||
|
||||
call.unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(key, value)
|
||||
)
|
||||
unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(key, value)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(countByKeyRoute) {
|
||||
val key = call.decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
|
||||
call.unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count(key)
|
||||
)
|
||||
unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count(key)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(countRoute) {
|
||||
call.unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
unifiedRouter.apply {
|
||||
unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <Key, Value> Route.configureOneToManyReadKeyValueRepoRoutes(
|
||||
originalRepo: ReadOneToManyKeyValueRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureOneToManyReadKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
||||
|
@@ -1,9 +1,12 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.repos.WriteOneToManyKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.post
|
||||
import kotlinx.serialization.KSerializer
|
||||
@@ -13,61 +16,78 @@ fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
|
||||
originalRepo: WriteOneToManyKeyValueRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val keyValueSerializer = PairSerializer(keySerializer, valueSerializer)
|
||||
val keyValueMapSerializer = MapSerializer(keySerializer, ListSerializer(valueSerializer))
|
||||
|
||||
includeWebsocketHandling(
|
||||
onNewValueRoute,
|
||||
originalRepo.onNewValue,
|
||||
keyValueSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
onValueRemovedRoute,
|
||||
originalRepo.onValueRemoved,
|
||||
keyValueSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
onDataClearedRoute,
|
||||
originalRepo.onDataCleared,
|
||||
keySerializer
|
||||
)
|
||||
unifiedRouter.apply {
|
||||
includeWebsocketHandling(
|
||||
onNewValueRoute,
|
||||
originalRepo.onNewValue,
|
||||
keyValueSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
onValueRemovedRoute,
|
||||
originalRepo.onValueRemoved,
|
||||
keyValueSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
onDataClearedRoute,
|
||||
originalRepo.onDataCleared,
|
||||
keySerializer
|
||||
)
|
||||
}
|
||||
|
||||
post(addRoute) {
|
||||
val obj = call.uniload(keyValueMapSerializer)
|
||||
unifiedRouter.apply {
|
||||
val obj = uniload(keyValueMapSerializer)
|
||||
|
||||
call.unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.add(obj)
|
||||
)
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.add(obj)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(removeRoute) {
|
||||
val obj = call.uniload(
|
||||
keyValueMapSerializer
|
||||
)
|
||||
unifiedRouter.apply {
|
||||
val obj = uniload(keyValueMapSerializer)
|
||||
|
||||
call.unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.remove(obj),
|
||||
)
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.remove(obj),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(clearRoute) {
|
||||
val key = call.uniload(keySerializer)
|
||||
unifiedRouter.apply {
|
||||
val key = uniload(keySerializer)
|
||||
|
||||
call.unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.clear(key),
|
||||
)
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.clear(key),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(setRoute) {
|
||||
val obj = call.uniload(keyValueMapSerializer)
|
||||
unifiedRouter.apply {
|
||||
val obj = uniload(keyValueMapSerializer)
|
||||
|
||||
call.unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.set(obj)
|
||||
)
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.set(obj)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
|
||||
originalRepo: WriteOneToManyKeyValueRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureOneToManyWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
||||
|
17
selector/common/build.gradle
Normal file
17
selector/common/build.gradle
Normal file
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,168 @@
|
||||
package dev.inmo.micro_utils.selector
|
||||
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
||||
/**
|
||||
* Unified interface which can be used in any system which require some selection functionality
|
||||
*/
|
||||
interface Selector<T> {
|
||||
val selectedItems: List<T>
|
||||
val itemSelected: SharedFlow<T>
|
||||
val itemUnselected: SharedFlow<T>
|
||||
|
||||
suspend fun toggleSelection(element: T)
|
||||
suspend fun forceSelect(element: T)
|
||||
suspend fun forceDeselect(element: T)
|
||||
suspend fun clearSelection()
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline operator fun <T> Selector<T>.contains(element: T) = selectedItems.contains(element)
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> Selector<T>.nothingSelected(): Boolean = selectedItems.isEmpty()
|
||||
suspend inline fun <T> Selector<T>.toggleSelection(elements: List<T>) = elements.forEach { toggleSelection(it) }
|
||||
suspend inline fun <T> Selector<T>.forceSelect(elements: List<T>) = elements.forEach { forceSelect(it) }
|
||||
suspend inline fun <T> Selector<T>.forceDeselect(elements: List<T>) = elements.forEach { forceDeselect(it) }
|
||||
suspend inline fun <T> Selector<T>.toggleSelection(firstElement: T, vararg elements: T) = toggleSelection(listOf(firstElement) + elements.toList())
|
||||
suspend inline fun <T> Selector<T>.forceSelect(firstElement: T, vararg elements: T) = forceSelect(listOf(firstElement) + elements.toList())
|
||||
suspend inline fun <T> Selector<T>.forceDeselect(firstElement: T, vararg elements: T) = forceDeselect(listOf(firstElement) + elements.toList())
|
||||
|
||||
/**
|
||||
* Realization of [Selector] with one or without selected element. This realization will always have empty
|
||||
* [selectedItems] when nothing selected and one element in [selectedItems] when something selected. Contains
|
||||
* [selectedItem] value for simple access to currently selected item.
|
||||
*
|
||||
* On calling of [toggleSelection] previous selection will be erased and [itemUnselected] will emit this element.
|
||||
*
|
||||
* @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection]
|
||||
*/
|
||||
class SingleSelector<T>(
|
||||
selectedItem: T? = null,
|
||||
safeChanges: Boolean = true
|
||||
) : Selector<T> {
|
||||
var selectedItem: T? = selectedItem
|
||||
private set
|
||||
override val selectedItems: List<T>
|
||||
get() = selectedItem ?.let { listOf(it) } ?: emptyList()
|
||||
|
||||
private val _itemSelected = MutableSharedFlow<T>()
|
||||
override val itemSelected: SharedFlow<T> = _itemSelected.asSharedFlow()
|
||||
private val _itemUnselected = MutableSharedFlow<T>()
|
||||
override val itemUnselected: SharedFlow<T> = _itemUnselected.asSharedFlow()
|
||||
|
||||
private val mutex = if (safeChanges) {
|
||||
Mutex()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
override suspend fun forceDeselect(element: T) {
|
||||
mutex ?.lock()
|
||||
if (selectedItem == element) {
|
||||
selectedItem = null
|
||||
_itemUnselected.emit(element)
|
||||
}
|
||||
mutex ?.unlock()
|
||||
}
|
||||
|
||||
override suspend fun forceSelect(element: T) {
|
||||
mutex ?.lock()
|
||||
if (selectedItem != element) {
|
||||
selectedItem = element
|
||||
_itemSelected.emit(element)
|
||||
}
|
||||
mutex ?.unlock()
|
||||
}
|
||||
|
||||
override suspend fun toggleSelection(element: T) {
|
||||
mutex ?.lock()
|
||||
if (selectedItem == element) {
|
||||
selectedItem = null
|
||||
_itemUnselected.emit(element)
|
||||
} else {
|
||||
val previouslySelected = selectedItem
|
||||
selectedItem = null
|
||||
if (previouslySelected != null) {
|
||||
_itemUnselected.emit(previouslySelected)
|
||||
}
|
||||
selectedItem = element
|
||||
_itemSelected.emit(element)
|
||||
}
|
||||
mutex ?.unlock()
|
||||
}
|
||||
|
||||
override suspend fun clearSelection() {
|
||||
selectedItem ?.let { forceDeselect(it) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Realization of [Selector] with multiple selected elements. On calling of [toggleSelection] this realization will select passed element OR deselect it if it is already in
|
||||
* [selectedItems]
|
||||
*
|
||||
* @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection]
|
||||
*/
|
||||
class MultipleSelector<T>(
|
||||
selectedItems: List<T> = emptyList(),
|
||||
safeChanges: Boolean = true
|
||||
) : Selector<T> {
|
||||
private val _selectedItems: MutableList<T> = selectedItems.toMutableList()
|
||||
override val selectedItems: List<T> = _selectedItems
|
||||
|
||||
private val _itemSelected = MutableSharedFlow<T>()
|
||||
override val itemSelected: SharedFlow<T> = _itemSelected.asSharedFlow()
|
||||
private val _itemUnselected = MutableSharedFlow<T>()
|
||||
override val itemUnselected: SharedFlow<T> = _itemUnselected.asSharedFlow()
|
||||
|
||||
private val mutex = if (safeChanges) {
|
||||
Mutex()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
override suspend fun forceDeselect(element: T) {
|
||||
mutex ?.lock()
|
||||
if (_selectedItems.remove(element)) {
|
||||
_itemUnselected.emit(element)
|
||||
}
|
||||
mutex ?.unlock()
|
||||
}
|
||||
|
||||
override suspend fun forceSelect(element: T) {
|
||||
mutex ?.lock()
|
||||
if (element !in _selectedItems && _selectedItems.add(element)) {
|
||||
_itemSelected.emit(element)
|
||||
}
|
||||
mutex ?.unlock()
|
||||
}
|
||||
|
||||
override suspend fun toggleSelection(element: T) {
|
||||
mutex ?.lock()
|
||||
if (_selectedItems.remove(element)) {
|
||||
_itemUnselected.emit(element)
|
||||
} else {
|
||||
_selectedItems.add(element)
|
||||
_itemSelected.emit(element)
|
||||
}
|
||||
mutex ?.unlock()
|
||||
}
|
||||
|
||||
override suspend fun clearSelection() {
|
||||
mutex ?.lock()
|
||||
val preSelectedItems = _selectedItems.toList()
|
||||
_selectedItems.clear()
|
||||
preSelectedItems.forEach { _itemUnselected.emit(it) }
|
||||
mutex ?.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("FunctionName", "NOTHING_TO_INLINE")
|
||||
inline fun <T> Selector(
|
||||
multiple: Boolean,
|
||||
safeChanges: Boolean = true
|
||||
): Selector<T> = if (multiple) {
|
||||
MultipleSelector(safeChanges = safeChanges)
|
||||
} else {
|
||||
SingleSelector(safeChanges = safeChanges)
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.selector
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* @return Returned [SharedFlow] will emit true when [element] has been selected in [this] [Selector] and will emit
|
||||
* false when this [element] was deselected
|
||||
*
|
||||
* @see [Selector]
|
||||
* @see [Selector.itemSelected]
|
||||
* @see [Selector.itemUnselected]
|
||||
*/
|
||||
fun <T> Selector<T>.itemSelectionFlow(element: T, scope: CoroutineScope): SharedFlow<Boolean> = MutableSharedFlow<Boolean>().apply {
|
||||
itemSelected.onEach { if (it == element) emit(true) }.launchIn(scope)
|
||||
itemUnselected.onEach { if (it == element) emit(false) }.launchIn(scope)
|
||||
}.asSharedFlow()
|
1
selector/common/src/main/AndroidManifest.xml
Normal file
1
selector/common/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.selector"/>
|
17
serialization/base64/build.gradle
Normal file
17
serialization/base64/build.gradle
Normal file
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.crypto")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.serialization.base64
|
||||
|
||||
import dev.inmo.micro_utils.crypto.*
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
/**
|
||||
* Converts [ByteArray] into base64 encoded string due to serialization and decode base64 encoded string into bytes
|
||||
*/
|
||||
object Base64BytesToFromStringSerializer: KSerializer<ByteArray> {
|
||||
override val descriptor: SerialDescriptor = String.serializer().descriptor
|
||||
override fun deserialize(decoder: Decoder): SourceBytes = decoder.decodeString().decodeBase64()
|
||||
override fun serialize(encoder: Encoder, value: SourceBytes) = encoder.encodeString(value.encodeBase64String())
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package dev.inmo.micro_utils.serialization.base64
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
open class Base64Serializer<T>(
|
||||
private val converterFrom: (T) -> String,
|
||||
private val converterTo: (String) -> T,
|
||||
) : KSerializer<T> {
|
||||
override val descriptor: SerialDescriptor = Base64BytesToFromStringSerializer.descriptor
|
||||
override fun deserialize(decoder: Decoder): T = converterTo(Base64BytesToFromStringSerializer.deserialize(decoder).decodeToString())
|
||||
override fun serialize(encoder: Encoder, value: T) = Base64BytesToFromStringSerializer.serialize(encoder, converterFrom(value).encodeToByteArray())
|
||||
}
|
||||
|
||||
object Base64StringSerializer : Base64Serializer<String>({ it }, { it })
|
||||
object Base64ByteArraySerializer : Base64Serializer<ByteArray>({ it.decodeToString() }, { it.encodeToByteArray() })
|
1
serialization/base64/src/main/AndroidManifest.xml
Normal file
1
serialization/base64/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.serialization.base64"/>
|
7
serialization/encapsulator/build.gradle
Normal file
7
serialization/encapsulator/build.gradle
Normal file
@@ -0,0 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
@@ -0,0 +1,23 @@
|
||||
package dev.inmo.micro_utils.serialization.encapsulator
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
data class Encapsulator<T : Any>(
|
||||
val klass: KClass<T>,
|
||||
val serializer: KSerializer<T>
|
||||
) {
|
||||
fun <O> encapsulate(
|
||||
value: Any,
|
||||
callback: KSerializer<T>.(T) -> O
|
||||
): O? = if (klass.isInstance(value)) {
|
||||
callback(serializer, value as T)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any> Encapsulator<T>.tryEncode(encoder: Encoder, value: Any) = encapsulate(value) {
|
||||
encoder.encodeSerializableValue(this, it)
|
||||
}
|
1
serialization/encapsulator/src/main/AndroidManifest.xml
Normal file
1
serialization/encapsulator/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.serialization.encapsulator"/>
|
@@ -2,6 +2,9 @@ rootProject.name='micro_utils'
|
||||
|
||||
String[] includes = [
|
||||
":common",
|
||||
":matrix",
|
||||
":crypto",
|
||||
":selector:common",
|
||||
":pagination:common",
|
||||
":pagination:exposed",
|
||||
":pagination:ktor:common",
|
||||
@@ -20,6 +23,8 @@ String[] includes = [
|
||||
":android:recyclerview",
|
||||
":android:alerts:common",
|
||||
":android:alerts:recyclerview",
|
||||
":serialization:base64",
|
||||
":serialization:encapsulator",
|
||||
|
||||
":dokka"
|
||||
]
|
||||
|
Reference in New Issue
Block a user