Compare commits

..

7 Commits

Author SHA1 Message Date
216c03205c Revert "add Handlers subproject"
This reverts commit d1021d283a.
2022-04-26 13:23:46 +06:00
ab112aa7a4 Revert "HandlersRegistrar now is open"
This reverts commit 3ac56dcfd3.
2022-04-25 22:04:36 +06:00
d85b3d0da9 Revert "now HandlersRegisrar properties are open"
This reverts commit 67b9a03366.
2022-04-25 22:04:33 +06:00
67b9a03366 now HandlersRegisrar properties are open 2022-04-25 22:01:04 +06:00
3ac56dcfd3 HandlersRegistrar now is open 2022-04-25 21:55:38 +06:00
d1021d283a add Handlers subproject 2022-04-25 21:51:42 +06:00
97ed973cb5 start 0.9.25 2022-04-25 21:50:41 +06:00
147 changed files with 1289 additions and 4529 deletions

View File

@@ -15,7 +15,7 @@ jobs:
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Build
run: ./gradlew build && ./gradlew dokkaHtml
run: ./gradlew dokkaHtml
- name: Publish KDocs
uses: peaceiris/actions-gh-pages@v3
with:

View File

@@ -1,8 +1,8 @@
name: Build
name: Publish package to GitHub Packages
on: [push]
jobs:
build:
publishing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -20,9 +20,9 @@ jobs:
mv gradle.properties.tmp gradle.properties
- name: Build
run: ./gradlew build
# - name: Publish
# continue-on-error: true
# run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
# env:
# GITHUBPACKAGES_USER: ${{ github.actor }}
# GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
- name: Publish
continue-on-error: true
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
env:
GITHUBPACKAGES_USER: ${{ github.actor }}
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,201 +1,6 @@
# Changelog
## 0.12.1
* `Versions`:
* `Ktor`: `2.0.3` -> `2.1.0`
## 0.12.0
**OLD DEPRECATIONS HAVE BEEN REMOVED**
**MINIMAL ANDROID API HAS BEEN ENLARGED UP TO API 21 (Android 5.0)**
* `Versions`
* `Kotlin`: `1.6.21` -> `1.7.0`
* `Coroutines`: `1.6.3` -> `1.6.4`
* `Exposed`: `0.38.2` -> `0.39.2`
* `Compose`: `1.2.0-alpha01-dev729` -> `1.2.0-alpha01-dev753`
* `Klock`: `2.7.0` -> `3.0.0`
* `uuid`: `0.4.1` -> `0.5.0`
* `Android Core KTX`: `1.7.0` -> `1.8.0`
* `Android AppCompat`: `1.4.1` -> `1.4.2`
* `Ktor`:
* All previously standard functions related to work with binary data by default have been deprecated
## 0.11.14
* `Pagination`:
* `PaginationResult` got new field `objectsNumber` which by default is a times between `pagesNumber` and `size`
## 0.11.13
* `Versions`:
* `Coroutines`: `1.6.3` -> `1.6.4`
* `Compose`: `1.2.0-alpha01-dev629` -> `1.2.0-alpha01-dev731`
## 0.11.12
* `Repos`:
* `Common`:
* `JVM`:
* Fixes in `ReadFileKeyValueRepo` methods (`values`/`keys`)
## 0.11.11
* `Crypto`:
* `hmacSha256` has been deprecated
* `Ktor`:
* `Client`:
* `BodyPair` has been deprecated
* `Repos`:
* `Cache`:
* New interface `CacheRepo`
* New interface `FullCacheRepo`
* `actualize*` methods inside of full cache repos now open for overriding
## 0.11.10
* `Repos`:
* `Cache`:
* `KVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache`
* `SimpleKVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache`
* New `KVCache` subtype - `FullKVCache`
* Add `Full*` variants of standard repos
* Add `cached`/`caching` (for write repos) extensions for all standard types of repos
## 0.11.9
* `Versions`
* `Coroutines`: `1.6.1` -> `1.6.3`
* `Ktor`: `2.0.2` -> `2.0.3`
* `Compose`: `1.2.0-alpha01-dev686` -> `1.2.0-alpha01-dev729`
## 0.11.8
* `Repos`:
* `Common`:
* Fixes in `FileKeyValueRepo`
## 0.11.7
* `Common`:
* New abstractions `SimpleMapper` and `SimpleSuspendableMapper`
* `Repos`:
* `Common`:
* Add mappers for `CRUDRepo`
## 0.11.6
* `FSM`:
* `Common`
* Several fixes related to the jobs handling
## 0.11.5
* `Coroutines`:
* `Compose`:
* Add extension `StateFlow#asMutableComposeListState` and `StateFlow#asComposeList`
* Add extension `StateFlow#asMutableComposeState`/`StateFlow#asComposeState`
## 0.11.4
**THIS VERSION HAS BEEN BROKEN, DO NOT USE IT**
## 0.11.3
* `Ktor`:
* Support of `WebSockets` has been improved
* `Client`:
* New extensions: `HttpClient#openBaseWebSocketFlow`, `HttpClient#openWebSocketFlow`, `HttpClient#openSecureWebSocketFlow`
## 0.11.2
* `Ktor`:
* Support of `WebSockets` has been improved and added fixes inside of clients
## 0.11.1
* `Repos`
* `Ktor`
* In `configureReadKeyValueRepoRoutes` and `configureReadKeyValuesRepoRoutes` configurators fixed requiring of `reversed` property
## 0.11.0
* `Versions`
* `UUID`: `0.4.0` -> `0.4.1`
* `Ktor`
* `Client`:
* New extension fun `HttpResponse#throwOnUnsuccess`
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
* `Server`:
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
* `Repos`
* `Ktor`:
* Fully rewritten work with all declared repositories
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
## 0.10.8
* `Common`
* Add `Element.isOverflow*` extension properties
## 0.10.7
* `Pagination`:
* Now it is possible to use `doForAll*` and `getForAll` functions in non suspend places
## 0.10.6
* `Versions`
* `Ktor`: `2.0.1` -> `2.0.2`
* `Common`
* `JS`:
* Add `ResizeObserver` functionality
## 0.10.5
* `Versions`
* `Compose`: `1.2.0-alpha01-dev683` -> `1.2.0-alpha01-dev686`
* `Repos`
* `Android`:
* New function `SharedPreferencesKeyValueRepo`
* `FSM`
* Add `StateHandlingErrorHandler` and opportunity to handle states handling errors
## 0.10.4
* `Versions`:
* `Serialization`: `1.3.2` -> `1.3.3`
## 0.10.3
* `Versions`:
* `Compose`: `1.2.0-alpha01-dev682` -> `1.2.0-alpha01-dev683`
* `Coroutines`:
* Fixes in `AccumulatorFlow`
## 0.10.2
* `Versions`:
* `Compose`: `1.2.0-alpha01-dev675` -> `1.2.0-alpha01-dev682`
## 0.10.1
* `Versions`:
* `Ktor`: `2.0.0` -> `2.0.1`
* `Crypto`:
* Add `hmacSha256`
* Add `hex`
## 0.10.0
* `Versions`:
* `Kotlin`: `1.6.10` -> `1.6.21`
* `Compose`: `1.1.1` -> `1.2.0-alpha01-dev675`
* `Exposed`: `0.37.3` -> `0.38.2`
* `Ktor`: `1.6.8` -> `2.0.0`
* `Dokka`: `1.6.10` -> `1.6.21`
## 0.9.25
## 0.9.24

View File

@@ -21,7 +21,6 @@ allprojects {
mavenLocal()
mavenCentral()
google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
}
// temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

