Compare commits

..

127 Commits

Author SHA1 Message Date
d9c15db9de add compose submodule and add several functions in top of composition 2022-03-10 17:47:47 +06:00
cf2be8ed43 migration onto toml 2022-03-10 17:04:05 +06:00
97339c9b1d update wrapper version 2022-03-10 15:55:45 +06:00
eb35903de9 Update CHANGELOG.md 2022-03-10 15:20:42 +06:00
43e88e00da Update klock 2022-03-08 10:01:53 +06:00
1e51f9d77b Update CHANGELOG.md 2022-03-08 10:01:02 +06:00
dad738357c start 0.9.11 2022-03-08 10:00:19 +06:00
cb828ab3f2 Merge pull request #132 from InsanusMokrassar/0.9.10
0.9.10
2022-03-06 16:24:51 +06:00
aefaf4a8cc update klock (again) 2022-03-06 16:08:02 +06:00
b29f37a251 update klock 2022-03-04 20:20:17 +06:00
88854020ac extend functionality of websockets 2022-03-04 20:16:59 +06:00
14ebe01fc6 improvements in includeWebsocketHandling 2022-03-04 15:31:32 +06:00
771aed0f0f UnifiedRequester#createStandardWebsocketFlow(String,DeserializationStrategy) 2022-03-04 15:20:16 +06:00
16c57fcd6a start 0.9.10 2022-03-04 15:17:36 +06:00
75397a7ccb Merge pull request #131 from InsanusMokrassar/0.9.9
0.9.9
2022-02-24 14:44:13 +06:00
b05404f828 IntersectionObserver 2022-02-24 00:26:07 +06:00
edea942874 small refactor of operatons in applyDiff 2022-02-23 23:02:31 +06:00
8a8f568b9a add applyDiff in Diff Utils and update korlibs klock version 2022-02-23 22:55:56 +06:00
4dc8d30c52 start 0.9.9 2022-02-23 22:32:05 +06:00
cb4e08e823 Merge pull request #130 from InsanusMokrassar/0.9.8
0.9.8
2022-02-19 21:44:56 +06:00
c443bf4fa0 update dependencies 2022-02-19 16:15:36 +06:00
a6982de822 start 0.9.8 2022-02-19 16:06:45 +06:00
4f1a663e75 fixes in ExposedOneToManyKeyValueRepo and includeWebsocketHandling 2022-02-19 16:06:33 +06:00
dab262d626 start 0.9.7 2022-02-19 16:06:33 +06:00
87a3f61ca6 fix in ExposedOneToManyKeyValueRepo 2022-02-07 20:43:43 +06:00
506e937a68 start 0.9.6 2022-02-07 20:39:44 +06:00
5a037c76dd Merge pull request #126 from InsanusMokrassar/0.9.5
0.9.5
2022-02-01 23:30:26 +06:00
313f622f7e Update CHANGELOG.md 2022-02-01 14:27:07 +06:00
6cba1fe1a2 Update klock 2022-02-01 14:25:24 +06:00
fd2d0e80b7 start 0.9.5 2022-02-01 14:24:12 +06:00
96ab2e8aca Merge pull request #125 from InsanusMokrassar/0.9.4
0.9.4
2022-01-18 18:58:49 +06:00
0202988cae several fixes in optionallyReverse 2022-01-17 16:11:55 +06:00
d619d59947 Either updates 2022-01-17 15:56:48 +06:00
85b3e48d18 add optionallyReverse extensions 2022-01-17 15:38:23 +06:00
7a9b7d98a1 start 0.9.4 2022-01-14 13:22:04 +06:00
b212acfcaf Merge pull request #124 from InsanusMokrassar/0.9.3
0.9.3
2022-01-14 00:46:20 +06:00
3a45e5dc70 Update CHANGELOG.md 2022-01-14 00:37:03 +06:00
73190518d5 Update gradle.properties 2022-01-14 00:35:51 +06:00
03f78180dc Merge pull request #123 from InsanusMokrassar/0.9.2
0.9.2
2022-01-13 11:22:54 +06:00
1c0b8cf842 Update CHANGELOG.md 2022-01-13 10:20:35 +06:00
a1624ea2a9 Update gradle.properties 2022-01-13 10:19:08 +06:00
23a050cf1e Merge pull request #122 from InsanusMokrassar/0.9.1
0.9.1
2022-01-08 16:12:04 +06:00
916f2f96f4 typealiases for exposed one to many 2022-01-08 14:35:28 +06:00
00cc214754 repo exposed updates 2022-01-08 14:14:44 +06:00
b2e38f72b9 start 0.9.1 2022-01-08 14:12:57 +06:00
e7107d238d Update dokka_push.yml 2021-12-30 02:55:19 +06:00
ed9ebdbd1a Merge pull request #121 from InsanusMokrassar/0.9.0
0.9.0
2021-12-30 02:52:34 +06:00
e80676d3d2 Update packages_push.yml 2021-12-29 19:43:19 +06:00
02d02fa8f2 update android tools build 2021-12-29 19:24:25 +06:00
bd783fb74f update dokka 2021-12-29 17:44:45 +06:00
50386adf70 ignore kotlin-js-store folder 2021-12-28 21:19:47 +06:00
f4ee6c2890 update exposed and adapt to new version of kotlin serialization 2021-12-28 21:17:17 +06:00
d45aef9fe5 start 0.9.0 2021-12-28 09:58:59 +06:00
a56cd3dddd Merge pull request #120 from InsanusMokrassar/0.8.9
0.8.9
2021-12-27 17:02:16 +06:00
419e7070ee more fixes to god of fixes 2021-12-27 17:02:00 +06:00
612cf40b5f small hotfix 2021-12-27 16:00:07 +06:00
8b39882e83 fixes in DefaultUpdatableStatesMachine 2021-12-27 15:57:55 +06:00
e639ae172b Fixes in uniloadMultipart 2021-12-27 15:55:05 +06:00
d0446850ae start 0.8.9 2021-12-27 15:45:44 +06:00
c48465b90b Merge pull request #119 from InsanusMokrassar/0.8.8
0.8.8
2021-12-26 22:11:04 +06:00
f419fd03d2 small fix of performing state update for UpdatableStatesMachine 2021-12-26 22:00:26 +06:00
494812a660 one more fix 2021-12-26 21:25:12 +06:00
eb78f21eec fix FSMBuilder 2021-12-26 21:21:40 +06:00
4bda70268b small fix of style in DefaultUpdatableStatesMachine 2021-12-26 21:07:40 +06:00
f037ce4371 updates in FSM 2021-12-26 21:06:26 +06:00
3d2196e35d update dependencies (which possible) 2021-12-26 20:07:13 +06:00
a74f061b02 start 0.8.8 2021-12-26 20:02:18 +06:00
11ade14676 Merge pull request #118 from InsanusMokrassar/0.8.7
0.8.7
2021-12-15 01:22:42 +06:00
eb562d8784 add stream using for multipart 2021-12-14 22:03:29 +06:00
1ee5b4bfd4 hotfix for multipart 2021-12-14 21:13:11 +06:00
d97892080b add preview work with multipart 2021-12-14 18:01:41 +06:00
6f37125724 start 0.8.7 and make UnifiedRequester/UnifiedRouter fields are public 2021-12-14 13:25:58 +06:00
ed1baaade7 Merge pull request #117 from InsanusMokrassar/0.8.6
0.8.6
2021-12-08 15:57:43 +06:00
bb9669f8fd fill changelog 2021-12-08 11:45:01 +06:00
bdac715d48 remove crossinline where possible 2021-12-08 11:41:49 +06:00
acf4971298 downgrade ktor 2021-12-08 11:36:24 +06:00
249bc83a8c update ktor 2021-11-27 16:22:13 +06:00
0fbb92f03f start 0.8.6 2021-11-27 16:20:56 +06:00
ca27cb3f82 Merge pull request #116 from InsanusMokrassar/0.8.5
0.8.5
2021-11-27 01:00:18 +06:00
3a5771a0cc Update RepeatOnFailure.kt 2021-11-26 23:07:00 +06:00
527a2a91ac repeatOnFailure 2021-11-26 19:45:59 +06:00
6763e5c4c6 start 0.8.5 2021-11-26 19:32:52 +06:00
06918d8310 add mime types generator python script 2021-11-24 14:31:24 +06:00
89ccaa1b57 Merge pull request #115 from InsanusMokrassar/0.8.4
0.8.4
2021-11-19 14:02:38 +06:00
5d0bdb9bcf fix pagination 2021-11-19 13:58:59 +06:00
31fdcf74a5 ktor server createKtorServer extensions 2021-11-19 13:24:45 +06:00
afca09cc1d start 0.8.4 2021-11-19 13:18:37 +06:00
531d89d9db Merge pull request #114 from InsanusMokrassar/0.8.3
0.8.3
2021-11-19 09:49:35 +06:00
6bbbea0bc3 suppressions in Optional.kt 2021-11-18 23:03:24 +06:00
e337cd98c8 optional workaround 2021-11-17 21:31:35 +06:00
bcbab3b380 add Optional type 2021-11-17 17:38:41 +06:00
fb63de7568 intersect 2021-11-17 14:22:45 +06:00
aa45a4ab13 now Pagination is an ClosedRange 2021-11-17 14:07:11 +06:00
2af7e2f681 start 0.8.3 2021-11-17 14:01:44 +06:00
34fd9edce0 Merge pull request #113 from InsanusMokrassar/0.8.2
0.8.2
2021-11-12 13:23:39 +06:00
2a4cb8c5f9 improvements in FSM 2021-11-12 13:19:15 +06:00
50ea40bc3a add changelog for dependencies update 2021-11-12 13:14:57 +06:00
a77654052d Update gradle.properties 2021-11-11 21:20:17 +06:00
88aafce552 start 0.8.2 2021-11-11 20:31:04 +06:00
4e95d6bfff Merge pull request #112 from InsanusMokrassar/0.8.1
0.8.1
2021-11-09 14:04:11 +06:00
38d0e34fb5 updates in scripts and update core ktx 2021-11-09 14:03:51 +06:00
8fbc6b9041 update changelog 2021-11-09 13:43:46 +06:00
e8219d6cf4 start 0.8.1 2021-11-09 10:21:23 +06:00
6c20fc4ca6 Merge pull request #111 from InsanusMokrassar/0.8.0
0.8.0
2021-11-05 21:56:00 +06:00
85cd975492 preparations for release 2021-11-05 21:45:01 +06:00
1171a717fe optimize imports 2021-11-05 15:49:39 +06:00
bbe5320312 rework of FSM + 0.8.0 2021-11-05 15:43:05 +06:00
00acb9fddd add suppresses to the generated classes 2021-11-03 17:22:32 +06:00
de3d14dc41 solution of #109 2021-11-03 15:36:25 +06:00
67ff9cc9b3 update dependencies 2021-11-03 15:18:41 +06:00
af132103a0 start 0.7.5 and add either serializer 2021-11-02 12:43:59 +06:00
3b1124a804 fixes 2021-10-30 12:17:18 +06:00
f226c2dfd6 Merge pull request #106 from InsanusMokrassar/0.7.4
0.7.4
2021-10-29 13:57:58 +06:00
69d6e63846 getAllBy* 2021-10-29 13:51:59 +06:00
02c3d397ad solution of #104 2021-10-29 13:40:53 +06:00
67a1050646 solution for #105 2021-10-28 18:46:53 +06:00
8cd0775a6c update kdocs of Either 2021-10-28 18:42:05 +06:00
162294d6c6 either 2021-10-28 18:02:02 +06:00
c4dd19dd00 start 0.7.4 2021-10-28 17:50:28 +06:00
d2314422f1 Merge pull request #103 from InsanusMokrassar/0.7.3
0.7.3
2021-10-23 14:26:42 +06:00
6fedd6f859 update dependencies 2021-10-23 14:24:31 +06:00
e52b59665f start 0.7.3 2021-10-23 14:19:30 +06:00
cda9d09689 fixes for kdocs 2021-10-18 22:57:25 +06:00
c9237b3f00 Merge pull request #102 from InsanusMokrassar/0.7.2
0.7.2
2021-10-16 09:51:24 +06:00
18bba66c4a Update CHANGELOG.md 2021-10-16 08:56:38 +06:00
63418c4a8a Update gradle.properties 2021-10-16 08:55:44 +06:00
2e66c6f4e3 Merge pull request #101 from InsanusMokrassar/0.7.1
0.7.1
2021-10-13 15:11:26 +06:00
85 changed files with 2033 additions and 478 deletions

View File

@@ -10,10 +10,10 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 11
- name: Fix android 31.0.0 dx - name: Fix android 32.0.0 dx
continue-on-error: true continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/31.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Build - name: Build
run: ./gradlew dokkaHtml run: ./gradlew dokkaHtml
- name: Publish KDocs - name: Publish KDocs

