mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
109 Commits
Author | SHA1 | Date | |
---|---|---|---|
c6785f1a4f | |||
83fe621c56 | |||
b3a93e17eb | |||
546a391af3 | |||
786cf9bd8b | |||
dfd6fe062d | |||
b6ef818613 | |||
b0f9e9c30a | |||
7e5c88ddc3 | |||
9824c3e00f | |||
9171d5ed11 | |||
a1830ebb82 | |||
22d2a3d9bf | |||
c9b97fc965 | |||
51f85becd5 | |||
a8a281cfb4 | |||
ddd1304949 | |||
86d70b6c02 | |||
a22bdb39e7 | |||
7ae4d5ef95 | |||
a2038cbefa | |||
992091eade | |||
e3bfead0c5 | |||
0de96141fd | |||
fa18d15c3c | |||
ea9dbf2371 | |||
d34e3ec7a9 | |||
c8833a36af | |||
a067cb0c0f | |||
999c8327bd | |||
c2ec73c70a | |||
702f782fc1 | |||
25dbcaaf83 | |||
b00d454a24 | |||
dbc921d56d | |||
438fefa7a3 | |||
5d74eac814 | |||
9fb62e1e25 | |||
3e366ea73b | |||
0ff895bffa | |||
c5bb120280 | |||
4b26a92b37 | |||
0a8453b4d2 | |||
c9872a61b6 | |||
149a1aa278 | |||
13d0e1b682 | |||
6f9be2a9f8 | |||
93ba98d993 | |||
940ee3df06 | |||
2e7bab10fd | |||
|
3ed70a37ea | ||
fe8f80b9d9 | |||
d81fb32fb9 | |||
2877b5532c | |||
b938b21395 | |||
58836359cc | |||
5edb0e1331 | |||
0f0d0b5d58 | |||
46c1887cbe | |||
5f231c2212 | |||
4e97ce86aa | |||
315a7cb29e | |||
aa7cc503f2 | |||
4bbe7e5a80 | |||
d9c05f38d2 | |||
cd0c4c9650 | |||
fc3407f104 | |||
3a5544206b | |||
e17e2f7fb8 | |||
|
d32c95f143 | ||
6d8a8ab018 | |||
a7dce8fa78 | |||
ca73ff8e19 | |||
d01ad10d7d | |||
81041ee43c | |||
|
6e004c2ae4 | ||
0e2fac5b22 | |||
269da7f155 | |||
3cb6b73ee0 | |||
a938ee1efb | |||
6ea5e2e5a6 | |||
617dfb54e0 | |||
d23e005985 | |||
e5207f5bc5 | |||
c96cea8db0 | |||
0a8e71d76a | |||
cf1fd32b08 | |||
cc4224ea1f | |||
f4c148bc58 | |||
022297ad3f | |||
5180d6fc3e | |||
eeebbff70d | |||
afc6aeea15 | |||
486515eddd | |||
0e21699cd1 | |||
f1678ef7cf | |||
cea65fc76e | |||
c9e320b72a | |||
555956087d | |||
b3f468f901 | |||
f5f7511781 | |||
4be1d93f60 | |||
7d684608ef | |||
2c7fd320eb | |||
88ee82e1c6 | |||
d6402c624e | |||
8b9c93bc10 | |||
4f5e261d01 | |||
cf455aebe6 |
128
CHANGELOG.md
128
CHANGELOG.md
@@ -1,5 +1,133 @@
|
||||
# Changelog
|
||||
|
||||
## 0.19.5
|
||||
|
||||
* `Repos`:
|
||||
* `Generator`:
|
||||
* Fixes in new type generation
|
||||
|
||||
## 0.19.4
|
||||
|
||||
* `Versions`:
|
||||
* `Koin`: `3.4.1` -> `3.4.2`
|
||||
* `Android Fragments`: `1.5.7` -> `1.6.0`
|
||||
* `Koin`
|
||||
* `Generator`
|
||||
* Fixes in new generic generator part
|
||||
|
||||
## 0.19.3
|
||||
|
||||
* `Koin`
|
||||
* `Generator`
|
||||
* New getter methods now available with opportunity to use parameters
|
||||
* Old notation `*Single` and `*Factory` is deprecated since this release. With old
|
||||
will be generated new `single*` and `factory*` notations for new generations
|
||||
* Add opportunity to use generic-oriented koin definitions
|
||||
|
||||
## 0.19.2
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `2.3.0` -> `2.3.1`
|
||||
* `Koin`: `3.4.0` -> `3.4.1`
|
||||
* `Uuid`: `0.7.0` -> `0.7.1`
|
||||
|
||||
## 0.19.1
|
||||
|
||||
* `Versions`:
|
||||
* `Korlibs`: `4.0.1` -> `4.0.3`
|
||||
* `Kotlin Poet`: `1.13.2` -> `1.14.0`
|
||||
|
||||
## 0.19.0
|
||||
|
||||
* `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`:
|
||||
|
@@ -23,6 +23,7 @@ allprojects {
|
||||
mavenCentral()
|
||||
google()
|
||||
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
|
||||
|
@@ -20,5 +20,16 @@ kotlin {
|
||||
}
|
||||
dependsOn jvmMain
|
||||
}
|
||||
|
||||
linuxX64Main {
|
||||
dependencies {
|
||||
api libs.okio
|
||||
}
|
||||
}
|
||||
mingwX64Main {
|
||||
dependencies {
|
||||
api libs.okio
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -34,7 +34,11 @@ data class Diff<T> internal constructor(
|
||||
*/
|
||||
val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
|
||||
val added: List<@Serializable(IndexedValueSerializer::class) 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(
|
||||
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
||||
@@ -43,14 +47,14 @@ private inline fun <T> performChanges(
|
||||
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||
removedList: MutableList<IndexedValue<T>>,
|
||||
addedList: MutableList<IndexedValue<T>>,
|
||||
strictComparison: Boolean
|
||||
comparisonFun: (T?, T?) -> Boolean
|
||||
) {
|
||||
var i = -1
|
||||
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
|
||||
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
|
||||
i++
|
||||
val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
|
||||
val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
|
||||
val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value)
|
||||
val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value)
|
||||
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
|
||||
changedList.addAll(
|
||||
potentialChanges.take(i).mapNotNull {
|
||||
@@ -104,7 +108,7 @@ private inline fun <T> performChanges(
|
||||
*/
|
||||
fun <T> Iterable<T>.calculateDiff(
|
||||
other: Iterable<T>,
|
||||
strictComparison: Boolean = false
|
||||
comparisonFun: (T?, T?) -> Boolean
|
||||
): Diff<T> {
|
||||
var i = -1
|
||||
var j = -1
|
||||
@@ -132,7 +136,7 @@ fun <T> Iterable<T>.calculateDiff(
|
||||
}
|
||||
|
||||
when {
|
||||
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
|
||||
comparisonFun(oldObject, newObject) -> {
|
||||
changedObjects.addAll(potentiallyChangedObjects.map {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
it as Pair<IndexedValue<T>, IndexedValue<T>>
|
||||
@@ -143,23 +147,49 @@ fun <T> Iterable<T>.calculateDiff(
|
||||
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
|
||||
val previousOldsAdditionsSize = additionalInOld.size
|
||||
val previousNewsAdditionsSize = additionalInNew.size
|
||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
|
||||
i -= (additionalInOld.size - previousOldsAdditionsSize)
|
||||
j -= (additionalInNew.size - previousNewsAdditionsSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
potentiallyChangedObjects.add(null to null)
|
||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
|
||||
|
||||
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(
|
||||
other: Iterable<T>,
|
||||
strictComparison: Boolean = false
|
||||
): 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)
|
||||
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, strictComparison = false)
|
||||
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
|
||||
|
||||
/**
|
||||
@@ -169,6 +199,23 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
|
||||
other: Iterable<T>
|
||||
) = 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]
|
||||
* mutable list
|
||||
@@ -177,13 +224,26 @@ fun <T> MutableList<T>.applyDiff(
|
||||
source: Iterable<T>,
|
||||
strictComparison: Boolean = false
|
||||
): Diff<T> = calculateDiff(source, strictComparison).also {
|
||||
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)
|
||||
}
|
||||
applyDiff(it)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
)
|
||||
|
@@ -0,0 +1,135 @@
|
||||
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
|
||||
)
|
@@ -0,0 +1,28 @@
|
||||
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()
|
@@ -0,0 +1,6 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
val FixedSignsRange = 0 .. 100
|
||||
|
||||
expect fun Float.fixed(signs: Int): Float
|
||||
expect fun Double.fixed(signs: Int): Double
|
@@ -0,0 +1,4 @@
|
||||
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()
|
@@ -0,0 +1,12 @@
|
||||
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();
|
36
common/src/linuxX64Main/kotlin/ActualMPPFile.kt
Normal file
36
common/src/linuxX64Main/kotlin/ActualMPPFile.kt
Normal file
@@ -0,0 +1,36 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import okio.FileSystem
|
||||
import okio.Path
|
||||
import okio.use
|
||||
|
||||
actual typealias MPPFile = Path
|
||||
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.filename: FileName
|
||||
get() = FileName(toString())
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.filesize: Long
|
||||
get() = FileSystem.SYSTEM.openReadOnly(this).use {
|
||||
it.size()
|
||||
}
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||
get() = {
|
||||
FileSystem.SYSTEM.read(this) {
|
||||
readByteArray()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||
get() = {
|
||||
bytesAllocatorSync()
|
||||
}
|
26
common/src/linuxX64Main/kotlin/fixed.kt
Normal file
26
common/src/linuxX64Main/kotlin/fixed.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
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()
|
||||
}
|
||||
}
|
36
common/src/mingwX64Main/kotlin/ActualMPPFile.kt
Normal file
36
common/src/mingwX64Main/kotlin/ActualMPPFile.kt
Normal file
@@ -0,0 +1,36 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import okio.FileSystem
|
||||
import okio.Path
|
||||
import okio.use
|
||||
|
||||
actual typealias MPPFile = Path
|
||||
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.filename: FileName
|
||||
get() = FileName(toString())
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.filesize: Long
|
||||
get() = FileSystem.SYSTEM.openReadOnly(this).use {
|
||||
it.size()
|
||||
}
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
|
||||
get() = {
|
||||
FileSystem.SYSTEM.read(this) {
|
||||
readByteArray()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
|
||||
get() = {
|
||||
bytesAllocatorSync()
|
||||
}
|
26
common/src/mingwX64Main/kotlin/fixed.kt
Normal file
26
common/src/mingwX64Main/kotlin/fixed.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
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()
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
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
|
||||
|
||||
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(initial, scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
|
||||
fun <T> Flow<T>.toMutableState(
|
||||
initial: T,
|
||||
scope: CoroutineScope
|
||||
): MutableState<T> = asMutableComposeState(initial, scope)
|
||||
|
||||
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun <T> StateFlow<T>.toMutableState(
|
||||
scope: CoroutineScope
|
||||
): MutableState<T> = asMutableComposeState(scope)
|
||||
|
@@ -0,0 +1,136 @@
|
||||
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 }
|
@@ -0,0 +1,26 @@
|
||||
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
|
||||
}
|
@@ -11,6 +11,7 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.common")
|
||||
api libs.krypto
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
import korlibs.crypto.md5
|
||||
|
||||
typealias MD5 = String
|
||||
|
||||
expect fun SourceBytes.md5(): MD5
|
||||
fun SourceString.md5(): MD5 = encodeToByteArray().md5()
|
||||
fun SourceBytes.md5(): MD5 = md5().hexLower
|
||||
fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower
|
||||
|
@@ -1,6 +0,0 @@
|
||||
package dev.inmo.micro_utils.crypto
|
||||
|
||||
/**
|
||||
* @suppress
|
||||
*/
|
||||
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())
|
@@ -1,12 +0,0 @@
|
||||
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)
|
@@ -13,10 +13,10 @@ repositories {
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
// js(IR) {
|
||||
// browser()
|
||||
// nodejs()
|
||||
// }
|
||||
js(IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
android {}
|
||||
|
||||
sourceSets {
|
||||
@@ -26,44 +26,44 @@ kotlin {
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
// && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// jsMain {
|
||||
// dependencies {
|
||||
// implementation kotlin('stdlib')
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
// project.parent.subprojects.forEach {
|
||||
// if (
|
||||
// it != project
|
||||
// && it.hasProperty("kotlin")
|
||||
// && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
// && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||
// ) {
|
||||
// api it
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
@@ -76,10 +76,10 @@ kotlin {
|
||||
|
||||
project.parent.subprojects.forEach {
|
||||
if (
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||
it != project
|
||||
&& it.hasProperty("kotlin")
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") }
|
||||
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") }
|
||||
) {
|
||||
api it
|
||||
}
|
||||
@@ -116,9 +116,9 @@ tasks.dokkaHtml {
|
||||
sourceRoots.setFrom(findSourcesWithName("commonMain"))
|
||||
}
|
||||
|
||||
// named("jsMain") {
|
||||
// sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
|
||||
// }
|
||||
named("jsMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("jsMain"))
|
||||
}
|
||||
|
||||
named("jvmMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("jvmMain"))
|
||||
|
@@ -23,7 +23,7 @@ allprojects {
|
||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
||||
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
||||
mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle"
|
||||
mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
|
||||
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
||||
|
||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.17.2
|
||||
android_code_version=184
|
||||
version=0.19.5
|
||||
android_code_version=201
|
||||
|
@@ -1,40 +1,42 @@
|
||||
[versions]
|
||||
|
||||
kt = "1.8.10"
|
||||
kt-serialization = "1.5.0"
|
||||
kt = "1.8.20"
|
||||
kt-serialization = "1.5.1"
|
||||
kt-coroutines = "1.6.4"
|
||||
|
||||
kslog = "1.0.0"
|
||||
kslog = "1.1.1"
|
||||
|
||||
jb-compose = "1.3.1-rc01"
|
||||
jb-compose = "1.4.0"
|
||||
jb-exposed = "0.41.1"
|
||||
jb-dokka = "1.7.20"
|
||||
jb-dokka = "1.8.20"
|
||||
|
||||
klock = "3.4.0"
|
||||
uuid = "0.7.0"
|
||||
korlibs = "4.0.3"
|
||||
uuid = "0.7.1"
|
||||
|
||||
ktor = "2.2.4"
|
||||
ktor = "2.3.1"
|
||||
|
||||
gh-release = "2.4.1"
|
||||
|
||||
koin = "3.3.2"
|
||||
koin = "3.4.2"
|
||||
|
||||
ksp = "1.8.10-1.0.9"
|
||||
kotlin-poet = "1.12.0"
|
||||
okio = "3.3.0"
|
||||
|
||||
android-gradle = "7.3.1"
|
||||
ksp = "1.8.20-1.0.11"
|
||||
kotlin-poet = "1.14.2"
|
||||
|
||||
android-gradle = "7.4.2"
|
||||
dexcount = "4.0.0"
|
||||
|
||||
android-coreKtx = "1.9.0"
|
||||
android-recyclerView = "1.2.1"
|
||||
android-coreKtx = "1.10.1"
|
||||
android-recyclerView = "1.3.0"
|
||||
android-appCompat = "1.6.1"
|
||||
android-fragment = "1.5.5"
|
||||
android-fragment = "1.6.0"
|
||||
android-espresso = "3.5.1"
|
||||
android-test = "1.1.5"
|
||||
|
||||
android-props-minSdk = "21"
|
||||
android-props-compileSdk = "33"
|
||||
android-props-buildTools = "33.0.1"
|
||||
android-props-buildTools = "33.0.2"
|
||||
|
||||
[libraries]
|
||||
|
||||
@@ -67,7 +69,8 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
|
||||
|
||||
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
|
||||
|
||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
|
||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" }
|
||||
krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" }
|
||||
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
|
||||
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
|
||||
@@ -91,6 +94,8 @@ kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref
|
||||
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" }
|
||||
|
@@ -13,3 +13,8 @@ dependencies {
|
||||
api libs.kotlin.poet
|
||||
api libs.ksp
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
@@ -9,19 +9,28 @@ 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.google.devtools.ksp.symbol.Modifier
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.CodeBlock
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import com.squareup.kotlinpoet.FunSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.ParameterSpec
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||
import com.squareup.kotlinpoet.PropertySpec
|
||||
import com.squareup.kotlinpoet.TypeName
|
||||
import com.squareup.kotlinpoet.TypeVariableName
|
||||
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.GenerateGenericKoinDefinition
|
||||
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.core.parameter.ParametersDefinition
|
||||
import org.koin.core.scope.Scope
|
||||
import java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
@@ -32,11 +41,236 @@ class Processor(
|
||||
private val definitionClassName = ClassName("org.koin.core.definition", "Definition")
|
||||
private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition")
|
||||
|
||||
private fun FileSpec.Builder.addCodeForType(
|
||||
targetType: TypeName,
|
||||
name: String,
|
||||
nullable: Boolean,
|
||||
generateSingle: Boolean,
|
||||
generateFactory: Boolean,
|
||||
) {
|
||||
val targetTypeAsGenericType = (targetType as? TypeVariableName) ?.copy(reified = true)
|
||||
|
||||
fun addGetterProperty(
|
||||
receiver: KClass<*>
|
||||
) {
|
||||
addProperty(
|
||||
PropertySpec.builder(
|
||||
name,
|
||||
targetType,
|
||||
).apply {
|
||||
addKdoc(
|
||||
"""
|
||||
@return Definition by key "${name}"
|
||||
""".trimIndent()
|
||||
)
|
||||
getter(
|
||||
FunSpec.getterBuilder().apply {
|
||||
targetTypeAsGenericType ?.let {
|
||||
addModifiers(KModifier.INLINE)
|
||||
}
|
||||
addCode(
|
||||
"return " + (if (nullable) {
|
||||
"getOrNull"
|
||||
} else {
|
||||
"get"
|
||||
}) + "(named(\"${name}\"))"
|
||||
)
|
||||
}.build()
|
||||
)
|
||||
targetTypeAsGenericType ?.let {
|
||||
addTypeVariable(it)
|
||||
}
|
||||
receiver(receiver)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
||||
if (targetTypeAsGenericType == null) {
|
||||
addGetterProperty(Scope::class)
|
||||
addGetterProperty(Koin::class)
|
||||
}
|
||||
|
||||
val parametersDefinitionClassName = ClassName(
|
||||
"org.koin.core.parameter",
|
||||
"ParametersDefinition"
|
||||
)
|
||||
fun addGetterMethod(
|
||||
receiver: KClass<*>
|
||||
) {
|
||||
addFunction(
|
||||
FunSpec.builder(
|
||||
name
|
||||
).apply {
|
||||
addKdoc(
|
||||
"""
|
||||
@return Definition by key "${name}" with [parameters]
|
||||
""".trimIndent()
|
||||
)
|
||||
receiver(receiver)
|
||||
addParameter(
|
||||
ParameterSpec(
|
||||
"parameters",
|
||||
parametersDefinitionClassName.let {
|
||||
if (targetTypeAsGenericType != null) {
|
||||
it.copy(nullable = true)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
},
|
||||
KModifier.NOINLINE
|
||||
).toBuilder().apply {
|
||||
if (targetTypeAsGenericType != null) {
|
||||
defaultValue("null")
|
||||
}
|
||||
}.build()
|
||||
)
|
||||
addModifiers(KModifier.INLINE)
|
||||
targetTypeAsGenericType ?.let {
|
||||
addTypeVariable(it)
|
||||
returns(it.copy(nullable = nullable))
|
||||
} ?: returns(targetType)
|
||||
addCode(
|
||||
"return " + (if (nullable) {
|
||||
"getOrNull"
|
||||
} else {
|
||||
"get"
|
||||
}) + "(named(\"${name}\"), parameters)"
|
||||
)
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
||||
addGetterMethod(Scope::class)
|
||||
addGetterMethod(Koin::class)
|
||||
|
||||
fun FunSpec.Builder.addDefinitionParameter() {
|
||||
val definitionModifiers = if (targetTypeAsGenericType == null) {
|
||||
arrayOf()
|
||||
} else {
|
||||
arrayOf(KModifier.NOINLINE)
|
||||
}
|
||||
addParameter(
|
||||
ParameterSpec.builder(
|
||||
"definition",
|
||||
definitionClassName.parameterizedBy(targetType.copy(nullable = false)),
|
||||
*definitionModifiers
|
||||
).build()
|
||||
)
|
||||
}
|
||||
|
||||
if (generateSingle) {
|
||||
fun FunSpec.Builder.configure(
|
||||
useInstead: String? = null
|
||||
) {
|
||||
addKdoc(
|
||||
"""
|
||||
Will register [definition] with [org.koin.core.module.Module.single] and key "${name}"
|
||||
""".trimIndent()
|
||||
)
|
||||
receiver(Module::class)
|
||||
addParameter(
|
||||
ParameterSpec.builder(
|
||||
"createdAtStart",
|
||||
Boolean::class
|
||||
).apply {
|
||||
defaultValue("false")
|
||||
}.build()
|
||||
)
|
||||
addDefinitionParameter()
|
||||
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
|
||||
addCode(
|
||||
"return single(named(\"${name}\"), createdAtStart = createdAtStart, definition = definition)"
|
||||
)
|
||||
targetTypeAsGenericType ?.let {
|
||||
addTypeVariable(it)
|
||||
addModifiers(KModifier.INLINE)
|
||||
}
|
||||
if (useInstead != null) {
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(
|
||||
Deprecated::class
|
||||
).apply {
|
||||
addMember(
|
||||
CodeBlock.of(
|
||||
"""
|
||||
"This definition is old style and should not be used anymore. Use $useInstead instead"
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
addMember(CodeBlock.of("ReplaceWith(\"$useInstead\")"))
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val actualSingleName = "single${name.replaceFirstChar { it.uppercase() }}"
|
||||
if (targetTypeAsGenericType == null) { // classic type
|
||||
addFunction(
|
||||
FunSpec.builder("${name}Single").apply { configure(actualSingleName) }.build()
|
||||
)
|
||||
}
|
||||
|
||||
addFunction(
|
||||
FunSpec.builder(actualSingleName).apply { configure() }.build()
|
||||
)
|
||||
}
|
||||
|
||||
if (generateFactory) {
|
||||
fun FunSpec.Builder.configure(
|
||||
useInstead: String? = null
|
||||
) {
|
||||
addKdoc(
|
||||
"""
|
||||
Will register [definition] with [org.koin.core.module.Module.factory] and key "${name}"
|
||||
""".trimIndent()
|
||||
)
|
||||
receiver(Module::class)
|
||||
addDefinitionParameter()
|
||||
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
|
||||
addCode(
|
||||
"return factory(named(\"${name}\"), definition = definition)"
|
||||
)
|
||||
targetTypeAsGenericType ?.let {
|
||||
addTypeVariable(it)
|
||||
addModifiers(KModifier.INLINE)
|
||||
}
|
||||
if (useInstead != null) {
|
||||
addAnnotation(
|
||||
AnnotationSpec.builder(
|
||||
Deprecated::class
|
||||
).apply {
|
||||
addMember(
|
||||
CodeBlock.of(
|
||||
"""
|
||||
"This definition is old style and should not be used anymore. Use $useInstead instead"
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
addMember(CodeBlock.of("ReplaceWith(\"$useInstead\")"))
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
val actualFactoryName = "factory${name.replaceFirstChar { it.uppercase() }}"
|
||||
if (targetTypeAsGenericType == null) { // classic type
|
||||
addFunction(
|
||||
FunSpec.builder("${name}Factory").apply { configure(useInstead = actualFactoryName) }.build()
|
||||
)
|
||||
}
|
||||
addFunction(
|
||||
FunSpec.builder(actualFactoryName).apply { configure() }.build()
|
||||
)
|
||||
}
|
||||
addImport("org.koin.core.qualifier", "named")
|
||||
}
|
||||
|
||||
@OptIn(KspExperimental::class)
|
||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||
resolver.getSymbolsWithAnnotation(
|
||||
(resolver.getSymbolsWithAnnotation(
|
||||
GenerateKoinDefinition::class.qualifiedName!!
|
||||
).filterIsInstance<KSFile>().forEach { ksFile ->
|
||||
) + resolver.getSymbolsWithAnnotation(
|
||||
GenerateGenericKoinDefinition::class.qualifiedName!!
|
||||
)).filterIsInstance<KSFile>().forEach { ksFile ->
|
||||
FileSpec.builder(
|
||||
ksFile.packageName.asString(),
|
||||
"GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}"
|
||||
@@ -72,92 +306,12 @@ class Processor(
|
||||
}.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")
|
||||
addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory)
|
||||
}
|
||||
ksFile.getAnnotationsByType(GenerateGenericKoinDefinition::class).forEach {
|
||||
val targetType = TypeVariableName("T", Any::class)
|
||||
addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory)
|
||||
}
|
||||
}.build().let {
|
||||
File(
|
||||
|
@@ -3,12 +3,15 @@
|
||||
// ORIGINAL FILE: Test.kt
|
||||
package dev.inmo.micro_utils.koin.generator.test
|
||||
|
||||
import kotlin.Any
|
||||
import kotlin.Boolean
|
||||
import kotlin.Deprecated
|
||||
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.parameter.ParametersDefinition
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.core.scope.Scope
|
||||
|
||||
@@ -24,15 +27,98 @@ public val Scope.sampleInfo: Test<String>
|
||||
public val Koin.sampleInfo: Test<String>
|
||||
get() = get(named("sampleInfo"))
|
||||
|
||||
/**
|
||||
* @return Definition by key "sampleInfo" with [parameters]
|
||||
*/
|
||||
public inline fun Scope.sampleInfo(noinline parameters: ParametersDefinition): Test<String> =
|
||||
get(named("sampleInfo"), parameters)
|
||||
|
||||
/**
|
||||
* @return Definition by key "sampleInfo" with [parameters]
|
||||
*/
|
||||
public inline fun Koin.sampleInfo(noinline parameters: ParametersDefinition): Test<String> =
|
||||
get(named("sampleInfo"), parameters)
|
||||
|
||||
/**
|
||||
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
|
||||
*/
|
||||
@Deprecated(
|
||||
"This definition is old style and should not be used anymore. Use singleSampleInfo instead",
|
||||
ReplaceWith("singleSampleInfo"),
|
||||
)
|
||||
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.single] and key "sampleInfo"
|
||||
*/
|
||||
public fun Module.singleSampleInfo(createdAtStart: Boolean = false,
|
||||
definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
|
||||
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
|
||||
|
||||
/**
|
||||
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
|
||||
*/
|
||||
@Deprecated(
|
||||
"This definition is old style and should not be used anymore. Use factorySampleInfo instead",
|
||||
ReplaceWith("factorySampleInfo"),
|
||||
)
|
||||
public fun Module.sampleInfoFactory(definition: Definition<Test<String>>):
|
||||
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
|
||||
|
||||
/**
|
||||
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
|
||||
*/
|
||||
public fun Module.factorySampleInfo(definition: Definition<Test<String>>):
|
||||
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
|
||||
|
||||
/**
|
||||
* @return Definition by key "test" with [parameters]
|
||||
*/
|
||||
public inline fun <reified T : Any> Scope.test(noinline parameters: ParametersDefinition? = null): T
|
||||
= get(named("test"), parameters)
|
||||
|
||||
/**
|
||||
* @return Definition by key "test" with [parameters]
|
||||
*/
|
||||
public inline fun <reified T : Any> Koin.test(noinline parameters: ParametersDefinition? = null): T
|
||||
= get(named("test"), parameters)
|
||||
|
||||
/**
|
||||
* Will register [definition] with [org.koin.core.module.Module.single] and key "test"
|
||||
*/
|
||||
public inline fun <reified T : Any> Module.singleTest(createdAtStart: Boolean = false, noinline
|
||||
definition: Definition<T>): KoinDefinition<T> = single(named("test"), createdAtStart =
|
||||
createdAtStart, definition = definition)
|
||||
|
||||
/**
|
||||
* Will register [definition] with [org.koin.core.module.Module.factory] and key "test"
|
||||
*/
|
||||
public inline fun <reified T : Any> Module.factoryTest(noinline definition: Definition<T>):
|
||||
KoinDefinition<T> = factory(named("test"), definition = definition)
|
||||
|
||||
/**
|
||||
* @return Definition by key "testNullable" with [parameters]
|
||||
*/
|
||||
public inline fun <reified T : Any> Scope.testNullable(noinline parameters: ParametersDefinition? =
|
||||
null): T? = getOrNull(named("testNullable"), parameters)
|
||||
|
||||
/**
|
||||
* @return Definition by key "testNullable" with [parameters]
|
||||
*/
|
||||
public inline fun <reified T : Any> Koin.testNullable(noinline parameters: ParametersDefinition? =
|
||||
null): T? = getOrNull(named("testNullable"), parameters)
|
||||
|
||||
/**
|
||||
* Will register [definition] with [org.koin.core.module.Module.single] and key "testNullable"
|
||||
*/
|
||||
public inline fun <reified T : Any> Module.singleTestNullable(createdAtStart: Boolean = false,
|
||||
noinline definition: Definition<T>): KoinDefinition<T> = single(named("testNullable"),
|
||||
createdAtStart = createdAtStart, definition = definition)
|
||||
|
||||
/**
|
||||
* Will register [definition] with [org.koin.core.module.Module.factory] and key "testNullable"
|
||||
*/
|
||||
public inline fun <reified T : Any> Module.factoryTestNullable(noinline definition: Definition<T>):
|
||||
KoinDefinition<T> = factory(named("testNullable"), definition = definition)
|
||||
|
@@ -1,6 +1,9 @@
|
||||
@file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false)
|
||||
@file:GenerateGenericKoinDefinition("test", nullable = false)
|
||||
@file:GenerateGenericKoinDefinition("testNullable", nullable = true)
|
||||
package dev.inmo.micro_utils.koin.generator.test
|
||||
|
||||
import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition
|
||||
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
|
||||
import org.koin.core.Koin
|
||||
|
||||
|
40
koin/src/commonMain/kotlin/GetWithDefinition.kt
Normal file
40
koin/src/commonMain/kotlin/GetWithDefinition.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
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
|
||||
)
|
@@ -0,0 +1,26 @@
|
||||
package dev.inmo.micro_utils.koin.annotations
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Use this annotation to mark files near to which generator should place generated extensions for koin [org.koin.core.scope.Scope]
|
||||
* and [org.koin.core.Koin]
|
||||
*
|
||||
* @param name Name for definitions. This name will be available as extension for [org.koin.core.scope.Scope] and [org.koin.core.Koin]
|
||||
* @param type Type of extensions. It is base star-typed class
|
||||
* @param typeArgs Generic types for [type]. For example, if [type] == `Something::class` and [typeArgs] == `G1::class,
|
||||
* G2::class`, the result type will be `Something<G1, G2>`
|
||||
* @param nullable In case when true, extension will not throw error when definition has not been registered in koin
|
||||
* @param generateSingle Generate definition factory with [org.koin.core.module.Module.single]. You will be able to use
|
||||
* the extension [org.koin.core.module.Module].[name]Single(createdAtStart/* default false */) { /* your definition */ }
|
||||
* @param generateFactory Generate definition factory with [org.koin.core.module.Module.factory]. You will be able to use
|
||||
* the extension [org.koin.core.module.Module].[name]Factory { /* your definition */ }
|
||||
*/
|
||||
@Target(AnnotationTarget.FILE)
|
||||
@Repeatable
|
||||
annotation class GenerateGenericKoinDefinition(
|
||||
val name: String,
|
||||
val nullable: Boolean = true,
|
||||
val generateSingle: Boolean = true,
|
||||
val generateFactory: Boolean = true
|
||||
)
|
27
koin/src/jvmMain/kotlin/LazyInject.kt
Normal file
27
koin/src/jvmMain/kotlin/LazyInject.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
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)
|
@@ -18,5 +18,17 @@ kotlin {
|
||||
androidMain {
|
||||
dependsOn jvmMain
|
||||
}
|
||||
|
||||
linuxX64Main {
|
||||
dependencies {
|
||||
api internalProject("micro_utils.mime_types")
|
||||
}
|
||||
}
|
||||
|
||||
mingwX64Main {
|
||||
dependencies {
|
||||
api internalProject("micro_utils.mime_types")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,10 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
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
|
||||
|
||||
expect suspend fun MPPFile.inputProvider(): InputProvider
|
||||
fun MPPFile.inputProvider(): InputProvider = InputProvider(filesize) {
|
||||
input()
|
||||
}
|
||||
|
@@ -1,11 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
@@ -7,5 +7,3 @@ import io.ktor.utils.io.streams.asInput
|
||||
fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
|
||||
inputStream().asInput()
|
||||
}
|
||||
|
||||
actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync()
|
||||
|
40
ktor/client/src/linuxX64Main/kotlin/ActualTemporalUpload.kt
Normal file
40
ktor/client/src/linuxX64Main/kotlin/ActualTemporalUpload.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.filename
|
||||
import dev.inmo.micro_utils.ktor.common.TemporalFileId
|
||||
import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.onUpload
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
|
||||
internal val MPPFile.mimeType: String
|
||||
get() = getMimeTypeOrAny(filename.extension).raw
|
||||
|
||||
actual suspend fun HttpClient.tempUpload(
|
||||
fullTempUploadDraftPath: String,
|
||||
file: MPPFile,
|
||||
onUpload: OnUploadCallback
|
||||
): TemporalFileId {
|
||||
val inputProvider = file.inputProvider()
|
||||
val fileId = submitFormWithBinaryData(
|
||||
fullTempUploadDraftPath,
|
||||
formData = formData {
|
||||
append(
|
||||
"data",
|
||||
inputProvider,
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, file.mimeType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
onUpload(onUpload)
|
||||
}.bodyAsText()
|
||||
return TemporalFileId(fileId)
|
||||
}
|
107
ktor/client/src/linuxX64Main/kotlin/ActualUniUpload.kt
Normal file
107
ktor/client/src/linuxX64Main/kotlin/ActualUniUpload.kt
Normal file
@@ -0,0 +1,107 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.Progress
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.mergeHeaders
|
||||
import io.ktor.client.plugins.onUpload
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.forms.InputProvider
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitForm
|
||||
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.Parameters
|
||||
import io.ktor.http.content.PartData
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import kotlinx.serialization.StringFormat
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.serializer
|
||||
|
||||
/**
|
||||
* Will execute submitting of multipart data request
|
||||
*
|
||||
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
|
||||
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
|
||||
* in case you wish to pass other source of multipart binary data than regular file
|
||||
* @suppress
|
||||
*/
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
actual suspend fun <T> HttpClient.uniUpload(
|
||||
url: String,
|
||||
data: Map<String, Any>,
|
||||
resultDeserializer: DeserializationStrategy<T>,
|
||||
headers: Headers,
|
||||
stringFormat: StringFormat,
|
||||
onUpload: OnUploadCallback
|
||||
): T? {
|
||||
val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo }
|
||||
|
||||
val formData = formData {
|
||||
for (k in data.keys) {
|
||||
val v = data[k] ?: continue
|
||||
when (v) {
|
||||
is MPPFile -> append(
|
||||
k,
|
||||
v.inputProvider(),
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, v.mimeType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
|
||||
}
|
||||
)
|
||||
is UniUploadFileInfo -> append(
|
||||
k,
|
||||
InputProvider(block = v.inputAllocator),
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, v.mimeType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
|
||||
}
|
||||
)
|
||||
else -> append(
|
||||
k,
|
||||
stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val requestBuilder: HttpRequestBuilder.() -> Unit = {
|
||||
headers {
|
||||
appendAll(headers)
|
||||
}
|
||||
onUpload { bytesSentTotal, contentLength ->
|
||||
onUpload(bytesSentTotal, contentLength)
|
||||
}
|
||||
}
|
||||
|
||||
val response = if (withBinary) {
|
||||
submitFormWithBinaryData(
|
||||
url,
|
||||
formData,
|
||||
block = requestBuilder
|
||||
)
|
||||
} else {
|
||||
submitForm(
|
||||
url,
|
||||
Parameters.build {
|
||||
for (it in formData) {
|
||||
val formItem = (it as PartData.FormItem)
|
||||
append(it.name!!, it.value)
|
||||
}
|
||||
},
|
||||
block = requestBuilder
|
||||
)
|
||||
}
|
||||
|
||||
return if (response.status == HttpStatusCode.OK) {
|
||||
stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
40
ktor/client/src/mingwX64Main/kotlin/ActualTemporalUpload.kt
Normal file
40
ktor/client/src/mingwX64Main/kotlin/ActualTemporalUpload.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.filename
|
||||
import dev.inmo.micro_utils.ktor.common.TemporalFileId
|
||||
import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.onUpload
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
|
||||
internal val MPPFile.mimeType: String
|
||||
get() = getMimeTypeOrAny(filename.extension).raw
|
||||
|
||||
actual suspend fun HttpClient.tempUpload(
|
||||
fullTempUploadDraftPath: String,
|
||||
file: MPPFile,
|
||||
onUpload: OnUploadCallback
|
||||
): TemporalFileId {
|
||||
val inputProvider = file.inputProvider()
|
||||
val fileId = submitFormWithBinaryData(
|
||||
fullTempUploadDraftPath,
|
||||
formData = formData {
|
||||
append(
|
||||
"data",
|
||||
inputProvider,
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, file.mimeType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
onUpload(onUpload)
|
||||
}.bodyAsText()
|
||||
return TemporalFileId(fileId)
|
||||
}
|
107
ktor/client/src/mingwX64Main/kotlin/ActualUniUpload.kt
Normal file
107
ktor/client/src/mingwX64Main/kotlin/ActualUniUpload.kt
Normal file
@@ -0,0 +1,107 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.Progress
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.mergeHeaders
|
||||
import io.ktor.client.plugins.onUpload
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.forms.InputProvider
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitForm
|
||||
import io.ktor.client.request.forms.submitFormWithBinaryData
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.Parameters
|
||||
import io.ktor.http.content.PartData
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import kotlinx.serialization.StringFormat
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.serializer
|
||||
|
||||
/**
|
||||
* Will execute submitting of multipart data request
|
||||
*
|
||||
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
|
||||
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
|
||||
* in case you wish to pass other source of multipart binary data than regular file
|
||||
* @suppress
|
||||
*/
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
actual suspend fun <T> HttpClient.uniUpload(
|
||||
url: String,
|
||||
data: Map<String, Any>,
|
||||
resultDeserializer: DeserializationStrategy<T>,
|
||||
headers: Headers,
|
||||
stringFormat: StringFormat,
|
||||
onUpload: OnUploadCallback
|
||||
): T? {
|
||||
val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo }
|
||||
|
||||
val formData = formData {
|
||||
for (k in data.keys) {
|
||||
val v = data[k] ?: continue
|
||||
when (v) {
|
||||
is MPPFile -> append(
|
||||
k,
|
||||
v.inputProvider(),
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, v.mimeType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
|
||||
}
|
||||
)
|
||||
is UniUploadFileInfo -> append(
|
||||
k,
|
||||
InputProvider(block = v.inputAllocator),
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, v.mimeType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
|
||||
}
|
||||
)
|
||||
else -> append(
|
||||
k,
|
||||
stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val requestBuilder: HttpRequestBuilder.() -> Unit = {
|
||||
headers {
|
||||
appendAll(headers)
|
||||
}
|
||||
onUpload { bytesSentTotal, contentLength ->
|
||||
onUpload(bytesSentTotal, contentLength)
|
||||
}
|
||||
}
|
||||
|
||||
val response = if (withBinary) {
|
||||
submitFormWithBinaryData(
|
||||
url,
|
||||
formData,
|
||||
block = requestBuilder
|
||||
)
|
||||
} else {
|
||||
submitForm(
|
||||
url,
|
||||
Parameters.build {
|
||||
for (it in formData) {
|
||||
val formItem = (it as PartData.FormItem)
|
||||
append(it.name!!, it.value)
|
||||
}
|
||||
},
|
||||
block = requestBuilder
|
||||
)
|
||||
}
|
||||
|
||||
return if (response.status == HttpStatusCode.OK) {
|
||||
stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
package dev.inmo.micro_utils.ktor.common
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import korlibs.time.DateTime
|
||||
|
||||
typealias FromToDateTime = Pair<DateTime?, DateTime?>
|
||||
|
||||
|
11
ktor/common/src/linuxX64Main/kotlin/ActualMPPFileInput.kt
Normal file
11
ktor/common/src/linuxX64Main/kotlin/ActualMPPFileInput.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package dev.inmo.micro_utils.ktor.common
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.bytesAllocatorSync
|
||||
import io.ktor.utils.io.core.ByteReadPacket
|
||||
import io.ktor.utils.io.core.Input
|
||||
|
||||
actual fun MPPFile.input(): Input {
|
||||
return ByteReadPacket(bytesAllocatorSync())
|
||||
}
|
||||
|
10
ktor/common/src/mingwX64Main/kotlin/ActualMPPFileInput.kt
Normal file
10
ktor/common/src/mingwX64Main/kotlin/ActualMPPFileInput.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
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())
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import korlibs.time.DateTime
|
||||
import dev.inmo.micro_utils.ktor.common.FromToDateTime
|
||||
import io.ktor.http.Parameters
|
||||
|
||||
|
@@ -12,10 +12,22 @@ 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(
|
||||
field: String
|
||||
) = request.queryParameters[field]
|
||||
|
||||
fun ApplicationCall.getQueryParameters(
|
||||
field: String
|
||||
) = request.queryParameters.getAll(field)
|
||||
|
||||
suspend fun ApplicationCall.getQueryParameterOrSendError(
|
||||
field: String
|
||||
) = getQueryParameter(field).also {
|
||||
@@ -23,3 +35,11 @@ suspend fun ApplicationCall.getQueryParameterOrSendError(
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import math
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import pandas as pd
|
||||
@@ -17,33 +19,45 @@ def fix_name(category, raw_name):
|
||||
result += out1
|
||||
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
|
||||
if __name__ == '__main__':
|
||||
df = pd.read_html(open('table.html', 'r'))
|
||||
df = pd.read_html(open('local.table.html', 'r'))
|
||||
mimes = []
|
||||
for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows():
|
||||
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_name = mime.split('/', 1)[1]
|
||||
mimes.append({
|
||||
'mime_category': mime_category,
|
||||
'mime_name': mime_name,
|
||||
})
|
||||
mimes.append([
|
||||
mime_category,
|
||||
mime_name,
|
||||
extensions
|
||||
])
|
||||
|
||||
# codegen
|
||||
|
||||
mimes.sort(key=lambda x: x['mime_category'])
|
||||
grouped = itertools.groupby(mimes, lambda x: x['mime_category'])
|
||||
mimes.sort(key=lambda x: x[0])
|
||||
grouped = itertools.groupby(mimes, lambda x: x[0])
|
||||
code = ''
|
||||
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
|
||||
code2 += ' KnownMimeTypes.Any,\n'
|
||||
for key, group in grouped:
|
||||
group_name = fix_name(group, key)
|
||||
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name
|
||||
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String, extensions: Array<String> = emptyArray()) : MimeType, KnownMimeTypes(raw, extensions) {\n' % group_name
|
||||
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
|
||||
for mime in group:
|
||||
name = fix_name(mime['mime_category'], mime['mime_name'])
|
||||
code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s")\n' % (name, group_name, mime['mime_category'], mime['mime_name'])
|
||||
name = fix_name(mime[0], mime[1])
|
||||
code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s", arrayOf(%s))\n' % (name, group_name, mime[0], mime[1], ", ".join(mime[2]))
|
||||
code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name)
|
||||
code += '}\n\n'
|
||||
code2 += ')\n'
|
||||
|
@@ -0,0 +1,24 @@
|
||||
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
|
File diff suppressed because it is too large
Load Diff
@@ -5,4 +5,6 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable(MimeTypeSerializer::class)
|
||||
interface MimeType {
|
||||
val raw: String
|
||||
val extensions: Array<String>
|
||||
get() = emptyArray()
|
||||
}
|
||||
|
@@ -15,6 +15,8 @@ kotlin {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
@@ -18,6 +18,8 @@ kotlin {
|
||||
android {
|
||||
publishAllLibraryVariants()
|
||||
}
|
||||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
@@ -50,6 +52,16 @@ kotlin {
|
||||
implementation libs.android.espresso
|
||||
}
|
||||
}
|
||||
mingwX64Test {
|
||||
dependencies {
|
||||
implementation kotlin('test-junit')
|
||||
}
|
||||
}
|
||||
linuxX64Test {
|
||||
dependencies {
|
||||
implementation kotlin('test-junit')
|
||||
}
|
||||
}
|
||||
|
||||
androidMain.dependsOn jvmMain
|
||||
}
|
||||
|
6
renovate.json
Normal file
6
renovate.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
}
|
@@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.cache
|
||||
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
@@ -15,6 +16,12 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
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 invalidate() = kvCache.clear()
|
||||
|
@@ -24,6 +24,12 @@ 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()
|
||||
}
|
||||
|
||||
|
@@ -52,6 +52,14 @@ open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
||||
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 {
|
||||
|
@@ -21,9 +21,9 @@ open class AutoRecacheWriteCRUDRepo<RegisteredObject, Id, InputObject>(
|
||||
protected val idGetter: (RegisteredObject) -> Id
|
||||
) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo {
|
||||
override val deletedObjectsIdsFlow: Flow<Id>
|
||||
get() = (originalRepo.deletedObjectsIdsFlow + kvCache.onValueRemoved).distinctUntilChanged()
|
||||
get() = (originalRepo.deletedObjectsIdsFlow).distinctUntilChanged()
|
||||
override val newObjectsFlow: Flow<RegisteredObject>
|
||||
get() = (originalRepo.newObjectsFlow + kvCache.onNewValue.map { it.second }).distinctUntilChanged()
|
||||
get() = (originalRepo.newObjectsFlow).distinctUntilChanged()
|
||||
override val updatedObjectsFlow: Flow<RegisteredObject>
|
||||
get() = originalRepo.updatedObjectsFlow
|
||||
|
||||
|
@@ -52,6 +52,14 @@ open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
|
||||
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 {
|
||||
|
@@ -17,10 +17,10 @@ open class AutoRecacheWriteKeyValueRepo<Id, RegisteredObject>(
|
||||
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache()
|
||||
) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||
override val onValueRemoved: Flow<Id>
|
||||
get() = (originalRepo.onValueRemoved + kvCache.onValueRemoved).distinctUntilChanged()
|
||||
get() = (originalRepo.onValueRemoved).distinctUntilChanged()
|
||||
|
||||
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
|
||||
get() = (originalRepo.onNewValue + kvCache.onNewValue).distinctUntilChanged()
|
||||
get() = (originalRepo.onNewValue).distinctUntilChanged()
|
||||
|
||||
private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) {
|
||||
kvCache.unset(it)
|
||||
|
@@ -7,6 +7,7 @@ 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
|
||||
@@ -24,7 +25,7 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
|
||||
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
|
||||
get() = originalRepo.onNewValue
|
||||
override val onDataCleared: Flow<Id>
|
||||
get() = (originalRepo.onDataCleared + kvCache.onValueRemoved).distinctUntilChanged()
|
||||
get() = (originalRepo.onDataCleared).distinctUntilChanged()
|
||||
|
||||
private val onDataClearedListeningJob = originalRepo.onDataCleared.subscribeSafelyWithoutExceptions(scope) {
|
||||
kvCache.unset(it)
|
||||
@@ -50,7 +51,7 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
|
||||
|
||||
override suspend fun clearWithValue(v: RegisteredObject) {
|
||||
originalRepo.clearWithValue(v)
|
||||
doForAllWithNextPaging(FirstPagePagination(kvCache.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE)) {
|
||||
doForAllWithNextPaging(kvCache.maxPagePagination()) {
|
||||
kvCache.keys(it).also {
|
||||
it.results.forEach { id ->
|
||||
kvCache.get(id) ?.takeIf { it.contains(v) } ?.let {
|
||||
@@ -73,6 +74,19 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
|
||||
}
|
||||
}
|
||||
|
||||
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) ->
|
||||
|
@@ -58,6 +58,12 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
{ 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(
|
||||
{ get(id) ?.optional ?: Optional.absent() },
|
||||
{ getById(id) },
|
||||
@@ -96,8 +102,15 @@ open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
kvCache: FullKVCache<IdType, ObjectType>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
idGetter: (ObjectType) -> IdType
|
||||
) = FullCRUDCacheRepo(this, kvCache, scope, idGetter)
|
||||
) = fullyCached(kvCache, scope, idGetter)
|
||||
|
@@ -9,7 +9,6 @@ import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
open class FullReadKeyValueCacheRepo<Key,Value>(
|
||||
@@ -59,6 +58,12 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
|
||||
{ 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(
|
||||
{ keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||
{ keys(pagination, reversed) },
|
||||
@@ -112,7 +117,13 @@ open class FullKeyValueCacheRepo<Key,Value>(
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
kvCache: FullKVCache<Key, Value>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) = FullKeyValueCacheRepo(this, kvCache, scope)
|
||||
) = fullyCached(kvCache, scope)
|
||||
|
@@ -157,8 +157,18 @@ 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(
|
||||
kvCache: FullKVCache<Key, List<Value>>,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
|
@@ -28,7 +28,7 @@ suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||
clear: Boolean = true,
|
||||
) {
|
||||
actualizeAll(clear) {
|
||||
repo.getAll { keys(it) }.toMap()
|
||||
repo.getAll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
|
||||
clear: Boolean = true,
|
||||
) {
|
||||
actualizeAll(clear) {
|
||||
repo.getAll { keys(it) }.toMap()
|
||||
repo.getAll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||
clear: Boolean = true,
|
||||
) {
|
||||
actualizeAll(clear) {
|
||||
repo.getAllByWithNextPaging {
|
||||
getIdsByPagination(it)
|
||||
}.mapNotNull { it to (repo.getById(it) ?: return@mapNotNull null) }.toMap()
|
||||
repo.getAll()
|
||||
}
|
||||
}
|
||||
|
@@ -2,8 +2,10 @@ package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
@@ -51,6 +53,16 @@ interface ReadKeyValueRepo<Key, Value> : Repo {
|
||||
*/
|
||||
suspend fun contains(key: Key): Boolean
|
||||
|
||||
suspend fun getAll(): Map<Key, Value> = getAllByWithNextPaging(maxPagePagination()) {
|
||||
keys(it).let {
|
||||
it.changeResultsUnchecked(
|
||||
it.results.mapNotNull {
|
||||
it to (get(it) ?: return@mapNotNull null)
|
||||
}
|
||||
)
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
/**
|
||||
* @return count of all collection objects
|
||||
*/
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@@ -44,9 +45,23 @@ interface WriteKeyValuesRepo<Key, Value> : Repo {
|
||||
|
||||
suspend fun add(toAdd: Map<Key, List<Value>>)
|
||||
|
||||
/**
|
||||
* Removes [Value]s by passed [Key]s without full clear of all data by [Key]
|
||||
*/
|
||||
suspend fun remove(toRemove: Map<Key, List<Value>>)
|
||||
|
||||
/**
|
||||
* Removes [v] without full clear of all data by [Key]s with [v]
|
||||
*/
|
||||
suspend fun removeWithValue(v: Value)
|
||||
|
||||
/**
|
||||
* Fully clear all data by [k]
|
||||
*/
|
||||
suspend fun clear(k: Key)
|
||||
/**
|
||||
* Clear [v] **with** full clear of all data by [Key]s with [v]
|
||||
*/
|
||||
suspend fun clearWithValue(v: Value)
|
||||
|
||||
suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||
@@ -100,6 +115,21 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
|
||||
keysResult.currentPageIfNotEmpty()
|
||||
}
|
||||
}
|
||||
suspend override fun removeWithValue(v: Value) {
|
||||
val toRemove = mutableMapOf<Key, List<Value>>()
|
||||
|
||||
doForAllWithNextPaging {
|
||||
keys(it).also {
|
||||
it.results.forEach {
|
||||
if (contains(it, v)) {
|
||||
toRemove[it] = listOf(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove(toRemove)
|
||||
}
|
||||
}
|
||||
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value>
|
||||
|
||||
|
@@ -2,6 +2,9 @@ package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.Pagination
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithCurrentPaging
|
||||
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
|
||||
@@ -9,6 +12,14 @@ interface ReadCRUDRepo<ObjectType, IdType> : Repo {
|
||||
suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType>
|
||||
suspend fun getById(id: IdType): ObjectType?
|
||||
suspend fun contains(id: IdType): Boolean
|
||||
suspend fun getAll(): Map<IdType, ObjectType> = getAllWithCurrentPaging(maxPagePagination()) {
|
||||
getIdsByPagination(it).let {
|
||||
it.changeResultsUnchecked(
|
||||
it.results.mapNotNull { it to (getById(it) ?: return@mapNotNull null) }
|
||||
)
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType>
|
||||
|
@@ -32,6 +32,10 @@ open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>(
|
||||
|
||||
override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey())
|
||||
|
||||
override suspend fun getAll(): Map<FromId, FromRegistered> = to.getAll().asSequence().associate { (k, v) ->
|
||||
k.toInnerKey() to v.toInnerValue()
|
||||
}
|
||||
|
||||
override suspend fun getById(id: FromId): FromRegistered? = to.getById(
|
||||
id.toOutKey()
|
||||
) ?.toInnerValue()
|
||||
|
@@ -55,6 +55,10 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
key.toOutKey()
|
||||
)
|
||||
|
||||
override suspend fun getAll(): Map<FromKey, FromValue> = to.getAll().map { (k, v) ->
|
||||
k.toInnerKey() to v.toInnerValue()
|
||||
}.toMap()
|
||||
|
||||
override suspend fun count(): Long = to.count()
|
||||
}
|
||||
|
||||
|
@@ -97,6 +97,8 @@ open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
|
||||
}.toMap()
|
||||
)
|
||||
|
||||
override suspend fun removeWithValue(v: FromValue) = to.removeWithValue(v.toOutValue())
|
||||
|
||||
override suspend fun set(toSet: Map<FromKey, List<FromValue>>) {
|
||||
to.set(
|
||||
toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap()
|
||||
|
@@ -1,12 +1,19 @@
|
||||
package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.common.toCoercedInt
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
|
||||
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
|
||||
pagination: Pagination,
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
||||
): List<T> = getAllWithNextPaging {
|
||||
): List<T> = getAllWithNextPaging(pagination) {
|
||||
methodCaller(this, it)
|
||||
}
|
||||
|
||||
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
||||
): List<T> = getAll(maxPagePagination(), methodCaller)
|
||||
|
@@ -1,15 +1,22 @@
|
||||
package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.common.toCoercedInt
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll(
|
||||
pagination: Pagination,
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
): List<Pair<Key, Value>> = getAllWithNextPaging {
|
||||
): List<Pair<Key, Value>> = getAllWithNextPaging(pagination) {
|
||||
val result = methodCaller(it)
|
||||
result.changeResultsUnchecked(
|
||||
result.results.mapNotNull { it to (get(it) ?: return@mapNotNull null) }
|
||||
)
|
||||
}
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
): List<Pair<Key, Value>> = getAll(maxPagePagination(), methodCaller)
|
||||
|
@@ -0,0 +1,11 @@
|
||||
package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.common.toCoercedInt
|
||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
|
||||
suspend inline fun ReadCRUDRepo<*, *>.maxPagePagination() = FirstPagePagination(count().toCoercedInt())
|
||||
suspend inline fun ReadKeyValueRepo<*, *>.maxPagePagination() = FirstPagePagination(count().toCoercedInt())
|
||||
suspend inline fun ReadKeyValuesRepo<*, *>.maxPagePagination() = FirstPagePagination(count().toCoercedInt())
|
@@ -1,13 +1,15 @@
|
||||
package dev.inmo.micro_utils.repos.pagination
|
||||
|
||||
import dev.inmo.micro_utils.common.toCoercedInt
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll(
|
||||
pagination: Pagination,
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
): List<Pair<Key, List<Value>>> = getAllWithNextPaging {
|
||||
): List<Pair<Key, List<Value>>> = getAllWithNextPaging(pagination) {
|
||||
val keysResult = methodCaller(it)
|
||||
keysResult.changeResultsUnchecked(
|
||||
keysResult.results.map { k ->
|
||||
@@ -15,3 +17,8 @@ suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAl
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll(
|
||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||
): List<Pair<Key, List<Value>>> = getAll(maxPagePagination(), methodCaller)
|
||||
|
@@ -12,6 +12,8 @@ open class ReadCRUDFromKeyValueRepo<RegisteredType, IdType>(
|
||||
|
||||
override suspend fun count(): Long = original.count()
|
||||
|
||||
override suspend fun getAll(): Map<IdType, RegisteredType> = original.getAll()
|
||||
|
||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<RegisteredType> = original.values(pagination)
|
||||
|
||||
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = original.keys(pagination)
|
||||
|
@@ -11,6 +11,7 @@ import dev.inmo.micro_utils.pagination.utils.paginate
|
||||
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.pagination.getAll
|
||||
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
@@ -40,6 +41,8 @@ value class ReadKeyValueFromCRUDRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAll(): Map<Key, Value> = original.getAll()
|
||||
|
||||
override suspend fun count(): Long = original.count()
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = original.contains(key)
|
||||
|
@@ -41,6 +41,8 @@ open class ReadKeyValueFromKeyValuesRepo<Key, Value>(
|
||||
return original.contains(key)
|
||||
}
|
||||
|
||||
override suspend fun getAll(): Map<Key, List<Value>> = original.getAll()
|
||||
|
||||
override suspend fun keys(v: List<Value>, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||
val keys = mutableSetOf<Key>()
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.set
|
||||
import dev.inmo.micro_utils.repos.unset
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -60,6 +61,24 @@ open class KeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeWithValue(v: Value) {
|
||||
val toRemove = mutableMapOf<Key, List<Value>>()
|
||||
|
||||
doForAllWithNextPaging {
|
||||
original.keys(it).also {
|
||||
it.results.forEach {
|
||||
val data = original.get(it) ?: return@forEach
|
||||
|
||||
if (v in data) {
|
||||
toRemove[it] = listOf(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove(toRemove)
|
||||
}
|
||||
|
||||
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||
original.set(
|
||||
toAdd.mapNotNull { (k, adding) ->
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.common.Warning
|
||||
import dev.inmo.micro_utils.common.filename
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import kotlinx.coroutines.*
|
||||
@@ -94,6 +95,8 @@ class FileReadKeyValueRepo(
|
||||
return File(folder, key).exists()
|
||||
}
|
||||
|
||||
override suspend fun getAll(): Map<String, File> = (folder.listFiles() ?.toList() ?: emptyList()).associateBy { it.filename.name }
|
||||
|
||||
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,18 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAll(): Map<IdType, ObjectType> = helper.readableTransaction {
|
||||
select(
|
||||
tableName,
|
||||
null,
|
||||
""
|
||||
).use {
|
||||
it.map {
|
||||
it.toId() to it.toObject()
|
||||
}
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
override suspend fun getById(id: IdType): ObjectType? = helper.readableTransaction {
|
||||
select(
|
||||
tableName,
|
||||
|
@@ -104,6 +104,15 @@ class KeyValueStore<T : Any> internal constructor (
|
||||
|
||||
override suspend fun contains(key: String): Boolean = sharedPreferences.contains(key)
|
||||
|
||||
override suspend fun getAll(): Map<String, T> {
|
||||
val resultMap = mutableMapOf<String, T>()
|
||||
for ((k, v) in sharedPreferences.all) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
resultMap[k] = (v as? T) ?: continue
|
||||
}
|
||||
return resultMap.toMap()
|
||||
}
|
||||
|
||||
override suspend fun count(): Long = sharedPreferences.all.size.toLong()
|
||||
|
||||
override suspend fun set(toSet: Map<String, T>) {
|
||||
|
@@ -6,6 +6,7 @@ import dev.inmo.micro_utils.common.mapNotNullA
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||
import dev.inmo.micro_utils.repos.*
|
||||
import dev.inmo.micro_utils.repos.crud.asId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
@@ -260,6 +261,19 @@ class OneToManyAndroidRepo<Key, Value>(
|
||||
_onValueRemoved.emit(k to v)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeWithValue(v: Value) {
|
||||
helper.blockingWritableTransaction {
|
||||
val keys = select(tableName, idColumnArray, "$valueColumnName=?", arrayOf(v.valueAsString())).map {
|
||||
it.asId.keyFromString()
|
||||
}
|
||||
keys.filter {
|
||||
delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(it.keyAsString(), v.valueAsString())) > 0
|
||||
}
|
||||
}.forEach { k ->
|
||||
_onValueRemoved.emit(k to v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> OneToManyAndroidRepo(
|
||||
|
@@ -45,5 +45,9 @@ abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
|
||||
select { selectById(id) }.limit(1).any()
|
||||
}
|
||||
|
||||
override suspend fun getAll(): Map<IdType, ObjectType> = transaction(database) {
|
||||
selectAll().associate { it.asId to it.asObject }
|
||||
}
|
||||
|
||||
override suspend fun count(): Long = transaction(db = database) { selectAll().count() }
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.*
|
||||
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
|
||||
@@ -32,6 +31,8 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
|
||||
select { selectById(key) }.limit(1).any()
|
||||
}
|
||||
|
||||
override suspend fun getAll(): Map<Key, Value> = transaction(database) { selectAll().associate { it.asKey to it.asObject } }
|
||||
|
||||
override suspend fun count(): Long = transaction(database) { selectAll().count() }
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||
|
@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value>
|
||||
@@ -66,9 +67,36 @@ open class ExposedKeyValuesRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeWithValue(v: Value) {
|
||||
transaction(database) {
|
||||
val keys = select { selectByValue(v) }.map { it.asKey }
|
||||
deleteWhere { SqlExpressionBuilder.selectByValue(v) }
|
||||
keys
|
||||
}.forEach {
|
||||
_onValueRemoved.emit(it to v)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear(k: Key) {
|
||||
transaction(database) {
|
||||
deleteWhere { keyColumn.eq(k) }
|
||||
}.also { _onDataCleared.emit(k) }
|
||||
}
|
||||
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
transaction(database) {
|
||||
val toClear = select { selectByValue(v) }
|
||||
.asSequence()
|
||||
.map { it.asKey to it.asObject }
|
||||
.groupBy { it.first }
|
||||
.mapValues { it.value.map { it.second } }
|
||||
deleteWhere { keyColumn.inList(toClear.keys) }
|
||||
toClear
|
||||
}.forEach {
|
||||
it.value.forEach { v ->
|
||||
_onValueRemoved.emit(it.key to v)
|
||||
}
|
||||
_onDataCleared.emit(it.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,3 +14,8 @@ dependencies {
|
||||
api libs.kotlin.poet
|
||||
api libs.ksp
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
@@ -100,7 +100,11 @@ class Processor(
|
||||
primaryConstructor(
|
||||
FunSpec.constructorBuilder().apply {
|
||||
ksClassProperties.forEach {
|
||||
addParameter(it.simpleName.getShortName(), it.typeName)
|
||||
addParameter(
|
||||
ParameterSpec.builder(it.simpleName.getShortName(), it.typeName).apply {
|
||||
annotations += it.annotations.map { it.toAnnotationSpec() }
|
||||
}.build()
|
||||
)
|
||||
typeBuilder.addProperty(
|
||||
PropertySpec.builder(it.simpleName.getShortName(), it.typeName, KModifier.OVERRIDE).apply {
|
||||
initializer(it.simpleName.getShortName())
|
||||
|
@@ -11,18 +11,20 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
@SerialName(value = "NewTest")
|
||||
public data class NewTest(
|
||||
public override val property1: String,
|
||||
public override val property2: Int,
|
||||
public override val parent: ParentTypeId?,
|
||||
override val property1: String,
|
||||
override val property2: Int,
|
||||
@Serializable
|
||||
override val parent: ParentTypeId?,
|
||||
) : Test
|
||||
|
||||
@Serializable
|
||||
@SerialName(value = "RegisteredTest")
|
||||
public data class RegisteredTest(
|
||||
public override val id: TestId,
|
||||
public override val property1: String,
|
||||
public override val property2: Int,
|
||||
public override val parent: ParentTypeId?,
|
||||
override val id: TestId,
|
||||
override val property1: String,
|
||||
override val property2: Int,
|
||||
@Serializable
|
||||
override val parent: ParentTypeId?,
|
||||
) : Test, IRegisteredTest
|
||||
|
||||
public fun Test.asNew(): NewTest = NewTest(property1, property2, parent)
|
||||
|
@@ -17,6 +17,7 @@ typealias ParentTypeId = TestId
|
||||
sealed interface Test {
|
||||
val property1: String
|
||||
val property2: Int
|
||||
@Serializable
|
||||
val parent: ParentTypeId?
|
||||
|
||||
@GenerateCRUDModelExcludeOverride
|
||||
|
@@ -26,6 +26,8 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
|
||||
|
||||
override suspend fun contains(id: IdType): Boolean = map.containsKey(id)
|
||||
|
||||
override suspend fun getAll(): Map<IdType, ObjectType> = map.toMap()
|
||||
|
||||
override suspend fun count(): Long = map.size.toLong()
|
||||
}
|
||||
|
||||
|
@@ -51,6 +51,8 @@ class ReadMapKeyValueRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAll(): Map<Key, Value> = map.toMap()
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = map.containsKey(key)
|
||||
|
||||
override suspend fun count(): Long = map.size.toLong()
|
||||
|
@@ -87,13 +87,27 @@ class MapWriteKeyValuesRepo<Key, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun removeWithValue(v: Value) {
|
||||
map.forEach { (k, values) ->
|
||||
if (values.remove(v)) {
|
||||
_onValueRemoved.emit(k to v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear(k: Key) {
|
||||
map.remove(k) ?.also { _onDataCleared.emit(k) }
|
||||
}
|
||||
|
||||
override suspend fun clearWithValue(v: Value) {
|
||||
map.forEach { (k, values) ->
|
||||
if (values.remove(v)) _onValueRemoved.emit(k to v)
|
||||
map.filter { (_, values) ->
|
||||
values.contains(v)
|
||||
}.forEach {
|
||||
map.remove(it.key) ?.onEach { v ->
|
||||
_onValueRemoved.emit(it.key to v)
|
||||
} ?.also { _ ->
|
||||
_onDataCleared.emit(it.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -44,6 +44,7 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
||||
typeInfo<PaginationResult<ObjectType>>(),
|
||||
typeInfo<PaginationResult<IdType>>(),
|
||||
contentType,
|
||||
typeInfo<Map<IdType, ObjectType>>(),
|
||||
idSerializer
|
||||
),
|
||||
KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(
|
||||
|
@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.countRouting
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.getAllRoute
|
||||
import dev.inmo.micro_utils.repos.ktor.common.idParameterName
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
@@ -21,6 +22,7 @@ class KtorReadCRUDRepoClient<ObjectType, IdType> (
|
||||
private val paginationObjectType: TypeInfo,
|
||||
private val paginationIdType: TypeInfo,
|
||||
private val contentType: ContentType,
|
||||
private val mapTypeInfo: TypeInfo,
|
||||
private val idSerializer: suspend (IdType) -> String
|
||||
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = httpClient.get(
|
||||
@@ -58,6 +60,15 @@ class KtorReadCRUDRepoClient<ObjectType, IdType> (
|
||||
contentType(contentType)
|
||||
}.body()
|
||||
|
||||
override suspend fun getAll(): Map<IdType, ObjectType> = httpClient.get(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
getAllRoute
|
||||
)
|
||||
) {
|
||||
contentType(contentType)
|
||||
}.body(mapTypeInfo)
|
||||
|
||||
override suspend fun count(): Long = httpClient.get(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
@@ -80,6 +91,7 @@ inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient(
|
||||
typeInfo<PaginationResult<ObjectType>>(),
|
||||
typeInfo<PaginationResult<IdType>>(),
|
||||
contentType,
|
||||
typeInfo<Map<IdType, ObjectType>>(),
|
||||
idSerializer
|
||||
)
|
||||
|
||||
|
@@ -23,6 +23,7 @@ class KtorReadKeyValueRepoClient<Key, Value>(
|
||||
private val objectType: TypeInfo,
|
||||
private val paginationResultObjectsTypeInfo: TypeInfo,
|
||||
private val paginationResultIdsTypeInfo: TypeInfo,
|
||||
private val mapKeyValueTypeInfo: TypeInfo,
|
||||
private val idSerializer: suspend (Key) -> String,
|
||||
private val valueSerializer: suspend (Value) -> String
|
||||
) : ReadKeyValueRepo<Key, Value> {
|
||||
@@ -48,6 +49,15 @@ class KtorReadKeyValueRepoClient<Key, Value>(
|
||||
contentType(contentType)
|
||||
}.body()
|
||||
|
||||
override suspend fun getAll(): Map<Key, Value> = httpClient.get(
|
||||
buildStandardUrl(
|
||||
baseUrl,
|
||||
getAllRoute
|
||||
)
|
||||
) {
|
||||
contentType(contentType)
|
||||
}.body(mapKeyValueTypeInfo)
|
||||
|
||||
override suspend fun values(
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
@@ -103,6 +113,7 @@ inline fun <reified Key, reified Value> KtorReadKeyValueRepoClient(
|
||||
typeInfo<Value>(),
|
||||
typeInfo<PaginationResult<Value>>(),
|
||||
typeInfo<PaginationResult<Key>>(),
|
||||
typeInfo<Map<Key, Value>>(),
|
||||
idSerializer,
|
||||
valueSerializer
|
||||
)
|
||||
|
@@ -47,6 +47,17 @@ class KtorWriteKeyValuesRepoClient<Key : Any, Value : Any>(
|
||||
}.throwOnUnsuccess { "Unable to remove $toRemove" }
|
||||
}
|
||||
|
||||
@OptIn(InternalAPI::class)
|
||||
override suspend fun removeWithValue(v: Value) {
|
||||
httpClient.post(
|
||||
buildStandardUrl(baseUrl, removeWithValueRoute)
|
||||
) {
|
||||
body = v
|
||||
bodyType = valueTypeInfo
|
||||
contentType(contentType)
|
||||
}.throwOnUnsuccess { "Unable to remove $v" }
|
||||
}
|
||||
|
||||
@OptIn(InternalAPI::class)
|
||||
override suspend fun clear(k: Key) {
|
||||
httpClient.post(
|
||||
|
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.common
|
||||
|
||||
const val getAllRoute = "getAll"
|
@@ -13,6 +13,7 @@ const val onDataClearedRoute = "onDataCleared"
|
||||
|
||||
const val addRoute = "add"
|
||||
const val removeRoute = "remove"
|
||||
const val removeWithValueRoute = "removeWithValue"
|
||||
const val clearRoute = "clear"
|
||||
const val clearWithValueRoute = "clearWithValue"
|
||||
const val setRoute = "set"
|
||||
|
@@ -83,6 +83,9 @@ class CRUDTests {
|
||||
assertEquals(map.size, 0)
|
||||
assertEquals(map.size.toLong(), crudClient.count())
|
||||
assertEquals(0, crudClient.count())
|
||||
|
||||
assertEquals(map, crudClient.getAll())
|
||||
|
||||
server.stop()
|
||||
}
|
||||
}
|
||||
|
@@ -133,6 +133,8 @@ class KVTests {
|
||||
crudClient.count()
|
||||
)
|
||||
|
||||
assertEquals(map, crudClient.getAll())
|
||||
|
||||
server.stop()
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import dev.inmo.micro_utils.pagination.extractPagination
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.countRouting
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.getAllRoute
|
||||
import dev.inmo.micro_utils.repos.ktor.common.idParameterName
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.application.call
|
||||
@@ -53,6 +54,10 @@ inline fun <reified ObjectType, reified IdType> Route.configureReadCRUDRepoRoute
|
||||
)
|
||||
}
|
||||
|
||||
get(getAllRoute) {
|
||||
call.respond(originalRepo.getAll())
|
||||
}
|
||||
|
||||
get(countRouting) {
|
||||
call.respond(
|
||||
originalRepo.count()
|
||||
|
@@ -70,6 +70,10 @@ inline fun <reified Key, reified Value> Route.configureReadKeyValueRepoRoutes (
|
||||
call.respond(originalRepo.contains(key))
|
||||
}
|
||||
|
||||
get(getAllRoute) {
|
||||
call.respond(originalRepo.getAll())
|
||||
}
|
||||
|
||||
get(countRoute) {
|
||||
call.respond(originalRepo.count())
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user