Compare commits

...

83 Commits

Author SHA1 Message Date
438fefa7a3 fixes 2023-05-09 15:09:50 +06:00
5d74eac814 Update libs.versions.toml 2023-05-09 01:41:25 +06:00
9fb62e1e25 start 0.18.2 and make json of Startup customizable 2023-05-09 01:38:04 +06:00
3e366ea73b Update libs.versions.toml 2023-05-06 01:54:15 +06:00
0ff895bffa Update gradle.properties 2023-05-06 01:53:33 +06:00
c5bb120280 Merge pull request #248 from InsanusMokrassar/0.18.1
0.18.1
2023-05-05 22:26:53 +06:00
4b26a92b37 improve old diff API 2023-05-05 12:01:29 +06:00
0a8453b4d2 add MapDiff#reversed and MutableMap#applyDiff(MapDiff) 2023-05-05 11:58:27 +06:00
c9872a61b6 now applyDiff will return its receiver 2023-05-05 11:50:23 +06:00
149a1aa278 fix in new applyDiff 2023-05-05 11:33:22 +06:00
13d0e1b682 add MapDiff utils 2023-05-04 21:12:26 +06:00
6f9be2a9f8 start 0.18.1 and add SmartMutex 2023-05-03 23:47:05 +06:00
93ba98d993 Merge pull request #246 from InsanusMokrassar/revert-245-renovate/ksp
Revert "Update ksp to v1.8.21-1.0.11"
2023-04-28 00:34:20 +06:00
940ee3df06 Revert "Update ksp to v1.8.21-1.0.11" 2023-04-28 00:34:10 +06:00
2e7bab10fd Merge pull request #245 from InsanusMokrassar/renovate/ksp
Update ksp to v1.8.21-1.0.11
2023-04-28 00:14:25 +06:00
renovate[bot]
3ed70a37ea Update ksp to v1.8.21-1.0.11 2023-04-27 18:10:41 +00:00
fe8f80b9d9 Merge pull request #243 from InsanusMokrassar/0.18.0
0.18.0
2023-04-27 17:08:42 +06:00
d81fb32fb9 remove previous deprecations 2023-04-27 16:29:08 +06:00
2877b5532c Revert "Get back some functional of "trying to update up to gradle wrapper 8.1.1""
This reverts commit b938b21395.
2023-04-27 16:24:58 +06:00
b938b21395 Get back some functional of "trying to update up to gradle wrapper 8.1.1" 2023-04-27 16:24:37 +06:00
58836359cc Revert "trying to update up to gradle wrapper 8.1.1"
This reverts commit 5edb0e1331.
2023-04-27 15:00:43 +06:00
5edb0e1331 trying to update up to gradle wrapper 8.1.1 2023-04-27 14:15:45 +06:00
0f0d0b5d58 update android fragments dependency 2023-04-27 12:16:48 +06:00
46c1887cbe remove usage of cache updates flows in autorecached repos 2023-04-27 12:06:50 +06:00
5f231c2212 ReadKeyValuesRepo#removeWithValue 2023-04-25 19:32:43 +06:00
4e97ce86aa add defauult value to kvCache in fullyCached 2023-04-25 17:39:14 +06:00
315a7cb29e Rename full caching factories functions to fullyCached 2023-04-25 17:23:47 +06:00
aa7cc503f2 improvements in query parameters getting 2023-04-25 13:14:12 +06:00
4bbe7e5a80 update build tools version 2023-04-25 12:23:53 +06:00
d9c05f38d2 start 0.18.0 2023-04-25 12:23:08 +06:00
cd0c4c9650 Merge pull request #237 from InsanusMokrassar/renovate/android-gradle
Update dependency com.android.tools.build:gradle to v7.4.2
2023-04-25 00:38:47 +06:00
fc3407f104 Merge pull request #241 from InsanusMokrassar/0.17.8
0.17.8
2023-04-19 20:16:36 +06:00
3a5544206b update ktor 2023-04-19 19:46:39 +06:00
e17e2f7fb8 start 0.17.8 2023-04-19 19:45:58 +06:00
renovate[bot]
d32c95f143 Update dependency com.android.tools.build:gradle to v7.4.2 2023-04-18 18:12:52 +00:00
6d8a8ab018 Merge pull request #233 from InsanusMokrassar/0.17.7
0.17.7
2023-04-18 19:37:06 +06:00
a7dce8fa78 update dependencies 2023-04-18 19:24:05 +06:00
ca73ff8e19 add support of native in startup 2023-04-18 19:20:39 +06:00
d01ad10d7d start 0.17.7 2023-04-18 19:20:39 +06:00
81041ee43c Merge pull request #232 from InsanusMokrassar/renovate/configure
Configure Renovate
2023-04-18 19:20:15 +06:00
renovate[bot]
6e004c2ae4 Add renovate.json 2023-04-18 08:23:10 +00:00
0e2fac5b22 Merge pull request #231 from InsanusMokrassar/0.17.6
0.17.6
2023-04-13 11:27:18 +06:00
269da7f155 update dependencies and fill changelog 2023-04-13 11:20:34 +06:00
3cb6b73ee0 add preloadImage 2023-04-07 01:17:04 +06:00
a938ee1efb simplify API of MPPFile.input 2023-04-04 01:26:18 +06:00
6ea5e2e5a6 update version 2023-04-03 22:54:57 +06:00
617dfb54e0 experimentally add linuxx64 and mingwx64 as target platforms 2023-04-03 22:35:41 +06:00
d23e005985 preview of files realization 2023-04-03 15:49:46 +06:00
e5207f5bc5 Revert "Revert "trying to add native support""
This reverts commit c96cea8db0.
2023-04-03 10:26:03 +06:00
c96cea8db0 Revert "trying to add native support"
This reverts commit 0a8e71d76a.
2023-04-03 10:22:53 +06:00
0a8e71d76a trying to add native support 2023-04-03 10:22:43 +06:00
cf1fd32b08 Merge pull request #230 from InsanusMokrassar/0.17.5
0.17.5
2023-03-10 22:30:32 +06:00
cc4224ea1f update kv and crud tests 2023-03-10 22:08:35 +06:00
f4c148bc58 add map type info in ktor read kv and crud repos 2023-03-10 21:41:01 +06:00
022297ad3f fixes in AutoRecacheReadKeyValueRepo 2023-03-10 19:37:36 +06:00
5180d6fc3e hotfixes 2023-03-10 18:43:43 +06:00
eeebbff70d getAll 2023-03-10 18:37:48 +06:00
afc6aeea15 fixes 2023-03-10 16:14:51 +06:00
486515eddd update dokka 2023-03-10 16:09:33 +06:00
0e21699cd1 several updates 2023-03-10 15:45:02 +06:00
f1678ef7cf start 0.17.5 2023-03-10 15:36:36 +06:00
cea65fc76e Merge pull request #229 from InsanusMokrassar/0.17.4
0.17.4
2023-03-09 23:23:55 +06:00
c9e320b72a update compose version 2023-03-09 23:22:33 +06:00
555956087d add android manifest to mapper module 2023-03-09 22:23:56 +06:00
b3f468f901 add docs to mapper serialization and allow to use them as supertypes 2023-03-09 22:21:22 +06:00
f5f7511781 add mapper serializer 2023-03-09 21:55:07 +06:00
4be1d93f60 start 0.17.4 2023-03-09 21:49:15 +06:00
7d684608ef Merge pull request #228 from InsanusMokrassar/0.17.3
0.17.3
2023-03-07 23:30:23 +06:00
2c7fd320eb fixed 2023-03-07 22:28:56 +06:00
88ee82e1c6 add Diff#isEmpty 2023-03-07 21:50:23 +06:00
d6402c624e optimization of nonstrict comparison 2023-03-07 19:14:43 +06:00
8b9c93bc10 diffs improvement 2023-03-07 19:12:12 +06:00
4f5e261d01 start 0.17.3 2023-03-07 18:59:34 +06:00
cf455aebe6 Merge pull request #227 from InsanusMokrassar/0.17.2
0.17.2
2023-03-02 21:57:49 +06:00
1380d5f8e1 fill changelog 2023-03-02 21:38:20 +06:00
5f65260bfe Update DefaultStatesManager.kt 2023-03-02 21:35:59 +06:00
11f0dcfc01 Update DefaultStatesManager.kt 2023-03-02 21:32:58 +06:00
555b7886a4 Update gradle.properties 2023-03-02 21:28:50 +06:00
3707a6c0ce Merge pull request #226 from InsanusMokrassar/0.17.1
0.17.1
2023-02-28 19:36:08 +06:00
4c8d92b4c3 update ktor 2023-02-28 19:32:32 +06:00
8bbd33c896 now all android modules depends on jvm 2023-02-28 19:08:39 +06:00
ac33a3580f start 0.17.1 2023-02-28 19:04:04 +06:00
a64a32fbe6 Merge pull request #225 from InsanusMokrassar/0.17.0
0.17.0
2023-02-28 12:15:41 +06:00
104 changed files with 2514 additions and 879 deletions