View File

@@ -8,10 +8,10 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 11
- name: Fix android 31.0.0 dx - name: Fix android 32.0.0 dx
continue-on-error: true continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/31.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Rewrite version - name: Rewrite version
run: | run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`" branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"

1
.gitignore vendored
View File

@@ -11,5 +11,6 @@ out/
secret.gradle secret.gradle
local.properties local.properties
kotlin-js-store
publishing.sh publishing.sh

View File

@@ -1,5 +1,222 @@
# Changelog # Changelog
## 0.9.11
* `Versions`:
* `Klock`: `2.6.1` -> `2.6.2`
* `Coroutines`:
* `Compose`:
* Created :)
* New extensions and function:
* `Composition#linkWithJob`
* `Composition#linkWithContext`
* `renderComposableAndLinkToContext`
## 0.9.10
* `Versions`:
* `Klock`: `2.5.2` -> `2.6.1`
* Ktor:
* Client:
* New function `UnifiedRequester#createStandardWebsocketFlow` without `checkReconnection` arg
* Server:
* Now it is possible to filter data in `Route#includeWebsocketHandling`
* Callback in `Route#includeWebsocketHandling` and dependent methods is `suspend` since now
* Add `URLProtocol` support in `Route#includeWebsocketHandling` and dependent methods
## 0.9.9
* `Versions`:
* `Klock`: `2.5.1` -> `2.5.2`
* `Common`:
* Add new diff tool - `applyDiff`
* Implementation of `IntersectionObserver` in JS part (copypaste of [this](https://youtrack.jetbrains.com/issue/KT-43157#focus=Comments-27-4498582.0-0) comment)
## 0.9.8
* `Versions`:
* `Exposed`: `0.37.2` -> `0.37.3`
* `Klock`: `2.4.13` -> `2.5.1`
* `AppCompat`: `1.4.0` -> `1.4.1`
## 0.9.7
* `Repos`:
* `Exposed`:
* Fix in `ExposedOneToManyKeyValueRepo` - now it will not use `insertIgnore`
* `Ktor`:
* `Server`:
* `Route#includeWebsocketHandling` now will check that `WebSockets` feature and install it if not
## 0.9.6
* `Repos`:
* `Exposed`:
* Fix in `ExposedOneToManyKeyValueRepo` - now it will not use `deleteIgnoreWhere`
## 0.9.5
* `Versions`:
* `Klock`: `2.4.12` -> `2.4.13`
## 0.9.4
* `Pagination`:
* `Common`:
* Add several `optionallyReverse` functions
* `Common`:
* Changes in `Either`:
* Now `Either` uses `optionalT1` and `optionalT2` as main properties
* `Either#t1` and `Either#t2` are deprecated
* New extensions `Either#mapOnFirst` and `Either#mapOnSecond`
## 0.9.3
* `Versions`:
* `UUID`: `0.3.1` -> `0.4.0`
## 0.9.2
* `Versions`:
* `Klock`: `2.4.10` -> `2.4.12`
## 0.9.1
* `Repos`:
* `Exposed`:
* Default realizations of standard interfaces for exposed DB are using public fields for now:
* `ExposedReadKeyValueRepo`
* `ExposedReadOneToManyKeyValueRepo`
* `ExposedStandardVersionsRepoProxy`
* New typealiases for one to many exposed realizations:
* `ExposedReadKeyValuesRepo`
* `ExposedKeyValuesRepo`
## 0.9.0
* `Versions`:
* `Kotlin`: `1.5.31` -> `1.6.10`
* `Coroutines`: `1.5.2` -> `1.6.0`
* `Serialization`: `1.3.1` -> `1.3.2`
* `Exposed`: `0.36.2` -> `0.37.2`
* `Ktor`: `1.6.5` -> `1.6.7`
* `Klock`: `2.4.8` -> `2.4.10`
## 0.8.9
* `Ktor`:
* `Server`:
* Fixes in `uniloadMultipart`
* `Client`:
* Fixes in `unimultipart`
* `FSM`:
* Fixes in `DefaultUpdatableStatesMachine`
## 0.8.8
* `Versions`:
* `AppCompat`: `1.3.1` -> `1.4.0`
* Android Compile SDK: `31.0.0` -> `32.0.0`
* `FSM`:
* `DefaultStatesMachine` now is extendable
* New type `UpdatableStatesMachine` with default realization`DefaultUpdatableStatesMachine`
## 0.8.7
* `Ktor`:
* `Client`:
* `UnifiedRequester` now have no private fields
* Add preview work with multipart
* `Server`
* `UnifiedRouter` now have no private fields
* Add preview work with multipart
## 0.8.6
* `Common`:
* `Either` extensions `onFirst` and `onSecond` now accept not `crossinline` callbacks
* All `joinTo` now accept not `crossinline` callbacks
## 0.8.5
* `Common`:
* `repeatOnFailure`
## 0.8.4
* `Ktor`:
* `Server`:
* Several new `createKtorServer`
## 0.8.3
* `Common`:
* Ranges intersection functionality
* New type `Optional`
* `Pagination`:
* `Pagination` now extends `ClosedRange<Int>`
* `Pagination` intersection functionality
## 0.8.2
* `Versions`:
* `Klock`: `2.4.7` -> `2.4.8`
* `Serialization`: `1.3.0` -> `1.3.1`
* `FSM`:
* Now it is possible to pass any `CheckableHandlerHolder` in `FSMBuilder`
* Now `StatesMachine` works with `CheckableHandlerHolder` instead of `CustomizableHandlerHolder`
## 0.8.1
* `Versions`:
* `Exposed`: `0.36.1` -> `0.36.2`
* `Core KTX`: `1.6.0` -> `1.7.0`
## 0.8.0
* `Versions`:
* `Klock`: `2.4.6` -> `2.4.7`
* `Ktor`: `1.6.4` -> `1.6.5`
* `Exposed`: `0.35.3` -> `0.36.1`
* `Common`:
* Type `Either` got its own serializer
* `FSM`:
* `Common`:
* Full rework of FSM:
* Now it is more flexible for checking of handler opportunity to handle state
* Now machine and states managers are type-oriented
* `StateHandlerHolder` has been renamed to `CheckableHandlerHolder`
* Add opportunity for comfortable adding default state handler
## 0.7.4
* `Common`:
* New type `Either`
* `Serialization`:
* `TypedSerializer`
* New factory fun which accept vararg pairs of type and its serializer
* `Repos`:
* `Common` (`Android`):
* `AbstractMutableAndroidCRUDRepo` flows now will have extra buffer capacity instead of reply. It means that
android crud repo _WILL NOT_ send previous events to the
* `Exposed`:
* New parameter `AbstractExposedWriteCRUDRepo#replyCacheInFlows`
* KeyValue realization `ExposedKeyValueRepo` properties `_onNewValue` and `_onValueRemoved` now are available in
inheritors
* `Pagination`:
* `Common`:
* New types `getAllBy*` for current, next and custom paging
## 0.7.3
* `Versions`:
* `Exposed`: `0.35.2` -> `0.35.3`
## 0.7.2
* `Versions`:
* `Klock`: `2.4.5` -> `2.4.6`
## 0.7.1 ## 0.7.1
* `Versions`: * `Versions`:

View File

@@ -10,7 +10,7 @@ kotlin {
sourceSets { sourceSets {
androidMain { androidMain {
dependencies { dependencies {
api "androidx.appcompat:appcompat-resources:$appcompat_version" api libs.android.appCompat.resources
} }
} }
} }

View File

@@ -10,13 +10,13 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" api libs.kt.coroutines
api project(":micro_utils.common") api project(":micro_utils.common")
} }
} }
androidMain { androidMain {
dependencies { dependencies {
api "androidx.recyclerview:recyclerview:$androidx_recycler_version" api libs.android.recyclerView
} }
} }
} }

View File

@@ -7,12 +7,12 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.3' classpath libs.buildscript.kt.gradle
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath libs.buildscript.kt.serialization
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath libs.buildscript.jb.dokka
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version" classpath libs.buildscript.gh.release
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version" classpath libs.buildscript.android.gradle
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" classpath libs.buildscript.android.dexcount
} }
} }

View File

@@ -2,8 +2,6 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
import kotlin.jvm.JvmInline
private inline fun <T> getObject( private inline fun <T> getObject(
additional: MutableList<T>, additional: MutableList<T>,
iterator: Iterator<T> iterator: Iterator<T>
@@ -155,3 +153,22 @@ inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDif
inline fun <T> Iterable<T>.calculateStrictDiff( inline fun <T> Iterable<T>.calculateStrictDiff(
other: Iterable<T> other: Iterable<T>
) = calculateDiff(other, strictComparison = true) ) = calculateDiff(other, strictComparison = true)
/**
* This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
* mutable list
*/
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
strictComparison: Boolean = false
) = calculateDiff(source, strictComparison).let {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}
it.added.forEach { (i, t) ->
add(i, t)
}
it.replaced.forEach { (_, new) ->
set(new.index, new.value)
}
}

View File

@@ -0,0 +1,168 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
/**
* Realization of this interface will contains at least one not null - [optionalT1] or [optionalT2]
*
* @see EitherFirst
* @see EitherSecond
* @see Either.Companion.first
* @see Either.Companion.second
* @see Either.onFirst
* @see Either.onSecond
* @see Either.mapOnFirst
* @see Either.mapOnSecond
*/
@Serializable(EitherSerializer::class)
sealed interface Either<T1, T2> {
val optionalT1: Optional<T1>
val optionalT2: Optional<T2>
@Deprecated("Use optionalT1 instead", ReplaceWith("optionalT1"))
val t1: T1?
get() = optionalT1.dataOrNull()
@Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
val t2: T2?
get() = optionalT2.dataOrNull()
companion object {
fun <T1, T2> serializer(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
): KSerializer<Either<T1, T2>> = EitherSerializer(t1Serializer, t2Serializer)
}
}
class EitherSerializer<T1, T2>(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
) : KSerializer<Either<T1, T2>> {
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer",
SerialKind.CONTEXTUAL
) {
element("type", String.serializer().descriptor)
element("value", ContextualSerializer(Either::class).descriptor)
}
private val t1EitherSerializer = EitherFirst.serializer(t1Serializer, t2Serializer)
private val t2EitherSerializer = EitherSecond.serializer(t1Serializer, t2Serializer)
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
override fun deserialize(decoder: Decoder): Either<T1, T2> {
return decoder.decodeStructure(descriptor) {
var type: String? = null
lateinit var result: Either<T1, T2>
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> type = decodeStringElement(descriptor, 0)
1 -> {
result = when (type) {
"t1" -> decodeSerializableElement(
descriptor,
1,
t1EitherSerializer
)
"t2" -> decodeSerializableElement(
descriptor,
1,
t2EitherSerializer
)
else -> error("Unknown type of either: $type")
}
}
CompositeDecoder.DECODE_DONE -> break
else -> error("Unexpected index: $index")
}
}
result
}
}
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
override fun serialize(encoder: Encoder, value: Either<T1, T2>) {
encoder.encodeStructure(descriptor) {
when (value) {
is EitherFirst -> {
encodeStringElement(descriptor, 0, "t1")
encodeSerializableElement(descriptor, 1, t1EitherSerializer, value)
}
is EitherSecond -> {
encodeStringElement(descriptor, 0, "t2")
encodeSerializableElement(descriptor, 1, t2EitherSerializer, value)
}
}
}
}
}
/**
* This type [Either] will always have not nullable [optionalT1]
*/
@Serializable
data class EitherFirst<T1, T2>(
override val t1: T1
) : Either<T1, T2> {
override val optionalT1: Optional<T1> = t1.optional
override val optionalT2: Optional<T2> = Optional.absent()
}
/**
* This type [Either] will always have not nullable [optionalT2]
*/
@Serializable
data class EitherSecond<T1, T2>(
override val t2: T2
) : Either<T1, T2> {
override val optionalT1: Optional<T1> = Optional.absent()
override val optionalT2: Optional<T2> = t2.optional
}
/**
* @return New instance of [EitherFirst]
*/
inline fun <T1, T2> Either.Companion.first(t1: T1): Either<T1, T2> = EitherFirst(t1)
/**
* @return New instance of [EitherSecond]
*/
inline fun <T1, T2> Either.Companion.second(t2: T2): Either<T1, T2> = EitherSecond(t2)
/**
* Will call [block] in case when [this] is [EitherFirst]
*/
inline fun <T1, T2, E : Either<T1, T2>> E.onFirst(block: (T1) -> Unit): E {
optionalT1.onPresented(block)
return this
}
/**
* Will call [block] in case when [this] is [EitherSecond]
*/
inline fun <T1, T2, E : Either<T1, T2>> E.onSecond(block: (T2) -> Unit): E {
optionalT2.onPresented(block)
return this
}
/**
* @return Result of [block] if [this] is [EitherFirst]
*/
inline fun <T1, R> Either<T1, *>.mapOnFirst(block: (T1) -> R): R? {
return optionalT1.mapOnPresented(block)
}
/**
* @return Result of [block] if [this] is [EitherSecond]
*/
inline fun <T2, R> Either<*, T2>.mapOnSecond(block: (T2) -> R): R? {
return optionalT2.mapOnPresented(block)
}
inline fun <reified T1, reified T2> Any.either() = when (this) {
is T1 -> Either.first<T1, T2>(this)
is T2 -> Either.second<T1, T2>(this)
else -> error("Incorrect type of either argument $this")
}

