Compare commits

...

43 Commits

Author SHA1 Message Date
6a89ffaa8a rework of context exceptions 2020-12-22 23:17:28 +06:00
d5a8d0f4d4 Revert "unworking version"
This reverts commit 9739bd871e.
2020-12-22 22:45:43 +06:00
9739bd871e unworking version 2020-12-22 22:45:36 +06:00
632d2545d4 start 0.4.15 2020-12-22 21:39:43 +06:00
ecfa273a81 Merge pull request #33 from InsanusMokrassar/0.4.14
0.4.14
2020-12-22 16:00:50 +06:00
a36828116e safely fixes 2020-12-22 15:42:11 +06:00
14aa9ca26c update documentation 2020-12-22 15:21:25 +06:00
069e51f2ff add defaultSafelyExceptionHandler and SafelyExceptionHandler 2020-12-22 15:16:06 +06:00
a15cbdfb1a update versions 2020-12-22 14:55:01 +06:00
4af8114eda start 0.4.14 2020-12-22 14:48:02 +06:00
90dc84e900 Merge pull request #32 from InsanusMokrassar/0.4.13
0.4.13
2020-12-15 13:39:22 +06:00
67c595b440 small refactor in expand/collapse 2020-12-15 13:26:34 +06:00
830b7aee56 horizontal expand/collapse 2020-12-15 13:25:08 +06:00
1890608cb3 start 0.4.13 2020-12-15 12:49:50 +06:00
bd396959a9 Merge pull request #31 from InsanusMokrassar/0.4.12
0.4.12
2020-12-14 21:11:57 +06:00
d5fe19f0a5 update changelog 2020-12-14 20:20:30 +06:00
46bfb09415 launchSynchronously signature update 2020-12-14 20:06:46 +06:00
a60cb596d1 add functionality to selector 2020-12-14 20:01:37 +06:00
6f9d5e2d5f add itemSelectionFlow 2020-12-14 19:45:14 +06:00
80bc226ee1 selector docs and several refactorings 2020-12-14 19:36:07 +06:00
12e37184e1 add selector 2020-12-14 19:17:16 +06:00
25e9345d02 start 0.4.12 2020-12-14 18:44:24 +06:00
ccc4d030c3 Merge pull request #30 from InsanusMokrassar/0.4.11
0.4.11
2020-12-07 22:44:29 +06:00
90c0817b6d add clamp 2020-12-07 22:39:23 +06:00
527f7bbafe start 0.4.11 2020-12-07 22:36:32 +06:00
765a32729f Update README.md 2020-12-05 19:01:36 +06:00
de783f77a2 Update README.md 2020-12-05 18:59:28 +06:00
de4c8d104c Set theme jekyll-theme-cayman 2020-12-05 18:57:46 +06:00
88c8c28f45 Update README.md 2020-12-05 18:57:12 +06:00
57b36826d1 Merge pull request #29 from InsanusMokrassar/0.4.10
0.4.10
2020-12-05 18:36:24 +06:00
f81a2f309b add new unseWithValues extensions 2020-12-05 18:12:17 +06:00
5fc760f4a5 fix build and add filestandardkeyvaluerepo unsetWithValues realization 2020-12-05 18:00:27 +06:00
091cb38339 WriteStandardKeyValueRepo#unsetWithValues 2020-12-05 17:52:37 +06:00
3ae9b3e576 update klock dependency 2020-12-05 17:29:49 +06:00
b03b4cbeec start 0.4.10 2020-12-05 17:25:43 +06:00
f2c1b3c76a Merge pull request #27 from InsanusMokrassar/0.4.9
add Synchronously
2020-12-02 17:29:27 +06:00
f3bec34882 add Synchronously 2020-12-02 17:28:59 +06:00
d6aa9fe9c2 Merge pull request #26 from InsanusMokrassar/0.4.9
0.4.9
2020-12-02 16:41:50 +06:00
2d5304a770 BroadcastChannel -> MutableSharedFlow in repos 2020-12-02 16:39:04 +06:00
88f2c16c82 crud exposed realization open fun asObject 2020-12-02 16:28:08 +06:00
490c318d1c update ktor 2020-12-02 16:21:31 +06:00
8beaf61a08 start 0.4.9 2020-12-02 16:13:54 +06:00
8b61c984eb Merge pull request #25 from InsanusMokrassar/0.4.8
0.4.8
2020-12-01 15:52:07 +06:00
33 changed files with 714 additions and 101 deletions

View File