View File

@@ -1,5 +1,91 @@
# Changelog # Changelog
## 0.18.2
* `Startup`:
* Now internal `Json` is fully customizable
## 0.18.1
* `Common`:
* Add `MapDiff`
* `Coroutines`:
* Add `SmartMutex`
## 0.18.0
**ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED**
* `Versions`:
* `Android Fragments`: `1.5.6` -> `1.5.7`
* `Ktor`:
* `Server`:
* Now it is possible to take query parameters as list
* `Repos`:
* `Common`:
* New `WriteKeyValuesRepo.removeWithValue`
* `Cache`:
* Rename full caching factories functions to `fullyCached`
## 0.17.8
* `Versions`:
* `Ktor`: `2.2.4` -> `2.3.0`
## 0.17.7
* `Versions`:
* `Android CoreKtx`: `1.9.0` -> `1.10.0`
* `Startup`:
* Add support of `linuxX64` and `mingwX64` platforms
## 0.17.6
* `Versions`:
* `Kotlin`: `1.8.10` -> `1.8.20`
* `KSLog`: `1.0.0` -> `1.1.1`
* `Compose`: `1.3.1` -> `1.4.0`
* `Koin`: `3.3.2` -> `3.4.0`
* `RecyclerView`: `1.2.1` -> `1.3.0`
* `Fragment`: `1.5.5` -> `1.5.6`
* Experimentally (`!!!`) add `linuxX64` and `mingwX64` targets
## 0.17.5
* `Common`:
* Conversations of number primitives with bounds care
* `Repos`:
* `Common`:
* By default, `getAll` for repos will take all the size of repo as page size
* New extension for all built-in repos `maxPagePagination`
* All the repos got `getAll` functions
## 0.17.4
* `Serialization`:
* `Mapper`:
* Module inited
* `Versions`:
* `Compose`: `1.3.1-rc02` -> `1.3.1`
## 0.17.3
* `Common`:
* Add `fixed` extensions for `Float` and `Double`
* New function `emptyDiff`
* Now you may pass custom `comparisonFun` to all `diff` functions
## 0.17.2
* `FSM`:
* `DefaultStatesManager.onUpdateContextsConflictResolver` and `DefaultStatesManager.onStartContextsConflictResolver` now return `false` by default
## 0.17.1
* **Hotfix** for absence of jvm dependencies in android modules
* `Versions`:
* `Ktor`: `2.2.3` -> `2.2.4`
## 0.17.0 ## 0.17.0
* `Versions`: * `Versions`:

View File

@@ -23,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

View File

@@ -20,5 +20,16 @@ kotlin {
} }
dependsOn jvmMain dependsOn jvmMain
} }
linuxX64Main {
dependencies {
api libs.okio
}
}
mingwX64Main {
dependencies {
api libs.okio
}
}
} }
} }

View File

@@ -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)
/** /**
@@ -169,6 +199,23 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T> other: Iterable<T>
) = calculateDiff(other, strictComparison = true) ) = calculateDiff(other, strictComparison = true)
/**
* Applies [diff] to [this] [MutableList]
*/
fun <T> MutableList<T>.applyDiff(
diff: Diff<T>
) {
for (i in diff.removed.indices.sortedDescending()) {
removeAt(diff.removed[i].index)
}
diff.added.forEach { (i, t) ->
add(i, t)
}
diff.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}
/** /**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this] * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list * mutable list
@@ -177,13 +224,26 @@ fun <T> MutableList<T>.applyDiff(
source: Iterable<T>, source: Iterable<T>,
strictComparison: Boolean = false strictComparison: Boolean = false
): Diff<T> = calculateDiff(source, strictComparison).also { ): Diff<T> = calculateDiff(source, strictComparison).also {
for (i in it.removed.indices.sortedDescending()) { applyDiff(it)
removeAt(it.removed[i].index)
}
it.added.forEach { (i, t) ->
add(i, t)
}
it.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
} }
/**
* This method call [calculateDiff] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
comparisonFun: (T?, T?) -> Boolean
): Diff<T> = calculateDiff(source, comparisonFun).also {
applyDiff(it)
}
/**
* Reverse [this] [Diff]. Result will contain [Diff.added] on [Diff.removed] (and vice-verse), all the
* [Diff.replaced] values will be reversed too
*/
fun <T> Diff<T>.reversed() = Diff(
removed = added,
replaced = replaced.map { it.second to it.first },
added = removed
)

View File

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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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();

View 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()
}

View 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()
}
}

View 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()
}

View 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()
}
}

View File

@@ -1,21 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(initial, scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
fun <T> Flow<T>.toMutableState(
initial: T,
scope: CoroutineScope
): MutableState<T> = asMutableComposeState(initial, scope)
@Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState"))
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.toMutableState(
scope: CoroutineScope
): MutableState<T> = asMutableComposeState(scope)

View File

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

View File

@@ -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
}

