Compare commits

..

1 Commits

Author SHA1 Message Date
3e24cf1768 launchInCurrentThread 2022-12-27 09:28:37 +06:00
192 changed files with 1077 additions and 5840 deletions

View File

@@ -1,233 +1,9 @@
# Changelog # Changelog
## 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
* `Versions`:
* `Korlibs`: `3.4.0` -> `4.0.1`
## 0.18.4
* `Koin`:
* New extension `lazyInject`
## 0.18.3
* `Versions`:
* `Serialization`: `1.5.0` -> `1.5.1`
* `Android Cor Ktx`: `1.10.0` -> `1.10.1`
## 0.18.2
* `Startup`:
* Now internal `Json` is fully customizable
## 0.18.1
* `Common`:
* Add `MapDiff`
* `Coroutines`:
* Add `SmartMutex`
## 0.18.0
**ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED**
* `Versions`:
* `Android Fragments`: `1.5.6` -> `1.5.7`
* `Ktor`:
* `Server`:
* Now it is possible to take query parameters as list
* `Repos`:
* `Common`:
* New `WriteKeyValuesRepo.removeWithValue`
* `Cache`:
* Rename full caching factories functions to `fullyCached`
## 0.17.8
* `Versions`:
* `Ktor`: `2.2.4` -> `2.3.0`
## 0.17.7
* `Versions`:
* `Android CoreKtx`: `1.9.0` -> `1.10.0`
* `Startup`:
* Add support of `linuxX64` and `mingwX64` platforms
## 0.17.6
* `Versions`:
* `Kotlin`: `1.8.10` -> `1.8.20`
* `KSLog`: `1.0.0` -> `1.1.1`
* `Compose`: `1.3.1` -> `1.4.0`
* `Koin`: `3.3.2` -> `3.4.0`
* `RecyclerView`: `1.2.1` -> `1.3.0`
* `Fragment`: `1.5.5` -> `1.5.6`
* Experimentally (`!!!`) add `linuxX64` and `mingwX64` targets
## 0.17.5
* `Common`:
* Conversations of number primitives with bounds care
* `Repos`:
* `Common`:
* By default, `getAll` for repos will take all the size of repo as page size
* New extension for all built-in repos `maxPagePagination`
* All the repos got `getAll` functions
## 0.17.4
* `Serialization`:
* `Mapper`:
* Module inited
* `Versions`:
* `Compose`: `1.3.1-rc02` -> `1.3.1`
## 0.17.3
* `Common`:
* Add `fixed` extensions for `Float` and `Double`
* New function `emptyDiff`
* Now you may pass custom `comparisonFun` to all `diff` functions
## 0.17.2
* `FSM`:
* `DefaultStatesManager.onUpdateContextsConflictResolver` and `DefaultStatesManager.onStartContextsConflictResolver` now return `false` by default
## 0.17.1
* **Hotfix** for absence of jvm dependencies in android modules
* `Versions`:
* `Ktor`: `2.2.3` -> `2.2.4`
## 0.17.0
* `Versions`:
* `Kotlin`: `1.7.20` -> `1.8.10`
* `Serialization`: `1.4.1` -> `1.5.0`
* `KSLog`: `0.5.4` -> `1.0.0`
* `AppCompat`: `1.6.0` -> `1.6.1`
## 0.16.13
* `Repos`:
* `Generator`:
* Module has been created
## 0.16.12
* `Repos`:
* `Exposed`:
* `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach
* `Koin`:
* `Generator`:
* Module has been created
## 0.16.11
* `LanguageCodes`:
* In android and JVM targets now available `toJavaLocale` and from Java `Locale` conversations from/to
`IetfLanguageCode`
## 0.16.10
* `Repos`:
* `Cache`:
* New transformer type: `ReadCRUDFromKeyValueRepo`
* New transformer type: `ReadKeyValueFromCRUDRepo`
* `Pagination`:
* New `paginate` extensions with `reversed` support for `List`/`Set`
## 0.16.9
* `Versions`:
* `Koin`: `3.2.2` -> `3.3.2`
* `AppCompat`: `1.5.1` -> `1.6.0`
* `Ktor`:
* `Client`
* `HttpResponse.bodyOrNull` now retrieve callback to check if body should be received or null
* New extension `HttpResponse.bodyOrNullOnNoContent`
## 0.16.8
* `Versions`:
* `Ktor`: `2.2.2` -> `2.2.3`
* `Ktor`:
* `Client`
* Fixes in `HttpClient.uniUpload`
* `Server`
* Fixes in `PartData.FileItem.download`
* `Repos`:
* `Cache`:
* New type of caches: `FallbackCacheRepo`
* Fixes in `Write*` variants of cached repos
* New type `ActionWrapper`
* New `AutoRecache*` classes for all types of repos as `FallbackCacheRepo`s
* `Common`:
* New transformations for key-value and key-values vice-verse
* Fixes in `FileReadKeyValueRepo`
## 0.16.7
* `Common`:
* New extensions `ifTrue`/`ifFalse`/`alsoIfTrue`/`alsoIfFalse`/`letIfTrue`/`letIfFalse`
* `Diff` now is serializable
* Add `IndexedValue` serializer
* `repeatOnFailure` extending: now you may pass any lambda to check if continue to try/do something
* `Compose`:
* New extension `MutableState.asState`
* `Coroutines`:
* `Compose`:
* All the `Flow` conversations to compose `State`/`MutableState`/`SnapshotStateList`/`List` got several new
parameters
* `Flow.toMutableState` now is deprecated in favor to `asMutableComposeState`
* `Repos`:
* `Cache`:
* New type `FullCacheRepo`
* New type `CommonCacheRepo`
* `CacheRepo` got `invalidate` method. It will fully reload `FullCacheRepo` and just clear `CommonCacheRepo`
* New extensions `KVCache.actualizeAll`
## 0.16.6
* `Startup`:
* `Launcher`:
* Improvements in `StartLauncherPlugin#start` methods
* Add opportunity to pass second argument on `JVM` platform as log level
* `Repos`:
* `Ktor`:
* `Client`:
* All clients repos got opportunity to customize their flows
* `Exposed`:
* Extensions `eqOrIsNull` and `neqOrIsNotNull` for `Column`
## 0.16.5
* `Versions`:
* `Ktor`: `2.2.1` -> `2.2.2`
## 0.16.4
* `Coroutines`:
* Create `launchInCurrentThread`
## 0.16.3 ## 0.16.3
* `Coroutines`:
* Create `launchInCurrentThread`
* `Startup`: * `Startup`:
* `Launcher`: * `Launcher`:
* All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now * All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now

View File

@@ -9,7 +9,6 @@ buildscript {
dependencies { dependencies {
classpath libs.buildscript.kt.gradle classpath libs.buildscript.kt.gradle
classpath libs.buildscript.kt.serialization classpath libs.buildscript.kt.serialization
classpath libs.buildscript.kt.ksp
classpath libs.buildscript.jb.dokka classpath libs.buildscript.jb.dokka
classpath libs.buildscript.gh.release classpath libs.buildscript.gh.release
classpath libs.buildscript.android.gradle classpath libs.buildscript.android.gradle
@@ -23,7 +22,6 @@ allprojects {
mavenCentral() mavenCentral()
google() google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" }
} }
// temporal crutch until legacy tests will be stabled or legacy target will be removed // temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

@@ -18,18 +18,6 @@ kotlin {
api project(":micro_utils.coroutines") api project(":micro_utils.coroutines")
api libs.android.fragment api libs.android.fragment
} }
dependsOn jvmMain
}
linuxX64Main {
dependencies {
api libs.okio
}
}
mingwX64Main {
dependencies {
api libs.okio
}
} }
} }
} }

View File

@@ -1,10 +0,0 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
/**
* Converts current [MutableState] to immutable [State] using [derivedStateOf]
*/
fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value }

View File