@@ -1,5 +1,64 @@
# Changelog
## 0.4.15
* `Coroutines`:
* `safely`:
* `SafelyExceptionHandlerKey` has been deprecated
* `SafelyExceptionHandler` has been deprecated
* `ContextSafelyExceptionHandlerKey` has been added
* `ContextSafelyExceptionHandler` has been added
* `safelyWithContextExceptionHandler` has been added
## 0.4.14
* `Versions`:
* `Kotlin`: `1.4.20` -> `1.4.21`
* `Ktor`: `1.4.3` -> `1.5.0`
* `Klock`: `2.0.1` -> `2.0.2`
* `Coroutines`:
* Add global variable `defaultSafelyExceptionHandler`
* Add `SafelyExceptionHandlerKey` and `SafelyExceptionHandler` classes to be able to overwrite
`defaultSafelyExceptionHandler` using context of coroutine
## 0.4.13
* `Common`
* `Android`
* Add expand/collapse functionality for horizontal expand/collapse
## 0.4.12
* `Coroutines`
* `JVM`
* Update `launchSynchronously` signature
* `Selector`
* Project created
## 0.4.11
* `Common`
* Add `clamp` function
## 0.4.10
* `Versions`:
* `Klock`: `2.0.0` -> `2.0.1`
* `Repo`
* Repo `WriteStandardKeyValueRepo` got new method `unsetWithValues`
## 0.4.9
* `Versions`:
* `Ktor`: `1.4.2` -> `1.4.3`
* `Coroutines`:
* `launchSynchronously` has been added in JVM
* `Repo`
* `Common`
* In repos different usages of `BroadcastChannel`s has been replaced with `MutableSharedFlow`
* `Exposed`
* `asObject` open fun has been added in CRUD realization
## 0.4.8
* `Versions`:

View File