View File

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

View File

@@ -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

View File

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

View File

@@ -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)

View File

@@ -23,7 +23,7 @@ allprojects {
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle" mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle" mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"

View File

@@ -48,8 +48,8 @@ interface DefaultStatesManagerRepo<T : State> {
*/ */
open class DefaultStatesManager<T : State>( open class DefaultStatesManager<T : State>(
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(), protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> 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()

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.17.0 version=0.18.2
android_code_version=182 android_code_version=193

View File

@@ -1,40 +1,42 @@
[versions] [versions]
kt = "1.8.10" kt = "1.8.20"
kt-serialization = "1.5.0" kt-serialization = "1.5.0"
kt-coroutines = "1.6.4" kt-coroutines = "1.6.4"
kslog = "1.0.0" kslog = "1.1.1"
jb-compose = "1.3.1-rc01" jb-compose = "1.4.0"
jb-exposed = "0.41.1" jb-exposed = "0.41.1"
jb-dokka = "1.7.20" jb-dokka = "1.8.10"
klock = "3.4.0" korlibs = "3.4.0"
uuid = "0.7.0" uuid = "0.7.0"
ktor = "2.2.3" ktor = "2.3.0"
gh-release = "2.4.1" gh-release = "2.4.1"
koin = "3.3.2" koin = "3.4.0"
ksp = "1.8.10-1.0.9" okio = "3.3.0"
kotlin-poet = "1.12.0"
android-gradle = "7.3.1" ksp = "1.8.20-1.0.11"
kotlin-poet = "1.13.0"
android-gradle = "7.4.2"
dexcount = "4.0.0" dexcount = "4.0.0"
android-coreKtx = "1.9.0" android-coreKtx = "1.10.0"
android-recyclerView = "1.2.1" android-recyclerView = "1.3.0"
android-appCompat = "1.6.1" android-appCompat = "1.6.1"
android-fragment = "1.5.5" android-fragment = "1.5.7"
android-espresso = "3.5.1" android-espresso = "3.5.1"
android-test = "1.1.5" 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.1" android-props-buildTools = "33.0.2"
[libraries] [libraries]
@@ -67,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" }
@@ -91,6 +94,8 @@ kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref
kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" } kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
# Buildscript # 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" }

View File

@@ -18,5 +18,17 @@ kotlin {
androidMain { androidMain {
dependsOn jvmMain dependsOn jvmMain
} }
linuxX64Main {
dependencies {
api internalProject("micro_utils.mime_types")
}
}
mingwX64Main {
dependencies {
api internalProject("micro_utils.mime_types")
}
}
} }
} }

View File

@@ -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()
}

View File

@@ -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)
}
}

View File

@@ -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()

View 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)
}

View 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
}
}

View 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)
}

View 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
}
}

View 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())
}

View 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())
}

View File

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

View File

@@ -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'

View File

@@ -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

View File

@@ -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()
} }

View File

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

View File

@@ -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
} }
} }

View File

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

6
renovate.json Normal file
View File

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

View File

@@ -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()

View File

@@ -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()
} }

View File