View File

@@ -1,10 +1,10 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
inline fun <I, R> Iterable<I>.joinTo( inline fun <I, R> Iterable<I>.joinTo(
crossinline separatorFun: (I) -> R?, separatorFun: (I) -> R?,
prefix: R? = null, prefix: R? = null,
postfix: R? = null, postfix: R? = null,
crossinline transform: (I) -> R? transform: (I) -> R?
): List<R> { ): List<R> {
val result = mutableListOf<R>() val result = mutableListOf<R>()
val iterator = iterator() val iterator = iterator()
@@ -29,11 +29,11 @@ inline fun <I, R> Iterable<I>.joinTo(
separator: R? = null, separator: R? = null,
prefix: R? = null, prefix: R? = null,
postfix: R? = null, postfix: R? = null,
crossinline transform: (I) -> R? transform: (I) -> R?
): List<R> = joinTo({ separator }, prefix, postfix, transform) ): List<R> = joinTo({ separator }, prefix, postfix, transform)
inline fun <I> Iterable<I>.joinTo( inline fun <I> Iterable<I>.joinTo(
crossinline separatorFun: (I) -> I?, separatorFun: (I) -> I?,
prefix: I? = null, prefix: I? = null,
postfix: I? = null postfix: I? = null
): List<I> = joinTo<I, I>(separatorFun, prefix, postfix) { it } ): List<I> = joinTo<I, I>(separatorFun, prefix, postfix) { it }
@@ -45,15 +45,15 @@ inline fun <I> Iterable<I>.joinTo(
): List<I> = joinTo<I>({ separator }, prefix, postfix) ): List<I> = joinTo<I>({ separator }, prefix, postfix)
inline fun <I, reified R> Array<I>.joinTo( inline fun <I, reified R> Array<I>.joinTo(
crossinline separatorFun: (I) -> R?, separatorFun: (I) -> R?,
prefix: R? = null, prefix: R? = null,
postfix: R? = null, postfix: R? = null,
crossinline transform: (I) -> R? transform: (I) -> R?
): Array<R> = asIterable().joinTo(separatorFun, prefix, postfix, transform).toTypedArray() ): Array<R> = asIterable().joinTo(separatorFun, prefix, postfix, transform).toTypedArray()
inline fun <I, reified R> Array<I>.joinTo( inline fun <I, reified R> Array<I>.joinTo(
separator: R? = null, separator: R? = null,
prefix: R? = null, prefix: R? = null,
postfix: R? = null, postfix: R? = null,
crossinline transform: (I) -> R? transform: (I) -> R?
): Array<R> = asIterable().joinTo(separator, prefix, postfix, transform).toTypedArray() ): Array<R> = asIterable().joinTo(separator, prefix, postfix, transform).toTypedArray()

View File

@@ -23,11 +23,12 @@ value class FileName(val string: String) {
} }
@PreviewFeature
expect class MPPFile expect class MPPFile
expect val MPPFile.filename: FileName expect val MPPFile.filename: FileName
expect val MPPFile.filesize: Long expect val MPPFile.filesize: Long
expect val MPPFile.bytesAllocatorSync: ByteArrayAllocator
expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator expect val MPPFile.bytesAllocator: SuspendByteArrayAllocator
fun MPPFile.bytesSync() = bytesAllocatorSync()
suspend fun MPPFile.bytes() = bytesAllocator() suspend fun MPPFile.bytes() = bytesAllocator()

View File

@@ -0,0 +1,92 @@
@file:Suppress("unused")
package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
/**
* This type represents [T] as not only potentially nullable data, but also as a data which can not be presented. This
* type will be useful in cases when [T] is nullable and null as valuable data too in time of data absence should be
* presented by some third type.
*
* Let's imagine, you have nullable name in some database. In case when name is not nullable everything is clear - null
* will represent absence of row in the database. In case when name is nullable null will be a little bit dual-meaning,
* cause this null will say nothing about availability of the row (of course, it is exaggerated example)
*
* @see Optional.presented
* @see Optional.absent
* @see Optional.optional
* @see Optional.onPresented
* @see Optional.onAbsent
*/
@Serializable
data class Optional<T> internal constructor(
@Warning("It is unsafe to use this data directly")
val data: T?,
@Warning("It is unsafe to use this data directly")
val dataPresented: Boolean
) {
companion object {
/**
* Will create [Optional] with presented data
*/
fun <T> presented(data: T) = Optional(data, true)
/**
* Will create [Optional] without data
*/
fun <T> absent() = Optional<T>(null, false)
}
}
inline val <T> T.optional
get() = Optional.presented(this)
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
}
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
}
/**
* Will call [block] when data absent ([Optional.dataPresented] == false)
*/
inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
if (!dataPresented) { block() }
}
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
}
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
*/
fun <T> Optional<T>.dataOrNull() = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise
*/
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()

View File

@@ -0,0 +1,19 @@
package dev.inmo.micro_utils.common
fun <T : Comparable<T>> ClosedRange<T>.intersect(other: ClosedRange<T>): Pair<T, T>? = when {
start == other.start && endInclusive == other.endInclusive -> start to endInclusive
start > other.endInclusive || other.start > endInclusive -> null
else -> maxOf(start, other.start) to minOf(endInclusive, other.endInclusive)
}
fun IntRange.intersect(
other: IntRange
): IntRange? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
it.first .. it.second
}
fun LongRange.intersect(
other: LongRange
): LongRange? = (this as ClosedRange<Long>).intersect(other as ClosedRange<Long>) ?.let {
it.first .. it.second
}

View File

@@ -0,0 +1,21 @@
package dev.inmo.micro_utils.common
/**
* Executes the given [action] until getting of successful result specified number of [times].
*
* A zero-based index of current iteration is passed as a parameter to [action].
*/
inline fun <R> repeatOnFailure(
times: Int,
onEachFailure: (Throwable) -> Unit = {},
action: (Int) -> R
): Optional<R> {
repeat(times) {
runCatching {
action(it)
}.onFailure(onEachFailure).onSuccess {
return Optional.presented(it)
}
}
return Optional.absent()
}

View File

@@ -54,7 +54,7 @@ class DiffUtilsTests {
val oldList = (0 until 10).map { it.toString() } val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex() val withIndex = oldList.withIndex()
for (step in 0 until oldList.size) { for (step in oldList.indices) {
for ((i, v) in withIndex) { for ((i, v) in withIndex) {
val mutable = oldList.toMutableList() val mutable = oldList.toMutableList()
val changes = ( val changes = (
@@ -73,4 +73,78 @@ class DiffUtilsTests {
} }
} }
} }
@Test
fun testThatSimpleRemoveApplyWorks() {
val oldList = (0 until 10).toList()
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
val removedSublist = oldList.subList(i, i + count)
val mutableOldList = oldList.toMutableList()
val targetList = oldList - removedSublist
mutableOldList.applyDiff(targetList)
assertEquals(
targetList,
mutableOldList
)
}
}
}
@Test
fun testThatSimpleAddApplyWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
val addedSublist = oldList.subList(i, i + count).map { "added$it" }
val mutable = oldList.toMutableList()
mutable.addAll(i, addedSublist)
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(
mutable,
mutableOldList
)
}
}
}
@Test
fun testThatSimpleChangesApplyWorks() {
val oldList = (0 until 10).map { it.toString() }
val withIndex = oldList.withIndex()
for (step in oldList.indices) {
for ((i, v) in withIndex) {
val mutable = oldList.toMutableList()
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step)
).map { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value
}
}
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(
mutable,
mutableOldList
)
}
}
}
} }

View File

@@ -0,0 +1,124 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.DOMRectReadOnly
import org.w3c.dom.Element
external interface IntersectionObserverOptions {
/**
* An Element or Document object which is an ancestor of the intended target, whose bounding rectangle will be
* considered the viewport. Any part of the target not visible in the visible area of the root is not considered
* visible.
*/
var root: Element?
/**
* A string which specifies a set of offsets to add to the root's bounding_box when calculating intersections,
* effectively shrinking or growing the root for calculation purposes. The syntax is approximately the same as that
* for the CSS margin property; see The root element and root margin in Intersection Observer API for more
* information on how the margin works and the syntax. The default is "0px 0px 0px 0px".
*/
var rootMargin: String?
/**
* Either a single number or an array of numbers between 0.0 and 1.0, specifying a ratio of intersection area to
* total bounding box area for the observed target. A value of 0.0 means that even a single visible pixel counts as
* the target being visible. 1.0 means that the entire target element is visible. See Thresholds in Intersection
* Observer API for a more in-depth description of how thresholds are used. The default is a threshold of 0.0.
*/
var threshold: Array<Number>?
}
fun IntersectionObserverOptions(
block: IntersectionObserverOptions.() -> Unit = {}
): IntersectionObserverOptions = js("{}").unsafeCast<IntersectionObserverOptions>().apply(block)
external interface IntersectionObserverEntry {
/**
* Returns the bounds rectangle of the target element as a DOMRectReadOnly. The bounds are computed as described in
* the documentation for Element.getBoundingClientRect().
*/
val boundingClientRect: DOMRectReadOnly
/**
* Returns the ratio of the intersectionRect to the boundingClientRect.
*/
val intersectionRatio: Number
/**
* Returns a DOMRectReadOnly representing the target's visible area.
*/
val intersectionRect: DOMRectReadOnly
/**
* A Boolean value which is true if the target element intersects with the intersection observer's root. If this is
* true, then, the IntersectionObserverEntry describes a transition into a state of intersection; if it's false,
* then you know the transition is from intersecting to not-intersecting.
*/
val isIntersecting: Boolean
/**
* Returns a DOMRectReadOnly for the intersection observer's root.
*/
val rootBounds: DOMRectReadOnly
/**
* The Element whose intersection with the root changed.
*/
val target: Element
/**
* A DOMHighResTimeStamp indicating the time at which the intersection was recorded, relative to the
* IntersectionObserver's time origin.
*/
val time: Double
}
typealias IntersectionObserverCallback = (entries: Array<IntersectionObserverEntry>, observer: IntersectionObserver) -> Unit
/**
* This is just an implementation from [this commentary](https://youtrack.jetbrains.com/issue/KT-43157#focus=Comments-27-4498582.0-0)
* of Kotlin JS issue related to the absence of [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver)
*/
external class IntersectionObserver(callback: IntersectionObserverCallback) {
constructor(callback: IntersectionObserverCallback, options: IntersectionObserverOptions)
/**
* The Element or Document whose bounds are used as the bounding box when testing for intersection. If no root value
* was passed to the constructor or its value is null, the top-level document's viewport is used.
*/
val root: Element
/**
* An offset rectangle applied to the root's bounding box when calculating intersections, effectively shrinking or
* growing the root for calculation purposes. The value returned by this property may not be the same as the one
* specified when calling the constructor as it may be changed to match internal requirements. Each offset can be
* expressed in pixels (px) or as a percentage (%). The default is "0px 0px 0px 0px".
*/
val rootMargin: String
/**
* A list of thresholds, sorted in increasing numeric order, where each threshold is a ratio of intersection area to
* bounding box area of an observed target. Notifications for a target are generated when any of the thresholds are
* crossed for that target. If no value was passed to the constructor, 0 is used.
*/
val thresholds: Array<Number>
/**
* Stops the IntersectionObserver object from observing any target.
*/
fun disconnect()
/**
* Tells the IntersectionObserver a target element to observe.
*/
fun observe(targetElement: Element)
/**
* Returns an array of IntersectionObserverEntry objects for all observed targets.
*/
fun takeRecords(): Array<IntersectionObserverEntry>
/**
* Tells the IntersectionObserver to stop observing a particular target element.
*/
fun unobserve(targetElement: Element)
}

View File