@@ -1 +1,36 @@
# MicroUtils
# MicroUtils
This is a library with collection of tools for working in Kotlin environment. First of all, this library collection is oriented to use next technologies:
* [`Kotlin Coroutines`](https://github.com/Kotlin/kotlinx.coroutines)
* [`Kotlin Serialization`](https://github.com/Kotlin/kotlinx.serialization)
* [`Kotlin Exposed`](https://github.com/JetBrains/Exposed)
* [`Ktor`](https://ktor.io)
<details>
<summary> <b>Android environment</b> </summary>
You always can look at the <a href="https://github.com/InsanusMokrassar/MicroUtils/blob/master/gradle.properties#L24-L34">properties file</a> to get information about current project dependencies, compile and build tools for `Android` target.
</details>
## Projects
* `common` contains common tools for platform which usually are absent out-of-the-box when you starting project
* `selector` contains tools to use `Selector` interface with things like `RecyclerView` in android or other selection needs
* `coroutines` is a module for `Kotlin Coroutines` with different things like subscribing on flows (`onEach` + `launchIn` shortcut :) )
* `ktor` is a set of modules for `client`s and `server`s
* `mime_types` is NOT lightweight set of `MimeType`s with a lot of different objected and serializable (with `Kotlin Serialization`) mime types
* `pagination` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for lightweight pagination
* `repos` is a complex of modules (explanation in [Complex modules structure](#complex-modules-structure) section) for `KeyValue`/`OneToMany`/`CRUD` repos created to be able to exclude some heavy dependencies when you need some simple and lightweight typical repositories
## Complex modules structure
Most of complex modules are built with next hierarchy:
* `common` submodule for `API` things which are common for all platforms
* `exposed` submodule contains realizations for exposed tables
* `ktor` submodule is usually unavailable directly, because it contains its own submodules for clients and servers
* `common` part contains routes which are common for clients and servers
* `client` submodule contains clients which are usually using `UnifiedRequester` to make requests using routes from `ktor/common` module and some internal logic of requests
* `server` submodule (in most cases `JVM`-only) contains some extensions for `Route` instances which usually will give opportunity to proxy internet requests from `ktor/client` realization to some proxy object

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -0,0 +1,10 @@
package dev.inmo.micro_utils.common
@Suppress("NOTHING_TO_INLINE")
inline fun <T : Comparable<T>> T.clamp(min: T, max: T): T {
return when {
this < min -> min
this > max -> max
else -> this
}
}

View File

@@ -5,23 +5,44 @@ import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.Transformation
@PreviewFeature
fun View.expand(
private fun View.performExpand(
duration: Long = 500,
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT,
onMeasured: View.() -> Unit,
onPerformAnimation: View.(interpolatedTime: Float, t: Transformation?) -> Unit
) {
measure(targetWidth, targetHeight)
val measuredHeight: Int = measuredHeight
layoutParams.height = 0
visibility = View.VISIBLE
onMeasured()
show()
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
super.applyTransformation(interpolatedTime, t)
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
onPerformAnimation(interpolatedTime, t)
requestLayout()
}
override fun willChangeBounds(): Boolean = true
}
a.duration = duration
startAnimation(a)
}
private fun View.performCollapse(
duration: Long = 500,
onPerformAnimation: View.(interpolatedTime: Float, t: Transformation?) -> Unit
) {
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
if (interpolatedTime == 1f) {
gone()
} else {
onPerformAnimation(interpolatedTime, t)
requestLayout()
}
}
override fun willChangeBounds(): Boolean {
return true
}
@@ -32,27 +53,58 @@ fun View.expand(
startAnimation(a)
}
@PreviewFeature
fun View.expand(
duration: Long = 500,
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
) {
var measuredHeight = 0
performExpand(
duration,
targetWidth,
targetHeight,
{
measuredHeight = this.measuredHeight
}
) { interpolatedTime, _ ->
layoutParams.height = if (interpolatedTime == 1f) targetHeight else (measuredHeight * interpolatedTime).toInt()
}
}
@PreviewFeature
fun View.expandHorizontally(
duration: Long = 500,
targetWidth: Int = ViewGroup.LayoutParams.MATCH_PARENT,
targetHeight: Int = ViewGroup.LayoutParams.WRAP_CONTENT
) {
var measuredWidth = 0
performExpand(
duration,
targetWidth,
targetHeight,
{
measuredWidth = this.measuredWidth
}
) { interpolatedTime, _ ->
layoutParams.width = if (interpolatedTime == 1f) targetWidth else (measuredWidth * interpolatedTime).toInt()
}
}
@PreviewFeature
fun View.collapse(duration: Long = 500) {
val initialHeight: Int = measuredHeight
val a: Animation = object : Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
if (interpolatedTime == 1f) {
visibility = View.GONE
} else {
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
requestLayout()
}
}
override fun willChangeBounds(): Boolean {
return true
}
performCollapse(duration) { interpolatedTime, _ ->
layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
}
}
a.duration = duration
startAnimation(a)
@PreviewFeature
fun View.collapseHorizontally(duration: Long = 500) {
val initialWidth: Int = measuredWidth
performCollapse(duration) { interpolatedTime, _ ->
layoutParams.width = initialWidth - (initialWidth * interpolatedTime).toInt()
}
}
@PreviewFeature
@@ -74,3 +126,15 @@ fun View.toggleExpandState(duration: Long = 500): Boolean = if (isCollapsed) {
collapse(duration)
false
}
/**
* @return true in case of expanding
*/
@PreviewFeature
fun View.toggleExpandHorizontallyState(duration: Long = 500): Boolean = if (isCollapsed) {
expandHorizontally(duration)
true
} else {
collapseHorizontally(duration)
false
}

View File

@@ -19,7 +19,7 @@ fun <T> CoroutineScope.actor(
inline fun <T> CoroutineScope.safeActor(
channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = {},
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
crossinline block: suspend (T) -> Unit
): Channel<T> = actor(
channelCapacity

View File

@@ -16,7 +16,7 @@ inline fun <T> Flow<T>.subscribe(scope: CoroutineScope, noinline block: suspend
*/
inline fun <T> Flow<T>.subscribeSafely(
scope: CoroutineScope,
noinline onException: ExceptionHandler<Unit> = { throw it },
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend (T) -> Unit
) = subscribe(scope) {
safely(onException) {

View File

@@ -1,23 +1,123 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
typealias ExceptionHandler<T> = suspend (Throwable) -> T
/**
* This instance will be used in all calls of [safely] where exception handler has not been passed
*/
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
/**
* Key for [SafelyExceptionHandler] which can be used in [CoroutineContext.get] to get current default
* [SafelyExceptionHandler]
*/
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
class SafelyExceptionHandlerKey<T> : CoroutineContext.Key<SafelyExceptionHandler<T>>
/**
* Shortcut for creating instance of [SafelyExceptionHandlerKey]
*/
@Suppress("NOTHING_TO_INLINE")
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandlerKey", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
inline fun <T> safelyExceptionHandlerKey() = SafelyExceptionHandlerKey<T>()
/**
* Wrapper for [ExceptionHandler] which can be used in [CoroutineContext] to set local (for [CoroutineContext]) default
* [ExceptionHandler]. To get it use [CoroutineContext.get] with key [SafelyExceptionHandlerKey]
*
* @see SafelyExceptionHandlerKey
* @see ExceptionHandler
*/
@Deprecated("This method will be useless in future major update", ReplaceWith("ContextSafelyExceptionHandler", "dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler"))
class SafelyExceptionHandler<T>(
val handler: ExceptionHandler<T>
) : CoroutineContext.Element {
override val key: CoroutineContext.Key<*> = safelyExceptionHandlerKey<T>()
}
/**
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandler
*/
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
/**
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandlerKey
*/
class ContextSafelyExceptionHandler(
val handler: ExceptionHandler<Unit>
) : CoroutineContext.Element {
override val key: CoroutineContext.Key<*>
get() = ContextSafelyExceptionHandlerKey
}
/**
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
* exists
*
* @see ContextSafelyExceptionHandler
* @see ContextSafelyExceptionHandlerKey
*/
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
/**
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
* already contains [ContextSafelyExceptionHandler], [ContextSafelyExceptionHandler.handler] will be used BEFORE
* [contextExceptionHandler] in case of exception.
*
* After all, will be called [withContext] method with created [ContextSafelyExceptionHandler] and block which will call
* [safely] method with [safelyExceptionHandler] as onException parameter and [block] as execution block
*/
suspend fun <T> safelyWithContextExceptionHandler(
contextExceptionHandler: ExceptionHandler<Unit>,
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T
): T {
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
ContextSafelyExceptionHandler {
oldHandler(it)
contextExceptionHandler(it)
}
} ?: ContextSafelyExceptionHandler(contextExceptionHandler)
return withContext(contextSafelyExceptionHandler) {
safely(safelyExceptionHandler, block)
}
}
/**
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
*
* Priorities of [ExceptionHandler]s:
*
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
* * [defaultSafelyExceptionHandler]
*
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
* exception will be available for catching
*
* @see defaultSafelyExceptionHandler
* @see safelyWithoutExceptions
* @see safelyWithContextExceptionHandler
*/
suspend inline fun <T> safely(
noinline onException: ExceptionHandler<T> = { throw it },
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
): T {
return try {
supervisorScope(block)
} catch (e: Throwable) {
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
onException(e)
}
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T): T {
var throwable: Throwable? = null
var result: T? = null
val objectToSynchronize = java.lang.Object()
val launchCallback = {
launch {
safely(
{
throwable = it
}
) {
result = block()
}
synchronized(objectToSynchronize) {
objectToSynchronize.notifyAll()
}
}
}
synchronized(objectToSynchronize) {
launchCallback()
objectToSynchronize.wait()
}
throw throwable ?: return result!!
}
fun <T> launchSynchronously(block: suspend CoroutineScope.() -> T): T = CoroutineScope(Dispatchers.Default).launchSynchronously(block)

View File

@@ -0,0 +1,38 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.test.Test
class HandleSafelyCoroutineContextTest {
@Test
fun testHandleSafelyCoroutineContext() {
val scope = CoroutineScope(Dispatchers.Default)
var contextHandlerHappen = false
var localHandlerHappen = false
var defaultHandlerHappen = false
defaultSafelyExceptionHandler = {
defaultHandlerHappen = true
throw it
}
val contextHandler: ExceptionHandler<Unit> = {
contextHandlerHappen = true
}
val checkJob = scope.launch {
safelyWithContextExceptionHandler(contextHandler) {
safely(
{
localHandlerHappen = true
}
) {
error("That must happen :)")
}
println(coroutineContext)
error("That must happen too:)")
}
}
launchSynchronously { checkJob.join() }
assert(contextHandlerHappen)
assert(localHandlerHappen)
assert(defaultHandlerHappen)
}
}

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.coroutines
import kotlin.test.Test
import kotlin.test.assertEquals
class LaunchSynchronouslyTest {
@Test
fun testRunInCoroutine() {
(0 .. 10000).forEach {
assertEquals(it, launchSynchronously { it })
}
}
}

View File

@@ -1,3 +1,5 @@
apply plugin: 'com.getkeepsafe.dexcount'
android {
compileSdkVersion "$android_compileSdkVersion".toInteger()
buildToolsVersion "$android_buildToolsVersion"

View File

@@ -6,14 +6,14 @@ kotlin.incremental.js=true
android.useAndroidX=true
android.enableJetifier=true
kotlin_version=1.4.20
kotlin_version=1.4.21
kotlin_coroutines_version=1.4.2
kotlin_serialisation_core_version=1.0.1
kotlin_exposed_version=0.28.1
ktor_version=1.4.2
ktor_version=1.5.0
klockVersion=2.0.0
klockVersion=2.0.2
github_release_plugin_version=2.2.12
@@ -28,17 +28,17 @@ appcompat_version=1.2.0
android_minSdkVersion=19
android_compileSdkVersion=30
android_buildToolsVersion=30.0.2
dexcount_version=2.0.0-RC1
dexcount_version=2.0.0
junit_version=4.12
test_ext_junit_version=1.1.2
espresso_core=3.3.0
# Dokka
dokka_version=1.4.10.2
dokka_version=1.4.20
# Project data
group=dev.inmo
version=0.4.8
android_code_version=12
version=0.4.15
android_code_version=19

View File

@@ -25,7 +25,7 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
val producerScope = this@channelFlow
do {
val reconnect = try {
safely ({ throw it }) {
safely {
ws(correctedUrl) {
for (received in incoming) {
when (received) {

View File

@@ -1,7 +1,6 @@
package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.*
import kotlinx.coroutines.flow.Flow
interface ReadStandardKeyValueRepo<Key, Value> : Repo {
@@ -20,6 +19,7 @@ interface WriteStandardKeyValueRepo<Key, Value> : Repo {
suspend fun set(toSet: Map<Key, Value>)
suspend fun unset(toUnset: List<Key>)
suspend fun unsetWithValues(toUnset: List<Value>)
}
typealias WriteKeyValueRepo<Key,Value> = WriteStandardKeyValueRepo<Key, Value>
@@ -35,5 +35,17 @@ suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unset(
vararg k: Key
) = unset(k.toList())
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value>
suspend inline fun <Key, Value> WriteStandardKeyValueRepo<Key, Value>.unsetWithValues(
vararg v: Value
) = unsetWithValues(v.toList())
interface StandardKeyValueRepo<Key, Value> : ReadStandardKeyValueRepo<Key, Value>, WriteStandardKeyValueRepo<Key, Value> {
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
doWithPagination {
keys(v, it).also {
unset(it.results)
}
}
}
}
typealias KeyValueRepo<Key,Value> = StandardKeyValueRepo<Key, Value>

View File

@@ -105,6 +105,10 @@ open class MapperWriteStandardKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
k.toOutKey()
}
)
override suspend fun unsetWithValues(toUnset: List<FromValue>) = to.unsetWithValues(
toUnset.map { it.toOutValue() }
)
}
@Suppress("NOTHING_TO_INLINE")

View File

@@ -160,6 +160,18 @@ class FileWriteStandardKeyValueRepo(
}
}
}
override suspend fun unsetWithValues(toUnset: List<File>) {
val keys = toUnset.mapNotNull { v ->
val key = v.absolutePath.removePrefix(folder.absolutePath)
if (key != v.absolutePath) {
key
} else {
null
}
}
unset(keys)
}
}
@Warning("Files watching will not correctly works on Android Platform with version of API lower than API 26")

View File

@@ -10,21 +10,6 @@ import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
private data class CallbackContinuationPair<T> (
val callback: suspend SQLiteDatabase.() -> T,
val continuation: Continuation<T>
) {
suspend fun SQLiteDatabase.execute() {
safely(
{
continuation.resumeWithException(it)
}
) {
continuation.resume(callback())
}
}
}
class StandardSQLHelper(
context: Context,
name: String,

View File

@@ -3,20 +3,18 @@ package dev.inmo.micro_utils.repos.crud
import android.content.ContentValues
import dev.inmo.micro_utils.common.mapNotNullA
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.*
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
helper: StandardSQLHelper
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper) {
protected val newObjectsChannel = BroadcastChannel<ObjectType>(64)
protected val updateObjectsChannel = BroadcastChannel<ObjectType>(64)
protected val deleteObjectsIdsChannel = BroadcastChannel<IdType>(64)
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asFlow()
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asFlow()
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(64)
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
protected abstract suspend fun InputValueType.asContentValues(id: IdType? = null): ContentValues
@@ -42,7 +40,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
}
}.also {
it.forEach {
newObjectsChannel.send(it)
newObjectsChannel.emit(it)
}
}
}
@@ -59,7 +57,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
}
}
deleted.forEach {
deleteObjectsIdsChannel.send(it)
deleteObjectsIdsChannel.emit(it)
}
}
@@ -76,7 +74,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
}
}
return getById(id) ?.also {
updateObjectsChannel.send(it)
updateObjectsChannel.emit(it)
}
}
@@ -95,7 +93,7 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
getById(it.first)
}.also {
it.forEach {
updateObjectsChannel.send(it)
updateObjectsChannel.emit(it)
}
}
}