@@ -52,6 +52,14 @@ open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
kvCache.contains(id) kvCache.contains(id)
} }
override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap {
originalRepo.getAll()
}.onSuccess {
kvCache.actualizeAll(clear = true) { it }
}.getOrElse {
kvCache.getAll()
}
override suspend fun count(): Long = actionWrapper.wrap { override suspend fun count(): Long = actionWrapper.wrap {
originalRepo.count() originalRepo.count()
}.getOrElse { }.getOrElse {

View File

@@ -21,9 +21,9 @@ open class AutoRecacheWriteCRUDRepo<RegisteredObject, Id, InputObject>(
protected val idGetter: (RegisteredObject) -> Id protected val idGetter: (RegisteredObject) -> Id
) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo { ) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo {
override val deletedObjectsIdsFlow: Flow<Id> override val deletedObjectsIdsFlow: Flow<Id>
get() = (originalRepo.deletedObjectsIdsFlow + kvCache.onValueRemoved).distinctUntilChanged() get() = (originalRepo.deletedObjectsIdsFlow).distinctUntilChanged()
override val newObjectsFlow: Flow<RegisteredObject> override val newObjectsFlow: Flow<RegisteredObject>
get() = (originalRepo.newObjectsFlow + kvCache.onNewValue.map { it.second }).distinctUntilChanged() get() = (originalRepo.newObjectsFlow).distinctUntilChanged()
override val updatedObjectsFlow: Flow<RegisteredObject> override val updatedObjectsFlow: Flow<RegisteredObject>
get() = originalRepo.updatedObjectsFlow get() = originalRepo.updatedObjectsFlow

View File

@@ -52,6 +52,14 @@ open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
kvCache.contains(key) 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 { override suspend fun count(): Long = actionWrapper.wrap {
originalRepo.count() originalRepo.count()
}.getOrElse { }.getOrElse {

View File

@@ -17,10 +17,10 @@ open class AutoRecacheWriteKeyValueRepo<Id, RegisteredObject>(
protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache() protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache()
) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo { ) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo {
override val onValueRemoved: Flow<Id> override val onValueRemoved: Flow<Id>
get() = (originalRepo.onValueRemoved + kvCache.onValueRemoved).distinctUntilChanged() get() = (originalRepo.onValueRemoved).distinctUntilChanged()
override val onNewValue: Flow<Pair<Id, RegisteredObject>> override val onNewValue: Flow<Pair<Id, RegisteredObject>>
get() = (originalRepo.onNewValue + kvCache.onNewValue).distinctUntilChanged() get() = (originalRepo.onNewValue).distinctUntilChanged()
private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) { private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) {
kvCache.unset(it) kvCache.unset(it)

View File

@@ -7,6 +7,7 @@ import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
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.FallbackCacheRepo import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
import dev.inmo.micro_utils.repos.pagination.maxPagePagination
import dev.inmo.micro_utils.repos.set import dev.inmo.micro_utils.repos.set
import dev.inmo.micro_utils.repos.unset import dev.inmo.micro_utils.repos.unset
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -24,7 +25,7 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
override val onNewValue: Flow<Pair<Id, RegisteredObject>> override val onNewValue: Flow<Pair<Id, RegisteredObject>>
get() = originalRepo.onNewValue get() = originalRepo.onNewValue
override val onDataCleared: Flow<Id> override val onDataCleared: Flow<Id>
get() = (originalRepo.onDataCleared + kvCache.onValueRemoved).distinctUntilChanged() get() = (originalRepo.onDataCleared).distinctUntilChanged()
private val onDataClearedListeningJob = originalRepo.onDataCleared.subscribeSafelyWithoutExceptions(scope) { private val onDataClearedListeningJob = originalRepo.onDataCleared.subscribeSafelyWithoutExceptions(scope) {
kvCache.unset(it) kvCache.unset(it)
@@ -50,7 +51,7 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
override suspend fun clearWithValue(v: RegisteredObject) { override suspend fun clearWithValue(v: RegisteredObject) {
originalRepo.clearWithValue(v) originalRepo.clearWithValue(v)
doForAllWithNextPaging(FirstPagePagination(kvCache.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE)) { doForAllWithNextPaging(kvCache.maxPagePagination()) {
kvCache.keys(it).also { kvCache.keys(it).also {
it.results.forEach { id -> it.results.forEach { id ->
kvCache.get(id) ?.takeIf { it.contains(v) } ?.let { kvCache.get(id) ?.takeIf { it.contains(v) } ?.let {
@@ -73,6 +74,19 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>(
} }
} }
override suspend fun removeWithValue(v: RegisteredObject) {
originalRepo.removeWithValue(v)
doForAllWithNextPaging(kvCache.maxPagePagination()) {
kvCache.keys(it).also {
it.results.forEach { id ->
kvCache.get(id) ?.takeIf { it.contains(v) } ?.let {
kvCache.set(id, it - v)
}
}
}
}
}
override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) { override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) {
originalRepo.add(toAdd) originalRepo.add(toAdd)
toAdd.forEach { (k, v) -> toAdd.forEach { (k, v) ->

View File

@@ -58,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) },
@@ -96,8 +102,15 @@ open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>(
} }
} }
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.fullyCached(
kvCache: FullKVCache<IdType, ObjectType> = FullKVCache(),
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
idGetter: (ObjectType) -> IdType
) = FullCRUDCacheRepo(this, kvCache, scope, idGetter)
@Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope, idGetter)", "dev.inmo.micro_utils.repos.cache.full.fullyCached"))
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached( fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
kvCache: FullKVCache<IdType, ObjectType>, kvCache: FullKVCache<IdType, ObjectType>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default), scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
idGetter: (ObjectType) -> IdType idGetter: (ObjectType) -> IdType
) = FullCRUDCacheRepo(this, kvCache, scope, idGetter) ) = fullyCached(kvCache, scope, idGetter)

View File

@@ -9,7 +9,6 @@ import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.pagination.getAll import dev.inmo.micro_utils.repos.pagination.getAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
open class FullReadKeyValueCacheRepo<Key,Value>( open class FullReadKeyValueCacheRepo<Key,Value>(
@@ -59,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) },
@@ -112,7 +117,13 @@ open class FullKeyValueCacheRepo<Key,Value>(
} }
} }
fun <Key, Value> KeyValueRepo<Key, Value>.fullyCached(
kvCache: FullKVCache<Key, Value> = FullKVCache(),
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = FullKeyValueCacheRepo(this, kvCache, scope)
@Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached"))
fun <Key, Value> KeyValueRepo<Key, Value>.cached( fun <Key, Value> KeyValueRepo<Key, Value>.cached(
kvCache: FullKVCache<Key, Value>, kvCache: FullKVCache<Key, Value>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = FullKeyValueCacheRepo(this, kvCache, scope) ) = fullyCached(kvCache, scope)

View File

@@ -157,8 +157,18 @@ open class FullKeyValuesCacheRepo<Key,Value>(
override suspend fun invalidate() { override suspend fun invalidate() {
kvCache.actualizeAll(parentRepo) kvCache.actualizeAll(parentRepo)
} }
override suspend fun removeWithValue(v: Value) {
super<FullWriteKeyValuesCacheRepo>.removeWithValue(v)
}
} }
fun <Key, Value> KeyValuesRepo<Key, Value>.fullyCached(
kvCache: FullKVCache<Key, List<Value>> = FullKVCache(),
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) = FullKeyValuesCacheRepo(this, kvCache, scope)
@Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached"))
fun <Key, Value> KeyValuesRepo<Key, Value>.caching( fun <Key, Value> KeyValuesRepo<Key, Value>.caching(
kvCache: FullKVCache<Key, List<Value>>, kvCache: FullKVCache<Key, List<Value>>,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.Default)

View File

@@ -28,7 +28,7 @@ suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
clear: Boolean = true, clear: Boolean = true,
) { ) {
actualizeAll(clear) { actualizeAll(clear) {
repo.getAll { keys(it) }.toMap() repo.getAll()
} }
} }
@@ -37,7 +37,7 @@ suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll(
clear: Boolean = true, clear: Boolean = true,
) { ) {
actualizeAll(clear) { actualizeAll(clear) {
repo.getAll { keys(it) }.toMap() repo.getAll()
} }
} }
@@ -46,8 +46,6 @@ suspend inline fun <K, V> KVCache<K, V>.actualizeAll(
clear: Boolean = true, clear: Boolean = true,
) { ) {
actualizeAll(clear) { actualizeAll(clear) {
repo.getAllByWithNextPaging { repo.getAll()
getIdsByPagination(it)
}.mapNotNull { it to (repo.getById(it) ?: return@mapNotNull null) }.toMap()
} }
} }

View File

@@ -2,8 +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.getAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.paginate 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
/** /**
@@ -51,6 +53,16 @@ interface ReadKeyValueRepo<Key, Value> : Repo {
*/ */
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
*/ */

View File

@@ -1,6 +1,7 @@
package dev.inmo.micro_utils.repos 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.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -44,9 +45,23 @@ interface WriteKeyValuesRepo<Key, Value> : Repo {
suspend fun add(toAdd: Map<Key, List<Value>>) suspend fun add(toAdd: Map<Key, List<Value>>)
/**
* Removes [Value]s by passed [Key]s without full clear of all data by [Key]
*/
suspend fun remove(toRemove: Map<Key, List<Value>>) suspend fun remove(toRemove: Map<Key, List<Value>>)
/**
* Removes [v] without full clear of all data by [Key]s with [v]
*/
suspend fun removeWithValue(v: Value)
/**
* Fully clear all data by [k]
*/
suspend fun clear(k: Key) suspend fun clear(k: Key)
/**
* Clear [v] **with** full clear of all data by [Key]s with [v]
*/
suspend fun clearWithValue(v: Value) suspend fun clearWithValue(v: Value)
suspend fun set(toSet: Map<Key, List<Value>>) { suspend fun set(toSet: Map<Key, List<Value>>) {
@@ -100,6 +115,21 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
keysResult.currentPageIfNotEmpty() keysResult.currentPageIfNotEmpty()
} }
} }
suspend override fun removeWithValue(v: Value) {
val toRemove = mutableMapOf<Key, List<Value>>()
doForAllWithNextPaging {
keys(it).also {
it.results.forEach {
if (contains(it, v)) {
toRemove[it] = listOf(v)
}
}
}
}
remove(toRemove)
}
} }
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value> typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value>

View File

@@ -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>

View File

@@ -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()

View File

@@ -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()
} }

View File

