mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
aaf1299da7 | |||
a411355b4f | |||
eba41066b4 | |||
f295dff8a2 | |||
a16815143c | |||
6ff3f6ae42 | |||
84071881af | |||
7cccf7e56e | |||
2516d5e381 | |||
cdec8bac75 | |||
fa30aae194 | |||
eb959a3135 | |||
24033e0cac | |||
71f9a505e0 | |||
979b8f017b | |||
af78f01682 | |||
0b16d5c826 | |||
597e14bc7e | |||
04a95867e2 | |||
e0d5eb45b7 | |||
b90cab318e | |||
3252b61abe | |||
2a2da21ff3 | |||
04ef371337 | |||
623e0cd369 | |||
1f466747f0 | |||
2215462f99 | |||
ac4c0a2e4c | |||
f7496db5ac | |||
3028fe975d | |||
23a5034493 | |||
65e339f811 | |||
2020e48659 | |||
9566d6f81f | |||
a00d734712 | |||
27a3e8706a | |||
e601efcfc0 | |||
2bfad9f885 | |||
e78e984943 | |||
242f4b02d0 | |||
041be5a1d1 | |||
976ce056c1 | |||
00c23c73a8 | |||
9dd1848337 | |||
9b30efd9a2 | |||
5853f7cc49 | |||
7b00a06f3e | |||
9ef9be0f37 |
12
.github/workflows/build.yml
vendored
Normal file
12
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Regular build
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build
|
||||
run: ./gradlew build
|
81
CHANGELOG.md
81
CHANGELOG.md
@@ -1,5 +1,86 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.10
|
||||
|
||||
* `Versions`
|
||||
* `Core KTX`: `1.3.2` -> `1.5.0`
|
||||
* `AndroidX Recycler`: `1.2.0` -> `1.2.1`
|
||||
* `AppCompat`: `1.2.0` -> `1.3.0`
|
||||
* `Android`
|
||||
* `RecyclerView`:
|
||||
* `data` of `RecyclerViewAdapter` became an abstract field
|
||||
* New function `RecyclerViewAdapter`
|
||||
* `Common`:
|
||||
* New extension `View#changeVisibility`
|
||||
* `Repos`:
|
||||
* `Common`:
|
||||
* `WriteOneToManyRepo` got new function `clearWithValue`
|
||||
* `Android`:
|
||||
* New extension `SQLiteDatabase#selectDistinct`
|
||||
* Fixes in `OneToManyAndroidRepo`
|
||||
* `Ktor`
|
||||
* `Server`
|
||||
* All elements in configurators became a `fun interface`
|
||||
* `Pagination`
|
||||
* New function `doForAllWithCurrentPaging`
|
||||
|
||||
## 0.5.9
|
||||
|
||||
* `Repos`
|
||||
* `Common`
|
||||
* `OneToManyAndroidRepo` got new primary constructor
|
||||
|
||||
## 0.5.8
|
||||
|
||||
* `Common`:
|
||||
* New extension `Iterable#firstNotNull`
|
||||
* `Coroutines`
|
||||
* New extension `Flow#firstNotNull`
|
||||
* New extensions `CoroutineContext#LinkedSupervisorJob`, `CoroutineScope#LinkedSupervisorJob` and
|
||||
`CoroutineScope#LinkedSupervisorScope`
|
||||
|
||||
## 0.5.7
|
||||
|
||||
* `Pagination`
|
||||
* `Ktor`
|
||||
* `Server`
|
||||
* Fixes in extension `extractPagination`
|
||||
* `Repos`
|
||||
* `Cache`
|
||||
* All standard cache repos have been separated to read and read/write repos
|
||||
|
||||
## 0.5.6
|
||||
|
||||
* `Versions`
|
||||
* `Exposed`: `0.31.1` -> `0.32.1`
|
||||
* `Coroutines`
|
||||
* `JVM`
|
||||
* `launchSynchronously` and subsequent functions got improved mechanism
|
||||
* New method `safelyWithResult`
|
||||
|
||||
## 0.5.5
|
||||
|
||||
* `Versions`
|
||||
* `Ktor`: `1.5.4` -> `1.6.0`
|
||||
|
||||
## 0.5.4
|
||||
|
||||
* `Versions`:
|
||||
* `Klock`: `2.1.0` -> `2.1.2`
|
||||
|
||||
## 0.5.3
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.5.0` -> `1.5.10`
|
||||
* `Coroutines`:
|
||||
* Extensions `doInUI` and `doInDefault` were replaced in common and available on any supported platform
|
||||
* Extension `doInIO` replaced into `jvm` and available on any `JVM` platform
|
||||
* Old extension `safelyWithouException` without `onException` has been replaced by its copy with `onException` and
|
||||
default value
|
||||
* New value `defaultSafelyWithoutExceptionHandlerWithNull` which is used in all `*WithoutExceptions` by default
|
||||
* Analogs of `launch` and `async` for `safely` and `safelyWithoutExceptions` were added
|
||||
* Analogs of `runCatching` for `safely` and `safelyWithoutExceptions` were added
|
||||
|
||||
## 0.5.2
|
||||
|
||||
* `Ktor`:
|
||||
|
@@ -35,9 +35,9 @@ class ActionViewHolder(
|
||||
}
|
||||
|
||||
class ActionsRecyclerViewAdapter(
|
||||
data: List<AlertAction>,
|
||||
override val data: List<AlertAction>,
|
||||
private val dialogInterfaceGetter: () -> DialogInterface
|
||||
) : RecyclerViewAdapter<AlertAction>(data) {
|
||||
) : RecyclerViewAdapter<AlertAction>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<AlertAction> = ActionViewHolder(
|
||||
parent, dialogInterfaceGetter
|
||||
)
|
||||
|
@@ -1,12 +1,13 @@
|
||||
package dev.inmo.micro_utils.android.recyclerview
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
|
||||
abstract class RecyclerViewAdapter<T>(
|
||||
val data: List<T>
|
||||
): RecyclerView.Adapter<AbstractViewHolder<T>>() {
|
||||
abstract class RecyclerViewAdapter<T>: RecyclerView.Adapter<AbstractViewHolder<T>>() {
|
||||
protected abstract val data: List<T>
|
||||
|
||||
var emptyView: View? = null
|
||||
set(value) {
|
||||
field = value
|
||||
@@ -66,3 +67,11 @@ abstract class RecyclerViewAdapter<T>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> RecyclerViewAdapter(
|
||||
data: List<T>,
|
||||
onCreateViewHolder: (parent: ViewGroup, viewType: Int) -> AbstractViewHolder<T>
|
||||
) = object : RecyclerViewAdapter<T>() {
|
||||
override val data: List<T> = data
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractViewHolder<T> = onCreateViewHolder(parent, viewType)
|
||||
}
|
||||
|
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
fun <T> Iterable<T?>.firstNotNull() = first { it != null }!!
|
@@ -33,3 +33,15 @@ fun View.toggleVisibility(goneOnHide: Boolean = true) {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
fun View.changeVisibility(show: Boolean = !isShown, goneOnHide: Boolean = true) {
|
||||
if (show) {
|
||||
if (goneOnHide) {
|
||||
gone()
|
||||
} else {
|
||||
hide()
|
||||
}
|
||||
} else {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
@@ -19,4 +19,4 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ inline fun CoroutineScope.createSafeActionsActor(
|
||||
suspend fun <T> Channel<suspend () -> Unit>.doWithSuspending(
|
||||
action: ActorAction<T>
|
||||
) = suspendCoroutine<T> {
|
||||
offer {
|
||||
trySend {
|
||||
safely({ e -> it.resumeWithException(e) }) {
|
||||
it.resume(action())
|
||||
}
|
||||
|
@@ -0,0 +1,23 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
inline val UI
|
||||
get() = Dispatchers.Main
|
||||
inline val Default
|
||||
get() = Dispatchers.Default
|
||||
|
||||
suspend inline fun <T> doIn(context: CoroutineContext, noinline block: suspend CoroutineScope.() -> T) = withContext(
|
||||
context,
|
||||
block
|
||||
)
|
||||
|
||||
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = doIn(
|
||||
UI,
|
||||
block
|
||||
)
|
||||
suspend inline fun <T> doInDefault(noinline block: suspend CoroutineScope.() -> T) = doIn(
|
||||
Default,
|
||||
block
|
||||
)
|
@@ -0,0 +1,6 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
suspend fun <T> Flow<T?>.firstNotNull() = first { it != null }!!
|
@@ -86,6 +86,9 @@ suspend fun <T> safelyWithContextExceptionHandler(
|
||||
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
|
||||
* * [defaultSafelyExceptionHandler]
|
||||
*
|
||||
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
|
||||
* be called [onException]
|
||||
*
|
||||
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
|
||||
* exception will be available for catching
|
||||
*
|
||||
@@ -105,24 +108,42 @@ suspend inline fun <T> safely(
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <T> runCatchingSafely(
|
||||
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): Result<T> = runCatching {
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
suspend inline fun <T> safelyWithResult(
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
||||
|
||||
/**
|
||||
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
|
||||
* returning null at one time
|
||||
*
|
||||
* @see safelyWithoutExceptions
|
||||
* @see launchSafelyWithoutExceptions
|
||||
* @see asyncSafelyWithoutExceptions
|
||||
*/
|
||||
val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
|
||||
defaultSafelyWithoutExceptionHandler.invoke(it)
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
|
||||
* result from exception (instead of throwing it)
|
||||
* result from exception (instead of throwing it, by default always returns null)
|
||||
*/
|
||||
suspend inline fun <T> safelyWithoutExceptions(
|
||||
noinline onException: ExceptionHandler<T?>,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T? = safely(onException, block)
|
||||
|
||||
/**
|
||||
* Shortcut for [safely] without exception handler (instead of this you will always receive null as a result)
|
||||
*/
|
||||
suspend inline fun <T> safelyWithoutExceptions(
|
||||
suspend inline fun <T> runCatchingSafelyWithoutExceptions(
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): T? = safelyWithoutExceptions(
|
||||
{
|
||||
defaultSafelyWithoutExceptionHandler.invoke(it)
|
||||
null
|
||||
},
|
||||
block
|
||||
)
|
||||
): Result<T?> = runCatching {
|
||||
safelyWithoutExceptions(onException, block)
|
||||
}
|
||||
|
@@ -0,0 +1,41 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
inline fun CoroutineScope.launchSafely(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> Unit
|
||||
) = launch(context, start) {
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
inline fun CoroutineScope.launchSafelyWithoutExceptions(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
noinline onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> Unit
|
||||
) = launch(context, start) {
|
||||
safelyWithoutExceptions(onException, block)
|
||||
}
|
||||
|
||||
inline fun <T> CoroutineScope.asyncSafely(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
) = async(context, start) {
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
inline fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
start: CoroutineStart = CoroutineStart.DEFAULT,
|
||||
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
) = async(context, start) {
|
||||
safelyWithoutExceptions(onException, block)
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun CoroutineContext.LinkedSupervisorJob(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = SupervisorJob(job).let { if (additionalContext != null) it + additionalContext else it }
|
||||
fun CoroutineScope.LinkedSupervisorJob(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = coroutineContext.LinkedSupervisorJob(additionalContext)
|
||||
|
||||
fun CoroutineScope.LinkedSupervisorScope(
|
||||
additionalContext: CoroutineContext? = null
|
||||
) = CoroutineScope(
|
||||
coroutineContext + LinkedSupervisorJob(additionalContext)
|
||||
)
|
@@ -0,0 +1,11 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
val IO
|
||||
get() = Dispatchers.IO
|
||||
|
||||
suspend inline fun <T> doInIO(noinline block: suspend CoroutineScope.() -> T) = doIn(
|
||||
IO,
|
||||
block
|
||||
)
|
@@ -3,27 +3,21 @@ package dev.inmo.micro_utils.coroutines
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T {
|
||||
val deferred = CompletableDeferred<T>()
|
||||
val objectToSynchronize = java.lang.Object()
|
||||
val launchCallback = {
|
||||
var result: Result<T>? = null
|
||||
val objectToSynchronize = Object()
|
||||
synchronized(objectToSynchronize) {
|
||||
launch {
|
||||
safely(
|
||||
{
|
||||
deferred.completeExceptionally(it)
|
||||
}
|
||||
) {
|
||||
deferred.complete(block())
|
||||
}
|
||||
result = safelyWithResult(block)
|
||||
}.invokeOnCompletion {
|
||||
synchronized(objectToSynchronize) {
|
||||
objectToSynchronize.notifyAll()
|
||||
}
|
||||
}
|
||||
while (result == null) {
|
||||
objectToSynchronize.wait()
|
||||
}
|
||||
}
|
||||
synchronized(objectToSynchronize) {
|
||||
launchCallback()
|
||||
objectToSynchronize.wait()
|
||||
}
|
||||
return deferred.getCompleted()
|
||||
return result!!.getOrThrow()
|
||||
}
|
||||
|
||||
fun <T> launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block)
|
||||
|
@@ -1,9 +1,8 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.asDeferred
|
||||
import dev.inmo.micro_utils.coroutines.launchSynchronously
|
||||
import kotlinx.coroutines.*
|
||||
import kotlin.test.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DoWithFirstTests {
|
||||
@Test
|
||||
|
@@ -1,18 +0,0 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
suspend inline fun <T> doInUI(noinline block: suspend CoroutineScope.() -> T) = withContext(
|
||||
Dispatchers.Main,
|
||||
block
|
||||
)
|
||||
suspend inline fun <T> doInDefault(noinline block: suspend CoroutineScope.() -> T) = withContext(
|
||||
Dispatchers.Default,
|
||||
block
|
||||
)
|
||||
suspend inline fun <T> doInIO(noinline block: suspend CoroutineScope.() -> T) = withContext(
|
||||
Dispatchers.IO,
|
||||
block
|
||||
)
|
@@ -7,14 +7,14 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
org.gradle.jvmargs=-Xmx2g
|
||||
|
||||
kotlin_version=1.5.0
|
||||
kotlin_version=1.5.10
|
||||
kotlin_coroutines_version=1.5.0
|
||||
kotlin_serialisation_core_version=1.2.1
|
||||
kotlin_exposed_version=0.31.1
|
||||
kotlin_exposed_version=0.32.1
|
||||
|
||||
ktor_version=1.5.4
|
||||
ktor_version=1.6.0
|
||||
|
||||
klockVersion=2.1.0
|
||||
klockVersion=2.1.2
|
||||
|
||||
github_release_plugin_version=2.2.12
|
||||
|
||||
@@ -22,9 +22,9 @@ uuidVersion=0.3.0
|
||||
|
||||
# ANDROID
|
||||
|
||||
core_ktx_version=1.3.2
|
||||
androidx_recycler_version=1.2.0
|
||||
appcompat_version=1.2.0
|
||||
core_ktx_version=1.5.0
|
||||
androidx_recycler_version=1.2.1
|
||||
appcompat_version=1.3.0
|
||||
|
||||
android_minSdkVersion=19
|
||||
android_compileSdkVersion=30
|
||||
@@ -45,5 +45,5 @@ dokka_version=1.4.32
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.5.2
|
||||
android_code_version=43
|
||||
version=0.5.10
|
||||
android_code_version=51
|
||||
|
@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
private suspend fun DefaultWebSocketSession.checkReceivedAndCloseIfExists() {
|
||||
if (incoming.poll() != null) {
|
||||
if (incoming.tryReceive() != null) {
|
||||
close()
|
||||
throw CorrectCloseException
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
|
||||
data class ApplicationCachingHeadersConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun CachingHeaders.Configuration.invoke() }
|
||||
fun interface Element { operator fun CachingHeaders.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(CachingHeaders) {
|
||||
|
@@ -10,17 +10,18 @@ import kotlinx.serialization.Serializable
|
||||
class ApplicationRoutingConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun Route.invoke() }
|
||||
fun interface Element { operator fun Route.invoke() }
|
||||
private val rootInstaller = Element {
|
||||
elements.forEach {
|
||||
it.apply { invoke() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun Application.configure() {
|
||||
try {
|
||||
feature(Routing)
|
||||
} catch (e: IllegalStateException) {
|
||||
install(Routing) {
|
||||
elements.forEach {
|
||||
it.apply { invoke() }
|
||||
}
|
||||
}
|
||||
featureOrNull(Routing) ?.apply {
|
||||
rootInstaller.apply { invoke() }
|
||||
} ?: install(Routing) {
|
||||
rootInstaller.apply { invoke() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
|
||||
class ApplicationSessionsConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun Sessions.Configuration.invoke() }
|
||||
fun interface Element { operator fun Sessions.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(Sessions) {
|
||||
|
@@ -8,7 +8,7 @@ import kotlinx.serialization.Contextual
|
||||
class StatusPagesConfigurator(
|
||||
private val elements: List<@Contextual Element>
|
||||
) : KtorApplicationConfigurator {
|
||||
interface Element { operator fun StatusPages.Configuration.invoke() }
|
||||
fun interface Element { operator fun StatusPages.Configuration.invoke() }
|
||||
|
||||
override fun Application.configure() {
|
||||
install(StatusPages) {
|
||||
|
@@ -33,3 +33,8 @@ suspend fun <T> doAllWithCurrentPaging(
|
||||
block
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> doForAllWithCurrentPaging(
|
||||
initialPagination: Pagination = FirstPagePagination(),
|
||||
block: suspend (Pagination) -> PaginationResult<T>
|
||||
) = doAllWithCurrentPaging(initialPagination, block)
|
||||
|
@@ -5,8 +5,8 @@ import io.ktor.http.Parameters
|
||||
|
||||
val Parameters.extractPagination: Pagination
|
||||
get() = SimplePagination(
|
||||
get("page") ?.toIntOrNull() ?: 0,
|
||||
get("size") ?.toIntOrNull() ?: defaultPaginationPageSize
|
||||
get(paginationPageKey) ?.toIntOrNull() ?: 0,
|
||||
get(paginationSizeKey) ?.toIntOrNull() ?: defaultPaginationPageSize
|
||||
)
|
||||
|
||||
val ApplicationCall.extractPagination: Pagination
|
||||
|
@@ -6,19 +6,29 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
protected val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
protected val parentRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
protected val kvCache: KVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
protected val idGetter: (ObjectType) -> IdType
|
||||
) : CRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
|
||||
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
|
||||
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo {
|
||||
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
|
||||
kvCache.set(id, it)
|
||||
})
|
||||
|
||||
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
||||
}
|
||||
|
||||
open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>,
|
||||
kvCache: KVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
idGetter: (ObjectType) -> IdType
|
||||
) : ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
parentRepo,
|
||||
kvCache,
|
||||
idGetter
|
||||
), CRUDRepo<ObjectType, IdType, InputValueType>, WriteCRUDRepo<ObjectType, IdType, InputValueType> by parentRepo {
|
||||
protected val onNewJob = parentRepo.newObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onUpdatedJob = parentRepo.updatedObjectsFlow.onEach { kvCache.set(idGetter(it), it) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.deletedObjectsIdsFlow.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
}
|
||||
|
@@ -7,14 +7,19 @@ import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class KeyValueCacheRepo<Key,Value>(
|
||||
protected val parentRepo: KeyValueRepo<Key, Value>,
|
||||
open class ReadKeyValueCacheRepo<Key,Value>(
|
||||
protected val parentRepo: ReadKeyValueRepo<Key, Value>,
|
||||
protected val kvCache: KVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : KeyValueRepo<Key,Value> by parentRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
|
||||
) : ReadKeyValueRepo<Key,Value> by parentRepo {
|
||||
override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
|
||||
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
|
||||
}
|
||||
|
||||
open class KeyValueCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValueRepo<Key, Value>,
|
||||
kvCache: KVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
}
|
||||
|
@@ -11,15 +11,10 @@ import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
open class KeyValuesCacheRepo<Key,Value>(
|
||||
protected val parentRepo: KeyValuesRepo<Key, Value>,
|
||||
protected val kvCache: KVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : KeyValuesRepo<Key,Value> by parentRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
|
||||
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
|
||||
open class ReadKeyValuesCacheRepo<Key,Value>(
|
||||
protected val parentRepo: ReadKeyValuesRepo<Key, Value>,
|
||||
protected val kvCache: KVCache<Key, List<Value>>
|
||||
) : ReadKeyValuesRepo<Key,Value> by parentRepo {
|
||||
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
|
||||
return kvCache.get(k) ?.paginate(
|
||||
pagination.let { if (reversed) it.reverse(count(k)) else it }
|
||||
@@ -35,3 +30,13 @@ open class KeyValuesCacheRepo<Key,Value>(
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = kvCache.get(k) ?.contains(v) ?: parentRepo.contains(k, v)
|
||||
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
|
||||
}
|
||||
|
||||
open class KeyValuesCacheRepo<Key,Value>(
|
||||
parentRepo: KeyValuesRepo<Key, Value>,
|
||||
kvCache: KVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo {
|
||||
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.plus(it.second) ?: listOf(it.second)) }.launchIn(scope)
|
||||
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.set(it.first, kvCache.get(it.first) ?.minus(it.second) ?: return@onEach) }.launchIn(scope)
|
||||
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { kvCache.unset(it) }.launchIn(scope)
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithCurrentPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -47,6 +48,7 @@ interface WriteOneToManyKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun remove(toRemove: Map<Key, List<Value>>)
|
||||
|
||||
suspend fun clear(k: Key)
|
||||
suspend fun clearWithValue(v: Value)
|
||||
|
||||
suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||
toSet.keys.forEach { key -> clear(key) }
|
||||
@@ -87,7 +89,19 @@ suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.set(
|
||||
k: Key, vararg v: Value
|
||||
) = set(k, v.toList())
|
||||
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value>
|
||||
interface OneToManyKeyValueRepo<Key, Value> : ReadOneToManyKeyValueRepo<Key, Value>, WriteOneToManyKeyValueRepo<Key, Value> {
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
doWithPagination {
|
||||
val keysResult = keys(v, it)
|
||||
|
||||
if (keysResult.results.isNotEmpty()) {
|
||||
remove(keysResult.results.map { it to listOf(v) })
|
||||
}
|
||||
|
||||
keysResult.currentPageIfNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias KeyValuesRepo<Key,Value> = OneToManyKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteOneToManyKeyValueRepo<Key, Value>.remove(
|
||||
|
@@ -114,6 +114,7 @@ open class MapperWriteOneToManyKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}
|
||||
|
||||
override suspend fun clear(k: FromKey) = to.clear(k.toOutKey())
|
||||
override suspend fun clearWithValue(v: FromValue) = to.clearWithValue(v.toOutValue())
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
|
@@ -74,6 +74,19 @@ fun SQLiteDatabase.select(
|
||||
table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
|
||||
)
|
||||
|
||||
fun SQLiteDatabase.selectDistinct(
|
||||
table: String,
|
||||
columns: Array<String>? = null,
|
||||
selection: String? = null,
|
||||
selectionArgs: Array<String>? = null,
|
||||
groupBy: String? = null,
|
||||
having: String? = null,
|
||||
orderBy: String? = null,
|
||||
limit: String? = null
|
||||
) = query(
|
||||
true, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit
|
||||
)
|
||||
|
||||
fun makePlaceholders(count: Int): String {
|
||||
return (0 until count).joinToString { "?" }
|
||||
}
|
||||
|
@@ -3,10 +3,7 @@ package dev.inmo.micro_utils.repos.onetomany
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import androidx.core.content.contentValuesOf
|
||||
import dev.inmo.micro_utils.common.mapNotNullA
|
||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.createPaginationResult
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -20,10 +17,14 @@ private val internalSerialFormat = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
typealias KeyValuesAndroidRepo<Key, Value> = OneToManyAndroidRepo<Key, Value>
|
||||
|
||||
class OneToManyAndroidRepo<Key, Value>(
|
||||
private val tableName: String,
|
||||
private val keySerializer: KSerializer<Key>,
|
||||
private val valueSerializer: KSerializer<Value>,
|
||||
private val keyAsString: Key.() -> String,
|
||||
private val valueAsString: Value.() -> String,
|
||||
private val keyFromString: String.() -> Key,
|
||||
private val valueFromString: String.() -> Value,
|
||||
private val helper: SQLiteOpenHelper
|
||||
) : OneToManyKeyValueRepo<Key, Value> {
|
||||
private val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow()
|
||||
@@ -34,12 +35,9 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
override val onDataCleared: Flow<Key> = _onDataCleared.asSharedFlow()
|
||||
|
||||
private val idColumnName = "id"
|
||||
private val idColumnArray = arrayOf(idColumnName)
|
||||
private val valueColumnName = "value"
|
||||
|
||||
private fun Key.asId() = internalSerialFormat.encodeToString(keySerializer, this)
|
||||
private fun Value.asValue() = internalSerialFormat.encodeToString(valueSerializer, this)
|
||||
private fun String.asValue(): Value = internalSerialFormat.decodeFromString(valueSerializer, this)
|
||||
private fun String.asKey(): Key = internalSerialFormat.decodeFromString(keySerializer, this)
|
||||
private val valueColumnArray = arrayOf(valueColumnName)
|
||||
|
||||
init {
|
||||
helper.blockingWritableTransaction {
|
||||
@@ -61,8 +59,8 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
tableName,
|
||||
null,
|
||||
contentValuesOf(
|
||||
idColumnName to k.asId(),
|
||||
valueColumnName to v.asValue()
|
||||
idColumnName to k.keyAsString(),
|
||||
valueColumnName to v.valueAsString()
|
||||
)
|
||||
).also {
|
||||
if (it != -1L) {
|
||||
@@ -77,7 +75,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
|
||||
override suspend fun clear(k: Key) {
|
||||
helper.blockingWritableTransaction {
|
||||
delete(tableName, "$idColumnName=?", arrayOf(k.asId()))
|
||||
delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString()))
|
||||
}.also {
|
||||
if (it > 0) {
|
||||
_onDataCleared.emit(k)
|
||||
@@ -88,7 +86,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
override suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||
val (clearedKeys, inserted) = helper.blockingWritableTransaction {
|
||||
toSet.mapNotNull { (k, _) ->
|
||||
if (delete(tableName, "$idColumnName=?", arrayOf(k.asId())) > 0) {
|
||||
if (delete(tableName, "$idColumnName=?", arrayOf(k.keyAsString())) > 0) {
|
||||
k
|
||||
} else {
|
||||
null
|
||||
@@ -98,7 +96,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
insert(
|
||||
tableName,
|
||||
null,
|
||||
contentValuesOf(idColumnName to k.asId(), valueColumnName to v.asValue())
|
||||
contentValuesOf(idColumnName to k.keyAsString(), valueColumnName to v.valueAsString())
|
||||
)
|
||||
k to v
|
||||
}
|
||||
@@ -109,7 +107,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = helper.blockingReadableTransaction {
|
||||
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = firstPageWithOneElementPagination.limitClause()).use {
|
||||
it.count > 0
|
||||
}
|
||||
}
|
||||
@@ -118,14 +116,14 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
select(
|
||||
tableName,
|
||||
selection = "$idColumnName=? AND $valueColumnName=?",
|
||||
selectionArgs = arrayOf(k.asId(), v.asValue()),
|
||||
selectionArgs = arrayOf(k.keyAsString(), v.valueAsString()),
|
||||
limit = FirstPagePagination(1).limitClause()
|
||||
).use {
|
||||
it.count > 0
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun count(): Long =helper.blockingReadableTransaction {
|
||||
override suspend fun count(): Long = helper.blockingReadableTransaction {
|
||||
select(
|
||||
tableName
|
||||
).use {
|
||||
@@ -134,7 +132,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
}.toLong()
|
||||
|
||||
override suspend fun count(k: Key): Long = helper.blockingReadableTransaction {
|
||||
select(tableName, selection = "$idColumnName=?", selectionArgs = arrayOf(k.asId()), limit = FirstPagePagination(1).limitClause()).use {
|
||||
selectDistinct(tableName, columns = valueColumnArray, selection = "$idColumnName=?", selectionArgs = arrayOf(k.keyAsString()), limit = FirstPagePagination(1).limitClause()).use {
|
||||
it.count
|
||||
}
|
||||
}.toLong()
|
||||
@@ -144,18 +142,25 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Value> = count(k).let { count ->
|
||||
if (pagination.firstIndex >= count) {
|
||||
return@let emptyList<Value>().createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.blockingReadableTransaction {
|
||||
select(
|
||||
tableName,
|
||||
valueColumnArray,
|
||||
selection = "$idColumnName=?",
|
||||
selectionArgs = arrayOf(k.asId()),
|
||||
selectionArgs = arrayOf(k.keyAsString()),
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Value>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(valueColumnName).asValue())
|
||||
it.add(c.getString(valueColumnName).valueFromString())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
@@ -170,16 +175,23 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = count().let { count ->
|
||||
if (pagination.firstIndex >= count) {
|
||||
return@let emptyList<Key>().createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.blockingReadableTransaction {
|
||||
select(
|
||||
selectDistinct(
|
||||
tableName,
|
||||
idColumnArray,
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Key>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(idColumnName).asKey())
|
||||
it.add(c.getString(idColumnName).keyFromString())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
@@ -197,16 +209,17 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
): PaginationResult<Key> = count().let { count ->
|
||||
val resultPagination = pagination.let { if (reversed) pagination.reverse(count) else pagination }
|
||||
helper.blockingReadableTransaction {
|
||||
select(
|
||||
selectDistinct(
|
||||
tableName,
|
||||
idColumnArray,
|
||||
selection = "$valueColumnName=?",
|
||||
selectionArgs = arrayOf(v.asValue()),
|
||||
selectionArgs = arrayOf(v.valueAsString()),
|
||||
limit = resultPagination.limitClause()
|
||||
).use { c ->
|
||||
mutableListOf<Key>().also {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
it.add(c.getString(idColumnName).asKey())
|
||||
it.add(c.getString(idColumnName).keyFromString())
|
||||
} while (c.moveToNext())
|
||||
}
|
||||
}
|
||||
@@ -221,7 +234,7 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
helper.blockingWritableTransaction {
|
||||
toRemove.flatMap { (k, vs) ->
|
||||
vs.mapNotNullA { v ->
|
||||
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.asId(), v.asValue())) > 0) {
|
||||
if (delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(k.keyAsString(), v.valueAsString())) > 0) {
|
||||
k to v
|
||||
} else {
|
||||
null
|
||||
@@ -233,3 +246,24 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> OneToManyAndroidRepo(
|
||||
tableName: String,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
helper: SQLiteOpenHelper
|
||||
) = OneToManyAndroidRepo(
|
||||
tableName,
|
||||
{ internalSerialFormat.encodeToString(keySerializer, this) },
|
||||
{ internalSerialFormat.encodeToString(valueSerializer, this) },
|
||||
{ internalSerialFormat.decodeFromString(keySerializer, this) },
|
||||
{ internalSerialFormat.decodeFromString(valueSerializer, this) },
|
||||
helper
|
||||
)
|
||||
|
||||
fun <Key, Value> KeyValuesAndroidRepo(
|
||||
tableName: String,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
helper: SQLiteOpenHelper
|
||||
) = OneToManyAndroidRepo(tableName, keySerializer, valueSerializer, helper)
|
||||
|
@@ -86,6 +86,12 @@ class MapWriteOneToManyKeyValueRepo<Key, Value>(
|
||||
override suspend fun clear(k: Key) {
|
||||
map.remove(k) ?.also { _onDataCleared.emit(k) }
|
||||
}
|
||||
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
map.forEach { (k, values) ->
|
||||
if (values.remove(v)) _onValueRemoved.emit(k to v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MapOneToManyKeyValueRepo<Key, Value>(
|
||||
|
@@ -67,6 +67,15 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun clearWithValue(v: Value) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
clearWithValueRoute,
|
||||
),
|
||||
BodyPair(valueSerializer, v),
|
||||
Unit.serializer(),
|
||||
)
|
||||
|
||||
override suspend fun set(toSet: Map<Key, List<Value>>) = unifiedRequester.unipost(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
@@ -75,4 +84,4 @@ class KtorWriteOneToManyKeyValueRepo<Key, Value> (
|
||||
BodyPair(keyValueMapSerializer, toSet),
|
||||
Unit.serializer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -14,4 +14,5 @@ const val onDataClearedRoute = "onDataCleared"
|
||||
const val addRoute = "add"
|
||||
const val removeRoute = "remove"
|
||||
const val clearRoute = "clear"
|
||||
const val setRoute = "set"
|
||||
const val clearWithValueRoute = "clearWithValue"
|
||||
const val setRoute = "set"
|
||||
|
@@ -72,6 +72,17 @@ fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
|
||||
}
|
||||
}
|
||||
|
||||
post(clearWithValueRoute) {
|
||||
unifiedRouter.apply {
|
||||
val v = uniload(valueSerializer)
|
||||
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.clearWithValue(v),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(setRoute) {
|
||||
unifiedRouter.apply {
|
||||
val obj = uniload(keyValueMapSerializer)
|
||||
|
Reference in New Issue
Block a user