@@ -2,10 +2,12 @@ package dev.inmo.micro_utils.common
import org.khronos.webgl.ArrayBuffer import org.khronos.webgl.ArrayBuffer
import org.w3c.dom.ErrorEvent import org.w3c.dom.ErrorEvent
import org.w3c.files.File import org.w3c.files.*
import org.w3c.files.FileReader
import kotlin.js.Promise import kotlin.js.Promise
/**
* @suppress
*/
actual typealias MPPFile = File actual typealias MPPFile = File
fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure -> fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
@@ -21,12 +23,32 @@ fun MPPFile.readBytesPromise() = Promise<ByteArray> { success, failure ->
reader.readAsArrayBuffer(this) reader.readAsArrayBuffer(this)
} }
fun MPPFile.readBytes(): ByteArray {
val reader = FileReaderSync()
return reader.readAsArrayBuffer(this).toByteArray()
}
private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await() private suspend fun MPPFile.dirtyReadBytes(): ByteArray = readBytesPromise().await()
/**
* @suppress
*/
actual val MPPFile.filename: FileName actual val MPPFile.filename: FileName
get() = FileName(name) get() = FileName(name)
/**
* @suppress
*/
actual val MPPFile.filesize: Long actual val MPPFile.filesize: Long
get() = size.toLong() get() = size.toLong()
/**
* @suppress
*/
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = ::readBytes
/**
* @suppress
*/
@Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can") @Warning("That is not optimized version of bytes allocator. Use asyncBytesAllocator everywhere you can")
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = ::dirtyReadBytes get() = ::dirtyReadBytes

View File

@@ -4,12 +4,29 @@ import dev.inmo.micro_utils.coroutines.doInIO
import dev.inmo.micro_utils.coroutines.doOutsideOfCoroutine import dev.inmo.micro_utils.coroutines.doOutsideOfCoroutine
import java.io.File import java.io.File
/**
* @suppress
*/
actual typealias MPPFile = File actual typealias MPPFile = File
/**
* @suppress
*/
actual val MPPFile.filename: FileName actual val MPPFile.filename: FileName
get() = FileName(name) get() = FileName(name)
/**
* @suppress
*/
actual val MPPFile.filesize: Long actual val MPPFile.filesize: Long
get() = length() get() = length()
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = ::readBytes
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = { get() = {
doInIO { doInIO {

View File

@@ -10,12 +10,12 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" api libs.kt.coroutines
} }
} }
androidMain { androidMain {
dependencies { dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" api libs.kt.coroutines.android
} }
} }
} }

View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api libs.kt.coroutines
}
}
}
}

View File

@@ -0,0 +1,14 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import kotlinx.coroutines.Job
import kotlinx.coroutines.job
import kotlin.coroutines.CoroutineContext
fun Composition.linkWithJob(job: Job) {
job.invokeOnCompletion {
this@linkWithJob.dispose()
}
}
fun Composition.linkWithContext(coroutineContext: CoroutineContext) = linkWithJob(coroutineContext.job)

View File

@@ -0,0 +1,16 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import kotlinx.coroutines.*
import org.jetbrains.compose.web.dom.DOMScope
import org.w3c.dom.Element
suspend fun <TElement : Element> renderComposableAndLinkToContext(
root: TElement,
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
content: @Composable DOMScope<TElement>.() -> Unit
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
linkWithContext(
currentCoroutineContext()
)
}

View File

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

View File

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

View File

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

View File

@@ -26,12 +26,12 @@ ext {
} }
android { android {
compileSdkVersion "$android_compileSdkVersion".toInteger() compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
buildToolsVersion "$android_buildToolsVersion" buildToolsVersion libs.versions.android.props.buildTools.get()
defaultConfig { defaultConfig {
minSdkVersion "$android_minSdkVersion".toInteger() minSdkVersion libs.versions.android.props.minSdk.get().toInteger()
targetSdkVersion "$android_compileSdkVersion".toInteger() targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
versionCode "${android_code_version}".toInteger() versionCode "${android_code_version}".toInteger()
versionName "$version" versionName "$version"
} }

View File

@@ -13,10 +13,10 @@ repositories {
kotlin { kotlin {
jvm() jvm()
js(IR) { // js(IR) {
browser() // browser()
nodejs() // nodejs()
} // }
android {} android {}
sourceSets { sourceSets {
@@ -29,7 +29,7 @@ kotlin {
it != project it != project
&& it.hasProperty("kotlin") && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") } // && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jvmMain") } && it.kotlin.sourceSets.any { it.name.contains("jvmMain") }
&& it.kotlin.sourceSets.any { it.name.contains("androidMain") } && it.kotlin.sourceSets.any { it.name.contains("androidMain") }
) { ) {
@@ -38,22 +38,22 @@ kotlin {
} }
} }
} }
jsMain { // jsMain {
dependencies { // dependencies {
implementation kotlin('stdlib') // implementation kotlin('stdlib')
project.parent.subprojects.forEach { // project.parent.subprojects.forEach {
if ( // if (
it != project // it != project
&& it.hasProperty("kotlin") // && it.hasProperty("kotlin")
&& it.kotlin.sourceSets.any { it.name.contains("commonMain") } // && it.kotlin.sourceSets.any { it.name.contains("commonMain") }
&& it.kotlin.sourceSets.any { it.name.contains("jsMain") } // && it.kotlin.sourceSets.any { it.name.contains("jsMain") }
) { // ) {
api it // api it
} // }
} // }
} // }
} // }
jvmMain { jvmMain {
dependencies { dependencies {
implementation kotlin('stdlib') implementation kotlin('stdlib')
@@ -116,9 +116,9 @@ tasks.dokkaHtml {
sourceRoots.setFrom(findSourcesWithName("commonMain")) sourceRoots.setFrom(findSourcesWithName("commonMain"))
} }
named("jsMain") { // named("jsMain") {
sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain")) // sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
} // }
named("jvmMain") { named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain")) sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))

View File

@@ -21,6 +21,7 @@ allprojects {
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true" releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"

View File

@@ -10,6 +10,7 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api project(":micro_utils.common")
api project(":micro_utils.coroutines") api project(":micro_utils.coroutines")
} }
} }

View File

@@ -0,0 +1,81 @@
package dev.inmo.micro_utils.fsm.common
import kotlin.reflect.KClass
/**
* Define checkable holder which can be used to precheck that this handler may handle incoming [State]
*/
interface CheckableHandlerHolder<I : State, O : State> : StatesHandler<I, O> {
suspend fun checkHandleable(state: O): Boolean
}
/**
* Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class
* casting in [handleState]
*/
class CustomizableHandlerHolder<I : O, O : State>(
private val delegateTo: StatesHandler<I, O>,
private val filter: suspend (state: O) -> Boolean
) : CheckableHandlerHolder<I, O> {
/**
* Checks that [state] can be handled by [delegateTo]. Under the hood it will check exact equality of [state]
* [KClass] and use [KClass.isInstance] of [inputKlass] if [strict] == false
*/
override suspend fun checkHandleable(state: O) = filter(state)
/**
* Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable]
* to be sure that this [StatesHandlerHolder] will be able to handle [state]
*/
override suspend fun StatesMachine<in O>.handleState(state: I): O? {
return delegateTo.run { handleState(state) }
}
}
fun <I : O, O : State> CheckableHandlerHolder(
inputKlass: KClass<I>,
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CustomizableHandlerHolder(
StatesHandler<O, O> {
delegateTo.run { handleState(it as I) }
},
if (strict) {
{ it::class == inputKlass }
} else {
{ inputKlass.isInstance(it) }
}
)
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
fun <I : O, O : State> StateHandlerHolder(
inputKlass: KClass<I>,
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(inputKlass, strict, delegateTo)
inline fun <reified I : O, O : State> CheckableHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(I::class, strict, delegateTo)
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
inline fun <reified I : O, O : State> StateHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(strict, delegateTo)
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
strict: Boolean = true
) = CheckableHandlerHolder<I, O>(
I::class,
strict,
this
)
inline fun <I : O, O: State> StatesHandler<I, O>.holder(
noinline filter: suspend (state: State) -> Boolean
) = CustomizableHandlerHolder<O, O>(
{ this@holder.run { handleState(it as I) } },
filter
)

View File

@@ -1,6 +0,0 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
@Deprecated("Replaced", ReplaceWith("InMemoryStatesManager", "dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager"))
typealias InMemoryStatesManager = InMemoryStatesManager

View File

@@ -1,27 +0,0 @@
package dev.inmo.micro_utils.fsm.common
import kotlin.reflect.KClass
/**
* Default realization of [StatesHandler]. It will incapsulate checking of [State] type in [checkHandleable] and class
* casting in [handleState]
*/
class StateHandlerHolder<I : State>(
private val inputKlass: KClass<I>,
private val strict: Boolean = false,
private val delegateTo: StatesHandler<I>
) : StatesHandler<State> {
/**
* Checks that [state] can be handled by [delegateTo]. Under the hood it will check exact equality of [state]
* [KClass] and use [KClass.isInstance] of [inputKlass] if [strict] == false
*/
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
/**
* Calls [delegateTo] method [StatesHandler.handleState] with [state] casted to [I]. Use [checkHandleable]
* to be sure that this [StateHandlerHolder] will be able to handle [state]
*/
override suspend fun StatesMachine.handleState(state: State): State? {
return delegateTo.run { handleState(state as I) }
}
}

View File

@@ -3,10 +3,10 @@ package dev.inmo.micro_utils.fsm.common
/** /**
* Default realization of states handler * Default realization of states handler
*/ */
fun interface StatesHandler<I : State> { fun interface StatesHandler<I : State, O: State> {
/** /**
* Main handling of [state]. In case when this [state] leads to another [State] and [handleState] returns not null * Main handling of [state]. In case when this [state] leads to another [State] and [handleState] returns not null
* [State] it is assumed that chain is not completed. * [State] it is assumed that chain is not completed.
*/ */
suspend fun StatesMachine.handleState(state: I): State? suspend fun StatesMachine<in O>.handleState(state: I): O?
} }

View File

@@ -1,24 +1,27 @@
package dev.inmo.micro_utils.fsm.common package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.* import dev.inmo.micro_utils.coroutines.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
private suspend fun <I : State> StatesMachine.launchStateHandling(
state: State,
handlers: List<StateHandlerHolder<out I>>
): State? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}
/** /**
* Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use * Default [StatesMachine] may [startChain] and use inside logic for handling [State]s. By default you may use
* [DefaultStatesMachine] or build it with [dev.inmo.micro_utils.fsm.common.dsl.buildFSM]. Implementers MUST NOT start * [DefaultStatesMachine] or build it with [dev.inmo.micro_utils.fsm.common.dsl.buildFSM]. Implementers MUST NOT start
* handling until [start] method will be called * handling until [start] method will be called
*/ */
interface StatesMachine : StatesHandler<State> { interface StatesMachine<T : State> : StatesHandler<T, T> {
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>
): T? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}
/** /**
* Starts handling of [State]s * Starts handling of [State]s
*/ */
@@ -27,31 +30,67 @@ interface StatesMachine : StatesHandler<State> {
/** /**
* Start chain of [State]s witn [state] * Start chain of [State]s witn [state]
*/ */
suspend fun startChain(state: State) suspend fun startChain(state: T)
companion object { companion object {
/** /**
* Creates [DefaultStatesMachine] * Creates [DefaultStatesMachine]
*/ */
operator fun invoke( operator fun <T: State> invoke(
statesManager: StatesManager, statesManager: StatesManager<T>,
handlers: List<StateHandlerHolder<*>> handlers: List<CheckableHandlerHolder<in T, T>>
) = DefaultStatesMachine(statesManager, handlers) ) = DefaultStatesMachine(statesManager, handlers)
} }
} }
/** /**
* Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts * Default realization of [StatesMachine]. It uses [statesManager] for incapsulation of [State]s storing and contexts
* resolving, and uses [launchStateHandling] for [State] handling * resolving, and uses [launchStateHandling] for [State] handling.
*
* This class suppose to be extended in case you wish some custom behaviour inside of [launchStateHandling], for example
*/ */
class DefaultStatesMachine ( open class DefaultStatesMachine <T: State>(
private val statesManager: StatesManager, protected val statesManager: StatesManager<T>,
private val handlers: List<StateHandlerHolder<*>> protected val handlers: List<CheckableHandlerHolder<in T, T>>,
) : StatesMachine { ) : StatesMachine<T> {
/** /**
* Will call [launchStateHandling] for state handling * Will call [launchStateHandling] for state handling
*/ */
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers) override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
/**
* This
*/
protected val statesJobs = mutableMapOf<T, Job>()
protected val statesJobsMutex = Mutex()
protected open suspend fun performUpdate(state: T) {
val newState = launchStateHandling(state, handlers)
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
open suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
statesJobs[actualState] ?.cancel()
statesJobs[actualState] = scope.launch {
performUpdate(actualState)
}.also { job ->
job.invokeOnCompletion { _ ->
scope.launch {
statesJobsMutex.withLock {
if (statesJobs[actualState] == job) {
statesJobs.remove(actualState)
}
}
}
}
}
}
}
/** /**
* Launch handling of states. On [statesManager] [StatesManager.onStartChain], * Launch handling of states. On [statesManager] [StatesManager.onStartChain],
@@ -60,30 +99,22 @@ class DefaultStatesMachine (
* [StatesManager.endChain]. * [StatesManager.endChain].
*/ */
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
val statePerformer: suspend (State) -> Unit = { state: State ->
val newState = launchStateHandling(state, handlers)
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) { statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
launch { statePerformer(it) } launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
} }
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
launch { statePerformer(it.second) } launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
} }
statesManager.getActiveStates().forEach { statesManager.getActiveStates().forEach {
launch { statePerformer(it) } launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
} }
} }
/** /**
* Just calls [StatesManager.startChain] of [statesManager] * Just calls [StatesManager.startChain] of [statesManager]
*/ */
override suspend fun startChain(state: State) { override suspend fun startChain(state: T) {
statesManager.startChain(state) statesManager.startChain(state)
} }
} }