View File

@@ -8,10 +8,7 @@ import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.utils.paginate
import dev.inmo.micro_utils.pagination.utils.reverse
import dev.inmo.micro_utils.repos.StandardKeyValueRepo
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.*
private val cache = HashMap<String, KeyValueStore<*>>()
@@ -37,11 +34,11 @@ class KeyValueStore<T : Any> internal constructor (
null
}
private val onNewValueChannel = BroadcastChannel<Pair<String, T>>(Channel.BUFFERED)
private val onValueRemovedChannel = BroadcastChannel<String>(Channel.BUFFERED)
private val onNewValueChannel = MutableSharedFlow<Pair<String, T>>()
private val _onValueRemovedFlow = MutableSharedFlow<String>()
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asFlow()
override val onValueRemoved: Flow<String> = onValueRemovedChannel.asFlow()
override val onNewValue: Flow<Pair<String, T>> = onNewValueChannel.asSharedFlow()
override val onValueRemoved: Flow<String> = _onValueRemovedFlow.asSharedFlow()
init {
cachedData ?.let {
@@ -131,7 +128,7 @@ class KeyValueStore<T : Any> internal constructor (
}
}
toSet.forEach { (k, v) ->
onNewValueChannel.send(k to v)
onNewValueChannel.emit(k to v)
}
}
@@ -139,6 +136,18 @@ class KeyValueStore<T : Any> internal constructor (
sharedPreferences.edit {
toUnset.forEach { remove(it) }
}
toUnset.forEach { onValueRemovedChannel.send(it) }
toUnset.forEach { _onValueRemovedFlow.emit(it) }
}
override suspend fun unsetWithValues(toUnset: List<T>) {
val keysToRemove = sharedPreferences.all.mapNotNull { if (it.value in toUnset) it.key else null }
sharedPreferences.edit {
keysToRemove.map {
remove(it)
}
}
keysToRemove.forEach {
_onValueRemovedFlow.emit(it)
}
}
}