@@ -97,6 +97,8 @@ open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>(
}.toMap() }.toMap()
) )
override suspend fun removeWithValue(v: FromValue) = to.removeWithValue(v.toOutValue())
override suspend fun set(toSet: Map<FromKey, List<FromValue>>) { override suspend fun set(toSet: Map<FromKey, List<FromValue>>) {
to.set( to.set(
toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap() toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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())

View File

@@ -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)

View File

@@ -12,6 +12,8 @@ open class ReadCRUDFromKeyValueRepo<RegisteredType, IdType>(
override suspend fun count(): Long = original.count() 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 getByPagination(pagination: Pagination): PaginationResult<RegisteredType> = original.values(pagination)
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = original.keys(pagination) override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = original.keys(pagination)

View File

@@ -11,6 +11,7 @@ import dev.inmo.micro_utils.pagination.utils.paginate
import dev.inmo.micro_utils.repos.ReadCRUDRepo import dev.inmo.micro_utils.repos.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.pagination.getAll
import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline
@@ -40,6 +41,8 @@ value class ReadKeyValueFromCRUDRepo<Key, Value>(
} }
} }
override suspend fun getAll(): Map<Key, Value> = original.getAll()
override suspend fun count(): Long = original.count() override suspend fun count(): Long = original.count()
override suspend fun contains(key: Key): Boolean = original.contains(key) override suspend fun contains(key: Key): Boolean = original.contains(key)

View File

@@ -41,6 +41,8 @@ open class ReadKeyValueFromKeyValuesRepo<Key, Value>(
return original.contains(key) 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> { override suspend fun keys(v: List<Value>, pagination: Pagination, reversed: Boolean): PaginationResult<Key> {
val keys = mutableSetOf<Key>() val keys = mutableSetOf<Key>()

View File

@@ -4,6 +4,7 @@ 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.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.KeyValuesRepo import dev.inmo.micro_utils.repos.KeyValuesRepo
import dev.inmo.micro_utils.repos.set
import dev.inmo.micro_utils.repos.unset import dev.inmo.micro_utils.repos.unset
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@@ -60,6 +61,24 @@ open class KeyValuesFromKeyValueRepo<Key, Value, ValuesIterable : Iterable<Value
} }
} }
override suspend fun removeWithValue(v: Value) {
val toRemove = mutableMapOf<Key, List<Value>>()
doForAllWithNextPaging {
original.keys(it).also {
it.results.forEach {
val data = original.get(it) ?: return@forEach
if (v in data) {
toRemove[it] = listOf(v)
}
}
}
}
remove(toRemove)
}
override suspend fun add(toAdd: Map<Key, List<Value>>) { override suspend fun add(toAdd: Map<Key, List<Value>>) {
original.set( original.set(
toAdd.mapNotNull { (k, adding) -> toAdd.mapNotNull { (k, adding) ->

View File

@@ -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.*
@@ -94,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
} }

View File

@@ -32,6 +32,18 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
} }
} }
override suspend fun getAll(): Map<IdType, ObjectType> = helper.readableTransaction {
select(
tableName,
null,
""
).use {
it.map {
it.toId() to it.toObject()
}
}
}.toMap()
override suspend fun getById(id: IdType): ObjectType? = helper.readableTransaction { override suspend fun getById(id: IdType): ObjectType? = helper.readableTransaction {
select( select(
tableName, tableName,

View File

@@ -104,6 +104,15 @@ class KeyValueStore<T : Any> internal constructor (
override suspend fun contains(key: String): Boolean = sharedPreferences.contains(key) override suspend fun contains(key: String): Boolean = sharedPreferences.contains(key)
override suspend fun getAll(): Map<String, T> {
val resultMap = mutableMapOf<String, T>()
for ((k, v) in sharedPreferences.all) {
@Suppress("UNCHECKED_CAST")
resultMap[k] = (v as? T) ?: continue
}
return resultMap.toMap()
}
override suspend fun count(): Long = sharedPreferences.all.size.toLong() override suspend fun count(): Long = sharedPreferences.all.size.toLong()
override suspend fun set(toSet: Map<String, T>) { override suspend fun set(toSet: Map<String, T>) {

View File

@@ -6,6 +6,7 @@ import dev.inmo.micro_utils.common.mapNotNullA
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 dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.crud.asId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
@@ -260,6 +261,19 @@ class OneToManyAndroidRepo<Key, Value>(
_onValueRemoved.emit(k to v) _onValueRemoved.emit(k to v)
} }
} }
override suspend fun removeWithValue(v: Value) {
helper.blockingWritableTransaction {
val keys = select(tableName, idColumnArray, "$valueColumnName=?", arrayOf(v.valueAsString())).map {
it.asId.keyFromString()
}
keys.filter {
delete(tableName, "$idColumnName=? AND $valueColumnName=?", arrayOf(it.keyAsString(), v.valueAsString())) > 0
}
}.forEach { k ->
_onValueRemoved.emit(k to v)
}
}
} }
fun <Key, Value> OneToManyAndroidRepo( fun <Key, Value> OneToManyAndroidRepo(

View File

@@ -45,5 +45,9 @@ abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
select { selectById(id) }.limit(1).any() select { selectById(id) }.limit(1).any()
} }
override suspend fun getAll(): Map<IdType, ObjectType> = transaction(database) {
selectAll().associate { it.asId to it.asObject }
}
override suspend fun count(): Long = transaction(db = database) { selectAll().count() } override suspend fun count(): Long = transaction(db = database) { selectAll().count() }
} }

View File

@@ -5,7 +5,6 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.* import dev.inmo.micro_utils.repos.exposed.*
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedReadKeyValueRepo<Key, Value>( abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
@@ -32,6 +31,8 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
select { selectById(key) }.limit(1).any() select { selectById(key) }.limit(1).any()
} }
override suspend fun getAll(): Map<Key, Value> = transaction(database) { selectAll().associate { it.asKey to it.asObject } }
override suspend fun count(): Long = transaction(database) { selectAll().count() } override suspend fun count(): Long = transaction(database) { selectAll().count() }
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) { override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {

View File

@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value> typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value>
@@ -66,9 +67,36 @@ open class ExposedKeyValuesRepo<Key, Value>(
} }
} }
override suspend fun removeWithValue(v: Value) {
transaction(database) {
val keys = select { selectByValue(v) }.map { it.asKey }
deleteWhere { SqlExpressionBuilder.selectByValue(v) }
keys
}.forEach {
_onValueRemoved.emit(it to v)
}
}
override suspend fun clear(k: Key) { override suspend fun clear(k: Key) {
transaction(database) { transaction(database) {
deleteWhere { keyColumn.eq(k) } deleteWhere { keyColumn.eq(k) }
}.also { _onDataCleared.emit(k) } }.also { _onDataCleared.emit(k) }
} }
override suspend fun clearWithValue(v: Value) {
transaction(database) {
val toClear = select { selectByValue(v) }
.asSequence()
.map { it.asKey to it.asObject }
.groupBy { it.first }
.mapValues { it.value.map { it.second } }
deleteWhere { keyColumn.inList(toClear.keys) }
toClear
}.forEach {
it.value.forEach { v ->
_onValueRemoved.emit(it.key to v)
}
_onDataCleared.emit(it.key)
}
}
} }