View File

@@ -1,30 +1,30 @@
package dev.inmo.micro_utils.fsm.common package dev.inmo.micro_utils.fsm.common
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.Flow
interface StatesManager { interface StatesManager<T : State> {
val onChainStateUpdated: Flow<Pair<State, State>> val onChainStateUpdated: Flow<Pair<T, T>>
val onStartChain: Flow<State> val onStartChain: Flow<T>
val onEndChain: Flow<State> val onEndChain: Flow<T>
/** /**
* Must set current set using [State.context] * Must set current set using [State.context]
*/ */
suspend fun update(old: State, new: State) suspend fun update(old: T, new: T)
/** /**
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already * Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
* busy by the other [State] * busy by the other [State]
*/ */
suspend fun startChain(state: State) suspend fun startChain(state: T)
/** /**
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just * Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
* ignored * ignored
*/ */
suspend fun endChain(state: State) suspend fun endChain(state: T)
suspend fun getActiveStates(): List<State> suspend fun getActiveStates(): List<T>
} }

View File

@@ -0,0 +1,60 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.*
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.withLock
/**
* This extender of [StatesMachine] interface declare one new function [updateChain]. Realizations of this interface
* must be able to perform update of chain in internal [StatesManager]
*/
interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
/**
* Update chain with current state equal to [currentState] with [newState]. Behaviour of this update preforming
* in cases when [currentState] does not exist in [StatesManager] must be declared inside of realization of
* [StatesManager.update] function
*/
suspend fun updateChain(currentState: T, newState: T)
}
open class DefaultUpdatableStatesMachine<T : State>(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
) : DefaultStatesMachine<T>(
statesManager,
handlers
), UpdatableStatesMachine<T> {
protected val jobsStates = mutableMapOf<Job, T>()
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
if (compare(previousState, actualState)) {
statesJobs[actualState] ?.cancel()
}
val job = previousState.mapOnPresented {
statesJobs.remove(it)
} ?.takeIf { it.isActive } ?: scope.launch {
performUpdate(actualState)
}.also { job ->
job.invokeOnCompletion { _ ->
scope.launch {
statesJobsMutex.withLock {
statesJobs.remove(
jobsStates[job] ?: return@withLock
)
}
}
}
}
jobsStates.remove(job)
statesJobs[actualState] = job
jobsStates[job] = actualState
}
}
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
override suspend fun updateChain(currentState: T, newState: T) {
statesManager.update(currentState, newState)
}
}

View File

@@ -1,37 +1,61 @@
package dev.inmo.micro_utils.fsm.common.dsl package dev.inmo.micro_utils.fsm.common.dsl
import dev.inmo.micro_utils.fsm.common.* import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.managers.* import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager import dev.inmo.micro_utils.fsm.common.managers.InMemoryDefaultStatesManagerRepo
import kotlin.reflect.KClass import kotlin.reflect.KClass
class FSMBuilder( class FSMBuilder<T : State>(
var statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()) var statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
val fsmBuilder: (statesManager: StatesManager<T>, states: List<CheckableHandlerHolder<T, T>>) -> StatesMachine<T> = { statesManager, states ->
StatesMachine(
statesManager,
states
)
},
var defaultStateHandler: StatesHandler<T, T>? = StatesHandler { null }
) { ) {
private var states = mutableListOf<StateHandlerHolder<*>>() private var states = mutableListOf<CheckableHandlerHolder<T, T>>()
fun <I : State> add(kClass: KClass<I>, handler: StatesHandler<I>) { fun add(handler: CheckableHandlerHolder<T, T>) {
states.add(StateHandlerHolder(kClass, false, handler)) states.add(handler)
} }
fun <I : State> addStrict(kClass: KClass<I>, handler: StatesHandler<I>) { fun <I : T> add(kClass: KClass<I>, handler: StatesHandler<I, T>) {
states.add(StateHandlerHolder(kClass, true, handler)) add(CheckableHandlerHolder(kClass, false, handler))
} }
fun build() = StatesMachine( fun <I : T> add(filter: suspend (state: State) -> Boolean, handler: StatesHandler<I, T>) {
add(handler.holder(filter))
}
fun <I : T> addStrict(kClass: KClass<I>, handler: StatesHandler<I, T>) {
states.add(CheckableHandlerHolder(kClass, true, handler))
}
inline fun <reified I : T> onStateOrSubstate(handler: StatesHandler<I, T>) {
add(I::class, handler)
}
inline fun <reified I : T> strictlyOn(handler: StatesHandler<I, T>) {
addStrict(I::class, handler)
}
inline fun <reified I : T> doWhen(
noinline filter: suspend (state: State) -> Boolean,
handler: StatesHandler<I, T>
) {
add(filter, handler)
}
fun build() = fsmBuilder(
statesManager, statesManager,
states.toList() states.toList().let { list ->
defaultStateHandler ?.let { list + it.holder { true } } ?: list
}
) )
} }
inline fun <reified I : State> FSMBuilder.onStateOrSubstate(handler: StatesHandler<I>) { fun <T : State> buildFSM(
add(I::class, handler) block: FSMBuilder<T>.() -> Unit
} ): StatesMachine<T> = FSMBuilder<T>().apply(block).build()
inline fun <reified I : State> FSMBuilder.strictlyOn(handler: StatesHandler<I>) {
addStrict(I::class, handler)
}
fun buildFSM(
block: FSMBuilder.() -> Unit
): StatesMachine = FSMBuilder().apply(block).build()

View File

@@ -9,25 +9,25 @@ import kotlinx.coroutines.sync.withLock
/** /**
* Implement this repo if you want to use some custom repo for [DefaultStatesManager] * Implement this repo if you want to use some custom repo for [DefaultStatesManager]
*/ */
interface DefaultStatesManagerRepo { interface DefaultStatesManagerRepo<T : State> {
/** /**
* Must save [state] as current state of chain with [State.context] of [state] * Must save [state] as current state of chain with [State.context] of [state]
*/ */
suspend fun set(state: State) suspend fun set(state: T)
/** /**
* Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should * Remove exactly [state]. In case if internally [State.context] is busy with different [State], that [State] should
* NOT be removed * NOT be removed
*/ */
suspend fun removeState(state: State) suspend fun removeState(state: T)
/** /**
* @return Current list of available and saved states * @return Current list of available and saved states
*/ */
suspend fun getStates(): List<State> suspend fun getStates(): List<T>
/** /**
* @return Current state by [context] * @return Current state by [context]
*/ */
suspend fun getContextState(context: Any): State? suspend fun getContextState(context: Any): T?
/** /**
* @return Current state by [context] * @return Current state by [context]
@@ -43,21 +43,21 @@ interface DefaultStatesManagerRepo {
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by * key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state * new state by using [endChain] with that state
*/ */
class DefaultStatesManager( class DefaultStatesManager<T : State>(
private val repo: DefaultStatesManagerRepo = InMemoryDefaultStatesManagerRepo(), private val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
) : StatesManager { ) : StatesManager<T> {
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0) private val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow() override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
private val _onStartChain = MutableSharedFlow<State>(0) private val _onStartChain = MutableSharedFlow<T>(0)
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow() override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
private val _onEndChain = MutableSharedFlow<State>(0) private val _onEndChain = MutableSharedFlow<T>(0)
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow() override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
private val mapMutex = Mutex() private val mapMutex = Mutex()
override suspend fun update(old: State, new: State) = mapMutex.withLock { override suspend fun update(old: T, new: T) = mapMutex.withLock {
val stateByOldContext: State? = repo.getContextState(old.context) val stateByOldContext: T? = repo.getContextState(old.context)
when { when {
stateByOldContext != old -> return@withLock stateByOldContext != old -> return@withLock
stateByOldContext == null || old.context == new.context -> { stateByOldContext == null || old.context == new.context -> {
@@ -76,26 +76,26 @@ class DefaultStatesManager(
} }
} }
override suspend fun startChain(state: State) = mapMutex.withLock { override suspend fun startChain(state: T) = mapMutex.withLock {
if (!repo.contains(state.context)) { if (!repo.contains(state.context)) {
repo.set(state) repo.set(state)
_onStartChain.emit(state) _onStartChain.emit(state)
} }
} }
private suspend fun endChainWithoutLock(state: State) { private suspend fun endChainWithoutLock(state: T) {
if (repo.getContextState(state.context) == state) { if (repo.getContextState(state.context) == state) {
repo.removeState(state) repo.removeState(state)
_onEndChain.emit(state) _onEndChain.emit(state)
} }
} }
override suspend fun endChain(state: State) { override suspend fun endChain(state: T) {
mapMutex.withLock { mapMutex.withLock {
endChainWithoutLock(state) endChainWithoutLock(state)
} }
} }
override suspend fun getActiveStates(): List<State> = repo.getStates() override suspend fun getActiveStates(): List<T> = repo.getStates()
} }

View File

@@ -6,20 +6,20 @@ import dev.inmo.micro_utils.fsm.common.State
* Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive * Simple [DefaultStatesManagerRepo] for [DefaultStatesManager] which will store data in [map] and use primitive
* functionality * functionality
*/ */
class InMemoryDefaultStatesManagerRepo( class InMemoryDefaultStatesManagerRepo<T : State>(
private val map: MutableMap<Any, State> = mutableMapOf() private val map: MutableMap<Any, T> = mutableMapOf()
) : DefaultStatesManagerRepo { ) : DefaultStatesManagerRepo<T> {
override suspend fun set(state: State) { override suspend fun set(state: T) {
map[state.context] = state map[state.context] = state
} }
override suspend fun removeState(state: State) { override suspend fun removeState(state: T) {
map.remove(state.context) map.remove(state.context)
} }
override suspend fun getStates(): List<State> = map.values.toList() override suspend fun getStates(): List<T> = map.values.toList()
override suspend fun getContextState(context: Any): State? = map[context] override suspend fun getContextState(context: Any): T? = map[context]
override suspend fun contains(context: Any): Boolean = map.contains(context) override suspend fun contains(context: Any): Boolean = map.contains(context)
} }

View File

@@ -1,68 +1,16 @@
package dev.inmo.micro_utils.fsm.common.managers package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.StatesManager
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/** /**
* Creates [DefaultStatesManager] with [InMemoryDefaultStatesManagerRepo]
*
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context] * @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by * key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state * new state by using [endChain] with that state
*/ */
class InMemoryStatesManager( @Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true } fun <T: State> InMemoryStatesManager(
) : StatesManager { onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0) ) = DefaultStatesManager(onContextsConflictResolver = onContextsConflictResolver)
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
private val _onStartChain = MutableSharedFlow<State>(0)
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
private val _onEndChain = MutableSharedFlow<State>(0)
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
private val contextsToStates = mutableMapOf<Any, State>()
private val mapMutex = Mutex()
override suspend fun update(old: State, new: State) = mapMutex.withLock {
when {
contextsToStates[old.context] != old -> return@withLock
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
contextsToStates[old.context] = new
_onChainStateUpdated.emit(old to new)
}
else -> {
val stateOnNewOneContext = contextsToStates.getValue(new.context)
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
endChainWithoutLock(stateOnNewOneContext)
contextsToStates.remove(old.context)
contextsToStates[new.context] = new
_onChainStateUpdated.emit(old to new)
}
}
}
}
override suspend fun startChain(state: State) = mapMutex.withLock {
if (!contextsToStates.containsKey(state.context)) {
contextsToStates[state.context] = state
_onStartChain.emit(state)
}
}
private suspend fun endChainWithoutLock(state: State) {
if (contextsToStates[state.context] == state) {
contextsToStates.remove(state.context)
_onEndChain.emit(state)
}
}
override suspend fun endChain(state: State) {
mapMutex.withLock {
endChainWithoutLock(state)
}
}
override suspend fun getActiveStates(): List<State> = contextsToStates.values.toList()
}

View File