View File

@@ -32,11 +32,11 @@ class AndroidSQLStandardVersionsRepoProxy(
}
}
override suspend fun getTableVersion(table: String): Int? = database.writableTransaction {
override suspend fun getTableVersion(tableName: String): Int? = database.writableTransaction {
select(
tableName,
this@AndroidSQLStandardVersionsRepoProxy.tableName,
selection = "$tableNameColumnName=?",
selectionArgs = arrayOf(table),
selectionArgs = arrayOf(tableName),
limit = limitClause(1)
).use {
if (it.moveToFirst()) {
@@ -47,16 +47,16 @@ class AndroidSQLStandardVersionsRepoProxy(
}
}
override suspend fun updateTableVersion(table: String, version: Int) {
override suspend fun updateTableVersion(tableName: String, version: Int) {
database.writableTransaction {
val updated = update(
tableName,
this@AndroidSQLStandardVersionsRepoProxy.tableName,
contentValuesOf(tableVersionColumnName to version),
"$tableNameColumnName=?",
arrayOf(table)
arrayOf(tableName)
) > 0
if (!updated) {
insert(tableName, null, contentValuesOf(tableNameColumnName to table, tableVersionColumnName to version))
insert(this@AndroidSQLStandardVersionsRepoProxy.tableName, null, contentValuesOf(tableNameColumnName to tableName, tableVersionColumnName to version))
}
}
}

View File

@@ -2,7 +2,6 @@ package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteStandardCRUDRepo
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement
@@ -25,8 +24,10 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
@Deprecated("Will be removed in near major update. Override open fun with the same name instead")
abstract val InsertStatement<Number>.asObject: ObjectType
abstract val selectByIds: SqlExpressionBuilder.(List<out IdType>) -> Op<Boolean>
protected open fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType = asObject
abstract val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement)
@@ -34,7 +35,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
private fun createWithoutNotification(value: InputValueType): ObjectType {
return transaction(database) {
insert { insert(value, it) }.asObject
insert { insert(value, it) }.asObject(value)
}
}
@@ -42,10 +43,8 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
onBeforeCreate(values)
return transaction(db = database) {
values.map { value -> createWithoutNotification(value) }
}.also {
it.forEach {
newObjectsChannel.emit(it)
}
}.onEach {
newObjectsChannel.emit(it)
}
}
@@ -83,13 +82,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
return (
transaction(db = database) {
values.map { (id, value) -> updateWithoutNotification(id, value) }
}.filter {
it != null
} as List<ObjectType>
).also {
it.forEach {
updateObjectsChannel.emit(it)
}
}.filterNotNull()
).onEach {
updateObjectsChannel.emit(it)
}
}
protected open suspend fun onBeforeDelete(ids: List<IdType>) {}

