Compare commits

...

64 Commits

Author SHA1 Message Date
5b070a8478 add small kdoc for map repos 2023-08-24 17:15:29 +06:00
8a61193500 update korlibs 2023-08-24 14:29:15 +06:00
fad522b8fe Update CHANGELOG.md 2023-08-24 03:00:08 +06:00
0acac205af fix build 2023-08-24 02:17:27 +06:00
069d4c61b7 update android target 2023-08-23 14:52:09 +06:00
7c113f5700 Update libs.versions.toml 2023-08-23 13:27:23 +06:00
dfdaf4225b start 0.20.2 and all main repos uses 'SmartRWLocker' 2023-08-23 03:26:54 +06:00
bd39ab2467 Merge pull request #295 from InsanusMokrassar/0.20.1
0.20.1
2023-08-12 22:47:03 +06:00
6ce1eb3f2d add tests for smartrwlocker 2023-08-12 22:37:35 +06:00
ce7d4fe9a2 fix of #293 2023-08-12 22:08:12 +06:00
a65bb2f419 add number picker, set picker and small text field 2023-08-11 01:51:40 +06:00
cbc868448b start 0.20.1 2023-08-11 00:58:09 +06:00
9c336a0b56 Update CHANGELOG.md 2023-08-09 02:43:53 +06:00
0f0d09399e Update CHANGELOG.md 2023-08-09 02:42:28 +06:00
e13a1162a9 Merge pull request #294 from InsanusMokrassar/0.20.0
0.20.0
2023-08-09 00:32:03 +06:00
57ebed903f done migration 2023-08-08 18:45:36 +06:00
4478193d8a update dependencies 2023-08-05 13:49:45 +06:00
ee948395e3 start 0.20.0 2023-08-02 13:03:40 +06:00
0616b051ae Merge pull request #291 from InsanusMokrassar/0.19.9
0.19.9
2023-07-29 17:37:38 +06:00
4d155d0505 update koin 2023-07-29 16:37:25 +06:00
a169e733d9 fill changelog 2023-07-29 16:31:50 +06:00
f081e237c8 improvements in startup plugin 2023-07-29 16:30:17 +06:00
f412d387fa start 0.19.9 2023-07-29 16:05:24 +06:00
67354b43e2 Merge pull request #287 from InsanusMokrassar/0.19.8
0.19.8
2023-07-27 01:33:49 +06:00
39135a4000 skipStartInvalidate 2023-07-27 00:35:01 +06:00
eaa014cebd update dependencies 2023-07-27 00:30:26 +06:00
856e657f81 fixes in KeyValueRepo.clear 2023-07-23 13:47:20 +06:00
3a609e5b66 start 0.19.8 2023-07-23 13:26:39 +06:00
d0022dd599 Merge pull request #281 from InsanusMokrassar/0.19.7
0.19.7
2023-06-30 14:37:36 +06:00
7ba6eed453 Update CHANGELOG.md 2023-06-30 12:04:42 +06:00
beeb6ecc0a Update CHANGELOG.md 2023-06-30 12:03:17 +06:00
7cdc17a714 Update libs.versions.toml 2023-06-30 12:02:20 +06:00
4765a950a9 start 0.19.7 2023-06-30 12:01:21 +06:00
65e8137e08 Merge pull request #279 from InsanusMokrassar/0.19.6
0.19.6
2023-06-29 20:27:19 +06:00
ae546dd9ad update dependencies 2023-06-29 18:38:58 +06:00
8110c42be0 update compose 2023-06-27 16:53:10 +06:00
bd2b5ae5fc add versions plugin 2023-06-27 16:46:45 +06:00
2ddfffa6a9 start 0.19.6 2023-06-27 16:46:45 +06:00
a4b54e861d Merge pull request #277 from InsanusMokrassar/0.19.5
0.19.5
2023-06-20 20:22:33 +06:00
c6785f1a4f fixes in types generation 2023-06-20 20:20:47 +06:00
83fe621c56 start 0.19.5 2023-06-20 20:16:24 +06:00
b3a93e17eb Merge pull request #275 from InsanusMokrassar/0.19.4
0.19.4
2023-06-19 16:25:43 +06:00
546a391af3 dependencies update 2023-06-19 16:19:14 +06:00
786cf9bd8b fix in koin generator 2023-06-19 13:52:41 +06:00
dfd6fe062d start 0.19.4 2023-06-19 13:45:29 +06:00
b6ef818613 Merge pull request #274 from InsanusMokrassar/0.19.3
0.19.3
2023-06-19 08:45:45 +06:00
b0f9e9c30a generic-oriented koin definitions 2023-06-19 00:32:45 +06:00
7e5c88ddc3 koin generator improvements 2023-06-18 23:38:36 +06:00
9824c3e00f start 0.19.3 2023-06-18 23:11:50 +06:00
9171d5ed11 update dokka version and return js kdocs 2023-06-09 12:01:11 +06:00
a1830ebb82 Merge pull request #269 from InsanusMokrassar/0.19.2
0.19.2
2023-06-02 13:02:38 +06:00
22d2a3d9bf Update CHANGELOG.md 2023-06-02 12:47:45 +06:00
c9b97fc965 Update libs.versions.toml 2023-06-02 12:46:56 +06:00
51f85becd5 Merge pull request #267 from InsanusMokrassar/0.19.2
0.19.2
2023-06-02 12:39:27 +06:00
a8a281cfb4 downgrade kotlin-poet version 2023-06-02 12:19:02 +06:00
ddd1304949 update dependencies 2023-06-02 12:17:21 +06:00
86d70b6c02 start 0.19.2 2023-06-02 12:11:49 +06:00
a22bdb39e7 Merge pull request #261 from InsanusMokrassar/0.19.1
update kotlin poet (0.19.1)
2023-05-29 17:35:29 +06:00
7ae4d5ef95 update kotlin poet 2023-05-29 17:31:40 +06:00
a2038cbefa Merge pull request #259 from InsanusMokrassar/0.19.1
0.19.1
2023-05-29 17:28:19 +06:00
992091eade Update CHANGELOG.md 2023-05-29 17:27:57 +06:00
e3bfead0c5 Update libs.versions.toml 2023-05-29 10:15:19 +06:00
0de96141fd start 0.19.1 2023-05-29 10:14:35 +06:00
fa18d15c3c Merge pull request #257 from InsanusMokrassar/0.19.0
0.19.0
2023-05-26 20:17:30 +06:00
138 changed files with 2160 additions and 503 deletions