@@ -1,6 +1,7 @@
import dev.inmo.micro_utils.fsm.common.* import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
import dev.inmo.micro_utils.fsm.common.dsl.strictlyOn import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
import kotlinx.coroutines.* import kotlinx.coroutines.*
sealed interface TrafficLightState : State { sealed interface TrafficLightState : State {
@@ -25,9 +26,9 @@ class PlayableMain {
} }
} }
val statesManager = InMemoryStatesManager() val statesManager = DefaultStatesManager<TrafficLightState>()
val machine = buildFSM { val machine = buildFSM<TrafficLightState> {
strictlyOn<GreenCommon> { strictlyOn<GreenCommon> {
delay(1000L) delay(1000L)
YellowCommon(it.context).also(::println) YellowCommon(it.context).also(::println)

View File

@@ -5,21 +5,21 @@ import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.pagination.getAll import dev.inmo.micro_utils.repos.pagination.getAll
class KeyValueBasedDefaultStatesManagerRepo( class KeyValueBasedDefaultStatesManagerRepo<T : State>(
private val keyValueRepo: KeyValueRepo<Any, State> private val keyValueRepo: KeyValueRepo<Any, T>
) : DefaultStatesManagerRepo { ) : DefaultStatesManagerRepo<T> {
override suspend fun set(state: State) { override suspend fun set(state: T) {
keyValueRepo.set(state.context, state) keyValueRepo.set(state.context, state)
} }
override suspend fun removeState(state: State) { override suspend fun removeState(state: T) {
if (keyValueRepo.get(state.context) == state) { if (keyValueRepo.get(state.context) == state) {
keyValueRepo.unset(state.context) keyValueRepo.unset(state.context)
} }
} }
override suspend fun getStates(): List<State> = keyValueRepo.getAll { keys(it) }.map { it.second } override suspend fun getStates(): List<T> = keyValueRepo.getAll { keys(it) }.map { it.second }
override suspend fun getContextState(context: Any): State? = keyValueRepo.get(context) override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context)
override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context) override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context)
} }

View File

@@ -1,84 +0,0 @@
package dev.inmo.micro_utils.fsm.repos.common
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.StatesManager
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.micro_utils.repos.pagination.getAll
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@Deprecated("Replace with DefaultStatesManager and KeyValueBasedDefaultStatesManagerRepo")
class KeyValueBasedStatesManager(
private val keyValueRepo: KeyValueRepo<Any, State>,
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
) : StatesManager {
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
private val _onEndChain = MutableSharedFlow<State>(0)
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
override val onStartChain: Flow<State> = keyValueRepo.onNewValue.map { it.second }
private val mutex = Mutex()
override suspend fun update(old: State, new: State) {
mutex.withLock {
when {
keyValueRepo.get(old.context) != old -> return@withLock
old.context == new.context || !keyValueRepo.contains(new.context) -> {
keyValueRepo.set(old.context, new)
_onChainStateUpdated.emit(old to new)
}
else -> {
val stateOnNewOneContext = keyValueRepo.get(new.context)!!
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
endChainWithoutLock(stateOnNewOneContext)
keyValueRepo.unset(old.context)
keyValueRepo.set(new.context, new)
_onChainStateUpdated.emit(old to new)
}
}
}
}
}
override suspend fun startChain(state: State) {
if (!keyValueRepo.contains(state.context)) {
keyValueRepo.set(state.context, state)
}
}
private suspend fun endChainWithoutLock(state: State) {
if (keyValueRepo.get(state.context) == state) {
keyValueRepo.unset(state.context)
_onEndChain.emit(state)
}
}
override suspend fun endChain(state: State) {
mutex.withLock { endChainWithoutLock(state) }
}
override suspend fun getActiveStates(): List<State> {
return keyValueRepo.getAll { keys(it) }.map { it.second }
}
}
inline fun <reified TargetContextType, reified TargetStateType> createStatesManager(
targetKeyValueRepo: KeyValueRepo<TargetContextType, TargetStateType>,
noinline contextToOutTransformer: suspend Any.() -> TargetContextType,
noinline stateToOutTransformer: suspend State.() -> TargetStateType,
noinline outToContextTransformer: suspend TargetContextType.() -> Any,
noinline outToStateTransformer: suspend TargetStateType.() -> State,
) = KeyValueBasedStatesManager(
targetKeyValueRepo.withMapper<Any, State, TargetContextType, TargetStateType>(
contextToOutTransformer,
stateToOutTransformer,
outToContextTransformer,
outToStateTransformer
)
)

View File

@@ -7,43 +7,12 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx2g
kotlin_version=1.5.31
kotlin_coroutines_version=1.5.2
kotlin_serialisation_core_version=1.3.0
kotlin_exposed_version=0.35.2
ktor_version=1.6.4
klockVersion=2.4.5
github_release_plugin_version=2.2.12
uuidVersion=0.3.1
# ANDROID
core_ktx_version=1.6.0
androidx_recycler_version=1.2.1
appcompat_version=1.3.1
android_minSdkVersion=19
android_compileSdkVersion=31
android_buildToolsVersion=31.0.0
dexcount_version=3.0.0
junit_version=4.12
test_ext_junit_version=1.1.2
espresso_core=3.3.0
# JS NPM # JS NPM
crypto_js_version=4.1.1 crypto_js_version=4.1.1
# Dokka
dokka_version=1.5.31
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.7.1 version=0.9.11
android_code_version=75 android_code_version=101

77
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,77 @@
[versions]
kt = "1.6.10"
kt-serialization = "1.3.2"
kt-coroutines = "1.6.0"
jb-compose = "1.1.0"
jb-exposed = "0.37.3"
jb-dokka = "1.6.10"
klock = "2.6.2"
uuid = "0.4.0"
ktor = "1.6.7"
gh-release = "2.2.12"
android-gradle = "7.0.4"
dexcount = "3.0.1"
android-coreKtx = "1.7.0"
android-recyclerView = "1.2.1"
android-appCompat = "1.4.1"
android-espresso = "3.3.0"
android-test = "1.1.2"
android-props-minSdk = "19"
android-props-compileSdk = "32"
android-props-buildTools = "32.0.0"
[libraries]
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" }
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gradle-plugin", version.ref = "dexcount" }
[plugins]
jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" }

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -12,7 +12,7 @@ kotlin {
dependencies { dependencies {
api internalProject("micro_utils.ktor.common") api internalProject("micro_utils.ktor.common")
api internalProject("micro_utils.coroutines") api internalProject("micro_utils.coroutines")
api "io.ktor:ktor-client-core:$ktor_version" api libs.ktor.client
} }
} }
} }

View File

@@ -4,6 +4,7 @@ import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.features.websocket.ws import io.ktor.client.features.websocket.ws
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.cio.websocket.Frame import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readBytes import io.ktor.http.cio.websocket.readBytes
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -17,6 +18,7 @@ import kotlinx.serialization.DeserializationStrategy
inline fun <T> HttpClient.createStandardWebsocketFlow( inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String, url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true }, crossinline checkReconnection: (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
crossinline conversation: suspend (StandardKtorSerialInputData) -> T crossinline conversation: suspend (StandardKtorSerialInputData) -> T
): Flow<T> { ): Flow<T> {
val correctedUrl = url.asCorrectWebSocketUrl val correctedUrl = url.asCorrectWebSocketUrl
@@ -26,7 +28,7 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
do { do {
val reconnect = try { val reconnect = try {
safely { safely {
ws(correctedUrl) { ws(correctedUrl, requestBuilder) {
for (received in incoming) { for (received in incoming) {
when (received) { when (received) {
is Frame.Binary -> producerScope.send(conversation(received.readBytes())) is Frame.Binary -> producerScope.send(conversation(received.readBytes()))
@@ -65,10 +67,12 @@ inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String, url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true }, crossinline checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow( ) = createStandardWebsocketFlow(
url, url,
checkReconnection checkReconnection,
requestBuilder
) { ) {
serialFormat.decodeDefault(deserializer, it) serialFormat.decodeDefault(deserializer, it)
} }

View File

@@ -0,0 +1,6 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import io.ktor.client.request.forms.InputProvider
expect suspend fun MPPFile.inputProvider(): InputProvider

View File

@@ -1,16 +1,20 @@
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.filename
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.*
import io.ktor.client.request.post import io.ktor.client.request.forms.*
import io.ktor.http.*
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.serialization.* import kotlinx.serialization.*
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T> typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
class UnifiedRequester( class UnifiedRequester(
private val client: HttpClient = HttpClient(), val client: HttpClient = HttpClient(),
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) { ) {
suspend fun <ResultType> uniget( suspend fun <ResultType> uniget(
url: String, url: String,
@@ -31,11 +35,66 @@ class UnifiedRequester(
resultDeserializer: DeserializationStrategy<ResultType> resultDeserializer: DeserializationStrategy<ResultType>
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat) ) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
mppFile: MPPFile,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
fun <T> createStandardWebsocketFlow( fun <T> createStandardWebsocketFlow(
url: String, url: String,
checkReconnection: (Throwable?) -> Boolean = { true }, checkReconnection: (Throwable?) -> Boolean,
deserializer: DeserializationStrategy<T> deserializer: DeserializationStrategy<T>,
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat) requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat, requestBuilder)
fun <T> createStandardWebsocketFlow(
url: String,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
} }
val defaultRequester = UnifiedRequester() val defaultRequester = UnifiedRequester()
@@ -69,3 +128,124 @@ suspend fun <BodyType, ResultType> HttpClient.unipost(
}.let { }.let {
serialFormat.decodeDefault(resultDeserializer, it) serialFormat.decodeDefault(resultDeserializer, it)
} }
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>(
url,
formData = formData {
append(
"bytes",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, mimetype)
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
}
) {
requestBuilder()
}.let { serialFormat.decodeDefault(resultDeserializer, it) }
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
filename: String,
otherData: BodyPair<BodyType>,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
filename,
inputProvider,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile.filename.string,
mppFile.inputProvider(),
resultDeserializer,
mimetype,
additionalParametersBuilder,
dataHeadersBuilder,
requestBuilder,
serialFormat
)
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)

View File

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

View File

@@ -0,0 +1,9 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import io.ktor.client.request.forms.InputProvider
import io.ktor.utils.io.streams.asInput
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
inputStream().asInput()
}

View File

@@ -10,8 +10,9 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlin_serialisation_core_version" api internalProject("micro_utils.common")
api "com.soywiz.korlibs.klock:klock:$klockVersion" api libs.kt.serialization.cbor
api libs.klock
} }
} }
} }

View File

@@ -16,10 +16,10 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
api "io.ktor:ktor-server:$ktor_version" api libs.ktor.server
api "io.ktor:ktor-server-cio:$ktor_version" api libs.ktor.server.cio
api "io.ktor:ktor-server-host-common:$ktor_version" api libs.ktor.server.host.common
api "io.ktor:ktor-websockets:$ktor_version" api libs.ktor.websockets
} }
} }
} }

View File