View File

@@ -26,6 +26,8 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
override suspend fun contains(id: IdType): Boolean = map.containsKey(id) override suspend fun contains(id: IdType): Boolean = map.containsKey(id)
override suspend fun getAll(): Map<IdType, ObjectType> = map.toMap()
override suspend fun count(): Long = map.size.toLong() override suspend fun count(): Long = map.size.toLong()
} }

View File

@@ -51,6 +51,8 @@ class ReadMapKeyValueRepo<Key, Value>(
} }
} }
override suspend fun getAll(): Map<Key, Value> = map.toMap()
override suspend fun contains(key: Key): Boolean = map.containsKey(key) override suspend fun contains(key: Key): Boolean = map.containsKey(key)
override suspend fun count(): Long = map.size.toLong() override suspend fun count(): Long = map.size.toLong()

View File

@@ -87,13 +87,27 @@ class MapWriteKeyValuesRepo<Key, Value>(
} }
} }
override suspend fun removeWithValue(v: Value) {
map.forEach { (k, values) ->
if (values.remove(v)) {
_onValueRemoved.emit(k to v)
}
}
}
override suspend fun clear(k: Key) { override suspend fun clear(k: Key) {
map.remove(k) ?.also { _onDataCleared.emit(k) } map.remove(k) ?.also { _onDataCleared.emit(k) }
} }
override suspend fun clearWithValue(v: Value) { override suspend fun clearWithValue(v: Value) {
map.forEach { (k, values) -> map.filter { (_, values) ->
if (values.remove(v)) _onValueRemoved.emit(k to v) values.contains(v)
}.forEach {
map.remove(it.key) ?.onEach { v ->
_onValueRemoved.emit(it.key to v)
} ?.also { _ ->
_onDataCleared.emit(it.key)
}
} }
} }
} }

View File

