mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
64 Commits
Author | SHA1 | Date | |
---|---|---|---|
d7ee45ca64 | |||
d7c31b1b22 | |||
7d6794a358 | |||
473eb87346 | |||
8b18b07790 | |||
ab3c80a5ec | |||
075b93ecd6 | |||
f6d0f72e49 | |||
fcda3af862 | |||
d5c7a589b1 | |||
86e099ed25 | |||
ebd7befe73 | |||
b8c7e581a1 | |||
8281259179 | |||
d3e06b07df | |||
4967018418 | |||
537a3c38fa | |||
0124957833 | |||
f0420e2d61 | |||
7090566041 | |||
f0d5035cd0 | |||
0fb9b8dc30 | |||
eef6e81134 | |||
1593159a3f | |||
3da9eb9dbe | |||
f17613f3fb | |||
14337ccb46 | |||
1a3913b09c | |||
039aed2747 | |||
173991e3cb | |||
8b3f8cab01 | |||
2a20d24589 | |||
53c2d552ec | |||
af11c1a83d | |||
a65cf1481c | |||
0318716236 | |||
88eb4b3342 | |||
4810d1ef6a | |||
f2a9514d89 | |||
925ba6ac24 | |||
|
ef407268a2 | ||
cd6c4bbe38 | |||
c058e18408 | |||
6d3ca565ca | |||
236a7b4fd2 | |||
e1f387dbf7 | |||
3d113dd31e | |||
e0e57f0336 | |||
e775b58d41 | |||
5b070a8478 | |||
8a61193500 | |||
fad522b8fe | |||
0acac205af | |||
069d4c61b7 | |||
7c113f5700 | |||
dfdaf4225b | |||
bd39ab2467 | |||
6ce1eb3f2d | |||
ce7d4fe9a2 | |||
a65bb2f419 | |||
cbc868448b | |||
9c336a0b56 | |||
0f0d09399e | |||
e13a1162a9 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 17
|
||||||
- name: Rewrite version
|
- name: Rewrite version
|
||||||
run: |
|
run: |
|
||||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
|
2
.github/workflows/dokka_push.yml
vendored
2
.github/workflows/dokka_push.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 17
|
||||||
- name: Build
|
- name: Build
|
||||||
run: ./gradlew build && ./gradlew dokkaHtml
|
run: ./gradlew build && ./gradlew dokkaHtml
|
||||||
- name: Publish KDocs
|
- name: Publish KDocs
|
||||||
|
106
CHANGELOG.md
106
CHANGELOG.md
@@ -1,7 +1,113 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.20.12
|
||||||
|
|
||||||
|
**It is experimental migration onto new gradle version. Be careful in use of this version**
|
||||||
|
|
||||||
|
**This update have JDK 17 in `compatibility` and `target` versions**
|
||||||
|
|
||||||
|
## 0.20.11
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.9.20-RC2` -> `1.9.20`
|
||||||
|
* `Exposed`: `0.44.0` -> `0.44.1`
|
||||||
|
* `Compose`: `1.5.10-rc02` -> `1.5.10`
|
||||||
|
* `Coroutines`:
|
||||||
|
* `SmartRWLocker` now will wait first unlock of write mutex for acquiring read
|
||||||
|
|
||||||
|
## 0.20.10
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.9.20-RC` -> `1.9.20-RC1`
|
||||||
|
* `KSLog`: `1.2.1` -> `1.2.2`
|
||||||
|
* `Compose`: `1.5.10-rc01` -> `1.5.10-rc02`
|
||||||
|
* `RecyclerView`: `1.3.1` -> `1.3.2`
|
||||||
|
|
||||||
|
## 0.20.9
|
||||||
|
|
||||||
|
* Most of common modules now supports `linuxArm64` target
|
||||||
|
|
||||||
|
## 0.20.8
|
||||||
|
|
||||||
|
**THIS VERSION CONTAINS UPDATES OF DEPENDENCIES UP TO RC VERSIONS. USE WITH CAUTION**
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.9.20-Beta2` -> `1.9.20-RC`
|
||||||
|
* `Compose`: `1.5.10-beta02` -> `1.5.10-rc01`
|
||||||
|
|
||||||
|
## 0.20.7
|
||||||
|
|
||||||
|
**THIS VERSION CONTAINS UPDATES OF DEPENDENCIES UP TO BETA VERSIONS. USE WITH CAUTION**
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.9.10` -> `1.9.20-Beta2`
|
||||||
|
* `Compose`: `1.5.1` -> `1.5.10-beta02`
|
||||||
|
* `Exposed`: `0.43.0` -> `0.44.0`
|
||||||
|
* `Ktor`: `2.3.4` -> `2.3.5`
|
||||||
|
* `Koin`: `3.4.3` -> `3.5.0`
|
||||||
|
* `Okio`: `3.5.0` -> `3.6.0`
|
||||||
|
* `Android Core`: `1.10.1` -> `1.12.0`
|
||||||
|
* `Android Compose Material`: `1.1.1` -> `1.1.2`
|
||||||
|
|
||||||
|
## 0.20.6
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Exposed`
|
||||||
|
* Fixes in exposed key-values repos
|
||||||
|
|
||||||
|
## 0.20.5
|
||||||
|
|
||||||
|
* `Coroutines`:
|
||||||
|
* Fixes in `SmartRWLocker`
|
||||||
|
|
||||||
|
## 0.20.4
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.9.0` -> `1.9.10`
|
||||||
|
* `KSLog`: `1.2.0` -> `1.2.1`
|
||||||
|
* `Compose`: `1.5.0` -> `1.5.1`
|
||||||
|
* `UUID`: `0.8.0` -> `0.8.1`
|
||||||
|
|
||||||
|
## 0.20.3
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Compose`: `1.4.3` -> `1.5.0`
|
||||||
|
* `Exposed`: `0.42.1` -> `0.43.0`
|
||||||
|
* `Ktor`: `2.3.3` -> `2.3.4`
|
||||||
|
* `Repos`:
|
||||||
|
* `Cache`:
|
||||||
|
* Fixes in locks of caches
|
||||||
|
|
||||||
|
## 0.20.2
|
||||||
|
|
||||||
|
* All main repos uses `SmartRWLocker`
|
||||||
|
* `Versions`:
|
||||||
|
* `Serialization`: `1.5.1` -> `1.6.0`
|
||||||
|
* `Exposed`: `0.42.0` -> `0.42.1`
|
||||||
|
* `Korlibs`: `4.0.9` -> `4.0.10`
|
||||||
|
* `Androis SDK`: `33` -> `34`
|
||||||
|
|
||||||
|
## 0.20.1
|
||||||
|
|
||||||
|
* `SmallTextField`:
|
||||||
|
* Module is initialized
|
||||||
|
* `Pickers`:
|
||||||
|
* Module is initialized
|
||||||
|
* `Coroutines`:
|
||||||
|
* Add `SmartSemaphore`
|
||||||
|
* Add `SmartRWLocker`
|
||||||
|
|
||||||
## 0.20.0
|
## 0.20.0
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.8.22` -> `1.9.0`
|
||||||
|
* `KSLog`: `1.1.1` -> `1.2.0`
|
||||||
|
* `Exposed`: `0.41.1` -> `0.42.0`
|
||||||
|
* `UUID`: `0.7.1` -> `0.8.0`
|
||||||
|
* `Korlibs`: `4.0.3` -> `4.0.9`
|
||||||
|
* `Ktor`: `2.3.2` -> `2.3.3`
|
||||||
|
* `Okio`: `3.4.0` -> `3.5.0`
|
||||||
|
|
||||||
## 0.19.9
|
## 0.19.9
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.android.alerts.common"/>
|
<manifest/>
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.android.alerts.recyclerview"/>
|
<manifest/>
|
||||||
|
18
android/pickers/build.gradle
Normal file
18
android/pickers/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
alias(libs.plugins.jb.compose)
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationAndComposePresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
androidMain {
|
||||||
|
dependencies {
|
||||||
|
api project(":micro_utils.android.smalltextfield")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
android/pickers/src/androidMain/AndroidManifest.xml
Normal file
1
android/pickers/src/androidMain/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
27
android/pickers/src/androidMain/kotlin/Fling.kt
Normal file
27
android/pickers/src/androidMain/kotlin/Fling.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package dev.inmo.micro_utils.android.pickers
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
|
|
||||||
|
internal suspend fun Animatable<Float, AnimationVector1D>.fling(
|
||||||
|
initialVelocity: Float,
|
||||||
|
animationSpec: DecayAnimationSpec<Float>,
|
||||||
|
adjustTarget: ((Float) -> Float)?,
|
||||||
|
block: (Animatable<Float, AnimationVector1D>.() -> Unit)? = null,
|
||||||
|
): AnimationResult<Float, AnimationVector1D> {
|
||||||
|
val targetValue = animationSpec.calculateTargetValue(value, initialVelocity)
|
||||||
|
val adjustedTarget = adjustTarget?.invoke(targetValue)
|
||||||
|
|
||||||
|
return if (adjustedTarget != null) {
|
||||||
|
animateTo(
|
||||||
|
targetValue = adjustedTarget,
|
||||||
|
initialVelocity = initialVelocity,
|
||||||
|
block = block
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
animateDecay(
|
||||||
|
initialVelocity = initialVelocity,
|
||||||
|
animationSpec = animationSpec,
|
||||||
|
block = block,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
222
android/pickers/src/androidMain/kotlin/NumberPicker.kt
Normal file
222
android/pickers/src/androidMain/kotlin/NumberPicker.kt
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
package dev.inmo.micro_utils.android.pickers
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.exponentialDecay
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.*
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.ContentAlpha
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.ProvideTextStyle
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.PointerInputScope
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.ExperimentalTextApi
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.center
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.inmo.micro_utils.android.smalltextfield.SmallTextField
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
private inline fun PointerInputScope.checkContains(offset: Offset): Boolean {
|
||||||
|
return ((size.center.x - offset.x).absoluteValue < size.width / 2) && ((size.center.y - offset.y).absoluteValue < size.height / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// src: https://gist.github.com/vganin/a9a84653a9f48a2d669910fbd48e32d5
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
|
||||||
|
@Composable
|
||||||
|
fun NumberPicker(
|
||||||
|
number: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
range: IntRange? = null,
|
||||||
|
textStyle: TextStyle = LocalTextStyle.current,
|
||||||
|
arrowsColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
allowUseManualInput: Boolean = true,
|
||||||
|
onStateChanged: (Int) -> Unit = {},
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val numbersColumnHeight = 36.dp
|
||||||
|
val halvedNumbersColumnHeight = numbersColumnHeight / 2
|
||||||
|
val halvedNumbersColumnHeightPx = with(LocalDensity.current) { halvedNumbersColumnHeight.toPx() }
|
||||||
|
|
||||||
|
fun animatedStateValue(offset: Float): Int = number - (offset / halvedNumbersColumnHeightPx).toInt()
|
||||||
|
|
||||||
|
val animatedOffset = remember { Animatable(0f) }.apply {
|
||||||
|
if (range != null) {
|
||||||
|
val offsetRange = remember(number, range) {
|
||||||
|
val value = number
|
||||||
|
val first = -(range.last - value) * halvedNumbersColumnHeightPx
|
||||||
|
val last = -(range.first - value) * halvedNumbersColumnHeightPx
|
||||||
|
first..last
|
||||||
|
}
|
||||||
|
updateBounds(offsetRange.start, offsetRange.endInclusive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
|
||||||
|
val animatedStateValue = animatedStateValue(animatedOffset.value)
|
||||||
|
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
|
||||||
|
|
||||||
|
val inputFieldShown = if (allowUseManualInput) {
|
||||||
|
remember { mutableStateOf(false) }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.wrapContentSize()
|
||||||
|
.draggable(
|
||||||
|
orientation = Orientation.Vertical,
|
||||||
|
state = rememberDraggableState { deltaY ->
|
||||||
|
if (inputFieldShown ?.value != true) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
animatedOffset.snapTo(animatedOffset.value + deltaY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragStopped = { velocity ->
|
||||||
|
if (inputFieldShown ?.value != true) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
val endValue = animatedOffset.fling(
|
||||||
|
initialVelocity = velocity,
|
||||||
|
animationSpec = exponentialDecay(frictionMultiplier = 20f),
|
||||||
|
adjustTarget = { target ->
|
||||||
|
val coercedTarget = target % halvedNumbersColumnHeightPx
|
||||||
|
val coercedAnchors =
|
||||||
|
listOf(-halvedNumbersColumnHeightPx, 0f, halvedNumbersColumnHeightPx)
|
||||||
|
val coercedPoint = coercedAnchors.minByOrNull { abs(it - coercedTarget) }!!
|
||||||
|
val base =
|
||||||
|
halvedNumbersColumnHeightPx * (target / halvedNumbersColumnHeightPx).toInt()
|
||||||
|
coercedPoint + base
|
||||||
|
}
|
||||||
|
).endState.value
|
||||||
|
|
||||||
|
onStateChanged(animatedStateValue(endValue))
|
||||||
|
animatedOffset.snapTo(0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
val spacing = 4.dp
|
||||||
|
|
||||||
|
val upEnabled = range == null || range.first < number
|
||||||
|
IconButton(
|
||||||
|
{
|
||||||
|
onStateChanged(number - 1)
|
||||||
|
inputFieldShown ?.value = false
|
||||||
|
},
|
||||||
|
enabled = upEnabled
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.KeyboardArrowUp, "", tint = if (upEnabled) arrowsColor else disabledArrowsColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(spacing))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.offset { IntOffset(x = 0, y = coercedAnimatedOffset.roundToInt()) },
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
val baseLabelModifier = Modifier.align(Alignment.Center)
|
||||||
|
ProvideTextStyle(textStyle) {
|
||||||
|
Text(
|
||||||
|
text = (animatedStateValue - 1).toString(),
|
||||||
|
modifier = baseLabelModifier
|
||||||
|
.offset(y = -halvedNumbersColumnHeight)
|
||||||
|
.alpha(coercedAnimatedOffset / halvedNumbersColumnHeightPx)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (inputFieldShown ?.value == true) {
|
||||||
|
val currentValue = remember { mutableStateOf(number.toString()) }
|
||||||
|
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
SmallTextField(
|
||||||
|
currentValue.value,
|
||||||
|
{
|
||||||
|
val asDigit = it.toIntOrNull()
|
||||||
|
when {
|
||||||
|
(asDigit == null && it.isEmpty()) -> currentValue.value = (range ?.first ?: 0).toString()
|
||||||
|
(asDigit != null && (range == null || asDigit in range)) -> currentValue.value = it
|
||||||
|
else -> { /* do nothing */ }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
baseLabelModifier.focusRequester(focusRequester).width(IntrinsicSize.Min).pointerInput(number) {
|
||||||
|
detectTapGestures {
|
||||||
|
if (!checkContains(it)) {
|
||||||
|
currentValue.value.toIntOrNull() ?.let(onStateChanged)
|
||||||
|
inputFieldShown.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Number
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions {
|
||||||
|
currentValue.value.toIntOrNull() ?.let(onStateChanged)
|
||||||
|
inputFieldShown.value = false
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
textStyle = textStyle
|
||||||
|
)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = animatedStateValue.toString(),
|
||||||
|
modifier = baseLabelModifier
|
||||||
|
.alpha(1 - abs(coercedAnimatedOffset) / halvedNumbersColumnHeightPx)
|
||||||
|
.clickable {
|
||||||
|
if (inputFieldShown ?.value == false) {
|
||||||
|
inputFieldShown.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = (animatedStateValue + 1).toString(),
|
||||||
|
modifier = baseLabelModifier
|
||||||
|
.offset(y = halvedNumbersColumnHeight)
|
||||||
|
.alpha(-coercedAnimatedOffset / halvedNumbersColumnHeightPx)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(spacing))
|
||||||
|
|
||||||
|
val downEnabled = range == null || range.last > number
|
||||||
|
IconButton(
|
||||||
|
{
|
||||||
|
onStateChanged(number + 1)
|
||||||
|
inputFieldShown ?.value = false
|
||||||
|
},
|
||||||
|
enabled = downEnabled
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.KeyboardArrowDown, "", tint = if (downEnabled) arrowsColor else disabledArrowsColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
156
android/pickers/src/androidMain/kotlin/SetPicker.kt
Normal file
156
android/pickers/src/androidMain/kotlin/SetPicker.kt
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package dev.inmo.micro_utils.android.pickers
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.exponentialDecay
|
||||||
|
import androidx.compose.foundation.gestures.*
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.material.ContentAlpha
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.ExperimentalTextApi
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.*
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTextApi::class, ExperimentalComposeUiApi::class)
|
||||||
|
@Composable
|
||||||
|
fun <T> SetPicker(
|
||||||
|
current: T,
|
||||||
|
dataList: List<T>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
textStyle: TextStyle = LocalTextStyle.current,
|
||||||
|
arrowsColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
dataToString: @Composable (T) -> String = { it.toString() },
|
||||||
|
onStateChanged: (T) -> Unit = {},
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val numbersColumnHeight = 8.dp + with(LocalDensity.current) {
|
||||||
|
textStyle.lineHeight.toDp()
|
||||||
|
}
|
||||||
|
val numbersColumnHeightPx = with(LocalDensity.current) { numbersColumnHeight.toPx() }
|
||||||
|
val halvedNumbersColumnHeight = numbersColumnHeight / 2
|
||||||
|
val halvedNumbersColumnHeightPx = with(LocalDensity.current) { halvedNumbersColumnHeight.toPx() }
|
||||||
|
|
||||||
|
val index = dataList.indexOfFirst { it === current }.takeIf { it > -1 } ?: dataList.indexOf(current)
|
||||||
|
val lastIndex = dataList.size - 1
|
||||||
|
|
||||||
|
fun animatedStateValue(offset: Float): Int = index - (offset / halvedNumbersColumnHeightPx).toInt()
|
||||||
|
|
||||||
|
val animatedOffset = remember { Animatable(0f) }.apply {
|
||||||
|
val offsetRange = remember(index, lastIndex) {
|
||||||
|
val value = index
|
||||||
|
val first = -(lastIndex - value) * halvedNumbersColumnHeightPx
|
||||||
|
val last = value * halvedNumbersColumnHeightPx
|
||||||
|
first..last
|
||||||
|
}
|
||||||
|
updateBounds(offsetRange.start, offsetRange.endInclusive)
|
||||||
|
}
|
||||||
|
val indexAnimatedOffset = if (animatedOffset.value > 0) {
|
||||||
|
(index - floor(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
|
||||||
|
} else {
|
||||||
|
(index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
|
||||||
|
}
|
||||||
|
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
|
||||||
|
val boxOffset = (indexAnimatedOffset * halvedNumbersColumnHeightPx) - coercedAnimatedOffset
|
||||||
|
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.wrapContentSize()
|
||||||
|
.draggable(
|
||||||
|
orientation = Orientation.Vertical,
|
||||||
|
state = rememberDraggableState { deltaY ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
animatedOffset.snapTo(animatedOffset.value + deltaY)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragStopped = { velocity ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
val endValue = animatedOffset.fling(
|
||||||
|
initialVelocity = velocity,
|
||||||
|
animationSpec = exponentialDecay(frictionMultiplier = 20f),
|
||||||
|
adjustTarget = { target ->
|
||||||
|
val coercedTarget = target % halvedNumbersColumnHeightPx
|
||||||
|
val coercedAnchors =
|
||||||
|
listOf(-halvedNumbersColumnHeightPx, 0f, halvedNumbersColumnHeightPx)
|
||||||
|
val coercedPoint = coercedAnchors.minByOrNull { abs(it - coercedTarget) }!!
|
||||||
|
val base =
|
||||||
|
halvedNumbersColumnHeightPx * (target / halvedNumbersColumnHeightPx).toInt()
|
||||||
|
coercedPoint + base
|
||||||
|
}
|
||||||
|
).endState.value
|
||||||
|
|
||||||
|
onStateChanged(dataList.elementAt(animatedStateValue(endValue)))
|
||||||
|
animatedOffset.snapTo(0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
val spacing = 4.dp
|
||||||
|
|
||||||
|
val upEnabled = index > 0
|
||||||
|
IconButton(
|
||||||
|
{
|
||||||
|
onStateChanged(dataList.elementAt(index - 1))
|
||||||
|
},
|
||||||
|
enabled = upEnabled
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.KeyboardArrowUp, "", tint = if (upEnabled) arrowsColor else disabledArrowsColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(spacing))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier,
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
ProvideTextStyle(textStyle) {
|
||||||
|
dataList.forEachIndexed { i, t ->
|
||||||
|
val alpha = when {
|
||||||
|
i == indexAnimatedOffset - 1 -> coercedAnimatedOffset / halvedNumbersColumnHeightPx
|
||||||
|
i == indexAnimatedOffset -> 1 - (abs(coercedAnimatedOffset) / halvedNumbersColumnHeightPx)
|
||||||
|
i == indexAnimatedOffset + 1 -> -coercedAnimatedOffset / halvedNumbersColumnHeightPx
|
||||||
|
else -> return@forEachIndexed
|
||||||
|
}
|
||||||
|
val offset = when {
|
||||||
|
i == indexAnimatedOffset - 1 && coercedAnimatedOffset > 0 -> coercedAnimatedOffset - halvedNumbersColumnHeightPx
|
||||||
|
i == indexAnimatedOffset -> coercedAnimatedOffset
|
||||||
|
i == indexAnimatedOffset + 1 && coercedAnimatedOffset < 0 -> coercedAnimatedOffset + halvedNumbersColumnHeightPx
|
||||||
|
else -> return@forEachIndexed
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = dataToString(t),
|
||||||
|
modifier = Modifier
|
||||||
|
.alpha(alpha)
|
||||||
|
.offset(y = with(LocalDensity.current) { offset.toDp() })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(spacing))
|
||||||
|
|
||||||
|
val downEnabled = index < lastIndex
|
||||||
|
IconButton(
|
||||||
|
{
|
||||||
|
onStateChanged(dataList.elementAt(index + 1))
|
||||||
|
},
|
||||||
|
enabled = downEnabled
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.KeyboardArrowDown, "", tint = if (downEnabled) arrowsColor else disabledArrowsColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.android.recyclerview"/>
|
<manifest/>
|
18
android/smalltextfield/build.gradle
Normal file
18
android/smalltextfield/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
alias(libs.plugins.jb.compose)
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationAndComposePresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
androidMain {
|
||||||
|
dependencies {
|
||||||
|
api libs.android.compose.material3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
@@ -0,0 +1,66 @@
|
|||||||
|
package dev.inmo.micro_utils.android.smalltextfield
|
||||||
|
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.takeOrElse
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SmallTextField(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
readOnly: Boolean = false,
|
||||||
|
textStyle: TextStyle = LocalTextStyle.current,
|
||||||
|
textColor: Color = textStyle.color.takeOrElse {
|
||||||
|
LocalContentColor.current
|
||||||
|
},
|
||||||
|
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||||
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||||
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||||
|
singleLine: Boolean = false,
|
||||||
|
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
|
||||||
|
minLines: Int = 1,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
) {
|
||||||
|
BasicTextField(
|
||||||
|
value = value,
|
||||||
|
modifier = modifier,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
enabled = enabled,
|
||||||
|
readOnly = readOnly,
|
||||||
|
textStyle = textStyle.copy(
|
||||||
|
color = textColor
|
||||||
|
),
|
||||||
|
visualTransformation = visualTransformation,
|
||||||
|
keyboardOptions = keyboardOptions,
|
||||||
|
keyboardActions = keyboardActions,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
singleLine = singleLine,
|
||||||
|
maxLines = maxLines,
|
||||||
|
minLines = minLines,
|
||||||
|
cursorBrush = SolidColor(
|
||||||
|
textStyle.color.takeOrElse {
|
||||||
|
LocalContentColor.current
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -31,5 +31,10 @@ kotlin {
|
|||||||
api libs.okio
|
api libs.okio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
linuxArm64Main {
|
||||||
|
dependencies {
|
||||||
|
api libs.okio
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.common.compose"/>
|
<manifest/>
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.common"/>
|
<manifest/>
|
36
common/src/linuxArm64Main/kotlin/ActualMPPFile.kt
Normal file
36
common/src/linuxArm64Main/kotlin/ActualMPPFile.kt
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import okio.FileSystem
|
||||||
|
import okio.Path
|
||||||
|
import okio.use
|
||||||
|
|
||||||
|
actual typealias MPPFile = Path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
|
actual val MPPFile.filename: FileName
|
||||||
|
get() = FileName(toString())
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
|
actual val MPPFile.filesize: Long
|
||||||
|
get() = FileSystem.SYSTEM.openReadOnly(this).use {
|
||||||
|
it.size()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
|
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||||
|
get() = {
|
||||||
|
FileSystem.SYSTEM.read(this) {
|
||||||
|
readByteArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
|
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||||
|
get() = {
|
||||||
|
bytesAllocatorSync()
|
||||||
|
}
|
25
common/src/linuxArm64Main/kotlin/fixed.kt
Normal file
25
common/src/linuxArm64Main/kotlin/fixed.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package dev.inmo.micro_utils.common
|
||||||
|
|
||||||
|
import kotlinx.cinterop.*
|
||||||
|
import platform.posix.snprintf
|
||||||
|
import platform.posix.sprintf
|
||||||
|
|
||||||
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
|
actual fun Float.fixed(signs: Int): Float {
|
||||||
|
return memScoped {
|
||||||
|
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
|
||||||
|
|
||||||
|
sprintf(buff, "%.${signs}f", this@fixed)
|
||||||
|
buff.toKString().toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
|
actual fun Double.fixed(signs: Int): Double {
|
||||||
|
return memScoped {
|
||||||
|
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
|
||||||
|
|
||||||
|
sprintf(buff, "%.${signs}f", this@fixed)
|
||||||
|
buff.toKString().toDouble()
|
||||||
|
}
|
||||||
|
}
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.coroutines.compose"/>
|
<manifest/>
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.coroutines"/>
|
<manifest/>
|
@@ -0,0 +1,105 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composite mutex which works with next rules:
|
||||||
|
*
|
||||||
|
* * [acquireRead] require to [writeMutex] be free. Then it will take one lock from [readSemaphore]
|
||||||
|
* * [releaseRead] will just free up one permit in [readSemaphore]
|
||||||
|
* * [lockWrite] will lock [writeMutex] and then await while all [readSemaphore] will be freed
|
||||||
|
* * [unlockWrite] will just unlock [writeMutex]
|
||||||
|
*/
|
||||||
|
class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked: Boolean = false) {
|
||||||
|
private val _readSemaphore = SmartSemaphore.Mutable(permits = readPermits, acquiredPermits = 0)
|
||||||
|
private val _writeMutex = SmartMutex.Mutable(locked = writeIsLocked)
|
||||||
|
|
||||||
|
val readSemaphore: SmartSemaphore.Immutable = _readSemaphore.immutable()
|
||||||
|
val writeMutex: SmartMutex.Immutable = _writeMutex.immutable()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do lock in [readSemaphore] inside of [writeMutex] locking
|
||||||
|
*/
|
||||||
|
suspend fun acquireRead() {
|
||||||
|
_writeMutex.waitUnlock()
|
||||||
|
_readSemaphore.acquire()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release one read permit in [readSemaphore]
|
||||||
|
*/
|
||||||
|
suspend fun releaseRead(): Boolean {
|
||||||
|
return _readSemaphore.release()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locking [writeMutex] and wait while all [readSemaphore] permits will be freed
|
||||||
|
*/
|
||||||
|
suspend fun lockWrite() {
|
||||||
|
_writeMutex.lock()
|
||||||
|
_readSemaphore.acquire(readPermits)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlock [writeMutex]
|
||||||
|
*/
|
||||||
|
suspend fun unlockWrite(): Boolean {
|
||||||
|
return _writeMutex.unlock().also {
|
||||||
|
if (it) {
|
||||||
|
_readSemaphore.release(readPermits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [SmartSemaphore.Mutable.lock], then execute [action] and return the result after [SmartSemaphore.Mutable.unlock]
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
suspend inline fun <T> SmartRWLocker.withReadAcquire(action: () -> T): T {
|
||||||
|
contract {
|
||||||
|
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
acquireRead()
|
||||||
|
try {
|
||||||
|
return action()
|
||||||
|
} finally {
|
||||||
|
releaseRead()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will wait until the [SmartSemaphore.permitsStateFlow] of [this] instance will have [permits] count free permits.
|
||||||
|
*
|
||||||
|
* Anyway, after the end of this block there are no any guaranties that [SmartSemaphore.freePermits] >= [permits] due to
|
||||||
|
* the fact that some other parties may lock it again
|
||||||
|
*/
|
||||||
|
suspend fun SmartRWLocker.waitReadRelease(permits: Int = 1) = readSemaphore.waitRelease(permits)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [SmartMutex.Mutable.lock], then execute [action] and return the result after [SmartMutex.Mutable.unlock]
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
suspend inline fun <T> SmartRWLocker.withWriteLock(action: () -> T): T {
|
||||||
|
contract {
|
||||||
|
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
lockWrite()
|
||||||
|
try {
|
||||||
|
return action()
|
||||||
|
} finally {
|
||||||
|
unlockWrite()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will wait until the [SmartMutex.lockStateFlow] of [this] instance will be false.
|
||||||
|
*
|
||||||
|
* Anyway, after the end of this block there are no any guaranties that [SmartMutex.isLocked] == false due to the fact
|
||||||
|
* that some other parties may lock it again
|
||||||
|
*/
|
||||||
|
suspend fun SmartRWLocker.waitWriteUnlock() = writeMutex.waitUnlock()
|
@@ -0,0 +1,168 @@
|
|||||||
|
package dev.inmo.micro_utils.coroutines
|
||||||
|
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is interface which will work like classic [Semaphore], but in difference have [permitsStateFlow] for listening of the
|
||||||
|
* [SmartSemaphore] state.
|
||||||
|
*
|
||||||
|
* There is [Mutable] and [Immutable] realizations. In case you are owner and manager current state of lock, you need
|
||||||
|
* [Mutable] [SmartSemaphore]. Otherwise, [Immutable].
|
||||||
|
*
|
||||||
|
* Any [Mutable] [SmartSemaphore] may produce its [Immutable] variant which will contains [permitsStateFlow] equal to its
|
||||||
|
* [Mutable] creator
|
||||||
|
*/
|
||||||
|
sealed interface SmartSemaphore {
|
||||||
|
val permitsStateFlow: StateFlow<Int>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* * True - locked
|
||||||
|
* * False - unlocked
|
||||||
|
*/
|
||||||
|
val freePermits: Int
|
||||||
|
get() = permitsStateFlow.value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable variant of [SmartSemaphore]. In fact will depend on the owner of [permitsStateFlow]
|
||||||
|
*/
|
||||||
|
class Immutable(override val permitsStateFlow: StateFlow<Int>) : SmartSemaphore
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable variant of [SmartSemaphore]. With that variant you may [lock] and [unlock]. Besides, you may create
|
||||||
|
* [Immutable] variant of [this] instance with [immutable] factory
|
||||||
|
*
|
||||||
|
* @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow]
|
||||||
|
*/
|
||||||
|
class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
|
||||||
|
private val _freePermitsStateFlow = MutableStateFlow<Int>(permits - acquiredPermits)
|
||||||
|
override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow()
|
||||||
|
|
||||||
|
private val internalChangesMutex = Mutex(false)
|
||||||
|
|
||||||
|
fun immutable() = Immutable(permitsStateFlow)
|
||||||
|
|
||||||
|
private fun checkedPermits(permits: Int) = permits.coerceIn(1 .. this.permits)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds call until this [SmartSemaphore] will be re-locked. That means that current method will
|
||||||
|
*/
|
||||||
|
suspend fun acquire(permits: Int = 1) {
|
||||||
|
var acquiredPermits = 0
|
||||||
|
val checkedPermits = checkedPermits(permits)
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
val shouldContinue = internalChangesMutex.withLock {
|
||||||
|
val requiredPermits = checkedPermits - acquiredPermits
|
||||||
|
val acquiring = minOf(freePermits, requiredPermits).takeIf { it > 0 } ?: return@withLock true
|
||||||
|
acquiredPermits += acquiring
|
||||||
|
_freePermitsStateFlow.value -= acquiring
|
||||||
|
|
||||||
|
acquiredPermits != checkedPermits
|
||||||
|
}
|
||||||
|
if (shouldContinue) {
|
||||||
|
waitRelease()
|
||||||
|
}
|
||||||
|
} while (shouldContinue && currentCoroutineContext().isActive)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
release(acquiredPermits)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds call until this [SmartSemaphore] will be re-locked. That means that while [freePermits] == true, [holds] will
|
||||||
|
* wait for [freePermits] == false and then try to lock
|
||||||
|
*/
|
||||||
|
suspend fun acquireByOne(permits: Int = 1) {
|
||||||
|
val checkedPermits = checkedPermits(permits)
|
||||||
|
do {
|
||||||
|
waitRelease(checkedPermits)
|
||||||
|
val shouldContinue = internalChangesMutex.withLock {
|
||||||
|
if (_freePermitsStateFlow.value < checkedPermits) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
_freePermitsStateFlow.value -= checkedPermits
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (shouldContinue && currentCoroutineContext().isActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will try to lock this [SmartSemaphore] immediataly
|
||||||
|
*
|
||||||
|
* @return True if lock was successful. False otherwise
|
||||||
|
*/
|
||||||
|
suspend fun tryAcquire(permits: Int = 1): Boolean {
|
||||||
|
val checkedPermits = checkedPermits(permits)
|
||||||
|
return if (_freePermitsStateFlow.value < checkedPermits) {
|
||||||
|
internalChangesMutex.withLock {
|
||||||
|
if (_freePermitsStateFlow.value < checkedPermits) {
|
||||||
|
_freePermitsStateFlow.value -= checkedPermits
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If [freePermits] == true - will change it to false and return true. If current call will not unlock this
|
||||||
|
* [SmartSemaphore] - false
|
||||||
|
*/
|
||||||
|
suspend fun release(permits: Int = 1): Boolean {
|
||||||
|
val checkedPermits = checkedPermits(permits)
|
||||||
|
return if (_freePermitsStateFlow.value < this.permits) {
|
||||||
|
internalChangesMutex.withLock {
|
||||||
|
if (_freePermitsStateFlow.value < this.permits) {
|
||||||
|
_freePermitsStateFlow.value = minOf(_freePermitsStateFlow.value + checkedPermits, this.permits)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will call [SmartSemaphore.Mutable.lock], then execute [action] and return the result after [SmartSemaphore.Mutable.unlock]
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
suspend inline fun <T> SmartSemaphore.Mutable.withAcquire(permits: Int = 1, action: () -> T): T {
|
||||||
|
contract {
|
||||||
|
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire(permits)
|
||||||
|
try {
|
||||||
|
return action()
|
||||||
|
} finally {
|
||||||
|
release(permits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will wait until the [SmartSemaphore.permitsStateFlow] of [this] instance will have [permits] count free permits.
|
||||||
|
*
|
||||||
|
* Anyway, after the end of this block there are no any guaranties that [SmartSemaphore.freePermits] >= [permits] due to
|
||||||
|
* the fact that some other parties may lock it again
|
||||||
|
*/
|
||||||
|
suspend fun SmartSemaphore.waitRelease(permits: Int = 1) = permitsStateFlow.first { it >= permits }
|
151
coroutines/src/commonTest/kotlin/SmartRWLockerTests.kt
Normal file
151
coroutines/src/commonTest/kotlin/SmartRWLockerTests.kt
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class SmartRWLockerTests {
|
||||||
|
@Test
|
||||||
|
fun compositeTest() {
|
||||||
|
val locker = SmartRWLocker()
|
||||||
|
|
||||||
|
val readAndWriteWorkers = 10
|
||||||
|
runTest {
|
||||||
|
var started = 0
|
||||||
|
var done = 0
|
||||||
|
val doneMutex = Mutex()
|
||||||
|
val readWorkers = (0 until readAndWriteWorkers).map {
|
||||||
|
launch(start = CoroutineStart.LAZY) {
|
||||||
|
locker.withReadAcquire {
|
||||||
|
doneMutex.withLock {
|
||||||
|
started++
|
||||||
|
}
|
||||||
|
delay(100L)
|
||||||
|
doneMutex.withLock {
|
||||||
|
done++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var doneWrites = 0
|
||||||
|
|
||||||
|
val writeWorkers = (0 until readAndWriteWorkers).map {
|
||||||
|
launch(start = CoroutineStart.LAZY) {
|
||||||
|
locker.withWriteLock {
|
||||||
|
assertTrue(done == readAndWriteWorkers || started == 0)
|
||||||
|
delay(10L)
|
||||||
|
doneWrites++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readWorkers.forEach { it.start() }
|
||||||
|
writeWorkers.forEach { it.start() }
|
||||||
|
|
||||||
|
readWorkers.joinAll()
|
||||||
|
writeWorkers.joinAll()
|
||||||
|
|
||||||
|
assertEquals(expected = readAndWriteWorkers, actual = done)
|
||||||
|
assertEquals(expected = readAndWriteWorkers, actual = doneWrites)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleWithWriteLockTest() {
|
||||||
|
val locker = SmartRWLocker()
|
||||||
|
|
||||||
|
runTest {
|
||||||
|
locker.withWriteLock {
|
||||||
|
assertEquals(0, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(true, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun failureWithWriteLockTest() {
|
||||||
|
val locker = SmartRWLocker()
|
||||||
|
|
||||||
|
val exception = IllegalArgumentException()
|
||||||
|
try {
|
||||||
|
runTest {
|
||||||
|
val subscope = kotlinx.coroutines.CoroutineScope(this.coroutineContext)
|
||||||
|
var happenException: Throwable? = null
|
||||||
|
try {
|
||||||
|
locker.withWriteLock {
|
||||||
|
val checkFunction = fun (): Deferred<Unit> {
|
||||||
|
return subscope.async {
|
||||||
|
assertEquals(0, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(true, locker.writeMutex.isLocked)
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doInDefault {
|
||||||
|
assertEquals(0, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(true, locker.writeMutex.isLocked)
|
||||||
|
checkFunction().await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
happenException = e
|
||||||
|
}
|
||||||
|
if (exception != happenException) {
|
||||||
|
assertEquals(exception, happenException ?.cause)
|
||||||
|
}
|
||||||
|
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
assertEquals(exception, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simpleWithReadAcquireTest() {
|
||||||
|
val locker = SmartRWLocker()
|
||||||
|
|
||||||
|
runTest {
|
||||||
|
locker.withReadAcquire {
|
||||||
|
assertEquals(Int.MAX_VALUE - 1, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
locker.withReadAcquire {
|
||||||
|
assertEquals(Int.MAX_VALUE - 2, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun simple2WithWriteLockTest() {
|
||||||
|
val locker = SmartRWLocker()
|
||||||
|
|
||||||
|
val unlockDelay = 1000L // 1 sec
|
||||||
|
var unlocked: Boolean = false
|
||||||
|
runTest {
|
||||||
|
launch {
|
||||||
|
locker.withReadAcquire {
|
||||||
|
delay(unlockDelay)
|
||||||
|
}
|
||||||
|
unlocked = true
|
||||||
|
}
|
||||||
|
locker.readSemaphore.permitsStateFlow.first { it == Int.MAX_VALUE - 1 }
|
||||||
|
assertEquals(false, unlocked)
|
||||||
|
locker.withWriteLock {
|
||||||
|
assertEquals(true, unlocked)
|
||||||
|
assertEquals(0, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(true, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
assertEquals(Int.MAX_VALUE, locker.readSemaphore.freePermits)
|
||||||
|
assertEquals(false, locker.writeMutex.isLocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.crypto"/>
|
<manifest/>
|
@@ -9,6 +9,7 @@ android {
|
|||||||
targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
||||||
versionCode "${android_code_version}".toInteger()
|
versionCode "${android_code_version}".toInteger()
|
||||||
versionName "$version"
|
versionName "$version"
|
||||||
|
namespace "${project.group}.${project.name}"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
@@ -26,7 +27,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -106,7 +106,7 @@ tasks.dokkaHtml {
|
|||||||
skipDeprecated.set(true)
|
skipDeprecated.set(true)
|
||||||
|
|
||||||
sourceLink {
|
sourceLink {
|
||||||
localDirectory.set(file("./"))
|
localDirectory.set(file("../"))
|
||||||
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/MicroUtils/blob/master/"))
|
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/MicroUtils/blob/master/"))
|
||||||
remoteLineSuffix.set("#L")
|
remoteLineSuffix.set("#L")
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.dokka"/>
|
<manifest/>
|
@@ -19,11 +19,19 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
|
||||||
|
// String compilerPluginVersionFromProperties = (String) project.properties["compose.kotlinCompilerPluginVersion"]
|
||||||
|
// String compilerPluginVersionFromLibrariesVersions = libs.versions.compose.kotlin.get()
|
||||||
|
// composePluginKotlinVersion = compilerPluginVersionFromProperties
|
||||||
|
// if (compilerPluginVersionFromProperties == null) {
|
||||||
|
// composePluginKotlinVersion = compilerPluginVersionFromLibrariesVersions
|
||||||
|
// }
|
||||||
|
|
||||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsAndroidProject.gradle"
|
||||||
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
||||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
||||||
mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
|
mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
|
||||||
|
mppJvmJsLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwLinuxArm64Project.gradle"
|
||||||
|
mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle"
|
||||||
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
||||||
|
|
||||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
||||||
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.fsm.common"/>
|
<manifest/>
|
||||||
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.fsm.repos.common"/>
|
<manifest/>
|
||||||
|
@@ -6,7 +6,7 @@ kotlin.incremental.js=true
|
|||||||
#kotlin.experimental.tryK2=true
|
#kotlin.experimental.tryK2=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
org.gradle.jvmargs=-Xmx2g
|
org.gradle.jvmargs=-Xmx2500m
|
||||||
|
|
||||||
# JS NPM
|
# JS NPM
|
||||||
|
|
||||||
@@ -15,5 +15,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.20.0
|
version=0.20.12
|
||||||
android_code_version=206
|
android_code_version=218
|
||||||
|
@@ -1,44 +1,45 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kt = "1.9.0"
|
kt = "1.9.20"
|
||||||
kt-serialization = "1.5.1"
|
kt-serialization = "1.6.0"
|
||||||
kt-coroutines = "1.7.3"
|
kt-coroutines = "1.7.3"
|
||||||
|
|
||||||
kslog = "1.2.0"
|
kslog = "1.2.4"
|
||||||
|
|
||||||
jb-compose = "1.4.3"
|
jb-compose = "1.5.10"
|
||||||
jb-exposed = "0.42.0"
|
jb-exposed = "0.44.1"
|
||||||
jb-dokka = "1.8.20"
|
jb-dokka = "1.9.10"
|
||||||
|
|
||||||
korlibs = "4.0.9"
|
korlibs = "4.0.10"
|
||||||
uuid = "0.8.0"
|
uuid = "0.8.1"
|
||||||
|
|
||||||
ktor = "2.3.3"
|
ktor = "2.3.5"
|
||||||
|
|
||||||
gh-release = "2.4.1"
|
gh-release = "2.4.1"
|
||||||
|
|
||||||
koin = "3.4.3"
|
koin = "3.5.0"
|
||||||
|
|
||||||
okio = "3.5.0"
|
okio = "3.6.0"
|
||||||
|
|
||||||
ksp = "1.9.0-1.0.13"
|
ksp = "1.9.20-1.0.13"
|
||||||
kotlin-poet = "1.14.2"
|
kotlin-poet = "1.14.2"
|
||||||
|
|
||||||
versions = "0.47.0"
|
versions = "0.49.0"
|
||||||
|
|
||||||
android-gradle = "7.4.2"
|
android-gradle = "8.1.2"
|
||||||
dexcount = "4.0.0"
|
dexcount = "4.0.0"
|
||||||
|
|
||||||
android-coreKtx = "1.10.1"
|
android-coreKtx = "1.12.0"
|
||||||
android-recyclerView = "1.3.1"
|
android-recyclerView = "1.3.2"
|
||||||
android-appCompat = "1.6.1"
|
android-appCompat = "1.6.1"
|
||||||
android-fragment = "1.6.1"
|
android-fragment = "1.6.2"
|
||||||
android-espresso = "3.5.1"
|
android-espresso = "3.5.1"
|
||||||
android-test = "1.1.5"
|
android-test = "1.1.5"
|
||||||
|
android-compose-material3 = "1.1.2"
|
||||||
|
|
||||||
android-props-minSdk = "21"
|
android-props-minSdk = "21"
|
||||||
android-props-compileSdk = "33"
|
android-props-compileSdk = "34"
|
||||||
android-props-buildTools = "33.0.2"
|
android-props-buildTools = "34.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
||||||
@@ -83,6 +84,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
|
|||||||
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
|
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
|
||||||
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
|
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
|
||||||
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
|
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
|
||||||
|
android-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "android-compose-material3" }
|
||||||
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
|
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
|
||||||
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
|
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
|
||||||
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
|
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -2,11 +2,11 @@ apply plugin: 'maven-publish'
|
|||||||
|
|
||||||
task javadocJar(type: Jar) {
|
task javadocJar(type: Jar) {
|
||||||
from javadoc
|
from javadoc
|
||||||
classifier = 'javadoc'
|
archiveClassifier = 'javadoc'
|
||||||
}
|
}
|
||||||
task sourcesJar(type: Jar) {
|
task sourcesJar(type: Jar) {
|
||||||
from sourceSets.main.allSource
|
from sourceSets.main.allSource
|
||||||
classifier = 'sources'
|
archiveClassifier = 'sources'
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
@@ -115,4 +115,10 @@ if (project.hasProperty("signing.gnupg.keyName")) {
|
|||||||
dependsOn(it)
|
dependsOn(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workaround to make android sign operations depend on signing tasks
|
||||||
|
project.getTasks().withType(AbstractPublishToMaven.class).configureEach {
|
||||||
|
def signingTasks = project.getTasks().withType(Sign.class)
|
||||||
|
mustRunAfter(signingTasks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,6 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.koin.generator.test"/>
|
<manifest/>
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.koin"/>
|
<manifest/>
|
||||||
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -30,5 +30,11 @@ kotlin {
|
|||||||
api internalProject("micro_utils.mime_types")
|
api internalProject("micro_utils.mime_types")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
linuxArm64Main {
|
||||||
|
dependencies {
|
||||||
|
api internalProject("micro_utils.mime_types")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.ktor.client"/>
|
<manifest/>
|
@@ -0,0 +1,40 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import dev.inmo.micro_utils.common.filename
|
||||||
|
import dev.inmo.micro_utils.ktor.common.TemporalFileId
|
||||||
|
import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.plugins.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
|
||||||
|
|
||||||
|
internal val MPPFile.mimeType: String
|
||||||
|
get() = getMimeTypeOrAny(filename.extension).raw
|
||||||
|
|
||||||
|
actual suspend fun HttpClient.tempUpload(
|
||||||
|
fullTempUploadDraftPath: String,
|
||||||
|
file: MPPFile,
|
||||||
|
onUpload: OnUploadCallback
|
||||||
|
): TemporalFileId {
|
||||||
|
val inputProvider = file.inputProvider()
|
||||||
|
val fileId = submitFormWithBinaryData(
|
||||||
|
fullTempUploadDraftPath,
|
||||||
|
formData = formData {
|
||||||
|
append(
|
||||||
|
"data",
|
||||||
|
inputProvider,
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, file.mimeType)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
onUpload(onUpload)
|
||||||
|
}.bodyAsText()
|
||||||
|
return TemporalFileId(fileId)
|
||||||
|
}
|
107
ktor/client/src/linuxArm64Main/kotlin/ActualUniUpload.kt
Normal file
107
ktor/client/src/linuxArm64Main/kotlin/ActualUniUpload.kt
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import dev.inmo.micro_utils.common.Progress
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.mergeHeaders
|
||||||
|
import io.ktor.client.plugins.onUpload
|
||||||
|
import io.ktor.client.request.HttpRequestBuilder
|
||||||
|
import io.ktor.client.request.forms.InputProvider
|
||||||
|
import io.ktor.client.request.forms.formData
|
||||||
|
import io.ktor.client.request.forms.submitForm
|
||||||
|
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||||
|
import io.ktor.client.request.headers
|
||||||
|
import io.ktor.client.statement.bodyAsText
|
||||||
|
import io.ktor.http.Headers
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.Parameters
|
||||||
|
import io.ktor.http.content.PartData
|
||||||
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
|
import kotlinx.serialization.SerializationStrategy
|
||||||
|
import kotlinx.serialization.StringFormat
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will execute submitting of multipart data request
|
||||||
|
*
|
||||||
|
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
|
||||||
|
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
|
||||||
|
* in case you wish to pass other source of multipart binary data than regular file
|
||||||
|
* @suppress
|
||||||
|
*/
|
||||||
|
@OptIn(InternalSerializationApi::class)
|
||||||
|
actual suspend fun <T> HttpClient.uniUpload(
|
||||||
|
url: String,
|
||||||
|
data: Map<String, Any>,
|
||||||
|
resultDeserializer: DeserializationStrategy<T>,
|
||||||
|
headers: Headers,
|
||||||
|
stringFormat: StringFormat,
|
||||||
|
onUpload: OnUploadCallback
|
||||||
|
): T? {
|
||||||
|
val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo }
|
||||||
|
|
||||||
|
val formData = formData {
|
||||||
|
for (k in data.keys) {
|
||||||
|
val v = data[k] ?: continue
|
||||||
|
when (v) {
|
||||||
|
is MPPFile -> append(
|
||||||
|
k,
|
||||||
|
v.inputProvider(),
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, v.mimeType)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is UniUploadFileInfo -> append(
|
||||||
|
k,
|
||||||
|
InputProvider(block = v.inputAllocator),
|
||||||
|
Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, v.mimeType)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else -> append(
|
||||||
|
k,
|
||||||
|
stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val requestBuilder: HttpRequestBuilder.() -> Unit = {
|
||||||
|
headers {
|
||||||
|
appendAll(headers)
|
||||||
|
}
|
||||||
|
onUpload { bytesSentTotal, contentLength ->
|
||||||
|
onUpload(bytesSentTotal, contentLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = if (withBinary) {
|
||||||
|
submitFormWithBinaryData(
|
||||||
|
url,
|
||||||
|
formData,
|
||||||
|
block = requestBuilder
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
submitForm(
|
||||||
|
url,
|
||||||
|
Parameters.build {
|
||||||
|
for (it in formData) {
|
||||||
|
val formItem = (it as PartData.FormItem)
|
||||||
|
append(it.name!!, it.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
block = requestBuilder
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (response.status == HttpStatusCode.OK) {
|
||||||
|
stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.ktor.common"/>
|
<manifest/>
|
11
ktor/common/src/linuxArm64Main/kotlin/ActualMPPFileInput.kt
Normal file
11
ktor/common/src/linuxArm64Main/kotlin/ActualMPPFileInput.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package dev.inmo.micro_utils.ktor.common
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import dev.inmo.micro_utils.common.bytesAllocatorSync
|
||||||
|
import io.ktor.utils.io.core.ByteReadPacket
|
||||||
|
import io.ktor.utils.io.core.Input
|
||||||
|
|
||||||
|
actual fun MPPFile.input(): Input {
|
||||||
|
return ByteReadPacket(bytesAllocatorSync())
|
||||||
|
}
|
||||||
|
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -26,6 +26,6 @@ dependencies {
|
|||||||
mainClassName="MainKt"
|
mainClassName="MainKt"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.language_codes"/>
|
<manifest/>
|
||||||
|
@@ -4,4 +4,4 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.matrix"/>
|
<manifest/>
|
@@ -4,4 +4,4 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.mime_types"/>
|
<manifest/>
|
@@ -4,8 +4,13 @@ project.group = "$group"
|
|||||||
apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
android {
|
androidTarget {
|
||||||
publishAllLibraryVariants()
|
publishAllLibraryVariants()
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -18,6 +23,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('test-common')
|
implementation kotlin('test-common')
|
||||||
implementation kotlin('test-annotations-common')
|
implementation kotlin('test-annotations-common')
|
||||||
|
implementation libs.kt.coroutines.test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +32,6 @@ kotlin {
|
|||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ kotlin {
|
|||||||
jvm {
|
jvm {
|
||||||
compilations.main {
|
compilations.main {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('test-common')
|
implementation kotlin('test-common')
|
||||||
implementation kotlin('test-annotations-common')
|
implementation kotlin('test-annotations-common')
|
||||||
|
implementation libs.kt.coroutines.test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +35,6 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
71
mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle
Normal file
71
mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
project.version = "$version"
|
||||||
|
project.group = "$group"
|
||||||
|
|
||||||
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm {
|
||||||
|
compilations.main {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
js (IR) {
|
||||||
|
browser()
|
||||||
|
nodejs()
|
||||||
|
}
|
||||||
|
androidTarget {
|
||||||
|
publishAllLibraryVariants()
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
linuxX64()
|
||||||
|
mingwX64()
|
||||||
|
linuxArm64()
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib')
|
||||||
|
api libs.kt.serialization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commonTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-common')
|
||||||
|
implementation kotlin('test-annotations-common')
|
||||||
|
implementation libs.kt.coroutines.test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
androidUnitTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
implementation libs.android.test.junit
|
||||||
|
implementation libs.android.espresso
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jvmTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-js')
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
@@ -7,7 +7,7 @@ kotlin {
|
|||||||
jvm {
|
jvm {
|
||||||
compilations.main {
|
compilations.main {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,8 +15,13 @@ kotlin {
|
|||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
}
|
}
|
||||||
android {
|
androidTarget {
|
||||||
publishAllLibraryVariants()
|
publishAllLibraryVariants()
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
linuxX64()
|
linuxX64()
|
||||||
mingwX64()
|
mingwX64()
|
||||||
@@ -32,6 +37,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('test-common')
|
implementation kotlin('test-common')
|
||||||
implementation kotlin('test-annotations-common')
|
implementation kotlin('test-annotations-common')
|
||||||
|
implementation libs.kt.coroutines.test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmTest {
|
jvmTest {
|
||||||
@@ -70,6 +76,6 @@ kotlin {
|
|||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
54
mppJvmJsLinuxMingwLinuxArm64Project.gradle
Normal file
54
mppJvmJsLinuxMingwLinuxArm64Project.gradle
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
project.version = "$version"
|
||||||
|
project.group = "$group"
|
||||||
|
|
||||||
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm {
|
||||||
|
compilations.main {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
js (IR) {
|
||||||
|
browser()
|
||||||
|
nodejs()
|
||||||
|
}
|
||||||
|
linuxX64()
|
||||||
|
mingwX64()
|
||||||
|
linuxArm64()
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('stdlib')
|
||||||
|
api libs.kt.serialization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commonTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-common')
|
||||||
|
implementation kotlin('test-annotations-common')
|
||||||
|
implementation libs.kt.coroutines.test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jvmTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-js')
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
@@ -7,7 +7,7 @@ kotlin {
|
|||||||
jvm {
|
jvm {
|
||||||
compilations.main {
|
compilations.main {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,12 +22,14 @@ kotlin {
|
|||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('stdlib')
|
implementation kotlin('stdlib')
|
||||||
|
api libs.kt.serialization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commonTest {
|
commonTest {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('test-common')
|
implementation kotlin('test-common')
|
||||||
implementation kotlin('test-annotations-common')
|
implementation kotlin('test-annotations-common')
|
||||||
|
implementation libs.kt.coroutines.test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +44,22 @@ kotlin {
|
|||||||
implementation kotlin('test-junit')
|
implementation kotlin('test-junit')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mingwX64Test {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
linuxX64Test {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
androidMain.dependsOn jvmMain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ kotlin {
|
|||||||
jvm {
|
jvm {
|
||||||
compilations.main {
|
compilations.main {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,8 +15,13 @@ kotlin {
|
|||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
}
|
}
|
||||||
android {
|
androidTarget {
|
||||||
publishAllLibraryVariants()
|
publishAllLibraryVariants()
|
||||||
|
compilations.all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -31,6 +36,7 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('test-common')
|
implementation kotlin('test-common')
|
||||||
implementation kotlin('test-annotations-common')
|
implementation kotlin('test-annotations-common')
|
||||||
|
implementation libs.kt.coroutines.test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
jvmMain {
|
||||||
@@ -69,6 +75,13 @@ kotlin {
|
|||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
apply from: "$defaultAndroidSettingsPresetPath"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//compose {
|
||||||
|
// if (composePluginKotlinVersion != null && !composePluginKotlinVersion.isEmpty()) {
|
||||||
|
// kotlinCompilerPlugin.set(composePluginKotlinVersion)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.pagination.common"/>
|
<manifest/>
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.pagination.ktor.common"/>
|
<manifest/>
|
@@ -1,7 +1,7 @@
|
|||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
task javadocsJar(type: Jar) {
|
task javadocsJar(type: Jar) {
|
||||||
classifier = 'javadoc'
|
archiveClassifier = 'javadoc'
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
@@ -19,29 +19,29 @@ publishing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
developers {
|
developers {
|
||||||
|
|
||||||
developer {
|
developer {
|
||||||
id = "InsanusMokrassar"
|
id = "InsanusMokrassar"
|
||||||
name = "Aleksei Ovsiannikov"
|
name = "Aleksei Ovsiannikov"
|
||||||
email = "ovsyannikov.alexey95@gmail.com"
|
email = "ovsyannikov.alexey95@gmail.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
developer {
|
developer {
|
||||||
id = "000Sanya"
|
id = "000Sanya"
|
||||||
name = "Syrov Aleksandr"
|
name = "Syrov Aleksandr"
|
||||||
email = "000sanya.000sanya@gmail.com"
|
email = "000sanya.000sanya@gmail.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
licenses {
|
licenses {
|
||||||
|
|
||||||
license {
|
license {
|
||||||
name = "Apache Software License 2.0"
|
name = "Apache Software License 2.0"
|
||||||
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
|
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
@@ -49,57 +49,64 @@ publishing {
|
|||||||
maven {
|
maven {
|
||||||
name = "GithubPackages"
|
name = "GithubPackages"
|
||||||
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
|
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
|
||||||
|
|
||||||
credentials {
|
credentials {
|
||||||
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
|
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
|
||||||
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
|
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
|
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
|
||||||
maven {
|
maven {
|
||||||
name = "Gitea"
|
name = "Gitea"
|
||||||
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
|
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
|
||||||
|
|
||||||
credentials(HttpHeaderCredentials) {
|
credentials(HttpHeaderCredentials) {
|
||||||
name = "Authorization"
|
name = "Authorization"
|
||||||
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
|
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
|
||||||
}
|
}
|
||||||
|
|
||||||
authentication {
|
authentication {
|
||||||
header(HttpHeaderAuthentication)
|
header(HttpHeaderAuthentication)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
|
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
|
||||||
maven {
|
maven {
|
||||||
name = "sonatype"
|
name = "sonatype"
|
||||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||||
|
|
||||||
credentials {
|
credentials {
|
||||||
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||||
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.hasProperty("signing.gnupg.keyName")) {
|
if (project.hasProperty("signing.gnupg.keyName")) {
|
||||||
apply plugin: 'signing'
|
apply plugin: 'signing'
|
||||||
|
|
||||||
signing {
|
signing {
|
||||||
useGpgCmd()
|
useGpgCmd()
|
||||||
|
|
||||||
sign publishing.publications
|
sign publishing.publications
|
||||||
}
|
}
|
||||||
|
|
||||||
task signAll {
|
task signAll {
|
||||||
tasks.withType(Sign).forEach {
|
tasks.withType(Sign).forEach {
|
||||||
dependsOn(it)
|
dependsOn(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workaround to make android sign operations depend on signing tasks
|
||||||
|
project.getTasks().withType(AbstractPublishToMaven.class).configureEach {
|
||||||
|
def signingTasks = project.getTasks().withType(Sign.class)
|
||||||
|
mustRunAfter(signingTasks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
2
repos/cache/build.gradle
vendored
2
repos/cache/build.gradle
vendored
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.repos.cache"/>
|
<manifest/>
|
@@ -1,5 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
|
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||||
|
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
@@ -10,32 +13,47 @@ import kotlinx.coroutines.flow.*
|
|||||||
open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||||
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||||
protected open val kvCache: KVCache<IdType, ObjectType>,
|
protected open val kvCache: KVCache<IdType, ObjectType>,
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker(),
|
||||||
protected open val idGetter: (ObjectType) -> IdType
|
protected open val idGetter: (ObjectType) -> IdType
|
||||||
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CommonCacheRepo {
|
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CommonCacheRepo {
|
||||||
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
|
override suspend fun getById(id: IdType): ObjectType? = locker.withReadAcquire {
|
||||||
kvCache.set(id, it)
|
kvCache.get(id)
|
||||||
|
} ?: (parentRepo.getById(id) ?.also {
|
||||||
|
locker.withWriteLock {
|
||||||
|
kvCache.set(id, it)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
override suspend fun getAll(): Map<IdType, ObjectType> {
|
override suspend fun getAll(): Map<IdType, ObjectType> {
|
||||||
return kvCache.getAll().takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also {
|
return locker.withReadAcquire {
|
||||||
kvCache.actualizeAll(true) { it }
|
kvCache.getAll()
|
||||||
|
}.takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also {
|
||||||
|
locker.withWriteLock {
|
||||||
|
kvCache.actualizeAll(true) { it }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
override suspend fun contains(id: IdType): Boolean = locker.withReadAcquire {
|
||||||
|
kvCache.contains(id)
|
||||||
|
} || parentRepo.contains(id)
|
||||||
|
|
||||||
override suspend fun invalidate() = kvCache.clear()
|
override suspend fun invalidate() = locker.withWriteLock {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||||
kvCache: KVCache<IdType, ObjectType>,
|
kvCache: KVCache<IdType, ObjectType>,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
idGetter: (ObjectType) -> IdType
|
idGetter: (ObjectType) -> IdType
|
||||||
) = ReadCRUDCacheRepo(this, kvCache, idGetter)
|
) = ReadCRUDCacheRepo(this, kvCache, locker, idGetter)
|
||||||
|
|
||||||
open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||||
protected open val parentRepo: WriteCRUDRepo<ObjectType, IdType, InputValueType>,
|
protected open val parentRepo: WriteCRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
protected open val kvCache: KVCache<IdType, ObjectType>,
|
protected open val kvCache: KeyValueRepo<IdType, ObjectType>,
|
||||||
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker(),
|
||||||
protected open val idGetter: (ObjectType) -> IdType
|
protected open val idGetter: (ObjectType) -> IdType
|
||||||
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CommonCacheRepo {
|
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CommonCacheRepo {
|
||||||
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
|
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
|
||||||
@@ -43,69 +61,91 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
|||||||
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
|
||||||
|
|
||||||
val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach {
|
val createdObjectsFlowJob = parentRepo.newObjectsFlow.onEach {
|
||||||
kvCache.set(idGetter(it), it)
|
locker.withWriteLock {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach {
|
val updatedObjectsFlowJob = parentRepo.updatedObjectsFlow.onEach {
|
||||||
kvCache.set(idGetter(it), it)
|
locker.withWriteLock {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach {
|
val deletedObjectsFlowJob = parentRepo.deletedObjectsIdsFlow.onEach {
|
||||||
kvCache.unset(it)
|
locker.withWriteLock {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
override suspend fun deleteById(ids: List<IdType>) = parentRepo.deleteById(ids)
|
override suspend fun deleteById(ids: List<IdType>) = parentRepo.deleteById(ids).also {
|
||||||
|
locker.withWriteLock {
|
||||||
|
kvCache.unset(ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
override suspend fun update(values: List<UpdatedValuePair<IdType, InputValueType>>): List<ObjectType> {
|
||||||
val updated = parentRepo.update(values)
|
val updated = parentRepo.update(values)
|
||||||
|
|
||||||
kvCache.unset(values.map { it.id })
|
locker.withWriteLock {
|
||||||
kvCache.set(updated.associateBy { idGetter(it) })
|
kvCache.unset(values.map { it.id })
|
||||||
|
kvCache.set(updated.associateBy { idGetter(it) })
|
||||||
|
}
|
||||||
|
|
||||||
return updated
|
return updated
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
||||||
return parentRepo.update(id, value) ?.also {
|
return parentRepo.update(id, value) ?.also {
|
||||||
kvCache.unset(id)
|
locker.withWriteLock {
|
||||||
kvCache.set(idGetter(it), it)
|
kvCache.unset(id)
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||||
val created = parentRepo.create(values)
|
val created = parentRepo.create(values)
|
||||||
|
|
||||||
kvCache.set(
|
locker.withWriteLock {
|
||||||
created.associateBy { idGetter(it) }
|
kvCache.set(
|
||||||
)
|
created.associateBy { idGetter(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return created
|
return created
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun invalidate() = kvCache.clear()
|
override suspend fun invalidate() = locker.withWriteLock {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
|
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(
|
||||||
kvCache: KVCache<IdType, ObjectType>,
|
kvCache: KVCache<IdType, ObjectType>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
idGetter: (ObjectType) -> IdType
|
idGetter: (ObjectType) -> IdType
|
||||||
) = WriteCRUDCacheRepo(this, kvCache, scope, idGetter)
|
) = WriteCRUDCacheRepo(this, kvCache, scope, locker, idGetter)
|
||||||
|
|
||||||
|
|
||||||
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||||
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
kvCache: KVCache<IdType, ObjectType>,
|
kvCache: KVCache<IdType, ObjectType>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
idGetter: (ObjectType) -> IdType
|
idGetter: (ObjectType) -> IdType
|
||||||
) : ReadCRUDCacheRepo<ObjectType, IdType>(
|
) : ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||||
parentRepo,
|
parentRepo,
|
||||||
kvCache,
|
kvCache,
|
||||||
|
locker,
|
||||||
idGetter
|
idGetter
|
||||||
),
|
),
|
||||||
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
|
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
|
||||||
parentRepo,
|
parentRepo,
|
||||||
kvCache,
|
kvCache,
|
||||||
scope,
|
scope,
|
||||||
|
locker,
|
||||||
idGetter
|
idGetter
|
||||||
),
|
),
|
||||||
CRUDRepo<ObjectType, IdType, InputValueType>
|
CRUDRepo<ObjectType, IdType, InputValueType>
|
||||||
@@ -113,5 +153,6 @@ open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
|||||||
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
||||||
kvCache: KVCache<IdType, ObjectType>,
|
kvCache: KVCache<IdType, ObjectType>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
idGetter: (ObjectType) -> IdType
|
idGetter: (ObjectType) -> IdType
|
||||||
) = CRUDCacheRepo(this, kvCache, scope, idGetter)
|
) = CRUDCacheRepo(this, kvCache, scope, locker, idGetter)
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
|
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||||
|
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
@@ -10,50 +13,82 @@ import kotlinx.coroutines.flow.*
|
|||||||
open class ReadKeyValueCacheRepo<Key,Value>(
|
open class ReadKeyValueCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||||
protected open val kvCache: KVCache<Key, Value>,
|
protected open val kvCache: KVCache<Key, Value>,
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker(),
|
||||||
) : ReadKeyValueRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
) : ReadKeyValueRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
||||||
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
|
override suspend fun get(k: Key): Value? = locker.withReadAcquire {
|
||||||
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
|
kvCache.get(k)
|
||||||
|
} ?: parentRepo.get(k) ?.also {
|
||||||
|
locker.withWriteLock {
|
||||||
|
kvCache.set(k, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override suspend fun contains(key: Key): Boolean = locker.withReadAcquire {
|
||||||
|
kvCache.contains(key)
|
||||||
|
} || parentRepo.contains(key)
|
||||||
|
|
||||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||||
return keys(pagination, reversed).let {
|
return locker.withReadAcquire {
|
||||||
it.changeResultsUnchecked(
|
keys(pagination, reversed).let {
|
||||||
it.results.mapNotNull {
|
it.changeResultsUnchecked(
|
||||||
get(it)
|
it.results.mapNotNull {
|
||||||
}
|
get(it)
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAll(): Map<Key, Value> = kvCache.getAll().takeIf {
|
override suspend fun getAll(): Map<Key, Value> = locker.withReadAcquire {
|
||||||
|
kvCache.getAll()
|
||||||
|
}.takeIf {
|
||||||
it.size.toLong() == count()
|
it.size.toLong() == count()
|
||||||
} ?: parentRepo.getAll().also {
|
} ?: parentRepo.getAll().also {
|
||||||
kvCache.set(it)
|
locker.withWriteLock {
|
||||||
|
kvCache.set(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun invalidate() = kvCache.clear()
|
override suspend fun invalidate() = locker.withWriteLock {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||||
kvCache: KVCache<Key, Value>
|
kvCache: KVCache<Key, Value>,
|
||||||
) = ReadKeyValueCacheRepo(this, kvCache)
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) = ReadKeyValueCacheRepo(this, kvCache, locker)
|
||||||
|
|
||||||
open class KeyValueCacheRepo<Key,Value>(
|
open class KeyValueCacheRepo<Key,Value>(
|
||||||
override val parentRepo: KeyValueRepo<Key, Value>,
|
override val parentRepo: KeyValueRepo<Key, Value>,
|
||||||
kvCache: KVCache<Key, Value>,
|
kvCache: KVCache<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CommonCacheRepo {
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, locker), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CommonCacheRepo {
|
||||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
protected val onNewJob = parentRepo.onNewValue.onEach {
|
||||||
|
locker.withWriteLock {
|
||||||
|
kvCache.set(it.first, it.second)
|
||||||
|
}
|
||||||
|
}.launchIn(scope)
|
||||||
|
protected val onRemoveJob = parentRepo.onValueRemoved.onEach {
|
||||||
|
locker.withWriteLock {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
|
}.launchIn(scope)
|
||||||
|
|
||||||
override suspend fun invalidate() = kvCache.clear()
|
override suspend fun invalidate() = locker.withWriteLock {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun clear() {
|
override suspend fun clear() {
|
||||||
parentRepo.clear()
|
parentRepo.clear()
|
||||||
kvCache.clear()
|
locker.withWriteLock {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||||
kvCache: KVCache<Key, Value>,
|
kvCache: KVCache<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
) = KeyValueCacheRepo(this, kvCache, scope)
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) = KeyValueCacheRepo(this, kvCache, scope, locker)
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
|
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||||
|
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.*
|
import dev.inmo.micro_utils.pagination.utils.*
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
@@ -10,59 +13,85 @@ import kotlinx.coroutines.flow.*
|
|||||||
|
|
||||||
open class ReadKeyValuesCacheRepo<Key,Value>(
|
open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||||
protected open val kvCache: KVCache<Key, List<Value>>
|
protected open val kvCache: KVCache<Key, List<Value>>,
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker(),
|
||||||
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
||||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||||
return getAll(k, reversed).paginate(
|
return locker.withReadAcquire {
|
||||||
|
getAll(k, reversed)
|
||||||
|
}.paginate(
|
||||||
pagination
|
pagination
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override suspend fun getAll(k: Key, reversed: Boolean): List<Value> {
|
override suspend fun getAll(k: Key, reversed: Boolean): List<Value> {
|
||||||
return kvCache.get(k) ?.let {
|
return locker.withReadAcquire {
|
||||||
|
kvCache.get(k)
|
||||||
|
} ?.let {
|
||||||
if (reversed) it.reversed() else it
|
if (reversed) it.reversed() else it
|
||||||
} ?: parentRepo.getAll(k, reversed).also {
|
} ?: parentRepo.getAll(k, reversed).also {
|
||||||
kvCache.set(k, it)
|
locker.withWriteLock {
|
||||||
|
kvCache.set(k, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: (parentRepo.contains(k, v).also {
|
override suspend fun contains(k: Key, v: Value): Boolean = locker.withReadAcquire {
|
||||||
|
kvCache.get(k)
|
||||||
|
} ?.contains(v) ?: (parentRepo.contains(k, v).also {
|
||||||
if (it) {
|
if (it) {
|
||||||
kvCache.unset(k) // clear as invalid
|
locker.withWriteLock {
|
||||||
|
kvCache.unset(k) // clear as invalid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
|
override suspend fun contains(k: Key): Boolean = locker.withReadAcquire {
|
||||||
|
kvCache.contains(k)
|
||||||
|
} || parentRepo.contains(k)
|
||||||
|
|
||||||
override suspend fun invalidate() = kvCache.clear()
|
override suspend fun invalidate() = locker.withWriteLock {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||||
kvCache: KVCache<Key, List<Value>>
|
kvCache: KVCache<Key, List<Value>>,
|
||||||
) = ReadKeyValuesCacheRepo(this, kvCache)
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) = ReadKeyValuesCacheRepo(this, kvCache, locker)
|
||||||
|
|
||||||
open class KeyValuesCacheRepo<Key,Value>(
|
open class KeyValuesCacheRepo<Key,Value>(
|
||||||
parentRepo: KeyValuesRepo<Key, Value>,
|
parentRepo: KeyValuesRepo<Key, Value>,
|
||||||
kvCache: KVCache<Key, List<Value>>,
|
kvCache: KVCache<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache, locker), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo {
|
||||||
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
|
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
|
||||||
kvCache.set(
|
locker.withWriteLock {
|
||||||
k,
|
kvCache.set(
|
||||||
kvCache.get(k) ?.plus(v) ?: return@onEach
|
k,
|
||||||
)
|
kvCache.get(k) ?.plus(v) ?: return@onEach
|
||||||
|
)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { (k, v) ->
|
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { (k, v) ->
|
||||||
kvCache.set(
|
locker.withWriteLock {
|
||||||
k,
|
kvCache.set(
|
||||||
kvCache.get(k) ?.minus(v) ?: return@onEach
|
k,
|
||||||
)
|
kvCache.get(k)?.minus(v) ?: return@onEach
|
||||||
|
)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
|
protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
|
||||||
kvCache.unset(it)
|
locker.withWriteLock {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
override suspend fun invalidate() = kvCache.clear()
|
override suspend fun invalidate() = locker.withWriteLock {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
|
fun <Key, Value> KeyValuesRepo<Key, Value>.cached(
|
||||||
kvCache: KVCache<Key, List<Value>>,
|
kvCache: KVCache<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
) = KeyValuesCacheRepo(this, kvCache, scope)
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) = KeyValuesCacheRepo(this, kvCache, scope, locker)
|
||||||
|
@@ -3,6 +3,7 @@ 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
|
* This interface declares that current type of [KVCache] will contains all the data all the time of its life
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("This type of KV repos is obsolete and will be removed soon", ReplaceWith("KeyValueRepo<K, V>", "dev.inmo.micro_utils.repos.KeyValueRepo"))
|
||||||
interface FullKVCache<K, V> : KVCache<K, V> {
|
interface FullKVCache<K, V> : KVCache<K, V> {
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,9 @@ import dev.inmo.micro_utils.repos.MapKeyValueRepo
|
|||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
open class SimpleFullKVCache<K, V>(
|
@Deprecated("This type of KV repos is obsolete and will be removed soon", ReplaceWith("MapKeyValueRepo<K, V>()", "dev.inmo.micro_utils.repos.MapKeyValueRepo"))
|
||||||
|
|
||||||
|
class SimpleFullKVCache<K, V>(
|
||||||
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
private val kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||||
) : FullKVCache<K, V>, KeyValueRepo<K, V> by kvParent {
|
) : FullKVCache<K, V>, KeyValueRepo<K, V> by kvParent {
|
||||||
protected val syncMutex = Mutex()
|
protected val syncMutex = Mutex()
|
||||||
@@ -29,6 +31,7 @@ open class SimpleFullKVCache<K, V>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("This type of KV repos is obsolete and will be removed soon", ReplaceWith("kvParent", "dev.inmo.micro_utils.repos.MapKeyValueRepo"))
|
||||||
inline fun <K, V> FullKVCache(
|
inline fun <K, V> FullKVCache(
|
||||||
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
kvParent: KeyValueRepo<K, V> = MapKeyValueRepo<K, V>()
|
||||||
) = SimpleFullKVCache<K, V>(kvParent)
|
) = SimpleFullKVCache<K, V>(kvParent)
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.CRUDRepo
|
import dev.inmo.micro_utils.repos.CRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.MapKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@@ -10,7 +11,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
open class AutoRecacheCRUDRepo<RegisteredObject, Id, InputObject>(
|
open class AutoRecacheCRUDRepo<RegisteredObject, Id, InputObject>(
|
||||||
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo(),
|
||||||
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
idGetter: (RegisteredObject) -> Id
|
idGetter: (RegisteredObject) -> Id
|
||||||
@@ -29,7 +30,7 @@ open class AutoRecacheCRUDRepo<RegisteredObject, Id, InputObject>(
|
|||||||
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
originalCallTimeoutMillis: Long,
|
originalCallTimeoutMillis: Long,
|
||||||
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo(),
|
||||||
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
idGetter: (RegisteredObject) -> Id
|
idGetter: (RegisteredObject) -> Id
|
||||||
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
@@ -3,8 +3,9 @@ package dev.inmo.micro_utils.repos.cache.fallback.crud
|
|||||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.MapKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
@@ -18,7 +19,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
||||||
protected open val originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
protected open val originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
||||||
protected val scope: CoroutineScope,
|
protected val scope: CoroutineScope,
|
||||||
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
protected val kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo(),
|
||||||
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
protected val idGetter: (RegisteredObject) -> Id
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
@@ -35,7 +36,7 @@ open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
|||||||
originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
originalCallTimeoutMillis: Long,
|
originalCallTimeoutMillis: Long,
|
||||||
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo(),
|
||||||
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
idGetter: (RegisteredObject) -> Id
|
idGetter: (RegisteredObject) -> Id
|
||||||
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
@@ -1,23 +1,17 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.plus
|
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
import dev.inmo.micro_utils.repos.set
|
|
||||||
import dev.inmo.micro_utils.repos.unset
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
|
|
||||||
open class AutoRecacheWriteCRUDRepo<RegisteredObject, Id, InputObject>(
|
open class AutoRecacheWriteCRUDRepo<RegisteredObject, Id, InputObject>(
|
||||||
protected val originalRepo: WriteCRUDRepo<RegisteredObject, Id, InputObject>,
|
protected val originalRepo: WriteCRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
protected val scope: CoroutineScope,
|
protected val scope: CoroutineScope,
|
||||||
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
protected val kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo(),
|
||||||
protected val idGetter: (RegisteredObject) -> Id
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo {
|
) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo {
|
||||||
override val deletedObjectsIdsFlow: Flow<Id>
|
override val deletedObjectsIdsFlow: Flow<Id>
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.MapKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@@ -10,7 +10,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
open class AutoRecacheKeyValueRepo<Id, RegisteredObject>(
|
open class AutoRecacheKeyValueRepo<Id, RegisteredObject>(
|
||||||
override val originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
override val originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo(),
|
||||||
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
idGetter: (RegisteredObject) -> Id
|
idGetter: (RegisteredObject) -> Id
|
||||||
@@ -29,7 +29,7 @@ open class AutoRecacheKeyValueRepo<Id, RegisteredObject>(
|
|||||||
originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
originalCallTimeoutMillis: Long,
|
originalCallTimeoutMillis: Long,
|
||||||
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo(),
|
||||||
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
idGetter: (RegisteredObject) -> Id
|
idGetter: (RegisteredObject) -> Id
|
||||||
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
@@ -3,8 +3,9 @@ package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
|||||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.MapKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
@@ -18,7 +19,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
|
open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
|
||||||
protected open val originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
protected open val originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
||||||
protected val scope: CoroutineScope,
|
protected val scope: CoroutineScope,
|
||||||
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
protected val kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo(),
|
||||||
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
protected val idGetter: (RegisteredObject) -> Id
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
@@ -35,7 +36,7 @@ open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
|
|||||||
originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
originalCallTimeoutMillis: Long,
|
originalCallTimeoutMillis: Long,
|
||||||
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo(),
|
||||||
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
idGetter: (RegisteredObject) -> Id
|
idGetter: (RegisteredObject) -> Id
|
||||||
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
@@ -1,12 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.plus
|
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
import dev.inmo.micro_utils.repos.set
|
|
||||||
import dev.inmo.micro_utils.repos.unset
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
@@ -14,7 +10,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||||||
open class AutoRecacheWriteKeyValueRepo<Id, RegisteredObject>(
|
open class AutoRecacheWriteKeyValueRepo<Id, RegisteredObject>(
|
||||||
protected val originalRepo: WriteKeyValueRepo<Id, RegisteredObject>,
|
protected val originalRepo: WriteKeyValueRepo<Id, RegisteredObject>,
|
||||||
protected val scope: CoroutineScope,
|
protected val scope: CoroutineScope,
|
||||||
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache()
|
protected val kvCache: KeyValueRepo<Id, RegisteredObject> = MapKeyValueRepo()
|
||||||
) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
override val onValueRemoved: Flow<Id>
|
override val onValueRemoved: Flow<Id>
|
||||||
get() = (originalRepo.onValueRemoved).distinctUntilChanged()
|
get() = (originalRepo.onValueRemoved).distinctUntilChanged()
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.MapKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@@ -10,7 +11,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
open class AutoRecacheKeyValuesRepo<Id, RegisteredObject>(
|
open class AutoRecacheKeyValuesRepo<Id, RegisteredObject>(
|
||||||
override val originalRepo: KeyValuesRepo<Id, RegisteredObject>,
|
override val originalRepo: KeyValuesRepo<Id, RegisteredObject>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
kvCache: KeyValueRepo<Id, List<RegisteredObject>> = MapKeyValueRepo(),
|
||||||
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
actionWrapper: ActionWrapper = ActionWrapper.Direct
|
actionWrapper: ActionWrapper = ActionWrapper.Direct
|
||||||
) : AutoRecacheReadKeyValuesRepo<Id, RegisteredObject> (
|
) : AutoRecacheReadKeyValuesRepo<Id, RegisteredObject> (
|
||||||
@@ -27,7 +28,7 @@ open class AutoRecacheKeyValuesRepo<Id, RegisteredObject>(
|
|||||||
originalRepo: KeyValuesRepo<Id, RegisteredObject>,
|
originalRepo: KeyValuesRepo<Id, RegisteredObject>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
originalCallTimeoutMillis: Long,
|
originalCallTimeoutMillis: Long,
|
||||||
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
kvCache: KeyValueRepo<Id, List<RegisteredObject>> = MapKeyValueRepo(),
|
||||||
recacheDelay: Long = 60.seconds.inWholeMilliseconds
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds
|
||||||
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
|
||||||
|
|
||||||
|
@@ -10,8 +10,9 @@ import dev.inmo.micro_utils.pagination.firstIndex
|
|||||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
import dev.inmo.micro_utils.pagination.utils.paginate
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.MapKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
@@ -25,7 +26,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
open class AutoRecacheReadKeyValuesRepo<Id, RegisteredObject>(
|
open class AutoRecacheReadKeyValuesRepo<Id, RegisteredObject>(
|
||||||
protected open val originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
|
protected open val originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
|
||||||
protected val scope: CoroutineScope,
|
protected val scope: CoroutineScope,
|
||||||
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
protected val kvCache: KeyValueRepo<Id, List<RegisteredObject>> = MapKeyValueRepo(),
|
||||||
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct
|
||||||
) : ReadKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
) : ReadKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
@@ -41,7 +42,7 @@ open class AutoRecacheReadKeyValuesRepo<Id, RegisteredObject>(
|
|||||||
originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
|
originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
originalCallTimeoutMillis: Long,
|
originalCallTimeoutMillis: Long,
|
||||||
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
kvCache: KeyValueRepo<Id, List<RegisteredObject>> = MapKeyValueRepo(),
|
||||||
recacheDelay: Long = 60.seconds.inWholeMilliseconds
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds
|
||||||
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
|
||||||
|
|
||||||
|
@@ -1,15 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.plus
|
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
|
||||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
|
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
|
||||||
import dev.inmo.micro_utils.repos.set
|
|
||||||
import dev.inmo.micro_utils.repos.unset
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
@@ -17,7 +12,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||||||
open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
|
open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
|
||||||
protected val originalRepo: WriteKeyValuesRepo<Id, RegisteredObject>,
|
protected val originalRepo: WriteKeyValuesRepo<Id, RegisteredObject>,
|
||||||
protected val scope: CoroutineScope,
|
protected val scope: CoroutineScope,
|
||||||
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache()
|
protected val kvCache: KeyValueRepo<Id, List<RegisteredObject>> = MapKeyValueRepo()
|
||||||
) : WriteKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
) : WriteKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
override val onValueRemoved: Flow<Pair<Id, RegisteredObject>>
|
override val onValueRemoved: Flow<Pair<Id, RegisteredObject>>
|
||||||
get() = originalRepo.onValueRemoved
|
get() = originalRepo.onValueRemoved
|
||||||
|
@@ -1,38 +1,48 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.full
|
package dev.inmo.micro_utils.repos.cache.full
|
||||||
|
|
||||||
import dev.inmo.micro_utils.common.*
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||||
|
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.*
|
import dev.inmo.micro_utils.repos.cache.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||||
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
protected open val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||||
protected open val kvCache: FullKVCache<IdType, ObjectType>,
|
protected open val kvCache: KeyValueRepo<IdType, ObjectType>,
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker(),
|
||||||
protected open val idGetter: (ObjectType) -> IdType
|
protected open val idGetter: (ObjectType) -> IdType
|
||||||
) : ReadCRUDRepo<ObjectType, IdType>, FullCacheRepo {
|
) : ReadCRUDRepo<ObjectType, IdType>, FullCacheRepo {
|
||||||
protected inline fun <T> doOrTakeAndActualize(
|
protected suspend inline fun <T> doOrTakeAndActualize(
|
||||||
action: FullKVCache<IdType, ObjectType>.() -> Optional<T>,
|
action: KeyValueRepo<IdType, ObjectType>.() -> Optional<T>,
|
||||||
actionElse: ReadCRUDRepo<ObjectType, IdType>.() -> T,
|
actionElse: ReadCRUDRepo<ObjectType, IdType>.() -> T,
|
||||||
actualize: FullKVCache<IdType, ObjectType>.(T) -> Unit
|
actualize: KeyValueRepo<IdType, ObjectType>.(T) -> Unit
|
||||||
): T {
|
): T {
|
||||||
kvCache.action().onPresented {
|
locker.withReadAcquire {
|
||||||
return it
|
kvCache.action().onPresented { return it }
|
||||||
}.onAbsent {
|
}
|
||||||
return parentRepo.actionElse().also {
|
return parentRepo.actionElse().also {
|
||||||
kvCache.actualize(it)
|
kvCache.actualize(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
error("The result should be returned above")
|
|
||||||
}
|
}
|
||||||
|
protected suspend inline fun <T> doOrTakeAndActualizeWithWriteLock(
|
||||||
|
action: KeyValueRepo<IdType, ObjectType>.() -> Optional<T>,
|
||||||
|
actionElse: ReadCRUDRepo<ObjectType, IdType>.() -> T,
|
||||||
|
actualize: KeyValueRepo<IdType, ObjectType>.(T) -> Unit
|
||||||
|
): T = doOrTakeAndActualize(
|
||||||
|
action = action,
|
||||||
|
actionElse = actionElse,
|
||||||
|
actualize = { locker.withWriteLock { actualize(it) } }
|
||||||
|
)
|
||||||
|
|
||||||
protected open suspend fun actualizeAll() {
|
protected open suspend fun actualizeAll() {
|
||||||
kvCache.actualizeAll(parentRepo)
|
locker.withWriteLock { kvCache.actualizeAll(parentRepo) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
|
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
|
||||||
@@ -53,22 +63,22 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
{ if (it != 0L) actualizeAll() }
|
{ if (it != 0L) actualizeAll() }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun contains(id: IdType): Boolean = doOrTakeAndActualize(
|
override suspend fun contains(id: IdType): Boolean = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ contains(id).takeIf { it }.optionalOrAbsentIfNull },
|
{ contains(id).takeIf { it }.optionalOrAbsentIfNull },
|
||||||
{ contains(id) },
|
{ contains(id) },
|
||||||
{ if (it) parentRepo.getById(id) ?.let { set(id, it) } }
|
{ if (it) parentRepo.getById(id) ?.let { kvCache.set(id, it) } }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualize(
|
override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
{ getAll() },
|
{ getAll() },
|
||||||
{ kvCache.actualizeAll(clear = true) { it } }
|
{ kvCache.actualizeAll(clear = true) { it } }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize(
|
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ get(id) ?.optional ?: Optional.absent() },
|
{ get(id) ?.optional ?: Optional.absent() },
|
||||||
{ getById(id) },
|
{ getById(id) },
|
||||||
{ it ?.let { set(idGetter(it), it) } }
|
{ it ?.let { kvCache.set(idGetter(it), it) } }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun invalidate() {
|
override suspend fun invalidate() {
|
||||||
@@ -77,25 +87,29 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
|
||||||
kvCache: FullKVCache<IdType, ObjectType>,
|
kvCache: KeyValueRepo<IdType, ObjectType>,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
idGetter: (ObjectType) -> IdType
|
idGetter: (ObjectType) -> IdType
|
||||||
) = FullReadCRUDCacheRepo(this, kvCache, idGetter)
|
) = FullReadCRUDCacheRepo(this, kvCache, locker, idGetter)
|
||||||
|
|
||||||
open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||||
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
kvCache: FullKVCache<IdType, ObjectType>,
|
kvCache: KeyValueRepo<IdType, ObjectType>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
skipStartInvalidate: Boolean = false,
|
skipStartInvalidate: Boolean = false,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
idGetter: (ObjectType) -> IdType
|
idGetter: (ObjectType) -> IdType
|
||||||
) : FullReadCRUDCacheRepo<ObjectType, IdType>(
|
) : FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||||
parentRepo,
|
parentRepo,
|
||||||
kvCache,
|
kvCache,
|
||||||
|
locker,
|
||||||
idGetter
|
idGetter
|
||||||
),
|
),
|
||||||
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
|
WriteCRUDRepo<ObjectType, IdType, InputValueType> by WriteCRUDCacheRepo(
|
||||||
parentRepo,
|
parentRepo,
|
||||||
kvCache,
|
kvCache,
|
||||||
scope,
|
scope,
|
||||||
|
locker,
|
||||||
idGetter
|
idGetter
|
||||||
),
|
),
|
||||||
CRUDRepo<ObjectType, IdType, InputValueType> {
|
CRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
@@ -111,16 +125,18 @@ open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.fullyCached(
|
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.fullyCached(
|
||||||
kvCache: FullKVCache<IdType, ObjectType> = FullKVCache(),
|
kvCache: KeyValueRepo<IdType, ObjectType> = MapKeyValueRepo(),
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
skipStartInvalidate: Boolean = false,
|
skipStartInvalidate: Boolean = false,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
idGetter: (ObjectType) -> IdType
|
idGetter: (ObjectType) -> IdType
|
||||||
) = FullCRUDCacheRepo(this, kvCache, scope, skipStartInvalidate, idGetter)
|
) = FullCRUDCacheRepo(this, kvCache, scope, skipStartInvalidate, locker, idGetter)
|
||||||
|
|
||||||
@Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope, idGetter)", "dev.inmo.micro_utils.repos.cache.full.fullyCached"))
|
@Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope, idGetter)", "dev.inmo.micro_utils.repos.cache.full.fullyCached"))
|
||||||
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
|
||||||
kvCache: FullKVCache<IdType, ObjectType>,
|
kvCache: KeyValueRepo<IdType, ObjectType>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
skipStartInvalidate: Boolean = false,
|
skipStartInvalidate: Boolean = false,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
idGetter: (ObjectType) -> IdType
|
idGetter: (ObjectType) -> IdType
|
||||||
) = fullyCached(kvCache, scope, skipStartInvalidate, idGetter)
|
) = fullyCached(kvCache, scope, skipStartInvalidate, locker, idGetter)
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.full
|
package dev.inmo.micro_utils.repos.cache.full
|
||||||
|
|
||||||
import dev.inmo.micro_utils.common.*
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||||
|
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import dev.inmo.micro_utils.repos.pagination.getAll
|
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -14,31 +16,41 @@ import kotlinx.coroutines.flow.*
|
|||||||
|
|
||||||
open class FullReadKeyValueCacheRepo<Key,Value>(
|
open class FullReadKeyValueCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||||
protected open val kvCache: FullKVCache<Key, Value>,
|
protected open val kvCache: KeyValueRepo<Key, Value>,
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker()
|
||||||
) : ReadKeyValueRepo<Key, Value>, FullCacheRepo {
|
) : ReadKeyValueRepo<Key, Value>, FullCacheRepo {
|
||||||
protected inline fun <T> doOrTakeAndActualize(
|
protected suspend inline fun <T> doOrTakeAndActualize(
|
||||||
action: FullKVCache<Key, Value>.() -> Optional<T>,
|
action: KeyValueRepo<Key, Value>.() -> Optional<T>,
|
||||||
actionElse: ReadKeyValueRepo<Key, Value>.() -> T,
|
actionElse: ReadKeyValueRepo<Key, Value>.() -> T,
|
||||||
actualize: FullKVCache<Key, Value>.(T) -> Unit
|
actualize: KeyValueRepo<Key, Value>.(T) -> Unit
|
||||||
): T {
|
): T {
|
||||||
kvCache.action().onPresented {
|
locker.withReadAcquire {
|
||||||
return it
|
kvCache.action().onPresented { return it }
|
||||||
}.onAbsent {
|
}
|
||||||
return parentRepo.actionElse().also {
|
return parentRepo.actionElse().also {
|
||||||
kvCache.actualize(it)
|
kvCache.actualize(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
error("The result should be returned above")
|
|
||||||
}
|
}
|
||||||
|
protected suspend inline fun <T> doOrTakeAndActualizeWithWriteLock(
|
||||||
|
action: KeyValueRepo<Key, Value>.() -> Optional<T>,
|
||||||
|
actionElse: ReadKeyValueRepo<Key, Value>.() -> T,
|
||||||
|
actualize: KeyValueRepo<Key, Value>.(T) -> Unit
|
||||||
|
): T = doOrTakeAndActualize(
|
||||||
|
action = action,
|
||||||
|
actionElse = actionElse,
|
||||||
|
actualize = { locker.withWriteLock { actualize(it) } }
|
||||||
|
)
|
||||||
protected open suspend fun actualizeAll() {
|
protected open suspend fun actualizeAll() {
|
||||||
kvCache.clear()
|
locker.withWriteLock {
|
||||||
kvCache.set(parentRepo.getAll { keys(it) }.toMap())
|
kvCache.clear()
|
||||||
|
kvCache.set(parentRepo.getAll())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(k: Key): Value? = doOrTakeAndActualize(
|
override suspend fun get(k: Key): Value? = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ get(k) ?.optional ?: Optional.absent() },
|
{ get(k) ?.optional ?: Optional.absent() },
|
||||||
{ get(k) },
|
{ get(k) },
|
||||||
{ set(k, it ?: return@doOrTakeAndActualize) }
|
{ kvCache.set(k, it ?: return@doOrTakeAndActualizeWithWriteLock) }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = doOrTakeAndActualize(
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = doOrTakeAndActualize(
|
||||||
@@ -53,13 +65,13 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
|
|||||||
{ if (it != 0L) actualizeAll() }
|
{ if (it != 0L) actualizeAll() }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun contains(key: Key): Boolean = doOrTakeAndActualize(
|
override suspend fun contains(key: Key): Boolean = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ contains(key).takeIf { it }.optionalOrAbsentIfNull },
|
{ contains(key).takeIf { it }.optionalOrAbsentIfNull },
|
||||||
{ contains(key) },
|
{ contains(key) },
|
||||||
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
|
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
|
||||||
)
|
)
|
||||||
|
|
||||||
override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualize(
|
override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualizeWithWriteLock(
|
||||||
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
{ getAll() },
|
{ getAll() },
|
||||||
{ kvCache.actualizeAll(clear = true) { it } }
|
{ kvCache.actualizeAll(clear = true) { it } }
|
||||||
@@ -83,37 +95,51 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
||||||
kvCache: FullKVCache<Key, Value>
|
kvCache: KeyValueRepo<Key, Value>,
|
||||||
) = FullReadKeyValueCacheRepo(this, kvCache)
|
locker: SmartRWLocker = SmartRWLocker()
|
||||||
|
) = FullReadKeyValueCacheRepo(this, kvCache, locker)
|
||||||
|
|
||||||
open class FullWriteKeyValueCacheRepo<Key,Value>(
|
open class FullWriteKeyValueCacheRepo<Key,Value>(
|
||||||
parentRepo: WriteKeyValueRepo<Key, Value>,
|
parentRepo: WriteKeyValueRepo<Key, Value>,
|
||||||
protected open val kvCache: FullKVCache<Key, Value>,
|
protected open val kvCache: KeyValueRepo<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker()
|
||||||
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
|
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
|
||||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
protected val onNewJob = parentRepo.onNewValue.onEach {
|
||||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
locker.withWriteLock {
|
||||||
|
kvCache.set(it.first, it.second)
|
||||||
|
}
|
||||||
|
}.launchIn(scope)
|
||||||
|
protected val onRemoveJob = parentRepo.onValueRemoved.onEach {
|
||||||
|
locker.withWriteLock {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
|
}.launchIn(scope)
|
||||||
|
|
||||||
override suspend fun invalidate() {
|
override suspend fun invalidate() {
|
||||||
kvCache.clear()
|
locker.withWriteLock {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
||||||
kvCache: FullKVCache<Key, Value>,
|
kvCache: KeyValueRepo<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
|
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
|
||||||
|
|
||||||
open class FullKeyValueCacheRepo<Key,Value>(
|
open class FullKeyValueCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: KeyValueRepo<Key, Value>,
|
protected open val parentRepo: KeyValueRepo<Key, Value>,
|
||||||
kvCache: FullKVCache<Key, Value>,
|
kvCache: KeyValueRepo<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
skipStartInvalidate: Boolean = false
|
skipStartInvalidate: Boolean = false,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker()
|
||||||
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
|
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
|
||||||
KeyValueRepo<Key,Value>,
|
KeyValueRepo<Key,Value>,
|
||||||
ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(
|
ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(
|
||||||
parentRepo,
|
parentRepo,
|
||||||
kvCache
|
kvCache,
|
||||||
|
locker
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
if (!skipStartInvalidate) {
|
if (!skipStartInvalidate) {
|
||||||
@@ -124,7 +150,9 @@ open class FullKeyValueCacheRepo<Key,Value>(
|
|||||||
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
|
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
|
||||||
|
|
||||||
override suspend fun invalidate() {
|
override suspend fun invalidate() {
|
||||||
kvCache.actualizeAll(parentRepo)
|
locker.withWriteLock {
|
||||||
|
kvCache.actualizeAll(parentRepo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clear() {
|
override suspend fun clear() {
|
||||||
@@ -134,12 +162,16 @@ open class FullKeyValueCacheRepo<Key,Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> KeyValueRepo<Key, Value>.fullyCached(
|
fun <Key, Value> KeyValueRepo<Key, Value>.fullyCached(
|
||||||
kvCache: FullKVCache<Key, Value> = FullKVCache(),
|
kvCache: KeyValueRepo<Key, Value> = MapKeyValueRepo(),
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
) = FullKeyValueCacheRepo(this, kvCache, scope)
|
skipStartInvalidate: Boolean = false,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker()
|
||||||
|
) = FullKeyValueCacheRepo(this, kvCache, scope, skipStartInvalidate, locker)
|
||||||
|
|
||||||
@Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached"))
|
@Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached"))
|
||||||
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
fun <Key, Value> KeyValueRepo<Key, Value>.cached(
|
||||||
kvCache: FullKVCache<Key, Value>,
|
kvCache: KeyValueRepo<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
) = fullyCached(kvCache, scope)
|
skipStartInvalidate: Boolean = false,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker()
|
||||||
|
) = fullyCached(kvCache, scope, skipStartInvalidate, locker)
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.full
|
package dev.inmo.micro_utils.repos.cache.full
|
||||||
|
|
||||||
import dev.inmo.micro_utils.common.*
|
import dev.inmo.micro_utils.common.*
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||||
|
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.*
|
import dev.inmo.micro_utils.pagination.utils.*
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -13,29 +15,41 @@ import kotlinx.coroutines.flow.*
|
|||||||
|
|
||||||
open class FullReadKeyValuesCacheRepo<Key,Value>(
|
open class FullReadKeyValuesCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
protected open val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||||
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
protected open val kvCache: KeyValueRepo<Key, List<Value>>,
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker(),
|
||||||
) : ReadKeyValuesRepo<Key, Value>, FullCacheRepo {
|
) : ReadKeyValuesRepo<Key, Value>, FullCacheRepo {
|
||||||
protected inline fun <T> doOrTakeAndActualize(
|
protected suspend inline fun <T> doOrTakeAndActualize(
|
||||||
action: FullKVCache<Key, List<Value>>.() -> Optional<T>,
|
action: KeyValueRepo<Key, List<Value>>.() -> Optional<T>,
|
||||||
actionElse: ReadKeyValuesRepo<Key, Value>.() -> T,
|
actionElse: ReadKeyValuesRepo<Key, Value>.() -> T,
|
||||||
actualize: FullKVCache<Key, List<Value>>.(T) -> Unit
|
actualize: KeyValueRepo<Key, List<Value>>.(T) -> Unit
|
||||||
): T {
|
): T {
|
||||||
kvCache.action().onPresented {
|
locker.withReadAcquire {
|
||||||
return it
|
kvCache.action().onPresented { return it }
|
||||||
}.onAbsent {
|
}
|
||||||
return parentRepo.actionElse().also {
|
return parentRepo.actionElse().also {
|
||||||
kvCache.actualize(it)
|
kvCache.actualize(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
error("The result should be returned above")
|
|
||||||
}
|
}
|
||||||
|
protected suspend inline fun <T> doOrTakeAndActualizeWithWriteLock(
|
||||||
|
action: KeyValueRepo<Key, List<Value>>.() -> Optional<T>,
|
||||||
|
actionElse: ReadKeyValuesRepo<Key, Value>.() -> T,
|
||||||
|
actualize: KeyValueRepo<Key, List<Value>>.(T) -> Unit
|
||||||
|
): T = doOrTakeAndActualize(
|
||||||
|
action = action,
|
||||||
|
actionElse = actionElse,
|
||||||
|
actualize = { locker.withWriteLock { actualize(it) } }
|
||||||
|
)
|
||||||
|
|
||||||
protected open suspend fun actualizeKey(k: Key) {
|
protected open suspend fun actualizeKey(k: Key) {
|
||||||
kvCache.set(k, parentRepo.getAll(k))
|
locker.withWriteLock {
|
||||||
|
kvCache.set(k, parentRepo.getAll(k))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open suspend fun actualizeAll() {
|
protected open suspend fun actualizeAll() {
|
||||||
kvCache.actualizeAll(parentRepo)
|
locker.withWriteLock {
|
||||||
|
kvCache.actualizeAll(parentRepo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||||
@@ -109,45 +123,55 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
||||||
kvCache: FullKVCache<Key, List<Value>>
|
kvCache: KeyValueRepo<Key, List<Value>>,
|
||||||
) = FullReadKeyValuesCacheRepo(this, kvCache)
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) = FullReadKeyValuesCacheRepo(this, kvCache, locker)
|
||||||
|
|
||||||
open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
||||||
parentRepo: WriteKeyValuesRepo<Key, Value>,
|
parentRepo: WriteKeyValuesRepo<Key, Value>,
|
||||||
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
protected open val kvCache: KeyValueRepo<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker(),
|
||||||
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
|
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
|
||||||
protected val onNewJob = parentRepo.onNewValue.onEach {
|
protected val onNewJob = parentRepo.onNewValue.onEach {
|
||||||
kvCache.set(
|
locker.withWriteLock {
|
||||||
it.first,
|
kvCache.set(
|
||||||
kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)
|
it.first,
|
||||||
)
|
kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)
|
||||||
|
)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach {
|
protected val onRemoveJob = parentRepo.onValueRemoved.onEach {
|
||||||
kvCache.set(
|
locker.withWriteLock {
|
||||||
it.first,
|
kvCache.set(
|
||||||
kvCache.get(it.first) ?.minus(it.second) ?: return@onEach
|
it.first,
|
||||||
)
|
kvCache.get(it.first)?.minus(it.second) ?: return@onEach
|
||||||
|
)
|
||||||
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
override suspend fun invalidate() {
|
override suspend fun invalidate() {
|
||||||
kvCache.clear()
|
locker.withWriteLock {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
||||||
kvCache: FullKVCache<Key, List<Value>>,
|
kvCache: KeyValueRepo<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope, locker)
|
||||||
|
|
||||||
open class FullKeyValuesCacheRepo<Key,Value>(
|
open class FullKeyValuesCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: KeyValuesRepo<Key, Value>,
|
protected open val parentRepo: KeyValuesRepo<Key, Value>,
|
||||||
kvCache: FullKVCache<Key, List<Value>>,
|
kvCache: KeyValueRepo<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
skipStartInvalidate: Boolean = false
|
skipStartInvalidate: Boolean = false,
|
||||||
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope, locker),
|
||||||
KeyValuesRepo<Key, Value>,
|
KeyValuesRepo<Key, Value>,
|
||||||
ReadKeyValuesRepo<Key, Value> by FullReadKeyValuesCacheRepo(parentRepo, kvCache) {
|
ReadKeyValuesRepo<Key, Value> by FullReadKeyValuesCacheRepo(parentRepo, kvCache, locker) {
|
||||||
init {
|
init {
|
||||||
if (!skipStartInvalidate) {
|
if (!skipStartInvalidate) {
|
||||||
scope.launchSafelyWithoutExceptions { invalidate() }
|
scope.launchSafelyWithoutExceptions { invalidate() }
|
||||||
@@ -163,7 +187,9 @@ open class FullKeyValuesCacheRepo<Key,Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun invalidate() {
|
override suspend fun invalidate() {
|
||||||
kvCache.actualizeAll(parentRepo)
|
locker.withWriteLock {
|
||||||
|
kvCache.actualizeAll(parentRepo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun removeWithValue(v: Value) {
|
override suspend fun removeWithValue(v: Value) {
|
||||||
@@ -172,12 +198,16 @@ open class FullKeyValuesCacheRepo<Key,Value>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <Key, Value> KeyValuesRepo<Key, Value>.fullyCached(
|
fun <Key, Value> KeyValuesRepo<Key, Value>.fullyCached(
|
||||||
kvCache: FullKVCache<Key, List<Value>> = FullKVCache(),
|
kvCache: KeyValueRepo<Key, List<Value>> = MapKeyValueRepo(),
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
) = FullKeyValuesCacheRepo(this, kvCache, scope)
|
skipStartInvalidate: Boolean = false,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) = FullKeyValuesCacheRepo(this, kvCache, scope, skipStartInvalidate, locker)
|
||||||
|
|
||||||
@Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached"))
|
@Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached"))
|
||||||
fun <Key, Value> KeyValuesRepo<Key, Value>.caching(
|
fun <Key, Value> KeyValuesRepo<Key, Value>.caching(
|
||||||
kvCache: FullKVCache<Key, List<Value>>,
|
kvCache: KeyValueRepo<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
) = FullKeyValuesCacheRepo(this, kvCache, scope)
|
skipStartInvalidate: Boolean = false,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
|
) = FullKeyValuesCacheRepo(this, kvCache, scope, skipStartInvalidate, locker)
|
||||||
|
@@ -1,16 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.repos.cache.util
|
package dev.inmo.micro_utils.repos.cache.util
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
|
|
||||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
|
||||||
import dev.inmo.micro_utils.repos.pagination.getAll
|
|
||||||
import dev.inmo.micro_utils.repos.set
|
|
||||||
|
|
||||||
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
|
||||||
clear: Boolean = true,
|
clear: Boolean = true,
|
||||||
getAll: () -> Map<K, V>
|
getAll: () -> Map<K, V>
|
||||||
) {
|
) {
|
||||||
@@ -23,7 +15,7 @@ suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
|
||||||
repo: ReadKeyValueRepo<K, V>,
|
repo: ReadKeyValueRepo<K, V>,
|
||||||
clear: Boolean = true,
|
clear: Boolean = true,
|
||||||
) {
|
) {
|
||||||
@@ -32,7 +24,7 @@ suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
|
suspend inline fun <K, V> KeyValueRepo<K, List<V>>.actualizeAll(
|
||||||
repo: ReadKeyValuesRepo<K, V>,
|
repo: ReadKeyValuesRepo<K, V>,
|
||||||
clear: Boolean = true,
|
clear: Boolean = true,
|
||||||
) {
|
) {
|
||||||
@@ -41,7 +33,7 @@ suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
|
||||||
repo: ReadCRUDRepo<V, K>,
|
repo: ReadCRUDRepo<V, K>,
|
||||||
clear: Boolean = true,
|
clear: Boolean = true,
|
||||||
) {
|
) {
|
||||||
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.repos.common"/>
|
<manifest/>
|
@@ -52,7 +52,9 @@ class KeyValueStore<T : Any> internal constructor (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sp: SharedPreferences, key: String) {
|
override fun onSharedPreferenceChanged(sp: SharedPreferences?, key: String?) {
|
||||||
|
sp ?: return
|
||||||
|
key ?: return
|
||||||
val value = sp.all[key]
|
val value = sp.all[key]
|
||||||
cachedData ?: return
|
cachedData ?: return
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.exposed.*
|
import dev.inmo.micro_utils.repos.exposed.*
|
||||||
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
|
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
|
abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
|
||||||
@@ -73,4 +71,26 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
|
|||||||
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
|
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
|
||||||
select { selectById(k).and(selectByValue(v)) }.limit(1).any()
|
select { selectById(k).and(selectByValue(v)) }.limit(1).any()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getAll(reverseLists: Boolean): Map<Key, List<Value>> = transaction(database) {
|
||||||
|
val query = if (reverseLists) {
|
||||||
|
selectAll().orderBy(keyColumn, SortOrder.DESC)
|
||||||
|
} else {
|
||||||
|
selectAll()
|
||||||
|
}
|
||||||
|
query.asSequence().map { it.asKey to it.asObject }.groupBy { it.first }.mapValues {
|
||||||
|
it.value.map { it.second }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAll(k: Key, reverseLists: Boolean): List<Value> = transaction(database) {
|
||||||
|
val query = if (reverseLists) {
|
||||||
|
select { selectById(k) }.orderBy(keyColumn, SortOrder.DESC)
|
||||||
|
} else {
|
||||||
|
select { selectById(k) }
|
||||||
|
}
|
||||||
|
query.map {
|
||||||
|
it.asObject
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -16,6 +16,6 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ plugins {
|
|||||||
id "com.google.devtools.ksp"
|
id "com.google.devtools.ksp"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.repos.generator.test"/>
|
<manifest/>
|
||||||
|
@@ -4,7 +4,7 @@ plugins {
|
|||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.micro_utils.repos.inmemory"/>
|
<manifest/>
|
@@ -1,64 +1,100 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
|
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||||
|
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Map]-based [ReadMapCRUDRepo]. All internal operations will be locked with [locker] (mostly with
|
||||||
|
* [SmartRWLocker.withReadAcquire])
|
||||||
|
*
|
||||||
|
* **Warning**: It is not recommended to use constructor with both [Map] and [SmartRWLocker]. Besides, in case
|
||||||
|
* you are using your own [Map] as a [map] you should be careful with operations on this [map]
|
||||||
|
*/
|
||||||
class ReadMapCRUDRepo<ObjectType, IdType>(
|
class ReadMapCRUDRepo<ObjectType, IdType>(
|
||||||
private val map: Map<IdType, ObjectType> = emptyMap()
|
private val map: Map<IdType, ObjectType> = emptyMap(),
|
||||||
|
private val locker: SmartRWLocker = SmartRWLocker()
|
||||||
) : ReadCRUDRepo<ObjectType, IdType> {
|
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> {
|
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> {
|
||||||
return map.keys.drop(pagination.firstIndex).take(pagination.size).mapNotNull {
|
return locker.withReadAcquire {
|
||||||
map[it]
|
map.keys.drop(pagination.firstIndex).take(pagination.size).mapNotNull {
|
||||||
}.createPaginationResult(
|
map[it]
|
||||||
pagination,
|
}.createPaginationResult(
|
||||||
count()
|
pagination,
|
||||||
)
|
count()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> {
|
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> {
|
||||||
return map.keys.drop(pagination.firstIndex).take(pagination.size).createPaginationResult(
|
return locker.withReadAcquire {
|
||||||
pagination,
|
map.keys.drop(pagination.firstIndex).take(pagination.size).createPaginationResult(
|
||||||
count()
|
pagination,
|
||||||
)
|
count()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getById(id: IdType): ObjectType? = map[id]
|
override suspend fun getById(id: IdType): ObjectType? = locker.withReadAcquire {
|
||||||
|
map[id]
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun contains(id: IdType): Boolean = map.containsKey(id)
|
override suspend fun contains(id: IdType): Boolean = locker.withReadAcquire {
|
||||||
|
map.containsKey(id)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getAll(): Map<IdType, ObjectType> = map.toMap()
|
override suspend fun getAll(): Map<IdType, ObjectType> = locker.withReadAcquire {
|
||||||
|
map.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun count(): Long = map.size.toLong()
|
override suspend fun count(): Long = locker.withReadAcquire {
|
||||||
|
map.size.toLong()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MutableMap]-based [WriteMapCRUDRepo]. All internal operations will be locked with [locker] (mostly with
|
||||||
|
* [SmartRWLocker.withWriteLock])
|
||||||
|
*
|
||||||
|
* **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case
|
||||||
|
* you are using your own [MutableMap] as a [map] you should be careful with operations on this [map]
|
||||||
|
*/
|
||||||
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf()
|
protected val map: MutableMap<IdType, ObjectType> = mutableMapOf(),
|
||||||
|
protected val locker: SmartRWLocker = SmartRWLocker()
|
||||||
) : WriteCRUDRepo<ObjectType, IdType, InputValueType> {
|
) : WriteCRUDRepo<ObjectType, IdType, InputValueType> {
|
||||||
protected val _newObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected open val _newObjectsFlow: MutableSharedFlow<ObjectType> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow()
|
override val newObjectsFlow: Flow<ObjectType> by lazy { _newObjectsFlow.asSharedFlow() }
|
||||||
protected val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MutableSharedFlow()
|
protected open val _updatedObjectsFlow: MutableSharedFlow<ObjectType> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow()
|
override val updatedObjectsFlow: Flow<ObjectType> by lazy { _updatedObjectsFlow.asSharedFlow() }
|
||||||
protected val _deletedObjectsIdsFlow: MutableSharedFlow<IdType> = MutableSharedFlow()
|
protected open val _deletedObjectsIdsFlow: MutableSharedFlow<IdType> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow()
|
override val deletedObjectsIdsFlow: Flow<IdType> by lazy { _deletedObjectsIdsFlow.asSharedFlow() }
|
||||||
|
|
||||||
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
protected abstract suspend fun updateObject(newValue: InputValueType, id: IdType, old: ObjectType): ObjectType
|
||||||
protected abstract suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType>
|
protected abstract suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType>
|
||||||
|
|
||||||
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||||
return values.map {
|
return locker.withWriteLock {
|
||||||
val (id, newObject) = createObject(it)
|
values.map {
|
||||||
map[id] = newObject
|
val (id, newObject) = createObject(it)
|
||||||
newObject.also { _ ->
|
map[id] = newObject
|
||||||
_newObjectsFlow.emit(newObject)
|
newObject
|
||||||
}
|
}
|
||||||
|
}.onEach {
|
||||||
|
_newObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
||||||
val newValue = updateObject(value, id, map[id] ?: return null)
|
return locker.withWriteLock {
|
||||||
|
val newValue = updateObject(value, id, map[id] ?: return@withWriteLock null)
|
||||||
|
|
||||||
return newValue.also {
|
newValue.also {
|
||||||
map[id] = it
|
map[id] = it
|
||||||
|
}
|
||||||
|
} ?.also {
|
||||||
_updatedObjectsFlow.emit(it)
|
_updatedObjectsFlow.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,24 +104,40 @@ abstract class WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteById(ids: List<IdType>) {
|
override suspend fun deleteById(ids: List<IdType>) {
|
||||||
ids.forEach {
|
locker.withWriteLock {
|
||||||
map.remove(it) ?.also { _ -> _deletedObjectsIdsFlow.emit(it) }
|
ids.mapNotNull {
|
||||||
}
|
it.takeIf { map.remove(it) != null }
|
||||||
|
}
|
||||||
|
}.onEach { _deletedObjectsIdsFlow.emit(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MutableMap]-based [MapCRUDRepo]. All internal operations will be locked with [locker]
|
||||||
|
*
|
||||||
|
* **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case
|
||||||
|
* you are using your own [MutableMap] as a [map] you should be careful with operations on this [map]
|
||||||
|
*/
|
||||||
abstract class MapCRUDRepo<ObjectType, IdType, InputValueType>(
|
abstract class MapCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||||
map: MutableMap<IdType, ObjectType>
|
map: MutableMap<IdType, ObjectType>,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker()
|
||||||
) : CRUDRepo<ObjectType, IdType, InputValueType>,
|
) : CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||||
ReadCRUDRepo<ObjectType, IdType> by ReadMapCRUDRepo(map),
|
ReadCRUDRepo<ObjectType, IdType> by ReadMapCRUDRepo(map, locker),
|
||||||
WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(map)
|
WriteMapCRUDRepo<ObjectType, IdType, InputValueType>(map, locker)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MutableMap]-based [MapCRUDRepo]. All internal operations will be locked with [locker]
|
||||||
|
*
|
||||||
|
* **Warning**: Besides, in case you are using your own [MutableMap] as a [map] you should be careful with operations
|
||||||
|
* on this [map]
|
||||||
|
*/
|
||||||
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
||||||
map: MutableMap<IdType, ObjectType>,
|
map: MutableMap<IdType, ObjectType>,
|
||||||
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||||
) = object : MapCRUDRepo<ObjectType, IdType, InputValueType>(map) {
|
) = object : MapCRUDRepo<ObjectType, IdType, InputValueType>(map, locker) {
|
||||||
override suspend fun updateObject(
|
override suspend fun updateObject(
|
||||||
newValue: InputValueType,
|
newValue: InputValueType,
|
||||||
id: IdType,
|
id: IdType,
|
||||||
@@ -95,12 +147,23 @@ fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
|||||||
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = map.createCallback(newValue)
|
override suspend fun createObject(newValue: InputValueType): Pair<IdType, ObjectType> = map.createCallback(newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MutableMap]-based [MapCRUDRepo]. All internal operations will be locked with [locker]
|
||||||
|
*/
|
||||||
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
fun <ObjectType, IdType, InputValueType> MapCRUDRepo(
|
||||||
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||||
) = MapCRUDRepo(mutableMapOf(), updateCallback, createCallback)
|
) = MapCRUDRepo(mutableMapOf(), updateCallback, locker, createCallback)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MutableMap]-based [MapCRUDRepo]. All internal operations will be locked with [locker]
|
||||||
|
*
|
||||||
|
* **Warning**: Besides, in case you are using your own [MutableMap] as a [this] you should be careful with operations
|
||||||
|
* on this [this]
|
||||||
|
*/
|
||||||
fun <ObjectType, IdType, InputValueType> MutableMap<IdType, ObjectType>.asCrudRepo(
|
fun <ObjectType, IdType, InputValueType> MutableMap<IdType, ObjectType>.asCrudRepo(
|
||||||
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
updateCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType, id: IdType, old: ObjectType) -> ObjectType,
|
||||||
|
locker: SmartRWLocker = SmartRWLocker(),
|
||||||
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
createCallback: suspend MutableMap<IdType, ObjectType>.(newValue: InputValueType) -> Pair<IdType, ObjectType>
|
||||||
) = MapCRUDRepo(this, updateCallback, createCallback)
|
) = MapCRUDRepo(this, updateCallback, locker, createCallback)
|
||||||
|
@@ -1,103 +1,152 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
|
import dev.inmo.micro_utils.coroutines.withReadAcquire
|
||||||
|
import dev.inmo.micro_utils.coroutines.withWriteLock
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import dev.inmo.micro_utils.pagination.utils.paginate
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Map]-based [ReadKeyValueRepo]. All internal operations will be locked with [locker] (mostly with
|
||||||
|
* [SmartRWLocker.withReadAcquire])
|
||||||
|
*
|
||||||
|
* **Warning**: It is not recommended to use constructor with both [Map] and [SmartRWLocker]. Besides, in case you are
|
||||||
|
* using your own [Map] as a [map] you should be careful with operations on this [map]
|
||||||
|
*/
|
||||||
class ReadMapKeyValueRepo<Key, Value>(
|
class ReadMapKeyValueRepo<Key, Value>(
|
||||||
protected val map: Map<Key, Value> = emptyMap()
|
protected val map: Map<Key, Value>,
|
||||||
|
private val locker: SmartRWLocker
|
||||||
) : ReadKeyValueRepo<Key, Value> {
|
) : ReadKeyValueRepo<Key, Value> {
|
||||||
override suspend fun get(k: Key): Value? = map[k]
|
constructor(map: Map<Key, Value> = emptyMap()) : this(map, SmartRWLocker())
|
||||||
|
|
||||||
|
override suspend fun get(k: Key): Value? = locker.withReadAcquire { map[k] }
|
||||||
|
|
||||||
override suspend fun values(
|
override suspend fun values(
|
||||||
pagination: Pagination,
|
pagination: Pagination,
|
||||||
reversed: Boolean
|
reversed: Boolean
|
||||||
): PaginationResult<Value> {
|
): PaginationResult<Value> {
|
||||||
val values = map.values
|
return locker.withReadAcquire {
|
||||||
val actualPagination = if (reversed) pagination.reverse(values.size) else pagination
|
val values = map.values
|
||||||
return values.paginate(actualPagination).let {
|
val actualPagination = if (reversed) pagination.reverse(values.size) else pagination
|
||||||
if (reversed) {
|
values.paginate(actualPagination).let {
|
||||||
it.copy(results = it.results.reversed())
|
if (reversed) {
|
||||||
} else {
|
it.copy(results = it.results.reversed())
|
||||||
it
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
val keys = map.keys
|
return locker.withReadAcquire {
|
||||||
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
val keys = map.keys
|
||||||
return keys.paginate(actualPagination).let {
|
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
||||||
if (reversed) {
|
keys.paginate(actualPagination).let {
|
||||||
it.copy(results = it.results.reversed())
|
if (reversed) {
|
||||||
} else {
|
it.copy(results = it.results.reversed())
|
||||||
it
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
val keys: List<Key> = map.mapNotNull { (k, value) -> if (v == value) k else null }
|
return locker.withReadAcquire {
|
||||||
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
val keys: List<Key> = map.mapNotNull { (k, value) -> if (v == value) k else null }
|
||||||
return keys.paginate(actualPagination).let {
|
val actualPagination = if (reversed) pagination.reverse(keys.size) else pagination
|
||||||
if (reversed) {
|
keys.paginate(actualPagination).let {
|
||||||
it.copy(results = it.results.reversed())
|
if (reversed) {
|
||||||
} else {
|
it.copy(results = it.results.reversed())
|
||||||
it
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAll(): Map<Key, Value> = map.toMap()
|
override suspend fun getAll(): Map<Key, Value> = locker.withReadAcquire { map.toMap() }
|
||||||
|
|
||||||
override suspend fun contains(key: Key): Boolean = map.containsKey(key)
|
override suspend fun contains(key: Key): Boolean = locker.withReadAcquire { map.containsKey(key) }
|
||||||
|
|
||||||
override suspend fun count(): Long = map.size.toLong()
|
override suspend fun count(): Long = locker.withReadAcquire { map.size.toLong() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MutableMap]-based [WriteKeyValueRepo]. All internal operations will be locked with [locker] (mostly with
|
||||||
|
* [SmartRWLocker.withWriteLock])
|
||||||
|
*
|
||||||
|
* **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case
|
||||||
|
* you are using your own [MutableMap] as a [map] you should be careful with operations on this [map]
|
||||||
|
*/
|
||||||
class WriteMapKeyValueRepo<Key, Value>(
|
class WriteMapKeyValueRepo<Key, Value>(
|
||||||
private val map: MutableMap<Key, Value> = mutableMapOf()
|
private val map: MutableMap<Key, Value>,
|
||||||
|
private val locker: SmartRWLocker
|
||||||
) : WriteKeyValueRepo<Key, Value> {
|
) : WriteKeyValueRepo<Key, Value> {
|
||||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MapsReposDefaultMutableSharedFlow()
|
||||||
override val onNewValue: Flow<Pair<Key, Value>>
|
override val onNewValue: Flow<Pair<Key, Value>> by lazy {
|
||||||
get() = _onNewValue
|
_onNewValue.asSharedFlow()
|
||||||
private val _onValueRemoved: MutableSharedFlow<Key> = MutableSharedFlow()
|
}
|
||||||
override val onValueRemoved: Flow<Key>
|
private val _onValueRemoved: MutableSharedFlow<Key> = MapsReposDefaultMutableSharedFlow()
|
||||||
get() = _onValueRemoved
|
override val onValueRemoved: Flow<Key> by lazy {
|
||||||
|
_onValueRemoved.asSharedFlow()
|
||||||
|
}
|
||||||
|
constructor(map: MutableMap<Key, Value> = mutableMapOf()) : this(map, SmartRWLocker())
|
||||||
|
|
||||||
override suspend fun set(toSet: Map<Key, Value>) {
|
override suspend fun set(toSet: Map<Key, Value>) {
|
||||||
map.putAll(toSet)
|
locker.withWriteLock { map.putAll(toSet) }
|
||||||
toSet.forEach { (k, v) -> _onNewValue.emit(k to v) }
|
toSet.forEach { (k, v) -> _onNewValue.emit(k to v) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unset(toUnset: List<Key>) {
|
override suspend fun unset(toUnset: List<Key>) {
|
||||||
toUnset.forEach { k ->
|
locker.withWriteLock {
|
||||||
map.remove(k) ?.also { _ -> _onValueRemoved.emit(k) }
|
toUnset.mapNotNull { k ->
|
||||||
}
|
map.remove(k) ?.let { _ -> k }
|
||||||
|
}
|
||||||
|
}.forEach { _onValueRemoved.emit(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
override suspend fun unsetWithValues(toUnset: List<Value>) {
|
||||||
map.mapNotNull { (k, v) ->
|
locker.withWriteLock {
|
||||||
k.takeIf { v in toUnset }
|
map.mapNotNull { (k, v) ->
|
||||||
}.forEach {
|
k.takeIf { v in toUnset }
|
||||||
map.remove(it)
|
}.forEach {
|
||||||
_onValueRemoved.emit(it)
|
map.remove(it)
|
||||||
|
_onValueRemoved.emit(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [MutableMap]-based [KeyValueRepo]. All internal operations will be locked with [locker]
|
||||||
|
*
|
||||||
|
* **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case
|
||||||
|
* you are using your own [MutableMap] as a [map] you should be careful with operations on this [map]
|
||||||
|
*/
|
||||||
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
|
||||||
class MapKeyValueRepo<Key, Value>(
|
class MapKeyValueRepo<Key, Value>(
|
||||||
private val map: MutableMap<Key, Value> = mutableMapOf()
|
private val map: MutableMap<Key, Value>,
|
||||||
|
private val locker: SmartRWLocker
|
||||||
) : KeyValueRepo<Key, Value>,
|
) : KeyValueRepo<Key, Value>,
|
||||||
ReadKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map),
|
ReadKeyValueRepo<Key, Value> by ReadMapKeyValueRepo(map, locker),
|
||||||
WriteKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map) {
|
WriteKeyValueRepo<Key, Value> by WriteMapKeyValueRepo(map, locker) {
|
||||||
|
constructor(map: MutableMap<Key, Value> = mutableMapOf()) : this(map, SmartRWLocker())
|
||||||
override suspend fun clear() {
|
override suspend fun clear() {
|
||||||
map.clear()
|
locker.withWriteLock { map.clear() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <K, V> MutableMap<K, V>.asKeyValueRepo(): KeyValueRepo<K, V> = MapKeyValueRepo(this)
|
/**
|
||||||
|
* [MutableMap]-based [KeyValueRepo]. All internal operations will be locked with [locker]
|
||||||
|
*
|
||||||
|
* **Warning**: It is not recommended to use constructor with both [MutableMap] and [SmartRWLocker]. Besides, in case
|
||||||
|
* you are using your own [MutableMap] as a [this] you should be careful with operations on this [this]
|
||||||
|
*/
|
||||||
|
fun <K, V> MutableMap<K, V>.asKeyValueRepo(locker: SmartRWLocker = SmartRWLocker()): KeyValueRepo<K, V> = MapKeyValueRepo(this, locker)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user