@@ -2,8 +2,6 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
private inline fun <T> getObject( private inline fun <T> getObject(
additional: MutableList<T>, additional: MutableList<T>,
iterator: Iterator<T> iterator: Iterator<T>
@@ -26,19 +24,14 @@ private inline fun <T> getObject(
* *
* @see calculateDiff * @see calculateDiff
*/ */
@Serializable
data class Diff<T> internal constructor( data class Diff<T> internal constructor(
val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>, val removed: List<IndexedValue<T>>,
/** /**
* Old-New values pairs * Old-New values pairs
*/ */
val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>, val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>> val added: List<IndexedValue<T>>
) { )
fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty()
}
fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList())
private inline fun <T> performChanges( private inline fun <T> performChanges(
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>, potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
@@ -47,14 +40,14 @@ private inline fun <T> performChanges(
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>, changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
removedList: MutableList<IndexedValue<T>>, removedList: MutableList<IndexedValue<T>>,
addedList: MutableList<IndexedValue<T>>, addedList: MutableList<IndexedValue<T>>,
comparisonFun: (T?, T?) -> Boolean strictComparison: Boolean
) { ) {
var i = -1 var i = -1
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) { for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
i++ i++
val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value) val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value) val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
if (oldOneEqualToNewObject || newOneEqualToOldObject) { if (oldOneEqualToNewObject || newOneEqualToOldObject) {
changedList.addAll( changedList.addAll(
potentialChanges.take(i).mapNotNull { potentialChanges.take(i).mapNotNull {
@@ -108,7 +101,7 @@ private inline fun <T> performChanges(
*/ */
fun <T> Iterable<T>.calculateDiff( fun <T> Iterable<T>.calculateDiff(
other: Iterable<T>, other: Iterable<T>,
comparisonFun: (T?, T?) -> Boolean strictComparison: Boolean = false
): Diff<T> { ): Diff<T> {
var i = -1 var i = -1
var j = -1 var j = -1
@@ -136,7 +129,7 @@ fun <T> Iterable<T>.calculateDiff(
} }
when { when {
comparisonFun(oldObject, newObject) -> { oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
changedObjects.addAll(potentiallyChangedObjects.map { changedObjects.addAll(potentiallyChangedObjects.map {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
it as Pair<IndexedValue<T>, IndexedValue<T>> it as Pair<IndexedValue<T>, IndexedValue<T>>
@@ -147,49 +140,23 @@ fun <T> Iterable<T>.calculateDiff(
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) }) potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
val previousOldsAdditionsSize = additionalInOld.size val previousOldsAdditionsSize = additionalInOld.size
val previousNewsAdditionsSize = additionalInNew.size val previousNewsAdditionsSize = additionalInNew.size
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun) performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
i -= (additionalInOld.size - previousOldsAdditionsSize) i -= (additionalInOld.size - previousOldsAdditionsSize)
j -= (additionalInNew.size - previousNewsAdditionsSize) j -= (additionalInNew.size - previousNewsAdditionsSize)
} }
} }
} }
potentiallyChangedObjects.add(null to null) potentiallyChangedObjects.add(null to null)
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun) performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList()) return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
} }
/**
* Calculating [Diff] object
*
* @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different
* objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links
*/
fun <T> Iterable<T>.calculateDiff(
other: Iterable<T>,
strictComparison: Boolean = false
): Diff<T> = calculateDiff(
other,
comparisonFun = if (strictComparison) {
{ t1, t2 ->
t1 === t2
}
} else {
{ t1, t2 ->
t1 === t2 || t1 == t2 // small optimization for cases when t1 and t2 are the same - comparison will be faster potentially
}
}
)
inline fun <T> Iterable<T>.diff( inline fun <T> Iterable<T>.diff(
other: Iterable<T>, other: Iterable<T>,
strictComparison: Boolean = false strictComparison: Boolean = false
): Diff<T> = calculateDiff(other, strictComparison) ): Diff<T> = calculateDiff(other, strictComparison)
inline fun <T> Iterable<T>.diff(
other: Iterable<T>,
noinline comparisonFun: (T?, T?) -> Boolean
): Diff<T> = calculateDiff(other, comparisonFun)
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, strictComparison = false) inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true) inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
/** /**
@@ -199,23 +166,6 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T> other: Iterable<T>
) = calculateDiff(other, strictComparison = true) ) = calculateDiff(other, strictComparison = true)
/**
* Applies [diff] to [this] [MutableList]
*/
fun <T> MutableList<T>.applyDiff(
diff: Diff<T>
) {
for (i in diff.removed.indices.sortedDescending()) {
removeAt(diff.removed[i].index)
}
diff.added.forEach { (i, t) ->
add(i, t)
}
diff.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}
/** /**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this] * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list * mutable list
@@ -224,26 +174,13 @@ fun <T> MutableList<T>.applyDiff(
source: Iterable<T>, source: Iterable<T>,
strictComparison: Boolean = false strictComparison: Boolean = false
): Diff<T> = calculateDiff(source, strictComparison).also { ): Diff<T> = calculateDiff(source, strictComparison).also {
applyDiff(it) for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}
it.added.forEach { (i, t) ->
add(i, t)
}
it.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
} }
/**
* This method call [calculateDiff] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
comparisonFun: (T?, T?) -> Boolean
): Diff<T> = calculateDiff(source, comparisonFun).also {
applyDiff(it)
}
/**
* Reverse [this] [Diff]. Result will contain [Diff.added] on [Diff.removed] (and vice-verse), all the
* [Diff.replaced] values will be reversed too
*/
fun <T> Diff<T>.reversed() = Diff(
removed = added,
replaced = replaced.map { it.second to it.first },
added = removed
)

View File

@@ -1,43 +0,0 @@
package dev.inmo.micro_utils.common
inline fun <T> Boolean.letIfTrue(block: () -> T): T? {
return if (this) {
block()
} else {
null
}
}
inline fun <T> Boolean.letIfFalse(block: () -> T): T? {
return if (this) {
null
} else {
block()
}
}
inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean {
letIfTrue(block)
return this
}
inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean {
letIfFalse(block)
return this
}
inline fun <T> Boolean.ifTrue(block: () -> T): T? {
return if (this) {
block()
} else {
null
}
}
inline fun <T> Boolean.ifFalse(block: () -> T): T? {
return if (this) {
null
} else {
block()
}
}

View File

@@ -1,30 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.PairSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
class IndexedValueSerializer<T>(private val subSerializer: KSerializer<T>) : KSerializer<IndexedValue<T>> {
private val originalSerializer = PairSerializer(Int.serializer(), subSerializer)
override val descriptor: SerialDescriptor
get() = originalSerializer.descriptor
override fun deserialize(decoder: Decoder): IndexedValue<T> {
val pair = originalSerializer.deserialize(decoder)
return IndexedValue(
pair.first,
pair.second
)
}
override fun serialize(encoder: Encoder, value: IndexedValue<T>) {
originalSerializer.serialize(
encoder,
Pair(value.index, value.value)
)
}
}

View File

@@ -1,135 +0,0 @@
package dev.inmo.micro_utils.common
/**
* Contains diff based on the comparison of objects with the same [K].
*
* @param removed Contains map with keys removed from parent map
* @param changed Contains map with keys values changed new map in comparison with old one
* @param added Contains map with new keys and values
*/
data class MapDiff<K, V> @Warning(warning) constructor(
val removed: Map<K, V>,
val changed: Map<K, Pair<V, V>>,
val added: Map<K, V>
) {
fun isEmpty() = removed.isEmpty() && changed.isEmpty() && added.isEmpty()
inline fun isNotEmpty() = !isEmpty()
companion object {
private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing"
fun <K, V> empty() = MapDiff<K, V>(emptyMap(), emptyMap(), emptyMap())
}
}
private inline fun <K, V> createCompareFun(
strictComparison: Boolean
): (K, V, V) -> Boolean = if (strictComparison) {
{ _, first, second -> first === second }
} else {
{ _, first, second -> first == second }
}
/**
* Compare [this] [Map] with the [other] one in principle when [other] is newer than [this]
*
* @param compareFun Will be used to determine changed values
*/
fun <K, V> Map<K, V>.diff(
other: Map<K, V>,
compareFun: (K, V, V) -> Boolean
): MapDiff<K, V> {
val removed: Map<K, V> = (keys - other.keys).associateWith {
getValue(it)
}
val added: Map<K, V> = (other.keys - keys).associateWith {
other.getValue(it)
}
val changed = keys.intersect(other.keys).mapNotNull {
val old = getValue(it)
val new = other.getValue(it)
if (compareFun(it, old, new)) {
return@mapNotNull null
} else {
it to (old to new)
}
}.toMap()
return MapDiff(
removed,
changed,
added
)
}
/**
* Compare [this] [Map] with the [other] one in principle when [other] is newer than [this]
*
* @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard
* `equals` will be used
*/
fun <K, V> Map<K, V>.diff(
other: Map<K, V>,
strictComparison: Boolean = false
): MapDiff<K, V> = diff(
other,
compareFun = createCompareFun(strictComparison)
)
/**
* Will apply [mapDiff] to [this] [MutableMap]
*/
fun <K, V> MutableMap<K, V>.applyDiff(
mapDiff: MapDiff<K, V>
) {
mapDiff.apply {
removed.keys.forEach { remove(it) }
changed.forEach { (k, oldNew) ->
put(k, oldNew.second)
}
added.forEach { (k, new) ->
put(k, new)
}
}
}
/**
* Will apply changes with [from] map into [this] one
*
* @param compareFun Will be used to determine changed values
*
* @return [MapDiff] applied to [this] [MutableMap]
*/
fun <K, V> MutableMap<K, V>.applyDiff(
from: Map<K, V>,
compareFun: (K, V, V) -> Boolean
): MapDiff<K, V> {
return diff(from, compareFun).also {
applyDiff(it)
}
}
/**
* Will apply changes with [from] map into [this] one
*
* @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard
* `equals` will be used
*
* @return [MapDiff] applied to [this] [MutableMap]
*/
fun <K, V> MutableMap<K, V>.applyDiff(
from: Map<K, V>,
strictComparison: Boolean = false
): MapDiff<K, V> = applyDiff(
from,
compareFun = createCompareFun(strictComparison)
)
/**
* Reverse [this] [MapDiff]. Result will contain [MapDiff.added] on [MapDiff.removed] (and vice-verse), all the
* [MapDiff.changed] values will be reversed too
*/
fun <K, V> MapDiff<K, V>.reversed(): MapDiff<K, V> = MapDiff(
removed = added,
changed = changed.mapValues { (_, oldNew) -> oldNew.second to oldNew.first },
added = removed
)

View File

@@ -1,28 +0,0 @@
package dev.inmo.micro_utils.common
/**
* Convert [this] [Long] to [Int] with bounds of [Int.MIN_VALUE] and [Int.MAX_VALUE]
*/
fun Long.toCoercedInt(): Int = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
/**
* Convert [this] [Long] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
*/
fun Long.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()).toShort()
/**
* Convert [this] [Long] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
*/
fun Long.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()).toByte()
/**
* Convert [this] [Int] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
*/
fun Int.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort()
/**
* Convert [this] [Int] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
*/
fun Int.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()).toByte()
/**
* Convert [this] [Short] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
*/
fun Short.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toShort(), Byte.MAX_VALUE.toShort()).toByte()

View File

@@ -1,27 +1,5 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
/**
* Executes the given [action] until getting of successful result specified number of [times].
*
* A zero-based index of current iteration is passed as a parameter to [action].
*/
inline fun <R> repeatOnFailure(
onFailure: (Throwable) -> Boolean,
action: () -> R
): Result<R> {
do {
runCatching {
action()
}.onFailure {
if (!onFailure(it)) {
return Result.failure(it)
}
}.onSuccess {
return Result.success(it)
}
} while (true)
}
/** /**
* Executes the given [action] until getting of successful result specified number of [times]. * Executes the given [action] until getting of successful result specified number of [times].
* *
@@ -32,23 +10,12 @@ inline fun <R> repeatOnFailure(
onEachFailure: (Throwable) -> Unit = {}, onEachFailure: (Throwable) -> Unit = {},
action: (Int) -> R action: (Int) -> R
): Optional<R> { ): Optional<R> {
var i = 0 repeat(times) {
val result = repeatOnFailure( runCatching {
{ action(it)
onEachFailure(it) }.onFailure(onEachFailure).onSuccess {
if (i < times) { return Optional.presented(it)
i++
true
} else {
false
} }
} }
) { return Optional.absent()
action(i)
}
return if (result.isSuccess) {
Optional.presented(result.getOrThrow())
} else {
Optional.absent()
}
} }

View File

@@ -1,6 +0,0 @@
package dev.inmo.micro_utils.common
val FixedSignsRange = 0 .. 100
expect fun Float.fixed(signs: Int): Float
expect fun Double.fixed(signs: Int): Double

View File

@@ -1,4 +0,0 @@
package dev.inmo.micro_utils.common
actual fun Float.fixed(signs: Int): Float = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toFloat()
actual fun Double.fixed(signs: Int): Double = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toDouble()

View File

@@ -1,12 +0,0 @@
package dev.inmo.micro_utils.common
import java.math.BigDecimal
import java.math.RoundingMode
actual fun Float.fixed(signs: Int): Float = BigDecimal.valueOf(this.toDouble())
.setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
.toFloat();
actual fun Double.fixed(signs: Int): Double = BigDecimal.valueOf(this)
.setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
.toDouble();

View File

@@ -1,36 +0,0 @@
package dev.inmo.micro_utils.common
import okio.FileSystem
import okio.Path
import okio.use
actual typealias MPPFile = Path
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = FileSystem.SYSTEM.openReadOnly(this).use {
it.size()
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
FileSystem.SYSTEM.read(this) {
readByteArray()
}
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@@ -1,26 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf
import platform.posix.sprintf
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@@ -1,36 +0,0 @@
package dev.inmo.micro_utils.common
import okio.FileSystem
import okio.Path
import okio.use
actual typealias MPPFile = Path
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = FileSystem.SYSTEM.openReadOnly(this).use {
it.size()
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
FileSystem.SYSTEM.read(this) {
readByteArray()
}
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@@ -1,26 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf
import platform.posix.sprintf
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@@ -3,58 +3,24 @@ package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import dev.inmo.micro_utils.common.applyDiff import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.coroutines.ExceptionHandler
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
/**
* Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList]
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asMutableComposeListState( inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
scope: CoroutineScope, scope: CoroutineScope
useContextOnChange: CoroutineContext? = Dispatchers.Main,
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
): SnapshotStateList<T> { ): SnapshotStateList<T> {
val state = mutableStateListOf<T>() val state = mutableStateListOf<T>()
val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let { subscribeSafelyWithoutExceptions(scope) {
{
withContext(useContextOnChange) {
state.applyDiff(it) state.applyDiff(it)
} }
}
} ?: {
state.applyDiff(it)
}
subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
return state return state
} }
/**
* In fact, it is just classcast of [asMutableComposeListState] to [List]
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*
* @return Changing in time [List] which follow [Flow] values
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asComposeList( inline fun <reified T> Flow<List<T>>.asComposeList(
scope: CoroutineScope, scope: CoroutineScope
useContextOnChange: CoroutineContext? = Dispatchers.Main, ): List<T> = asMutableComposeListState(scope)
noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
): List<T> = asMutableComposeListState(scope, useContextOnChange, onException)

View File

@@ -1,94 +1,35 @@
package dev.inmo.micro_utils.coroutines.compose package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.micro_utils.common.compose.asState
import dev.inmo.micro_utils.coroutines.ExceptionHandler
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
import dev.inmo.micro_utils.coroutines.doInUI
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
/**
* Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow]
*
* @param initial First value which will be passed to the result [MutableState]
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
fun <T> Flow<T>.asMutableComposeState( fun <T> Flow<T>.asMutableComposeState(
initial: T, initial: T,
scope: CoroutineScope, scope: CoroutineScope
useContextOnChange: CoroutineContext? = Dispatchers.Main,
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): MutableState<T> { ): MutableState<T> {
val state = mutableStateOf(initial) val state = mutableStateOf(initial)
val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let { subscribeSafelyWithoutExceptions(scope) { state.value = it }
{
withContext(useContextOnChange) {
state.value = it
}
}
} ?: {
state.value = it
}
subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock)
return state return state
} }
/**
* Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow].
* This conversation will pass its [StateFlow.value] as the first value
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asMutableComposeState( inline fun <T> StateFlow<T>.asMutableComposeState(
scope: CoroutineScope, scope: CoroutineScope
useContextOnChange: CoroutineContext? = Dispatchers.Main, ): MutableState<T> = asMutableComposeState(value, scope)
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException)
/**
* Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state
*
* @param initial First value which will be passed to the result [State]
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
fun <T> Flow<T>.asComposeState( fun <T> Flow<T>.asComposeState(
initial: T, initial: T,
scope: CoroutineScope, scope: CoroutineScope
useContextOnChange: CoroutineContext? = Dispatchers.Main,
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): State<T> { ): State<T> {
val state = asMutableComposeState(initial, scope, useContextOnChange, onException) val state = asMutableComposeState(initial, scope)
return state.asState() return derivedStateOf { state.value }
} }
/**
* Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value
*
* @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
* @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
* change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
* @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
*/
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asComposeState( inline fun <T> StateFlow<T>.asComposeState(
scope: CoroutineScope, scope: CoroutineScope
useContextOnChange: CoroutineContext? = Dispatchers.Main, ): State<T> = asComposeState(value, scope)
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
): State<T> = asComposeState(value, scope, useContextOnChange, onException)