@@ -2,29 +2,31 @@ package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.featureOrNull
import io.ktor.application.install
import io.ktor.http.URLProtocol
import io.ktor.http.cio.websocket.* import io.ktor.http.cio.websocket.*
import io.ktor.routing.Route import io.ktor.routing.Route
import io.ktor.websocket.webSocket import io.ktor.routing.application
import io.ktor.websocket.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
private suspend fun DefaultWebSocketSession.checkReceivedAndCloseIfExists() {
if (incoming.tryReceive() != null) {
close()
throw CorrectCloseException
}
}
fun <T> Route.includeWebsocketHandling( fun <T> Route.includeWebsocketHandling(
suburl: String, suburl: String,
flow: Flow<T>, flow: Flow<T>,
converter: (T) -> StandardKtorSerialInputData protocol: URLProtocol = URLProtocol.WS,
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
) { ) {
webSocket(suburl) { application.apply {
featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets)
}
webSocket(suburl, protocol.name) {
safely { safely {
flow.collect { flow.collect {
send(converter(it)) converter(it) ?.let { data ->
send(data)
}
} }
} }
} }
@@ -34,10 +36,24 @@ fun <T> Route.includeWebsocketHandling(
suburl: String, suburl: String,
flow: Flow<T>, flow: Flow<T>,
serializer: SerializationStrategy<T>, serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling( ) = includeWebsocketHandling(
suburl, suburl,
flow flow,
) { protocol,
serialFormat.encodeDefault(serializer, it) converter = if (filter == null) {
} {
serialFormat.encodeDefault(serializer, it)
}
} else {
{
if (filter(it)) {
serialFormat.encodeDefault(serializer, it)
} else {
null
}
}
}
)

View File

@@ -1,28 +1,39 @@
package dev.inmo.micro_utils.ktor.server package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.* import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCall
import io.ktor.application.call import io.ktor.application.call
import io.ktor.http.ContentType import io.ktor.http.*
import io.ktor.http.HttpStatusCode import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.request.receive import io.ktor.request.receive
import io.ktor.request.receiveMultipart
import io.ktor.response.respond import io.ktor.response.respond
import io.ktor.response.respondBytes import io.ktor.response.respondBytes
import io.ktor.routing.Route import io.ktor.routing.Route
import io.ktor.util.asStream
import io.ktor.util.cio.writeChannel
import io.ktor.util.pipeline.PipelineContext import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.*
import io.ktor.websocket.WebSocketServerSession
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.* import kotlinx.serialization.*
import java.io.File
import java.io.File.createTempFile
class UnifiedRouter( class UnifiedRouter(
private val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
private val serialFormatContentType: ContentType = standardKtorSerialFormatContentType val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) { ) {
fun <T> Route.includeWebsocketHandling( fun <T> Route.includeWebsocketHandling(
suburl: String, suburl: String,
flow: Flow<T>, flow: Flow<T>,
serializer: SerializationStrategy<T> serializer: SerializationStrategy<T>,
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat) protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer( suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
answerSerializer: SerializationStrategy<T>, answerSerializer: SerializationStrategy<T>,
@@ -104,6 +115,139 @@ suspend fun <T> ApplicationCall.uniload(
) )
} }
suspend fun ApplicationCall.uniloadMultipart(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
) = safely {
val multipartData = receiveMultipart()
var resultInput: Input? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
when (it.name) {
"bytes" -> resultInput = it.provider()
else -> onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
}
}
resultInput ?: error("Bytes has not been received")
}
suspend fun <T> ApplicationCall.uniloadMultipart(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
): Pair<Input, T> {
var data: Optional<T>? = null
val resultInput = uniloadMultipart(
onFormItem,
{
if (it.name == "data") {
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
} else {
onCustomFileItem(it)
}
},
onBinaryContent
)
val completeData = data ?: error("Data has not been received")
return resultInput to (completeData.dataOrNull().let { it as T })
}
suspend fun <T> ApplicationCall.uniloadMultipartFile(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
var resultInput: MPPFile? = null
var data: Optional<T>? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
when (it.name) {
"bytes" -> {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile(
name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.provider().asStream().copyTo(fileStream)
}
}
}
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
else -> onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
}
}
val completeData = data ?: error("Data has not been received")
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
}
suspend fun ApplicationCall.uniloadMultipartFile(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
var resultInput: MPPFile? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
if (it.name == "bytes") {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile(
name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.provider().asStream().copyTo(fileStream)
}
}
} else {
onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
}
}
resultInput ?: error("Bytes has not been received")
}
suspend fun ApplicationCall.getParameterOrSendError( suspend fun ApplicationCall.getParameterOrSendError(
field: String field: String
) = parameters[field].also { ) = parameters[field].also {

View File

@@ -1,5 +1,6 @@
package dev.inmo.micro_utils.ktor.server package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.server.cio.CIO import io.ktor.server.cio.CIO
import io.ktor.server.engine.* import io.ktor.server.engine.*
@@ -31,3 +32,27 @@ fun createKtorServer(
port: Int = Random.nextInt(1024, 65535), port: Int = Random.nextInt(1024, 65535),
block: Application.() -> Unit block: Application.() -> Unit
): ApplicationEngine = createKtorServer(CIO, host, port, block) ): ApplicationEngine = createKtorServer(CIO, host, port, block)
fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration> createKtorServer(
engine: ApplicationEngineFactory<TEngine, TConfiguration>,
host: String = "localhost",
port: Int = Random.nextInt(1024, 65535),
configurators: List<KtorApplicationConfigurator>
): TEngine = createKtorServer(
engine,
host,
port
) {
configurators.forEach { it.apply { configure() } }
}
/**
* Create server with [CIO] server engine without starting of it
*
* @see ApplicationEngine.start
*/
fun createKtorServer(
host: String = "localhost",
port: Int = Random.nextInt(1024, 65535),
configurators: List<KtorApplicationConfigurator>
): ApplicationEngine = createKtorServer(CIO, host, port, configurators)

View File

@@ -4,8 +4,8 @@ buildscript {
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath libs.buildscript.kt.gradle
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath libs.buildscript.kt.serialization
} }
} }
@@ -16,11 +16,16 @@ plugins {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation libs.kt.stdlib
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version" implementation libs.kt.serialization
implementation "io.ktor:ktor-client-core:$ktor_version" implementation libs.ktor.client
implementation "io.ktor:ktor-client-java:$ktor_version" implementation libs.ktor.client.java
} }
mainClassName="MainKt" mainClassName="MainKt"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -1,3 +1,5 @@
@file:Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
package dev.inmo.micro_utils.language_codes package dev.inmo.micro_utils.language_codes
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@@ -0,0 +1,52 @@
import requests
from bs4 import BeautifulSoup
import pandas as pd
import itertools
def fix_name(category, raw_name):
splitted = raw_name.replace('-', '+').replace('.', '+').replace(',', '+').split('+')
out1 = ""
for s in splitted:
out1 += s.capitalize()
result = ""
if out1[0].isdigit():
result += category[0].capitalize()
result += out1
else:
result += out1
return result
if __name__ == '__main__':
df = pd.read_html(open('table.html', 'r'))
mimes = []
for row in df[0].iterrows():
mime = row[1][1]
mime_category = mime.split('/', 1)[0]
mime_name = mime.split('/', 1)[1]
mimes.append({
'mime_category': mime_category,
'mime_name': mime_name,
})
# codegen
mimes.sort(key=lambda x: x['mime_category'])
grouped = itertools.groupby(mimes, lambda x: x['mime_category'])
code = ''
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
code2 += ' KnownMimeTypes.Any,\n'
for key, group in grouped:
group_name = key.capitalize()
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
for mime in group:
name = fix_name(mime['mime_category'], mime['mime_name'])
code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s")\n' % (name, group_name, mime['mime_category'], mime['mime_name'])
code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name)
code += '}\n\n'
code2 += ')\n'
with open('out1.txt', 'w') as file:
file.write(code)
with open('out2.txt', 'w') as file:
file.write(code2)

View File

@@ -1,3 +1,5 @@
@file:Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
package dev.inmo.micro_utils.mime_types package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@@ -26,7 +26,6 @@ kotlin {
apply from: "$defaultAndroidSettingsPresetPath" apply from: "$defaultAndroidSettingsPresetPath"
java { java {
toolchain { sourceCompatibility = JavaVersion.VERSION_1_8
languageVersion = JavaLanguageVersion.of(8) targetCompatibility = JavaVersion.VERSION_1_8
}
} }

View File

@@ -4,7 +4,13 @@ project.group = "$group"
apply from: "$publishGradlePath" apply from: "$publishGradlePath"
kotlin { kotlin {
jvm() jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
sourceSets { sourceSets {
commonMain { commonMain {
@@ -28,7 +34,6 @@ kotlin {
} }
java { java {
toolchain { sourceCompatibility = JavaVersion.VERSION_1_8
languageVersion = JavaLanguageVersion.of(8) targetCompatibility = JavaVersion.VERSION_1_8
}
} }

View File

@@ -4,7 +4,13 @@ project.group = "$group"
apply from: "$publishGradlePath" apply from: "$publishGradlePath"
kotlin { kotlin {
jvm() jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js (IR) { js (IR) {
browser() browser()
nodejs() nodejs()
@@ -17,7 +23,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
implementation kotlin('stdlib') implementation kotlin('stdlib')
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version" implementation libs.kt.serialization
} }
} }
commonTest { commonTest {
@@ -40,8 +46,8 @@ kotlin {
androidTest { androidTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation "androidx.test.ext:junit:$test_ext_junit_version" implementation libs.android.test.junit
implementation "androidx.test.espresso:espresso-core:$espresso_core" implementation libs.android.espresso
} }
} }
} }
@@ -50,7 +56,6 @@ kotlin {
apply from: "$defaultAndroidSettingsPresetPath" apply from: "$defaultAndroidSettingsPresetPath"
java { java {
toolchain { sourceCompatibility = JavaVersion.VERSION_1_8
languageVersion = JavaLanguageVersion.of(8) targetCompatibility = JavaVersion.VERSION_1_8
}
} }

View File

@@ -0,0 +1,72 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js (IR) {
browser()
nodejs()
}
android {
publishAllLibraryVariants()
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
implementation libs.kt.serialization
implementation compose.runtime
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation compose.desktop.currentOs
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
jsMain {
dependencies {
implementation compose.web.core
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
androidTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
}
}
apply from: "$defaultAndroidSettingsPresetPath"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -5,3 +5,13 @@ plugins {
} }
apply from: "$mppProjectWithSerializationPresetPath" apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.common")
}
}
}
}

View File

@@ -1,5 +1,6 @@
package dev.inmo.micro_utils.pagination package dev.inmo.micro_utils.pagination
import dev.inmo.micro_utils.common.intersect
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.floor import kotlin.math.floor
@@ -9,7 +10,7 @@ import kotlin.math.floor
* If you want to request something, you should use [SimplePagination]. If you need to return some result including * If you want to request something, you should use [SimplePagination]. If you need to return some result including
* pagination - [PaginationResult] * pagination - [PaginationResult]
*/ */
interface Pagination { interface Pagination : ClosedRange<Int> {
/** /**
* Started with 0. * Started with 0.
* Number of page inside of pagination. Offset can be calculated as [page] * [size] * Number of page inside of pagination. Offset can be calculated as [page] * [size]
@@ -20,6 +21,17 @@ interface Pagination {
* Size of current page. Offset can be calculated as [page] * [size] * Size of current page. Offset can be calculated as [page] * [size]
*/ */
val size: Int val size: Int
override val start: Int
get() = page * size
override val endInclusive: Int
get() = start + size - 1
}
fun Pagination.intersect(
other: Pagination
): Pagination? = (this as ClosedRange<Int>).intersect(other as ClosedRange<Int>) ?.let {
PaginationByIndexes(it.first, it.second)
} }
/** /**
@@ -32,7 +44,7 @@ inline val Pagination.isFirstPage
* First number in index of objects. It can be used as offset for databases or other data sources * First number in index of objects. It can be used as offset for databases or other data sources
*/ */
val Pagination.firstIndex: Int val Pagination.firstIndex: Int
get() = page * size get() = start
/** /**
* Last number in index of objects. In fact, one [Pagination] object represent data in next range: * Last number in index of objects. In fact, one [Pagination] object represent data in next range:
@@ -41,7 +53,7 @@ val Pagination.firstIndex: Int
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20 * you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. Here [Pagination.lastIndexExclusive] == 20
*/ */
val Pagination.lastIndexExclusive: Int val Pagination.lastIndexExclusive: Int
get() = firstIndex + size get() = endInclusive + 1
/** /**
* Last number in index of objects. In fact, one [Pagination] object represent data in next range: * Last number in index of objects. In fact, one [Pagination] object represent data in next range:
@@ -50,7 +62,7 @@ val Pagination.lastIndexExclusive: Int
* you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19. * you will retrieve [Pagination.firstIndex] == 10 and [Pagination.lastIndex] == 19.
*/ */
val Pagination.lastIndex: Int val Pagination.lastIndex: Int
get() = lastIndexExclusive - 1 get() = endInclusive
/** /**
* Calculates pages count for given [datasetSize] * Calculates pages count for given [datasetSize]

View File

@@ -16,6 +16,16 @@ suspend fun <T> getAll(
return results.toList() return results.toList()
} }
suspend fun <T, R> R.getAllBy(
initialPagination: Pagination = FirstPagePagination(),
paginationMapper: R.(PaginationResult<T>) -> Pagination?,
block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAll(
initialPagination,
{ paginationMapper(it) },
{ block(it) }
)
suspend fun <T> getAllWithNextPaging( suspend fun <T> getAllWithNextPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
@@ -25,6 +35,14 @@ suspend fun <T> getAllWithNextPaging(
block block
) )
suspend fun <T, R> R.getAllByWithNextPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithNextPaging(
initialPagination,
{ block(it) }
)
suspend fun <T> getAllWithCurrentPaging( suspend fun <T> getAllWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(), initialPagination: Pagination = FirstPagePagination(),
block: suspend (Pagination) -> PaginationResult<T> block: suspend (Pagination) -> PaginationResult<T>
@@ -33,3 +51,11 @@ suspend fun <T> getAllWithCurrentPaging(
{ it.currentPageIfNotEmpty() }, { it.currentPageIfNotEmpty() },
block block
) )
suspend fun <T, R> R.getAllByWithCurrentPaging(
initialPagination: Pagination = FirstPagePagination(),
block: suspend R.(Pagination) -> PaginationResult<T>
): List<T> = getAllWithCurrentPaging(
initialPagination,
{ block(it) }
)

View File

@@ -38,3 +38,31 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {
size.toLong() size.toLong()
) )
} }
fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) {
is List<T> -> optionallyReverse(reverse)
is Set<T> -> optionallyReverse(reverse)
else -> if (reverse) {
reversed()
} else {
this
}
}
fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) {
reversed()
} else {
this
}
fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) {
reversed().toSet()
} else {
this
}
inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) {
Array(size) {
get(lastIndex - it)
}
} else {
this
}

View File

@@ -26,3 +26,15 @@ fun Pagination.reverse(datasetSize: Long): SimplePagination {
* Shortcut for [reverse] * Shortcut for [reverse]
*/ */
fun Pagination.reverse(objectsCount: Int) = reverse(objectsCount.toLong()) fun Pagination.reverse(objectsCount: Int) = reverse(objectsCount.toLong())
fun Pagination.optionallyReverse(objectsCount: Int, reverse: Boolean) = if (reverse) {
reverse(objectsCount)
} else {
this
}
fun Pagination.optionallyReverse(objectsCount: Long, reverse: Boolean) = if (reverse) {
reverse(objectsCount)
} else {
this
}

View File

@@ -14,7 +14,7 @@ kotlin {
} }
jvmMain { jvmMain {
dependencies { dependencies {
api "org.jetbrains.exposed:exposed-core:$kotlin_exposed_version" api libs.jb.exposed
} }
} }
} }