View File

@@ -60,4 +60,16 @@ open class ExposedKeyValueRepo<Key, Value>(
_onValueRemoved.emit(it)
}
}
override suspend fun unsetWithValues(toUnset: List<Value>) {
transaction(database) {
toUnset.flatMap {
val keys = select { valueColumn.eq(it) }.mapNotNull { it[keyColumn] }
deleteWhere { keyColumn.inList(keys) }
keys
}
}.distinct().forEach {
_onValueRemoved.emit(it)
}
}
}

View File

@@ -76,6 +76,15 @@ class WriteMapKeyValueRepo<Key, Value>(
map.remove(k) ?.also { _ -> _onValueRemoved.emit(k) }
}
}
override suspend fun unsetWithValues(toUnset: List<Value>) {
map.forEach {
if (it.value in toUnset) {
map.remove(it.key)
_onValueRemoved.emit(it.key)
}
}
}
}
class MapKeyValueRepo<Key, Value>(

View File

@@ -7,6 +7,7 @@ import dev.inmo.micro_utils.repos.*
import io.ktor.client.HttpClient
import kotlinx.serialization.*
@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
class KtorStandartKeyValueRepo<K, V> (
baseUrl: String,
baseSubpart: String,

View File

@@ -17,6 +17,7 @@ class KtorWriteStandardKeyValueRepo<K, V> (
) : WriteStandardKeyValueRepo<K, V> {
private val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
private val keysListSerializer = ListSerializer(keySerializer)
private val valuesListSerializer = ListSerializer(valueSerializer)
constructor(
baseUrl: String,
@@ -55,4 +56,13 @@ class KtorWriteStandardKeyValueRepo<K, V> (
BodyPair(keysListSerializer, toUnset),
Unit.serializer()
)
override suspend fun unsetWithValues(toUnset: List<V>) = unifiedRequester.unipost(
buildStandardUrl(
baseUrl,
unsetWithValuesRoute,
),
BodyPair(valuesListSerializer, toUnset),
Unit.serializer()
)
}

View File

@@ -9,4 +9,5 @@ const val countRoute = "count"
const val onNewValueRoute = "onNewValue"
const val onValueRemovedRoute = "onValueRemoved"
const val setRoute = "set"
const val unsetRoute = "unset"
const val unsetRoute = "unset"
const val unsetWithValuesRoute = "unsetWithValues"

View File

@@ -19,6 +19,7 @@ fun <K, V> Route.configureWriteStandardKeyValueRepoRoutes (
) {
val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
val keysListSerializer = ListSerializer(keySerializer)
val valuesListSerializer = ListSerializer(valueSerializer)
unifiedRouter.apply {
includeWebsocketHandling(
onNewValueRoute,
@@ -50,6 +51,14 @@ fun <K, V> Route.configureWriteStandardKeyValueRepoRoutes (
unianswer(Unit.serializer(), originalRepo.unset(toUnset))
}
}
post(unsetWithValuesRoute) {
unifiedRouter.apply {
val toUnset = uniload(valuesListSerializer)
unianswer(Unit.serializer(), originalRepo.unsetWithValues(toUnset))
}
}
}
fun <K, V> Route.configureWriteStandartKeyValueRepoRoutes (

View File

@@ -0,0 +1,17 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
}
}
}
}

View File

@@ -0,0 +1,168 @@
package dev.inmo.micro_utils.selector
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
/**
* Unified interface which can be used in any system which require some selection functionality
*/
interface Selector<T> {
val selectedItems: List<T>
val itemSelected: SharedFlow<T>
val itemUnselected: SharedFlow<T>
suspend fun toggleSelection(element: T)
suspend fun forceSelect(element: T)
suspend fun forceDeselect(element: T)
suspend fun clearSelection()
}
@Suppress("NOTHING_TO_INLINE")
inline operator fun <T> Selector<T>.contains(element: T) = selectedItems.contains(element)
@Suppress("NOTHING_TO_INLINE")
inline fun <T> Selector<T>.nothingSelected(): Boolean = selectedItems.isEmpty()
suspend inline fun <T> Selector<T>.toggleSelection(elements: List<T>) = elements.forEach { toggleSelection(it) }
suspend inline fun <T> Selector<T>.forceSelect(elements: List<T>) = elements.forEach { forceSelect(it) }
suspend inline fun <T> Selector<T>.forceDeselect(elements: List<T>) = elements.forEach { forceDeselect(it) }
suspend inline fun <T> Selector<T>.toggleSelection(firstElement: T, vararg elements: T) = toggleSelection(listOf(firstElement) + elements.toList())
suspend inline fun <T> Selector<T>.forceSelect(firstElement: T, vararg elements: T) = forceSelect(listOf(firstElement) + elements.toList())
suspend inline fun <T> Selector<T>.forceDeselect(firstElement: T, vararg elements: T) = forceDeselect(listOf(firstElement) + elements.toList())
/**
* Realization of [Selector] with one or without selected element. This realization will always have empty
* [selectedItems] when nothing selected and one element in [selectedItems] when something selected. Contains
* [selectedItem] value for simple access to currently selected item.
*
* On calling of [toggleSelection] previous selection will be erased and [itemUnselected] will emit this element.
*
* @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection]
*/
class SingleSelector<T>(
selectedItem: T? = null,
safeChanges: Boolean = true
) : Selector<T> {
var selectedItem: T? = selectedItem
private set
override val selectedItems: List<T>
get() = selectedItem ?.let { listOf(it) } ?: emptyList()
private val _itemSelected = MutableSharedFlow<T>()
override val itemSelected: SharedFlow<T> = _itemSelected.asSharedFlow()
private val _itemUnselected = MutableSharedFlow<T>()
override val itemUnselected: SharedFlow<T> = _itemUnselected.asSharedFlow()
private val mutex = if (safeChanges) {
Mutex()
} else {
null
}
override suspend fun forceDeselect(element: T) {
mutex ?.lock()
if (selectedItem == element) {
selectedItem = null
_itemUnselected.emit(element)
}
mutex ?.unlock()
}
override suspend fun forceSelect(element: T) {
mutex ?.lock()
if (selectedItem != element) {
selectedItem = element
_itemSelected.emit(element)
}
mutex ?.unlock()
}
override suspend fun toggleSelection(element: T) {
mutex ?.lock()
if (selectedItem == element) {
selectedItem = null
_itemUnselected.emit(element)
} else {
val previouslySelected = selectedItem
selectedItem = null
if (previouslySelected != null) {
_itemUnselected.emit(previouslySelected)
}
selectedItem = element
_itemSelected.emit(element)
}
mutex ?.unlock()
}
override suspend fun clearSelection() {
selectedItem ?.let { forceDeselect(it) }
}
}
/**
* Realization of [Selector] with multiple selected elements. On calling of [toggleSelection] this realization will select passed element OR deselect it if it is already in
* [selectedItems]
*
* @param safeChanges Set to false to disable using of [mutex] for synchronizing changes on [toggleSelection]
*/
class MultipleSelector<T>(
selectedItems: List<T> = emptyList(),
safeChanges: Boolean = true
) : Selector<T> {
private val _selectedItems: MutableList<T> = selectedItems.toMutableList()
override val selectedItems: List<T> = _selectedItems
private val _itemSelected = MutableSharedFlow<T>()
override val itemSelected: SharedFlow<T> = _itemSelected.asSharedFlow()
private val _itemUnselected = MutableSharedFlow<T>()
override val itemUnselected: SharedFlow<T> = _itemUnselected.asSharedFlow()
private val mutex = if (safeChanges) {
Mutex()
} else {
null
}
override suspend fun forceDeselect(element: T) {
mutex ?.lock()
if (_selectedItems.remove(element)) {
_itemUnselected.emit(element)
}
mutex ?.unlock()
}
override suspend fun forceSelect(element: T) {
mutex ?.lock()
if (element !in _selectedItems && _selectedItems.add(element)) {
_itemSelected.emit(element)
}
mutex ?.unlock()
}
override suspend fun toggleSelection(element: T) {
mutex ?.lock()
if (_selectedItems.remove(element)) {
_itemUnselected.emit(element)
} else {
_selectedItems.add(element)
_itemSelected.emit(element)
}
mutex ?.unlock()
}
override suspend fun clearSelection() {
mutex ?.lock()
val preSelectedItems = _selectedItems.toList()
_selectedItems.clear()
preSelectedItems.forEach { _itemUnselected.emit(it) }
mutex ?.unlock()
}
}
@Suppress("FunctionName", "NOTHING_TO_INLINE")
inline fun <T> Selector(
multiple: Boolean,
safeChanges: Boolean = true
): Selector<T> = if (multiple) {
MultipleSelector(safeChanges = safeChanges)
} else {
SingleSelector(safeChanges = safeChanges)
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.selector
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
/**
* @return Returned [SharedFlow] will emit true when [element] has been selected in [this] [Selector] and will emit
* false when this [element] was deselected
*
* @see [Selector]
* @see [Selector.itemSelected]
* @see [Selector.itemUnselected]
*/
fun <T> Selector<T>.itemSelectionFlow(element: T, scope: CoroutineScope): SharedFlow<Boolean> = MutableSharedFlow<Boolean>().apply {
itemSelected.onEach { if (it == element) emit(true) }.launchIn(scope)
itemUnselected.onEach { if (it == element) emit(false) }.launchIn(scope)
}.asSharedFlow()

View File

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

View File

@@ -2,6 +2,7 @@ rootProject.name='micro_utils'
String[] includes = [
":common",
":selector:common",
":pagination:common",
":pagination:exposed",
":pagination:ktor:common",