View File

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

View File

@@ -1,136 +0,0 @@
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.withLock
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* It is interface which will work like classic [Mutex], but in difference have [lockStateFlow] for listening of the
* [SmartMutex] state.
*
* There is [Mutable] and [Immutable] realizations. In case you are owner and manager current state of lock, you need
* [Mutable] [SmartMutex]. Otherwise, [Immutable].
*
* Any [Mutable] [SmartMutex] may produce its [Immutable] variant which will contains [lockStateFlow] equal to its
* [Mutable] creator
*/
sealed interface SmartMutex {
val lockStateFlow: StateFlow<Boolean>
/**
* * True - locked
* * False - unlocked
*/
val isLocked: Boolean
get() = lockStateFlow.value
/**
* Immutable variant of [SmartMutex]. In fact will depend on the owner of [lockStateFlow]
*/
class Immutable(override val lockStateFlow: StateFlow<Boolean>) : SmartMutex
/**
* Mutable variant of [SmartMutex]. 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 [isLocked] and its internal [_lockStateFlow]
*/
class Mutable(locked: Boolean = false) : SmartMutex {
private val _lockStateFlow = MutableStateFlow<Boolean>(locked)
override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow()
private val internalChangesMutex = Mutex()
fun immutable() = Immutable(lockStateFlow)
/**
* Holds call until this [SmartMutex] will be re-locked. That means that while [isLocked] == true, [holds] will
* wait for [isLocked] == false and then try to lock
*/
suspend fun lock() {
do {
waitUnlock()
val shouldContinue = internalChangesMutex.withLock {
if (_lockStateFlow.value) {
true
} else {
_lockStateFlow.value = true
false
}
}
} while (shouldContinue && currentCoroutineContext().isActive)
}
/**
* Will try to lock this [SmartMutex] immediataly
*
* @return True if lock was successful. False otherwise
*/
suspend fun tryLock(): Boolean {
return if (!_lockStateFlow.value) {
internalChangesMutex.withLock {
if (!_lockStateFlow.value) {
_lockStateFlow.value = true
true
} else {
false
}
}
} else {
false
}
}
/**
* If [isLocked] == true - will change it to false and return true. If current call will not unlock this
* [SmartMutex] - false
*/
suspend fun unlock(): Boolean {
return if (_lockStateFlow.value) {
internalChangesMutex.withLock {
if (_lockStateFlow.value) {
_lockStateFlow.value = false
true
} else {
false
}
}
} else {
false
}
}
}
}
/**
* Will call [SmartMutex.Mutable.lock], then execute [action] and return the result after [SmartMutex.Mutable.unlock]
*/
@OptIn(ExperimentalContracts::class)
suspend inline fun <T> SmartMutex.Mutable.withLock(action: () -> T): T {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
lock()
try {
return action()
} finally {
unlock()
}
}
/**
* 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 SmartMutex.waitUnlock() = lockStateFlow.first { !it }

View File

@@ -1,26 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import org.w3c.dom.Image
suspend fun preloadImage(src: String): Image {
val image = Image()
image.src = src
val job = Job()
image.addEventListener("load", {
runCatching { job.complete() }
})
runCatchingSafely {
job.join()
}.onFailure {
if (it is CancellationException) {
image.src = ""
}
}.getOrThrow()
return image
}

View File

@@ -11,7 +11,6 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
api project(":micro_utils.common") api project(":micro_utils.common")
api libs.krypto
} }
} }
jsMain { jsMain {

View File

@@ -1,8 +1,6 @@
package dev.inmo.micro_utils.crypto package dev.inmo.micro_utils.crypto
import korlibs.crypto.md5
typealias MD5 = String typealias MD5 = String
fun SourceBytes.md5(): MD5 = md5().hexLower expect fun SourceBytes.md5(): MD5
fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower fun SourceString.md5(): MD5 = encodeToByteArray().md5()

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.crypto
/**
* @suppress
*/
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())

View File

@@ -0,0 +1,12 @@
package dev.inmo.micro_utils.crypto
import java.math.BigInteger
import java.security.MessageDigest
/**
* @suppress
*/
actual fun SourceBytes.md5(): MD5 = BigInteger(
1,
MessageDigest.getInstance("MD5").digest(this)
).toString(16)

View File

@@ -1,5 +1,30 @@
apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'com.getkeepsafe.dexcount'
ext {
jvmKotlinFolderFile = {
String sep = File.separator
return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
}
enableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
if (jvmKotlinFolder.exists()) {
android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
}
}
disableIncludingJvmCodeInAndroidPart = {
File jvmKotlinFolder = jvmKotlinFolderFile()
String[] oldDirs = android.sourceSets.main.java.srcDirs
android.sourceSets.main.java.srcDirs = []
for (oldDir in oldDirs) {
if (oldDir != jvmKotlinFolder.path) {
android.sourceSets.main.java.srcDirs += oldDir
}
}
}
}
android { android {
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger() compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
buildToolsVersion libs.versions.android.props.buildTools.get() buildToolsVersion libs.versions.android.props.buildTools.get()
@@ -33,4 +58,10 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString() jvmTarget = JavaVersion.VERSION_1_8.toString()
} }
sourceSets {
String sep = File.separator
main.java.srcDirs += "src${sep}main${sep}kotlin"
enableIncludingJvmCodeInAndroidPart()
}
} }

View File

@@ -23,12 +23,12 @@ allprojects {
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle" mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle" mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle" publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle" publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
} }
} }

View File

@@ -48,8 +48,8 @@ interface DefaultStatesManagerRepo<T : State> {
*/ */
open class DefaultStatesManager<T : State>( open class DefaultStatesManager<T : State>(
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(), protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> false }, protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true },
protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> false } protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
) : StatesManager<T> { ) : StatesManager<T> {
protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0) protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow() override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.19.2 version=0.16.3
android_code_version=198 android_code_version=171

View File

@@ -1,42 +1,37 @@
[versions] [versions]
kt = "1.8.20" kt = "1.7.20"
kt-serialization = "1.5.1" kt-serialization = "1.4.1"
kt-coroutines = "1.6.4" kt-coroutines = "1.6.4"
kslog = "1.1.1" kslog = "0.5.4"
jb-compose = "1.4.0" jb-compose = "1.2.2"
jb-exposed = "0.41.1" jb-exposed = "0.41.1"
jb-dokka = "1.8.10" jb-dokka = "1.7.20"
korlibs = "4.0.3" klock = "3.4.0"
uuid = "0.7.1" uuid = "0.6.0"
ktor = "2.3.1" ktor = "2.2.1"
gh-release = "2.4.1" gh-release = "2.4.1"
koin = "3.4.1" koin = "3.2.2"
okio = "3.3.0" android-gradle = "7.3.0"
dexcount = "3.1.0"
ksp = "1.8.20-1.0.11" android-coreKtx = "1.9.0"
kotlin-poet = "1.14.2" android-recyclerView = "1.2.1"
android-appCompat = "1.5.1"
android-gradle = "7.4.2" android-fragment = "1.5.5"
dexcount = "4.0.0" android-espresso = "3.4.0"
android-test = "1.1.3"
android-coreKtx = "1.10.1"
android-recyclerView = "1.3.0"
android-appCompat = "1.6.1"
android-fragment = "1.5.7"
android-espresso = "3.5.1"
android-test = "1.1.5"
android-props-minSdk = "21" android-props-minSdk = "21"
android-props-compileSdk = "33" android-props-compileSdk = "33"
android-props-buildTools = "33.0.2" android-props-buildTools = "33.0.0"
[libraries] [libraries]
@@ -69,8 +64,7 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" } klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
@@ -89,18 +83,9 @@ android-test-junit = { module = "androidx.test.ext:junit", version.ref = "androi
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" } kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" } kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
# ksp dependencies
kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
# Buildscript
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" } buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" } buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
buildscript-kt-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" } buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" } buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }

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.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -1,118 +0,0 @@
apply plugin: 'maven-publish'
task javadocJar(type: Jar) {
from javadoc
classifier = 'javadoc'
}
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
classifier = 'sources'
}
publishing {
publications {
maven(MavenPublication) {
from components.java
artifact javadocJar
artifact sourcesJar
pom {
resolveStrategy = Closure.DELEGATE_FIRST
description = "It is set of projects with micro tools for avoiding of routines coding"
name = "${project.name}"
url = "https://github.com/InsanusMokrassar/MicroUtils/"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git"
url = "https://github.com/InsanusMokrassar/MicroUtils.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Aleksei Ovsiannikov"
email = "ovsyannikov.alexey95@gmail.com"
}
developer {
id = "000Sanya"
name = "Syrov Aleksandr"
email = "000sanya.000sanya@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
}
}
}
repositories {
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
credentials {
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
}
}
}
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
maven {
name = "Gitea"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
}
}
}
}
}
}
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
}
}
}

View File

@@ -1 +0,0 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}

View File