View File

@@ -15,8 +15,8 @@ kotlin {
jvmMain { jvmMain {
dependencies { dependencies {
api "io.ktor:ktor-server:$ktor_version" api libs.ktor.server
api "io.ktor:ktor-server-host-common:$ktor_version" api libs.ktor.server.host.common
} }
} }
} }

View File

@@ -1,5 +1,4 @@
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'signing'
task javadocsJar(type: Jar) { task javadocsJar(type: Jar) {
classifier = 'javadoc' classifier = 'javadoc'
@@ -69,8 +68,19 @@ publishing {
} }
} }
} }
signing { if (project.hasProperty("signing.gnupg.keyName")) {
useGpgCmd() apply plugin: 'signing'
sign publishing.publications
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
}
}
} }

View File

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

View File

@@ -10,10 +10,10 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" api libs.kt.coroutines
api internalProject("micro_utils.pagination.common") api internalProject("micro_utils.pagination.common")
api "com.benasher44:uuid:$uuidVersion" api libs.uuid
} }
} }
@@ -24,7 +24,7 @@ kotlin {
} }
androidMain { androidMain {
dependencies { dependencies {
api "androidx.core:core-ktx:$core_ktx_version" api libs.android.coreKtx
api internalProject("micro_utils.common") api internalProject("micro_utils.common")
api internalProject("micro_utils.coroutines") api internalProject("micro_utils.coroutines")
} }

View File

@@ -6,13 +6,15 @@ import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>( abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType>(
helper: StandardSQLHelper helper: StandardSQLHelper,
replyInFlows: Int = 0,
extraBufferCapacityInFlows: Int = 64
) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>, ) : WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>,
AbstractAndroidCRUDRepo<ObjectType, IdType>(helper), AbstractAndroidCRUDRepo<ObjectType, IdType>(helper),
StandardCRUDRepo<ObjectType, IdType, InputValueType> { StandardCRUDRepo<ObjectType, IdType, InputValueType> {
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(64) protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(64) protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyInFlows, extraBufferCapacityInFlows)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(64) protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyInFlows, extraBufferCapacityInFlows)
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow() override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow() override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow() override val deletedObjectsIdsFlow: Flow<IdType> = deleteObjectsIdsChannel.asSharedFlow()
@@ -102,4 +104,4 @@ abstract class AbstractMutableAndroidCRUDRepo<ObjectType, IdType, InputValueType
} }
override suspend fun count(): Long = helper.blockingReadableTransaction { select(tableName).use { it.count.toLong() } } override suspend fun count(): Long = helper.blockingReadableTransaction { select(tableName).use { it.count.toLong() } }
} }

View File

@@ -10,15 +10,16 @@ import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 0, flowsChannelsSize: Int = 0,
tableName: String = "" tableName: String = "",
replyCacheInFlows: Int = 0
) : ) :
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName), AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
ExposedCRUDRepo<ObjectType, IdType>, ExposedCRUDRepo<ObjectType, IdType>,
WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> WriteStandardCRUDRepo<ObjectType, IdType, InputValueType>
{ {
protected val newObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize) protected val newObjectsChannel = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(flowsChannelsSize) protected val updateObjectsChannel = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize)
protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(flowsChannelsSize) protected val deleteObjectsIdsChannel = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize)
override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow() override val newObjectsFlow: Flow<ObjectType> = newObjectsChannel.asSharedFlow()
override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow() override val updatedObjectsFlow: Flow<ObjectType> = updateObjectsChannel.asSharedFlow()

View File

@@ -19,8 +19,8 @@ open class ExposedKeyValueRepo<Key, Value>(
valueColumnAllocator, valueColumnAllocator,
tableName tableName
) { ) {
private val _onNewValue = MutableSharedFlow<Pair<Key, Value>>() protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>()
private val _onValueRemoved = MutableSharedFlow<Key>() protected val _onValueRemoved = MutableSharedFlow<Key>()
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow() override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow() override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()

View File

@@ -12,8 +12,8 @@ open class ExposedReadKeyValueRepo<Key, Value>(
valueColumnAllocator: ColumnAllocator<Value>, valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null tableName: String? = null
) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") { ) : ReadStandardKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
protected val keyColumn: Column<Key> = keyColumnAllocator() val keyColumn: Column<Key> = keyColumnAllocator()
protected val valueColumn: Column<Value> = valueColumnAllocator() val valueColumn: Column<Value> = valueColumnAllocator()
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn) override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
init { initTable() } init { initTable() }

View File

@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedKeyValuesRepo<Key, Value> = ExposedOneToManyKeyValueRepo<Key, Value>
open class ExposedOneToManyKeyValueRepo<Key, Value>( open class ExposedOneToManyKeyValueRepo<Key, Value>(
database: Database, database: Database,
keyColumnAllocator: ColumnAllocator<Key>, keyColumnAllocator: ColumnAllocator<Key>,
@@ -34,10 +35,15 @@ open class ExposedOneToManyKeyValueRepo<Key, Value>(
if (select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) { if (select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) {
return@mapNotNull null return@mapNotNull null
} }
insertIgnore { val insertResult = insert {
it[keyColumn] = k it[keyColumn] = k
it[valueColumn] = v it[valueColumn] = v
}.getOrNull(keyColumn) ?.let { k to v } }
if (insertResult.insertedCount > 0) {
k to v
} else {
null
}
} ?: emptyList() } ?: emptyList()
} }
}.forEach { _onNewValue.emit(it) } }.forEach { _onNewValue.emit(it) }
@@ -47,7 +53,7 @@ open class ExposedOneToManyKeyValueRepo<Key, Value>(
transaction(database) { transaction(database) {
toRemove.keys.flatMap { k -> toRemove.keys.flatMap { k ->
toRemove[k] ?.mapNotNull { v -> toRemove[k] ?.mapNotNull { v ->
if (deleteIgnoreWhere { keyColumn.eq(k).and(valueColumn.eq(v)) } > 0 ) { if (deleteWhere { keyColumn.eq(k).and(valueColumn.eq(v)) } > 0 ) {
k to v k to v
} else { } else {
null null

View File

@@ -3,17 +3,20 @@ package dev.inmo.micro_utils.repos.exposed.onetomany
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo import dev.inmo.micro_utils.repos.ReadOneToManyKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.* import dev.inmo.micro_utils.repos.exposed.*
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedReadKeyValueRepo
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
typealias ExposedReadKeyValuesRepo<Key, Value> = ExposedReadOneToManyKeyValueRepo<Key, Value>
open class ExposedReadOneToManyKeyValueRepo<Key, Value>( open class ExposedReadOneToManyKeyValueRepo<Key, Value>(
override val database: Database, override val database: Database,
keyColumnAllocator: ColumnAllocator<Key>, keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>, valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null tableName: String? = null
) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") { ) : ReadOneToManyKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
protected val keyColumn: Column<Key> = keyColumnAllocator() val keyColumn: Column<Key> = keyColumnAllocator()
protected val valueColumn: Column<Value> = valueColumnAllocator() val valueColumn: Column<Value> = valueColumnAllocator()
init { initTable() } init { initTable() }

View File

@@ -18,8 +18,8 @@ inline fun versionsRepo(database: Database): VersionsRepo<Database> = StandardVe
class ExposedStandardVersionsRepoProxy( class ExposedStandardVersionsRepoProxy(
override val database: Database override val database: Database
) : StandardVersionsRepoProxy<Database>, Table("ExposedVersionsProxy"), ExposedRepo { ) : StandardVersionsRepoProxy<Database>, Table("ExposedVersionsProxy"), ExposedRepo {
private val tableNameColumn = text("tableName") val tableNameColumn = text("tableName")
private val tableVersionColumn = integer("tableName") val tableVersionColumn = integer("tableName")
init { init {
initTable() initTable()

View File

@@ -10,8 +10,8 @@ kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" api libs.kt.coroutines
} }
} }
} }
} }

View File

@@ -11,8 +11,7 @@ open class TypedSerializer<T : Any>(
presetSerializers: Map<String, KSerializer<out T>> = emptyMap(), presetSerializers: Map<String, KSerializer<out T>> = emptyMap(),
) : KSerializer<T> { ) : KSerializer<T> {
protected val serializers = presetSerializers.toMutableMap() protected val serializers = presetSerializers.toMutableMap()
@ExperimentalSerializationApi @OptIn(InternalSerializationApi::class)
@InternalSerializationApi
override val descriptor: SerialDescriptor = buildSerialDescriptor( override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer", "TypedSerializer",
SerialKind.CONTEXTUAL SerialKind.CONTEXTUAL
@@ -21,8 +20,7 @@ open class TypedSerializer<T : Any>(
element("value", ContextualSerializer(kClass).descriptor) element("value", ContextualSerializer(kClass).descriptor)
} }
@ExperimentalSerializationApi @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
@InternalSerializationApi
override fun deserialize(decoder: Decoder): T { override fun deserialize(decoder: Decoder): T {
return decoder.decodeStructure(descriptor) { return decoder.decodeStructure(descriptor) {
var type: String? = null var type: String? = null
@@ -46,14 +44,12 @@ open class TypedSerializer<T : Any>(
} }
} }
@ExperimentalSerializationApi @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
@InternalSerializationApi
protected open fun <O: T> CompositeEncoder.encode(value: O) { protected open fun <O: T> CompositeEncoder.encode(value: O) {
encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer<O>, value) encodeSerializableElement(descriptor, 1, value::class.serializer() as KSerializer<O>, value)
} }
@ExperimentalSerializationApi @OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: T) { override fun serialize(encoder: Encoder, value: T) {
encoder.encodeStructure(descriptor) { encoder.encodeStructure(descriptor) {
val valueSerializer = value::class.serializer() val valueSerializer = value::class.serializer()
@@ -86,3 +82,7 @@ operator fun <T : Any> TypedSerializer<T>.minusAssign(kClass: KClass<T>) {
inline fun <reified T : Any> TypedSerializer( inline fun <reified T : Any> TypedSerializer(
presetSerializers: Map<String, KSerializer<out T>> = emptyMap() presetSerializers: Map<String, KSerializer<out T>> = emptyMap()
) = TypedSerializer(T::class, presetSerializers) ) = TypedSerializer(T::class, presetSerializers)
inline fun <reified T : Any> TypedSerializer(
vararg presetSerializers: Pair<String, KSerializer<out T>>
) = TypedSerializer(presetSerializers.toMap())

View File

@@ -23,6 +23,7 @@ String[] includes = [
":ktor:common", ":ktor:common",
":ktor:client", ":ktor:client",
":coroutines", ":coroutines",
":coroutines:compose",
":android:recyclerview", ":android:recyclerview",
":android:alerts:common", ":android:alerts:common",
":android:alerts:recyclerview", ":android:alerts:recyclerview",
@@ -38,11 +39,13 @@ String[] includes = [
includes.each { originalName -> includes.each { originalName ->
String projectDirectory = "${rootProject.projectDir.getAbsolutePath()}${originalName.replaceAll(":", File.separator)}" String projectDirectory = "${rootProject.projectDir.getAbsolutePath()}${originalName.replace(":", File.separator)}"
String projectName = "${rootProject.name}${originalName.replaceAll(":", ".")}" String projectName = "${rootProject.name}${originalName.replace(":", ".")}"
String projectIdentifier = ":${projectName}" String projectIdentifier = ":${projectName}"
include projectIdentifier include projectIdentifier
ProjectDescriptor project = project(projectIdentifier) ProjectDescriptor project = project(projectIdentifier)
project.name = projectName project.name = projectName
project.projectDir = new File(projectDirectory) project.projectDir = new File(projectDirectory)
} }
enableFeaturePreview("VERSION_CATALOGS")