mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-18 14:59:24 +00:00
Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
1380d5f8e1 | |||
5f65260bfe | |||
11f0dcfc01 | |||
555b7886a4 | |||
3707a6c0ce | |||
4c8d92b4c3 | |||
8bbd33c896 | |||
ac33a3580f | |||
a64a32fbe6 | |||
9493e97a38 | |||
88bd770260 | |||
a7bd33b7bf | |||
73c724a2e5 | |||
d8cf3c6726 | |||
15dee238b5 | |||
c70626734e | |||
5a765ea1bc | |||
8215f9d2c6 | |||
d2e6d2ec80 | |||
3718181e8f | |||
0d825cf424 | |||
28a804d988 | |||
9e4bb9d678 | |||
9c40d7da3d | |||
2b76ad0aa9 | |||
e4b619e050 | |||
36c09feaf2 | |||
2d68321503 | |||
85455ab21c | |||
18d63eb980 | |||
2e429e9704 | |||
f4af28059b | |||
c1476bd075 | |||
16c720fddd | |||
8b4b4a5eca | |||
32e6e5b7e2 | |||
a9f7fd8e32 | |||
95be1a26f2 | |||
ef9b31aee0 | |||
df3c01ff0a | |||
4704c5a33d | |||
225c06550a | |||
f0987614c6 | |||
269c2876f3 | |||
168d6acf7c | |||
a5f718e257 | |||
4f68459582 | |||
442db122cf | |||
580d757be2 | |||
47b0f6d2d8 | |||
3f6f6ebc2b | |||
2645ea29d6 | |||
79f2041565 | |||
4a7567f288 | |||
8a890ed6ed | |||
3d90df6897 | |||
681c13144a | |||
b64f2e6d32 | |||
428eabb1bd | |||
2162e83bce | |||
6142022283 | |||
e6d9c8250f | |||
46178e723b | |||
605f55acd2 | |||
0f8b69aa60 | |||
551d8ec480 |
114
CHANGELOG.md
114
CHANGELOG.md
@@ -1,5 +1,119 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.17.6
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.8.10` -> `1.8.20`
|
||||||
|
* `KSLog`: `1.0.0` -> `1.1.1`
|
||||||
|
* `Compose`: `1.3.1` -> `1.4.0`
|
||||||
|
* `Koin`: `3.3.2` -> `3.4.0`
|
||||||
|
* `RecyclerView`: `1.2.1` -> `1.3.0`
|
||||||
|
* `Fragment`: `1.5.5` -> `1.5.6`
|
||||||
|
* Experimentally (`!!!`) add `linuxX64` and `mingwX64` targets
|
||||||
|
|
||||||
|
## 0.17.5
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* Conversations of number primitives with bounds care
|
||||||
|
* `Repos`:
|
||||||
|
* `Common`:
|
||||||
|
* By default, `getAll` for repos will take all the size of repo as page size
|
||||||
|
* New extension for all built-in repos `maxPagePagination`
|
||||||
|
* All the repos got `getAll` functions
|
||||||
|
|
||||||
|
## 0.17.4
|
||||||
|
|
||||||
|
* `Serialization`:
|
||||||
|
* `Mapper`:
|
||||||
|
* Module inited
|
||||||
|
* `Versions`:
|
||||||
|
* `Compose`: `1.3.1-rc02` -> `1.3.1`
|
||||||
|
|
||||||
|
## 0.17.3
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* Add `fixed` extensions for `Float` and `Double`
|
||||||
|
* New function `emptyDiff`
|
||||||
|
* Now you may pass custom `comparisonFun` to all `diff` functions
|
||||||
|
|
||||||
|
## 0.17.2
|
||||||
|
|
||||||
|
* `FSM`:
|
||||||
|
* `DefaultStatesManager.onUpdateContextsConflictResolver` and `DefaultStatesManager.onStartContextsConflictResolver` now return `false` by default
|
||||||
|
|
||||||
|
## 0.17.1
|
||||||
|
|
||||||
|
* **Hotfix** for absence of jvm dependencies in android modules
|
||||||
|
* `Versions`:
|
||||||
|
* `Ktor`: `2.2.3` -> `2.2.4`
|
||||||
|
|
||||||
|
## 0.17.0
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Kotlin`: `1.7.20` -> `1.8.10`
|
||||||
|
* `Serialization`: `1.4.1` -> `1.5.0`
|
||||||
|
* `KSLog`: `0.5.4` -> `1.0.0`
|
||||||
|
* `AppCompat`: `1.6.0` -> `1.6.1`
|
||||||
|
|
||||||
|
## 0.16.13
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Generator`:
|
||||||
|
* Module has been created
|
||||||
|
|
||||||
|
## 0.16.12
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Exposed`:
|
||||||
|
* `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach
|
||||||
|
* `Koin`:
|
||||||
|
* `Generator`:
|
||||||
|
* Module has been created
|
||||||
|
|
||||||
|
## 0.16.11
|
||||||
|
|
||||||
|
* `LanguageCodes`:
|
||||||
|
* In android and JVM targets now available `toJavaLocale` and from Java `Locale` conversations from/to
|
||||||
|
`IetfLanguageCode`
|
||||||
|
|
||||||
|
## 0.16.10
|
||||||
|
|
||||||
|
* `Repos`:
|
||||||
|
* `Cache`:
|
||||||
|
* New transformer type: `ReadCRUDFromKeyValueRepo`
|
||||||
|
* New transformer type: `ReadKeyValueFromCRUDRepo`
|
||||||
|
* `Pagination`:
|
||||||
|
* New `paginate` extensions with `reversed` support for `List`/`Set`
|
||||||
|
|
||||||
|
## 0.16.9
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Koin`: `3.2.2` -> `3.3.2`
|
||||||
|
* `AppCompat`: `1.5.1` -> `1.6.0`
|
||||||
|
* `Ktor`:
|
||||||
|
* `Client`
|
||||||
|
* `HttpResponse.bodyOrNull` now retrieve callback to check if body should be received or null
|
||||||
|
* New extension `HttpResponse.bodyOrNullOnNoContent`
|
||||||
|
|
||||||
|
## 0.16.8
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Ktor`: `2.2.2` -> `2.2.3`
|
||||||
|
* `Ktor`:
|
||||||
|
* `Client`
|
||||||
|
* Fixes in `HttpClient.uniUpload`
|
||||||
|
* `Server`
|
||||||
|
* Fixes in `PartData.FileItem.download`
|
||||||
|
* `Repos`:
|
||||||
|
* `Cache`:
|
||||||
|
* New type of caches: `FallbackCacheRepo`
|
||||||
|
* Fixes in `Write*` variants of cached repos
|
||||||
|
* New type `ActionWrapper`
|
||||||
|
* New `AutoRecache*` classes for all types of repos as `FallbackCacheRepo`s
|
||||||
|
* `Common`:
|
||||||
|
* New transformations for key-value and key-values vice-verse
|
||||||
|
* Fixes in `FileReadKeyValueRepo`
|
||||||
|
|
||||||
## 0.16.7
|
## 0.16.7
|
||||||
|
|
||||||
* `Common`:
|
* `Common`:
|
||||||
|
@@ -9,6 +9,7 @@ buildscript {
|
|||||||
dependencies {
|
dependencies {
|
||||||
classpath libs.buildscript.kt.gradle
|
classpath libs.buildscript.kt.gradle
|
||||||
classpath libs.buildscript.kt.serialization
|
classpath libs.buildscript.kt.serialization
|
||||||
|
classpath libs.buildscript.kt.ksp
|
||||||
classpath libs.buildscript.jb.dokka
|
classpath libs.buildscript.jb.dokka
|
||||||
classpath libs.buildscript.gh.release
|
classpath libs.buildscript.gh.release
|
||||||
classpath libs.buildscript.android.gradle
|
classpath libs.buildscript.android.gradle
|
||||||
@@ -22,6 +23,7 @@ allprojects {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
|
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
|
||||||
|
maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" }
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporal crutch until legacy tests will be stabled or legacy target will be removed
|
// temporal crutch until legacy tests will be stabled or legacy target will be removed
|
||||||
|
@@ -18,6 +18,18 @@ kotlin {
|
|||||||
api project(":micro_utils.coroutines")
|
api project(":micro_utils.coroutines")
|
||||||
api libs.android.fragment
|
api libs.android.fragment
|
||||||
}
|
}
|
||||||
|
dependsOn jvmMain
|
||||||
|
}
|
||||||
|
|
||||||
|
linuxX64Main {
|
||||||
|
dependencies {
|
||||||
|
api libs.okio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mingwX64Main {
|
||||||
|
dependencies {
|
||||||
|
api libs.okio
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
|
||||||
val added: List<@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(
|
private inline fun <T> performChanges(
|
||||||
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
|
||||||
@@ -43,14 +47,14 @@ private inline fun <T> performChanges(
|
|||||||
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
|
||||||
removedList: MutableList<IndexedValue<T>>,
|
removedList: MutableList<IndexedValue<T>>,
|
||||||
addedList: MutableList<IndexedValue<T>>,
|
addedList: MutableList<IndexedValue<T>>,
|
||||||
strictComparison: Boolean
|
comparisonFun: (T?, T?) -> Boolean
|
||||||
) {
|
) {
|
||||||
var i = -1
|
var i = -1
|
||||||
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
|
val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
|
||||||
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
|
for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
|
||||||
i++
|
i++
|
||||||
val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
|
val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value)
|
||||||
val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
|
val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value)
|
||||||
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
|
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
|
||||||
changedList.addAll(
|
changedList.addAll(
|
||||||
potentialChanges.take(i).mapNotNull {
|
potentialChanges.take(i).mapNotNull {
|
||||||
@@ -104,7 +108,7 @@ private inline fun <T> performChanges(
|
|||||||
*/
|
*/
|
||||||
fun <T> Iterable<T>.calculateDiff(
|
fun <T> Iterable<T>.calculateDiff(
|
||||||
other: Iterable<T>,
|
other: Iterable<T>,
|
||||||
strictComparison: Boolean = false
|
comparisonFun: (T?, T?) -> Boolean
|
||||||
): Diff<T> {
|
): Diff<T> {
|
||||||
var i = -1
|
var i = -1
|
||||||
var j = -1
|
var j = -1
|
||||||
@@ -132,7 +136,7 @@ fun <T> Iterable<T>.calculateDiff(
|
|||||||
}
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
|
comparisonFun(oldObject, newObject) -> {
|
||||||
changedObjects.addAll(potentiallyChangedObjects.map {
|
changedObjects.addAll(potentiallyChangedObjects.map {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
it as Pair<IndexedValue<T>, IndexedValue<T>>
|
it as Pair<IndexedValue<T>, IndexedValue<T>>
|
||||||
@@ -143,23 +147,49 @@ fun <T> Iterable<T>.calculateDiff(
|
|||||||
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
|
potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
|
||||||
val previousOldsAdditionsSize = additionalInOld.size
|
val previousOldsAdditionsSize = additionalInOld.size
|
||||||
val previousNewsAdditionsSize = additionalInNew.size
|
val previousNewsAdditionsSize = additionalInNew.size
|
||||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
|
||||||
i -= (additionalInOld.size - previousOldsAdditionsSize)
|
i -= (additionalInOld.size - previousOldsAdditionsSize)
|
||||||
j -= (additionalInNew.size - previousNewsAdditionsSize)
|
j -= (additionalInNew.size - previousNewsAdditionsSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
potentiallyChangedObjects.add(null to null)
|
potentiallyChangedObjects.add(null to null)
|
||||||
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
|
performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
|
||||||
|
|
||||||
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculating [Diff] object
|
||||||
|
*
|
||||||
|
* @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different
|
||||||
|
* objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links
|
||||||
|
*/
|
||||||
|
fun <T> Iterable<T>.calculateDiff(
|
||||||
|
other: Iterable<T>,
|
||||||
|
strictComparison: Boolean = false
|
||||||
|
): Diff<T> = calculateDiff(
|
||||||
|
other,
|
||||||
|
comparisonFun = if (strictComparison) {
|
||||||
|
{ t1, t2 ->
|
||||||
|
t1 === t2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
{ t1, t2 ->
|
||||||
|
t1 === t2 || t1 == t2 // small optimization for cases when t1 and t2 are the same - comparison will be faster potentially
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
inline fun <T> Iterable<T>.diff(
|
inline fun <T> Iterable<T>.diff(
|
||||||
other: Iterable<T>,
|
other: Iterable<T>,
|
||||||
strictComparison: Boolean = false
|
strictComparison: Boolean = false
|
||||||
): Diff<T> = calculateDiff(other, strictComparison)
|
): Diff<T> = calculateDiff(other, strictComparison)
|
||||||
|
inline fun <T> Iterable<T>.diff(
|
||||||
|
other: Iterable<T>,
|
||||||
|
noinline comparisonFun: (T?, T?) -> Boolean
|
||||||
|
): Diff<T> = calculateDiff(other, comparisonFun)
|
||||||
|
|
||||||
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
|
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)
|
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,3 +217,22 @@ fun <T> MutableList<T>.applyDiff(
|
|||||||
set(new.index, new.value)
|
set(new.index, new.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method call [calculateDiff] with strict mode [strictComparison] 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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
@@ -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 {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(":micro_utils.common")
|
api project(":micro_utils.common")
|
||||||
|
api libs.krypto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsMain {
|
jsMain {
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package dev.inmo.micro_utils.crypto
|
package dev.inmo.micro_utils.crypto
|
||||||
|
|
||||||
|
import com.soywiz.krypto.md5
|
||||||
|
|
||||||
typealias MD5 = String
|
typealias MD5 = String
|
||||||
|
|
||||||
expect fun SourceBytes.md5(): MD5
|
fun SourceBytes.md5(): MD5 = md5().hexLower
|
||||||
fun SourceString.md5(): MD5 = encodeToByteArray().md5()
|
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)
|
|
@@ -1,30 +1,5 @@
|
|||||||
apply plugin: 'com.getkeepsafe.dexcount'
|
apply plugin: 'com.getkeepsafe.dexcount'
|
||||||
|
|
||||||
ext {
|
|
||||||
jvmKotlinFolderFile = {
|
|
||||||
String sep = File.separator
|
|
||||||
return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
|
|
||||||
}
|
|
||||||
|
|
||||||
enableIncludingJvmCodeInAndroidPart = {
|
|
||||||
File jvmKotlinFolder = jvmKotlinFolderFile()
|
|
||||||
if (jvmKotlinFolder.exists()) {
|
|
||||||
android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disableIncludingJvmCodeInAndroidPart = {
|
|
||||||
File jvmKotlinFolder = jvmKotlinFolderFile()
|
|
||||||
String[] oldDirs = android.sourceSets.main.java.srcDirs
|
|
||||||
android.sourceSets.main.java.srcDirs = []
|
|
||||||
for (oldDir in oldDirs) {
|
|
||||||
if (oldDir != jvmKotlinFolder.path) {
|
|
||||||
android.sourceSets.main.java.srcDirs += oldDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
|
||||||
buildToolsVersion libs.versions.android.props.buildTools.get()
|
buildToolsVersion libs.versions.android.props.buildTools.get()
|
||||||
@@ -58,10 +33,4 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
String sep = File.separator
|
|
||||||
main.java.srcDirs += "src${sep}main${sep}kotlin"
|
|
||||||
enableIncludingJvmCodeInAndroidPart()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -29,6 +29,6 @@ allprojects {
|
|||||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
||||||
|
|
||||||
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
||||||
publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
|
publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -48,8 +48,8 @@ interface DefaultStatesManagerRepo<T : State> {
|
|||||||
*/
|
*/
|
||||||
open class DefaultStatesManager<T : State>(
|
open class DefaultStatesManager<T : State>(
|
||||||
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
||||||
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true },
|
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> false },
|
||||||
protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> false }
|
||||||
) : StatesManager<T> {
|
) : StatesManager<T> {
|
||||||
protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
||||||
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
|
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
|
||||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.16.7
|
version=0.17.6
|
||||||
android_code_version=175
|
android_code_version=188
|
||||||
|
@@ -1,37 +1,42 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kt = "1.7.20"
|
kt = "1.8.20"
|
||||||
kt-serialization = "1.4.1"
|
kt-serialization = "1.5.0"
|
||||||
kt-coroutines = "1.6.4"
|
kt-coroutines = "1.6.4"
|
||||||
|
|
||||||
kslog = "0.5.4"
|
kslog = "1.1.1"
|
||||||
|
|
||||||
jb-compose = "1.2.2"
|
jb-compose = "1.4.0"
|
||||||
jb-exposed = "0.41.1"
|
jb-exposed = "0.41.1"
|
||||||
jb-dokka = "1.7.20"
|
jb-dokka = "1.8.10"
|
||||||
|
|
||||||
klock = "3.4.0"
|
korlibs = "3.4.0"
|
||||||
uuid = "0.6.0"
|
uuid = "0.7.0"
|
||||||
|
|
||||||
ktor = "2.2.2"
|
ktor = "2.2.4"
|
||||||
|
|
||||||
gh-release = "2.4.1"
|
gh-release = "2.4.1"
|
||||||
|
|
||||||
koin = "3.2.2"
|
koin = "3.4.0"
|
||||||
|
|
||||||
android-gradle = "7.3.0"
|
okio = "3.3.0"
|
||||||
dexcount = "3.1.0"
|
|
||||||
|
ksp = "1.8.20-1.0.10"
|
||||||
|
kotlin-poet = "1.13.0"
|
||||||
|
|
||||||
|
android-gradle = "7.3.1"
|
||||||
|
dexcount = "4.0.0"
|
||||||
|
|
||||||
android-coreKtx = "1.9.0"
|
android-coreKtx = "1.9.0"
|
||||||
android-recyclerView = "1.2.1"
|
android-recyclerView = "1.3.0"
|
||||||
android-appCompat = "1.5.1"
|
android-appCompat = "1.6.1"
|
||||||
android-fragment = "1.5.5"
|
android-fragment = "1.5.6"
|
||||||
android-espresso = "3.4.0"
|
android-espresso = "3.5.1"
|
||||||
android-test = "1.1.3"
|
android-test = "1.1.5"
|
||||||
|
|
||||||
android-props-minSdk = "21"
|
android-props-minSdk = "21"
|
||||||
android-props-compileSdk = "33"
|
android-props-compileSdk = "33"
|
||||||
android-props-buildTools = "33.0.0"
|
android-props-buildTools = "33.0.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
||||||
@@ -64,7 +69,8 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
|
|||||||
|
|
||||||
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
|
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
|
||||||
|
|
||||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "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" }
|
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
|
||||||
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||||
|
|
||||||
@@ -83,9 +89,18 @@ android-test-junit = { module = "androidx.test.ext:junit", version.ref = "androi
|
|||||||
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
|
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
|
||||||
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
|
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
|
||||||
|
|
||||||
|
# ksp dependencies
|
||||||
|
|
||||||
|
kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
|
||||||
|
ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||||
|
|
||||||
|
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
|
||||||
|
|
||||||
|
# Buildscript
|
||||||
|
|
||||||
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
|
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
|
||||||
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
|
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
|
||||||
|
buildscript-kt-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
|
||||||
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
|
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
|
||||||
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
|
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
|
||||||
buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
|
buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
118
jvm.publish.gradle
Normal file
118
jvm.publish.gradle
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
|
task javadocJar(type: Jar) {
|
||||||
|
from javadoc
|
||||||
|
classifier = 'javadoc'
|
||||||
|
}
|
||||||
|
task sourcesJar(type: Jar) {
|
||||||
|
from sourceSets.main.allSource
|
||||||
|
classifier = 'sources'
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
maven(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
|
||||||
|
artifact javadocJar
|
||||||
|
artifact sourcesJar
|
||||||
|
|
||||||
|
pom {
|
||||||
|
resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
|
||||||
|
description = "It is set of projects with micro tools for avoiding of routines coding"
|
||||||
|
name = "${project.name}"
|
||||||
|
url = "https://github.com/InsanusMokrassar/MicroUtils/"
|
||||||
|
|
||||||
|
scm {
|
||||||
|
developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git"
|
||||||
|
url = "https://github.com/InsanusMokrassar/MicroUtils.git"
|
||||||
|
}
|
||||||
|
|
||||||
|
developers {
|
||||||
|
|
||||||
|
developer {
|
||||||
|
id = "InsanusMokrassar"
|
||||||
|
name = "Aleksei Ovsiannikov"
|
||||||
|
email = "ovsyannikov.alexey95@gmail.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
developer {
|
||||||
|
id = "000Sanya"
|
||||||
|
name = "Syrov Aleksandr"
|
||||||
|
email = "000sanya.000sanya@gmail.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
|
||||||
|
license {
|
||||||
|
name = "Apache Software License 2.0"
|
||||||
|
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
|
||||||
|
maven {
|
||||||
|
name = "GithubPackages"
|
||||||
|
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
|
||||||
|
|
||||||
|
credentials {
|
||||||
|
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
|
||||||
|
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
|
||||||
|
maven {
|
||||||
|
name = "Gitea"
|
||||||
|
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
|
||||||
|
|
||||||
|
credentials(HttpHeaderCredentials) {
|
||||||
|
name = "Authorization"
|
||||||
|
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
|
||||||
|
}
|
||||||
|
|
||||||
|
authentication {
|
||||||
|
header(HttpHeaderAuthentication)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
|
||||||
|
maven {
|
||||||
|
name = "sonatype"
|
||||||
|
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||||
|
|
||||||
|
credentials {
|
||||||
|
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||||
|
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty("signing.gnupg.keyName")) {
|
||||||
|
apply plugin: 'signing'
|
||||||
|
|
||||||
|
signing {
|
||||||
|
useGpgCmd()
|
||||||
|
|
||||||
|
sign publishing.publications
|
||||||
|
}
|
||||||
|
|
||||||
|
task signAll {
|
||||||
|
tasks.withType(Sign).forEach {
|
||||||
|
dependsOn(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
jvm.publish.kpsb
Normal file
1
jvm.publish.kpsb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}
|
100
koin/generator/README.md
Normal file
100
koin/generator/README.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Koin generator
|
||||||
|
|
||||||
|
It is Kotlin Symbol Processing generator for `Koin` module in `MicroUtils`.
|
||||||
|
|
||||||
|
1. [What may do this generator](#what-may-do-this-generator)
|
||||||
|
2. [How to add generator](#how-to-add-generator)
|
||||||
|
|
||||||
|
## What may do this generator
|
||||||
|
|
||||||
|
Let's imagine you want to have shortcuts in koin, to get something easily:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val koin: Koin// some initialization
|
||||||
|
|
||||||
|
val someUrl = koin.serverUrl
|
||||||
|
```
|
||||||
|
|
||||||
|
So, in that case you may mark containing file with next annotation (in the beginning of file):
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@file:GenerateKoinDefinition("serverUrl", String::class, nullable = false)
|
||||||
|
```
|
||||||
|
|
||||||
|
If file is called like `Sample.kt`, will be generated file `GeneratedDefinitionsSample.kt` with next content:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
public val Scope.serverUrl: String
|
||||||
|
get() = get(named("serverUrl"))
|
||||||
|
|
||||||
|
public val Koin.serverUrl: String
|
||||||
|
get() = get(named("serverUrl"))
|
||||||
|
|
||||||
|
public fun Module.serverUrlSingle(createdAtStart: Boolean = false,
|
||||||
|
definition: Definition<String>): KoinDefinition<String> =
|
||||||
|
single(named("serverUrl"), createdAtStart = createdAtStart, definition = definition)
|
||||||
|
|
||||||
|
public fun Module.serverUrlFactory(definition: Definition<String>):
|
||||||
|
KoinDefinition<String> = factory(named("serverUrl"), definition = definition)
|
||||||
|
```
|
||||||
|
|
||||||
|
Besides, you may use the generics:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false)
|
||||||
|
```
|
||||||
|
|
||||||
|
Will generate:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
public val Scope.sampleInfo: Sample<G1, G2>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
public val Koin.sampleInfo: Sample<G1, G2>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
|
||||||
|
definition: Definition<Sample<G1, G2>>): KoinDefinition<Sample<G1, G2>> =
|
||||||
|
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
|
||||||
|
|
||||||
|
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
|
||||||
|
KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
|
||||||
|
```
|
||||||
|
|
||||||
|
In case you wish not to generate single:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false, generateSingle = false)
|
||||||
|
```
|
||||||
|
|
||||||
|
And you will take next code:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
public val Scope.sampleInfo: Sample<G1, G2>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
public val Koin.sampleInfo: Sample<G1, G2>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>):
|
||||||
|
KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition)
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to add generator
|
||||||
|
|
||||||
|
**Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project**
|
||||||
|
|
||||||
|
**Note: $microutils_version in the version of MicroUtils library in your project**
|
||||||
|
|
||||||
|
1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`)
|
||||||
|
2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"`
|
||||||
|
3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.koin.generator:$microutils_version`:
|
||||||
|
```groovy
|
||||||
|
dependencies {
|
||||||
|
add("kspCommonMainMetadata", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in commonMain of your multiplatform module
|
||||||
|
add("kspJvm", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in main of your JVM module
|
||||||
|
}
|
||||||
|
|
||||||
|
ksp { // this generator do not require any arguments and we should left `ksp` empty
|
||||||
|
}
|
||||||
|
```
|
15
koin/generator/build.gradle
Normal file
15
koin/generator/build.gradle
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.jvm"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$publishJvmOnlyPath"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api project(":micro_utils.koin")
|
||||||
|
api libs.kotlin.poet
|
||||||
|
api libs.ksp
|
||||||
|
}
|
178
koin/generator/src/main/kotlin/Processor.kt
Normal file
178
koin/generator/src/main/kotlin/Processor.kt
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package dev.inmo.micro_utils.koin.generator
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.KSTypeNotPresentException
|
||||||
|
import com.google.devtools.ksp.KSTypesNotPresentException
|
||||||
|
import com.google.devtools.ksp.KspExperimental
|
||||||
|
import com.google.devtools.ksp.getAnnotationsByType
|
||||||
|
import com.google.devtools.ksp.processing.CodeGenerator
|
||||||
|
import com.google.devtools.ksp.processing.Resolver
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||||
|
import com.google.devtools.ksp.symbol.KSFile
|
||||||
|
import com.squareup.kotlinpoet.ClassName
|
||||||
|
import com.squareup.kotlinpoet.FileSpec
|
||||||
|
import com.squareup.kotlinpoet.FunSpec
|
||||||
|
import com.squareup.kotlinpoet.ParameterSpec
|
||||||
|
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||||
|
import com.squareup.kotlinpoet.PropertySpec
|
||||||
|
import com.squareup.kotlinpoet.asTypeName
|
||||||
|
import com.squareup.kotlinpoet.ksp.toClassName
|
||||||
|
import com.squareup.kotlinpoet.ksp.toTypeName
|
||||||
|
import com.squareup.kotlinpoet.ksp.writeTo
|
||||||
|
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
|
||||||
|
import org.koin.core.Koin
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.scope.Scope
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
class Processor(
|
||||||
|
private val codeGenerator: CodeGenerator
|
||||||
|
) : SymbolProcessor {
|
||||||
|
private val definitionClassName = ClassName("org.koin.core.definition", "Definition")
|
||||||
|
private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition")
|
||||||
|
|
||||||
|
@OptIn(KspExperimental::class)
|
||||||
|
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||||
|
resolver.getSymbolsWithAnnotation(
|
||||||
|
GenerateKoinDefinition::class.qualifiedName!!
|
||||||
|
).filterIsInstance<KSFile>().forEach { ksFile ->
|
||||||
|
FileSpec.builder(
|
||||||
|
ksFile.packageName.asString(),
|
||||||
|
"GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}"
|
||||||
|
).apply {
|
||||||
|
addFileComment(
|
||||||
|
"""
|
||||||
|
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||||
|
TO REGENERATE IT JUST DELETE FILE
|
||||||
|
ORIGINAL FILE: ${ksFile.fileName}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach {
|
||||||
|
val type = runCatching {
|
||||||
|
it.type.asTypeName()
|
||||||
|
}.getOrElse { e ->
|
||||||
|
if (e is KSTypeNotPresentException) {
|
||||||
|
e.ksType.toClassName()
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val targetType = runCatching {
|
||||||
|
type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type))
|
||||||
|
}.getOrElse { e ->
|
||||||
|
when (e) {
|
||||||
|
is KSTypeNotPresentException -> e.ksType.toClassName()
|
||||||
|
}
|
||||||
|
if (e is KSTypesNotPresentException) {
|
||||||
|
type.parameterizedBy(*e.ksTypes.map { it.toTypeName() }.toTypedArray())
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}.copy(
|
||||||
|
nullable = it.nullable
|
||||||
|
)
|
||||||
|
fun addGetterProperty(
|
||||||
|
receiver: KClass<*>
|
||||||
|
) {
|
||||||
|
addProperty(
|
||||||
|
PropertySpec.builder(
|
||||||
|
it.name,
|
||||||
|
targetType,
|
||||||
|
).apply {
|
||||||
|
addKdoc(
|
||||||
|
"""
|
||||||
|
@return Definition by key "${it.name}"
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
getter(
|
||||||
|
FunSpec.getterBuilder().apply {
|
||||||
|
addCode(
|
||||||
|
"return " + (if (it.nullable) {
|
||||||
|
"getOrNull"
|
||||||
|
} else {
|
||||||
|
"get"
|
||||||
|
}) + "(named(\"${it.name}\"))"
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
receiver(receiver)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
addGetterProperty(Scope::class)
|
||||||
|
addGetterProperty(Koin::class)
|
||||||
|
|
||||||
|
if (it.generateSingle) {
|
||||||
|
addFunction(
|
||||||
|
FunSpec.builder("${it.name}Single").apply {
|
||||||
|
addKdoc(
|
||||||
|
"""
|
||||||
|
Will register [definition] with [org.koin.core.module.Module.single] and key "${it.name}"
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
receiver(Module::class)
|
||||||
|
addParameter(
|
||||||
|
ParameterSpec.builder(
|
||||||
|
"createdAtStart",
|
||||||
|
Boolean::class
|
||||||
|
).apply {
|
||||||
|
defaultValue("false")
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
addParameter(
|
||||||
|
ParameterSpec.builder(
|
||||||
|
"definition",
|
||||||
|
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
|
||||||
|
addCode(
|
||||||
|
"return single(named(\"${it.name}\"), createdAtStart = createdAtStart, definition = definition)"
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.generateFactory) {
|
||||||
|
addFunction(
|
||||||
|
FunSpec.builder("${it.name}Factory").apply {
|
||||||
|
addKdoc(
|
||||||
|
"""
|
||||||
|
Will register [definition] with [org.koin.core.module.Module.factory] and key "${it.name}"
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
receiver(Module::class)
|
||||||
|
addParameter(
|
||||||
|
ParameterSpec.builder(
|
||||||
|
"definition",
|
||||||
|
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
|
||||||
|
).build()
|
||||||
|
)
|
||||||
|
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
|
||||||
|
addCode(
|
||||||
|
"return factory(named(\"${it.name}\"), definition = definition)"
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
addImport("org.koin.core.qualifier", "named")
|
||||||
|
}
|
||||||
|
}.build().let {
|
||||||
|
File(
|
||||||
|
File(ksFile.filePath).parent,
|
||||||
|
"GeneratedDefinitions${ksFile.fileName}"
|
||||||
|
).takeIf { !it.exists() } ?.apply {
|
||||||
|
parentFile.mkdirs()
|
||||||
|
|
||||||
|
writer().use { writer ->
|
||||||
|
it.writeTo(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
11
koin/generator/src/main/kotlin/Provider.kt
Normal file
11
koin/generator/src/main/kotlin/Provider.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package dev.inmo.micro_utils.koin.generator
|
||||||
|
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||||
|
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||||
|
|
||||||
|
class Provider : SymbolProcessorProvider {
|
||||||
|
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(
|
||||||
|
environment.codeGenerator
|
||||||
|
)
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
dev.inmo.micro_utils.koin.generator.Provider
|
27
koin/generator/test/build.gradle
Normal file
27
koin/generator/test/build.gradle
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
id "com.google.devtools.ksp"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api project(":micro_utils.koin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
add("kspCommonMainMetadata", project(":micro_utils.koin.generator"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ksp {
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
|
||||||
|
// TO REGENERATE IT JUST DELETE FILE
|
||||||
|
// ORIGINAL FILE: Test.kt
|
||||||
|
package dev.inmo.micro_utils.koin.generator.test
|
||||||
|
|
||||||
|
import kotlin.Boolean
|
||||||
|
import kotlin.String
|
||||||
|
import org.koin.core.Koin
|
||||||
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.core.scope.Scope
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Definition by key "sampleInfo"
|
||||||
|
*/
|
||||||
|
public val Scope.sampleInfo: Test<String>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Definition by key "sampleInfo"
|
||||||
|
*/
|
||||||
|
public val Koin.sampleInfo: Test<String>
|
||||||
|
get() = get(named("sampleInfo"))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo"
|
||||||
|
*/
|
||||||
|
public fun Module.sampleInfoSingle(createdAtStart: Boolean = false,
|
||||||
|
definition: Definition<Test<String>>): KoinDefinition<Test<String>> =
|
||||||
|
single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo"
|
||||||
|
*/
|
||||||
|
public fun Module.sampleInfoFactory(definition: Definition<Test<String>>):
|
||||||
|
KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition)
|
13
koin/generator/test/src/commonMain/kotlin/Test.kt
Normal file
13
koin/generator/test/src/commonMain/kotlin/Test.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false)
|
||||||
|
package dev.inmo.micro_utils.koin.generator.test
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
|
||||||
|
import org.koin.core.Koin
|
||||||
|
|
||||||
|
class Test<T>(
|
||||||
|
koin: Koin
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
koin.sampleInfo
|
||||||
|
}
|
||||||
|
}
|
1
koin/generator/test/src/main/AndroidManifest.xml
Normal file
1
koin/generator/test/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.micro_utils.koin.generator.test"/>
|
@@ -0,0 +1,28 @@
|
|||||||
|
package dev.inmo.micro_utils.koin.annotations
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this annotation to mark files near to which generator should place generated extensions for koin [org.koin.core.scope.Scope]
|
||||||
|
* and [org.koin.core.Koin]
|
||||||
|
*
|
||||||
|
* @param name Name for definitions. This name will be available as extension for [org.koin.core.scope.Scope] and [org.koin.core.Koin]
|
||||||
|
* @param type Type of extensions. It is base star-typed class
|
||||||
|
* @param typeArgs Generic types for [type]. For example, if [type] == `Something::class` and [typeArgs] == `G1::class,
|
||||||
|
* G2::class`, the result type will be `Something<G1, G2>`
|
||||||
|
* @param nullable In case when true, extension will not throw error when definition has not been registered in koin
|
||||||
|
* @param generateSingle Generate definition factory with [org.koin.core.module.Module.single]. You will be able to use
|
||||||
|
* the extension [org.koin.core.module.Module].[name]Single(createdAtStart/* default false */) { /* your definition */ }
|
||||||
|
* @param generateFactory Generate definition factory with [org.koin.core.module.Module.factory]. You will be able to use
|
||||||
|
* the extension [org.koin.core.module.Module].[name]Factory { /* your definition */ }
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.FILE)
|
||||||
|
@Repeatable
|
||||||
|
annotation class GenerateKoinDefinition(
|
||||||
|
val name: String,
|
||||||
|
val type: KClass<*>,
|
||||||
|
vararg val typeArgs: KClass<*>,
|
||||||
|
val nullable: Boolean = true,
|
||||||
|
val generateSingle: Boolean = true,
|
||||||
|
val generateFactory: Boolean = true
|
||||||
|
)
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.koin
|
package dev.inmo.micro_utils.koin
|
||||||
|
|
||||||
import org.koin.core.definition.Definition
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
import org.koin.core.instance.InstanceFactory
|
import org.koin.core.instance.InstanceFactory
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import org.koin.core.qualifier.Qualifier
|
import org.koin.core.qualifier.Qualifier
|
||||||
@@ -13,7 +14,7 @@ inline fun <reified T : Any> Module.factoryWithBinds(
|
|||||||
qualifier: Qualifier? = null,
|
qualifier: Qualifier? = null,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ inline fun <reified T : Any> Module.factoryWithBinds(
|
|||||||
qualifier: String,
|
qualifier: String,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.koin
|
package dev.inmo.micro_utils.koin
|
||||||
|
|
||||||
import org.koin.core.definition.Definition
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
import org.koin.core.instance.InstanceFactory
|
import org.koin.core.instance.InstanceFactory
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@@ -8,6 +9,6 @@ import kotlin.reflect.KClass
|
|||||||
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
|
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return factoryWithBinds(RandomQualifier(), bindFilter, definition)
|
return factoryWithBinds(RandomQualifier(), bindFilter, definition)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.koin
|
package dev.inmo.micro_utils.koin
|
||||||
|
|
||||||
import org.koin.core.definition.Definition
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
import org.koin.core.instance.InstanceFactory
|
import org.koin.core.instance.InstanceFactory
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import org.koin.core.qualifier.Qualifier
|
import org.koin.core.qualifier.Qualifier
|
||||||
@@ -14,7 +15,7 @@ inline fun <reified T : Any> Module.singleWithBinds(
|
|||||||
createdAtStart: Boolean = false,
|
createdAtStart: Boolean = false,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ inline fun <reified T : Any> Module.singleWithBinds(
|
|||||||
createdAtStart: Boolean = false,
|
createdAtStart: Boolean = false,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.koin
|
package dev.inmo.micro_utils.koin
|
||||||
|
|
||||||
import org.koin.core.definition.Definition
|
import org.koin.core.definition.Definition
|
||||||
|
import org.koin.core.definition.KoinDefinition
|
||||||
import org.koin.core.instance.InstanceFactory
|
import org.koin.core.instance.InstanceFactory
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
@@ -9,6 +10,6 @@ inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds(
|
|||||||
createdAtStart: Boolean = false,
|
createdAtStart: Boolean = false,
|
||||||
bindFilter: (KClass<*>) -> Boolean = { true },
|
bindFilter: (KClass<*>) -> Boolean = { true },
|
||||||
noinline definition: Definition<T>
|
noinline definition: Definition<T>
|
||||||
): Pair<Module, InstanceFactory<*>> {
|
): KoinDefinition<*> {
|
||||||
return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
|
return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
|
||||||
}
|
}
|
||||||
|
@@ -15,9 +15,20 @@ kotlin {
|
|||||||
api libs.ktor.client
|
api libs.ktor.client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
androidMain {
|
androidMain {
|
||||||
dependsOn jvmMain
|
dependsOn jvmMain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
linuxX64Main {
|
||||||
|
dependencies {
|
||||||
|
api internalProject("micro_utils.mime_types")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mingwX64Main {
|
||||||
|
dependencies {
|
||||||
|
api internalProject("micro_utils.mime_types")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,10 @@ import io.ktor.client.call.body
|
|||||||
import io.ktor.client.statement.HttpResponse
|
import io.ktor.client.statement.HttpResponse
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
|
|
||||||
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf {
|
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull(
|
||||||
status == HttpStatusCode.OK
|
statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK }
|
||||||
} ?.body<T>()
|
) = takeIf(statusFilter) ?.body<T>()
|
||||||
|
|
||||||
|
suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> {
|
||||||
|
it.status != HttpStatusCode.NoContent
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
package dev.inmo.micro_utils.ktor.client
|
package dev.inmo.micro_utils.ktor.client
|
||||||
|
|
||||||
import dev.inmo.micro_utils.common.MPPFile
|
import dev.inmo.micro_utils.common.MPPFile
|
||||||
|
import dev.inmo.micro_utils.common.filesize
|
||||||
|
import dev.inmo.micro_utils.ktor.common.input
|
||||||
import io.ktor.client.request.forms.InputProvider
|
import io.ktor.client.request.forms.InputProvider
|
||||||
|
|
||||||
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()) {
|
fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
|
||||||
inputStream().asInput()
|
inputStream().asInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync()
|
|
||||||
|
@@ -17,8 +17,11 @@ import io.ktor.http.HttpStatusCode
|
|||||||
import io.ktor.http.Parameters
|
import io.ktor.http.Parameters
|
||||||
import io.ktor.http.content.PartData
|
import io.ktor.http.content.PartData
|
||||||
import kotlinx.serialization.DeserializationStrategy
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
|
import kotlinx.serialization.InternalSerializationApi
|
||||||
|
import kotlinx.serialization.SerializationStrategy
|
||||||
import kotlinx.serialization.StringFormat
|
import kotlinx.serialization.StringFormat
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +32,7 @@ import java.io.File
|
|||||||
* in case you wish to pass other source of multipart binary data than regular file
|
* in case you wish to pass other source of multipart binary data than regular file
|
||||||
* @suppress
|
* @suppress
|
||||||
*/
|
*/
|
||||||
|
@OptIn(InternalSerializationApi::class)
|
||||||
actual suspend fun <T> HttpClient.uniUpload(
|
actual suspend fun <T> HttpClient.uniUpload(
|
||||||
url: String,
|
url: String,
|
||||||
data: Map<String, Any>,
|
data: Map<String, Any>,
|
||||||
@@ -40,7 +44,8 @@ actual suspend fun <T> HttpClient.uniUpload(
|
|||||||
val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
|
val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
|
||||||
|
|
||||||
val formData = formData {
|
val formData = formData {
|
||||||
data.forEach { (k, v) ->
|
for (k in data.keys) {
|
||||||
|
val v = data[k] ?: continue
|
||||||
when (v) {
|
when (v) {
|
||||||
is File -> append(
|
is File -> append(
|
||||||
k,
|
k,
|
||||||
@@ -60,7 +65,7 @@ actual suspend fun <T> HttpClient.uniUpload(
|
|||||||
)
|
)
|
||||||
else -> append(
|
else -> append(
|
||||||
k,
|
k,
|
||||||
stringFormat.encodeToString(v)
|
stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +90,7 @@ actual suspend fun <T> HttpClient.uniUpload(
|
|||||||
submitForm(
|
submitForm(
|
||||||
url,
|
url,
|
||||||
Parameters.build {
|
Parameters.build {
|
||||||
formData.forEach {
|
for (it in formData) {
|
||||||
val formItem = (it as PartData.FormItem)
|
val formItem = (it as PartData.FormItem)
|
||||||
append(it.name!!, it.value)
|
append(it.name!!, it.value)
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@@ -17,5 +17,8 @@ kotlin {
|
|||||||
api libs.ktor.io
|
api libs.ktor.io
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
androidMain {
|
||||||
|
dependsOn jvmMain
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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())
|
||||||
|
}
|
@@ -10,8 +10,8 @@ import java.io.File
|
|||||||
|
|
||||||
fun PartData.FileItem.download(target: File) {
|
fun PartData.FileItem.download(target: File) {
|
||||||
provider().use { input ->
|
provider().use { input ->
|
||||||
target.outputStream().use {
|
target.outputStream().asOutput().use {
|
||||||
input.copyTo(it.asOutput())
|
input.copyTo(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,3 +5,11 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
androidMain {
|
||||||
|
dependsOn jvmMain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
8
language_codes/src/jvmMain/kotlin/Locale.kt
Normal file
8
language_codes/src/jvmMain/kotlin/Locale.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package dev.inmo.micro_utils.language_codes
|
||||||
|
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
fun IetfLanguageCode.toJavaLocale(): Locale = Locale.forLanguageTag(code)
|
||||||
|
fun IetfLanguageCode?.toJavaLocaleOrDefault(): Locale = this ?.toJavaLocale() ?: Locale.getDefault()
|
||||||
|
|
||||||
|
fun Locale.toIetfLanguageCode(): IetfLanguageCode = IetfLanguageCode(toLanguageTag())
|
@@ -1,3 +1,5 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@@ -17,33 +19,45 @@ def fix_name(category, raw_name):
|
|||||||
result += out1
|
result += out1
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def remove_prefix(text, prefix):
|
||||||
|
if text.startswith(prefix):
|
||||||
|
return text[len(prefix):]
|
||||||
|
return text # or whatever
|
||||||
|
|
||||||
|
def extensionPreparationFun(extension):
|
||||||
|
return "\"%s\"" % (remove_prefix(extension, "."))
|
||||||
|
|
||||||
# https://www.freeformatter.com/mime-types-list.html
|
# https://www.freeformatter.com/mime-types-list.html
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
df = pd.read_html(open('table.html', 'r'))
|
df = pd.read_html(open('local.table.html', 'r'))
|
||||||
mimes = []
|
mimes = []
|
||||||
for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows():
|
for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows():
|
||||||
mime = row[1][1]
|
mime = row[1][1]
|
||||||
|
extensions = list()
|
||||||
|
if isinstance(row[1][2], str):
|
||||||
|
extensions = list(map(extensionPreparationFun, row[1][2].split(", ")))
|
||||||
mime_category = mime.split('/', 1)[0]
|
mime_category = mime.split('/', 1)[0]
|
||||||
mime_name = mime.split('/', 1)[1]
|
mime_name = mime.split('/', 1)[1]
|
||||||
mimes.append({
|
mimes.append([
|
||||||
'mime_category': mime_category,
|
mime_category,
|
||||||
'mime_name': mime_name,
|
mime_name,
|
||||||
})
|
extensions
|
||||||
|
])
|
||||||
|
|
||||||
# codegen
|
# codegen
|
||||||
|
|
||||||
mimes.sort(key=lambda x: x['mime_category'])
|
mimes.sort(key=lambda x: x[0])
|
||||||
grouped = itertools.groupby(mimes, lambda x: x['mime_category'])
|
grouped = itertools.groupby(mimes, lambda x: x[0])
|
||||||
code = ''
|
code = ''
|
||||||
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
|
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
|
||||||
code2 += ' KnownMimeTypes.Any,\n'
|
code2 += ' KnownMimeTypes.Any,\n'
|
||||||
for key, group in grouped:
|
for key, group in grouped:
|
||||||
group_name = fix_name(group, key)
|
group_name = fix_name(group, key)
|
||||||
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : 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)
|
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
|
||||||
for mime in group:
|
for mime in group:
|
||||||
name = fix_name(mime['mime_category'], mime['mime_name'])
|
name = fix_name(mime[0], mime[1])
|
||||||
code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s")\n' % (name, group_name, mime['mime_category'], mime['mime_name'])
|
code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s", arrayOf(%s))\n' % (name, group_name, mime[0], mime[1], ", ".join(mime[2]))
|
||||||
code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name)
|
code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name)
|
||||||
code += '}\n\n'
|
code += '}\n\n'
|
||||||
code2 += ')\n'
|
code2 += ')\n'
|
||||||
|
@@ -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)
|
@Serializable(MimeTypeSerializer::class)
|
||||||
interface MimeType {
|
interface MimeType {
|
||||||
val raw: String
|
val raw: String
|
||||||
|
val extensions: Array<String>
|
||||||
|
get() = emptyArray()
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,8 @@ kotlin {
|
|||||||
android {
|
android {
|
||||||
publishAllLibraryVariants()
|
publishAllLibraryVariants()
|
||||||
}
|
}
|
||||||
|
linuxX64()
|
||||||
|
mingwX64()
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
@@ -50,6 +52,18 @@ kotlin {
|
|||||||
implementation libs.android.espresso
|
implementation libs.android.espresso
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mingwX64Test {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
linuxX64Test {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
androidMain.dependsOn jvmMain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -61,6 +61,8 @@ kotlin {
|
|||||||
implementation libs.android.espresso
|
implementation libs.android.espresso
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidMain.dependsOn jvmMain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
package dev.inmo.micro_utils.pagination.utils
|
||||||
|
|
||||||
|
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
|
||||||
|
is List<T> -> optionallyReverse(reverse)
|
||||||
|
is Set<T> -> optionallyReverse(reverse)
|
||||||
|
else -> if (reverse) {
|
||||||
|
reversed()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
|
||||||
|
reversed()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
|
||||||
|
reversed().toSet()
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
|
||||||
|
Array(size) {
|
||||||
|
get(lastIndex - it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
@@ -32,6 +32,24 @@ fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
|
||||||
|
val actualPagination = with.optionallyReverse(
|
||||||
|
size,
|
||||||
|
reversed
|
||||||
|
)
|
||||||
|
|
||||||
|
val firstIndex = maxOf(actualPagination.firstIndex, 0)
|
||||||
|
val lastIndex = minOf(actualPagination.lastIndexExclusive, size)
|
||||||
|
if (firstIndex > lastIndex) {
|
||||||
|
return emptyPaginationResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
return subList(firstIndex, lastIndex).optionallyReverse(reversed).createPaginationResult(
|
||||||
|
with,
|
||||||
|
size.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
|
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
|
||||||
return this.drop(with.firstIndex).take(with.size).createPaginationResult(
|
return this.drop(with.firstIndex).take(with.size).createPaginationResult(
|
||||||
with,
|
with,
|
||||||
@@ -39,30 +57,20 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
|
fun <T> Set<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
|
||||||
is List<T> -> optionallyReverse(reverse)
|
val actualPagination = with.optionallyReverse(
|
||||||
is Set<T> -> optionallyReverse(reverse)
|
size,
|
||||||
else -> if (reverse) {
|
reversed
|
||||||
reversed()
|
)
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
|
|
||||||
reversed()
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
|
|
||||||
reversed().toSet()
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
|
val firstIndex = maxOf(actualPagination.firstIndex, 0)
|
||||||
Array(size) {
|
val lastIndex = minOf(actualPagination.lastIndexExclusive, size)
|
||||||
get(lastIndex - it)
|
if (firstIndex > lastIndex) {
|
||||||
|
return emptyPaginationResult()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this
|
return this.drop(firstIndex).take(lastIndex - firstIndex).optionallyReverse(reversed).createPaginationResult(
|
||||||
|
with,
|
||||||
|
size.toLong()
|
||||||
|
)
|
||||||
}
|
}
|
@@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.cache
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
@@ -15,6 +16,12 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
kvCache.set(id, it)
|
kvCache.set(id, it)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
override suspend fun getAll(): Map<IdType, ObjectType> {
|
||||||
|
return kvCache.getAll().takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also {
|
||||||
|
kvCache.actualizeAll(true) { it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id)
|
||||||
|
|
||||||
override suspend fun invalidate() = kvCache.clear()
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
|
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/FallbackCacheRepo.kt
vendored
Normal file
7
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/FallbackCacheRepo.kt
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any inheritor of this should work with next logic: try to take data from their original repo, if successful - save data to internal
|
||||||
|
* [dev.inmo.micro_utils.repos.cache.cache.FullKVCache] or try to take data from that internal cache
|
||||||
|
*/
|
||||||
|
interface FallbackCacheRepo : CacheRepo
|
@@ -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()
|
override suspend fun invalidate() = kvCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
37
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/ActionWrapper.kt
vendored
Normal file
37
repos/cache/src/commonMain/kotlin/dev/inmo/micro_utils/repos/cache/fallback/ActionWrapper.kt
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Realizations should [wrap] the work with some conditions like retries on exceptions, calling timeout, etc.
|
||||||
|
*
|
||||||
|
* @see Timeouted
|
||||||
|
* @see Direct
|
||||||
|
*/
|
||||||
|
interface ActionWrapper {
|
||||||
|
/**
|
||||||
|
* Should execute [block] to take the result [T], but may return failure in case when something went wrong.
|
||||||
|
* This method should never throw any [Exception]
|
||||||
|
*/
|
||||||
|
suspend fun <T> wrap(block: suspend () -> T): Result<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This type of [ActionWrapper]s will use [withTimeout]([timeoutMillis]) and if original call
|
||||||
|
* will not return anything in that timeout just return [Result] with failure
|
||||||
|
*/
|
||||||
|
class Timeouted(private val timeoutMillis: Long) : ActionWrapper {
|
||||||
|
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely {
|
||||||
|
withTimeout(timeoutMillis) {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* It is passthrough variant of [ActionWrapper] which will just call incoming block with wrapping into [runCatchingSafely]
|
||||||
|
*/
|
||||||
|
object Direct : ActionWrapper {
|
||||||
|
|
||||||
|
override suspend fun <T> wrap(block: suspend () -> T): Result<T> = runCatchingSafely { block() }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.CRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheCRUDRepo<RegisteredObject, Id, InputObject>(
|
||||||
|
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
||||||
|
originalRepo,
|
||||||
|
scope,
|
||||||
|
kvCache,
|
||||||
|
recacheDelay,
|
||||||
|
actionWrapper,
|
||||||
|
idGetter
|
||||||
|
),
|
||||||
|
WriteCRUDRepo<RegisteredObject, Id, InputObject> by AutoRecacheWriteCRUDRepo(originalRepo, scope, kvCache, idGetter),
|
||||||
|
CRUDRepo<RegisteredObject, Id, InputObject> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: CRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
}
|
@@ -0,0 +1,94 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
|
||||||
|
protected open val originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
|
) : ReadCRUDRepo<RegisteredObject, Id>, FallbackCacheRepo {
|
||||||
|
val autoUpdateJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
actualizeAll()
|
||||||
|
|
||||||
|
delay(recacheDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: ReadCRUDRepo<RegisteredObject, Id>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
|
||||||
|
protected open suspend fun actualizeAll(): Result<Unit> {
|
||||||
|
return runCatchingSafely {
|
||||||
|
kvCache.actualizeAll(originalRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(id: Id): Boolean = actionWrapper.wrap {
|
||||||
|
originalRepo.contains(id)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.contains(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.getAll()
|
||||||
|
}.onSuccess {
|
||||||
|
kvCache.actualizeAll(clear = true) { it }
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count()
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getByPagination(
|
||||||
|
pagination: Pagination
|
||||||
|
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.getByPagination(pagination)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
it.results.forEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
} ?: kvCache.values(pagination)
|
||||||
|
|
||||||
|
override suspend fun getIdsByPagination(
|
||||||
|
pagination: Pagination
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.getIdsByPagination(pagination)
|
||||||
|
}.getOrElse { kvCache.keys(pagination) }
|
||||||
|
|
||||||
|
override suspend fun getById(id: Id): RegisteredObject? = actionWrapper.wrap {
|
||||||
|
originalRepo.getById(id)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
} ?: kvCache.get(id)
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.plus
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||||
|
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
|
|
||||||
|
open class AutoRecacheWriteCRUDRepo<RegisteredObject, Id, InputObject>(
|
||||||
|
protected val originalRepo: WriteCRUDRepo<RegisteredObject, Id, InputObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
|
) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo {
|
||||||
|
override val deletedObjectsIdsFlow: Flow<Id>
|
||||||
|
get() = (originalRepo.deletedObjectsIdsFlow + kvCache.onValueRemoved).distinctUntilChanged()
|
||||||
|
override val newObjectsFlow: Flow<RegisteredObject>
|
||||||
|
get() = (originalRepo.newObjectsFlow + kvCache.onNewValue.map { it.second }).distinctUntilChanged()
|
||||||
|
override val updatedObjectsFlow: Flow<RegisteredObject>
|
||||||
|
get() = originalRepo.updatedObjectsFlow
|
||||||
|
|
||||||
|
private val onRemovingUpdatesListeningJob = originalRepo.deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onNewAndUpdatedObjectsListeningJob = merge(
|
||||||
|
originalRepo.newObjectsFlow,
|
||||||
|
originalRepo.updatedObjectsFlow,
|
||||||
|
).subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
values: List<UpdatedValuePair<Id, InputObject>>
|
||||||
|
): List<RegisteredObject> = originalRepo.update(values).onEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun update(
|
||||||
|
id: Id,
|
||||||
|
value: InputObject
|
||||||
|
): RegisteredObject? = originalRepo.update(id, value) ?.also {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteById(ids: List<Id>) = originalRepo.deleteById(ids).also {
|
||||||
|
kvCache.unset(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun create(values: List<InputObject>): List<RegisteredObject> = originalRepo.create(values).onEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheKeyValueRepo<Id, RegisteredObject>(
|
||||||
|
override val originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : AutoRecacheReadKeyValueRepo<Id, RegisteredObject> (
|
||||||
|
originalRepo,
|
||||||
|
scope,
|
||||||
|
kvCache,
|
||||||
|
recacheDelay,
|
||||||
|
actionWrapper,
|
||||||
|
idGetter
|
||||||
|
),
|
||||||
|
WriteKeyValueRepo<Id, RegisteredObject> by AutoRecacheWriteKeyValueRepo(originalRepo, scope, kvCache),
|
||||||
|
KeyValueRepo<Id, RegisteredObject> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: KeyValueRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<RegisteredObject>) = originalRepo.unsetWithValues(
|
||||||
|
toUnset
|
||||||
|
).also {
|
||||||
|
kvCache.unsetWithValues(toUnset)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,104 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
|
||||||
|
protected open val originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct,
|
||||||
|
protected val idGetter: (RegisteredObject) -> Id
|
||||||
|
) : ReadKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
|
val autoUpdateJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
actualizeAll()
|
||||||
|
|
||||||
|
delay(recacheDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: ReadKeyValueRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
idGetter: (RegisteredObject) -> Id
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis), idGetter)
|
||||||
|
|
||||||
|
protected open suspend fun actualizeAll(): Result<Unit> {
|
||||||
|
return runCatchingSafely {
|
||||||
|
kvCache.actualizeAll(originalRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(key: Id): Boolean = actionWrapper.wrap {
|
||||||
|
originalRepo.contains(key)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.contains(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.getAll()
|
||||||
|
}.onSuccess {
|
||||||
|
kvCache.actualizeAll(clear = true) { it }
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count()
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(k: Id): RegisteredObject? = actionWrapper.wrap {
|
||||||
|
originalRepo.get(k)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
kvCache.set(k, it)
|
||||||
|
} ?: kvCache.get(k)
|
||||||
|
|
||||||
|
override suspend fun values(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.values(pagination, reversed)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
it.results.forEach {
|
||||||
|
kvCache.set(idGetter(it), it)
|
||||||
|
}
|
||||||
|
} ?: kvCache.values(pagination, reversed)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(pagination, reversed)
|
||||||
|
}.getOrElse { kvCache.keys(pagination, reversed) }
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: RegisteredObject,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(v, pagination, reversed)
|
||||||
|
}.getOrElse { kvCache.keys(v, pagination, reversed) }
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,54 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalue
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.plus
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
||||||
|
open class AutoRecacheWriteKeyValueRepo<Id, RegisteredObject>(
|
||||||
|
protected val originalRepo: WriteKeyValueRepo<Id, RegisteredObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache()
|
||||||
|
) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
|
override val onValueRemoved: Flow<Id>
|
||||||
|
get() = (originalRepo.onValueRemoved + kvCache.onValueRemoved).distinctUntilChanged()
|
||||||
|
|
||||||
|
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
|
||||||
|
get() = (originalRepo.onNewValue + kvCache.onNewValue).distinctUntilChanged()
|
||||||
|
|
||||||
|
private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onNewAndUpdatedObjectsListeningJob = originalRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.set(it.first, it.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unsetWithValues(toUnset: List<RegisteredObject>) = originalRepo.unsetWithValues(
|
||||||
|
toUnset
|
||||||
|
).also {
|
||||||
|
kvCache.unsetWithValues(toUnset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<Id>) = originalRepo.unset(
|
||||||
|
toUnset
|
||||||
|
).also {
|
||||||
|
kvCache.unset(toUnset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(toSet: Map<Id, RegisteredObject>) = originalRepo.set(
|
||||||
|
toSet
|
||||||
|
).also {
|
||||||
|
kvCache.set(toSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheKeyValuesRepo<Id, RegisteredObject>(
|
||||||
|
override val originalRepo: KeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
actionWrapper: ActionWrapper = ActionWrapper.Direct
|
||||||
|
) : AutoRecacheReadKeyValuesRepo<Id, RegisteredObject> (
|
||||||
|
originalRepo,
|
||||||
|
scope,
|
||||||
|
kvCache,
|
||||||
|
recacheDelay,
|
||||||
|
actionWrapper
|
||||||
|
),
|
||||||
|
WriteKeyValuesRepo<Id, RegisteredObject> by AutoRecacheWriteKeyValuesRepo(originalRepo, scope, kvCache),
|
||||||
|
KeyValuesRepo<Id, RegisteredObject> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: KeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
|
||||||
|
|
||||||
|
override suspend fun clearWithValue(v: RegisteredObject) {
|
||||||
|
super.clearWithValue(v)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,145 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.createPaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.emptyPaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.firstIndex
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
|
||||||
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
open class AutoRecacheReadKeyValuesRepo<Id, RegisteredObject>(
|
||||||
|
protected open val originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
||||||
|
protected val recacheDelay: Long = 60.seconds.inWholeMilliseconds,
|
||||||
|
protected val actionWrapper: ActionWrapper = ActionWrapper.Direct
|
||||||
|
) : ReadKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
|
val autoUpdateJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
actualizeAll()
|
||||||
|
|
||||||
|
delay(recacheDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
originalRepo: ReadKeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
originalCallTimeoutMillis: Long,
|
||||||
|
kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache(),
|
||||||
|
recacheDelay: Long = 60.seconds.inWholeMilliseconds
|
||||||
|
) : this(originalRepo, scope, kvCache, recacheDelay, ActionWrapper.Timeouted(originalCallTimeoutMillis))
|
||||||
|
|
||||||
|
protected open suspend fun actualizeAll(): Result<Unit> {
|
||||||
|
return runCatchingSafely {
|
||||||
|
kvCache.actualizeAll(originalRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(k: Id): Boolean = actionWrapper.wrap {
|
||||||
|
originalRepo.contains(k)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.contains(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count()
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: RegisteredObject,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(v, pagination, reversed)
|
||||||
|
}.getOrElse {
|
||||||
|
val results = mutableListOf<Id>()
|
||||||
|
|
||||||
|
val toSkip = pagination.firstIndex
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
doForAllWithNextPaging {
|
||||||
|
kvCache.keys(pagination, reversed).also {
|
||||||
|
it.results.forEach {
|
||||||
|
if (kvCache.get(it) ?.contains(v) == true) {
|
||||||
|
count++
|
||||||
|
if (count < toSkip || results.size >= pagination.size) {
|
||||||
|
return@forEach
|
||||||
|
} else {
|
||||||
|
results.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return@getOrElse results.createPaginationResult(
|
||||||
|
pagination,
|
||||||
|
count.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Id> = actionWrapper.wrap {
|
||||||
|
originalRepo.keys(pagination, reversed)
|
||||||
|
}.getOrElse { kvCache.keys(pagination, reversed) }
|
||||||
|
|
||||||
|
override suspend fun get(
|
||||||
|
k: Id,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<RegisteredObject> = actionWrapper.wrap {
|
||||||
|
originalRepo.get(k, pagination, reversed)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
it.results.forEach {
|
||||||
|
kvCache.set(k, ((kvCache.get(k) ?: return@also) + it).distinct())
|
||||||
|
}
|
||||||
|
} ?: kvCache.get(k) ?.run {
|
||||||
|
paginate(pagination.optionallyReverse(size, reversed)).let {
|
||||||
|
if (reversed) {
|
||||||
|
it.changeResultsUnchecked(
|
||||||
|
it.results.reversed()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: emptyPaginationResult()
|
||||||
|
|
||||||
|
override suspend fun count(k: Id): Long = actionWrapper.wrap {
|
||||||
|
originalRepo.count(k)
|
||||||
|
}.getOrElse {
|
||||||
|
kvCache.get(k) ?.size ?.toLong() ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(k: Id, v: RegisteredObject): Boolean {
|
||||||
|
return (actionWrapper.wrap {
|
||||||
|
originalRepo.contains(k, v)
|
||||||
|
}.getOrNull() ?.also {
|
||||||
|
kvCache.set(k, ((kvCache.get(k) ?: return@also) + v).distinct())
|
||||||
|
}) ?: (kvCache.get(k) ?.contains(v) == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
actualizeAll()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.cache.fallback.keyvalues
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.plus
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
||||||
|
open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
|
||||||
|
protected val originalRepo: WriteKeyValuesRepo<Id, RegisteredObject>,
|
||||||
|
protected val scope: CoroutineScope,
|
||||||
|
protected val kvCache: FullKVCache<Id, List<RegisteredObject>> = FullKVCache()
|
||||||
|
) : WriteKeyValuesRepo<Id, RegisteredObject>, FallbackCacheRepo {
|
||||||
|
override val onValueRemoved: Flow<Pair<Id, RegisteredObject>>
|
||||||
|
get() = originalRepo.onValueRemoved
|
||||||
|
|
||||||
|
override val onNewValue: Flow<Pair<Id, RegisteredObject>>
|
||||||
|
get() = originalRepo.onNewValue
|
||||||
|
override val onDataCleared: Flow<Id>
|
||||||
|
get() = (originalRepo.onDataCleared + kvCache.onValueRemoved).distinctUntilChanged()
|
||||||
|
|
||||||
|
private val onDataClearedListeningJob = originalRepo.onDataCleared.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.unset(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.set(
|
||||||
|
it.first,
|
||||||
|
(kvCache.get(
|
||||||
|
it.first
|
||||||
|
) ?: return@subscribeSafelyWithoutExceptions) - it.second
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val onNewAndUpdatedObjectsListeningJob = originalRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
kvCache.set(
|
||||||
|
it.first,
|
||||||
|
(kvCache.get(
|
||||||
|
it.first
|
||||||
|
) ?: return@subscribeSafelyWithoutExceptions) + it.second
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearWithValue(v: RegisteredObject) {
|
||||||
|
originalRepo.clearWithValue(v)
|
||||||
|
doForAllWithNextPaging(FirstPagePagination(kvCache.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE)) {
|
||||||
|
kvCache.keys(it).also {
|
||||||
|
it.results.forEach { id ->
|
||||||
|
kvCache.get(id) ?.takeIf { it.contains(v) } ?.let {
|
||||||
|
kvCache.unset(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear(k: Id) {
|
||||||
|
originalRepo.clear(k)
|
||||||
|
kvCache.unset(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun remove(toRemove: Map<Id, List<RegisteredObject>>) {
|
||||||
|
originalRepo.remove(toRemove)
|
||||||
|
toRemove.forEach { (k, v) ->
|
||||||
|
kvCache.set(k, (kvCache.get(k) ?: return@forEach) - v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) {
|
||||||
|
originalRepo.add(toAdd)
|
||||||
|
toAdd.forEach { (k, v) ->
|
||||||
|
kvCache.set(k, (kvCache.get(k) ?: return@forEach) + v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun invalidate() {
|
||||||
|
kvCache.clear()
|
||||||
|
}
|
||||||
|
}
|
@@ -3,11 +3,9 @@ package dev.inmo.micro_utils.repos.cache.full
|
|||||||
import dev.inmo.micro_utils.common.*
|
import dev.inmo.micro_utils.common.*
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.cache.*
|
import dev.inmo.micro_utils.repos.cache.*
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
|
||||||
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -60,6 +58,12 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
|||||||
{ if (it) parentRepo.getById(id) ?.let { set(id, it) } }
|
{ if (it) parentRepo.getById(id) ?.let { set(id, it) } }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualize(
|
||||||
|
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
|
{ getAll() },
|
||||||
|
{ kvCache.actualizeAll(clear = true) { it } }
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize(
|
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize(
|
||||||
{ get(id) ?.optional ?: Optional.absent() },
|
{ get(id) ?.optional ?: Optional.absent() },
|
||||||
{ getById(id) },
|
{ getById(id) },
|
||||||
|
@@ -58,6 +58,12 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
|
|||||||
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
|
{ if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualize(
|
||||||
|
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
|
{ getAll() },
|
||||||
|
{ kvCache.actualizeAll(clear = true) { it } }
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
|
||||||
{ keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
{ keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||||
{ keys(pagination, reversed) },
|
{ keys(pagination, reversed) },
|
||||||
@@ -80,7 +86,7 @@ fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
|
|||||||
) = FullReadKeyValueCacheRepo(this, kvCache)
|
) = FullReadKeyValueCacheRepo(this, kvCache)
|
||||||
|
|
||||||
open class FullWriteKeyValueCacheRepo<Key,Value>(
|
open class FullWriteKeyValueCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: WriteKeyValueRepo<Key, Value>,
|
parentRepo: WriteKeyValueRepo<Key, Value>,
|
||||||
protected open val kvCache: FullKVCache<Key, Value>,
|
protected open val kvCache: FullKVCache<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
|
) : WriteKeyValueRepo<Key, Value> by parentRepo, FullCacheRepo {
|
||||||
@@ -98,7 +104,7 @@ fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching(
|
|||||||
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
|
) = FullWriteKeyValueCacheRepo(this, kvCache, scope)
|
||||||
|
|
||||||
open class FullKeyValueCacheRepo<Key,Value>(
|
open class FullKeyValueCacheRepo<Key,Value>(
|
||||||
override val parentRepo: KeyValueRepo<Key, Value>,
|
protected open val parentRepo: KeyValueRepo<Key, Value>,
|
||||||
kvCache: FullKVCache<Key, Value>,
|
kvCache: FullKVCache<Key, Value>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
|
) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope),
|
||||||
|
@@ -112,7 +112,7 @@ fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
|
|||||||
) = FullReadKeyValuesCacheRepo(this, kvCache)
|
) = FullReadKeyValuesCacheRepo(this, kvCache)
|
||||||
|
|
||||||
open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
open class FullWriteKeyValuesCacheRepo<Key,Value>(
|
||||||
protected open val parentRepo: WriteKeyValuesRepo<Key, Value>,
|
parentRepo: WriteKeyValuesRepo<Key, Value>,
|
||||||
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
protected open val kvCache: FullKVCache<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
|
) : WriteKeyValuesRepo<Key, Value> by parentRepo, FullCacheRepo {
|
||||||
@@ -140,7 +140,7 @@ fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching(
|
|||||||
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
|
) = FullWriteKeyValuesCacheRepo(this, kvCache, scope)
|
||||||
|
|
||||||
open class FullKeyValuesCacheRepo<Key,Value>(
|
open class FullKeyValuesCacheRepo<Key,Value>(
|
||||||
override val parentRepo: KeyValuesRepo<Key, Value>,
|
protected open val parentRepo: KeyValuesRepo<Key, Value>,
|
||||||
kvCache: FullKVCache<Key, List<Value>>,
|
kvCache: FullKVCache<Key, List<Value>>,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||||
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
|
) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope),
|
||||||
|
@@ -2,60 +2,50 @@ package dev.inmo.micro_utils.repos.cache.util
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
import dev.inmo.micro_utils.repos.cache.cache.KVCache
|
||||||
|
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||||
import dev.inmo.micro_utils.repos.set
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
|
||||||
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||||
|
clear: Boolean = true,
|
||||||
getAll: () -> Map<K, V>
|
getAll: () -> Map<K, V>
|
||||||
) {
|
) {
|
||||||
clear()
|
set(
|
||||||
set(getAll())
|
getAll().also {
|
||||||
|
if (clear) {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||||
repo: ReadKeyValueRepo<K, V>
|
repo: ReadKeyValueRepo<K, V>,
|
||||||
|
clear: Boolean = true,
|
||||||
) {
|
) {
|
||||||
clear()
|
actualizeAll(clear) {
|
||||||
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
|
repo.getAll()
|
||||||
val initPagination = FirstPagePagination(count)
|
|
||||||
doForAllWithNextPaging(initPagination) {
|
|
||||||
keys(it).also {
|
|
||||||
set(
|
|
||||||
it.results.mapNotNull { k -> repo.get(k) ?.let { k to it } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
|
suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
|
||||||
repo: ReadKeyValuesRepo<K, V>
|
repo: ReadKeyValuesRepo<K, V>,
|
||||||
|
clear: Boolean = true,
|
||||||
) {
|
) {
|
||||||
clear()
|
actualizeAll(clear) {
|
||||||
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
|
repo.getAll()
|
||||||
val initPagination = FirstPagePagination(count)
|
|
||||||
doForAllWithNextPaging(initPagination) {
|
|
||||||
keys(it).also {
|
|
||||||
set(
|
|
||||||
it.results.associateWith { k -> repo.getAll(k) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
|
||||||
repo: ReadCRUDRepo<V, K>
|
repo: ReadCRUDRepo<V, K>,
|
||||||
|
clear: Boolean = true,
|
||||||
) {
|
) {
|
||||||
clear()
|
actualizeAll(clear) {
|
||||||
val count = repo.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE
|
repo.getAll()
|
||||||
val initPagination = FirstPagePagination(count)
|
|
||||||
doForAllWithNextPaging(initPagination) {
|
|
||||||
keys(it).also {
|
|
||||||
set(
|
|
||||||
it.results.mapNotNull { k -> repo.getById(k) ?.let { k to it } }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,5 +32,3 @@ kotlin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableIncludingJvmCodeInAndroidPart()
|
|
||||||
|
@@ -2,6 +2,10 @@ package dev.inmo.micro_utils.repos
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.*
|
import dev.inmo.micro_utils.pagination.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
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
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,30 +21,48 @@ interface ReadKeyValueRepo<Key, Value> : Repo {
|
|||||||
suspend fun get(k: Key): Value?
|
suspend fun get(k: Key): Value?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
* This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use
|
||||||
* ascending sort for [Key]s
|
* ascending sort for [Key]s
|
||||||
*/
|
*/
|
||||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
* This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use
|
||||||
* ascending sort for [Key]s
|
* ascending sort for [Key]s
|
||||||
*/
|
*/
|
||||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
* This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use
|
||||||
* ascending sort for [Key]s
|
* ascending sort for [Key]s.
|
||||||
|
*
|
||||||
|
* **DEFAULT REALIZATION IS NOT OPTIMAL AND HAS BEEN ADDED TO COVER CASES OF DIFFERENT COMMON MAPPINGS AND TRANSFORMATIONS**
|
||||||
*
|
*
|
||||||
* @param v This value should be used to exclude from search the items with different [Value]s
|
* @param v This value should be used to exclude from search the items with different [Value]s
|
||||||
*/
|
*/
|
||||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> {
|
||||||
|
return getAllWithNextPaging {
|
||||||
|
keys(it)
|
||||||
|
}.filter {
|
||||||
|
get(it) == v
|
||||||
|
}.paginate(pagination, reversed)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if [key] is presented in current collection or false otherwise
|
* @return true if [key] is presented in current collection or false otherwise
|
||||||
*/
|
*/
|
||||||
suspend fun contains(key: Key): Boolean
|
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
|
* @return count of all collection objects
|
||||||
*/
|
*/
|
||||||
|
@@ -2,6 +2,9 @@ package dev.inmo.micro_utils.repos
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAllWithCurrentPaging
|
||||||
|
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
|
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
|
||||||
@@ -9,6 +12,14 @@ interface ReadCRUDRepo<ObjectType, IdType> : Repo {
|
|||||||
suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType>
|
suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType>
|
||||||
suspend fun getById(id: IdType): ObjectType?
|
suspend fun getById(id: IdType): ObjectType?
|
||||||
suspend fun contains(id: IdType): Boolean
|
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
|
suspend fun count(): Long
|
||||||
}
|
}
|
||||||
typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType>
|
typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType>
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.annotations
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this annotation and ksp generator (module `micro_utils.repos.generator`) to create the next hierarchy of models:
|
||||||
|
*
|
||||||
|
* * New model. For example: data class NewTest
|
||||||
|
* * Registered model. For example: data class RegisteredTest
|
||||||
|
*
|
||||||
|
* @param registeredSupertypes These [KClass]es will be used as supertypes for registered model
|
||||||
|
* @param serializable If true (default) will generate @[kotlinx.serialization.Serializable] for models. Affects [generateSerialName]
|
||||||
|
* @param serializable If true (default) will generate @[kotlinx.serialization.SerialName] for models with their names as values
|
||||||
|
*
|
||||||
|
* @see GenerateCRUDModelExcludeOverride
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class GenerateCRUDModel(
|
||||||
|
vararg val registeredSupertypes: KClass<*>,
|
||||||
|
val serializable: Boolean = true,
|
||||||
|
val generateSerialName: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this annotation on properties which should be excluded from overriding in models.
|
||||||
|
*
|
||||||
|
* @see GenerateCRUDModel
|
||||||
|
*/
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
@Target(AnnotationTarget.PROPERTY)
|
||||||
|
annotation class GenerateCRUDModelExcludeOverride
|
||||||
|
|
||||||
|
|
@@ -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 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(
|
override suspend fun getById(id: FromId): FromRegistered? = to.getById(
|
||||||
id.toOutKey()
|
id.toOutKey()
|
||||||
) ?.toInnerValue()
|
) ?.toInnerValue()
|
||||||
|
@@ -55,6 +55,10 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>(
|
|||||||
key.toOutKey()
|
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()
|
override suspend fun count(): Long = to.count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,12 +1,19 @@
|
|||||||
package dev.inmo.micro_utils.repos.pagination
|
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.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
|
||||||
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
|
suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll(
|
||||||
|
pagination: Pagination,
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T>
|
||||||
): List<T> = getAllWithNextPaging {
|
): List<T> = getAllWithNextPaging(pagination) {
|
||||||
methodCaller(this, it)
|
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
|
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.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll(
|
suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll(
|
||||||
|
pagination: Pagination,
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||||
): List<Pair<Key, Value>> = getAllWithNextPaging {
|
): List<Pair<Key, Value>> = getAllWithNextPaging(pagination) {
|
||||||
val result = methodCaller(it)
|
val result = methodCaller(it)
|
||||||
result.changeResultsUnchecked(
|
result.changeResultsUnchecked(
|
||||||
result.results.mapNotNull { it to (get(it) ?: return@mapNotNull null) }
|
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
|
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.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
|
||||||
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll(
|
suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll(
|
||||||
|
pagination: Pagination,
|
||||||
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
|
||||||
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key>
|
||||||
): List<Pair<Key, List<Value>>> = getAllWithNextPaging {
|
): List<Pair<Key, List<Value>>> = getAllWithNextPaging(pagination) {
|
||||||
val keysResult = methodCaller(it)
|
val keysResult = methodCaller(it)
|
||||||
keysResult.changeResultsUnchecked(
|
keysResult.changeResultsUnchecked(
|
||||||
keysResult.results.map { k ->
|
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)
|
||||||
|
@@ -0,0 +1,10 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import kotlin.js.JsName
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
fun <K, V> ReadKeyValueRepo<K, V>.asReadCRUDRepo() = ReadCRUDFromKeyValueRepo(this)
|
@@ -0,0 +1,22 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.crud
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
|
||||||
|
open class ReadCRUDFromKeyValueRepo<RegisteredType, IdType>(
|
||||||
|
protected open val original: ReadKeyValueRepo<IdType, RegisteredType>
|
||||||
|
) : ReadCRUDRepo<RegisteredType, IdType> {
|
||||||
|
override suspend fun contains(id: IdType): Boolean = original.contains(id)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
override suspend fun getById(id: IdType): RegisteredType? = original.get(id)
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kv
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import kotlin.js.JsName
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
fun <K, V> ReadKeyValuesRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromKeyValuesRepo(this)
|
||||||
|
|
||||||
|
fun <K, V> KeyValuesRepo<K, V>.asKeyValueRepo() = KeyValueFromKeyValuesRepo(this)
|
||||||
|
|
||||||
|
fun <K, V> ReadCRUDRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromCRUDRepo(this)
|
@@ -0,0 +1,41 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kv
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResults
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
|
|
||||||
|
open class KeyValueFromKeyValuesRepo<Key, Value>(
|
||||||
|
private val original: KeyValuesRepo<Key, Value>
|
||||||
|
) : KeyValueRepo<Key, List<Value>>, ReadKeyValueFromKeyValuesRepo<Key, Value>(original) {
|
||||||
|
override val onNewValue: Flow<Pair<Key, List<Value>>> = merge(
|
||||||
|
original.onNewValue,
|
||||||
|
original.onValueRemoved
|
||||||
|
).mapNotNull {
|
||||||
|
it.first to (get(it.first) ?: return@mapNotNull null)
|
||||||
|
}
|
||||||
|
override val onValueRemoved: Flow<Key> = original.onDataCleared
|
||||||
|
|
||||||
|
override suspend fun unset(toUnset: List<Key>) {
|
||||||
|
toUnset.forEach {
|
||||||
|
original.clear(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun set(toSet: Map<Key, List<Value>>) {
|
||||||
|
original.set(toSet)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kv
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResults
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.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
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class ReadKeyValueFromCRUDRepo<Key, Value>(
|
||||||
|
private val original: ReadCRUDRepo<Value, Key>
|
||||||
|
) : ReadKeyValueRepo<Key, Value> {
|
||||||
|
override suspend fun get(k: Key): Value? = original.getById(k)
|
||||||
|
|
||||||
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = original.getByPagination(
|
||||||
|
pagination.optionallyReverse(count(), reversed)
|
||||||
|
).let {
|
||||||
|
if (reversed) {
|
||||||
|
it.changeResultsUnchecked(it.results.reversed())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = original.getIdsByPagination(
|
||||||
|
pagination.optionallyReverse(count(), reversed)
|
||||||
|
).let {
|
||||||
|
if (reversed) {
|
||||||
|
it.changeResultsUnchecked(it.results.reversed())
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kv
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResults
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
|
||||||
|
|
||||||
|
open class ReadKeyValueFromKeyValuesRepo<Key, Value>(
|
||||||
|
private val original: ReadKeyValuesRepo<Key, Value>
|
||||||
|
) : ReadKeyValueRepo<Key, List<Value>> {
|
||||||
|
override suspend fun get(k: Key): List<Value>? = original.getAll(k)
|
||||||
|
|
||||||
|
override suspend fun values(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<List<Value>> {
|
||||||
|
val keys = keys(pagination, reversed)
|
||||||
|
return keys.changeResults(
|
||||||
|
keys.results.mapNotNull {
|
||||||
|
get(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
|
||||||
|
return original.keys(pagination, reversed)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun count(): Long {
|
||||||
|
return original.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun contains(key: Key): Boolean {
|
||||||
|
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>()
|
||||||
|
|
||||||
|
doForAllWithNextPaging(FirstPagePagination(count().toInt())) {
|
||||||
|
original.keys(it).also {
|
||||||
|
it.results.forEach {
|
||||||
|
val values = get(it) ?: return@forEach
|
||||||
|
if (values.containsAll(v) && v.containsAll(values)) {
|
||||||
|
keys.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val paginated = keys.paginate(
|
||||||
|
pagination.optionallyReverse(keys.count(), reversed)
|
||||||
|
)
|
||||||
|
return if (reversed) {
|
||||||
|
paginated.changeResultsUnchecked(paginated.results.reversed())
|
||||||
|
} else {
|
||||||
|
paginated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kvs
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import kotlin.js.JsName
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
|
||||||
|
fun <K, V, VI : Iterable<V>> ReadKeyValueRepo<K, VI>.asReadKeyValuesRepo() = ReadKeyValuesFromKeyValueRepo(this)
|
||||||
|
|
||||||
|
fun <K, V, VI : Iterable<V>> KeyValueRepo<K, VI>.asKeyValuesRepo(
|
||||||
|
listToValuesIterable: suspend (List<V>) -> VI
|
||||||
|
): KeyValuesFromKeyValueRepo<K, V, VI> = KeyValuesFromKeyValueRepo(this, listToValuesIterable)
|
||||||
|
|
||||||
|
@JvmName("asListKeyValuesRepo")
|
||||||
|
@JsName("asListKeyValuesRepo")
|
||||||
|
fun <K, V> KeyValueRepo<K, List<V>>.asKeyValuesRepo(): KeyValuesFromKeyValueRepo<K, V, List<V>> = asKeyValuesRepo { it }
|
||||||
|
|
||||||
|
@JvmName("asSetKeyValuesRepo")
|
||||||
|
@JsName("asSetKeyValuesRepo")
|
||||||
|
fun <K, V> KeyValueRepo<K, Set<V>>.asKeyValuesRepo(): KeyValuesFromKeyValueRepo<K, V, Set<V>> = asKeyValuesRepo { it.toSet() }
|
@@ -0,0 +1,77 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kvs
|
||||||
|
|
||||||
|
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.unset
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlin.js.JsName
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
open class KeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value>>(
|
||||||
|
private val original: KeyValueRepo<Key, ValuesIterable>,
|
||||||
|
private val listToValuesIterable: suspend (List<Value>) -> ValuesIterable
|
||||||
|
) : KeyValuesRepo<Key, Value>, ReadKeyValuesFromKeyValueRepo<Key, Value, ValuesIterable>(original) {
|
||||||
|
private val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
|
||||||
|
private val _onValueRemoved = MutableSharedFlow<Pair<Key, Value>>()
|
||||||
|
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||||
|
override val onValueRemoved: Flow<Pair<Key, Value>> = _onValueRemoved.asSharedFlow()
|
||||||
|
override val onDataCleared: Flow<Key> = original.onValueRemoved
|
||||||
|
|
||||||
|
override suspend fun clearWithValue(v: Value) {
|
||||||
|
val keys = mutableSetOf<Key>()
|
||||||
|
|
||||||
|
doForAllWithNextPaging(FirstPagePagination(count().toInt())) {
|
||||||
|
original.keys(it).also {
|
||||||
|
it.results.forEach {
|
||||||
|
if (contains(it, v)) {
|
||||||
|
keys.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
original.unset(keys.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear(k: Key) {
|
||||||
|
original.unset(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun remove(toRemove: Map<Key, List<Value>>) {
|
||||||
|
original.set(
|
||||||
|
toRemove.mapNotNull { (k, removing) ->
|
||||||
|
val exists = original.get(k) ?: return@mapNotNull null
|
||||||
|
k to listToValuesIterable(exists - removing).also {
|
||||||
|
if (it.firstOrNull() == null) {
|
||||||
|
original.unset(k)
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
)
|
||||||
|
toRemove.forEach { (k, v) ->
|
||||||
|
v.forEach {
|
||||||
|
_onValueRemoved.emit(k to it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun add(toAdd: Map<Key, List<Value>>) {
|
||||||
|
original.set(
|
||||||
|
toAdd.mapNotNull { (k, adding) ->
|
||||||
|
val exists = original.get(k) ?: emptyList()
|
||||||
|
k to listToValuesIterable(exists + adding)
|
||||||
|
}.toMap()
|
||||||
|
)
|
||||||
|
toAdd.forEach { (k, v) ->
|
||||||
|
v.forEach {
|
||||||
|
_onNewValue.emit(k to it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,72 @@
|
|||||||
|
package dev.inmo.micro_utils.repos.transforms.kvs
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.changeResultsUnchecked
|
||||||
|
import dev.inmo.micro_utils.pagination.emptyPaginationResult
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||||
|
|
||||||
|
open class ReadKeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value>>(
|
||||||
|
private val original: ReadKeyValueRepo<Key, ValuesIterable>
|
||||||
|
) : ReadKeyValuesRepo<Key, Value> {
|
||||||
|
override suspend fun get(
|
||||||
|
k: Key,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Value> {
|
||||||
|
val iterable = original.get(k) ?: return emptyPaginationResult(pagination)
|
||||||
|
val paginated = iterable.paginate(
|
||||||
|
pagination.optionallyReverse(iterable.count(), reversed)
|
||||||
|
)
|
||||||
|
return if (reversed) {
|
||||||
|
paginated.changeResultsUnchecked(paginated.results.reversed())
|
||||||
|
} else {
|
||||||
|
paginated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> = original.keys(pagination, reversed)
|
||||||
|
|
||||||
|
override suspend fun count(): Long = original.count()
|
||||||
|
|
||||||
|
override suspend fun count(k: Key): Long = original.get(k) ?.count() ?.toLong() ?: 0L
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key, v: Value): Boolean = original.get(k) ?.contains(v) == true
|
||||||
|
|
||||||
|
override suspend fun contains(k: Key): Boolean = original.contains(k)
|
||||||
|
|
||||||
|
override suspend fun keys(
|
||||||
|
v: Value,
|
||||||
|
pagination: Pagination,
|
||||||
|
reversed: Boolean
|
||||||
|
): PaginationResult<Key> {
|
||||||
|
val keys = mutableSetOf<Key>()
|
||||||
|
|
||||||
|
doForAllWithNextPaging(FirstPagePagination(count().toInt())) {
|
||||||
|
original.keys(it).also {
|
||||||
|
it.results.forEach {
|
||||||
|
if (contains(it, v)) {
|
||||||
|
keys.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val paginated = keys.paginate(
|
||||||
|
pagination.optionallyReverse(keys.count(), reversed)
|
||||||
|
)
|
||||||
|
return if (reversed) {
|
||||||
|
paginated.changeResultsUnchecked(paginated.results.reversed())
|
||||||
|
} else {
|
||||||
|
paginated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.micro_utils.repos
|
package dev.inmo.micro_utils.repos
|
||||||
|
|
||||||
import dev.inmo.micro_utils.common.Warning
|
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.*
|
||||||
import dev.inmo.micro_utils.pagination.utils.reverse
|
import dev.inmo.micro_utils.pagination.utils.reverse
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -31,11 +32,19 @@ class FileReadKeyValueRepo(
|
|||||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
|
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<File> {
|
||||||
val count = count()
|
val count = count()
|
||||||
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
||||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
|
val filesList = folder.list()
|
||||||
if (reversed) {
|
val files: Array<String> = if (resultPagination.firstIndex < count) {
|
||||||
filesPaths.reverse()
|
val filesPaths = filesList.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive.coerceAtMost(filesList.size))
|
||||||
|
|
||||||
|
if (reversed) {
|
||||||
|
filesPaths.reversedArray()
|
||||||
|
} else {
|
||||||
|
filesPaths
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyArray<String>()
|
||||||
}
|
}
|
||||||
return filesPaths.map { File(folder, it) }.createPaginationResult(
|
return files.map { File(folder, it) }.createPaginationResult(
|
||||||
resultPagination,
|
resultPagination,
|
||||||
count
|
count
|
||||||
)
|
)
|
||||||
@@ -44,11 +53,21 @@ class FileReadKeyValueRepo(
|
|||||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
|
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<String> {
|
||||||
val count = count()
|
val count = count()
|
||||||
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
val resultPagination = if (reversed) pagination.reverse(count) else pagination
|
||||||
val filesPaths = folder.list() ?.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive) ?: return emptyPaginationResult()
|
val filesList = folder.list()
|
||||||
if (reversed) {
|
|
||||||
filesPaths.reverse()
|
val files: Array<String> = if (resultPagination.firstIndex < count) {
|
||||||
|
val filesPaths = filesList.copyOfRange(resultPagination.firstIndex, resultPagination.lastIndexExclusive.coerceAtMost(filesList.size))
|
||||||
|
|
||||||
|
if (reversed) {
|
||||||
|
filesPaths.reversedArray()
|
||||||
|
} else {
|
||||||
|
filesPaths
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyArray<String>()
|
||||||
}
|
}
|
||||||
return filesPaths.toList().createPaginationResult(
|
|
||||||
|
return files.toList().createPaginationResult(
|
||||||
resultPagination,
|
resultPagination,
|
||||||
count
|
count
|
||||||
)
|
)
|
||||||
@@ -76,6 +95,8 @@ class FileReadKeyValueRepo(
|
|||||||
return File(folder, key).exists()
|
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
|
override suspend fun count(): Long = folder.list() ?.size ?.toLong() ?: 0L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user