@@ -1,100 +0,0 @@
# Koin generator
It is Kotlin Symbol Processing generator for `Koin` module in `MicroUtils`.
1. [What may do this generator](#what-may-do-this-generator)
2. [How to add generator](#how-to-add-generator)
## What may do this generator
Let's imagine you want to have shortcuts in koin, to get something easily:
```kotlin
val koin: Koin// some initialization
val someUrl = koin.serverUrl
```
So, in that case you may mark containing file with next annotation (in the beginning of file):
```kotlin
@file:GenerateKoinDefinition("serverUrl", String::class, nullable = false)
```
If file is called like `Sample.kt`, will be generated file `GeneratedDefinitionsSample.kt` with next content:
```kotlin
public val Scope.serverUrl: String
get() = get(named("serverUrl"))
public val Koin.serverUrl: String
get() = get(named("serverUrl"))
public fun Module.serverUrlSingle(createdAtStart: Boolean = false,
definition: Definition<String>): KoinDefinition<String> =
single(named("serverUrl"), createdAtStart = createdAtStart, definition = definition)
public fun Module.serverUrlFactory(definition: Definition<String>):
KoinDefinition<String> = factory(named("serverUrl"), definition = definition)
```
Besides, you may use the generics:
```kotlin
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false)
```
Will generate:
```kotlin
public val Scope.sampleInfo: Sample<G1, G2>
get() = get(named("sampleInfo"))
public val Koin.sampleInfo: Sample<G1, G2>
get() = get(named("sampleInfo"))
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
definition: Definition<Sample<G1, G2>>): KoinDefinition<Sample<G1, G2>> =
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
```
In case you wish not to generate single:
```kotlin
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false, generateSingle = false)
```
And you will take next code:
```kotlin
public val Scope.sampleInfo: Sample<G1, G2>
get() = get(named("sampleInfo"))
public val Koin.sampleInfo: Sample<G1, G2>
get() = get(named("sampleInfo"))
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
```
## How to add generator
**Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project**
**Note: $microutils_version in the version of MicroUtils library in your project**
1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`)
2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"`
3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.koin.generator:$microutils_version`:
```groovy
dependencies {
add("kspCommonMainMetadata", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in commonMain of your multiplatform module
add("kspJvm", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in main of your JVM module
}
ksp { // this generator do not require any arguments and we should left `ksp` empty
}
```

View File

@@ -1,15 +0,0 @@
plugins {
id "org.jetbrains.kotlin.jvm"
}
apply from: "$publishJvmOnlyPath"
repositories {
mavenCentral()
}
dependencies {
api project(":micro_utils.koin")
api libs.kotlin.poet
api libs.ksp
}

View File

@@ -1,178 +0,0 @@
package dev.inmo.micro_utils.koin.generator
import com.google.devtools.ksp.KSTypeNotPresentException
import com.google.devtools.ksp.KSTypesNotPresentException
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSFile
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin
import org.koin.core.module.Module
import org.koin.core.scope.Scope
import java.io.File
import kotlin.reflect.KClass
class Processor(
private val codeGenerator: CodeGenerator
) : SymbolProcessor {
private val definitionClassName = ClassName("org.koin.core.definition", "Definition")
private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition")
@OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getSymbolsWithAnnotation(
GenerateKoinDefinition::class.qualifiedName!!
).filterIsInstance<KSFile>().forEach { ksFile ->
FileSpec.builder(
ksFile.packageName.asString(),
"GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}"
).apply {
addFileComment(
"""
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
TO REGENERATE IT JUST DELETE FILE
ORIGINAL FILE: ${ksFile.fileName}
""".trimIndent()
)
ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach {
val type = runCatching {
it.type.asTypeName()
}.getOrElse { e ->
if (e is KSTypeNotPresentException) {
e.ksType.toClassName()
} else {
throw e
}
}
val targetType = runCatching {
type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type))
}.getOrElse { e ->
when (e) {
is KSTypeNotPresentException -> e.ksType.toClassName()
}
if (e is KSTypesNotPresentException) {
type.parameterizedBy(*e.ksTypes.map { it.toTypeName() }.toTypedArray())
} else {
throw e
}
}.copy(
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)
addGetterProperty(Koin::class)
if (it.generateSingle) {
addFunction(
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 {
File(
File(ksFile.filePath).parent,
"GeneratedDefinitions${ksFile.fileName}"
).takeIf { !it.exists() } ?.apply {
parentFile.mkdirs()
writer().use { writer ->
it.writeTo(writer)
}
}
}
}
return emptyList()
}
}

View File

@@ -1,11 +0,0 @@
package dev.inmo.micro_utils.koin.generator
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
class Provider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
environment.codeGenerator
)
}

View File

@@ -1 +0,0 @@
dev.inmo.micro_utils.koin.generator.Provider

View File

@@ -1,27 +0,0 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
id "com.google.devtools.ksp"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.koin")
}
}
}
}
dependencies {
add("kspCommonMainMetadata", project(":micro_utils.koin.generator"))
}
ksp {
}

View File

@@ -1,38 +0,0 @@
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
// TO REGENERATE IT JUST DELETE FILE
// ORIGINAL FILE: Test.kt
package dev.inmo.micro_utils.koin.generator.test
import kotlin.Boolean
import kotlin.String
import org.koin.core.Koin
import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
/**
* @return Definition by key "sampleInfo"
*/
public val Scope.sampleInfo: Test<String>
get() = get(named("sampleInfo"))
/**
* @return Definition by key "sampleInfo"
*/
public val Koin.sampleInfo: Test<String>
get() = get(named("sampleInfo"))
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
*/
public fun Module.sampleInfoSingle(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"
*/
public fun Module.sampleInfoFactory(definition: Definition<Test<String>>):
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)

View File

@@ -1,13 +0,0 @@
@file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false)
package dev.inmo.micro_utils.koin.generator.test
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin
class Test<T>(
koin: Koin
) {
init {
koin.sampleInfo
}
}

View File

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

View File

@@ -1,40 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.definition.BeanDefinition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.scope.Scope
fun <T> Koin.get(definition: BeanDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.primaryType,
definition.qualifier,
parameters
)
fun <T> Koin.get(definition: InstanceFactory<T>, parameters: ParametersDefinition? = null): T = get(
definition.beanDefinition,
parameters
)
fun <T> Koin.get(definition: KoinDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.factory,
parameters
)
fun <T> Scope.get(definition: BeanDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.primaryType,
definition.qualifier,
parameters
)
fun <T> Scope.get(definition: InstanceFactory<T>, parameters: ParametersDefinition? = null): T = get(
definition.beanDefinition,
parameters
)
fun <T> Scope.get(definition: KoinDefinition<T>, parameters: ParametersDefinition? = null): T = get(
definition.factory,
parameters
)

View File

@@ -1,28 +0,0 @@
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 GenerateKoinDefinition(
val name: String,
val type: KClass<*>,
vararg val typeArgs: KClass<*>,
val nullable: Boolean = true,
val generateSingle: Boolean = true,
val generateFactory: Boolean = true
)

View File

@@ -1,7 +1,6 @@
package dev.inmo.micro_utils.koin package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier import org.koin.core.qualifier.Qualifier
@@ -14,7 +13,7 @@ inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: Qualifier? = null, qualifier: Qualifier? = null,
bindFilter: (KClass<*>) -> Boolean = { true }, bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T> noinline definition: Definition<T>
): KoinDefinition<*> { ): Pair<Module, InstanceFactory<*>> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
} }
@@ -22,7 +21,7 @@ inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: String, qualifier: String,
bindFilter: (KClass<*>) -> Boolean = { true }, bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T> noinline definition: Definition<T>
): KoinDefinition<*> { ): Pair<Module, InstanceFactory<*>> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
} }

View File

@@ -1,7 +1,6 @@
package dev.inmo.micro_utils.koin package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module import org.koin.core.module.Module
import kotlin.reflect.KClass import kotlin.reflect.KClass
@@ -9,6 +8,6 @@ import kotlin.reflect.KClass
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds( inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
bindFilter: (KClass<*>) -> Boolean = { true }, bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T> noinline definition: Definition<T>
): KoinDefinition<*> { ): Pair<Module, InstanceFactory<*>> {
return factoryWithBinds(RandomQualifier(), bindFilter, definition) return factoryWithBinds(RandomQualifier(), bindFilter, definition)
} }

View File

@@ -1,27 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import org.koin.java.KoinJavaComponent
import kotlin.reflect.KClass
fun <T> lazyInject(
kClassFactory: () -> KClass<*>,
qualifier: Qualifier? = null,
parameters: ParametersDefinition? = null
): Lazy<T> {
return lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
KoinJavaComponent.get(kClassFactory().java, qualifier, parameters)
}
}
fun <T> lazyInject(
kClass: KClass<*>,
qualifier: Qualifier? = null,
parameters: ParametersDefinition? = null
): Lazy<T> = lazyInject({ kClass }, qualifier, parameters)
inline fun <reified T> lazyInject(
qualifier: Qualifier? = null,
noinline parameters: ParametersDefinition? = null
): Lazy<T> = lazyInject(T::class, qualifier, parameters)

View File

@@ -1,7 +1,6 @@
package dev.inmo.micro_utils.koin package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier import org.koin.core.qualifier.Qualifier
@@ -15,7 +14,7 @@ inline fun <reified T : Any> Module.singleWithBinds(
createdAtStart: Boolean = false, createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true }, bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T> noinline definition: Definition<T>
): KoinDefinition<*> { ): Pair<Module, InstanceFactory<*>> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
} }
@@ -25,7 +24,7 @@ inline fun <reified T : Any> Module.singleWithBinds(
createdAtStart: Boolean = false, createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true }, bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T> noinline definition: Definition<T>
): KoinDefinition<*> { ): Pair<Module, InstanceFactory<*>> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
} }

View File

@@ -1,7 +1,6 @@
package dev.inmo.micro_utils.koin package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition import org.koin.core.definition.Definition
import org.koin.core.definition.KoinDefinition
import org.koin.core.instance.InstanceFactory import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module import org.koin.core.module.Module
import kotlin.reflect.KClass import kotlin.reflect.KClass
@@ -10,6 +9,6 @@ inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds(
createdAtStart: Boolean = false, createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true }, bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T> noinline definition: Definition<T>
): KoinDefinition<*> { ): Pair<Module, InstanceFactory<*>> {
return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition) return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
} }

View File

@@ -15,20 +15,9 @@ kotlin {
api libs.ktor.client api libs.ktor.client
} }
} }
androidMain { androidMain {
dependsOn jvmMain dependsOn jvmMain
} }
linuxX64Main {
dependencies {
api internalProject("micro_utils.mime_types")
}
}
mingwX64Main {
dependencies {
api internalProject("micro_utils.mime_types")
}
}
} }
} }

View File

@@ -4,10 +4,6 @@ import io.ktor.client.call.body
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull( suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf {
statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK } status == HttpStatusCode.OK
) = takeIf(statusFilter) ?.body<T>() } ?.body<T>()
suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> {
it.status != HttpStatusCode.NoContent
}

View File

@@ -1,10 +1,6 @@
package dev.inmo.micro_utils.ktor.client package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filesize
import dev.inmo.micro_utils.ktor.common.input
import io.ktor.client.request.forms.InputProvider import io.ktor.client.request.forms.InputProvider
fun MPPFile.inputProvider(): InputProvider = InputProvider(filesize) { expect suspend fun MPPFile.inputProvider(): InputProvider
input()
}

View File

@@ -19,7 +19,7 @@ import kotlinx.coroutines.isActive
* connection. Must return true in case if must be reconnected. By default always reconnecting * connection. Must return true in case if must be reconnected. By default always reconnecting
*/ */
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties") @Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
inline fun <T : Any> openBaseWebSocketFlow( inline fun <reified T : Any> openBaseWebSocketFlow(
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
): Flow<T> { ): Flow<T> {
@@ -57,7 +57,7 @@ inline fun <reified T : Any> HttpClient.openWebSocketFlow(
): Flow<T> { ): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow") pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
return openBaseWebSocketFlow(checkReconnection) { return openBaseWebSocketFlow<T>(checkReconnection) {
val block: suspend DefaultClientWebSocketSession.() -> Unit = { val block: suspend DefaultClientWebSocketSession.() -> Unit = {
while (isActive) { while (isActive) {
send(receiveDeserialized<T>()) send(receiveDeserialized<T>())

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.*
import io.ktor.client.request.forms.InputProvider
import io.ktor.utils.io.core.ByteReadPacket
actual suspend fun MPPFile.inputProvider(): InputProvider = bytes().let {
InputProvider(it.size.toLong()) {
ByteReadPacket(it)
}
}

View File

@@ -7,3 +7,5 @@ import io.ktor.utils.io.streams.asInput
fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) { fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
inputStream().asInput() inputStream().asInput()
} }
actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync()

View File

@@ -17,11 +17,8 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters import io.ktor.http.Parameters
import io.ktor.http.content.PartData import io.ktor.http.content.PartData
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.serializer
import java.io.File import java.io.File
/** /**
@@ -32,7 +29,6 @@ import java.io.File
* in case you wish to pass other source of multipart binary data than regular file * in case you wish to pass other source of multipart binary data than regular file
* @suppress * @suppress
*/ */
@OptIn(InternalSerializationApi::class)
actual suspend fun <T> HttpClient.uniUpload( actual suspend fun <T> HttpClient.uniUpload(
url: String, url: String,
data: Map<String, Any>, data: Map<String, Any>,
@@ -44,8 +40,7 @@ actual suspend fun <T> HttpClient.uniUpload(
val withBinary = data.values.any { it is File || it is UniUploadFileInfo } val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
val formData = formData { val formData = formData {
for (k in data.keys) { data.forEach { (k, v) ->
val v = data[k] ?: continue
when (v) { when (v) {
is File -> append( is File -> append(
k, k,
@@ -65,7 +60,7 @@ actual suspend fun <T> HttpClient.uniUpload(
) )
else -> append( else -> append(
k, k,
stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v) stringFormat.encodeToString(v)
) )
} }
} }
@@ -90,7 +85,7 @@ actual suspend fun <T> HttpClient.uniUpload(
submitForm( submitForm(
url, url,
Parameters.build { Parameters.build {
for (it in formData) { formData.forEach {
val formItem = (it as PartData.FormItem) val formItem = (it as PartData.FormItem)
append(it.name!!, it.value) append(it.name!!, it.value)
} }

View File

@@ -1,40 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
import io.ktor.client.HttpClient
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
internal val MPPFile.mimeType: String
get() = getMimeTypeOrAny(filename.extension).raw
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: OnUploadCallback
): TemporalFileId {
val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData(
fullTempUploadDraftPath,
formData = formData {
append(
"data",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, file.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
}
)
}
) {
onUpload(onUpload)
}.bodyAsText()
return TemporalFileId(fileId)
}

View File

@@ -1,107 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.Progress
import io.ktor.client.HttpClient
import io.ktor.client.engine.mergeHeaders
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.InputProvider
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.request.headers
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.http.content.PartData
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import kotlinx.serialization.serializer
/**
* Will execute submitting of multipart data request
*
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
* in case you wish to pass other source of multipart binary data than regular file
* @suppress
*/
@OptIn(InternalSerializationApi::class)
actual suspend fun <T> HttpClient.uniUpload(
url: String,
data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>,
headers: Headers,
stringFormat: StringFormat,
onUpload: OnUploadCallback
): T? {
val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo }
val formData = formData {
for (k in data.keys) {
val v = data[k] ?: continue
when (v) {
is MPPFile -> append(
k,
v.inputProvider(),
Headers.build {
append(HttpHeaders.ContentType, v.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
}
)
is UniUploadFileInfo -> append(
k,
InputProvider(block = v.inputAllocator),
Headers.build {
append(HttpHeaders.ContentType, v.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
}
)
else -> append(
k,
stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
)
}
}
}
val requestBuilder: HttpRequestBuilder.() -> Unit = {
headers {
appendAll(headers)
}
onUpload { bytesSentTotal, contentLength ->
onUpload(bytesSentTotal, contentLength)
}
}
val response = if (withBinary) {
submitFormWithBinaryData(
url,
formData,
block = requestBuilder
)
} else {
submitForm(
url,
Parameters.build {
for (it in formData) {
val formItem = (it as PartData.FormItem)
append(it.name!!, it.value)
}
},
block = requestBuilder
)
}
return if (response.status == HttpStatusCode.OK) {
stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
} else {
null
}
}

View File

@@ -1,40 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
import io.ktor.client.HttpClient
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
internal val MPPFile.mimeType: String
get() = getMimeTypeOrAny(filename.extension).raw
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: OnUploadCallback
): TemporalFileId {
val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData(
fullTempUploadDraftPath,
formData = formData {
append(
"data",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, file.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
}
)
}
) {
onUpload(onUpload)
}.bodyAsText()
return TemporalFileId(fileId)
}

View File

@@ -1,107 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.Progress
import io.ktor.client.HttpClient
import io.ktor.client.engine.mergeHeaders
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.InputProvider
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.request.headers
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.http.content.PartData
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import kotlinx.serialization.serializer
/**
* Will execute submitting of multipart data request
*
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
* in case you wish to pass other source of multipart binary data than regular file
* @suppress
*/
@OptIn(InternalSerializationApi::class)
actual suspend fun <T> HttpClient.uniUpload(
url: String,
data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>,
headers: Headers,
stringFormat: StringFormat,
onUpload: OnUploadCallback
): T? {
val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo }
val formData = formData {
for (k in data.keys) {
val v = data[k] ?: continue
when (v) {
is MPPFile -> append(
k,
v.inputProvider(),
Headers.build {
append(HttpHeaders.ContentType, v.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
}
)
is UniUploadFileInfo -> append(
k,
InputProvider(block = v.inputAllocator),
Headers.build {
append(HttpHeaders.ContentType, v.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
}
)
else -> append(
k,
stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
)
}
}
}
val requestBuilder: HttpRequestBuilder.() -> Unit = {
headers {
appendAll(headers)
}
onUpload { bytesSentTotal, contentLength ->
onUpload(bytesSentTotal, contentLength)
}
}
val response = if (withBinary) {
submitFormWithBinaryData(
url,
formData,
block = requestBuilder
)
} else {
submitForm(
url,
Parameters.build {
for (it in formData) {
val formItem = (it as PartData.FormItem)
append(it.name!!, it.value)
}
},
block = requestBuilder
)
}
return if (response.status == HttpStatusCode.OK) {
stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
} else {
null
}
}

View File

@@ -17,8 +17,5 @@ kotlin {
api libs.ktor.io api libs.ktor.io
} }
} }
androidMain {
dependsOn jvmMain
}
} }
} }

View File

@@ -1,6 +1,6 @@
package dev.inmo.micro_utils.ktor.common package dev.inmo.micro_utils.ktor.common
import korlibs.time.DateTime import com.soywiz.klock.DateTime
typealias FromToDateTime = Pair<DateTime?, DateTime?> typealias FromToDateTime = Pair<DateTime?, DateTime?>

View File

@@ -1,11 +0,0 @@
package dev.inmo.micro_utils.ktor.common
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.bytesAllocatorSync
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Input
actual fun MPPFile.input(): Input {
return ByteReadPacket(bytesAllocatorSync())
}

View File

@@ -1,10 +0,0 @@
package dev.inmo.micro_utils.ktor.common
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.bytesAllocatorSync
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Input
actual fun MPPFile.input(): Input {
return ByteReadPacket(bytesAllocatorSync())
}

View File

@@ -10,8 +10,8 @@ import java.io.File
fun PartData.FileItem.download(target: File) { fun PartData.FileItem.download(target: File) {
provider().use { input -> provider().use { input ->
target.outputStream().asOutput().use { target.outputStream().use {
input.copyTo(it) input.copyTo(it.asOutput())
} }
} }
} }

View File

@@ -1,6 +1,6 @@
package dev.inmo.micro_utils.ktor.server package dev.inmo.micro_utils.ktor.server
import korlibs.time.DateTime import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.ktor.common.FromToDateTime import dev.inmo.micro_utils.ktor.common.FromToDateTime
import io.ktor.http.Parameters import io.ktor.http.Parameters

View File

@@ -12,22 +12,10 @@ suspend fun ApplicationCall.getParameterOrSendError(
} }
} }
suspend fun ApplicationCall.getParametersOrSendError(
field: String
) = parameters.getAll(field).also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun ApplicationCall.getQueryParameter( fun ApplicationCall.getQueryParameter(
field: String field: String
) = request.queryParameters[field] ) = request.queryParameters[field]
fun ApplicationCall.getQueryParameters(
field: String
) = request.queryParameters.getAll(field)
suspend fun ApplicationCall.getQueryParameterOrSendError( suspend fun ApplicationCall.getQueryParameterOrSendError(
field: String field: String
) = getQueryParameter(field).also { ) = getQueryParameter(field).also {
@@ -35,11 +23,3 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
} }
} }
suspend fun ApplicationCall.getQueryParametersOrSendError(
field: String
) = getQueryParameters(field).also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}

View File

@@ -5,11 +5,3 @@ plugins {
} }
apply from: "$mppProjectWithSerializationPresetPath" apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
androidMain {
dependsOn jvmMain
}
}
}

View File

@@ -1,8 +0,0 @@
package dev.inmo.micro_utils.language_codes
import java.util.Locale
fun IetfLanguageCode.toJavaLocale(): Locale = Locale.forLanguageTag(code)
fun IetfLanguageCode?.toJavaLocaleOrDefault(): Locale = this ?.toJavaLocale() ?: Locale.getDefault()
fun Locale.toIetfLanguageCode(): IetfLanguageCode = IetfLanguageCode(toLanguageTag())

View File

@@ -1,5 +1,3 @@
import math
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import pandas as pd import pandas as pd
@@ -19,45 +17,33 @@ def fix_name(category, raw_name):
result += out1 result += out1
return result return result
def remove_prefix(text, prefix):
if text.startswith(prefix):
return text[len(prefix):]
return text # or whatever
def extensionPreparationFun(extension):
return "\"%s\"" % (remove_prefix(extension, "."))
# https://www.freeformatter.com/mime-types-list.html # https://www.freeformatter.com/mime-types-list.html
if __name__ == '__main__': if __name__ == '__main__':
df = pd.read_html(open('local.table.html', 'r')) df = pd.read_html(open('table.html', 'r'))
mimes = [] mimes = []
for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows(): for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows():
mime = row[1][1] mime = row[1][1]
extensions = list()
if isinstance(row[1][2], str):
extensions = list(map(extensionPreparationFun, row[1][2].split(", ")))
mime_category = mime.split('/', 1)[0] mime_category = mime.split('/', 1)[0]
mime_name = mime.split('/', 1)[1] mime_name = mime.split('/', 1)[1]
mimes.append([ mimes.append({
mime_category, 'mime_category': mime_category,
mime_name, 'mime_name': mime_name,
extensions })
])
# codegen # codegen
mimes.sort(key=lambda x: x[0]) mimes.sort(key=lambda x: x['mime_category'])
grouped = itertools.groupby(mimes, lambda x: x[0]) grouped = itertools.groupby(mimes, lambda x: x['mime_category'])
code = '' code = ''
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n' code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
code2 += ' KnownMimeTypes.Any,\n' code2 += ' KnownMimeTypes.Any,\n'
for key, group in grouped: for key, group in grouped:
group_name = fix_name(group, key) group_name = fix_name(group, key)
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String, extensions: Array<String> = emptyArray()) : MimeType, KnownMimeTypes(raw, extensions) {\n' % group_name code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key) code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
for mime in group: for mime in group:
name = fix_name(mime[0], mime[1]) name = fix_name(mime['mime_category'], mime['mime_name'])
code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s", arrayOf(%s))\n' % (name, group_name, mime[0], mime[1], ", ".join(mime[2])) code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s")\n' % (name, group_name, mime['mime_category'], mime['mime_name'])
code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name) code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name)
code += '}\n\n' code += '}\n\n'
code2 += ')\n' code2 += ')\n'

View File

@@ -1,24 +0,0 @@
package dev.inmo.micro_utils.mime_types
val mimeTypesByExtensions: Map<String, Array<MimeType>> by lazy {
val extensionsMap = mutableMapOf<String, MutableList<MimeType>>()
knownMimeTypes.forEach { mimeType ->
mimeType.extensions.forEach {
extensionsMap.getOrPut(it) { mutableListOf() }.add(mimeType)
}
}
extensionsMap.mapValues {
it.value.toTypedArray()
}
}
inline fun getMimeType(
stringWithExtension: String,
selector: (Array<MimeType>) -> MimeType? = { it.firstOrNull() }
) = mimeTypesByExtensions[stringWithExtension.takeLastWhile { it != '.' }] ?.takeIf { it.isNotEmpty() } ?.let(selector)
inline fun getMimeTypeOrAny(
stringWithExtension: String,
selector: (Array<MimeType>) -> MimeType? = { it.firstOrNull() }
) = getMimeType(stringWithExtension, selector) ?: KnownMimeTypes.Any

View File

@@ -5,6 +5,4 @@ import kotlinx.serialization.Serializable
@Serializable(MimeTypeSerializer::class) @Serializable(MimeTypeSerializer::class)
interface MimeType { interface MimeType {
val raw: String val raw: String
val extensions: Array<String>
get() = emptyArray()
} }

View File

@@ -15,8 +15,6 @@ kotlin {
browser() browser()
nodejs() nodejs()
} }
linuxX64()
mingwX64()
sourceSets { sourceSets {
commonMain { commonMain {

View File

@@ -18,8 +18,6 @@ kotlin {
android { android {
publishAllLibraryVariants() publishAllLibraryVariants()
} }
linuxX64()
mingwX64()
sourceSets { sourceSets {
commonMain { commonMain {
@@ -52,18 +50,6 @@ kotlin {
implementation libs.android.espresso implementation libs.android.espresso
} }
} }
mingwX64Test {
dependencies {
implementation kotlin('test-junit')
}
}
linuxX64Test {
dependencies {
implementation kotlin('test-junit')
}
}
androidMain.dependsOn jvmMain
} }
} }

View File

@@ -61,8 +61,6 @@ kotlin {
implementation libs.android.espresso implementation libs.android.espresso
} }
} }
androidMain.dependsOn jvmMain
} }
} }

View File

@@ -32,24 +32,6 @@ fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
) )
} }
fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
val actualPagination = with.optionallyReverse(
size,
reversed
)
val firstIndex = maxOf(actualPagination.firstIndex, 0)
val lastIndex = minOf(actualPagination.lastIndexExclusive, size)
if (firstIndex > lastIndex) {
return emptyPaginationResult()
}
return subList(firstIndex, lastIndex).optionallyReverse(reversed).createPaginationResult(
with,
size.toLong()
)
}
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> { fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
return this.drop(with.firstIndex).take(with.size).createPaginationResult( return this.drop(with.firstIndex).take(with.size).createPaginationResult(
with, with,
@@ -57,20 +39,30 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
) )
} }
fun <T> Set<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> { fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
val actualPagination = with.optionallyReverse( is List<T> -> optionallyReverse(reverse)
size, is Set<T> -> optionallyReverse(reverse)
reversed else -> if (reverse) {
) reversed()
} else {
val firstIndex = maxOf(actualPagination.firstIndex, 0) this
val lastIndex = minOf(actualPagination.lastIndexExclusive, size)
if (firstIndex > lastIndex) {
return emptyPaginationResult()
} }
}
return this.drop(firstIndex).take(lastIndex - firstIndex).optionallyReverse(reversed).createPaginationResult( fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
with, reversed()
size.toLong() } else {
) this
}
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
reversed().toSet()
} else {
this
}
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
Array(size) {
get(lastIndex - it)
}
} else {
this
} }

View File

@@ -1,29 +0,0 @@
package dev.inmo.micro_utils.pagination.utils
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
is List<T> -> optionallyReverse(reverse)
is Set<T> -> optionallyReverse(reverse)
else -> if (reverse) {
reversed()
} else {
this
}
}
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
reversed()
} else {
this
}
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
reversed().toSet()
} else {
this
}
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
Array(size) {
get(lastIndex - it)
}
} else {
this
}

View File

@@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}

View File

@@ -2,7 +2,6 @@ package dev.inmo.micro_utils.repos.cache
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 kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@@ -11,20 +10,12 @@ 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 open val idGetter: (ObjectType) -> IdType protected open val idGetter: (ObjectType) -> IdType
) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CommonCacheRepo { ) : ReadCRUDRepo<ObjectType, IdType> by parentRepo, CacheRepo {
override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also { override suspend fun getById(id: IdType): ObjectType? = kvCache.get(id) ?: (parentRepo.getById(id) ?.also {
kvCache.set(id, it) kvCache.set(id, it)
}) })
override suspend fun getAll(): Map<IdType, ObjectType> {
return kvCache.getAll().takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also {
kvCache.actualizeAll(true) { it }
}
}
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id) override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
override suspend fun invalidate() = kvCache.clear()
} }
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached( fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
@@ -37,7 +28,7 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
protected open val kvCache: KVCache<IdType, ObjectType>, protected open val kvCache: KVCache<IdType, ObjectType>,
protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default), protected open val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
protected open val idGetter: (ObjectType) -> IdType protected open val idGetter: (ObjectType) -> IdType
) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CommonCacheRepo { ) : WriteCRUDRepo<ObjectType, IdType, InputValueType>, CacheRepo {
override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow override val newObjectsFlow: Flow<ObjectType> by parentRepo::newObjectsFlow
override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow override val updatedObjectsFlow: Flow<ObjectType> by parentRepo::updatedObjectsFlow
override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow override val deletedObjectsIdsFlow: Flow<IdType> by parentRepo::deletedObjectsIdsFlow
@@ -81,8 +72,6 @@ open class WriteCRUDCacheRepo<ObjectType, IdType, InputValueType>(
return created return created
} }
override suspend fun invalidate() = kvCache.clear()
} }
fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching( fun <ObjectType, IdType, InputType> WriteCRUDRepo<ObjectType, IdType, InputType>.caching(

View File

@@ -1,5 +1,3 @@
package dev.inmo.micro_utils.repos.cache package dev.inmo.micro_utils.repos.cache
interface CacheRepo { interface CacheRepo
suspend fun invalidate()
}

View File

@@ -1,7 +0,0 @@
package dev.inmo.micro_utils.repos.cache
/**
* Any inheritor of this should work with next logic: try to take data from some [dev.inmo.micro_utils.repos.cache.cache.KVCache] and,
* if not exists, take from origin and save to the cache for future reuse
*/
interface CommonCacheRepo : CacheRepo

View File

@@ -1,7 +0,0 @@
package dev.inmo.micro_utils.repos.cache
/**
* Any inheritor of this should work with next logic: try to take data from their original repo, if successful - save data to internal
* [dev.inmo.micro_utils.repos.cache.cache.FullKVCache] or try to take data from that internal cache
*/
interface FallbackCacheRepo : CacheRepo

View File

@@ -10,7 +10,7 @@ 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>,
) : ReadKeyValueRepo<Key,Value> by parentRepo, CommonCacheRepo { ) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo {
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? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) }
override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key) override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key)
@@ -23,14 +23,6 @@ open class ReadKeyValueCacheRepo<Key,Value>(
) )
} }
} }
override suspend fun getAll(): Map<Key, Value> = kvCache.getAll().takeIf {
it.size.toLong() == count()
} ?: parentRepo.getAll().also {
kvCache.set(it)
}
override suspend fun invalidate() = kvCache.clear()
} }
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
@@ -41,11 +33,9 @@ open class KeyValueCacheRepo<Key,Value>(
parentRepo: KeyValueRepo<Key, Value>, 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 { ) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { kvCache.set(it.first, it.second) }.launchIn(scope) 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) protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun invalidate() = kvCache.clear()
} }
fun <Key, Value> KeyValueRepo<Key, Value>.cached( fun <Key, Value> KeyValueRepo<Key, Value>.cached(

View File

@@ -11,7 +11,7 @@ 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>>
) : ReadKeyValuesRepo<Key,Value> by parentRepo, CommonCacheRepo { ) : ReadKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
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 getAll(k, reversed).paginate(
pagination pagination
@@ -30,8 +30,6 @@ open class ReadKeyValuesCacheRepo<Key,Value>(
} }
}) })
override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k) override suspend fun contains(k: Key): Boolean = kvCache.contains(k) || parentRepo.contains(k)
override suspend fun invalidate() = kvCache.clear()
} }
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
@@ -42,7 +40,7 @@ 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 { ) : ReadKeyValuesCacheRepo<Key,Value>(parentRepo, kvCache), KeyValuesRepo<Key,Value>, WriteKeyValuesRepo<Key,Value> by parentRepo, CacheRepo {
protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) -> protected val onNewJob = parentRepo.onNewValue.onEach { (k, v) ->
kvCache.set( kvCache.set(
k, k,
@@ -58,8 +56,6 @@ open class KeyValuesCacheRepo<Key,Value>(
protected val onDataClearedJob = parentRepo.onDataCleared.onEach { protected val onDataClearedJob = parentRepo.onDataCleared.onEach {
kvCache.unset(it) kvCache.unset(it)
}.launchIn(scope) }.launchIn(scope)
override suspend fun invalidate() = kvCache.clear()
} }
fun <Key, Value> KeyValuesRepo<Key, Value>.cached( fun <Key, Value> KeyValuesRepo<Key, Value>.cached(

View File

@@ -1,37 +0,0 @@
package dev.inmo.micro_utils.repos.cache.fallback
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import kotlinx.coroutines.withTimeout
/**
* Realizations should [wrap] the work with some conditions like retries on exceptions, calling timeout, etc.
*
* @see Timeouted
* @see Direct
*/
interface ActionWrapper {
/**
* Should execute [block] to take the result [T], but may return failure in case when something went wrong.
* This method should never throw any [Exception]
*/
suspend fun <T> wrap(block: suspend () -> T): Result<T>
/**
* This type of [ActionWrapper]s will use [withTimeout]([timeoutMillis]) and if original call
* will not return anything in that timeout just return [Result] with failure
*/
class Timeouted(private val timeoutMillis: Long) : ActionWrapper {
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely {
withTimeout(timeoutMillis) {
block()
}
}
}
/**
* It is passthrough variant of [ActionWrapper] which will just call incoming block with wrapping into [runCatchingSafely]
*/
object Direct : ActionWrapper {
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely { block() }
}
}

View File

@@ -1,36 +0,0 @@
package dev.inmo.micro_utils.repos.cache.fallback.crud
import dev.inmo.micro_utils.repos.CRUDRepo
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 kotlinx.coroutines.CoroutineScope
import kotlin.time.Duration.Companion.seconds
open class AutoRecacheCRUDRepo<RegisteredObject, Id, InputObject>(
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
scope: CoroutineScope,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
actionWrapper: ActionWrapper = ActionWrapper.Direct,
idGetter: (RegisteredObject) -> Id
) : AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
originalRepo,
scope,
kvCache,
recacheDelay,
actionWrapper,
idGetter
),
WriteCRUDRepo<RegisteredObject, Id, InputObject> by AutoRecacheWriteCRUDRepo(originalRepo, scope, kvCache, idGetter),
CRUDRepo<RegisteredObject, Id, InputObject> {
constructor(
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
idGetter: (RegisteredObject) -> Id
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
}

View File

@@ -1,94 +0,0 @@
package dev.inmo.micro_utils.repos.cache.fallback.crud
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
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.util.actualizeAll
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
import dev.inmo.micro_utils.repos.set
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds
open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
protected open val originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
protected val idGetter: (RegisteredObject) -> Id
) : ReadCRUDRepo<RegisteredObject, Id>, FallbackCacheRepo {
val autoUpdateJob = scope.launch {
while (isActive) {
actualizeAll()
delay(recacheDelay)
}
}
constructor(
originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
idGetter: (RegisteredObject) -> Id
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
protected open suspend fun actualizeAll(): Result<Unit> {
return runCatchingSafely {
kvCache.actualizeAll(originalRepo)
}
}
override suspend fun contains(id: Id): Boolean = actionWrapper.wrap {
originalRepo.contains(id)
}.getOrElse {
kvCache.contains(id)
}
override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap {
originalRepo.getAll()
}.onSuccess {
kvCache.actualizeAll(clear = true) { it }
}.getOrElse {
kvCache.getAll()
}
override suspend fun count(): Long = actionWrapper.wrap {
originalRepo.count()
}.getOrElse {
kvCache.count()
}
override suspend fun getByPagination(
pagination: Pagination
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
originalRepo.getByPagination(pagination)
}.getOrNull() ?.also {
it.results.forEach {
kvCache.set(idGetter(it), it)
}
} ?: kvCache.values(pagination)
override suspend fun getIdsByPagination(
pagination: Pagination
): PaginationResult<Id> = actionWrapper.wrap {
originalRepo.getIdsByPagination(pagination)
}.getOrElse { kvCache.keys(pagination) }
override suspend fun getById(id: Id): RegisteredObject? = actionWrapper.wrap {
originalRepo.getById(id)
}.getOrNull() ?.also {
kvCache.set(idGetter(it), it)
} ?: kvCache.get(id)
override suspend fun invalidate() {
actualizeAll()
}
}

View File

@@ -1,65 +0,0 @@
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.repos.UpdatedValuePair
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.set
import dev.inmo.micro_utils.repos.unset
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
open class AutoRecacheWriteCRUDRepo<RegisteredObject, Id, InputObject>(
protected val originalRepo: WriteCRUDRepo<RegisteredObject, Id, InputObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
protected val idGetter: (RegisteredObject) -> Id
) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo {
override val deletedObjectsIdsFlow: Flow<Id>
get() = (originalRepo.deletedObjectsIdsFlow).distinctUntilChanged()
override val newObjectsFlow: Flow<RegisteredObject>
get() = (originalRepo.newObjectsFlow).distinctUntilChanged()
override val updatedObjectsFlow: Flow<RegisteredObject>
get() = originalRepo.updatedObjectsFlow
private val onRemovingUpdatesListeningJob = originalRepo.deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(scope) {
kvCache.unset(it)
}
private val onNewAndUpdatedObjectsListeningJob = merge(
originalRepo.newObjectsFlow,
originalRepo.updatedObjectsFlow,
).subscribeSafelyWithoutExceptions(scope) {
kvCache.set(idGetter(it), it)
}
override suspend fun update(
values: List<UpdatedValuePair<Id, InputObject>>
): List<RegisteredObject> = originalRepo.update(values).onEach {
kvCache.set(idGetter(it), it)
}
override suspend fun update(
id: Id,
value: InputObject
): RegisteredObject? = originalRepo.update(id, value) ?.also {
kvCache.set(idGetter(it), it)
}
override suspend fun deleteById(ids: List<Id>) = originalRepo.deleteById(ids).also {
kvCache.unset(ids)
}
override suspend fun create(values: List<InputObject>): List<RegisteredObject> = originalRepo.create(values).onEach {
kvCache.set(idGetter(it), it)
}
override suspend fun invalidate() {
kvCache.clear()
}
}

View File

@@ -1,42 +0,0 @@
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
import dev.inmo.micro_utils.repos.KeyValueRepo
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 kotlinx.coroutines.CoroutineScope
import kotlin.time.Duration.Companion.seconds
open class AutoRecacheKeyValueRepo<Id, RegisteredObject>(
override val originalRepo: KeyValueRepo<Id, RegisteredObject>,
scope: CoroutineScope,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
actionWrapper: ActionWrapper = ActionWrapper.Direct,
idGetter: (RegisteredObject) -> Id
) : AutoRecacheReadKeyValueRepo<Id, RegisteredObject> (
originalRepo,
scope,
kvCache,
recacheDelay,
actionWrapper,
idGetter
),
WriteKeyValueRepo<Id, RegisteredObject> by AutoRecacheWriteKeyValueRepo(originalRepo, scope, kvCache),
KeyValueRepo<Id, RegisteredObject> {
constructor(
originalRepo: KeyValueRepo<Id, RegisteredObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
idGetter: (RegisteredObject) -> Id
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
override suspend fun unsetWithValues(toUnset: List<RegisteredObject>) = originalRepo.unsetWithValues(
toUnset
).also {
kvCache.unsetWithValues(toUnset)
}
}

View File

@@ -1,104 +0,0 @@
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
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.util.actualizeAll
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
import dev.inmo.micro_utils.repos.set
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds
open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
protected open val originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
protected val idGetter: (RegisteredObject) -> Id
) : ReadKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
val autoUpdateJob = scope.launch {
while (isActive) {
actualizeAll()
delay(recacheDelay)
}
}
constructor(
originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
idGetter: (RegisteredObject) -> Id
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
protected open suspend fun actualizeAll(): Result<Unit> {
return runCatchingSafely {
kvCache.actualizeAll(originalRepo)
}
}
override suspend fun contains(key: Id): Boolean = actionWrapper.wrap {
originalRepo.contains(key)
}.getOrElse {
kvCache.contains(key)
}
override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap {
originalRepo.getAll()
}.onSuccess {
kvCache.actualizeAll(clear = true) { it }
}.getOrElse {
kvCache.getAll()
}
override suspend fun count(): Long = actionWrapper.wrap {
originalRepo.count()
}.getOrElse {
kvCache.count()
}
override suspend fun get(k: Id): RegisteredObject? = actionWrapper.wrap {
originalRepo.get(k)
}.getOrNull() ?.also {
kvCache.set(k, it)
} ?: kvCache.get(k)
override suspend fun values(
pagination: Pagination,
reversed: Boolean
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
originalRepo.values(pagination, reversed)
}.getOrNull() ?.also {
it.results.forEach {
kvCache.set(idGetter(it), it)
}
} ?: kvCache.values(pagination, reversed)
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<Id> = actionWrapper.wrap {
originalRepo.keys(pagination, reversed)
}.getOrElse { kvCache.keys(pagination, reversed) }
override suspend fun keys(
v: RegisteredObject,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Id> = actionWrapper.wrap {
originalRepo.keys(v, pagination, reversed)
}.getOrElse { kvCache.keys(v, pagination, reversed) }
override suspend fun invalidate() {
actualizeAll()
}
}

View File

@@ -1,54 +0,0 @@
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.repos.WriteKeyValueRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
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.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
open class AutoRecacheWriteKeyValueRepo<Id, RegisteredObject>(
protected val originalRepo: WriteKeyValueRepo<Id, RegisteredObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache()
) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
override val onValueRemoved: Flow<Id>
get() = (originalRepo.onValueRemoved).distinctUntilChanged()
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
get() = (originalRepo.onNewValue).distinctUntilChanged()
private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) {
kvCache.unset(it)
}
private val onNewAndUpdatedObjectsListeningJob = originalRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) {
kvCache.set(it.first, it.second)
}
override suspend fun unsetWithValues(toUnset: List<RegisteredObject>) = originalRepo.unsetWithValues(
toUnset
).also {
kvCache.unsetWithValues(toUnset)
}
override suspend fun unset(toUnset: List<Id>) = originalRepo.unset(
toUnset
).also {
kvCache.unset(toUnset)
}
override suspend fun set(toSet: Map<Id, RegisteredObject>) = originalRepo.set(
toSet
).also {
kvCache.set(toSet)
}
override suspend fun invalidate() {
kvCache.clear()
}
}

View File

@@ -1,37 +0,0 @@
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
import dev.inmo.micro_utils.repos.KeyValuesRepo
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 kotlinx.coroutines.CoroutineScope
import kotlin.time.Duration.Companion.seconds
open class AutoRecacheKeyValuesRepo<Id, RegisteredObject>(
override val originalRepo: KeyValuesRepo<Id, RegisteredObject>,
scope: CoroutineScope,
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
actionWrapper: ActionWrapper = ActionWrapper.Direct
) : AutoRecacheReadKeyValuesRepo<Id, RegisteredObject> (
originalRepo,
scope,
kvCache,
recacheDelay,
actionWrapper
),
WriteKeyValuesRepo<Id, RegisteredObject> by AutoRecacheWriteKeyValuesRepo(originalRepo, scope, kvCache),
KeyValuesRepo<Id, RegisteredObject> {
constructor(
originalRepo: KeyValuesRepo<Id, RegisteredObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
override suspend fun clearWithValue(v: RegisteredObject) {
super.clearWithValue(v)
}
}

View File

@@ -1,145 +0,0 @@
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
import dev.inmo.micro_utils.pagination.createPaginationResult
import dev.inmo.micro_utils.pagination.emptyPaginationResult
import dev.inmo.micro_utils.pagination.firstIndex
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
import dev.inmo.micro_utils.pagination.utils.paginate
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.util.actualizeAll
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
import dev.inmo.micro_utils.repos.set
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds
open class AutoRecacheReadKeyValuesRepo<Id, RegisteredObject>(
protected open val originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct
) : ReadKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
val autoUpdateJob = scope.launch {
while (isActive) {
actualizeAll()
delay(recacheDelay)
}
}
constructor(
originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
scope: CoroutineScope,
originalCallTimeoutMillis: Long,
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
recacheDelay: Long = 60.seconds.inWholeMilliseconds
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
protected open suspend fun actualizeAll(): Result<Unit> {
return runCatchingSafely {
kvCache.actualizeAll(originalRepo)
}
}
override suspend fun contains(k: Id): Boolean = actionWrapper.wrap {
originalRepo.contains(k)
}.getOrElse {
kvCache.contains(k)
}
override suspend fun count(): Long = actionWrapper.wrap {
originalRepo.count()
}.getOrElse {
kvCache.count()
}
override suspend fun keys(
v: RegisteredObject,
pagination: Pagination,
reversed: Boolean
): PaginationResult<Id> = actionWrapper.wrap {
originalRepo.keys(v, pagination, reversed)
}.getOrElse {
val results = mutableListOf<Id>()
val toSkip = pagination.firstIndex
var count = 0
doForAllWithNextPaging {
kvCache.keys(pagination, reversed).also {
it.results.forEach {
if (kvCache.get(it) ?.contains(v) == true) {
count++
if (count < toSkip || results.size >= pagination.size) {
return@forEach
} else {
results.add(it)
}
}
}
}
}
return@getOrElse results.createPaginationResult(
pagination,
count.toLong()
)
}
override suspend fun keys(
pagination: Pagination,
reversed: Boolean
): PaginationResult<Id> = actionWrapper.wrap {
originalRepo.keys(pagination, reversed)
}.getOrElse { kvCache.keys(pagination, reversed) }
override suspend fun get(
k: Id,
pagination: Pagination,
reversed: Boolean
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
originalRepo.get(k, pagination, reversed)
}.getOrNull() ?.also {
it.results.forEach {
kvCache.set(k, ((kvCache.get(k) ?: return@also) + it).distinct())
}
} ?: kvCache.get(k) ?.run {
paginate(pagination.optionallyReverse(size, reversed)).let {
if (reversed) {
it.changeResultsUnchecked(
it.results.reversed()
)
} else {
it
}
}
} ?: emptyPaginationResult()
override suspend fun count(k: Id): Long = actionWrapper.wrap {
originalRepo.count(k)
}.getOrElse {
kvCache.get(k) ?.size ?.toLong() ?: 0L
}
override suspend fun contains(k: Id, v: RegisteredObject): Boolean {
return (actionWrapper.wrap {
originalRepo.contains(k, v)
}.getOrNull() ?.also {
kvCache.set(k, ((kvCache.get(k) ?: return@also) + v).distinct())
}) ?: (kvCache.get(k) ?.contains(v) == true)
}
override suspend fun invalidate() {
actualizeAll()
}
}

View File

@@ -1,100 +0,0 @@
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.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
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.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
protected val originalRepo: WriteKeyValuesRepo<Id, RegisteredObject>,
protected val scope: CoroutineScope,
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache()
) : WriteKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
override val onValueRemoved: Flow<Pair<Id, RegisteredObject>>
get() = originalRepo.onValueRemoved
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
get() = originalRepo.onNewValue
override val onDataCleared: Flow<Id>
get() = (originalRepo.onDataCleared).distinctUntilChanged()
private val onDataClearedListeningJob = originalRepo.onDataCleared.subscribeSafelyWithoutExceptions(scope) {
kvCache.unset(it)
}
private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) {
kvCache.set(
it.first,
(kvCache.get(
it.first
) ?: return@subscribeSafelyWithoutExceptions) - it.second
)
}
private val onNewAndUpdatedObjectsListeningJob = originalRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) {
kvCache.set(
it.first,
(kvCache.get(
it.first
) ?: return@subscribeSafelyWithoutExceptions) + it.second
)
}
override suspend fun clearWithValue(v: RegisteredObject) {
originalRepo.clearWithValue(v)
doForAllWithNextPaging(kvCache.maxPagePagination()) {
kvCache.keys(it).also {
it.results.forEach { id ->
kvCache.get(id) ?.takeIf { it.contains(v) } ?.let {
kvCache.unset(id)
}
}
}
}
}
override suspend fun clear(k: Id) {
originalRepo.clear(k)
kvCache.unset(k)
}
override suspend fun remove(toRemove: Map<Id, List<RegisteredObject>>) {
originalRepo.remove(toRemove)
toRemove.forEach { (k, v) ->
kvCache.set(k, (kvCache.get(k) ?: return@forEach) - v)
}
}
override suspend fun removeWithValue(v: RegisteredObject) {
originalRepo.removeWithValue(v)
doForAllWithNextPaging(kvCache.maxPagePagination()) {
kvCache.keys(it).also {
it.results.forEach { id ->
kvCache.get(id) ?.takeIf { it.contains(v) } ?.let {
kvCache.set(id, it - v)
}
}
}
}
}
override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) {
originalRepo.add(toAdd)
toAdd.forEach { (k, v) ->
kvCache.set(k, (kvCache.get(k) ?: return@forEach) + v)
}
}
override suspend fun invalidate() {
kvCache.clear()
}
}

View File

@@ -3,10 +3,11 @@ 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.pagination.Pagination import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
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.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.cache.KVCache
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -31,7 +32,12 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
} }
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
kvCache.actualizeAll(parentRepo) kvCache.clear()
doForAllWithNextPaging {
parentRepo.getByPagination(it).also {
kvCache.set(it.results.associateBy { idGetter(it) })
}
}
} }
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize( override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
@@ -58,21 +64,11 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
{ if (it) parentRepo.getById(id) ?.let { set(id, it) } } { if (it) parentRepo.getById(id) ?.let { set(id, it) } }
) )
override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualize(
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
{ getAll() },
{ kvCache.actualizeAll(clear = true) { it } }
)
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize( override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize(
{ get(id) ?.optional ?: Optional.absent() }, { get(id) ?.optional ?: Optional.absent() },
{ getById(id) }, { getById(id) },
{ it ?.let { set(idGetter(it), it) } } { it ?.let { set(idGetter(it), it) } }
) )
override suspend fun invalidate() {
actualizeAll()
}
} }
fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached( fun <ObjectType, IdType> ReadCRUDRepo<ObjectType, IdType>.cached(
@@ -96,21 +92,10 @@ open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
scope, scope,
idGetter idGetter
), ),
CRUDRepo<ObjectType, IdType, InputValueType> { CRUDRepo<ObjectType, IdType, InputValueType>
override suspend fun invalidate() {
actualizeAll()
}
}
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.fullyCached(
kvCache: FullKVCache<IdType, ObjectType> = FullKVCache(),
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
idGetter: (ObjectType) -> IdType
) = FullCRUDCacheRepo(this, kvCache, scope, idGetter)
@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: FullKVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default), scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
idGetter: (ObjectType) -> IdType idGetter: (ObjectType) -> IdType
) = fullyCached(kvCache, scope, idGetter) ) = FullCRUDCacheRepo(this, kvCache, scope, idGetter)

View File

@@ -5,7 +5,6 @@ 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.cache.FullKVCache
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
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -58,12 +57,6 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } } { if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
) )
override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualize(
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
{ getAll() },
{ kvCache.actualizeAll(clear = true) { it } }
)
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize( override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
{ keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, { keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
{ keys(pagination, reversed) }, { keys(pagination, reversed) },
@@ -75,10 +68,6 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
{ parentRepo.keys(v, pagination, reversed) }, { parentRepo.keys(v, pagination, reversed) },
{ if (it.results.isNotEmpty()) actualizeAll() } { if (it.results.isNotEmpty()) actualizeAll() }
) )
override suspend fun invalidate() {
actualizeAll()
}
} }
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
@@ -86,16 +75,12 @@ fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
) = FullReadKeyValueCacheRepo(this, kvCache) ) = FullReadKeyValueCacheRepo(this, kvCache)
open class FullWriteKeyValueCacheRepo<Key,Value>( open class FullWriteKeyValueCacheRepo<Key,Value>(
parentRepo: WriteKeyValueRepo<Key, Value>, protected open val parentRepo: WriteKeyValueRepo<Key, Value>,
protected open val kvCache: FullKVCache<Key, Value>, protected open val kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : 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 { kvCache.set(it.first, it.second) }.launchIn(scope)
protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope) protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope)
override suspend fun invalidate() {
kvCache.clear()
}
} }
fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching( fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
@@ -104,26 +89,16 @@ fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
) = 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>, parentRepo: KeyValueRepo<Key, Value>,
kvCache: FullKVCache<Key, Value>, kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : 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) {
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset) override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
override suspend fun invalidate() {
kvCache.actualizeAll(parentRepo)
}
} }
fun <Key, Value> KeyValueRepo<Key, Value>.fullyCached(
kvCache: FullKVCache<Key, Value> = FullKVCache(),
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = FullKeyValueCacheRepo(this, kvCache, scope)
@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: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = fullyCached(kvCache, scope) ) = FullKeyValueCacheRepo(this, kvCache, scope)

View File

@@ -5,7 +5,6 @@ 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.cache.FullKVCache
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
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@@ -34,7 +33,8 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
} }
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
kvCache.actualizeAll(parentRepo) doAllWithCurrentPaging { kvCache.keys(it).also { kvCache.unset(it.results) } }
kvCache.set(parentRepo.getAll())
} }
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
@@ -102,9 +102,6 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
{ if (it.results.isNotEmpty()) actualizeAll() } { if (it.results.isNotEmpty()) actualizeAll() }
) )
override suspend fun invalidate() {
actualizeAll()
}
} }
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
@@ -112,7 +109,7 @@ fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
) = FullReadKeyValuesCacheRepo(this, kvCache) ) = FullReadKeyValuesCacheRepo(this, kvCache)
open class FullWriteKeyValuesCacheRepo<Key,Value>( open class FullWriteKeyValuesCacheRepo<Key,Value>(
parentRepo: WriteKeyValuesRepo<Key, Value>, protected open val parentRepo: WriteKeyValuesRepo<Key, Value>,
protected open val kvCache: FullKVCache<Key, List<Value>>, protected open val kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo { ) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
@@ -128,10 +125,6 @@ open class FullWriteKeyValuesCacheRepo<Key,Value>(
kvCache.get(it.first) ?.minus(it.second) ?: return@onEach kvCache.get(it.first) ?.minus(it.second) ?: return@onEach
) )
}.launchIn(scope) }.launchIn(scope)
override suspend fun invalidate() {
kvCache.clear()
}
} }
fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching( fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
@@ -140,7 +133,7 @@ fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope) ) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
open class FullKeyValuesCacheRepo<Key,Value>( open class FullKeyValuesCacheRepo<Key,Value>(
protected open val parentRepo: KeyValuesRepo<Key, Value>, parentRepo: KeyValuesRepo<Key, Value>,
kvCache: FullKVCache<Key, List<Value>>, kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope), ) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
@@ -153,22 +146,8 @@ open class FullKeyValuesCacheRepo<Key,Value>(
} }
} }
} }
override suspend fun invalidate() {
kvCache.actualizeAll(parentRepo)
}
override suspend fun removeWithValue(v: Value) {
super<FullWriteKeyValuesCacheRepo>.removeWithValue(v)
}
} }
fun <Key, Value> KeyValuesRepo<Key, Value>.fullyCached(
kvCache: FullKVCache<Key, List<Value>> = FullKVCache(),
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = FullKeyValuesCacheRepo(this, kvCache, scope)
@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: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)

View File

@@ -1,51 +0,0 @@
package dev.inmo.micro_utils.repos.cache.util
import dev.inmo.micro_utils.pagination.FirstPagePagination
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(
clear: Boolean = true,
getAll: () -> Map<K, V>
) {
set(
getAll().also {
if (clear) {
clear()
}
}
)
}
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
repo: ReadKeyValueRepo<K, V>,
clear: Boolean = true,
) {
actualizeAll(clear) {
repo.getAll()
}
}
suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
repo: ReadKeyValuesRepo<K, V>,
clear: Boolean = true,
) {
actualizeAll(clear) {
repo.getAll()
}
}
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
repo: ReadCRUDRepo<V, K>,
clear: Boolean = true,
) {
actualizeAll(clear) {
repo.getAll()
}
}

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