@@ -44,6 +44,7 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
typeInfo<PaginationResult<ObjectType>>(), typeInfo<PaginationResult<ObjectType>>(),
typeInfo<PaginationResult<IdType>>(), typeInfo<PaginationResult<IdType>>(),
contentType, contentType,
typeInfo<Map<IdType, ObjectType>>(),
idSerializer idSerializer
), ),
KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>( KtorWriteCrudRepoClient<ObjectType, IdType, InputValue>(

View File

@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadCRUDRepo import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.countRouting import dev.inmo.micro_utils.repos.ktor.common.countRouting
import dev.inmo.micro_utils.repos.ktor.common.crud.* import dev.inmo.micro_utils.repos.ktor.common.crud.*
import dev.inmo.micro_utils.repos.ktor.common.getAllRoute
import dev.inmo.micro_utils.repos.ktor.common.idParameterName import dev.inmo.micro_utils.repos.ktor.common.idParameterName
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.call.body import io.ktor.client.call.body
@@ -21,6 +22,7 @@ class KtorReadCRUDRepoClient<ObjectType, IdType> (
private val paginationObjectType: TypeInfo, private val paginationObjectType: TypeInfo,
private val paginationIdType: TypeInfo, private val paginationIdType: TypeInfo,
private val contentType: ContentType, private val contentType: ContentType,
private val mapTypeInfo: TypeInfo,
private val idSerializer: suspend (IdType) -> String private val idSerializer: suspend (IdType) -> String
) : ReadCRUDRepo<ObjectType, IdType> { ) : ReadCRUDRepo<ObjectType, IdType> {
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = httpClient.get( override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = httpClient.get(
@@ -58,6 +60,15 @@ class KtorReadCRUDRepoClient<ObjectType, IdType> (
contentType(contentType) contentType(contentType)
}.body() }.body()
override suspend fun getAll(): Map<IdType, ObjectType> = httpClient.get(
buildStandardUrl(
baseUrl,
getAllRoute
)
) {
contentType(contentType)
}.body(mapTypeInfo)
override suspend fun count(): Long = httpClient.get( override suspend fun count(): Long = httpClient.get(
buildStandardUrl( buildStandardUrl(
baseUrl, baseUrl,
@@ -80,6 +91,7 @@ inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient(
typeInfo<PaginationResult<ObjectType>>(), typeInfo<PaginationResult<ObjectType>>(),
typeInfo<PaginationResult<IdType>>(), typeInfo<PaginationResult<IdType>>(),
contentType, contentType,
typeInfo<Map<IdType, ObjectType>>(),
idSerializer idSerializer
) )

View File

@@ -23,6 +23,7 @@ class KtorReadKeyValueRepoClient<Key, Value>(
private val objectType: TypeInfo, private val objectType: TypeInfo,
private val paginationResultObjectsTypeInfo: TypeInfo, private val paginationResultObjectsTypeInfo: TypeInfo,
private val paginationResultIdsTypeInfo: TypeInfo, private val paginationResultIdsTypeInfo: TypeInfo,
private val mapKeyValueTypeInfo: TypeInfo,
private val idSerializer: suspend (Key) -> String, private val idSerializer: suspend (Key) -> String,
private val valueSerializer: suspend (Value) -> String private val valueSerializer: suspend (Value) -> String
) : ReadKeyValueRepo<Key, Value> { ) : ReadKeyValueRepo<Key, Value> {
@@ -48,6 +49,15 @@ class KtorReadKeyValueRepoClient<Key, Value>(
contentType(contentType) contentType(contentType)
}.body() }.body()
override suspend fun getAll(): Map<Key, Value> = httpClient.get(
buildStandardUrl(
baseUrl,
getAllRoute
)
) {
contentType(contentType)
}.body(mapKeyValueTypeInfo)
override suspend fun values( override suspend fun values(
pagination: Pagination, pagination: Pagination,
reversed: Boolean reversed: Boolean
@@ -103,6 +113,7 @@ inline fun <reified Key, reified Value> KtorReadKeyValueRepoClient(
typeInfo<Value>(), typeInfo<Value>(),
typeInfo<PaginationResult<Value>>(), typeInfo<PaginationResult<Value>>(),
typeInfo<PaginationResult<Key>>(), typeInfo<PaginationResult<Key>>(),
typeInfo<Map<Key, Value>>(),
idSerializer, idSerializer,
valueSerializer valueSerializer
) )

View File

@@ -47,6 +47,17 @@ class KtorWriteKeyValuesRepoClient<Key : Any, Value : Any>(
}.throwOnUnsuccess { "Unable to remove $toRemove" } }.throwOnUnsuccess { "Unable to remove $toRemove" }
} }
@OptIn(InternalAPI::class)
override suspend fun removeWithValue(v: Value) {
httpClient.post(
buildStandardUrl(baseUrl, removeWithValueRoute)
) {
body = v
bodyType = valueTypeInfo
contentType(contentType)
}.throwOnUnsuccess { "Unable to remove $v" }
}
@OptIn(InternalAPI::class) @OptIn(InternalAPI::class)
override suspend fun clear(k: Key) { override suspend fun clear(k: Key) {
httpClient.post( httpClient.post(

View File

@@ -0,0 +1,3 @@
package dev.inmo.micro_utils.repos.ktor.common
const val getAllRoute = "getAll"

View File

@@ -13,6 +13,7 @@ const val onDataClearedRoute = "onDataCleared"
const val addRoute = "add" const val addRoute = "add"
const val removeRoute = "remove" const val removeRoute = "remove"
const val removeWithValueRoute = "removeWithValue"
const val clearRoute = "clear" const val clearRoute = "clear"
const val clearWithValueRoute = "clearWithValue" const val clearWithValueRoute = "clearWithValue"
const val setRoute = "set" const val setRoute = "set"

View File

@@ -83,6 +83,9 @@ class CRUDTests {
assertEquals(map.size, 0) assertEquals(map.size, 0)
assertEquals(map.size.toLong(), crudClient.count()) assertEquals(map.size.toLong(), crudClient.count())
assertEquals(0, crudClient.count()) assertEquals(0, crudClient.count())
assertEquals(map, crudClient.getAll())
server.stop() server.stop()
} }
} }

View File

@@ -133,6 +133,8 @@ class KVTests {
crudClient.count() crudClient.count()
) )
assertEquals(map, crudClient.getAll())
server.stop() server.stop()
} }
} }

View File

@@ -6,6 +6,7 @@ import dev.inmo.micro_utils.pagination.extractPagination
import dev.inmo.micro_utils.repos.ReadCRUDRepo import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ktor.common.countRouting import dev.inmo.micro_utils.repos.ktor.common.countRouting
import dev.inmo.micro_utils.repos.ktor.common.crud.* import dev.inmo.micro_utils.repos.ktor.common.crud.*
import dev.inmo.micro_utils.repos.ktor.common.getAllRoute
import dev.inmo.micro_utils.repos.ktor.common.idParameterName import dev.inmo.micro_utils.repos.ktor.common.idParameterName
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call import io.ktor.server.application.call
@@ -53,6 +54,10 @@ inline fun <reified ObjectType, reified IdType> Route.configureReadCRUDRepoRoute
) )
} }
get(getAllRoute) {
call.respond(originalRepo.getAll())
}
get(countRouting) { get(countRouting) {
call.respond( call.respond(
originalRepo.count() originalRepo.count()

View File

@@ -70,6 +70,10 @@ inline fun <reified Key, reified Value> Route.configureReadKeyValueRepoRoutes (
call.respond(originalRepo.contains(key)) call.respond(originalRepo.contains(key))
} }
get(getAllRoute) {
call.respond(originalRepo.getAll())
}
get(countRoute) { get(countRoute) {
call.respond(originalRepo.count()) call.respond(originalRepo.count())
} }

View File

@@ -46,6 +46,11 @@ inline fun <reified Key : Any, reified Value : Any> Route.configureWriteKeyValue
call.respond(HttpStatusCode.OK) call.respond(HttpStatusCode.OK)
} }
post(removeWithValueRoute) {
originalRepo.removeWithValue(call.receive())
call.respond(HttpStatusCode.OK)
}
post(clearRoute) { post(clearRoute) {
originalRepo.clear(call.receive()) originalRepo.clear(call.receive())
call.respond(HttpStatusCode.OK) call.respond(HttpStatusCode.OK)

View File

@@ -0,0 +1,7 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

@@ -0,0 +1,99 @@
package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.serializer
import kotlin.reflect.KClass
/**
* Will create [MapperSerializationStrategy] to allow you to map [O] to [I] using [serialize] lambda during
* serialization process
*/
inline fun <reified I : Any, O> SerializationStrategy<I>.mapSerialization(
noinline serialize: (O) -> I
) = MapperSerializationStrategy(
this,
serialize
)
/**
* Will create [MapperDeserializationStrategy] to allow you to map [I] to [O] using [deserialize] lambda during
* deserialization process
*/
inline fun <reified I : Any, O> DeserializationStrategy<I>.mapDeserialization(
noinline deserialize: (I) -> O
) = MapperDeserializationStrategy(
this,
deserialize
)
/**
* Will create [MapperSerializer] to allow you to map [O] to [I] and vice verse using [serialize]/[deserialize] lambda during
* serialization/deserialization process
*/
inline fun <reified I : Any, O> KSerializer<I>.mapFullSerialization(
noinline serialize: (O) -> I,
noinline deserialize: (I) -> O
) = MapperSerializer(
this,
serialize,
deserialize
)
/**
* Will create [MapperSerializationStrategy] to allow you to map [O] to [I] using [serialize] lambda during
* serialization process
*/
@OptIn(InternalSerializationApi::class)
inline fun <reified I : Any, O> KClass<I>.mapSerialization(
serializer: SerializationStrategy<I> = serializer(),
noinline serialize: (O) -> I
) = serializer.mapSerialization(serialize)
/**
* Will create [MapperDeserializationStrategy] to allow you to map [I] to [O] using [deserialize] lambda during
* deserialization process
*/
@OptIn(InternalSerializationApi::class)
inline fun <reified I : Any, O> KClass<I>.mapDeserialization(
serializer: DeserializationStrategy<I> = serializer(),
noinline deserialize: (I) -> O
) = serializer.mapDeserialization(deserialize)
/**
* Will create [MapperSerializer] to allow you to map [O] to [I] and vice verse using [serialize]/[deserialize] lambda during
* serialization/deserialization process
*/
@OptIn(InternalSerializationApi::class)
inline fun <reified I : Any, O> KClass<I>.mapFullSerialization(
serializer: KSerializer<I> = serializer(),
noinline serialize: (O) -> I,
noinline deserialize: (I) -> O
) = serializer.mapFullSerialization(serialize, deserialize)
/**
* Will create [MapperSerializationStrategy] to allow you to map [O] to [I] using [serialize] lambda during
* serialization process
*/
inline fun <reified I : Any, O> mappedSerializationStrategy(
noinline serialize: (O) -> I,
) = serializer<I>().mapSerialization(serialize)
/**
* Will create [MapperDeserializationStrategy] to allow you to map [I] to [O] using [deserialize] lambda during
* deserialization process
*/
inline fun <reified I : Any, O> mappedDeserializationStrategy(
noinline deserialize: (I) -> O
) = serializer<I>().mapDeserialization(deserialize)
/**
* Will create [MapperSerializer] to allow you to map [O] to [I] and vice verse using [serialize]/[deserialize] lambda during
* serialization/deserialization process
*/
inline fun <reified I : Any, O> mappedSerializer(
noinline serialize: (O) -> I,
noinline deserialize: (I) -> O
) = serializer<I>().mapFullSerialization(serialize, deserialize)

View File

@@ -0,0 +1,26 @@
package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Use this serializer when you have deserializable type [I] and want to map it to some [O] in process of
* deserialization
*
* @param base Serializer for [I]
* @param deserialize Will be used in [deserialize] method to convert deserialized by [base] [I] to [O]
*/
open class MapperDeserializationStrategy<I, O>(
private val base: DeserializationStrategy<I>,
private val deserialize: (I) -> O
) : DeserializationStrategy<O> {
override val descriptor: SerialDescriptor = base.descriptor
override fun deserialize(decoder: Decoder): O {
return deserialize(base.deserialize(decoder))
}
}

View File

@@ -0,0 +1,25 @@
package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Use this serializer when you have serializable type [I] and want to map it to some [O] in process of
* serialization
*
* @param base Serializer for [I]
* @param serialize Will be used in [serialize] method to convert incoming [O] to [I] and serialize with [base]
*/
open class MapperSerializationStrategy<I, O>(
private val base: SerializationStrategy<I>,
private val serialize: (O) -> I
) : SerializationStrategy<O> {
override val descriptor: SerialDescriptor = base.descriptor
override fun serialize(encoder: Encoder, value: O) {
base.serialize(encoder, serialize(value))
}
}

View File

@@ -0,0 +1,30 @@
package dev.inmo.micro_utils.serialization.mapper
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Use this serializer when you have serializable type [I] and want to map it to some [O] in process of
* serialization/deserialization
*
* @param base Serializer for [I]
* @param serialize Will be used in [serialize] method to convert incoming [O] to [I] and serialize with [base]
* @param deserialize Will be used in [deserialize] method to convert deserialized by [base] [I] to [O]
*/
open class MapperSerializer<I, O>(
private val base: KSerializer<I>,
private val serialize: (O) -> I,
private val deserialize: (I) -> O
) : KSerializer<O> {
override val descriptor: SerialDescriptor = base.descriptor
override fun deserialize(decoder: Decoder): O {
return deserialize(base.deserialize(decoder))
}
override fun serialize(encoder: Encoder, value: O) {
base.serialize(encoder, serialize(value))
}
}

View File

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

View File

@@ -37,6 +37,7 @@ String[] includes = [
":serialization:base64", ":serialization:base64",
":serialization:encapsulator", ":serialization:encapsulator",
":serialization:typed_serializer", ":serialization:typed_serializer",
":serialization:mapper",
":startup:plugin", ":startup:plugin",
":startup:launcher", ":startup:launcher",

View File

@@ -2,15 +2,17 @@ plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "application" id "application"
id "com.google.devtools.ksp"
} }
apply from: "$mppJsAndJavaProjectPresetPath" apply from: "$mppJvmJsLinuxMingwProjectPresetPath"
kotlin { kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api internalProject("micro_utils.startup.plugin") api internalProject("micro_utils.startup.plugin")
api internalProject("micro_utils.koin")
} }
} }
commonTest { commonTest {
@@ -29,3 +31,10 @@ java {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
dependencies {
add("kspCommonMainMetadata", project(":micro_utils.koin.generator"))
}
ksp {
}

View File

@@ -0,0 +1,38 @@
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
// TO REGENERATE IT JUST DELETE FILE
// ORIGINAL FILE: StartLauncherPlugin.kt
package dev.inmo.micro_utils.startup.launcher
import kotlin.Boolean
import kotlinx.serialization.json.Json
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 "baseJsonProvider"
*/
public val Scope.baseJsonProvider: Json?
get() = getOrNull(named("baseJsonProvider"))
/**
* @return Definition by key "baseJsonProvider"
*/
public val Koin.baseJsonProvider: Json?
get() = getOrNull(named("baseJsonProvider"))
/**
* Will register [definition] with [org.koin.core.module.Module.single] and key "baseJsonProvider"
*/
public fun Module.baseJsonProviderSingle(createdAtStart: Boolean = false, definition: Definition<Json>):
KoinDefinition<Json> = single(named("baseJsonProvider"), createdAtStart = createdAtStart, definition
= definition)
/**
* Will register [definition] with [org.koin.core.module.Module.factory] and key "baseJsonProvider"
*/
public fun Module.baseJsonProviderFactory(definition: Definition<Json>): KoinDefinition<Json> =
factory(named("baseJsonProvider"), definition = definition)

View File

@@ -1,17 +0,0 @@
package dev.inmo.micro_utils.startup.launcher
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.setupDI
import kotlinx.serialization.json.JsonObject
import org.koin.core.KoinApplication
/**
* Will create [KoinApplication], init, load modules using [StartLauncherPlugin] and start plugins using the same base
* plugin
*
* @param rawConfig It is expected that this [JsonObject] will contain serialized [Config] ([StartLauncherPlugin] will
* deserialize it in its [StartLauncherPlugin.setupDI]
*/
@Deprecated("Fully replaced with StartLauncherPlugin#start", ReplaceWith("StartLauncherPlugin.start(rawConfig)", "dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin"))
suspend fun start(rawConfig: JsonObject) {
StartLauncherPlugin.start(rawConfig)
}

View File

@@ -1,9 +1,11 @@
@file:GenerateKoinDefinition("baseJsonProvider", Json::class)
package dev.inmo.micro_utils.startup.launcher package dev.inmo.micro_utils.startup.launcher
import dev.inmo.kslog.common.i import dev.inmo.kslog.common.i
import dev.inmo.kslog.common.taggedLogger import dev.inmo.kslog.common.taggedLogger
import dev.inmo.kslog.common.w import dev.inmo.kslog.common.w
import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.setupDI import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.setupDI
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.startPlugin import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.startPlugin
import dev.inmo.micro_utils.startup.plugin.StartPlugin import dev.inmo.micro_utils.startup.plugin.StartPlugin
@@ -13,9 +15,10 @@ import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.SerialFormat import kotlinx.serialization.SerialFormat
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.modules.SerializersModule
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.KoinApplication import org.koin.core.KoinApplication
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
@@ -35,7 +38,20 @@ object StartLauncherPlugin : StartPlugin {
single { rawJsonObject } single { rawJsonObject }
single { config } single { config }
single { CoroutineScope(Dispatchers.Default) } single { CoroutineScope(Dispatchers.Default) }
single { defaultJson } binds arrayOf(StringFormat::class, SerialFormat::class) single {
val serializersModules = getAll<SerializersModule>().distinct()
val baseJson = baseJsonProvider ?: defaultJson
if (serializersModules.isEmpty()) {
baseJson
} else {
Json(baseJson) {
serializersModule = SerializersModule {
include(baseJson.serializersModule)
serializersModules.forEach { include(it) }
}
}
}
} binds arrayOf(StringFormat::class, SerialFormat::class)
includes( includes(
config.plugins.mapNotNull { config.plugins.mapNotNull {

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