@@ -1,5 +1,3 @@
@file:Suppress("OPT_IN_IS_NOT_ENABLED")
package dev.inmo.micro_utils.common
@RequiresOptIn(

View File

@@ -43,7 +43,6 @@ private inline fun <T> performChanges(
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
changedList.addAll(
potentialChanges.take(i).mapNotNull {
@Suppress("UNCHECKED_CAST")
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
}
)
@@ -122,10 +121,7 @@ fun <T> Iterable<T>.calculateDiff(
when {
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
changedObjects.addAll(potentiallyChangedObjects.map {
@Suppress("UNCHECKED_CAST")
it as Pair<IndexedValue<T>, IndexedValue<T>>
})
changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
potentiallyChangedObjects.clear()
}
else -> {

View File

@@ -1,5 +1,3 @@
@file:Suppress("unused", "NOTHING_TO_INLINE")
package dev.inmo.micro_utils.common
import kotlinx.serialization.*
@@ -23,18 +21,26 @@ import kotlinx.serialization.encoding.*
sealed interface Either<T1, T2> {
val optionalT1: Optional<T1>
val optionalT2: Optional<T2>
val t1OrNull: T1?
@Deprecated("Use optionalT1 instead", ReplaceWith("optionalT1"))
val t1: T1?
get() = optionalT1.dataOrNull()
val t2OrNull: T2?
@Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
val t2: T2?
get() = optionalT2.dataOrNull()
companion object {
fun <T1, T2> serializer(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
): KSerializer<Either<T1, T2>> = EitherSerializer(t1Serializer, t2Serializer)
}
}
class EitherSerializer<T1, T2>(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
) : KSerializer<Either<T1, T2>> {
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer",
SerialKind.CONTEXTUAL
@@ -97,7 +103,7 @@ class EitherSerializer<T1, T2>(
*/
@Serializable
data class EitherFirst<T1, T2>(
val t1: T1
override val t1: T1
) : Either<T1, T2> {
override val optionalT1: Optional<T1> = t1.optional
override val optionalT2: Optional<T2> = Optional.absent()
@@ -108,7 +114,7 @@ data class EitherFirst<T1, T2>(
*/
@Serializable
data class EitherSecond<T1, T2>(
val t2: T2
override val t2: T2
) : Either<T1, T2> {
override val optionalT1: Optional<T1> = Optional.absent()
override val optionalT2: Optional<T2> = t2.optional

View File

@@ -1,53 +0,0 @@
package dev.inmo.micro_utils.common
import kotlin.jvm.JvmName
interface SimpleMapper<T1, T2> {
fun convertToT1(from: T2): T1
fun convertToT2(from: T1): T2
}
@JvmName("convertFromT2")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T2) = convertToT1(from)
@JvmName("convertFromT1")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T1) = convertToT2(from)
class SimpleMapperImpl<T1, T2>(
private val t1: (T2) -> T1,
private val t2: (T1) -> T2,
) : SimpleMapper<T1, T2> {
override fun convertToT1(from: T2): T1 = t1.invoke(from)
override fun convertToT2(from: T1): T2 = t2.invoke(from)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleMapper(
noinline t1: (T2) -> T1,
noinline t2: (T1) -> T2,
) = SimpleMapperImpl(t1, t2)
interface SimpleSuspendableMapper<T1, T2> {
suspend fun convertToT1(from: T2): T1
suspend fun convertToT2(from: T1): T2
}
@JvmName("convertFromT2")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T2) = convertToT1(from)
@JvmName("convertFromT1")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T1) = convertToT2(from)
class SimpleSuspendableMapperImpl<T1, T2>(
private val t1: suspend (T2) -> T1,
private val t2: suspend (T1) -> T2,
) : SimpleSuspendableMapper<T1, T2> {
override suspend fun convertToT1(from: T2): T1 = t1.invoke(from)
override suspend fun convertToT2(from: T1): T2 = t2.invoke(from)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleSuspendableMapper(
noinline t1: suspend (T2) -> T1,
noinline t2: suspend (T1) -> T2,
) = SimpleSuspendableMapperImpl(t1, t2)

View File

@@ -41,18 +41,10 @@ data class Optional<T> internal constructor(
inline val <T> T.optional
get() = Optional.presented(this)
inline val <T : Any> T?.optionalOrAbsentIfNull
get() = if (this == null) {
Optional.absent<T>()
} else {
Optional.presented(this)
}
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
@OptIn(Warning::class)
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
}
@@ -60,7 +52,6 @@ inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
@OptIn(Warning::class)
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
}
@@ -68,7 +59,6 @@ inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
* Will call [block] when data absent ([Optional.dataPresented] == false)
*/
inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
@OptIn(Warning::class)
if (!dataPresented) { block() }
}
@@ -76,22 +66,27 @@ inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
@OptIn(Warning::class)
if (!dataPresented) { block() } else null
if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
}
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
*/
fun <T> Optional<T>.dataOrNull() = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
fun <T> Optional<T>.dataOrNull() = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise
*/
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()

View File

@@ -32,7 +32,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) {
for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
@@ -55,7 +55,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (step in oldList.indices) {
for ((i, _) in withIndex) {
for ((i, v) in withIndex) {
val mutable = oldList.toMutableList()
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step)
@@ -104,7 +104,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) {
for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
@@ -129,20 +129,15 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (step in oldList.indices) {
for ((i, _) in withIndex) {
for ((i, v) in withIndex) {
val mutable = oldList.toMutableList()
val newList = if (step == 0) {
i until oldList.size
} else {
i until oldList.size step step
}
newList.forEach { index ->
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step)
).map { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value
}
}
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(

View File

@@ -1,12 +0,0 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.Element
inline val Element.isOverflowWidth
get() = scrollWidth > clientWidth
inline val Element.isOverflowHeight
get() = scrollHeight > clientHeight
inline val Element.isOverflow
get() = isOverflowHeight || isOverflowWidth

View File

@@ -1,58 +0,0 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.*
import kotlin.js.Json
import kotlin.js.json
external class ResizeObserver(
callback: (Array<ResizeObserverEntry>, ResizeObserver) -> Unit
) {
fun observe(target: Element, options: Json = definedExternally)
fun unobserve(target: Element)
fun disconnect()
}
external interface ResizeObserverSize {
val blockSize: Float
val inlineSize: Float
}
external interface ResizeObserverEntry {
val borderBoxSize: Array<ResizeObserverSize>
val contentBoxSize: Array<ResizeObserverSize>
val devicePixelContentBoxSize: Array<ResizeObserverSize>
val contentRect: DOMRectReadOnly
val target: Element
}
fun ResizeObserver.observe(target: Element, options: ResizeObserverObserveOptions) = observe(
target,
json(
"box" to options.box ?.name
)
)
class ResizeObserverObserveOptions(
val box: Box? = null
) {
sealed interface Box {
val name: String
object Content : Box {
override val name: String
get() = "content-box"
}
object Border : Box {
override val name: String
get() = "border-box"
}
object DevicePixelContent : Box {
override val name: String
get() = "device-pixel-content-box"
}
}
}

View File

@@ -1,26 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
scope: CoroutineScope
): SnapshotStateList<T> {
val state = mutableStateListOf<T>()
subscribeSafelyWithoutExceptions(scope) {
state.applyDiff(it)
}
return state
}
@Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asComposeList(
scope: CoroutineScope
): List<T> = asMutableComposeListState(scope)

View File

@@ -1,35 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
fun <T> Flow<T>.asMutableComposeState(
initial: T,
scope: CoroutineScope
): MutableState<T> {
val state = mutableStateOf(initial)
subscribeSafelyWithoutExceptions(scope) { state.value = it }
return state
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asMutableComposeState(
scope: CoroutineScope
): MutableState<T> = asMutableComposeState(value, scope)
fun <T> Flow<T>.asComposeState(
initial: T,
scope: CoroutineScope
): State<T> {
val state = asMutableComposeState(initial, scope)
return derivedStateOf { state.value }
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asComposeState(
scope: CoroutineScope
): State<T> = asComposeState(value, scope)

View File

@@ -16,7 +16,6 @@ fun <T> Flow<T>.toMutableState(
return state
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.toMutableState(
scope: CoroutineScope
): MutableState<T> = toMutableState(value, scope)

View File

@@ -6,12 +6,11 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.cancellation.CancellationException
private sealed interface AccumulatorFlowStep<T>
private data class DataRetrievedAccumulatorFlowStep<T>(val data: T) : AccumulatorFlowStep<T>
private data class SubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
private data class UnsubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
private sealed interface AccumulatorFlowStep
private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
/**
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
@@ -27,12 +26,12 @@ class AccumulatorFlow<T>(
private val subscope = scope.LinkedSupervisorScope()
private val activeData = ArrayDeque<T>()
private val dataMutex = Mutex()
private val channelsForBroadcast = mutableListOf<Channel<T>>()
private val channelsForBroadcast = mutableListOf<Channel<Any>>()
private val channelsMutex = Mutex()
private val steps = subscope.actor<AccumulatorFlowStep<T>> { step ->
private val steps = subscope.actor<AccumulatorFlowStep> { step ->
when (step) {
is DataRetrievedAccumulatorFlowStep -> {
if (activeData.firstOrNull() === step.data) {
if (activeData.first() === step.data) {
dataMutex.withLock {
activeData.removeFirst()
}
@@ -43,7 +42,7 @@ class AccumulatorFlow<T>(
dataMutex.withLock {
val dataToSend = activeData.toList()
safelyWithoutExceptions {
dataToSend.forEach { step.channel.send(it) }
dataToSend.forEach { step.channel.send(it as Any) }
}
}
}
@@ -59,29 +58,24 @@ class AccumulatorFlow<T>(
channelsMutex.withLock {
channelsForBroadcast.forEach { channel ->
safelyWithResult {
channel.send(it)
channel.send(it as Any)
}
}
}
}
override suspend fun collectSafely(collector: FlowCollector<T>) {
val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
steps.send(SubscribeAccumulatorFlowStep(channel))
val result = runCatchingSafely {
for (data in channel) {
val emitResult = runCatchingSafely {
collector.emit(data)
}
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) {
steps.send(DataRetrievedAccumulatorFlowStep(data))
}
emitResult.getOrThrow()
for (data in channel) {
try {
collector.emit(data as T)
steps.send(DataRetrievedAccumulatorFlowStep(data))
} finally {
channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel))
}
}
channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel))
result.getOrThrow()
}
}

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.crypto
val HEX_ARRAY = "0123456789abcdef".toCharArray()
fun SourceBytes.hex(): String {
val hexChars = CharArray(size * 2)
for (j in indices) {
val v: Int = this[j].toInt() and 0xFF
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
}
return hexChars.concatToString()
}
fun SourceString.hex(): String = encodeToByteArray().hex()

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.crypto
import kotlin.test.*
class Hex {
@Test
fun testSimpleHmacSHA256Message() {
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
val resultHex = text.hex()
assertEquals(
"4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742c2073656420646f20656975736d6f642074656d706f7220696e6369646964756e74207574206c61626f726520657420646f6c6f7265206d61676e6120616c697175612e20557420656e696d206164206d696e696d2076656e69616d2c2071756973206e6f737472756420657865726369746174696f6e20756c6c616d636f206c61626f726973206e69736920757420616c697175697020657820656120636f6d6d6f646f20636f6e7365717561742e2044756973206175746520697275726520646f6c6f7220696e20726570726568656e646572697420696e20766f6c7570746174652076656c697420657373652063696c6c756d20646f6c6f726520657520667567696174206e756c6c612070617269617475722e204578636570746575722073696e74206f6363616563617420637570696461746174206e6f6e2070726f6964656e742c2073756e7420696e2063756c706120717569206f666669636961206465736572756e74206d6f6c6c697420616e696d20696420657374206c61626f72756d2e",
resultHex
)
}
}

View File

@@ -47,11 +47,24 @@ fun <I : O, O : State> CheckableHandlerHolder(
}
)
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
fun <I : O, O : State> StateHandlerHolder(
inputKlass: KClass<I>,
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(inputKlass, strict, delegateTo)
inline fun <reified I : O, O : State> CheckableHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(I::class, strict, delegateTo)
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
inline fun <reified I : O, O : State> StateHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(strict, delegateTo)
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
strict: Boolean = true
) = CheckableHandlerHolder<I, O>(

View File

@@ -3,10 +3,7 @@ package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -16,24 +13,13 @@ import kotlinx.coroutines.sync.withLock
* handling until [start] method will be called
*/
interface StatesMachine<T : State> : StatesHandler<T, T> {
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T>
): T? {
return runCatchingSafely {
handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}.getOrElse {
onStateHandlingErrorHandler(state, it)
}
}
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>
): T? {
return launchStateHandling(state, handlers, defaultStateHandlingErrorHandler())
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}
/**
@@ -52,9 +38,8 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
*/
operator fun <T: State> invoke(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) = DefaultStatesMachine(statesManager, handlers, onStateHandlingErrorHandler)
handlers: List<CheckableHandlerHolder<in T, T>>
) = DefaultStatesMachine(statesManager, handlers)
}
}
@@ -67,17 +52,12 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
open class DefaultStatesMachine <T: State>(
protected val statesManager: StatesManager<T>,
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : StatesMachine<T> {
/**
* Will call [launchStateHandling] for state handling
*/
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
override suspend fun launchStateHandling(state: T, handlers: List<CheckableHandlerHolder<in T, T>>): T? {
return launchStateHandling(state, handlers, onStateHandlingErrorHandler)
}
/**
* This
*/
@@ -86,10 +66,10 @@ open class DefaultStatesMachine <T: State>(
protected open suspend fun performUpdate(state: T) {
val newState = launchStateHandling(state, handlers)
if (newState == null) {
statesManager.endChain(state)
} else {
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
@@ -119,7 +99,7 @@ open class DefaultStatesMachine <T: State>(
* [StatesManager.endChain].
*/
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) {
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
@@ -135,6 +115,10 @@ open class DefaultStatesMachine <T: State>(
}
}
}
statesManager.getActiveStates().forEach {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
}
/**

View File

@@ -1,8 +1,6 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.withLock
@@ -22,11 +20,9 @@ interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
open class DefaultUpdatableStatesMachine<T : State>(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : DefaultStatesMachine<T>(
statesManager,
handlers,
onStateHandlingErrorHandler
handlers
), UpdatableStatesMachine<T> {
protected val jobsStates = mutableMapOf<Job, T>()
@@ -38,7 +34,7 @@ open class DefaultUpdatableStatesMachine<T : State>(
*/
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
if (shouldReplaceJob(previousState, actualState)) {
if (compare(previousState, actualState)) {
statesJobs[actualState] ?.cancel()
}
val job = previousState.mapOnPresented {
@@ -52,7 +48,6 @@ open class DefaultUpdatableStatesMachine<T : State>(
statesJobs.remove(
jobsStates[job] ?: return@withLock
)
jobsStates.remove(job)
}
}
}
@@ -68,6 +63,9 @@ open class DefaultUpdatableStatesMachine<T : State>(
*/
protected open suspend fun shouldReplaceJob(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
@Deprecated("Overwrite shouldReplaceJob instead")
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = shouldReplaceJob(previous, new)
override suspend fun updateChain(currentState: T, newState: T) {
statesManager.update(currentState, newState)
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State
import kotlinx.coroutines.flow.*
/**
* Creates [DefaultStatesManager] with [InMemoryDefaultStatesManagerRepo]
*
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state
*/
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
fun <T: State> InMemoryStatesManager(
onStartContextsConflictResolver: suspend (old: T, new: T) -> Boolean = { _, _ -> true },
onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
) = DefaultStatesManager(onStartContextsConflictResolver = onStartContextsConflictResolver, onUpdateContextsConflictResolver = onUpdateContextsConflictResolver)

View File

@@ -1,6 +0,0 @@
package dev.inmo.micro_utils.fsm.common.utils
typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T?
val DefaultStateHandlingErrorHandler: StateHandlingErrorHandler<*> = { _, _ -> null }
inline fun <T> defaultStateHandlingErrorHandler(): StateHandlingErrorHandler<T> = DefaultStateHandlingErrorHandler as StateHandlingErrorHandler<T>

View File

@@ -1,6 +1,7 @@
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
import kotlinx.coroutines.*
sealed interface TrafficLightState : State {

View File

@@ -21,7 +21,7 @@ if (new File(projectDir, "secret.gradle").exists()) {
owner "InsanusMokrassar"
repo "MicroUtils"
tagName "v${project.version}"
tagName "${project.version}"
releaseName "${project.version}"
targetCommitish "${project.version}"

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.12.1
android_code_version=140
version=0.9.25
android_code_version=115

View File

@@ -1,30 +1,30 @@
[versions]
kt = "1.7.0"
kt-serialization = "1.4.0-RC"
kt-coroutines = "1.6.4"
kt = "1.6.10"
kt-serialization = "1.3.2"
kt-coroutines = "1.6.1"
jb-compose = "1.2.0-alpha01-dev753"
jb-exposed = "0.39.2"
jb-dokka = "1.7.0"
jb-compose = "1.1.1"
jb-exposed = "0.37.3"
jb-dokka = "1.6.10"
klock = "3.0.0"
uuid = "0.5.0"
klock = "2.7.0"
uuid = "0.4.0"
ktor = "2.1.0"
ktor = "1.6.8"
gh-release = "2.4.1"
gh-release = "2.2.12"
android-gradle = "7.2.2"
dexcount = "3.1.0"
android-gradle = "7.0.4"
dexcount = "3.0.1"
android-coreKtx = "1.8.0"
android-coreKtx = "1.7.0"
android-recyclerView = "1.2.1"
android-appCompat = "1.4.2"
android-espresso = "3.4.0"
android-test = "1.1.3"
android-appCompat = "1.4.1"
android-espresso = "3.3.0"
android-test = "1.1.2"
android-props-minSdk = "21"
android-props-minSdk = "19"
android-props-compileSdk = "32"
android-props-buildTools = "32.0.0"
@@ -37,24 +37,15 @@ kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
kt-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kt-coroutines" }
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" }
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" }
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }

View File

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

View File

@@ -1,62 +1,59 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.plugins.websocket.ws
import io.ktor.client.features.websocket.ws
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.websocket.Frame
import io.ktor.websocket.readBytes
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readBytes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
import kotlinx.serialization.DeserializationStrategy
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow {
val producerScope = this@channelFlow
do {
val reconnect = runCatchingSafely {
ws(correctedUrl, requestBuilder) {
for (received in incoming) {
when (received) {
is Frame.Binary -> send(conversation(received.data))
else -> {
close()
return@ws
val reconnect = try {
safely {
ws(correctedUrl, requestBuilder) {
for (received in incoming) {
when (received) {
is Frame.Binary -> producerScope.send(conversation(received.readBytes()))
else -> {
producerScope.close()
return@ws
}
}
}
}
}
checkReconnection(null)
}.getOrElse { e ->
} catch (e: Throwable) {
checkReconnection(e).also {
if (!it) {
close(e)
producerScope.close(e)
}
}
}
} while (reconnect && isActive)
if (isActive) {
safely {
close()
} while (reconnect)
if (!producerScope.isClosedForSend) {
safely(
{ it.printStackTrace() }
) {
producerScope.close()
}
}
}
@@ -66,11 +63,10 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>,
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.statement.HttpResponse
import io.ktor.http.isSuccess
inline fun HttpResponse.throwOnUnsuccess(
unsuccessMessage: () -> String
) {
if (status.isSuccess()) {
return
}
throw ClientRequestException(this, unsuccessMessage())
}

View File

@@ -1,103 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.Warning
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull
import io.ktor.client.plugins.websocket.*
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.URLProtocol
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
inline fun <reified T : Any> openBaseWebSocketFlow(
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
): Flow<T> {
return channelFlow {
do {
val reconnect = runCatchingSafely {
webSocketSessionRequest()
checkReconnection(null)
}.getOrElse { e ->
checkReconnection(e).also {
if (!it) {
close(e)
}
}
}
} while (reconnect && isActive)
if (isActive) {
safely {
close()
}
}
}
}
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T : Any> HttpClient.openWebSocketFlow(
url: String,
useSecureConnection: Boolean,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
return openBaseWebSocketFlow<T>(checkReconnection) {
val block: suspend DefaultClientWebSocketSession.() -> Unit = {
while (isActive) {
send(receiveDeserialized<T>())
}
}
if (useSecureConnection) {
wss(url, requestBuilder, block)
} else {
ws(url, requestBuilder, block)
}
}
}
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T : Any> HttpClient.openWebSocketFlow(
url: String,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> = openWebSocketFlow(url, false, checkReconnection, requestBuilder)
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T : Any> HttpClient.openSecureWebSocketFlow(
url: String,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> = openWebSocketFlow(url, true, checkReconnection, requestBuilder)
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T : Any> HttpClient.createStandardWebsocketFlow(
url: String,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> = openWebSocketFlow(url, checkReconnection, requestBuilder)

View File

@@ -4,15 +4,14 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.readBytes
import io.ktor.http.*
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.serialization.*
@Deprecated("This class will be removed soon. It is now recommended to use built-in ktor features instead")
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
class UnifiedRequester(
val client: HttpClient = HttpClient(),
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
@@ -32,7 +31,7 @@ class UnifiedRequester(
suspend fun <BodyType, ResultType> unipost(
url: String,
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
@@ -51,7 +50,7 @@ class UnifiedRequester(
url: String,
filename: String,
inputProvider: InputProvider,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
@@ -74,7 +73,7 @@ class UnifiedRequester(
suspend fun <BodyType, ResultType> unimultipart(
url: String,
mppFile: MPPFile,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
@@ -86,32 +85,31 @@ class UnifiedRequester(
fun <T> createStandardWebsocketFlow(
url: String,
checkReconnection: suspend (Throwable?) -> Boolean,
checkReconnection: (Throwable?) -> Boolean,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat, requestBuilder)
fun <T> createStandardWebsocketFlow(
url: String,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
}
@Deprecated("This property will be removed soon. It is now recommended to use built-in ktor features instead")
val defaultRequester = UnifiedRequester()
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = get(url).let {
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
) = get<StandardKtorSerialInputData>(
url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
value: T,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
@@ -120,21 +118,17 @@ fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
value
)
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unipost(
url: String,
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = post(url) {
setBody(
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
)
) = post<StandardKtorSerialInputData>(url) {
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
serialFormat.decodeDefault(resultDeserializer, it)
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
filename: String,
@@ -145,7 +139,7 @@ suspend fun <ResultType> HttpClient.unimultipart(
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = submitFormWithBinaryData(
): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>(
url,
formData = formData {
append(
@@ -161,13 +155,12 @@ suspend fun <ResultType> HttpClient.unimultipart(
}
) {
requestBuilder()
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
}.let { serialFormat.decodeDefault(resultDeserializer, it) }
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
filename: String,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
otherData: BodyPair<BodyType>,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
@@ -201,7 +194,6 @@ suspend fun <BodyType, ResultType> HttpClient.unimultipart(
serialFormat
)
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
@@ -223,11 +215,10 @@ suspend fun <ResultType> HttpClient.unimultipart(
serialFormat
)
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},

View File

@@ -4,10 +4,7 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import io.ktor.client.HttpClient
import kotlinx.coroutines.*
import org.w3c.dom.mediasource.ENDED
import org.w3c.dom.mediasource.ReadyState
import org.w3c.xhr.*
import org.w3c.xhr.XMLHttpRequest.Companion.DONE
suspend fun tempUpload(
fullTempUploadDraftPath: String,
@@ -15,7 +12,7 @@ suspend fun tempUpload(
onUpload: (Long, Long) -> Unit
): TemporalFileId {
val formData = FormData()
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
val answer = CompletableDeferred<TemporalFileId>()
formData.append(
"data",
@@ -40,15 +37,17 @@ suspend fun tempUpload(
request.open("POST", fullTempUploadDraftPath, true)
request.send(formData)
answer.invokeOnCompletion {
val handle = currentCoroutineContext().job.invokeOnCompletion {
runCatching {
if (request.readyState != DONE) {
request.abort()
}
request.abort()
}
}
return answer.await()
return runCatching {
answer.await()
}.also {
handle.dispose()
}.getOrThrow()
}

View File

@@ -4,10 +4,9 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import io.ktor.client.HttpClient
import io.ktor.client.plugins.onUpload
import io.ktor.client.features.onUpload
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import java.net.URLConnection
@@ -21,7 +20,7 @@ actual suspend fun HttpClient.tempUpload(
onUpload: (Long, Long) -> Unit
): TemporalFileId {
val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData(
val fileId = submitFormWithBinaryData<String>(
fullTempUploadDraftPath,
formData = formData {
append(
@@ -35,6 +34,6 @@ actual suspend fun HttpClient.tempUpload(
}
) {
onUpload(onUpload)
}.bodyAsText()
}
return TemporalFileId(fileId)
}

View File

@@ -1,5 +1,3 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.ktor.common
import kotlinx.serialization.*

View File

@@ -19,8 +19,7 @@ kotlin {
api libs.ktor.server
api libs.ktor.server.cio
api libs.ktor.server.host.common
api libs.ktor.server.websockets
api libs.ktor.server.statusPages
api libs.ktor.websockets
}
}
}

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.responseType
import io.ktor.util.InternalAPI
import io.ktor.util.reflect.TypeInfo
@InternalAPI
suspend fun <T : Any> ApplicationCall.respond(
message: T,
typeInfo: TypeInfo
) {
response.responseType = typeInfo
response.pipeline.execute(this, message as Any)
}

View File

@@ -2,27 +2,26 @@ 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.featureOrNull
import io.ktor.application.install
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.application.pluginOrNull
import io.ktor.server.routing.Route
import io.ktor.server.routing.application
import io.ktor.server.websocket.*
import io.ktor.websocket.send
import io.ktor.http.cio.websocket.*
import io.ktor.routing.Route
import io.ktor.routing.application
import io.ktor.websocket.*
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy
@Deprecated("This method will be removed soon")
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
protocol: URLProtocol? = null,
protocol: URLProtocol = URLProtocol.WS,
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
) {
application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets)
featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets)
}
webSocket(suburl, protocol ?.name) {
webSocket(suburl, protocol.name) {
safely {
flow.collect {
converter(it) ?.let { data ->
@@ -33,13 +32,12 @@ fun <T> Route.includeWebsocketHandling(
}
}
@Deprecated("This method will be removed soon")
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
protocol: URLProtocol? = null,
protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(
suburl,

View File

@@ -1,31 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.application.pluginOrNull
import io.ktor.server.routing.Route
import io.ktor.server.routing.application
import io.ktor.server.websocket.*
import io.ktor.websocket.send
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy
inline fun <reified T : Any> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
protocol: URLProtocol? = null,
noinline dataMapper: suspend WebSocketServerSession.(T) -> T? = { it }
) {
application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets)
}
webSocket(suburl, protocol ?.name) {
safely {
flow.collect {
sendSerialized(dataMapper(it) ?: return@collect)
}
}
}
}

View File

@@ -3,23 +3,26 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.*
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.*
import io.ktor.http.content.*
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.request.receiveMultipart
import io.ktor.server.response.respond
import io.ktor.server.response.respondBytes
import io.ktor.server.routing.Route
import io.ktor.server.websocket.WebSocketServerSession
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.request.receive
import io.ktor.request.receiveMultipart
import io.ktor.response.respond
import io.ktor.response.respondBytes
import io.ktor.routing.Route
import io.ktor.util.asStream
import io.ktor.util.cio.writeChannel
import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.*
import io.ktor.websocket.WebSocketServerSession
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.*
import java.io.File
import java.io.File.createTempFile
@Deprecated("This class method will be removed soon. It is now recommended to use built-in ktor features instead")
class UnifiedRouter(
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
@@ -28,7 +31,7 @@ class UnifiedRouter(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>,
protocol: URLProtocol? = null,
protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
@@ -98,7 +101,6 @@ class UnifiedRouter(
val defaultUnifiedRouter = UnifiedRouter()
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T
@@ -109,7 +111,6 @@ suspend fun <T> ApplicationCall.unianswer(
)
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniload(
deserializer: DeserializationStrategy<T>
) = safely {
@@ -122,7 +123,6 @@ suspend fun <T> ApplicationCall.uniload(
suspend fun ApplicationCall.uniloadMultipart(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
) = safely {
val multipartData = receiveMultipart()
@@ -139,19 +139,16 @@ suspend fun ApplicationCall.uniloadMultipart(
}
}
is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
}
}
resultInput ?: error("Bytes has not been received")
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniloadMultipart(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
): Pair<Input, T> {
var data: Optional<T>? = null
@@ -164,7 +161,6 @@ suspend fun <T> ApplicationCall.uniloadMultipart(
onCustomFileItem(it)
}
},
onBinaryChannelItem,
onBinaryContent
)
@@ -172,12 +168,10 @@ suspend fun <T> ApplicationCall.uniloadMultipart(
return resultInput to (completeData.dataOrNull().let { it as T })
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.uniloadMultipartFile(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
@@ -203,9 +197,7 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.streamProvider().use {
it.copyTo(fileStream)
}
it.provider().asStream().copyTo(fileStream)
}
}
}
@@ -214,7 +206,6 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
}
}
is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
}
}
@@ -225,7 +216,6 @@ suspend fun <T> ApplicationCall.uniloadMultipartFile(
suspend fun ApplicationCall.uniloadMultipartFile(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
@@ -249,9 +239,7 @@ suspend fun ApplicationCall.uniloadMultipartFile(
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.streamProvider().use {
it.copyTo(fileStream)
}
it.provider().asStream().copyTo(fileStream)
}
}
} else {
@@ -259,7 +247,6 @@ suspend fun ApplicationCall.uniloadMultipartFile(
}
}
is PartData.BinaryItem -> onBinaryContent(it)
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
}
}
@@ -286,7 +273,6 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
}
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
fun <T> ApplicationCall.decodeUrlQueryValue(
field: String,
deserializer: DeserializationStrategy<T>
@@ -297,7 +283,6 @@ fun <T> ApplicationCall.decodeUrlQueryValue(
)
}
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError(
field: String,
deserializer: DeserializationStrategy<T>

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
import io.ktor.server.application.Application
import io.ktor.application.Application
import io.ktor.server.cio.CIO
import io.ktor.server.cio.CIOApplicationEngine
import io.ktor.server.engine.*

View File

@@ -7,15 +7,14 @@ import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.common.DefaultTemporalFilesSubPath
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.streamProvider
import io.ktor.server.application.call
import io.ktor.server.request.receiveMultipart
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import io.ktor.request.receiveMultipart
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.routing.post
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
@@ -112,7 +111,7 @@ class TemporalFilesRoutingConfigurator(
temporalFilesMutex.withLock {
temporalFilesMap[fileId] = file
}
call.respondText(fileId.string)
call.respond(fileId.string)
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
} ?: call.respond(HttpStatusCode.BadRequest)
}

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.cachingheaders.CachingHeaders
import io.ktor.server.plugins.cachingheaders.CachingHeadersConfig
import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.features.CachingHeaders
import kotlinx.serialization.Contextual
data class ApplicationCachingHeadersConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun CachingHeadersConfig.invoke() }
fun interface Element { operator fun CachingHeaders.Configuration.invoke() }
override fun Application.configure() {
install(CachingHeaders) {

View File

@@ -1,9 +1,8 @@
package dev.inmo.micro_utils.ktor.server.configurators
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator.Element
import io.ktor.server.application.*
import io.ktor.server.routing.Route
import io.ktor.server.routing.Routing
import io.ktor.application.*
import io.ktor.routing.Route
import io.ktor.routing.Routing
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
@@ -19,7 +18,7 @@ class ApplicationRoutingConfigurator(
}
override fun Application.configure() {
pluginOrNull(Routing) ?.apply {
featureOrNull(Routing) ?.apply {
rootInstaller.apply { invoke() }
} ?: install(Routing) {
rootInstaller.apply { invoke() }

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.sessions.Sessions
import io.ktor.server.sessions.SessionsConfig
import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.sessions.Sessions
import kotlinx.serialization.Contextual
class ApplicationSessionsConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun SessionsConfig.invoke() }
fun interface Element { operator fun Sessions.Configuration.invoke() }
override fun Application.configure() {
install(Sessions) {

View File

@@ -1,6 +1,6 @@
package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application
import io.ktor.application.Application
interface KtorApplicationConfigurator {
fun Application.configure()

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.plugins.statuspages.StatusPagesConfig
import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.features.StatusPages
import kotlinx.serialization.Contextual
class StatusPagesConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun StatusPagesConfig.invoke() }
fun interface Element { operator fun StatusPages.Configuration.invoke() }
override fun Application.configure() {
install(StatusPages) {

View File

@@ -1,6 +1,5 @@
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
@@ -165,7 +164,7 @@ suspend fun main(vararg args: String) {
val ietfLanguageCodes = json.decodeFromString(
ListSerializer(LanguageCode.serializer()),
client.get(ietfLanguageCodesLink).bodyAsText()
client.get(ietfLanguageCodesLink)
).map {
it.copy(
title = it.title
@@ -176,7 +175,7 @@ suspend fun main(vararg args: String) {
}
val ietfLanguageCodesWithTagsMap = json.decodeFromString(
ListSerializer(LanguageCodeWithTag.serializer()),
client.get(ietfLanguageCodesAdditionalTagsLink).bodyAsText()
client.get(ietfLanguageCodesAdditionalTagsLink)
).filter { it.withSubtag != it.tag }.groupBy { it.tag }
val tags = ietfLanguageCodes.map {

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@@ -15,7 +16,6 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) {
internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw)
@Suppress("OPT_IN_USAGE")
@Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)

View File

@@ -1,60 +1,23 @@
package dev.inmo.micro_utils.pagination
import kotlinx.serialization.*
import kotlin.math.ceil
import kotlinx.serialization.Serializable
/**
* @param page Current page number
* @param size Current page size. It can be greater than size of [results]
* @param results Result objects
* @param objectsNumber Count of all objects across all pages
*/
@Serializable
data class PaginationResult<T>(
override val page: Int,
override val size: Int,
val pagesNumber: Int,
val results: List<T>,
val objectsNumber: Long
) : Pagination {
/**
* Amount of pages for current pagination
*/
@EncodeDefault
val pagesNumber: Int = ceil(objectsNumber / size.toFloat()).toInt()
override val size: Int
) : Pagination
constructor(
page: Int,
results: List<T>,
pagesNumber: Int,
size: Int
) : this(
page,
size,
results,
(pagesNumber * size).toLong()
)
@Deprecated("Replace with The other order of incoming parameters or objectsCount parameter")
constructor(
page: Int,
pagesNumber: Int,
results: List<T>,
size: Int
) : this(
page,
results,
pagesNumber,
size
)
}
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L)
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0)
/**
* @return New [PaginationResult] with [data] without checking of data sizes equality
*/
fun <I, O> PaginationResult<I>.changeResultsUnchecked(
data: List<O>
): PaginationResult<O> = PaginationResult(page, size, data, objectsNumber)
): PaginationResult<O> = PaginationResult(page, pagesNumber, data, size)
/**
* @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality
*/
@@ -70,9 +33,12 @@ fun <T> List<T>.createPaginationResult(
commonObjectsNumber: Long
) = PaginationResult(
pagination.page,
pagination.size,
calculatePagesNumber(
commonObjectsNumber,
pagination.size
),
this,
commonObjectsNumber
pagination.size
)
fun <T> List<T>.createPaginationResult(
@@ -80,9 +46,12 @@ fun <T> List<T>.createPaginationResult(
commonObjectsNumber: Long
) = PaginationResult(
calculatePage(firstIndex, size),
size,
calculatePagesNumber(
commonObjectsNumber,
size
),
this,
commonObjectsNumber
size
)
fun <T> Pair<Long, List<T>>.createPaginationResult(

View File

@@ -26,10 +26,6 @@ inline fun Pagination.nextPage() =
size
)
/**
* @param page Current page number
* @param size Current page size
*/
@Serializable
data class SimplePagination(
override val page: Int,

View File

@@ -27,5 +27,4 @@ inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? =
null
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()

View File

@@ -2,19 +2,19 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
inline fun <T> doForAll(
suspend fun <T> doForAll(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?,
block: (Pagination) -> PaginationResult<T>
block: suspend (Pagination) -> PaginationResult<T>
) {
doWithPagination(initialPagination) {
block(it).let(paginationMapper)
}
}
inline fun <T> doForAllWithNextPaging(
suspend fun <T> doForAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>
block: suspend (Pagination) -> PaginationResult<T>
) {
doForAll(
initialPagination,
@@ -23,9 +23,9 @@ inline fun <T> doForAllWithNextPaging(
)
}
inline fun <T> doAllWithCurrentPaging(
suspend fun <T> doAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>
block: suspend (Pagination) -> PaginationResult<T>
) {
doForAll(
initialPagination,
@@ -34,7 +34,7 @@ inline fun <T> doAllWithCurrentPaging(
)
}
inline fun <T> doForAllWithCurrentPaging(
suspend fun <T> doForAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>
block: suspend (Pagination) -> PaginationResult<T>
) = doAllWithCurrentPaging(initialPagination, block)

View File

@@ -2,10 +2,10 @@ package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
inline fun <T> getAll(
suspend fun <T> getAll(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: (PaginationResult<T>) -> Pagination?,
block: (Pagination) -> PaginationResult<T>
block: suspend (Pagination) -> PaginationResult<T>
): List<T> {
val results = mutableListOf<T>()
doForAll(initialPagination, paginationMapper) {
@@ -16,45 +16,46 @@ inline fun <T> getAll(
return results.toList()
}
inline fun <T, R> R.getAllBy(
suspend fun <T, R> R.getAllBy(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: R.(PaginationResult<T>) -> Pagination?,
block: R.(Pagination) -> PaginationResult<T>
block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ paginationMapper(it) },
{ block(it) }
)
inline fun <T> getAllWithNextPaging(
suspend fun <T> getAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>
block: suspend (Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ it.nextPageIfNotEmpty() },
block
)
inline fun <T, R> R.getAllByWithNextPaging(
suspend fun <T, R> R.getAllByWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T>
block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithNextPaging(
initialPagination,
{ block(it) }
)
inline fun <T> getAllWithCurrentPaging(
suspend fun <T> getAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: (Pagination) -> PaginationResult<T>
block: suspend (Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ it.currentPageIfNotEmpty() },
block
)
inline fun <T, R> R.getAllByWithCurrentPaging(
suspend fun <T, R> R.getAllByWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: R.(Pagination) -> PaginationResult<T>
block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithCurrentPaging(
initialPagination
) { block(it) }
initialPagination,
{ block(it) }
)

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.pagination
import io.ktor.application.ApplicationCall
import io.ktor.http.Parameters
import io.ktor.server.application.ApplicationCall
val Parameters.extractPagination: Pagination
get() = SimplePagination(

View File

@@ -15,4 +15,4 @@ kotlin {
}
}
}
}
}

View File

@@ -1,16 +1,16 @@
package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
open class ReadCRUDCacheRepo<ObjectType, IdType>(
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
protected open val kvCache: KVCache<IdType, ObjectType>,
protected open val idGetter: (ObjectType) -> IdType
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CacheRepo {
protected val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
protected val kvCache: KVCache<IdType, ObjectType>,
protected val idGetter: (ObjectType) -> IdType
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo {
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
kvCache.set(id, it)
})
@@ -18,63 +18,8 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
}
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
kvCache: KVCache<IdType, ObjectType>,
idGetter: (ObjectType) -> IdType
) = ReadCRUDCacheRepo(this, kvCache, idGetter)
open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
protected open val parentRepo: WriteCRUDRepo<ObjectType, IdType, InputValueType>,
protected open val kvCache: KVCache<IdType, ObjectType>,
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
protected open val idGetter: (ObjectType) -> IdType
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CacheRepo {
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach {
kvCache.unset(it)
}.launchIn(scope)
override suspend fun deleteById(ids: List<IdType>) = parentRepo.deleteById(ids)
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
val updated = parentRepo.update(values)
kvCache.unset(values.map { it.id })
kvCache.set(updated.associateBy { idGetter(it) })
return updated
}
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
return parentRepo.update(id, value) ?.also {
kvCache.unset(id)
kvCache.set(idGetter(it), it)
}
}
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
val created = parentRepo.create(values)
kvCache.set(
created.associateBy { idGetter(it) }
)
return created
}
}
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope,
idGetter: (ObjectType) -> IdType
) = WriteCRUDCacheRepo(this, kvCache, scope, idGetter)
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
idGetter: (ObjectType) -> IdType
@@ -82,17 +27,8 @@ open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
parentRepo,
kvCache,
idGetter
),
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
parentRepo,
kvCache,
scope,
idGetter
),
CRUDRepo<ObjectType, IdType, InputValueType>
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
kvCache: KVCache<IdType, ObjectType>,
scope: CoroutineScope,
idGetter: (ObjectType) -> IdType
) = CRUDCacheRepo(this, kvCache, scope, idGetter)
), CRUDRepo<ObjectType, IdType, InputValueType>, WriteCRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
}

View File

@@ -1,3 +0,0 @@
package dev.inmo.micro_utils.repos.cache
interface CacheRepo

View File

@@ -1,32 +1,36 @@
package dev.inmo.micro_utils.repos.cache.cache
package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
interface KVCache<K, V> : KeyValueRepo<K, V>
open class SimpleKVCache<K, V>(
protected val cachedValuesCount: Int,
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
) : KVCache<K, V>, KeyValueRepo<K, V> by kvParent {
protected open val cacheQueue = ArrayDeque<K>(cachedValuesCount)
protected open val cacheStack = ArrayList<K>(cachedValuesCount)
protected val syncMutex = Mutex()
protected suspend fun makeUnset(toUnset: List<K>) {
cacheQueue.removeAll(toUnset)
cacheStack.removeAll(toUnset)
kvParent.unset(toUnset)
}
override suspend fun set(toSet: Map<K, V>) {
syncMutex.withLock {
for ((k, v) in toSet) {
if (cacheQueue.size >= cachedValuesCount) {
cacheQueue.removeFirstOrNull() ?.let {
kvParent.unset(it)
}
}
cacheQueue.addLast(k)
kvParent.set(k, v)
if (toSet.size > cachedValuesCount) {
cacheStack.clear()
kvParent.unset(getAllWithNextPaging { kvParent.keys(it) })
val keysToInclude = toSet.keys.drop(toSet.size - cachedValuesCount)
cacheStack.addAll(keysToInclude)
kvParent.set(keysToInclude.associateWith { toSet.getValue(it) })
} else {
makeUnset(cacheStack.take(toSet.size))
}
}
}
@@ -35,8 +39,3 @@ open class SimpleKVCache<K, V>(
syncMutex.withLock { makeUnset(toUnset) }
}
}
inline fun <K, V> KVCache(
cachedValuesCount: Int,
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
) = SimpleKVCache<K, V>(cachedValuesCount, kvParent)

View File

@@ -1,35 +1,25 @@
package dev.inmo.micro_utils.repos.cache
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class ReadKeyValueCacheRepo<Key,Value>(
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
protected open val kvCache: KVCache<Key, Value>,
) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo {
protected val parentRepo: ReadKeyValueRepo<Key, Value>,
protected val kvCache: KVCache<Key, Value>,
) : ReadKeyValueRepo<Key,Value> by parentRepo {
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
}
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
kvCache: KVCache<Key, Value>
) = ReadKeyValueCacheRepo(this, kvCache)
open class KeyValueCacheRepo<Key,Value>(
parentRepo: KeyValueRepo<Key, Value>,
kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CacheRepo {
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
}
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
kvCache: KVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = KeyValueCacheRepo(this, kvCache, scope)

View File

@@ -5,15 +5,16 @@ 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.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class ReadKeyValuesCacheRepo<Key,Value>(
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
protected open val kvCache: KVCache<Key, List<Value>>
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
protected val parentRepo: ReadKeyValuesRepo<Key, Value>,
protected val kvCache: KVCache<Key, List<Value>>
) : ReadKeyValuesRepo<Key,Value> by parentRepo {
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return kvCache.get(k) ?.paginate(
pagination.let { if (reversed) it.reverse(count(k)) else it }
@@ -30,21 +31,12 @@ open class ReadKeyValuesCacheRepo<Key,Value>(
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
}
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
kvCache: KVCache<Key, List<Value>>
) = ReadKeyValuesCacheRepo(this, kvCache)
open class KeyValuesCacheRepo<Key,Value>(
parentRepo: KeyValuesRepo<Key, Value>,
kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
}
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
kvCache: KVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = KeyValuesCacheRepo(this, kvCache, scope)

View File

@@ -1,8 +0,0 @@
package dev.inmo.micro_utils.repos.cache.cache
/**
* This interface declares that current type of [KVCache] will contains all the data all the time of its life
*/
interface FullKVCache<K, V> : KVCache<K, V> {
companion object
}

View File

@@ -1,7 +0,0 @@
package dev.inmo.micro_utils.repos.cache.cache
import dev.inmo.micro_utils.repos.*
interface KVCache<K, V> : KeyValueRepo<K, V> {
companion object
}

View File

@@ -1,28 +0,0 @@
package dev.inmo.micro_utils.repos.cache.cache
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.MapKeyValueRepo
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class SimpleFullKVCache<K, V>(
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
) : FullKVCache<K, V>, KeyValueRepo<K, V> by kvParent {
protected val syncMutex = Mutex()
override suspend fun set(toSet: Map<K, V>) {
syncMutex.withLock {
kvParent.set(toSet)
}
}
override suspend fun unset(toUnset: List<K>) {
syncMutex.withLock {
kvParent.unset(toUnset)
}
}
}
inline fun <K, V> FullKVCache(
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
) = SimpleFullKVCache<K, V>(kvParent)

View File

@@ -1,95 +0,0 @@
package dev.inmo.micro_utils.repos.cache.full
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.*
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.cache.KVCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
open class FullReadCRUDCacheRepo<ObjectType, IdType>(
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
protected open val kvCache: FullKVCache<IdType, ObjectType>,
protected open val idGetter: (ObjectType) -> IdType
) : ReadCRUDRepo<ObjectType, IdType>, FullCacheRepo {
protected inline fun <T> doOrTakeAndActualize(
action: FullKVCache<IdType, ObjectType>.() -> Optional<T>,
actionElse: ReadCRUDRepo<ObjectType, IdType>.() -> T,
actualize: FullKVCache<IdType, ObjectType>.(T) -> Unit
): T {
kvCache.action().onPresented {
return it
}.onAbsent {
return parentRepo.actionElse().also {
kvCache.actualize(it)
}
}
error("The result should be returned above")
}
protected open suspend fun actualizeAll() {
kvCache.clear()
doForAllWithNextPaging {
parentRepo.getByPagination(it).also {
kvCache.set(it.results.associateBy { idGetter(it) })
}
}
}
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
{ values(pagination).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
{ getByPagination(pagination) },
{ if (it.results.isNotEmpty()) actualizeAll() }
)
override suspend fun count(): Long = doOrTakeAndActualize(
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
{ count() },
{ if (it != 0L) actualizeAll() }
)
override suspend fun contains(id: IdType): Boolean = doOrTakeAndActualize(
{ contains(id).takeIf { it }.optionalOrAbsentIfNull },
{ contains(id) },
{ if (it) parentRepo.getById(id) ?.let { set(id, it) } }
)
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize(
{ get(id) ?.optional ?: Optional.absent() },
{ getById(id) },
{ it ?.let { set(idGetter(it), it) } }
)
}
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
kvCache: FullKVCache<IdType, ObjectType>,
idGetter: (ObjectType) -> IdType
) = FullReadCRUDCacheRepo(this, kvCache, idGetter)
open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
kvCache: FullKVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
idGetter: (ObjectType) -> IdType
) : FullReadCRUDCacheRepo<ObjectType, IdType>(
parentRepo,
kvCache,
idGetter
),
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
parentRepo,
kvCache,
scope,
idGetter
),
CRUDRepo<ObjectType, IdType, InputValueType>
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
kvCache: FullKVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
idGetter: (ObjectType) -> IdType
) = FullCRUDCacheRepo(this, kvCache, scope, idGetter)

View File

@@ -1,5 +0,0 @@
package dev.inmo.micro_utils.repos.cache.full
import dev.inmo.micro_utils.repos.cache.CacheRepo
interface FullCacheRepo : CacheRepo

View File

@@ -1,104 +0,0 @@
package dev.inmo.micro_utils.repos.cache.full
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.pagination.getAll
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
open class FullReadKeyValueCacheRepo<Key,Value>(
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
protected open val kvCache: FullKVCache<Key, Value>,
) : ReadKeyValueRepo<Key, Value>, FullCacheRepo {
protected inline fun <T> doOrTakeAndActualize(
action: FullKVCache<Key, Value>.() -> Optional<T>,
actionElse: ReadKeyValueRepo<Key, Value>.() -> T,
actualize: FullKVCache<Key, Value>.(T) -> Unit
): T {
kvCache.action().onPresented {
return it
}.onAbsent {
return parentRepo.actionElse().also {
kvCache.actualize(it)
}
}
error("The result should be returned above")
}
protected open suspend fun actualizeAll() {
kvCache.clear()
kvCache.set(parentRepo.getAll { keys(it) }.toMap())
}
override suspend fun get(k: Key): Value? = doOrTakeAndActualize(
{ get(k) ?.optional ?: Optional.absent() },
{ get(k) },
{ set(k, it ?: return@doOrTakeAndActualize) }
)
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = doOrTakeAndActualize(
{ values(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
{ values(pagination, reversed) },
{ if (it.results.isNotEmpty()) actualizeAll() }
)
override suspend fun count(): Long = doOrTakeAndActualize(
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
{ count() },
{ if (it != 0L) actualizeAll() }
)
override suspend fun contains(key: Key): Boolean = doOrTakeAndActualize(
{ contains(key).takeIf { it }.optionalOrAbsentIfNull },
{ contains(key) },
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
)
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
{ keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
{ keys(pagination, reversed) },
{ if (it.results.isNotEmpty()) actualizeAll() }
)
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
{ keys(v, pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
{ parentRepo.keys(v, pagination, reversed) },
{ if (it.results.isNotEmpty()) actualizeAll() }
)
}
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
kvCache: FullKVCache<Key, Value>
) = FullReadKeyValueCacheRepo(this, kvCache)
open class FullWriteKeyValueCacheRepo<Key,Value>(
protected open val parentRepo: WriteKeyValueRepo<Key, Value>,
protected open val kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
}
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
open class FullKeyValueCacheRepo<Key,Value>(
parentRepo: KeyValueRepo<Key, Value>,
kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
KeyValueRepo<Key,Value>,
ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(parentRepo, kvCache) {
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
}
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = FullKeyValueCacheRepo(this, kvCache, scope)

View File

@@ -1,154 +0,0 @@
package dev.inmo.micro_utils.repos.cache.full
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.*
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
open class FullReadKeyValuesCacheRepo<Key,Value>(
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
protected open val kvCache: FullKVCache<Key, List<Value>>,
) : ReadKeyValuesRepo<Key, Value>, FullCacheRepo {
protected inline fun <T> doOrTakeAndActualize(
action: FullKVCache<Key, List<Value>>.() -> Optional<T>,
actionElse: ReadKeyValuesRepo<Key, Value>.() -> T,
actualize: FullKVCache<Key, List<Value>>.(T) -> Unit
): T {
kvCache.action().onPresented {
return it
}.onAbsent {
return parentRepo.actionElse().also {
kvCache.actualize(it)
}
}
error("The result should be returned above")
}
protected open suspend fun actualizeKey(k: Key) {
kvCache.set(k, parentRepo.getAll(k))
}
protected open suspend fun actualizeAll() {
doAllWithCurrentPaging { kvCache.keys(it).also { kvCache.unset(it.results) } }
kvCache.set(parentRepo.getAll())
}
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
return doOrTakeAndActualize(
{
get(k) ?.paginate(
pagination.let { if (reversed) it.reverse(count(k)) else it }
) ?.let {
if (reversed) it.copy(results = it.results.reversed()) else it
}.optionalOrAbsentIfNull
},
{ get(k, pagination, reversed) },
{ actualizeKey(k) }
)
}
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
return doOrTakeAndActualize(
{
kvCache.keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull
},
{ parentRepo.keys(pagination, reversed) },
{ actualizeAll() }
)
}
override suspend fun count(): Long = doOrTakeAndActualize(
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
{ count() },
{ if (it != 0L) actualizeAll() }
)
override suspend fun count(k: Key): Long = doOrTakeAndActualize(
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
{ count() },
{ if (it != 0L) actualizeKey(k) }
)
override suspend fun contains(k: Key, v: Value): Boolean = doOrTakeAndActualize(
{ get(k) ?.contains(v).takeIf { it == true }.optionalOrAbsentIfNull },
{ contains(k, v) },
{ if (it) actualizeKey(k) }
)
override suspend fun contains(k: Key): Boolean = doOrTakeAndActualize(
{ contains(k).takeIf { it }.optionalOrAbsentIfNull },
{ contains(k) },
{ if (it) actualizeKey(k) }
)
override suspend fun keys(
v: Value,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Key> = doOrTakeAndActualize(
{
val keys = getAllWithNextPaging { keys(it) }.filter { get(it) ?.contains(v) == true }.optionallyReverse(reversed)
if (keys.isNotEmpty()) {
keys.paginate(pagination.optionallyReverse(keys.size, reversed)).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull
} else {
Optional.absent()
}
},
{ parentRepo.keys(v, pagination, reversed) },
{ if (it.results.isNotEmpty()) actualizeAll() }
)
}
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
kvCache: FullKVCache<Key, List<Value>>
) = FullReadKeyValuesCacheRepo(this, kvCache)
open class FullWriteKeyValuesCacheRepo<Key,Value>(
protected open val parentRepo: WriteKeyValuesRepo<Key, Value>,
protected open val kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach {
kvCache.set(
it.first,
kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)
)
}.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach {
kvCache.set(
it.first,
kvCache.get(it.first) ?.minus(it.second) ?: return@onEach
)
}.launchIn(scope)
}
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
open class FullKeyValuesCacheRepo<Key,Value>(
parentRepo: KeyValuesRepo<Key, Value>,
kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
KeyValuesRepo<Key, Value>,
ReadKeyValuesRepo<Key, Value> by FullReadKeyValuesCacheRepo(parentRepo, kvCache) {
override suspend fun clearWithValue(v: Value) {
doAllWithCurrentPaging {
keys(v, it).also {
remove(it.results.associateWith { listOf(v) })
}
}
}
}
fun <Key, Value> KeyValuesRepo<Key, Value>.caching(
kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = FullKeyValuesCacheRepo(this, kvCache, scope)

View File

@@ -1,51 +1,21 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.common.*
@Suppress("UNCHECKED_CAST")
interface MapperRepo<FromKey, FromValue, ToKey, ToValue> {
val keyMapper: SimpleSuspendableMapper<FromKey, ToKey>
get() = simpleSuspendableMapper(
{ it.toInnerKey() },
{ it.toOutKey() }
)
val valueMapper: SimpleSuspendableMapper<FromValue, ToValue>
get() = simpleSuspendableMapper(
{ it.toInnerValue() },
{ it.toOutValue() }
)
suspend fun FromKey.toOutKey() = this as ToKey
suspend fun FromValue.toOutValue() = this as ToValue
suspend fun ToKey.toInnerKey() = this as FromKey
suspend fun ToValue.toInnerValue() = this as FromValue
companion object
}
class SimpleMapperRepo<FromKey, FromValue, ToKey, ToValue>(
private val keyFromToTo: suspend FromKey.() -> ToKey,
private val valueFromToTo: suspend FromValue.() -> ToValue,
private val keyToToFrom: suspend ToKey.() -> FromKey,
private val valueToToFrom: suspend ToValue.() -> FromValue
) : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
) = object : MapperRepo<FromKey, FromValue, ToKey, ToValue> {
override suspend fun FromKey.toOutKey(): ToKey = keyFromToTo()
override suspend fun FromValue.toOutValue(): ToValue = valueFromToTo()
override suspend fun ToKey.toInnerKey(): FromKey = keyToToFrom()
override suspend fun ToValue.toInnerValue(): FromValue = valueToToFrom()
}
operator fun <FromKey, FromValue, ToKey, ToValue> MapperRepo.Companion.invoke(
keyFromToTo: suspend FromKey.() -> ToKey,
valueFromToTo: suspend FromValue.() -> ToValue,
keyToToFrom: suspend ToKey.() -> FromKey,
valueToToFrom: suspend ToValue.() -> FromValue
) = SimpleMapperRepo(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> mapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
) = MapperRepo(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)

View File

@@ -1,10 +1,11 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithCurrentPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.flow.Flow
interface ReadKeyValuesRepo<Key, Value> : Repo {
interface ReadOneToManyKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key, pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
@@ -35,9 +36,9 @@ interface ReadKeyValuesRepo<Key, Value> : Repo {
}
}
}
typealias ReadOneToManyKeyValueRepo<Key,Value> = ReadKeyValuesRepo<Key, Value>
typealias ReadKeyValuesRepo<Key,Value> = ReadOneToManyKeyValueRepo<Key, Value>
interface WriteKeyValuesRepo<Key, Value> : Repo {
interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>>
val onValueRemoved: Flow<Pair<Key, Value>>
val onDataCleared: Flow<Key>
@@ -54,41 +55,41 @@ interface WriteKeyValuesRepo<Key, Value> : Repo {
add(toSet)
}
}
typealias WriteOneToManyKeyValueRepo<Key,Value> = WriteKeyValuesRepo<Key, Value>
typealias WriteKeyValuesRepo<Key,Value> = WriteOneToManyKeyValueRepo<Key, Value>
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add(
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
keysAndValues: List<Pair<Key, List<Value>>>
) = add(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.add(
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.add(
vararg keysAndValues: Pair<Key, List<Value>>
) = add(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add(
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
k: Key, v: List<Value>
) = add(mapOf(k to v))
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.add(
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.add(
k: Key, vararg v: Value
) = add(k, v.toList())
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set(
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
keysAndValues: List<Pair<Key, List<Value>>>
) = set(keysAndValues.toMap())
suspend inline fun <Key, Value, REPO : WriteKeyValuesRepo<Key, Value>> REPO.set(
suspend inline fun <Key, Value, REPO : WriteOneToManyKeyValueRepo<Key, Value>> REPO.set(
vararg keysAndValues: Pair<Key, List<Value>>
) = set(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set(
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, v: List<Value>
) = set(mapOf(k to v))
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.set(
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
k: Key, vararg v: Value
) = set(k, v.toList())
interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyValuesRepo<Key, Value> {
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> {
override suspend fun clearWithValue(v: Value) {
doWithPagination {
val keysResult = keys(v, it)
@@ -101,29 +102,22 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
}
}
}
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value>
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
class DelegateBasedKeyValuesRepo<Key, Value>(
readDelegate: ReadKeyValuesRepo<Key, Value>,
writeDelegate: WriteKeyValuesRepo<Key, Value>
) : KeyValuesRepo<Key, Value>,
ReadKeyValuesRepo<Key, Value> by readDelegate,
WriteKeyValuesRepo<Key, Value> by writeDelegate
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
keysAndValues: List<Pair<Key, List<Value>>>
) = remove(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
vararg keysAndValues: Pair<Key, List<Value>>
) = remove(keysAndValues.toMap())
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key,
v: List<Value>
) = remove(mapOf(k to v))
suspend inline fun <Key, Value> WriteKeyValuesRepo<Key, Value>.remove(
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
k: Key,
vararg v: Value
) = remove(k, v.toList())

View File

@@ -4,13 +4,13 @@ import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import kotlinx.coroutines.flow.Flow
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
interface ReadStandardCRUDRepo<ObjectType, IdType> : Repo {
suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType>
suspend fun getById(id: IdType): ObjectType?
suspend fun contains(id: IdType): Boolean
suspend fun count(): Long
}
typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType>
typealias ReadCRUDRepo<ObjectType, IdType> = ReadStandardCRUDRepo<ObjectType, IdType>
typealias UpdatedValuePair<IdType, ValueType> = Pair<IdType, ValueType>
val <IdType> UpdatedValuePair<IdType, *>.id
@@ -18,7 +18,7 @@ val <IdType> UpdatedValuePair<IdType, *>.id
val <ValueType> UpdatedValuePair<*, ValueType>.value
get() = second
interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
interface WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
val newObjectsFlow: Flow<ObjectType>
val updatedObjectsFlow: Flow<ObjectType>
val deletedObjectsIdsFlow: Flow<IdType>
@@ -28,25 +28,18 @@ interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType>
suspend fun deleteById(ids: List<IdType>)
}
typealias WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> = WriteCRUDRepo<ObjectType, IdType, InputValueType>
typealias WriteCRUDRepo<ObjectType, IdType, InputValueType> = WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.create(
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.create(
vararg values: InputValueType
): List<ObjectType> = create(values.toList())
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.update(
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.update(
vararg values: UpdatedValuePair<IdType, InputValueType>
): List<ObjectType> = update(values.toList())
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.deleteById(
suspend fun <ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>.deleteById(
vararg ids: IdType
) = deleteById(ids.toList())
interface CRUDRepo<ObjectType, IdType, InputValueType> : ReadCRUDRepo<ObjectType, IdType>,
WriteCRUDRepo<ObjectType, IdType, InputValueType>
typealias StandardCRUDRepo<ObjectType, IdType, InputValueType> = CRUDRepo<ObjectType, IdType, InputValueType>
class DelegateBasedCRUDRepo<ObjectType, IdType, InputValueType>(
readDelegate: ReadCRUDRepo<ObjectType, IdType>,
writeDelegate: WriteCRUDRepo<ObjectType, IdType, InputValueType>
) : CRUDRepo<ObjectType, IdType, InputValueType>,
ReadCRUDRepo<ObjectType, IdType> by readDelegate,
WriteCRUDRepo<ObjectType, IdType, InputValueType> by writeDelegate
interface StandardCRUDRepo<ObjectType, IdType, InputValueType> : ReadStandardCRUDRepo<ObjectType, IdType>,
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
typealias CRUDRepo<ObjectType, IdType, InputValueType> = StandardCRUDRepo<ObjectType, IdType, InputValueType>

View File

@@ -4,7 +4,7 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
import kotlinx.coroutines.flow.Flow
interface ReadKeyValueRepo<Key, Value> : Repo {
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
suspend fun get(k: Key): Value?
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
@@ -12,9 +12,9 @@ interface ReadKeyValueRepo<Key, Value> : Repo {
suspend fun contains(key: Key): Boolean
suspend fun count(): Long
}
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value>
typealias ReadKeyValueRepo<Key,Value> = ReadStandardKeyValueRepo<Key, Value>
interface WriteKeyValueRepo<Key, Value> : Repo {
interface WriteStandardKeyValueRepo<Key, Value> : Repo {
val onNewValue: Flow<Pair<Key, Value>>
val onValueRemoved: Flow<Key>
@@ -22,25 +22,25 @@ interface WriteKeyValueRepo<Key, Value> : Repo {
suspend fun unset(toUnset: List<Key>)
suspend fun unsetWithValues(toUnset: List<Value>)
}
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
vararg toSet: Pair<Key, Value>
) = set(toSet.toMap())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.set(
k: Key, v: Value
) = set(k to v)
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
vararg k: Key
) = unset(k.toList())
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithValues(
vararg v: Value
) = unsetWithValues(v.toList())
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> {
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> {
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
doAllWithCurrentPaging {
keys(v, it).also {
@@ -48,16 +48,5 @@ interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValue
}
}
}
suspend fun clear() {
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
}
}
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
class DelegateBasedKeyValueRepo<Key, Value>(
readDelegate: ReadKeyValueRepo<Key, Value>,
writeDelegate: WriteKeyValueRepo<Key, Value>
) : KeyValueRepo<Key, Value>,
ReadKeyValueRepo<Key, Value> by readDelegate,
WriteKeyValueRepo<Key, Value> by writeDelegate
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>

View File

@@ -1,130 +0,0 @@
package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>(
private val to: ReadCRUDRepo<ToRegistered, ToId>,
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>
) : ReadCRUDRepo<FromRegistered, FromId>, MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper {
override suspend fun getByPagination(
pagination: Pagination
): PaginationResult<FromRegistered> = to.getByPagination(
pagination
).let {
it.changeResultsUnchecked(
it.results.map { it.toInnerValue() }
)
}
override suspend fun count(): Long = to.count()
override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey())
override suspend fun getById(id: FromId): FromRegistered? = to.getById(
id.toOutKey()
) ?.toInnerValue()
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadCRUDRepo<FromValue, FromKey> = MapperReadCRUDRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadCRUDRepo<ToValue, ToKey>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadCRUDRepo<FromValue, FromKey> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperWriteCRUDRepo<FromId, FromRegistered, FromInput, ToId, ToRegistered, ToInput>(
private val to: WriteCRUDRepo<ToRegistered, ToId, ToInput>,
mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>,
inputMapper: SimpleSuspendableMapper<FromInput, ToInput>
) : WriteCRUDRepo<FromRegistered, FromId, FromInput>,
MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper,
SimpleSuspendableMapper<FromInput, ToInput> by inputMapper {
override val newObjectsFlow: Flow<FromRegistered> = to.newObjectsFlow.map { it.toInnerValue() }
override val updatedObjectsFlow: Flow<FromRegistered> = to.updatedObjectsFlow.map { it.toInnerValue() }
override val deletedObjectsIdsFlow: Flow<FromId> = to.deletedObjectsIdsFlow.map { it.toInnerKey() }
override suspend fun deleteById(ids: List<FromId>) = to.deleteById(ids.map { it.toOutKey() })
override suspend fun update(
values: List<UpdatedValuePair<FromId, FromInput>>
): List<FromRegistered> = to.update(
values.map {
it.first.toOutKey() to convert(it.second)
}
).map { it.toInnerValue() }
override suspend fun update(
id: FromId,
value: FromInput
): FromRegistered? = to.update(id.toOutKey(), convert(value)) ?.toInnerValue()
override suspend fun create(values: List<FromInput>): List<FromRegistered> = to.create(
values.map {
convert(it)
}
).map {
it.toInnerValue()
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, FromInput, ToKey, ToValue, ToInput> WriteCRUDRepo<ToValue, ToKey, ToInput>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>,
simpleSuspendableMapper: SimpleSuspendableMapper<FromInput, ToInput>
): WriteCRUDRepo<FromValue, FromKey, FromInput> = MapperWriteCRUDRepo(this, mapper, simpleSuspendableMapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified FromInput, reified ToKey, reified ToValue, reified ToInput> WriteCRUDRepo<ToValue, ToKey, ToInput>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline inputFromToTo: suspend FromInput.() -> ToInput = { this as ToInput },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
noinline inputToToFrom: suspend ToInput.() -> FromInput = { this as FromInput },
): WriteCRUDRepo<FromValue, FromKey, FromInput> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom),
simpleSuspendableMapper({ inputToToFrom(it) }, { inputFromToTo(it) })
)
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
open class MapperCRUDRepo<FromId, FromRegistered, FromInput, ToId, ToRegistered, ToInput>(
private val to: CRUDRepo<ToRegistered, ToId, ToInput>,
private val mapper: MapperRepo<FromId, FromRegistered, ToId, ToRegistered>,
private val inputMapper: SimpleSuspendableMapper<FromInput, ToInput>
) : CRUDRepo<FromRegistered, FromId, FromInput>,
MapperRepo<FromId, FromRegistered, ToId, ToRegistered> by mapper,
ReadCRUDRepo<FromRegistered, FromId> by MapperReadCRUDRepo(to, mapper),
WriteCRUDRepo<FromRegistered, FromId, FromInput> by MapperWriteCRUDRepo(to, mapper, inputMapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, FromInput, ToKey, ToValue, ToInput> CRUDRepo<ToValue, ToKey, ToInput>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>,
simpleSuspendableMapper: SimpleSuspendableMapper<FromInput, ToInput>
): CRUDRepo<FromValue, FromKey, FromInput> = MapperCRUDRepo(this, mapper, simpleSuspendableMapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified FromInput, reified ToKey, reified ToValue, reified ToInput> CRUDRepo<ToValue, ToKey, ToInput>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline inputFromToTo: suspend FromInput.() -> ToInput = { this as ToInput },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
noinline inputToToFrom: suspend ToInput.() -> FromInput = { this as FromInput },
): CRUDRepo<FromValue, FromKey, FromInput> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom),
simpleSuspendableMapper({ inputToToFrom(it) }, { inputFromToTo(it) })
)

View File

@@ -1,14 +1,15 @@
package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadKeyValueRepo<ToKey, ToValue>,
open class MapperReadStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadStandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
) : ReadStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override suspend fun get(k: FromKey): FromValue? = to.get(
k.toOutKey()
) ?.toInnerValue()
@@ -20,8 +21,11 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
pagination,
reversed
).let {
it.changeResultsUnchecked(
it.results.map { it.toInnerValue() }
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerValue() },
it.size
)
}
@@ -32,8 +36,11 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
pagination,
reversed
).let {
it.changeResultsUnchecked(
it.results.map { it.toInnerKey() }
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
@@ -46,8 +53,11 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
pagination,
reversed
).let {
it.changeResultsUnchecked(
it.results.map { it.toInnerKey() }
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
@@ -59,24 +69,24 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper(
inline fun <FromKey, FromValue, ToKey, ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadKeyValueRepo<FromKey, FromValue> = MapperReadKeyValueRepo(this, mapper)
): ReadStandardKeyValueRepo<FromKey, FromValue> = MapperReadStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadKeyValueRepo<FromKey, FromValue> = withMapper(
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadStandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteKeyValueRepo<ToKey, ToValue>,
open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteStandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
) : WriteStandardKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
k.toInnerKey() to v.toInnerValue()
}
@@ -102,40 +112,39 @@ open class MapperWriteKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper(
inline fun <FromKey, FromValue, ToKey, ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteKeyValueRepo<FromKey, FromValue> = MapperWriteKeyValueRepo(this, mapper)
): WriteStandardKeyValueRepo<FromKey, FromValue> = MapperWriteStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteKeyValueRepo<FromKey, FromValue> = withMapper(
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteStandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteStandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
open class MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: KeyValueRepo<ToKey, ToValue>,
private val mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : KeyValueRepo<FromKey, FromValue>,
open class MapperStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: StandardKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : StandardKeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadKeyValueRepo<FromKey, FromValue> by MapperReadKeyValueRepo(to, mapper),
WriteKeyValueRepo<FromKey, FromValue> by MapperWriteKeyValueRepo(to, mapper)
ReadStandardKeyValueRepo<FromKey, FromValue> by MapperReadStandardKeyValueRepo(to, mapper),
WriteStandardKeyValueRepo<FromKey, FromValue> by MapperWriteStandardKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValueRepo<ToKey, ToValue>.withMapper(
inline fun <FromKey, FromValue, ToKey, ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): KeyValueRepo<FromKey, FromValue> = MapperKeyValueRepo(this, mapper)
): StandardKeyValueRepo<FromKey, FromValue> = MapperStandardKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValueRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): KeyValueRepo<FromKey, FromValue> = withMapper(
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> StandardKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): StandardKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)

View File

@@ -1,14 +1,15 @@
package dev.inmo.micro_utils.repos.mappers
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadKeyValuesRepo<ToKey, ToValue>,
open class MapperReadOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: ReadOneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : ReadKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
) : ReadOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override suspend fun get(
k: FromKey,
pagination: Pagination,
@@ -18,8 +19,11 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
pagination,
reversed
).let {
it.changeResultsUnchecked(
it.results.map { it.toInnerValue() }
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerValue() },
it.size
)
}
@@ -30,8 +34,11 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
pagination,
reversed
).let {
it.changeResultsUnchecked(
it.results.map { it.toInnerKey() }
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
@@ -44,8 +51,11 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
pagination,
reversed
).let {
it.changeResultsUnchecked(
it.results.map { it.toInnerKey() }
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it.toInnerKey() },
it.size
)
}
@@ -57,24 +67,24 @@ open class MapperReadKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper(
inline fun <FromKey, FromValue, ToKey, ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): ReadKeyValuesRepo<FromKey, FromValue> = MapperReadKeyValuesRepo(this, mapper)
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = MapperReadOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadKeyValuesRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadKeyValuesRepo<FromKey, FromValue> = withMapper(
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> ReadOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): ReadOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteKeyValuesRepo<ToKey, ToValue>,
open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: WriteOneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : WriteKeyValuesRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
) : WriteOneToManyKeyValueRepo<FromKey, FromValue>, MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper {
override val onNewValue: Flow<Pair<FromKey, FromValue>> = to.onNewValue.map { (k, v) ->
k.toInnerKey() to v.toInnerValue()
}
@@ -108,40 +118,39 @@ open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
}
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper(
inline fun <FromKey, FromValue, ToKey, ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): WriteKeyValuesRepo<FromKey, FromValue> = MapperWriteKeyValuesRepo(this, mapper)
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = MapperWriteOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteKeyValuesRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteKeyValuesRepo<FromKey, FromValue> = withMapper(
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> WriteOneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): WriteOneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
open class MapperKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: KeyValuesRepo<ToKey, ToValue>,
open class MapperOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
private val to: OneToManyKeyValueRepo<ToKey, ToValue>,
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
) : KeyValuesRepo<FromKey, FromValue>,
) : OneToManyKeyValueRepo<FromKey, FromValue>,
MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper,
ReadKeyValuesRepo<FromKey, FromValue> by MapperReadKeyValuesRepo(to, mapper),
WriteKeyValuesRepo<FromKey, FromValue> by MapperWriteKeyValuesRepo(to, mapper)
ReadOneToManyKeyValueRepo<FromKey, FromValue> by MapperReadOneToManyKeyValueRepo(to, mapper),
WriteOneToManyKeyValueRepo<FromKey, FromValue> by MapperWriteOneToManyKeyValueRepo(to, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <FromKey, FromValue, ToKey, ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper(
inline fun <FromKey, FromValue, ToKey, ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
mapper: MapperRepo<FromKey, FromValue, ToKey, ToValue>
): KeyValuesRepo<FromKey, FromValue> = MapperKeyValuesRepo(this, mapper)
): OneToManyKeyValueRepo<FromKey, FromValue> = MapperOneToManyKeyValueRepo(this, mapper)
@Suppress("NOTHING_TO_INLINE")
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> KeyValuesRepo<ToKey, ToValue>.withMapper(
noinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
noinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
noinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
noinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): KeyValuesRepo<FromKey, FromValue> = withMapper(
inline fun <reified FromKey, reified FromValue, reified ToKey, reified ToValue> OneToManyKeyValueRepo<ToKey, ToValue>.withMapper(
crossinline keyFromToTo: suspend FromKey.() -> ToKey = { this as ToKey },
crossinline valueFromToTo: suspend FromValue.() -> ToValue = { this as ToValue },
crossinline keyToToFrom: suspend ToKey.() -> FromKey = { this as FromKey },
crossinline valueToToFrom: suspend ToValue.() -> FromValue = { this as FromValue },
): OneToManyKeyValueRepo<FromKey, FromValue> = withMapper(
mapper(keyFromToTo, valueFromToTo, keyToToFrom, valueToToFrom)
)

View File

@@ -1,10 +1,11 @@
package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
suspend inline fun <T, ID, REPO : ReadStandardCRUDRepo<T, ID>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithNextPaging {

View File

@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll(
suspend inline fun <Key, Value, REPO : ReadStandardKeyValueRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, Value>> = getAllWithNextPaging {

View File

@@ -2,9 +2,9 @@ package dev.inmo.micro_utils.repos.pagination
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll(
suspend inline fun <Key, Value, REPO : ReadOneToManyKeyValueRepo<Key, Value>> REPO.getAll(
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
): List<Pair<Key, List<Value>>> = getAllWithNextPaging {

View File

@@ -1,10 +1,10 @@
package dev.inmo.micro_utils.repos.versions
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import dev.inmo.micro_utils.repos.set
class KeyValueBasedVersionsRepoProxy<T>(
private val keyValueStore: KeyValueRepo<String, Int>,
private val keyValueStore: StandardKeyValueRepo<String, Int>,
override val database: T
) : StandardVersionsRepoProxy<T> {
override suspend fun getTableVersion(tableName: String): Int? = keyValueStore.get(tableName)

View File

@@ -13,9 +13,9 @@ import java.nio.file.StandardWatchEventKinds.*
private inline val String.isAbsolute
get() = startsWith(File.separator)
class FileReadKeyValueRepo(
class FileReadStandardKeyValueRepo(
private val folder: File
) : ReadKeyValueRepo<String, File> {
) : ReadStandardKeyValueRepo<String, File> {
init {
folder.mkdirs()
}
@@ -31,7 +31,7 @@ class FileReadKeyValueRepo(
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
}
@@ -44,7 +44,7 @@ class FileReadKeyValueRepo(
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
val count = count()
val resultPagination = if (reversed) pagination.reverse(count) else pagination
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndex) ?: return emptyPaginationResult()
if (reversed) {
filesPaths.reverse()
}
@@ -83,19 +83,17 @@ class FileReadKeyValueRepo(
* 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 FileWriteKeyValueRepo(
class FileWriteStandardKeyValueRepo(
private val folder: File,
filesChangedProcessingScope: CoroutineScope? = null
) : WriteKeyValueRepo<String, File> {
) : 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 {
if (!folder.mkdirs() && !folder.exists()) {
error("Unable to create folder ${folder.absolutePath}")
}
folder.mkdirs()
filesChangedProcessingScope ?.let {
it.launch {
try {
@@ -140,17 +138,15 @@ class FileWriteKeyValueRepo(
}
override suspend fun set(toSet: Map<String, File>) {
val scope = CoroutineScope(currentCoroutineContext())
toSet.map { (filename, fileSource) ->
scope.launch {
val file = File(folder, filename)
supervisorScope {
toSet.map { (filename, fileSource) ->
launch {
val file = File(folder, filename)
file.delete()
fileSource.copyTo(file, overwrite = true)
if (!file.exists()) {
error("Can't create file $file with new content")
file.delete()
fileSource.copyTo(file, overwrite = true)
_onNewValue.emit(filename to file)
}
_onNewValue.emit(filename to file)
}
}.joinAll()
}
@@ -179,11 +175,9 @@ class FileWriteKeyValueRepo(
}
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
class FileKeyValueRepo(
class FileStandardKeyValueRepo(
folder: File,
filesChangedProcessingScope: CoroutineScope? = null
) : KeyValueRepo<String, File>,
WriteKeyValueRepo<String, File> by FileWriteKeyValueRepo(folder, filesChangedProcessingScope),
ReadKeyValueRepo<String, File> by FileReadKeyValueRepo(folder) {
}
) : StandardKeyValueRepo<String, File>,
WriteStandardKeyValueRepo<String, File> by FileWriteStandardKeyValueRepo(folder, filesChangedProcessingScope),
ReadStandardKeyValueRepo<String, File> by FileReadStandardKeyValueRepo(folder)

View File

@@ -12,7 +12,7 @@ val <T> T.asId: String
abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
protected val helper: StandardSQLHelper
) : ReadCRUDRepo<ObjectType, IdType> {
) : ReadStandardCRUDRepo<ObjectType, IdType> {
protected abstract val tableName: String
protected abstract val idColumnName: String
protected abstract suspend fun Cursor.toObject(): ObjectType
@@ -64,4 +64,4 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
}
}
}
}
}

View File

@@ -9,9 +9,9 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
helper: StandardSQLHelper,
replyInFlows: Int = 0,
extraBufferCapacityInFlows: Int = 64
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>,
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
CRUDRepo<ObjectType, IdType, InputValueType> {
StandardCRUDRepo<ObjectType, IdType, InputValueType> {
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyInFlows, extraBufferCapacityInFlows)

View File

@@ -3,10 +3,11 @@ package dev.inmo.micro_utils.repos.keyvalue
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.utils.paginate
import dev.inmo.micro_utils.pagination.utils.reverse
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import kotlinx.coroutines.flow.*
private val cache = HashMap<String, KeyValueStore<*>>()
@@ -14,8 +15,7 @@ private val cache = HashMap<String, KeyValueStore<*>>()
fun <T : Any> Context.keyValueStore(
name: String = "default",
cacheValues: Boolean = false
): KeyValueRepo<String, T> {
@Suppress("UNCHECKED_CAST")
): StandardKeyValueRepo<String, T> {
return cache.getOrPut(name) {
KeyValueStore<T>(this, name, cacheValues)
} as KeyValueStore<T>
@@ -25,7 +25,7 @@ class KeyValueStore<T : Any> internal constructor (
c: Context,
preferencesName: String,
useCache: Boolean = false
) : SharedPreferences.OnSharedPreferenceChangeListener, KeyValueRepo<String, T> {
) : SharedPreferences.OnSharedPreferenceChangeListener, StandardKeyValueRepo<String, T> {
private val sharedPreferences = c.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
private val cachedData = if (useCache) {
@@ -62,7 +62,6 @@ class KeyValueStore<T : Any> internal constructor (
}
override suspend fun get(k: String): T? {
@Suppress("UNCHECKED_CAST")
return (cachedData ?. get(k) ?: sharedPreferences.all[k]) as? T
}
@@ -71,11 +70,11 @@ class KeyValueStore<T : Any> internal constructor (
return sharedPreferences.all.values.paginate(
resultPagination
).let {
it.changeResultsUnchecked(
it.results.map {
@Suppress("UNCHECKED_CAST")
it as T
}.let { if (reversed) it.reversed() else it }
PaginationResult(
it.page,
it.pagesNumber,
it.results.map { it as T }.let { if (reversed) it.reversed() else it },
it.size
)
}
}
@@ -85,8 +84,11 @@ class KeyValueStore<T : Any> internal constructor (
return sharedPreferences.all.keys.paginate(
resultPagination
).let {
it.changeResultsUnchecked(
it.results.let { if (reversed) it.reversed() else it }
PaginationResult(
it.page,
it.pagesNumber,
it.results.let { if (reversed) it.reversed() else it },
it.size
)
}
}
@@ -96,8 +98,11 @@ class KeyValueStore<T : Any> internal constructor (
return sharedPreferences.all.mapNotNull { (k, value) -> if (value == v) k else null }.paginate(
resultPagination
).let {
it.changeResultsUnchecked(
it.results.let { if (reversed) it.reversed() else it }
PaginationResult(
it.page,
it.pagesNumber,
it.results.let { if (reversed) it.reversed() else it },
it.size
)
}
}
@@ -150,9 +155,3 @@ class KeyValueStore<T : Any> internal constructor (
}
}
}
inline fun <T : Any> SharedPreferencesKeyValueRepo(
context: Context,
name: String = "default",
cacheValues: Boolean = false
) = context.keyValueStore<T>(name, cacheValues)

View File

@@ -9,6 +9,7 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
@@ -25,7 +26,7 @@ class OneToManyAndroidRepo<Key, Value>(
private val keyFromString: String.() -> Key,
private val valueFromString: String.() -> Value,
private val helper: SQLiteOpenHelper
) : KeyValuesRepo<Key, Value> {
) : OneToManyKeyValueRepo<Key, Value> {
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()

View File

@@ -4,11 +4,13 @@ package dev.inmo.micro_utils.repos.versions
import android.content.Context
import android.database.sqlite.SQLiteOpenHelper
import androidx.core.content.contentValuesOf
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.keyvalue.keyValueStore
import kotlinx.coroutines.runBlocking
/**
* Will create [VersionsRepo] based on [T], but versions will be stored in [KeyValueRepo]
* Will create [VersionsRepo] based on [T], but versions will be stored in [StandardKeyValueRepo]
*
* @receiver Will be used to create [KeyValueBasedVersionsRepoProxy] via [keyValueStore] and pass it to [StandardVersionsRepo]
*
@@ -24,9 +26,9 @@ inline fun <T> Context.versionsKeyValueRepo(
)
)
/**
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [KeyValueRepo]
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
*
* @receiver Will be used to create [KeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
* @receiver Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
*
* @see [keyValueStore]
*/
@@ -35,9 +37,9 @@ inline fun Context.versionsKeyValueRepoForSQL(
) = versionsKeyValueRepo(database)
/**
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [KeyValueRepo]
* Will create [VersionsRepo] based on [SQLiteOpenHelper], but versions will be stored in [StandardKeyValueRepo]
*
* @param context Will be used to create [KeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
* @param context Will be used to create [StandardKeyValueRepo] via [keyValueStore] and pass it to [StandardVersionsRepo]
*
* @see [keyValueStore]
*/

View File

@@ -1,6 +1,6 @@
package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.CRUDRepo
import dev.inmo.micro_utils.repos.StandardCRUDRepo
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 0,
@@ -11,4 +11,4 @@ abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
tableName
),
ExposedCRUDRepo<ObjectType, IdType>,
CRUDRepo<ObjectType, IdType, InputValueType>
StandardCRUDRepo<ObjectType, IdType, InputValueType>

View File

@@ -1,14 +1,14 @@
package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
tableName: String
) :
ReadCRUDRepo<ObjectType, IdType>,
ReadStandardCRUDRepo<ObjectType, IdType>,
ExposedCRUDRepo<ObjectType, IdType>,
Table(tableName)
{

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteCRUDRepo
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
@@ -15,11 +15,17 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
) :
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
ExposedCRUDRepo<ObjectType, IdType>,
WriteCRUDRepo<ObjectType, IdType, InputValueType>
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
{
protected val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
protected val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
protected val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize)
@Deprecated("Renamed", ReplaceWith("_newObjectsFlow"))
protected val newObjectsChannel = _newObjectsFlow
@Deprecated("Renamed", ReplaceWith("_updatedObjectsFlow"))
protected val updateObjectsChannel = _updatedObjectsFlow
@Deprecated("Renamed", ReplaceWith("_deletedObjectsIdsFlow"))
protected val deleteObjectsIdsChannel = _deletedObjectsIdsFlow
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()

View File

@@ -1,7 +1,9 @@
package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import dev.inmo.micro_utils.repos.exposed.initTable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
@@ -11,7 +13,7 @@ open class ExposedKeyValueRepo<Key, Value>(
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
) : StandardKeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
database,
keyColumnAllocator,
valueColumnAllocator,

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.ReadStandardKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
@@ -11,7 +11,7 @@ open class ExposedReadKeyValueRepo<Key, Value>(
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
val keyColumn: Column<Key> = keyColumnAllocator()
val valueColumn: Column<Value> = valueColumnAllocator()
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)

View File

@@ -1,18 +1,18 @@
package dev.inmo.micro_utils.repos.exposed.onetomany
import dev.inmo.micro_utils.repos.KeyValuesRepo
import dev.inmo.micro_utils.repos.OneToManyKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value>
open class ExposedKeyValuesRepo<Key, Value>(
typealias ExposedKeyValuesRepo<Key, Value> = ExposedOneToManyKeyValueRepo<Key, Value>
open class ExposedOneToManyKeyValueRepo<Key, Value>(
database: Database,
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
) : KeyValuesRepo<Key, Value>, ExposedReadKeyValuesRepo<Key, Value>(
) : OneToManyKeyValueRepo<Key, Value>, ExposedReadOneToManyKeyValueRepo<Key, Value>(
database,
keyColumnAllocator,
valueColumnAllocator,

View File

@@ -1,19 +1,20 @@
package dev.inmo.micro_utils.repos.exposed.onetomany
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.*
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedReadKeyValueRepo
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value>
typealias ExposedReadKeyValuesRepo<Key, Value> = ExposedReadOneToManyKeyValueRepo<Key, Value>
open class ExposedReadKeyValuesRepo<Key, Value>(
open class ExposedReadOneToManyKeyValueRepo<Key, Value>(
override val database: Database,
keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
val keyColumn: Column<Key> = keyColumnAllocator()
val valueColumn: Column<Value> = valueColumnAllocator()

View File

@@ -5,7 +5,7 @@ import kotlinx.coroutines.flow.*
class ReadMapCRUDRepo<ObjectType, IdType>(
private val map: Map<IdType, ObjectType> = emptyMap()
) : ReadCRUDRepo<ObjectType, IdType> {
) : ReadStandardCRUDRepo<ObjectType, IdType> {
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> {
return map.keys.drop(pagination.firstIndex).take(pagination.size).mapNotNull {
map[it]
@@ -24,7 +24,7 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf()
) : WriteCRUDRepo<ObjectType, IdType, InputValueType> {
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> {
protected val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
protected val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
@@ -68,30 +68,25 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
abstract class MapCRUDRepo<ObjectType, IdType, InputValueType>(
map: MutableMap<IdType, ObjectType>
) : CRUDRepo<ObjectType, IdType, InputValueType>,
ReadCRUDRepo<ObjectType, IdType> by ReadMapCRUDRepo(map),
) : StandardCRUDRepo<ObjectType, IdType, InputValueType>,
ReadStandardCRUDRepo<ObjectType, IdType> by ReadMapCRUDRepo(map),
WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(map)
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
map: MutableMap<IdType, ObjectType>,
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend (newValue: InputValueType) -> Pair<IdType, ObjectType>
) = object : MapCRUDRepo<ObjectType, IdType, InputValueType>(map) {
override suspend fun updateObject(
newValue: InputValueType,
id: IdType,
old: ObjectType
): ObjectType = map.updateCallback(newValue, id, old)
): ObjectType = updateCallback(newValue, id, old)
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = map.createCallback(newValue)
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = createCallback(newValue)
}
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
) = MapCRUDRepo(mutableMapOf(), updateCallback, createCallback)
fun <ObjectType, IdType, InputValueType> MutableMap<IdType, ObjectType>.asCrudRepo(
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
updateCallback: suspend (newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
createCallback: suspend (newValue: InputValueType) -> Pair<IdType, ObjectType>
) = MapCRUDRepo(this, updateCallback, createCallback)

View File

@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
class ReadMapKeyValueRepo<Key, Value>(
protected val map: Map<Key, Value> = emptyMap()
) : ReadKeyValueRepo<Key, Value> {
) : ReadStandardKeyValueRepo<Key, Value> {
override suspend fun get(k: Key): Value? = map[k]
override suspend fun values(
@@ -58,7 +58,7 @@ class ReadMapKeyValueRepo<Key, Value>(
class WriteMapKeyValueRepo<Key, Value>(
private val map: MutableMap<Key, Value> = mutableMapOf()
) : WriteKeyValueRepo<Key, Value> {
) : WriteStandardKeyValueRepo<Key, Value> {
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
override val onNewValue: Flow<Pair<Key, Value>>
get() = _onNewValue
@@ -78,20 +78,19 @@ class WriteMapKeyValueRepo<Key, Value>(
}
override suspend fun unsetWithValues(toUnset: List<Value>) {
map.mapNotNull { (k, v) ->
k.takeIf { v in toUnset }
}.forEach {
map.remove(it)
_onValueRemoved.emit(it)
map.forEach {
if (it.value in toUnset) {
map.remove(it.key)
_onValueRemoved.emit(it.key)
}
}
}
}
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
class MapKeyValueRepo<Key, Value>(
private val map: MutableMap<Key, Value> = mutableMapOf()
) : KeyValueRepo<Key, Value>,
ReadKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map),
WriteKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map)
) : StandardKeyValueRepo<Key, Value>,
ReadStandardKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map),
WriteStandardKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map)
fun <K, V> MutableMap<K, V>.asKeyValueRepo(): KeyValueRepo<K, V> = MapKeyValueRepo(this)
fun <K, V> MutableMap<K, V>.asKeyValueRepo(): StandardKeyValueRepo<K, V> = MapKeyValueRepo(this)

View File

@@ -5,9 +5,9 @@ import dev.inmo.micro_utils.pagination.utils.paginate
import dev.inmo.micro_utils.pagination.utils.reverse
import kotlinx.coroutines.flow.*
class MapReadKeyValuesRepo<Key, Value>(
class MapReadOneToManyKeyValueRepo<Key, Value>(
private val map: Map<Key, List<Value>> = emptyMap()
) : ReadKeyValuesRepo<Key, Value> {
) : ReadOneToManyKeyValueRepo<Key, Value> {
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
val list = map[k] ?: return emptyPaginationResult()
@@ -53,9 +53,9 @@ class MapReadKeyValuesRepo<Key, Value>(
override suspend fun count(): Long = map.size.toLong()
}
class MapWriteKeyValuesRepo<Key, Value>(
class MapWriteOneToManyKeyValueRepo<Key, Value>(
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
) : WriteKeyValuesRepo<Key, Value> {
) : WriteOneToManyKeyValueRepo<Key, Value> {
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
private val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
@@ -80,10 +80,6 @@ class MapWriteKeyValuesRepo<Key, Value>(
_onValueRemoved.emit(k to v)
}
}
if (map[k] ?.isEmpty() == true) {
map.remove(k)
_onDataCleared.emit(k)
}
}
}
@@ -98,13 +94,12 @@ class MapWriteKeyValuesRepo<Key, Value>(
}
}
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
class MapKeyValuesRepo<Key, Value>(
class MapOneToManyKeyValueRepo<Key, Value>(
private val map: MutableMap<Key, MutableList<Value>> = mutableMapOf()
) : KeyValuesRepo<Key, Value>,
ReadKeyValuesRepo<Key, Value> by MapReadKeyValuesRepo(map),
WriteKeyValuesRepo<Key, Value> by MapWriteKeyValuesRepo(map)
) : OneToManyKeyValueRepo<Key, Value>,
ReadOneToManyKeyValueRepo<Key, Value> by MapReadOneToManyKeyValueRepo(map),
WriteOneToManyKeyValueRepo<Key, Value> by MapWriteOneToManyKeyValueRepo(map)
fun <K, V> MutableMap<K, List<V>>.asKeyValuesRepo(): KeyValuesRepo<K, V> = MapKeyValuesRepo(
fun <K, V> MutableMap<K, List<V>>.asOneToManyKeyValueRepo(): OneToManyKeyValueRepo<K, V> = MapOneToManyKeyValueRepo(
map { (k, v) -> k to v.toMutableList() }.toMap().toMutableMap()
)

View File

@@ -1,106 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.*
import io.ktor.client.HttpClient
import io.ktor.http.ContentType
import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo
import kotlinx.serialization.*
class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
readDelegate: ReadCRUDRepo<ObjectType, IdType>,
writeDelegate: WriteCRUDRepo<ObjectType, IdType, InputValue>
) : CRUDRepo<ObjectType, IdType, InputValue> by DelegateBasedCRUDRepo(
readDelegate,
writeDelegate
) {
companion object {
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType,
noinline idSerializer: suspend (IdType) -> String
) = KtorCRUDRepoClient(
KtorReadCRUDRepoClient(
baseUrl,
httpClient,
typeInfo<ObjectType>(),
typeInfo<PaginationResult<ObjectType>>(),
contentType,
idSerializer
),
KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
baseUrl,
httpClient,
contentType
)
)
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
contentType: ContentType,
noinline idSerializer: suspend (IdType) -> String
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
buildStandardUrl(baseUrl, subpart),
httpClient,
contentType,
idSerializer
)
}
}
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat,
contentType: ContentType,
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(baseUrl, httpClient, contentType) {
serialFormat.encodeToString(idsSerializer, it)
}
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat,
contentType: ContentType,
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(baseUrl, httpClient, contentType) {
serialFormat.encodeHex(idsSerializer, it)
}
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
contentType: ContentType,
noinline idSerializer: suspend (IdType) -> String
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(
buildStandardUrl(baseUrl, subpart),
httpClient,
contentType,
idSerializer
)
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat,
contentType: ContentType,
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(buildStandardUrl(baseUrl, subpart), httpClient, idsSerializer, serialFormat, contentType)
inline fun <reified ObjectType, reified IdType, reified InputValue> KtorCRUDRepoClient(
baseUrl: String,
subpart: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat,
contentType: ContentType,
) = KtorCRUDRepoClient<ObjectType, IdType, InputValue>(buildStandardUrl(baseUrl, subpart), httpClient, idsSerializer, serialFormat, contentType)

View File

@@ -1,97 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.countRouting
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import dev.inmo.micro_utils.repos.ktor.common.idParameterName
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.http.*
import io.ktor.util.reflect.TypeInfo
import io.ktor.util.reflect.typeInfo
import kotlinx.serialization.*
class KtorReadCRUDRepoClient<ObjectType, IdType> (
private val baseUrl: String,
private val httpClient: HttpClient,
private val objectType: TypeInfo,
private val paginationObjectType: TypeInfo,
private val contentType: ContentType,
private val idSerializer: suspend (IdType) -> String
) : ReadCRUDRepo<ObjectType, IdType> {
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = httpClient.get(
buildStandardUrl(baseUrl, getByPaginationRouting, pagination.asUrlQueryParts)
) {
contentType(contentType)
}.body(paginationObjectType)
override suspend fun getById(id: IdType): ObjectType? = httpClient.get(
buildStandardUrl(
baseUrl,
getByIdRouting,
mapOf(
idParameterName to idSerializer(id)
)
)
) {
contentType(contentType)
}.takeIf { it.status != HttpStatusCode.NoContent } ?.body<ObjectType>(objectType)
override suspend fun contains(id: IdType): Boolean = httpClient.get(
buildStandardUrl(
baseUrl,
containsRouting,
mapOf(
idParameterName to idSerializer(id)
)
)
) {
contentType(contentType)
}.body()
override suspend fun count(): Long = httpClient.get(
buildStandardUrl(
baseUrl,
countRouting
)
) {
contentType(contentType)
}.body()
}
inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType,
noinline idSerializer: suspend (IdType) -> String
) = KtorReadCRUDRepoClient<ObjectType, IdType>(
baseUrl,
httpClient,
typeInfo<ObjectType>(),
typeInfo<PaginationResult<ObjectType>>(),
contentType,
idSerializer
)
inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: StringFormat,
contentType: ContentType,
) = KtorReadCRUDRepoClient<ObjectType, IdType>(baseUrl, httpClient, contentType) {
serialFormat.encodeToString(idsSerializer, it)
}
inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient(
baseUrl: String,
httpClient: HttpClient,
idsSerializer: KSerializer<IdType>,
serialFormat: BinaryFormat,
contentType: ContentType,
) = KtorReadCRUDRepoClient<ObjectType, IdType>(baseUrl, httpClient, contentType) {
serialFormat.encodeHex(idsSerializer, it)
}

View File

@@ -0,0 +1,66 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.client.*
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadStandardCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.client.HttpClient
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
class KtorReadStandardCrudRepo<ObjectType, IdType> (
private val baseUrl: String,
private val unifiedRequester: UnifiedRequester,
private val objectsSerializer: KSerializer<ObjectType>,
private val objectsSerializerNullable: KSerializer<ObjectType?>,
private val idsSerializer: KSerializer<IdType>
) : ReadStandardCRUDRepo<ObjectType, IdType> {
private val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
constructor(
baseUrl: String,
client: HttpClient,
objectsSerializer: KSerializer<ObjectType>,
objectsSerializerNullable: KSerializer<ObjectType?>,
idsSerializer: KSerializer<IdType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) : this (
baseUrl, UnifiedRequester(client, serialFormat), objectsSerializer, objectsSerializerNullable, idsSerializer
)
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = unifiedRequester.uniget(
buildStandardUrl(baseUrl, getByPaginationRouting, pagination.asUrlQueryParts),
paginationResultSerializer
)
override suspend fun getById(id: IdType): ObjectType? = unifiedRequester.uniget(
buildStandardUrl(
baseUrl,
getByIdRouting,
mapOf(
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
)
),
objectsSerializerNullable
)
override suspend fun contains(id: IdType): Boolean = unifiedRequester.uniget(
buildStandardUrl(
baseUrl,
containsRouting,
mapOf(
"id" to unifiedRequester.encodeUrlQueryValue(idsSerializer, id)
)
),
Boolean.serializer()
)
override suspend fun count(): Long = unifiedRequester.uniget(
buildStandardUrl(
baseUrl,
countRouting
),
Long.serializer()
)
}

View File

@@ -0,0 +1,46 @@
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
class KtorStandardCrudRepo<ObjectType, IdType, InputValue> (
baseUrl: String,
baseSubpart: String,
unifiedRequester: UnifiedRequester,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
inputsSerializer: KSerializer<InputValue>,
idsSerializer: KSerializer<IdType>
) : StandardCRUDRepo<ObjectType, IdType, InputValue>,
ReadStandardCRUDRepo<ObjectType, IdType> by KtorReadStandardCrudRepo(
"$baseUrl/$baseSubpart",
unifiedRequester,
objectsSerializer,
objectsNullableSerializer,
idsSerializer
),
WriteStandardCRUDRepo<ObjectType, IdType, InputValue> by KtorWriteStandardCrudRepo(
"$baseUrl/$baseSubpart",
unifiedRequester,
objectsSerializer,
objectsNullableSerializer,
inputsSerializer,
idsSerializer
) {
constructor(
baseUrl: String,
baseSubpart: String,
client: HttpClient,
objectsSerializer: KSerializer<ObjectType>,
objectsNullableSerializer: KSerializer<ObjectType?>,
inputsSerializer: KSerializer<InputValue>,
idsSerializer: KSerializer<IdType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) : this(
baseUrl, baseSubpart, UnifiedRequester(client, serialFormat), objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer
)
}

View File

@@ -1,85 +0,0 @@
package dev.inmo.micro_utils.repos.ktor.client.crud
import dev.inmo.micro_utils.ktor.client.*
import dev.inmo.micro_utils.ktor.common.*
import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.crud.*
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.*
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.coroutines.flow.Flow
class KtorWriteCrudRepoClient<ObjectType, IdType, InputValue> (
private val baseUrl: String,
private val httpClient: HttpClient,
override val newObjectsFlow: Flow<ObjectType>,
override val updatedObjectsFlow: Flow<ObjectType>,
override val deletedObjectsIdsFlow: Flow<IdType>,
private val createSetup: suspend HttpRequestBuilder.(List<InputValue>) -> Unit,
private val updateSetup: suspend HttpRequestBuilder.(List<UpdatedValuePair<IdType, InputValue>>) -> Unit,
private val deleteByIdSetup: suspend HttpRequestBuilder.(List<IdType>) -> Unit,
private val createBodyGetter: suspend HttpResponse.() -> List<ObjectType>,
private val updateBodyGetter: suspend HttpResponse.() -> List<ObjectType>
) : WriteCRUDRepo<ObjectType, IdType, InputValue> {
override suspend fun create(values: List<InputValue>): List<ObjectType> = httpClient.post(
buildStandardUrl(baseUrl, createRouting)
) {
createSetup(values)
}.createBodyGetter()
override suspend fun update(
values: List<UpdatedValuePair<IdType, InputValue>>
): List<ObjectType> = httpClient.post(
buildStandardUrl(baseUrl, updateRouting)
) {
updateSetup(values)
}.updateBodyGetter()
override suspend fun update(id: IdType, value: InputValue): ObjectType? = update(listOf(id to value)).firstOrNull()
override suspend fun deleteById(ids: List<IdType>) {
httpClient.post(
buildStandardUrl(baseUrl, deleteByIdRouting)
) {
deleteByIdSetup(ids)
}.throwOnUnsuccess { "Unable to delete $ids" }
}
companion object {
inline operator fun <reified ObjectType, reified IdType, reified InputValue> invoke(
baseUrl: String,
httpClient: HttpClient,
contentType: ContentType
) = KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
baseUrl,
httpClient,
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, newObjectsFlowRouting),
),
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, updatedObjectsFlowRouting),
),
httpClient.createStandardWebsocketFlow(
buildStandardUrl(baseUrl, deletedObjectsIdsFlowRouting),
),
{
contentType(contentType)
setBody(it)
},
{
contentType(contentType)
setBody(it)
},
{
contentType(contentType)
setBody(it)
},
{ body() },
{ body() }
)
}
}

Some files were not shown because too many files have changed in this diff Show More