View File

@@ -1,9 +1,110 @@
# Changelog # Changelog
## 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
* `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
* `Versions`:
* `Koin`: `3.4.2` -> `3.4.3`
* `Startup`:
* Now it is possible to start application in synchronous way
## 0.19.8
* `Versions`:
* `Coroutines`: `1.7.2` -> `1.7.3`
* `Kotlin`: `1.8.20` -> `1.8.22`
* `Compose`: `1.4.1` -> `1.4.3`
* `Okio`: `3.3.0` -> `3.4.0`
* `RecyclerView`: `1.3.0` -> `1.3.1`
* `Fragment`: `1.6.0` -> `1.6.1`
* `Repos`:
* Fixes In `KeyValueRepo.clear()` of almost all inheritors of `KeyValueRepo`
* `Cache`:
* All full caches got `skipStartInvalidate` property. By default, this property is `false` and fully caching repos
will be automatically invalidated on start of their work
## 0.19.7
* `Versions`:
* `Coroutines`: `1.7.1` -> `1.7.2`
## 0.19.6
* `Versions`:
* `Coroutines`: `1.6.4` -> `1.7.1`
* `Ktor`: `2.3.1` -> `2.3.2`
* `Compose`: `1.4.0` -> `1.4.1`
## 0.19.5
* `Repos`:
* `Generator`:
* Fixes in new type generation
## 0.19.4
* `Versions`:
* `Koin`: `3.4.1` -> `3.4.2`
* `Android Fragments`: `1.5.7` -> `1.6.0`
* `Koin`
* `Generator`
* Fixes in new generic generator part
## 0.19.3
* `Koin`
* `Generator`
* New getter methods now available with opportunity to use parameters
* Old notation `*Single` and `*Factory` is deprecated since this release. With old
will be generated new `single*` and `factory*` notations for new generations
* Add opportunity to use generic-oriented koin definitions
## 0.19.2
* `Versions`:
* `Ktor`: `2.3.0` -> `2.3.1`
* `Koin`: `3.4.0` -> `3.4.1`
* `Uuid`: `0.7.0` -> `0.7.1`
## 0.19.1
* `Versions`:
* `Korlibs`: `4.0.1` -> `4.0.3`
* `Kotlin Poet`: `1.13.2` -> `1.14.0`
## 0.19.0 ## 0.19.0
* `Versions`: * `Versions`:
* `Korlibs`: `4.0.1` * `Korlibs`: `3.4.0` -> `4.0.1`
## 0.18.4 ## 0.18.4

View 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")
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.android.pickers"/>

View 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,
)
}
}

View 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)
}
}
}

View 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)
}
}
}

View 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
}
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.micro_utils.android.smalltextfield"/>

View File

@@ -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
}
)
)
}

View File

@@ -17,6 +17,10 @@ buildscript {
} }
} }
plugins {
alias(libs.plugins.versions)
}
allprojects { allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
@@ -38,3 +42,4 @@ allprojects {
apply from: "./extensions.gradle" apply from: "./extensions.gradle"
apply from: "./github_release.gradle" apply from: "./github_release.gradle"
apply from: "./versions_plugin_setup.gradle"

View File

@@ -1,12 +1,10 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar import kotlinx.cinterop.*
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf import platform.posix.snprintf
import platform.posix.sprintf import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float { actual fun Float.fixed(signs: Int): Float {
return memScoped { return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
@@ -16,6 +14,7 @@ actual fun Float.fixed(signs: Int): Float {
} }
} }
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double { actual fun Double.fixed(signs: Int): Double {
return memScoped { return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)

View File

@@ -1,12 +1,10 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar import kotlinx.cinterop.*
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf import platform.posix.snprintf
import platform.posix.sprintf import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float { actual fun Float.fixed(signs: Int): Float {
return memScoped { return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
@@ -16,6 +14,7 @@ actual fun Float.fixed(signs: Int): Float {
} }
} }
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double { actual fun Double.fixed(signs: Int): Double {
return memScoped { return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)

View File

@@ -0,0 +1,102 @@
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.withLock {
_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.waitRelease(readPermits)
}
/**
* Unlock [writeMutex]
*/
suspend fun unlockWrite(): Boolean {
return _writeMutex.unlock()
}
}
/**
* 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()

View File

@@ -0,0 +1,142 @@
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 [_permitsStateFlow]
*/
class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
private val _permitsStateFlow = MutableStateFlow<Int>(permits - acquiredPermits)
override val permitsStateFlow: StateFlow<Int> = _permitsStateFlow.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 while [freePermits] == true, [holds] will
* wait for [freePermits] == false and then try to lock
*/
suspend fun acquire(permits: Int = 1) {
do {
val checkedPermits = checkedPermits(permits)
waitRelease(checkedPermits)
val shouldContinue = internalChangesMutex.withLock {
if (_permitsStateFlow.value < checkedPermits) {
true
} else {
_permitsStateFlow.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 (_permitsStateFlow.value < checkedPermits) {
internalChangesMutex.withLock {
if (_permitsStateFlow.value < checkedPermits) {
_permitsStateFlow.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 (_permitsStateFlow.value < this.permits) {
internalChangesMutex.withLock {
if (_permitsStateFlow.value < this.permits) {
_permitsStateFlow.value = minOf(_permitsStateFlow.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 }

View File

@@ -0,0 +1,60 @@
import dev.inmo.micro_utils.coroutines.SmartRWLocker
import dev.inmo.micro_utils.coroutines.withReadAcquire
import dev.inmo.micro_utils.coroutines.withWriteLock
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.delay
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
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)
}
}
}

View File

@@ -29,8 +29,4 @@ android {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
} }

View File

@@ -13,10 +13,10 @@ repositories {
kotlin { kotlin {
jvm() jvm()
// js(IR) { js(IR) {
// browser() browser()
// nodejs() nodejs()
// } }
android {} android {}
sourceSets { sourceSets {
@@ -26,44 +26,44 @@ kotlin {
project.parent.subprojects.forEach { project.parent.subprojects.forEach {
if ( if (
it != project it != project
&& it.hasProperty("kotlin") && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
// && it.kotlin.sourceSets.any { it.name.contains("jsMain") } && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") } && it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") } && it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) { ) {
api it api it
} }
} }
} }
} }
// jsMain { jsMain {
// dependencies { dependencies {
// implementation kotlin('stdlib') implementation kotlin('stdlib')
// project.parent.subprojects.forEach { project.parent.subprojects.forEach {
// if ( if (
// it != project it != project
// && it.hasProperty("kotlin") && it.hasProperty("kotlin")
// && it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
// && it.kotlin.sourceSets.any { it.name.contains("jsMain") } && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
// ) { ) {
// api it api it
// } }
// } }
// } }
// } }
jvmMain { jvmMain {
dependencies { dependencies {
implementation kotlin('stdlib') implementation kotlin('stdlib')
project.parent.subprojects.forEach { project.parent.subprojects.forEach {
if ( if (
it != project it != project
&& it.hasProperty("kotlin") && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") } && it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
) { ) {
api it api it
} }
@@ -76,10 +76,10 @@ kotlin {
project.parent.subprojects.forEach { project.parent.subprojects.forEach {
if ( if (
it != project it != project
&& it.hasProperty("kotlin") && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") } && it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) { ) {
api it api it
} }
@@ -116,9 +116,9 @@ tasks.dokkaHtml {
sourceRoots.setFrom(findSourcesWithName("commonMain")) sourceRoots.setFrom(findSourcesWithName("commonMain"))
} }
// named("jsMain") { named("jsMain") {
// sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain")) sourceRoots.setFrom(findSourcesWithName("jsMain"))
// } }
named("jvmMain") { named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain")) sourceRoots.setFrom(findSourcesWithName("jvmMain"))

View File

@@ -3,6 +3,7 @@ org.gradle.parallel=true
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
#kotlin.experimental.tryK2=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx2g
@@ -14,5 +15,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.19.0 version=0.20.2
android_code_version=196 android_code_version=208

View File

@@ -1,42 +1,45 @@
[versions] [versions]
kt = "1.8.20" kt = "1.9.0"
kt-serialization = "1.5.1" kt-serialization = "1.6.0"
kt-coroutines = "1.6.4" kt-coroutines = "1.7.3"
kslog = "1.1.1" kslog = "1.2.0"
jb-compose = "1.4.0" jb-compose = "1.4.3"
jb-exposed = "0.41.1" jb-exposed = "0.42.1"
jb-dokka = "1.8.10" jb-dokka = "1.8.20"
korlibs = "4.0.1" korlibs = "4.0.10"
uuid = "0.7.0" uuid = "0.8.0"
ktor = "2.3.0" ktor = "2.3.3"
gh-release = "2.4.1" gh-release = "2.4.1"
koin = "3.4.0" koin = "3.4.3"
okio = "3.3.0" okio = "3.5.0"
ksp = "1.8.20-1.0.11" ksp = "1.9.0-1.0.13"
kotlin-poet = "1.13.2" kotlin-poet = "1.14.2"
versions = "0.47.0"
android-gradle = "7.4.2" android-gradle = "7.4.2"
dexcount = "4.0.0" dexcount = "4.0.0"
android-coreKtx = "1.10.1" android-coreKtx = "1.10.1"
android-recyclerView = "1.3.0" android-recyclerView = "1.3.1"
android-appCompat = "1.6.1" android-appCompat = "1.6.1"
android-fragment = "1.5.7" android-fragment = "1.6.1"
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.1"
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]
@@ -81,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" }
@@ -109,3 +113,5 @@ buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gra
[plugins] [plugins]
jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" } jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" }
versions = { id = "com.github.ben-manes.versions", version.ref = "versions" }

View File

@@ -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.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -13,3 +13,8 @@ dependencies {
api libs.kotlin.poet api libs.kotlin.poet
api libs.ksp api libs.ksp
} }
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -9,19 +9,28 @@ import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSFile import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.Modifier
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.asTypeName import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.ksp.toClassName import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo import com.squareup.kotlinpoet.ksp.writeTo
import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.scope.Scope import org.koin.core.scope.Scope
import java.io.File import java.io.File
import kotlin.reflect.KClass import kotlin.reflect.KClass
@@ -32,11 +41,190 @@ class Processor(
private val definitionClassName = ClassName("org.koin.core.definition", "Definition") private val definitionClassName = ClassName("org.koin.core.definition", "Definition")
private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition") private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition")
private fun FileSpec.Builder.addCodeForType(
targetType: TypeName,
name: String,
nullable: Boolean,
generateSingle: Boolean,
generateFactory: Boolean,
) {
val targetTypeAsGenericType = (targetType as? TypeVariableName) ?.copy(reified = true)
fun addGetterProperty(
receiver: KClass<*>
) {
addProperty(
PropertySpec.builder(
name,
targetType,
).apply {
addKdoc(
"""
@return Definition by key "${name}"
""".trimIndent()
)
getter(
FunSpec.getterBuilder().apply {
targetTypeAsGenericType ?.let {
addModifiers(KModifier.INLINE)
}
addCode(
"return " + (if (nullable) {
"getOrNull"
} else {
"get"
}) + "(named(\"${name}\"))"
)
}.build()
)
targetTypeAsGenericType ?.let {
addTypeVariable(it)
}
receiver(receiver)
}.build()
)
}
if (targetTypeAsGenericType == null) {
addGetterProperty(Scope::class)
addGetterProperty(Koin::class)
}
val parametersDefinitionClassName = ClassName(
"org.koin.core.parameter",
"ParametersDefinition"
)
fun addGetterMethod(
receiver: KClass<*>
) {
addFunction(
FunSpec.builder(
name
).apply {
addKdoc(
"""
@return Definition by key "${name}" with [parameters]
""".trimIndent()
)
receiver(receiver)
addParameter(
ParameterSpec(
"parameters",
parametersDefinitionClassName.let {
if (targetTypeAsGenericType != null) {
it.copy(nullable = true)
} else {
it
}
},
KModifier.NOINLINE
).toBuilder().apply {
if (targetTypeAsGenericType != null) {
defaultValue("null")
}
}.build()
)
addModifiers(KModifier.INLINE)
targetTypeAsGenericType ?.let {
addTypeVariable(it)
returns(it.copy(nullable = nullable))
} ?: returns(targetType)
addCode(
"return " + (if (nullable) {
"getOrNull"
} else {
"get"
}) + "(named(\"${name}\"), parameters)"
)
}.build()
)
}
addGetterMethod(Scope::class)
addGetterMethod(Koin::class)
fun FunSpec.Builder.addDefinitionParameter() {
val definitionModifiers = if (targetTypeAsGenericType == null) {
arrayOf()
} else {
arrayOf(KModifier.NOINLINE)
}
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false)),
*definitionModifiers
).build()
)
}
if (generateSingle) {
fun FunSpec.Builder.configure() {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.single] and key "${name}"
""".trimIndent()
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"createdAtStart",
Boolean::class
).apply {
defaultValue("false")
}.build()
)
addDefinitionParameter()
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return single(named(\"${name}\"), createdAtStart = createdAtStart, definition = definition)"
)
targetTypeAsGenericType ?.let {
addTypeVariable(it)
addModifiers(KModifier.INLINE)
}
}
val actualSingleName = "single${name.replaceFirstChar { it.uppercase() }}"
addFunction(
FunSpec.builder(actualSingleName).apply { configure() }.build()
)
}
if (generateFactory) {
fun FunSpec.Builder.configure() {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.factory] and key "${name}"
""".trimIndent()
)
receiver(Module::class)
addDefinitionParameter()
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return factory(named(\"${name}\"), definition = definition)"
)
targetTypeAsGenericType ?.let {
addTypeVariable(it)
addModifiers(KModifier.INLINE)
}
}
val actualFactoryName = "factory${name.replaceFirstChar { it.uppercase() }}"
addFunction(
FunSpec.builder(actualFactoryName).apply { configure() }.build()
)
}
addImport("org.koin.core.qualifier", "named")
}
@OptIn(KspExperimental::class) @OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> { override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getSymbolsWithAnnotation( (resolver.getSymbolsWithAnnotation(
GenerateKoinDefinition::class.qualifiedName!! GenerateKoinDefinition::class.qualifiedName!!
).filterIsInstance<KSFile>().forEach { ksFile -> ) + resolver.getSymbolsWithAnnotation(
GenerateGenericKoinDefinition::class.qualifiedName!!
)).filterIsInstance<KSFile>().forEach { ksFile ->
FileSpec.builder( FileSpec.builder(
ksFile.packageName.asString(), ksFile.packageName.asString(),
"GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}" "GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}"
@@ -72,92 +260,12 @@ class Processor(
}.copy( }.copy(
nullable = it.nullable nullable = it.nullable
) )
fun addGetterProperty(
receiver: KClass<*>
) {
addProperty(
PropertySpec.builder(
it.name,
targetType,
).apply {
addKdoc(
"""
@return Definition by key "${it.name}"
""".trimIndent()
)
getter(
FunSpec.getterBuilder().apply {
addCode(
"return " + (if (it.nullable) {
"getOrNull"
} else {
"get"
}) + "(named(\"${it.name}\"))"
)
}.build()
)
receiver(receiver)
}.build()
)
}
addGetterProperty(Scope::class) addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory)
addGetterProperty(Koin::class) }
ksFile.getAnnotationsByType(GenerateGenericKoinDefinition::class).forEach {
if (it.generateSingle) { val targetType = TypeVariableName("T", Any::class)
addFunction( addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory)
FunSpec.builder("${it.name}Single").apply {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.single] and key "${it.name}"
""".trimIndent()
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"createdAtStart",
Boolean::class
).apply {
defaultValue("false")
}.build()
)
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
).build()
)
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return single(named(\"${it.name}\"), createdAtStart = createdAtStart, definition = definition)"
)
}.build()
)
}
if (it.generateFactory) {
addFunction(
FunSpec.builder("${it.name}Factory").apply {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.factory] and key "${it.name}"
""".trimIndent()
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
).build()
)
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return factory(named(\"${it.name}\"), definition = definition)"
)
}.build()
)
}
addImport("org.koin.core.qualifier", "named")
} }
}.build().let { }.build().let {
File( File(

View File

@@ -3,12 +3,15 @@
// ORIGINAL FILE: Test.kt // ORIGINAL FILE: Test.kt
package dev.inmo.micro_utils.koin.generator.test package dev.inmo.micro_utils.koin.generator.test
import kotlin.Any
import kotlin.Boolean import kotlin.Boolean
import kotlin.Deprecated
import kotlin.String import kotlin.String
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.definition.Definition import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition import org.koin.core.definition.KoinDefinition
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.core.scope.Scope import org.koin.core.scope.Scope
@@ -24,15 +27,98 @@ public val Scope.sampleInfo: Test<String>
public val Koin.sampleInfo: Test<String> public val Koin.sampleInfo: Test<String>
get() = get(named("sampleInfo")) get() = get(named("sampleInfo"))
/**
* @return Definition by key "sampleInfo" with [parameters]
*/
public inline fun Scope.sampleInfo(noinline parameters: ParametersDefinition): Test<String> =
get(named("sampleInfo"), parameters)
/**
* @return Definition by key "sampleInfo" with [parameters]
*/
public inline fun Koin.sampleInfo(noinline parameters: ParametersDefinition): Test<String> =
get(named("sampleInfo"), parameters)
/** /**
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo" * Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
*/ */
@Deprecated(
"This definition is old style and should not be used anymore. Use singleSampleInfo instead",
ReplaceWith("singleSampleInfo"),
)
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false, public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
definition: Definition<Test<String>>): KoinDefinition<Test<String>> = definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
*/
public fun Module.singleSampleInfo(createdAtStart: Boolean = false,
definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
/** /**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo" * Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
*/ */
@Deprecated(
"This definition is old style and should not be used anymore. Use factorySampleInfo instead",
ReplaceWith("factorySampleInfo"),
)
public fun Module.sampleInfoFactory(definition: Definition<Test<String>>): public fun Module.sampleInfoFactory(definition: Definition<Test<String>>):
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition) KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
*/
public fun Module.factorySampleInfo(definition: Definition<Test<String>>):
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
/**
* @return Definition by key "test" with [parameters]
*/
public inline fun <reified T : Any> Scope.test(noinline parameters: ParametersDefinition? = null): T
= get(named("test"), parameters)
/**
* @return Definition by key "test" with [parameters]
*/
public inline fun <reified T : Any> Koin.test(noinline parameters: ParametersDefinition? = null): T
= get(named("test"), parameters)
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "test"
*/
public inline fun <reified T : Any> Module.singleTest(createdAtStart: Boolean = false, noinline
definition: Definition<T>): KoinDefinition<T> = single(named("test"), createdAtStart =
createdAtStart, definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "test"
*/
public inline fun <reified T : Any> Module.factoryTest(noinline definition: Definition<T>):
KoinDefinition<T> = factory(named("test"), definition = definition)
/**
* @return Definition by key "testNullable" with [parameters]
*/
public inline fun <reified T : Any> Scope.testNullable(noinline parameters: ParametersDefinition? =
null): T? = getOrNull(named("testNullable"), parameters)
/**
* @return Definition by key "testNullable" with [parameters]
*/
public inline fun <reified T : Any> Koin.testNullable(noinline parameters: ParametersDefinition? =
null): T? = getOrNull(named("testNullable"), parameters)
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "testNullable"
*/
public inline fun <reified T : Any> Module.singleTestNullable(createdAtStart: Boolean = false,
noinline definition: Definition<T>): KoinDefinition<T> = single(named("testNullable"),
createdAtStart = createdAtStart, definition = definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "testNullable"
*/
public inline fun <reified T : Any> Module.factoryTestNullable(noinline definition: Definition<T>):
KoinDefinition<T> = factory(named("testNullable"), definition = definition)

View File

@@ -1,6 +1,9 @@
@file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false) @file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false)
@file:GenerateGenericKoinDefinition("test", nullable = false)
@file:GenerateGenericKoinDefinition("testNullable", nullable = true)
package dev.inmo.micro_utils.koin.generator.test package dev.inmo.micro_utils.koin.generator.test
import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin import org.koin.core.Koin

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.koin.annotations
import kotlin.reflect.KClass
/**
* Use this annotation to mark files near to which generator should place generated extensions for koin [org.koin.core.scope.Scope]
* and [org.koin.core.Koin]
*
* @param name Name for definitions. This name will be available as extension for [org.koin.core.scope.Scope] and [org.koin.core.Koin]
* @param type Type of extensions. It is base star-typed class
* @param typeArgs Generic types for [type]. For example, if [type] == `Something::class` and [typeArgs] == `G1::class,
* G2::class`, the result type will be `Something<G1, G2>`
* @param nullable In case when true, extension will not throw error when definition has not been registered in koin
* @param generateSingle Generate definition factory with [org.koin.core.module.Module.single]. You will be able to use
* the extension [org.koin.core.module.Module].[name]Single(createdAtStart/* default false */) { /* your definition */ }
* @param generateFactory Generate definition factory with [org.koin.core.module.Module.factory]. You will be able to use
* the extension [org.koin.core.module.Module].[name]Factory { /* your definition */ }
*/
@Target(AnnotationTarget.FILE)
@Repeatable
annotation class GenerateGenericKoinDefinition(
val name: String,
val nullable: Boolean = true,
val generateSingle: Boolean = true,
val generateFactory: Boolean = true
)

12
local.migrate.folder.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
function renameFolders() {
for folder in $(find . -depth -type d -name "$1");
do
sedString="s/$1/$2/g"
newFolder="$(echo $folder | sed $sedString)"
echo $folder "$newFolder"
done
}
renameFolders "androidTest" "androidUnitTest"

View File

@@ -18,6 +18,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
} }
} }
} }

View File

@@ -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
} }
} }

View File

@@ -28,6 +28,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
} }
} }

View File

@@ -32,6 +32,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 {
@@ -45,7 +46,7 @@ kotlin {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }
androidTest { androidUnitTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation libs.android.test.junit implementation libs.android.test.junit

View File

@@ -31,6 +31,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 {
@@ -54,7 +55,7 @@ kotlin {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }
androidTest { androidUnitTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation libs.android.test.junit implementation libs.android.test.junit

View File

@@ -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,15 +61,15 @@ 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)
@@ -59,53 +77,65 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
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 +143,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)

View File

@@ -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,45 +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>(
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() {
parentRepo.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)

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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()
@@ -21,8 +23,15 @@ open class SimpleFullKVCache<K, V>(
kvParent.unset(toUnset) kvParent.unset(toUnset)
} }
} }
override suspend fun clear() {
syncMutex.withLock {
kvParent.clear()
}
}
} }
@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)

View File

@@ -37,6 +37,10 @@ open class SimpleKVCache<K, V>(
override suspend fun unset(toUnset: List<K>) { override suspend fun unset(toUnset: List<K>) {
syncMutex.withLock { makeUnset(toUnset) } syncMutex.withLock { makeUnset(toUnset) }
} }
override suspend fun clear() {
syncMutex.withLock { makeUnset(cacheQueue) }
}
} }
inline fun <K, V> KVCache( inline fun <K, V> KVCache(

View File

@@ -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)

View File

@@ -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)

View File

@@ -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>

View File

@@ -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)
@@ -39,4 +39,9 @@ open class AutoRecacheKeyValueRepo<Id, RegisteredObject>(
).also { ).also {
kvCache.unsetWithValues(toUnset) kvCache.unsetWithValues(toUnset)
} }
override suspend fun clear() {
originalRepo.clear()
kvCache.clear()
}
} }

View File

@@ -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)

View File

@@ -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()

View File

@@ -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))

View File

@@ -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))

View File

@@ -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

View File

@@ -1,37 +1,39 @@
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.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) locker.withWriteLock { kvCache.actualize(it) }
}
} }
error("The result should be returned above")
} }
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(
@@ -76,41 +78,56 @@ 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,
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> {
init {
if (!skipStartInvalidate) {
scope.launchSafelyWithoutExceptions { invalidate() }
}
}
override suspend fun invalidate() { override suspend fun invalidate() {
actualizeAll() actualizeAll()
} }
} }
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,
locker: SmartRWLocker = SmartRWLocker(),
idGetter: (ObjectType) -> IdType idGetter: (ObjectType) -> IdType
) = FullCRUDCacheRepo(this, kvCache, scope, 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,
locker: SmartRWLocker = SmartRWLocker(),
idGetter: (ObjectType) -> IdType idGetter: (ObjectType) -> IdType
) = fullyCached(kvCache, scope, idGetter) ) = fullyCached(kvCache, scope, skipStartInvalidate, locker, idGetter)

View File

@@ -1,10 +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.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
@@ -13,25 +16,26 @@ 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) locker.withWriteLock { kvCache.actualize(it) }
}
} }
error("The result should be returned above")
} }
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 { keys(it) }.toMap())
}
} }
override suspend fun get(k: Key): Value? = doOrTakeAndActualize( override suspend fun get(k: Key): Value? = doOrTakeAndActualize(
@@ -82,48 +86,83 @@ 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,
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(parentRepo, kvCache) { ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(
parentRepo,
kvCache,
locker
) {
init {
if (!skipStartInvalidate) {
scope.launchSafelyWithoutExceptions { invalidate() }
}
}
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() {
parentRepo.clear()
kvCache.clear()
} }
} }
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)

View File

@@ -1,10 +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.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
@@ -12,29 +15,32 @@ 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) locker.withWriteLock { kvCache.actualize(it) }
}
} }
error("The result should be returned above")
} }
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> {
@@ -108,44 +114,61 @@ 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),
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope), skipStartInvalidate: Boolean = false,
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 {
if (!skipStartInvalidate) {
scope.launchSafelyWithoutExceptions { invalidate() }
}
}
override suspend fun clearWithValue(v: Value) { override suspend fun clearWithValue(v: Value) {
doAllWithCurrentPaging { doAllWithCurrentPaging {
keys(v, it).also { keys(v, it).also {
@@ -155,7 +178,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) {
@@ -164,12 +189,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)

View File

@@ -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,
) { ) {

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