Compare commits

...

165 Commits

Author SHA1 Message Date
374a5a1a37
Merge pull request #460 from InsanusMokrassar/0.21.1
0.21.1
2024-06-25 22:11:26 +06:00
827cf32c1b modules initialization 2024-06-25 22:04:25 +06:00
98ad6dbeb2 start 0.21.1 2024-06-25 22:03:30 +06:00
63c8f642ec
Merge pull request #451 from InsanusMokrassar/0.21.0
0.21.0
2024-06-20 01:53:51 +06:00
3bfe64f797
Update CHANGELOG.md 2024-06-20 01:53:42 +06:00
ec98029467 get back old function to break less API 2024-06-19 20:03:14 +06:00
ab58478686 Revert "fix of build"
This reverts commit 90247667d1.
2024-06-19 19:50:19 +06:00
90247667d1 fix of build 2024-06-19 19:50:00 +06:00
e661185534 fill changelog 2024-06-19 19:36:02 +06:00
d73e4e8e1f migrate onto 0.21.0 2024-06-19 19:30:16 +06:00
a6905e73cb small improvements 2024-06-17 01:31:50 +06:00
93b054d55e add .kotlin in gitignore 2024-06-16 22:27:07 +06:00
5db4c5c717 improvements in coroutines launch safely 2024-06-15 17:37:26 +06:00
5e04521929 start 0.20.53 2024-06-15 17:32:15 +06:00
30440b4ed1
Merge pull request #457 from InsanusMokrassar/0.20.52
0.20.52
2024-06-14 23:57:26 +06:00
09bb90604d weaks rework 2024-06-14 17:09:04 +06:00
4d55ec6f36 add separated WeakScope 2024-06-14 16:58:40 +06:00
f373524f34 a little improve of weak extensions and add tests for weak scopes 2024-06-14 16:53:56 +06:00
0398a7bebd start 0.20.52 2024-06-14 16:36:52 +06:00
fa8a5bcd97
Merge pull request #453 from InsanusMokrassar/0.20.51
0.20.51
2024-05-26 23:53:46 +06:00
758a92410b more fixes to god of fixes in paginations 2024-05-26 23:51:46 +06:00
77f56c5dda make improvements in pagination 2024-05-26 23:35:24 +06:00
72f2fe3cc3 add "indices" cache in pagination with reversed functionality 2024-05-26 23:01:23 +06:00
7e0e520f03 add note about android fragment update 2024-05-26 23:00:11 +06:00
92c4784e42 paginations improvements 2024-05-26 22:59:19 +06:00
a634229dc0 start 0.20.51 2024-05-26 19:46:53 +06:00
de3f36ef2a
Merge pull request #445 from InsanusMokrassar/0.20.50
0.20.50
2024-05-12 21:58:07 +06:00
c623a265ee fill changelog and remove redundant parts in coroutines compose build.gradle 2024-05-12 21:51:20 +06:00
e75125f6df now SmartMutex and SmartSemaphore (and SmartRWLocker as a result) works on SpecialMutableStateFlow 2024-05-12 21:44:33 +06:00
4901a8844c complete rework of states 2024-05-12 21:34:17 +06:00
a1854b68d8 almost updated dependencies and modules hierarchy 2024-05-12 19:17:16 +06:00
aee4a6243b start 0.20.50 2024-05-12 17:01:22 +06:00
4c0cb73d69
Merge pull request #438 from InsanusMokrassar/0.20.49
0.20.49
2024-05-04 23:04:10 +06:00
c6eab182f6 new crud repo extensions on* 2024-05-02 18:20:12 +06:00
3e3fbd97eb diff/applyDiff 2024-05-02 17:18:43 +06:00
feb695caa7 start 0.20.49 2024-05-02 15:08:53 +06:00
1bd46d9651
Merge pull request #437 from InsanusMokrassar/0.20.48
0.20.48
2024-05-02 14:09:47 +06:00
32eabb6b36 update dependencies 2024-05-02 14:06:46 +06:00
9bfe6dc6d8 start 0.20.48 2024-05-02 13:58:12 +06:00
3f366aeea4
Merge pull request #434 from InsanusMokrassar/0.20.47
0.20.47
2024-05-01 18:16:58 +06:00
4338fd46f2 update android gradle plugin 2024-05-01 18:14:28 +06:00
36974f5b49 update dependencies 2024-05-01 17:47:47 +06:00
d48f767408 start 0.20.47 2024-05-01 17:47:02 +06:00
bd2558e852
Merge pull request #430 from InsanusMokrassar/0.20.46
0.20.46
2024-04-27 15:41:22 +06:00
9f7c963cd5 add klock in common and DateTimeSerializer 2024-04-27 15:34:52 +06:00
2db0eadbfe start 0.20.46 2024-04-27 15:17:40 +06:00
580c7b8842
Merge pull request #429 from InsanusMokrassar/0.20.45
0.20.45
2024-04-22 11:34:25 +06:00
88fba347ea update android core ktx 2024-04-22 11:27:07 +06:00
c8c5255e62 start 0.20.45 2024-04-22 11:25:02 +06:00
01a1a2760a
Merge pull request #427 from InsanusMokrassar/0.20.44
0.20.44
2024-04-15 00:30:41 +06:00
6d34df8d48 update dependencies 2024-04-15 00:29:22 +06:00
b124ea65e1 start 0.20.44 2024-04-15 00:19:11 +06:00
f3214347a9
Merge pull request #424 from InsanusMokrassar/0.20.43
0.20.43
2024-04-08 22:13:09 +06:00
6b8724b59f update dependencies 2024-04-08 21:56:02 +06:00
ac3e378edf start 0.20.43 2024-04-08 21:53:43 +06:00
5d31fd1c91
Merge pull request #420 from InsanusMokrassar/0.20.42
0.20.42
2024-04-05 15:09:27 +06:00
046fe1ec08
Update Processor.kt 2024-04-04 23:36:59 +06:00
721873c843 repos models generator improvements 2024-04-04 19:28:59 +06:00
f6ffbfc10a start 0.20.42 2024-04-04 19:28:11 +06:00
4e91649e0a
Merge pull request #419 from InsanusMokrassar/0.20.41
0.20.41
2024-04-01 19:02:13 +06:00
4f7f8abec4 improve exposed repos 2024-04-01 18:20:05 +06:00
6370562dbc start 0.20.41 2024-04-01 18:12:25 +06:00
94e2e67522
Merge pull request #418 from InsanusMokrassar/0.20.40
0.20.40
2024-03-31 12:49:35 +06:00
1d8330015d update changelog 2024-03-31 12:48:55 +06:00
e5017b0258 update dependencies 2024-03-29 20:10:32 +06:00
cd412ca31b start 0.20.40 2024-03-29 20:03:36 +06:00
d2b6473095
Merge pull request #412 from InsanusMokrassar/0.20.39
0.20.39
2024-03-14 21:25:28 +06:00
ab42507275
Update CHANGELOG.md 2024-03-14 21:05:23 +06:00
dcef844e20
Update libs.versions.toml 2024-03-14 10:58:08 +06:00
3244708c1b
Update gradle.properties 2024-03-14 10:44:57 +06:00
73ac1f1741
Merge pull request #405 from InsanusMokrassar/0.20.38
0.20.38
2024-03-06 02:05:30 +06:00
f104e9f352 update ktor dependency 2024-03-06 01:51:48 +06:00
393c9a7d06 start 0.20.38 2024-03-06 01:50:34 +06:00
ea497ea488
Merge pull request #404 from InsanusMokrassar/renovate/jb.dokka
Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.20
2024-03-05 11:07:09 +06:00
renovate[bot]
5add89cad0
Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.20 2024-03-04 15:11:31 +00:00
2db447d2ef
Merge pull request #402 from InsanusMokrassar/0.20.37
0.20.37
2024-03-01 03:25:36 +06:00
31c83813e6 update dependencies 2024-03-01 03:15:25 +06:00
27483a282d start 0.20.37 2024-03-01 03:06:26 +06:00
48b816aa22
Merge pull request #396 from InsanusMokrassar/0.20.36
0.20.36
2024-02-26 17:10:05 +06:00
0065f94f52 update dependencies 2024-02-26 16:55:18 +06:00
ccc0002eb2 improve of actualize all 2024-02-21 00:05:57 +06:00
15a2eee141 start 0.20.36 2024-02-20 22:52:02 +06:00
b9faac71e5
Merge pull request #394 from InsanusMokrassar/0.20.35
0.20.35
2024-02-16 19:11:27 +06:00
0a4465de33 remove API dumps 2024-02-16 19:09:55 +06:00
f9dfd09628 update dependencies 2024-02-16 19:01:46 +06:00
8638d7afce start 0.20.35 2024-02-16 18:50:35 +06:00
8311793a43
Merge pull request #392 from InsanusMokrassar/0.20.34
0.20.34
2024-02-15 16:32:41 +06:00
0d552cfcd2 fix build 2024-02-15 16:18:31 +06:00
4b0f20dbd1 improve default 'set' of KeyValuesRepo 2024-02-15 15:36:32 +06:00
cf531c949d start 0.20.34 2024-02-15 15:14:38 +06:00
ba5c5f17d5
Merge pull request #391 from InsanusMokrassar/0.20.33
0.20.33
2024-02-12 14:42:31 +06:00
35825ad9b7 improve HEXAColorTests 2024-02-12 14:41:55 +06:00
b1eb26a89e fixes and testsAdditions in HEXAColor 2024-02-12 14:25:45 +06:00
c9d04b6698 add ahex to color 2024-02-12 13:58:06 +06:00
496133e014 start 0.20.33 2024-02-12 13:32:30 +06:00
f2857ee2be
Merge pull request #388 from InsanusMokrassar/0.20.32
0.20.32
2024-02-10 17:14:04 +06:00
22541f2d5e
Update CHANGELOG.md 2024-02-10 15:40:59 +06:00
e235c52b6c
Update libs.versions.toml 2024-02-10 15:39:48 +06:00
e89b8c72dd
Update gradle-wrapper.properties 2024-02-08 10:40:43 +06:00
a374e53a3a update StringResource 2024-02-06 19:56:44 +06:00
afb066c4ee start 0.20.32 2024-02-06 19:55:34 +06:00
f05761d4a5
Merge pull request #386 from InsanusMokrassar/0.20.31
0.20.31
2024-01-31 18:10:53 +06:00
76adc9ea33 update ktor 2024-01-31 17:37:27 +06:00
99dd291413 start 0.20.31 2024-01-31 17:36:30 +06:00
f904eb27e1
Merge pull request #384 from InsanusMokrassar/0.20.30
0.20.30
2024-01-31 17:27:17 +06:00
eeb8214812 update exposed 2024-01-31 17:05:43 +06:00
f7215b039e start 0.20.30 2024-01-31 17:04:38 +06:00
c07fe5a0f9
Merge pull request #382 from InsanusMokrassar/0.20.29
0.20.29
2024-01-30 23:03:30 +06:00
0d28cb6e20 update dependencies 2024-01-30 21:18:49 +06:00
a1a17bfd1f start 0.20.29 2024-01-30 20:21:45 +06:00
f386f09592
Merge pull request #377 from InsanusMokrassar/0.20.28
0.20.28
2024-01-19 12:59:11 +06:00
a47e17fe6e downgrade kotlin and compose 2024-01-18 23:19:17 +06:00
01dc3b63ff start 0.20.28 2024-01-18 23:12:36 +06:00
2d97e0699e
Merge pull request #372 from InsanusMokrassar/0.20.27
0.20.27
2024-01-16 12:43:08 +06:00
75f514d99b update github release 2024-01-16 12:38:53 +06:00
9a687cfc1c update dependencies 2024-01-16 12:19:30 +06:00
14edf8b6b7 start 0.20.27 2024-01-16 12:13:07 +06:00
23aa2d8917
Merge pull request #371 from InsanusMokrassar/0.20.26
0.20.26
2024-01-11 23:29:05 +06:00
7651388b5c update exposed version 2024-01-11 23:09:17 +06:00
805ab32b24 improvements in resources 2024-01-11 23:01:10 +06:00
cc623b1097 start 0.20.26 2024-01-11 23:00:14 +06:00
1420416b3e
Merge pull request #369 from InsanusMokrassar/0.20.25
0.20.25
2024-01-04 20:34:15 +06:00
9a0b67f938 fix of tests 2024-01-04 20:30:09 +06:00
303e1e6281 add rgb and rgba representations of HEXAColor 2024-01-04 20:20:55 +06:00
ff59b0cc9c add colors module 2024-01-04 19:53:43 +06:00
be5d2ee715 start 0.20.25 2024-01-04 18:14:09 +06:00
8dd2e3f6f9
Merge pull request #366 from InsanusMokrassar/0.20.24
0.20.24
2024-01-04 10:26:00 +06:00
2eedd196d2 update changelog 2024-01-04 10:25:50 +06:00
759a3f2784 make it possible to use encoder inside of serialize callback 2024-01-04 10:24:39 +06:00
386fa830c3 update soywiz dependencies and downgrade ksp 2024-01-04 09:58:03 +06:00
c382423d77 upgrade MapperSerializer 2023-12-31 13:44:50 +06:00
bb466ce66c start 0.20.24 2023-12-31 13:38:57 +06:00
72cd3dd8a1
Merge pull request #365 from InsanusMokrassar/renovate/ksp
Update ksp to v1.9.22-1.0.16
2023-12-25 21:53:05 +06:00
renovate[bot]
595cedaaf1
Update ksp to v1.9.22-1.0.16 2023-12-25 10:15:44 +00:00
eeaceb6cf5
Merge pull request #362 from InsanusMokrassar/0.20.23
0.20.23
2023-12-22 02:43:14 +06:00
1bd671685b update dependencies and remove redundant usages of old IetfLanguageCode 2023-12-21 23:25:36 +06:00
48d3fe41f2 a lot of improvements in language codes 2023-12-21 15:41:11 +06:00
7ab21871cd Revert "add klock module"
This reverts commit 65d01b1fb3.
2023-12-21 14:05:27 +06:00
65d01b1fb3 add klock module 2023-12-17 23:37:45 +06:00
6230accb68 start 0.20.23 2023-12-17 22:30:25 +06:00
c6ed821934
Merge pull request #360 from InsanusMokrassar/0.20.22
0.20.22
2023-12-14 23:57:51 +06:00
10e03bb951 allow to create own Diff with constructor 2023-12-14 23:56:56 +06:00
aa4f392948 start 0.20.22 2023-12-14 23:52:28 +06:00
f51b59ec02
Update libs.versions.toml 2023-12-14 13:47:34 +06:00
8c76834ae4
Merge pull request #359 from InsanusMokrassar/0.20.21
0.20.21
2023-12-12 23:49:26 +06:00
4a454f3d67 add opportunity to use translation with default locale 2023-12-12 23:45:22 +06:00
151aa1863d update gradle wrapper 2023-12-12 21:41:53 +06:00
3bf6896296 get back translations with android and java resources packages 2023-12-12 21:41:09 +06:00
0d01561476 optimizations and improvements in resources 2023-12-12 21:39:58 +06:00
f6ded92251 init resources module 2023-12-12 20:58:08 +06:00
d01b735cc6 start 0.20.21 2023-12-12 20:21:37 +06:00
6c12001080
Merge pull request #358 from InsanusMokrassar/0.20.20
0.20.20
2023-12-12 00:12:34 +06:00
1afbf03606 improvements in AbstractExposedCRUDRepo 2023-12-12 00:01:58 +06:00
f6ef5c61c5 start 0.20.20 2023-12-11 23:54:25 +06:00
c18fee8107 update gradle publishing scripts
one more potential ix of publishing scripts

drop new configs of publish scripts

revert publish scripts and update gradle properties
2023-12-09 20:38:44 +06:00
d9df7a4384
Revert gradle wrapper version 2023-12-09 01:38:45 +06:00
87c2230e8e revert xmx 2500 2023-12-08 23:42:26 +06:00
da7eb6de0a change xmx 2023-12-08 21:34:05 +06:00
ed3815118f update jvmargs of gradle 2023-12-08 19:34:47 +06:00
be726f42bd
Merge pull request #356 from InsanusMokrassar/0.20.19
update publishing scripts
2023-12-08 16:06:22 +06:00
a91006132f update publishing scripts 2023-12-08 16:06:07 +06:00
9a9f741a0b
Merge pull request #355 from InsanusMokrassar/0.20.19
0.20.19
2023-12-08 16:03:05 +06:00
5028f130e9 update dependencies 2023-12-08 15:58:16 +06:00
77fa019651 start 0.20.19 2023-12-08 15:56:44 +06:00
9715da9384
Merge pull request #353 from InsanusMokrassar/0.20.18
0.20.18
2023-12-04 16:13:33 +06:00
114 changed files with 5211 additions and 3153 deletions

5
.gitignore vendored
View File

@ -14,3 +14,8 @@ local.properties
kotlin-js-store kotlin-js-store
publishing.sh publishing.sh
local.*
local/
.kotlin/

View File

@ -1,5 +1,255 @@
# Changelog # Changelog
## 0.21.1
* `KSP`:
* Module has been initialized
* `Generator`:
* Module has been initialized
* `Sealed`:
* Module has been initialized
## 0.21.0
**THIS UPDATE CONTAINS BREAKING CHANGES IN `safely*`-ORIENTED FUNCTIONS**
* `Coroutines`:
* **All `safely` functions lost their `supervisorScope` in favor to wrapping `runCatching`**
* `runCatchingSafely` is the main handling function of all `safely` functions
* `launchSafely*` and `asyncSafely*` blocks lost `CoroutineScope` as their receiver
## 0.20.52
* `Coroutines`:
* Small rework of weak jobs: add `WeakScope` factory, rename old weal extensions and add kdocs
## 0.20.51
* `Versions`:
* `Android Fragment`: `1.7.0` -> `1.7.1`
* `Pagination`:
* Add `Pagination.nextPageIfTrue` and `Pagination.thisPageIftrue` extensions to get the page according to condition
pass status
* Add `PaginationResult.nextPageIfNotEmptyOrLastPage` and `PaginationResult.thisPageIfNotEmptyOrLastPage`
* Change all `doForAll` and `getAll` extensions fo pagination to work basing on `nextPageIfNotEmptyOrLastPage` and
`thisPageIfNotEmptyOrLastPage`
## 0.20.50
* `Versions`:
* `Coroutines`: `1.8.0` -> `1.8.1`
* `KSLog`: `1.3.3` -> `1.3.4`
* `Exposed`: `0.50.0` -> `0.50.1`
* `Ktor`: `2.3.10` -> `2.3.11`
* A lot of inline functions became common functions due to inline with only noinline callbacks in arguments leads to
low performance
* `Coroutines`:
* `SmartMutex`, `SmartSemaphore` and `SmartRWLocker` as their user changed their state flow to `SpecialMutableStateFlow`
## 0.20.49
* `Repos`:
* `Common`:
* All `Repo`s get `diff` extensions
* `KeyValueRepo` and `KeyValuesRepo` get `applyDiff` extension
* Add new extensions `on*` flows for crud repos
## 0.20.48
* `Versions`:
* `Android Core KTX`: `1.13.0` -> `1.13.1`
* `AndroidX Fragment`: `1.6.2` -> `1.7.0`
## 0.20.47
* `Versions`:
* `Exposed`: `0.49.0` -> `0.50.0`
## 0.20.46
* `Common`:
* Now this repo depends on `klock`
* Add new object-serializer `DateTimeSerializer` for `klock` serializer
## 0.20.45
* `Versions`:
* `Android Core KTX`: `1.12.0` -> `1.13.0`
## 0.20.44
* `Versions`:
* `Compose`: `1.6.1` -> `1.6.2`
* `Koin`: `3.5.4` -> `3.5.6`
## 0.20.43
* `Versions`:
* `Ktor`: `2.3.9` -> `2.3.10`
* `Koin`: `3.5.3` -> `3.5.4`
## 0.20.42
* `Repos`:
* `Generator`:
* Improvements
## 0.20.41
* `Repos`:
* `Exposed`:
* `AbstractExposedKeyValueRepo`, `ExposedKeyValueRepo`, `AbstractExposedKeyValuesRepo`, `ExposedKeyValuesRepo` got opportunity to setup some part of their flows
## 0.20.40
* `Versions`:
* `KSLog`: `1.3.2` -> `1.3.3`
* `Exposed`: `0.48.0` -> `0.49.0`
* `UUID`: `0.8.2` -> `0.8.4`
## 0.20.39
* `Versions`:
* `Kotlin`: `1.9.22` -> `1.9.23`
* `Korlibs`: `5.3.2` -> `5.4.0`
* `Okio`: `3.8.0` -> `3.9.0`
* `Compose`: `1.6.0` -> `1.6.1`
* `ComposeMaterial3`: `1.2.0` -> `1.2.1`
## 0.20.38
* `Versions`:
* `Ktor`: `2.3.8` -> `2.3.9`
## 0.20.37
* `Versions`:
* `Compose`: `1.5.12` -> `1.6.0`
* `Exposed`: `0.47.0` -> `0.48.0`
## 0.20.36
* `Versions`:
* `Serialization`: `1.6.2` -> `1.6.3`
* `Korlibs`: `5.3.1` -> `5.3.2`
* `Repos`:
* `Cache`:
* Improve work and functionality of `actualizeAll` and subsequent functions
* All internal repos `invalidate`/`actualizeAll` now use common `actualizeAll` functions
## 0.20.35
* `Versions`:
* `Coroutines`: `1.7.3` -> `1.8.0`
* `Material3`: `1.1.2` -> `1.2.0`
## 0.20.34
* `Repos`:
* `Common`:
* Improve default `set` realization of `KeyValuesRepo`
## 0.20.33
* `Colors`
* `Common`:
* Add opportunity to use `HEXAColor` with `ahex` colors
## 0.20.32
* `Versions`:
* `Okio`: `3.7.0` -> `3.8.0`
* `Resources`:
* Make `StringResource` serializable
* Add several variants of builder usages
## 0.20.31
* `Versions`:
* `Ktor`: `2.3.7` -> `2.3.8`
## 0.20.30
* `Versions`:
* `Exposed`: `0.46.0` -> `0.47.0`
## 0.20.29
* `Versions`:
* `Kotlin`: `1.9.21` -> `1.9.22`
* `Compose`: `1.5.11` -> `1.5.12`
* `Korlibs`: `5.3.0` -> `5.3.1`
## 0.20.28
* `Versions`:
* `Kotlin`: `1.9.22` -> `1.9.21` (downgrade)
* `Compose`: `1.6.0-dev13691` -> `1.5.11` (downgrade)
## 0.20.27
* `Versions`:
* `Kotlin`: `1.9.21` -> `1.9.22`
* `Compose`: `1.5.11` -> `1.6.0-dev13691`
## 0.20.26
* `Versions`:
* `Exposed`: `0.45.0` -> `0.46.0`. **This update brinds new api deprecations in exposed**
* `Resources`:
* Add opportunity to get default translation by passing `null` as `IetfLang` argument
* Add several useful extensions to get translations in `JS` target
## 0.20.25
* `Colors`:
* `Common`:
* Module inited
## 0.20.24
**Since this version depdendencies of klock and krypto replaced with `com.soywiz.korge:korlibs-time` and `com.soywiz.korge:korlibs-crypto`**
* `Versions`:
* `Klock` (since now `KorlibsTime`): `4.0.10` -> `5.3.0`
* `Krypto` (since now `KorlibsCrypto`): `4.0.10` -> `5.3.0`
* `Serialization`:
* `Mapper`:
* `Mapper` pass decoder into callback of deserialization strategy
* `Mapper` pass encoder into callback of serialization strategy
## 0.20.23
* `Versions`:
* `Koin`: `3.5.0` -> `3.5.3`
* `Okio`: `3.6.0` -> `3.7.0`
* `LanguageCodes`:
* Fixes in intermediate language codes (like `Chinese.Hans`)
* Rename `IetfLanguageCode` to `IetfLang`
* Rename all subsequent functions (including serializer)
* New lazy properties `knownLanguageCodesMap`, `knownLanguageCodesMapByLowerCasedKeys` and several others
## 0.20.22
* `Common`:
* Add opportunity to create own `Diff` with base constructor
## 0.20.21
* `Resources`:
* Inited
## 0.20.20
* `Repos`:
* `Exposed`:
* Add opportunity for setup flows in `AbstractExposedCRUDRepo`
## 0.20.19
* `Versions`:
* `Ktor`: `2.3.6` -> `2.3.7`
## 0.20.18 ## 0.20.18
* `Coroutines`: * `Coroutines`:

View File

@ -7,9 +7,6 @@ import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ContentAlpha
import androidx.compose.material.IconButton
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.KeyboardArrowUp
@ -75,7 +72,7 @@ fun NumberPicker(
} }
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
val animatedStateValue = animatedStateValue(animatedOffset.value) val animatedStateValue = animatedStateValue(animatedOffset.value)
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled) val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
val inputFieldShown = if (allowUseManualInput) { val inputFieldShown = if (allowUseManualInput) {
remember { mutableStateOf(false) } remember { mutableStateOf(false) }

View File

@ -5,7 +5,6 @@ import androidx.compose.animation.core.exponentialDecay
import androidx.compose.foundation.gestures.* import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.KeyboardArrowUp
@ -62,9 +61,7 @@ fun <T> SetPicker(
(index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt()) (index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
} }
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
val boxOffset = (indexAnimatedOffset * halvedNumbersColumnHeightPx) - coercedAnimatedOffset val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
val scrollState = rememberScrollState()
Column( Column(
modifier = modifier modifier = modifier

View File

@ -27,7 +27,7 @@ allprojects {
mavenCentral() mavenCentral()
google() google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" } maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
} }
// temporal crutch until legacy tests will be stabled or legacy target will be removed // temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

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

View File

@ -0,0 +1,174 @@
package dev.inmo.micro_utils.colors.common
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
import kotlin.math.floor
/**
* Wrapper for RGBA colors. Receiving [UInt] in main constructor. Each part in main constructor
* configured with `0x00 - 0xff` range. Examples:
*
* * Red: `0xff0000ffu`
* * Red (0.5 capacity): `0xff000088u`
*
* Anyway it is recommended to use
*
* @param hexaUInt rgba [UInt] in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
*/
@Serializable
@JvmInline
value class HEXAColor (
val hexaUInt: UInt
) : Comparable<HEXAColor> {
/**
* @returns [hexaUInt] as a string with format `#FFEEBBAA` where FF - red, EE - green, BB - blue and AA - alpha
*/
val hexa: String
get() = "#${hexaUInt.toString(16).padStart(8, '0')}"
/**
* @returns [hexaUInt] as a string with format `#FFEEBB` where FF - red, EE - green and BB - blue
*/
val hex: String
get() = hexa.take(7)
/**
* @returns [hexaUInt] as a string with format `#AAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue
*/
val ahex: String
get() = "#${a.toString(16).padStart(2, '2')}${hex.drop(1)}"
val rgba: String
get() = "rgba($r,$g,$b,${aOfOne.toString().take(5)})"
val rgb: String
get() = "rgb($r,$g,$b)"
val shortHex: String
get() = "#${r.shortPart()}${g.shortPart()}${b.shortPart()}"
val shortHexa: String
get() = "$shortHex${a.shortPart()}"
val rgbUInt: UInt
get() = (hexaUInt / 256u)
val rgbInt: Int
get() = rgbUInt.toInt()
val ahexUInt
get() = (a * 0x1000000).toUInt() + rgbUInt
val r: Int
get() = ((hexaUInt and 0xff000000u) / 0x1000000u).toInt()
val g: Int
get() = ((hexaUInt and 0x00ff0000u) / 0x10000u).toInt()
val b: Int
get() = ((hexaUInt and 0x0000ff00u) / 0x100u).toInt()
val a: Int
get() = ((hexaUInt and 0x000000ffu)).toInt()
val aOfOne: Float
get() = a.toFloat() / (0xff)
init {
require(hexaUInt in 0u ..0xffffffffu)
}
constructor(r: Int, g: Int, b: Int, a: Int) : this(
((r * 0x1000000).toLong() + g * 0x10000 + b * 0x100 + a).toUInt()
) {
require(r in 0 ..0xff)
require(g in 0 ..0xff)
require(b in 0 ..0xff)
require(a in 0 ..0xff)
}
constructor(r: Int, g: Int, b: Int, aOfOne: Float = 1f) : this(
r = r, g = g, b = b, a = (aOfOne * 0xff).toInt()
)
override fun toString(): String {
return hexa
}
override fun compareTo(other: HEXAColor): Int = (hexaUInt - other.hexaUInt).coerceIn(Int.MIN_VALUE.toUInt(), Int.MAX_VALUE.toLong().toUInt()).toInt()
fun copy(
r: Int = this.r,
g: Int = this.g,
b: Int = this.b,
aOfOne: Float = this.aOfOne
) = HEXAColor(r = r, g = g, b = b, aOfOne = aOfOne)
fun copy(
r: Int = this.r,
g: Int = this.g,
b: Int = this.b,
a: Int
) = HEXAColor(r = r, g = g, b = b, a = a)
companion object {
/**
* Parsing color from [color]
*
* Supported formats samples (on Red color based):
*
* * `#f00`
* * `#f00f`
* * `#ff0000`
* * `#ff0000ff`
* * `rgb(255, 0, 0)`
* * `rgba(255, 0, 0, 1)`
*/
fun parseStringColor(color: String): HEXAColor = when {
color.startsWith("#") -> color.removePrefix("#").let { color ->
when (color.length) {
3 -> color.map { "$it$it" }.joinToString(separator = "", postfix = "ff")
4 -> color.take(3).map { "$it$it" }.joinToString(separator = "", postfix = color.takeLast(1).let { "${it}0" })
6 -> "${color}ff"
8 -> color
else -> error("Malfurmed color string: $color. It is expected that color started with # will contains 3, 6 or 8 valuable parts")
}
}
color.startsWith("rgb(") -> color
.removePrefix("rgb(")
.removeSuffix(")")
.replace(Regex("\\s"), "")
.split(",")
.joinToString("", postfix = "ff") {
it.toInt().toString(16).padStart(2, '0')
}
color.startsWith("rgba(") -> color
.removePrefix("rgba(")
.removeSuffix(")")
.replace(Regex("\\s"), "")
.split(",").let {
it.take(3).map { it.toInt().toString(16).padStart(2, '0') } + (it.last().toFloat() * 0xff).toInt().toString(16).padStart(2, '0')
}
.joinToString("")
else -> color
}.lowercase().toUInt(16).let(::HEXAColor)
/**
* Creates [HEXAColor] from [uint] presume it is in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
*/
fun fromHexa(uint: UInt) = HEXAColor(uint)
/**
* Creates [HEXAColor] from [uint] presume it is in format `0xAAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue`
*/
fun fromAhex(uint: UInt) = HEXAColor(
a = ((uint and 0xff000000u) / 0x1000000u).toInt(),
r = ((uint and 0x00ff0000u) / 0x10000u).toInt(),
g = ((uint and 0x0000ff00u) / 0x100u).toInt(),
b = ((uint and 0x000000ffu)).toInt()
)
/**
* Parsing color from [color]
*
* Supported formats samples (on Red color based):
*
* * `#f00`
* * `#ff0000`
* * `#ff0000ff`
* * `rgb(255, 0, 0)`
* * `rgba(255, 0, 0, 1)`
*/
operator fun invoke(color: String) = parseStringColor(color)
private fun Int.shortPart(): String {
return (floor(toFloat() / 16)).toInt().toString(16)
}
}
}

View File

@ -0,0 +1,209 @@
package dev.inmo.micro_utils.colors.common
import kotlin.math.floor
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class HexColorTests {
val alphaRgbaPrecision = 5
class TestColor(
val color: HEXAColor,
val shortHex: String,
val shortHexa: String,
val hex: String,
val hexa: String,
val ahex: String,
val ahexUInt: UInt,
val rgbUInt: UInt,
val rgb: String,
val rgba: String,
val r: Int,
val g: Int,
val b: Int,
val a: Int,
vararg val additionalRGBAVariants: String
)
val testColors: List<TestColor>
get() = listOf(
TestColor(
color = HEXAColor(hexaUInt = 0xff0000ffu),
shortHex = "#f00",
shortHexa = "#f00f",
hex = "#ff0000",
hexa = "#ff0000ff",
ahex = "#ffff0000",
ahexUInt = 0xffff0000u,
rgbUInt = 0xff0000u,
rgb = "rgb(255,0,0)",
rgba = "rgba(255,0,0,1.0)",
r = 0xff,
g = 0x00,
b = 0x00,
a = 0xff,
"rgba(255,0,0,1)",
),
TestColor(
color = HEXAColor(hexaUInt = 0x00ff00ffu),
shortHex = "#0f0",
shortHexa = "#0f0f",
hex = "#00ff00",
hexa = "#00ff00ff",
ahex = "#ff00ff00",
ahexUInt = 0xff00ff00u,
rgbUInt = 0x00ff00u,
rgb = "rgb(0,255,0)",
rgba = "rgba(0,255,0,1.0)",
r = 0x00,
g = 0xff,
b = 0x00,
a = 0xff,
"rgba(0,255,0,1)"
),
TestColor(
color = HEXAColor(0x0000ffffu),
shortHex = "#00f",
shortHexa = "#00ff",
hex = "#0000ff",
hexa = "#0000ffff",
ahex = "#ff0000ff",
ahexUInt = 0xff0000ffu,
rgbUInt = 0x0000ffu,
rgb = "rgb(0,0,255)",
rgba = "rgba(0,0,255,1.0)",
r = 0x00,
g = 0x00,
b = 0xff,
a = 0xff,
"rgba(0,0,255,1)"
),
TestColor(
color = HEXAColor(0xff000088u),
shortHex = "#f00",
shortHexa = "#f008",
hex = "#ff0000",
hexa = "#ff000088",
ahex = "#88ff0000",
ahexUInt = 0x88ff0000u,
rgbUInt = 0xff0000u,
rgb = "rgb(255,0,0)",
rgba = "rgba(255,0,0,0.533)",
r = 0xff,
g = 0x00,
b = 0x00,
a = 0x88,
),
TestColor(
color = HEXAColor(0x00ff0088u),
shortHex = "#0f0",
shortHexa = "#0f08",
hex = "#00ff00",
hexa = "#00ff0088",
ahex = "#8800ff00",
ahexUInt = 0x8800ff00u,
rgbUInt = 0x00ff00u,
rgb = "rgb(0,255,0)",
rgba = "rgba(0,255,0,0.533)",
r = 0x00,
g = 0xff,
b = 0x00,
a = 0x88,
),
TestColor(
color = HEXAColor(0x0000ff88u),
shortHex = "#00f",
shortHexa = "#00f8",
hex = "#0000ff",
hexa = "#0000ff88",
ahex = "#880000ff",
ahexUInt = 0x880000ffu,
rgbUInt = 0x0000ffu,
rgb = "rgb(0,0,255)",
rgba = "rgba(0,0,255,0.533)",
r = 0x00,
g = 0x00,
b = 0xff,
a = 0x88,
),
TestColor(
color = HEXAColor(0xff000022u),
shortHex = "#f00",
shortHexa = "#f002",
hex = "#ff0000",
hexa = "#ff000022",
ahex = "#22ff0000",
ahexUInt = 0x22ff0000u,
rgbUInt = 0xff0000u,
rgb = "rgb(255,0,0)",
rgba = "rgba(255,0,0,0.133)",
r = 0xff,
g = 0x00,
b = 0x00,
a = 0x22,
),
TestColor(
color = HEXAColor(0x00ff0022u),
shortHex = "#0f0",
shortHexa = "#0f02",
hex = "#00ff00",
hexa = "#00ff0022",
ahex = "#2200ff00",
ahexUInt = 0x2200ff00u,
rgbUInt = 0x00ff00u,
rgb = "rgb(0,255,0)",
rgba = "rgba(0,255,0,0.133)",
r = 0x00,
g = 0xff,
b = 0x00,
a = 0x22,
),
TestColor(
color = HEXAColor(0x0000ff22u),
shortHex = "#00f",
shortHexa = "#00f2",
hex = "#0000ff",
hexa = "#0000ff22",
ahex = "#220000ff",
ahexUInt = 0x220000ffu,
rgbUInt = 0x0000ffu,
rgb = "rgb(0,0,255)",
rgba = "rgba(0,0,255,0.133)",
r = 0x00,
g = 0x00,
b = 0xff,
a = 0x22,
),
)
@Test
fun baseTest() {
testColors.forEach {
assertEquals(it.hex, it.color.hex)
assertEquals(it.hexa, it.color.hexa)
assertEquals(it.ahex, it.color.ahex)
assertEquals(it.rgbUInt, it.color.rgbUInt)
assertEquals(it.ahexUInt, it.color.ahexUInt)
assertEquals(it.shortHex, it.color.shortHex)
assertEquals(it.shortHexa, it.color.shortHexa)
assertEquals(it.rgb, it.color.rgb)
assertTrue(it.rgba == it.color.rgba || it.color.rgba in it.additionalRGBAVariants)
assertEquals(it.r, it.color.r)
assertEquals(it.g, it.color.g)
assertEquals(it.b, it.color.b)
assertEquals(it.a, it.color.a)
assertEquals(it.color, HEXAColor.fromAhex(it.ahexUInt))
}
}
@Test
fun testHexParseColor() {
testColors.forEach {
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.hex))
assertEquals(it.color, HEXAColor.parseStringColor(it.hexa))
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.rgb))
assertTrue(it.color.hexaUInt.toInt() - HEXAColor.parseStringColor(it.rgba).hexaUInt.toInt() in -0x1 .. 0x1, )
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.shortHex))
assertEquals(it.color.copy(a = floor(it.color.a.toFloat() / 16).toInt() * 0x10), HEXAColor.parseStringColor(it.shortHexa))
}
}
}

View File

@ -8,6 +8,11 @@ apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
kotlin { kotlin {
sourceSets { sourceSets {
commonMain {
dependencies {
api libs.klock
}
}
jvmMain { jvmMain {
dependencies { dependencies {
api project(":micro_utils.coroutines") api project(":micro_utils.coroutines")
@ -15,23 +20,11 @@ kotlin {
} }
androidMain { androidMain {
dependencies { dependencies {
api project(":micro_utils.coroutines")
api libs.android.fragment api libs.android.fragment
} }
dependsOn jvmMain
} }
linuxX64Main { nativeMain {
dependencies {
api libs.okio
}
}
mingwX64Main {
dependencies {
api libs.okio
}
}
linuxArm64Main {
dependencies { dependencies {
api libs.okio api libs.okio
} }

View File

@ -1 +0,0 @@
<manifest/>

View File

@ -0,0 +1,25 @@
package dev.inmo.micro_utils.common
import korlibs.time.DateTime
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Serializes [DateTime] as its raw [DateTime.unixMillis] and deserializes in the same way
*/
object DateTimeSerializer : KSerializer<DateTime> {
override val descriptor: SerialDescriptor
get() = Double.serializer().descriptor
override fun deserialize(decoder: Decoder): DateTime {
return DateTime(decoder.decodeDouble())
}
override fun serialize(encoder: Encoder, value: DateTime) {
encoder.encodeDouble(value.unixMillis)
}
}

View File

@ -27,7 +27,7 @@ private inline fun <T> getObject(
* @see calculateDiff * @see calculateDiff
*/ */
@Serializable @Serializable
data class Diff<T> internal constructor( data class Diff<T> @Warning(warning) constructor(
val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>, val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
/** /**
* Old-New values pairs * Old-New values pairs
@ -36,6 +36,10 @@ data class Diff<T> internal constructor(
val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>> val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
) { ) {
fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty() fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty()
companion object {
private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing"
}
} }
fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList()) fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList())

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,6 @@ kotlin {
dependencies { dependencies {
api libs.kt.coroutines.android api libs.kt.coroutines.android
} }
dependsOn(jvmMain)
} }
} }
} }

View File

@ -1 +0,0 @@
<manifest/>

View File

@ -1,46 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.MutableState
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
/**
* This type works like [MutableState], [kotlinx.coroutines.flow.StateFlow] and [kotlinx.coroutines.flow.MutableSharedFlow].
* Based on [SpecialMutableStateFlow]
*/
@Deprecated("Will be removed soon")
class FlowState<T>(
initial: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : MutableState<T>,
SpecialMutableStateFlow<T>(initial, internalScope) {
private var internalValue: T = initial
override var value: T
get() = internalValue
set(value) {
internalValue = value
tryEmit(value)
}
override fun onChangeWithoutSync(value: T) {
internalValue = value
super.onChangeWithoutSync(value)
}
override fun component1(): T = value
override fun component2(): (T) -> Unit = { tryEmit(it) }
override fun tryEmit(value: T): Boolean {
internalValue = value
return super.tryEmit(value)
}
override suspend fun emit(value: T) {
internalValue = value
super.emit(value)
}
}
//fun <T> MutableState<T>.asFlowState(scope: CoroutineScope = CoroutineScope(Dispatchers.Main)) = FlowState(this, scope)

View File

@ -0,0 +1,24 @@
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.test.*
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import org.jetbrains.annotations.TestOnly
import kotlin.test.Test
class FlowStateTests {
@OptIn(ExperimentalTestApi::class)
@Test
@TestOnly
fun simpleTest() = runComposeUiTest {
val flowState = SpecialMutableStateFlow(0)
setContent {
Button({ flowState.value++ }) { Text("Click") }
Text(flowState.collectAsState().value.toString())
}
onNodeWithText(0.toString()).assertExists()
onNodeWithText("Click").performClick()
onNodeWithText(1.toString()).assertExists()
}
}

View File

@ -0,0 +1,44 @@
package dev.inmo.micro_utils.coroutines
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
typealias ExceptionHandler<T> = suspend (Throwable) -> T
/**
* This instance will be used in all calls of [safely] where exception handler has not been passed
*/
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
/**
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
*/
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
try {
defaultSafelyExceptionHandler(it)
} catch (e: Throwable) {
// do nothing
}
}
/**
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandler
*/
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
/**
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandlerKey
*/
class ContextSafelyExceptionHandler(
val handler: ExceptionHandler<Unit>
) : CoroutineContext.Element {
override val key: CoroutineContext.Key<*>
get() = ContextSafelyExceptionHandlerKey
}

View File

@ -16,8 +16,8 @@ class DoWithFirstBuilder<T>(
operator fun plus(block: suspend CoroutineScope.() -> T) { operator fun plus(block: suspend CoroutineScope.() -> T) {
deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block)) deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block))
} }
inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block) fun add(block: suspend CoroutineScope.() -> T) = plus(block)
inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block) fun include(block: suspend CoroutineScope.() -> T) = plus(block)
fun build() = deferreds.toList() fun build() = deferreds.toList()
} }

View File

@ -85,32 +85,32 @@ fun <T, M> Flow<T>.subscribeAsync(
return job return job
} }
inline fun <T, M> Flow<T>.subscribeSafelyAsync( fun <T, M> Flow<T>.subscribeSafelyAsync(
scope: CoroutineScope, scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M, markerFactory: suspend (T) -> M,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend (T) -> Unit block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) { ) = subscribeAsync(scope, markerFactory) {
safely(onException) { safely(onException) {
block(it) block(it)
} }
} }
inline fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync( fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
scope: CoroutineScope, scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M, markerFactory: suspend (T) -> M,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend (T) -> Unit block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) { ) = subscribeAsync(scope, markerFactory) {
safelyWithoutExceptions(onException) { safelyWithoutExceptions(onException) {
block(it) block(it)
} }
} }
inline fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync( fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
scope: CoroutineScope, scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M, markerFactory: suspend (T) -> M,
noinline block: suspend (T) -> Unit block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) { ) = subscribeAsync(scope, markerFactory) {
safelyWithoutExceptions({ /* do nothing */}) { safelyWithoutExceptions({ /* do nothing */}) {
block(it) block(it)

View File

@ -4,46 +4,58 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
typealias ExceptionHandler<T> = suspend (Throwable) -> T
/** /**
* This instance will be used in all calls of [safely] where exception handler has not been passed * Launching [block] in [runCatching]. In case of failure, it will:
*
* * Try to get [ContextSafelyExceptionHandler] from current [coroutineContext] and call its
* [ContextSafelyExceptionHandler.handler] invoke. **Thrown exception from its handler
* will pass out of [runCatchingSafely]**
* * Execute [onException] inside of new [runCatching] and return its result. Throws exception
* will be caught by [runCatching] and wrapped in [Result]
*
* @return [Result] with result of [block] if no exceptions or [Result] from [onException] execution
*/ */
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it } suspend inline fun <T> runCatchingSafely(
onException: ExceptionHandler<T>,
/** block: suspend () -> T
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call ): Result<T> {
*/ return runCatching {
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = { block()
try { }.onFailure {
defaultSafelyExceptionHandler(it) coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(it)
} catch (e: Throwable) { return runCatching {
// do nothing onException(it)
}
} }
} }
/** suspend inline fun <T, R> R.runCatchingSafely(
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions onException: ExceptionHandler<T>,
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key] block: suspend R.() -> T
* ): Result<T> = runCatchingSafely<T>(onException) {
* @see safelyWithContextExceptionHandler block()
* @see ContextSafelyExceptionHandler }
*/
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
/** /**
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls * Launching [runCatchingSafely] with [defaultSafelyExceptionHandler] as `onException` parameter
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandlerKey
*/ */
class ContextSafelyExceptionHandler( suspend inline fun <T> runCatchingSafely(
val handler: ExceptionHandler<Unit> block: suspend () -> T
) : CoroutineContext.Element { ): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
override val key: CoroutineContext.Key<*>
get() = ContextSafelyExceptionHandlerKey suspend inline fun <T, R> R.runCatchingSafely(
block: suspend R.() -> T
): Result<T> = runCatchingSafely<T> {
block()
} }
//suspend inline fun <T, R> T.runCatchingSafely(
// onException: ExceptionHandler<R>,
// block: suspend T.() -> R
//): Result<R> = runCatchingSafely(onException) {
// block()
//}
/** /**
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if * @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
* exists * exists
@ -51,7 +63,7 @@ class ContextSafelyExceptionHandler(
* @see ContextSafelyExceptionHandler * @see ContextSafelyExceptionHandler
* @see ContextSafelyExceptionHandlerKey * @see ContextSafelyExceptionHandlerKey
*/ */
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey] suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
/** /**
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext] * This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
@ -64,7 +76,7 @@ suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSaf
suspend fun <T> safelyWithContextExceptionHandler( suspend fun <T> safelyWithContextExceptionHandler(
contextExceptionHandler: ExceptionHandler<Unit>, contextExceptionHandler: ExceptionHandler<Unit>,
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler, safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T block: suspend () -> T
): T { ): T {
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler -> val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
ContextSafelyExceptionHandler { ContextSafelyExceptionHandler {
@ -78,57 +90,35 @@ suspend fun <T> safelyWithContextExceptionHandler(
} }
/** /**
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions * Calls [runCatchingSafely] and getting the result via [Result.getOrThrow]
* *
* Priorities of [ExceptionHandler]s: * @see runCatchingSafely
*
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
* * [defaultSafelyExceptionHandler]
*
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
* be called [onException]
*
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
* exception will be available for catching
*
* @see defaultSafelyExceptionHandler
* @see safelyWithoutExceptions
* @see safelyWithContextExceptionHandler
*/ */
suspend inline fun <T> safely( suspend inline fun <T> safely(
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, onException: ExceptionHandler<T>,
noinline block: suspend CoroutineScope.() -> T block: suspend () -> T
): T { ): T = runCatchingSafely(onException, block).getOrThrow()
return try {
supervisorScope(block)
} catch (e: Throwable) {
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
onException(e)
}
}
suspend inline fun <T> runCatchingSafely( /**
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, * Calls [safely] with passing of [defaultSafelyExceptionHandler] as `onException`
noinline block: suspend CoroutineScope.() -> T *
): Result<T> = runCatching { * @see runCatchingSafely
safely(onException, block) */
} suspend inline fun <T> safely(
block: suspend () -> T
): T = safely(defaultSafelyExceptionHandler, block)
suspend inline fun <T, R> R.safely(
block: suspend R.() -> T
): T = safely<T> { block() }
suspend inline fun <T, R> T.runCatchingSafely( @Deprecated("Renamed", ReplaceWith("runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely"))
noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler, suspend fun <T> safelyWithResult(
noinline block: suspend T.() -> R block: suspend () -> T
): Result<R> = runCatching {
safely(onException) { block() }
}
suspend inline fun <T> safelyWithResult(
noinline block: suspend CoroutineScope.() -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block) ): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
@Deprecated("Renamed", ReplaceWith("this.runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely"))
suspend inline fun <T, R> T.safelyWithResult( suspend fun <T, R> R.safelyWithResult(
noinline block: suspend T.() -> R block: suspend R.() -> T
): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block) ): Result<T> = safelyWithResult<T> { block() }
/** /**
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and * Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
@ -147,21 +137,23 @@ val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of * Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
* result from exception (instead of throwing it, by default always returns null) * result from exception (instead of throwing it, by default always returns null)
*/ */
suspend inline fun <T> safelyWithoutExceptions( suspend fun <T> safelyWithoutExceptions(
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T block: suspend () -> T
): T? = safely(onException, block) ): T? = runCatchingSafely(onException, block).getOrNull()
suspend inline fun <T> runCatchingSafelyWithoutExceptions( suspend fun <T> runCatchingSafelyWithoutExceptions(
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, onException: ExceptionHandler<T?> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T block: suspend () -> T
): Result<T?> = runCatching { ): Result<T?> = runCatchingSafely(onException, block).let {
safelyWithoutExceptions(onException, block) if (it.isFailure) return Result.success<T?>(null)
it
} }
inline fun CoroutineScope( fun CoroutineScopeWithDefaultFallback(
context: CoroutineContext, context: CoroutineContext,
noinline defaultExceptionsHandler: ExceptionHandler<Unit> defaultExceptionsHandler: ExceptionHandler<Unit>
) = CoroutineScope( ) = CoroutineScope(
context + ContextSafelyExceptionHandler(defaultExceptionsHandler) context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
) )

View File

@ -4,38 +4,46 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
inline fun CoroutineScope.launchSafely( fun CoroutineScope.launchSafely(
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> Unit block: suspend CoroutineScope.() -> Unit
) = launch(context, start) { ) = launch(context, start) {
safely(onException, block) runCatchingSafely(onException) {
block()
}
} }
inline fun CoroutineScope.launchSafelyWithoutExceptions( fun CoroutineScope.launchSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull, onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> Unit block: suspend CoroutineScope.() -> Unit
) = launch(context, start) { ) = launch(context, start) {
safelyWithoutExceptions(onException, block) runCatchingSafelyWithoutExceptions(onException) {
block()
}
} }
inline fun <T> CoroutineScope.asyncSafely( fun <T> CoroutineScope.asyncSafely(
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) = async(context, start) { ) = async(context, start) {
safely(onException, block) runCatchingSafely(onException) {
block()
}
} }
inline fun <T> CoroutineScope.asyncSafelyWithoutExceptions( fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) = async(context, start) { ) = async(context, start) {
safelyWithoutExceptions(onException, block) runCatchingSafelyWithoutExceptions(onException) {
block()
}
} }

View File

@ -44,7 +44,7 @@ sealed interface SmartMutex {
* @param locked Preset state of [isLocked] and its internal [_lockStateFlow] * @param locked Preset state of [isLocked] and its internal [_lockStateFlow]
*/ */
class Mutable(locked: Boolean = false) : SmartMutex { class Mutable(locked: Boolean = false) : SmartMutex {
private val _lockStateFlow = MutableStateFlow<Boolean>(locked) private val _lockStateFlow = SpecialMutableStateFlow<Boolean>(locked)
override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow() override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow()
private val internalChangesMutex = Mutex() private val internalChangesMutex = Mutex()

View File

@ -45,7 +45,7 @@ sealed interface SmartSemaphore {
* @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow] * @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow]
*/ */
class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore { class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
private val _freePermitsStateFlow = MutableStateFlow<Int>(permits - acquiredPermits) private val _freePermitsStateFlow = SpecialMutableStateFlow<Int>(permits - acquiredPermits)
override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow() override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow()
private val internalChangesMutex = Mutex(false) private val internalChangesMutex = Mutex(false)

View File

@ -11,76 +11,60 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.internal.SynchronizedObject import kotlinx.coroutines.internal.SynchronizedObject
import kotlinx.coroutines.internal.synchronized import kotlinx.coroutines.internal.synchronized
import kotlin.coroutines.CoroutineContext
/** /**
* Works like [StateFlow], but guarantee that latest value update will always be delivered to * Works like [StateFlow], but guarantee that latest value update will always be delivered to
* each active subscriber * each active subscriber
*/ */
open class SpecialMutableStateFlow<T>( open class SpecialMutableStateFlow<T>(
initialValue: T, initialValue: T
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> { ) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> {
@OptIn(InternalCoroutinesApi::class) @OptIn(InternalCoroutinesApi::class)
private val syncObject = SynchronizedObject() private val syncObject = SynchronizedObject()
protected val internalSharedFlow: MutableSharedFlow<T> = MutableSharedFlow( protected val sharingFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 0,
extraBufferCapacity = 2,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
protected val publicSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 1, replay = 1,
extraBufferCapacity = 1, extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST onBufferOverflow = BufferOverflow.DROP_OLDEST
) )
protected var _value: T = initialValue @OptIn(InternalCoroutinesApi::class)
override var value: T override var value: T = initialValue
get() = _value
set(value) { set(value) {
doOnChangeAction(value) synchronized(syncObject) {
if (field != value) {
field = value
sharingFlow.tryEmit(value)
}
}
} }
protected val job = internalSharedFlow.subscribe(internalScope) {
doOnChangeAction(it)
}
override val replayCache: List<T> override val replayCache: List<T>
get() = publicSharedFlow.replayCache get() = sharingFlow.replayCache
override val subscriptionCount: StateFlow<Int> override val subscriptionCount: StateFlow<Int>
get() = publicSharedFlow.subscriptionCount get() = sharingFlow.subscriptionCount
init {
sharingFlow.tryEmit(initialValue)
}
@OptIn(InternalCoroutinesApi::class)
override fun compareAndSet(expect: T, update: T): Boolean { override fun compareAndSet(expect: T, update: T): Boolean {
return synchronized(syncObject) { if (expect == value) {
if (expect == _value && update != _value) { value = update
doOnChangeAction(update)
}
expect == _value
}
}
protected open fun onChangeWithoutSync(value: T) {
_value = value
publicSharedFlow.tryEmit(value)
}
@OptIn(InternalCoroutinesApi::class)
protected open fun doOnChangeAction(value: T) {
synchronized(syncObject) {
if (_value != value) {
onChangeWithoutSync(value)
}
} }
return expect == value
} }
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
override fun resetReplayCache() = publicSharedFlow.resetReplayCache() override fun resetReplayCache() = sharingFlow.resetReplayCache()
override fun tryEmit(value: T): Boolean { override fun tryEmit(value: T): Boolean {
return internalSharedFlow.tryEmit(value) return compareAndSet(this.value, value)
} }
override suspend fun emit(value: T) { override suspend fun emit(value: T) {
internalSharedFlow.emit(value) compareAndSet(this.value, value)
} }
override suspend fun collect(collector: FlowCollector<T>) = publicSharedFlow.collect(collector) override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector)
} }

View File

@ -10,8 +10,12 @@ fun CoroutineScope.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorJob(additionalContext) ) = coroutineContext.LinkedSupervisorJob(additionalContext)
fun CoroutineScope.LinkedSupervisorScope(
fun CoroutineContext.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null additionalContext: CoroutineContext? = null
) = CoroutineScope( ) = CoroutineScope(
coroutineContext + LinkedSupervisorJob(additionalContext) this + LinkedSupervisorJob(additionalContext)
) )
fun CoroutineScope.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorScope(additionalContext)

View File

@ -4,28 +4,71 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
private fun CoroutineScope.createWeakSubScope() = CoroutineScope(coroutineContext.minusKey(Job)).also { newScope -> /**
coroutineContext.job.invokeOnCompletion { newScope.cancel() } * Created [CoroutineScope] which will [launch] listening of [context] job completing and drop itself. Current weak
* scope **will not** be attached to [context] directly. So, this [CoroutineScope] will not prevent parent one from
* cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status
* of parent [Job]
*/
fun WeakScope(
context: CoroutineContext
) = CoroutineScope(context.minusKey(Job) + Job()).also { newScope ->
newScope.launch {
context.job.join()
newScope.cancel()
}
} }
fun CoroutineScope.weakLaunch( /**
* Created [CoroutineScope] which will [launch] listening of [scope] [CoroutineContext] job completing and drop itself. Current weak
* scope **will not** be attached to [scope] [CoroutineContext] directly. So, this [CoroutineScope] will not prevent parent one from
* cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status
* of parent [Job]
*/
fun WeakScope(
scope: CoroutineScope
) = WeakScope(scope.coroutineContext)
/**
* [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block])
* will be used to [launch] [Job]
*/
fun CoroutineScope.launchWeak(
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit block: suspend CoroutineScope.() -> Unit
): Job { ): Job {
val scope = createWeakSubScope() val scope = WeakScope(this)
val job = scope.launch(context, start, block) val job = scope.launch(context, start, block)
job.invokeOnCompletion { scope.cancel() } job.invokeOnCompletion { scope.cancel() }
return job return job
} }
fun <T> CoroutineScope.weakAsync( /**
* [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block])
* will be used to create [async] [Deferred]
*/
fun <T> CoroutineScope.asyncWeak(
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT, start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Deferred<T> { ): Deferred<T> {
val scope = createWeakSubScope() val scope = WeakScope(this)
val deferred = scope.async(context, start, block) val deferred = scope.async(context, start, block)
deferred.invokeOnCompletion { scope.cancel() } deferred.invokeOnCompletion { scope.cancel() }
return deferred return deferred
} }
@Deprecated("Renamed", ReplaceWith("launchWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.launchWeak"))
fun CoroutineScope.weakLaunch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = launchWeak(context, start, block)
@Deprecated("Renamed", ReplaceWith("asyncWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.asyncWeak"))
fun <T> CoroutineScope.weakAsync(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> = asyncWeak(context, start, block)

View File

@ -0,0 +1,33 @@
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.micro_utils.coroutines.asDeferred
import dev.inmo.micro_utils.coroutines.subscribe
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class SpecialMutableStateFlowTests {
@Test
fun simpleTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0)
specialMutableStateFlow.value = 1
specialMutableStateFlow.first { it == 1 }
assertEquals(1, specialMutableStateFlow.value)
}
@Test
fun specialTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0)
lateinit var subscriberJob: Job
subscriberJob = specialMutableStateFlow.subscribe(this) {
when (it) {
1 -> specialMutableStateFlow.value = 2
2 -> subscriberJob.cancel()
}
}
specialMutableStateFlow.value = 1
subscriberJob.join()
assertEquals(2, specialMutableStateFlow.value)
}
}

View File

@ -0,0 +1,64 @@
import dev.inmo.micro_utils.coroutines.asyncWeak
import dev.inmo.micro_utils.coroutines.launchWeak
import kotlinx.coroutines.*
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertTrue
class WeakJobTests {
@Test
fun testWeakJob() = runTest {
var commonJobDone = false
var weakJobStarted = false
try {
coroutineScope {
launch {
delay(1000)
commonJobDone = true
}
asyncWeak {
weakJobStarted = true
delay(100500L)
error("This must never happen")
}
}.await()
} catch (error: Throwable) {
assertTrue(error is CancellationException)
assertTrue(commonJobDone)
assertTrue(weakJobStarted)
return@runTest
}
error("Cancellation exception has not been thrown")
}
@Test
fun testThatWeakJobsWorksCorrectly() = runTest {
val scope = CoroutineScope(Dispatchers.Default)
lateinit var weakLaunchJob: Job
lateinit var weakAsyncJob: Job
val completeDeferred = Job()
coroutineScope {
weakLaunchJob = launchWeak {
while (isActive) {
delay(100L)
}
}
weakAsyncJob = asyncWeak {
while (isActive) {
delay(100L)
}
}
coroutineContext.job.invokeOnCompletion {
scope.launch {
delay(1000L)
completeDeferred.complete()
}
}
launch { delay(1000L); cancel() }
}
completeDeferred.join()
assertTrue(!weakLaunchJob.isActive)
assertTrue(!weakAsyncJob.isActive)
}
}

View File

@ -1,40 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import org.junit.Test
class WeakJob {
@Test
fun `test that weak jobs works correctly`() {
val scope = CoroutineScope(Dispatchers.Default)
lateinit var weakLaunchJob: Job
lateinit var weakAsyncJob: Job
scope.launchSynchronously {
val completeDeferred = Job()
coroutineScope {
weakLaunchJob = weakLaunch {
while (isActive) {
delay(100L)
}
}
weakAsyncJob = weakAsync {
while (isActive) {
delay(100L)
}
}
coroutineContext.job.invokeOnCompletion {
scope.launch {
delay(1000L)
completeDeferred.complete()
}
}
launch { delay(1000L); cancel() }
}
completeDeferred.join()
}
assert(!weakLaunchJob.isActive)
assert(!weakAsyncJob.isActive)
}
}

View File

@ -1,7 +1,6 @@
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.Optional
import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.* import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
@ -118,23 +117,28 @@ open class DefaultStatesMachine <T: State>(
* [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise * [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise
* [StatesManager.endChain]. * [StatesManager.endChain].
*/ */
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { override fun start(scope: CoroutineScope): Job {
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) { val supervisorScope = scope.LinkedSupervisorScope()
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) } supervisorScope.launchSafelyWithoutExceptions {
} (statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(supervisorScope) {
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { supervisorScope.launch { performStateUpdate(Optional.absent(), it, supervisorScope) }
launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) } }
} statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(supervisorScope) {
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) { removedState -> supervisorScope.launch { performStateUpdate(Optional.presented(it.first), it.second, supervisorScope) }
launch { }
statesJobsMutex.withLock { statesManager.onEndChain.subscribeSafelyWithoutExceptions(supervisorScope) { removedState ->
val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState } supervisorScope.launch {
if (stateInMap === removedState) { statesJobsMutex.withLock {
statesJobs[stateInMap] ?.cancel() val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState }
if (stateInMap === removedState) {
statesJobs[stateInMap] ?.cancel()
}
} }
} }
} }
} }
return supervisorScope.coroutineContext.job
} }
/** /**

View File

@ -18,13 +18,13 @@ if (new File(projectDir, "secret.gradle").exists()) {
githubRelease { githubRelease {
token "${project.property('GITHUB_RELEASE_TOKEN')}" token "${project.property('GITHUB_RELEASE_TOKEN')}"
owner "InsanusMokrassar" owner = "InsanusMokrassar"
repo "MicroUtils" repo = "MicroUtils"
tagName "v${project.version}" tagName = "v${project.version}"
releaseName "${project.version}" releaseName = "${project.version}"
targetCommitish "${project.version}" targetCommitish = "${project.version}"
body getCurrentVersionChangelog() body = getCurrentVersionChangelog()
} }
} }

View File

@ -6,7 +6,7 @@ kotlin.incremental.js=true
#kotlin.experimental.tryK2=true #kotlin.experimental.tryK2=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
org.gradle.jvmargs=-Xmx2500m org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g
# JS NPM # JS NPM
@ -15,5 +15,5 @@ crypto_js_version=4.1.1
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.20.18 version=0.21.1
android_code_version=224 android_code_version=260

View File

@ -1,41 +1,41 @@
[versions] [versions]
kt = "1.9.21" kt = "1.9.23"
kt-serialization = "1.6.2" kt-serialization = "1.6.3"
kt-coroutines = "1.7.3" kt-coroutines = "1.8.1"
kslog = "1.3.1" kslog = "1.3.4"
jb-compose = "1.5.11" jb-compose = "1.6.2"
jb-exposed = "0.45.0" jb-exposed = "0.50.1"
jb-dokka = "1.9.10" jb-dokka = "1.9.20"
korlibs = "4.0.10" korlibs = "5.4.0"
uuid = "0.8.2" uuid = "0.8.4"
ktor = "2.3.6" ktor = "2.3.11"
gh-release = "2.4.1" gh-release = "2.5.2"
koin = "3.5.0" koin = "3.5.6"
okio = "3.6.0" okio = "3.9.0"
ksp = "1.9.21-1.0.15" ksp = "1.9.23-1.0.20"
kotlin-poet = "1.15.2" kotlin-poet = "1.16.0"
versions = "0.50.0" versions = "0.51.0"
android-gradle = "8.2.0" android-gradle = "8.2.0"
dexcount = "4.0.0" dexcount = "4.0.0"
android-coreKtx = "1.12.0" android-coreKtx = "1.13.1"
android-recyclerView = "1.3.2" android-recyclerView = "1.3.2"
android-appCompat = "1.6.1" android-appCompat = "1.6.1"
android-fragment = "1.6.2" android-fragment = "1.7.1"
android-espresso = "3.5.1" android-espresso = "3.5.1"
android-test = "1.1.5" android-test = "1.1.5"
android-compose-material3 = "1.1.2" android-compose-material3 = "1.2.1"
android-props-minSdk = "21" android-props-minSdk = "21"
android-props-compileSdk = "34" android-props-compileSdk = "34"
@ -72,8 +72,8 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" } klock = { module = "com.soywiz.korge:korlibs-time", version.ref = "korlibs" }
krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" } krypto = { module = "com.soywiz.korge:korlibs-crypto", version.ref = "korlibs" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }

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-8.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -68,18 +68,14 @@ publishing {
} }
} }
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) { if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven { maven {
name = "Gitea" name = "InmoNexus"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials(HttpHeaderCredentials) { credentials {
name = "Authorization" username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
authentication {
header(HttpHeaderAuthentication)
} }
} }
@ -121,4 +117,21 @@ if (project.hasProperty("signing.gnupg.keyName")) {
def signingTasks = project.getTasks().withType(Sign.class) def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks) mustRunAfter(signingTasks)
} }
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
} }

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","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"} {"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":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}

View File

@ -0,0 +1,20 @@
plugins {
id "org.jetbrains.kotlin.jvm"
}
apply from: "$publishJvmOnlyPath"
repositories {
mavenCentral()
}
dependencies {
api project(":micro_utils.common")
api libs.kotlin.poet
api libs.ksp
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@ -0,0 +1,49 @@
package dev.inmo.micro_ksp.generator
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.squareup.kotlinpoet.FileSpec
import java.io.File
fun KSClassDeclaration.writeFile(
prefix: String = "",
suffix: String = "",
relatedPath: String = "",
force: Boolean = false,
fileSpecBuilder: () -> FileSpec
) {
val containingFile = containingFile!!
File(
File(
File(containingFile.filePath).parent,
relatedPath
),
"$prefix${simpleName.asString()}$suffix.kt"
).takeIf { force || !it.exists() } ?.apply {
parentFile.mkdirs()
writer().use { writer ->
fileSpecBuilder().writeTo(writer)
}
}
}
fun KSFile.writeFile(
prefix: String = "",
suffix: String = "",
relatedPath: String = "",
force: Boolean = false,
fileSpecBuilder: () -> FileSpec
) {
File(
File(
File(filePath).parent,
relatedPath
),
"$prefix${fileName.dropLastWhile { it != '.' }.removeSuffix(".")}$suffix.kt"
).takeIf { force || !it.exists() } ?.apply {
parentFile.mkdirs()
writer().use { writer ->
fileSpecBuilder().writeTo(writer)
}
}
}

7
ksp/sealed/build.gradle Normal file
View File

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

View File

@ -0,0 +1,21 @@
plugins {
id "org.jetbrains.kotlin.jvm"
}
apply from: "$publishJvmOnlyPath"
repositories {
mavenCentral()
}
dependencies {
api project(":micro_utils.ksp.generator")
api project(":micro_utils.ksp.sealed")
api libs.kotlin.poet
api libs.ksp
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@ -0,0 +1,110 @@
package dev.inmo.micro_utils.ksp.sealed.generator
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.*
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.ksp.toClassName
import dev.inmo.micro_ksp.generator.writeFile
import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround
class Processor(
private val codeGenerator: CodeGenerator
) : SymbolProcessor {
private fun KSClassDeclaration.resolveSubclasses(): List<KSClassDeclaration> {
return (getSealedSubclasses().flatMap {
it.resolveSubclasses()
}.ifEmpty {
sequenceOf(this)
}).toList()
}
@OptIn(KspExperimental::class)
private fun FileSpec.Builder.generateSealedWorkaround(
ksClassDeclaration: KSClassDeclaration,
resolver: Resolver
) {
val subClasses = ksClassDeclaration.resolveSubclasses().distinct()
val subClassesNames = subClasses.filter {
when (it.classKind) {
ClassKind.ENUM_ENTRY,
ClassKind.OBJECT -> true
ClassKind.INTERFACE,
ClassKind.CLASS,
ClassKind.ENUM_CLASS,
ClassKind.ANNOTATION_CLASS -> false
}
}.filter {
it.getAnnotationsByType(GenerateSealedWorkaround.Exclude::class).count() == 0
}.sortedBy {
(it.getAnnotationsByType(GenerateSealedWorkaround.Order::class).firstOrNull()) ?.order ?: 0
}.map {
it.toClassName()
}
val className = ksClassDeclaration.toClassName()
val setType = Set::class.asTypeName().parameterizedBy(
ksClassDeclaration.toClassName()
)
addProperty(
PropertySpec.builder(
"values",
setType
).apply {
modifiers.add(
KModifier.PRIVATE
)
initializer(
CodeBlock.of(
"""setOf(${subClassesNames.joinToString(",\n") {it.simpleNames.joinToString(".")}})"""
)
)
}.build()
)
addFunction(
FunSpec.builder("values").apply {
receiver(ClassName(className.packageName, *className.simpleNames.toTypedArray(), "Companion"))
returns(setType)
addCode(
CodeBlock.of(
"""return values"""
)
)
}.build()
)
}
@OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
(resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach {
val prefix = it.getAnnotationsByType(GenerateSealedWorkaround::class).first().prefix
it.writeFile(prefix = prefix, suffix = "SealedWorkaround") {
FileSpec.builder(
it.packageName.asString(),
"${it.simpleName.getShortName()}SealedWorkaround"
).apply {
addFileComment(
"""
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
TO REGENERATE IT JUST DELETE FILE
ORIGINAL FILE: ${it.containingFile ?.fileName}
""".trimIndent()
)
generateSealedWorkaround(it, resolver)
}.build()
}
}
return emptyList()
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,16 @@
package dev.inmo.micro_utils.ksp.sealed.generator.test
import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround
@GenerateSealedWorkaround
sealed interface Test {
@GenerateSealedWorkaround.Order(2)
object A : Test
@GenerateSealedWorkaround.Exclude
object B : Test
@GenerateSealedWorkaround.Order(0)
object C : Test
// Required for successful sealed workaround generation
companion object
}

View File

@ -0,0 +1,11 @@
// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
// TO REGENERATE IT JUST DELETE FILE
// ORIGINAL FILE: Test.kt
package dev.inmo.micro_utils.ksp.`sealed`.generator.test
import kotlin.collections.Set
private val values: Set<Test> = setOf(Test.C,
Test.A)
public fun Test.Companion.values(): Set<Test> = values

View File

@ -0,0 +1,14 @@
package dev.inmo.microutils.kps.sealed
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
annotation class GenerateSealedWorkaround(
val prefix: String = ""
) {
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
annotation class Order(val order: Int)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
annotation class Exclude
}

View File

@ -15,9 +15,6 @@ kotlin {
api libs.ktor.client api libs.ktor.client
} }
} }
androidMain {
dependsOn jvmMain
}
linuxX64Main { linuxX64Main {
dependencies { dependencies {

View File

@ -12,13 +12,9 @@ kotlin {
dependencies { dependencies {
api internalProject("micro_utils.common") api internalProject("micro_utils.common")
api libs.kt.serialization.cbor api libs.kt.serialization.cbor
api libs.klock
api libs.uuid api libs.uuid
api libs.ktor.io api libs.ktor.io
} }
} }
androidMain {
dependsOn jvmMain
}
} }
} }

View File

@ -41,7 +41,7 @@ class TemporalFilesRoutingConfigurator(
filesMutex: Mutex, filesMutex: Mutex,
onNewFileFlow: Flow<TemporalFileId> onNewFileFlow: Flow<TemporalFileId>
): Job = scope.launchSafelyWithoutExceptions { ): Job = scope.launchSafelyWithoutExceptions {
while (isActive) { while (currentCoroutineContext().isActive) {
val filesWithCreationInfo = filesMap.mapNotNull { (fileId, file) -> val filesWithCreationInfo = filesMap.mapNotNull { (fileId, file) ->
fileId to ((Files.getAttribute(file.toPath(), "creationTime") as? FileTime) ?.toMillis() ?: return@mapNotNull null) fileId to ((Files.getAttribute(file.toPath(), "creationTime") as? FileTime) ?.toMillis() ?: return@mapNotNull null)
} }

View File

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

View File

@ -12,9 +12,12 @@ private val json = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
} }
private const val baseClassName = "IetfLanguageCode" private const val baseClassName = "IetfLang"
private const val oldBaseClassName = "IetfLanguageCode"
private const val unknownBaseClassName = "Unknown$baseClassName" private const val unknownBaseClassName = "Unknown$baseClassName"
private const val baseClassSerializerName = "IetfLanguageCodeSerializer" private const val oldUnknownBaseClassName = "Unknown$oldBaseClassName"
private const val baseClassSerializerName = "${baseClassName}Serializer"
private const val oldBaseClassSerializerName = "IetfLanguageCodeSerializer"
private const val baseClassSerializerAnnotationName = "@Serializable(${baseClassSerializerName}::class)" private const val baseClassSerializerAnnotationName = "@Serializable(${baseClassSerializerName}::class)"
@Serializable @Serializable
@ -78,14 +81,12 @@ private fun printLanguageCodeAndTags(
indents: String = " " indents: String = " "
): String = if (tag.subtags.isEmpty()) { ): String = if (tag.subtags.isEmpty()) {
"""${indents}${baseClassSerializerAnnotationName} """${indents}${baseClassSerializerAnnotationName}
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"; override val withoutDialect: String get() = ${parent ?.title ?.let { "$it.code" } ?: "code"} }""" ${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"${parent ?.let { parent -> "; override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""} }"""
} else { } else {
""" """
${indents}${baseClassSerializerAnnotationName} ${indents}${baseClassSerializerAnnotationName}
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() { ${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
${indents} override val code: String = "${tag.tag}" ${indents} override val code: String = "${tag.tag}"${parent ?.let { parent -> "\n${indents} override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""}
${indents} override val withoutDialect: String
${indents} get() = code
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }} ${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
@ -95,7 +96,7 @@ ${indents}}
""" """
} }
fun buildKtFileContent(tags: List<Tag>): String = """ fun buildKtFileContent(tags: List<Tag>, prePackage: String): String = """
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -106,20 +107,27 @@ import kotlinx.serialization.Serializable
${baseClassSerializerAnnotationName} ${baseClassSerializerAnnotationName}
sealed class $baseClassName { sealed class $baseClassName {
abstract val code: String abstract val code: String
abstract val withoutDialect: String open val parentLang: $baseClassName?
get() = null
open val withoutDialect: String
get() = parentLang ?.code ?: code
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } } ${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
$baseClassSerializerAnnotationName $baseClassSerializerAnnotationName
data class $unknownBaseClassName (override val code: String) : $baseClassName() { data class $unknownBaseClassName (override val code: String) : $baseClassName() {
override val withoutDialect: String = code.takeWhile { it != '-' } override val parentLang = code.dropLastWhile { it != '-' }.removeSuffix("-").takeIf { it.length > 0 } ?.let(::$unknownBaseClassName)
} }
@Deprecated("Renamed", ReplaceWith("$baseClassName.$unknownBaseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName.$unknownBaseClassName"))
val $oldUnknownBaseClassName = $unknownBaseClassName
override fun toString() = code override fun toString() = code
} }
@Deprecated("Renamed", ReplaceWith("$baseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName"))
typealias $oldBaseClassName = $baseClassName
""".trimIndent() """.trimIndent()
fun createStringConverterCode(tags: List<Tag>): String { fun createStringConverterCode(tags: List<Tag>, prePackage: String): String {
fun createDeserializeVariantForTag( fun createDeserializeVariantForTag(
tag: Tag, tag: Tag,
pretitle: String = baseClassName, pretitle: String = baseClassName,
@ -128,19 +136,70 @@ fun createStringConverterCode(tags: List<Tag>): String {
val currentTitle = "$pretitle.${tag.title}" val currentTitle = "$pretitle.${tag.title}"
return """${indents}$currentTitle.code -> $currentTitle${if (tag.subtags.isNotEmpty()) tag.subtags.joinToString("\n", "\n") { createDeserializeVariantForTag(it, currentTitle, indents) } else ""}""" return """${indents}$currentTitle.code -> $currentTitle${if (tag.subtags.isNotEmpty()) tag.subtags.joinToString("\n", "\n") { createDeserializeVariantForTag(it, currentTitle, indents) } else ""}"""
} }
fun createInheritorVariantForTag(
return """fun String.as$baseClassName(): $baseClassName { tag: Tag,
return when (this) { pretitle: String = baseClassName,
${tags.joinToString("\n") { createDeserializeVariantForTag(it) }} indents: String = " "
else -> $baseClassName.${unknownBaseClassName}(this) ): String {
val currentTitle = "$pretitle.${tag.title}"
val subtags = if (tag.subtags.isNotEmpty()) {
tag.subtags.joinToString(",\n", ",\n") { createInheritorVariantForTag(it, currentTitle, "$indents ") }
} else {
""
}
return "${indents}$currentTitle$subtags"
} }
fun createInheritorVariantForMapForTag(
tag: Tag,
pretitle: String = baseClassName,
indents: String = " ",
codeSuffix: String = ""
): String {
val currentTitle = "$pretitle.${tag.title}"
val subtags = if (tag.subtags.isNotEmpty()) {
tag.subtags.joinToString(",\n", ",\n") { createInheritorVariantForMapForTag(it, currentTitle, "$indents ", codeSuffix) }
} else {
""
}
return "${indents}$currentTitle.code${codeSuffix} to $currentTitle$subtags"
}
return """val knownLanguageCodesMap: Map<String, $baseClassName> by lazy {
mapOf(
${tags.joinToString(",\n") { createInheritorVariantForMapForTag(it, indents = " ") }}
)
} }
val knownLanguageCodesMapByLowerCasedKeys: Map<String, $baseClassName> by lazy {
mapOf(
${tags.joinToString(",\n") { createInheritorVariantForMapForTag(it, indents = " ", codeSuffix = ".lowercase()") }}
)
}
val knownLanguageCodes: List<$baseClassName> by lazy {
knownLanguageCodesMap.values.toList()
}
fun String.as$baseClassName(): $baseClassName {
return knownLanguageCodesMap[this] ?: $baseClassName.${unknownBaseClassName}(this)
}
fun String.as${baseClassName}CaseInsensitive(): $baseClassName {
return knownLanguageCodesMapByLowerCasedKeys[this.lowercase()] ?: $baseClassName.${unknownBaseClassName}(this)
}
@Deprecated("Renamed", ReplaceWith("this.as$baseClassName()", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}as$baseClassName"))
fun String.as$oldBaseClassName(): $baseClassName = as$baseClassName()
fun convertTo$baseClassName(code: String) = code.as$baseClassName() fun convertTo$baseClassName(code: String) = code.as$baseClassName()
fun convertTo${baseClassName}CaseInsensitive(code: String) = code.as${baseClassName}CaseInsensitive()
@Deprecated("Renamed", ReplaceWith("convertTo$baseClassName(code)", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}convertTo$baseClassName"))
fun convertTo$oldBaseClassName(code: String) = convertTo$baseClassName(code)
fun $baseClassName(code: String) = code.as$baseClassName() fun $baseClassName(code: String) = code.as$baseClassName()
fun ${baseClassName}CaseInsensitive(code: String) = code.as${baseClassName}CaseInsensitive()
@Deprecated("Renamed", ReplaceWith("$baseClassName(code)", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName"))
fun $oldBaseClassName(code: String) = $baseClassName(code)
""" """
} }
fun createSerializerCode(tags: List<Tag>): String { fun createSerializerCode(tags: List<Tag>, prePackage: String): String {
return """import kotlinx.serialization.KSerializer return """import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
@ -153,16 +212,20 @@ object $baseClassSerializerName : KSerializer<$baseClassName> {
return $baseClassName(decoder.decodeString()) return $baseClassName(decoder.decodeString())
} }
override fun serialize(encoder: Encoder, value: IetfLanguageCode) { override fun serialize(encoder: Encoder, value: $baseClassName) {
encoder.encodeString(value.code) encoder.encodeString(value.code)
} }
} }
@Deprecated("Renamed", ReplaceWith("$baseClassSerializerName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassSerializerName"))
typealias $oldBaseClassSerializerName = $baseClassSerializerName
""" """
} }
suspend fun main(vararg args: String) { suspend fun main(vararg args: String) {
val outputFolder = args.firstOrNull() ?.let { File(it) } val outputFolder = args.firstOrNull() ?.let { File(it) }
outputFolder ?.mkdirs() outputFolder ?.mkdirs()
val targetPackage = args.getOrNull(1)
val targetPackagePrefix = targetPackage ?.let { "package $it\n\n" } ?: ""
val ietfLanguageCodesLink = "https://datahub.io/core/language-codes/r/language-codes.json" val ietfLanguageCodesLink = "https://datahub.io/core/language-codes/r/language-codes.json"
val ietfLanguageCodesAdditionalTagsLink = "https://datahub.io/core/language-codes/r/ietf-language-tags.json" val ietfLanguageCodesAdditionalTagsLink = "https://datahub.io/core/language-codes/r/ietf-language-tags.json"
@ -203,18 +266,18 @@ suspend fun main(vararg args: String) {
File(outputFolder, "LanguageCodes.kt").apply { File(outputFolder, "LanguageCodes.kt").apply {
delete() delete()
createNewFile() createNewFile()
writeText(buildKtFileContent(tags)) writeText(targetPackagePrefix + buildKtFileContent(tags, targetPackage ?: ""))
} }
File(outputFolder, "StringToLanguageCodes.kt").apply { File(outputFolder, "StringToLanguageCodes.kt").apply {
delete() delete()
createNewFile() createNewFile()
writeText(createStringConverterCode(tags)) writeText(targetPackagePrefix + createStringConverterCode(tags, targetPackage ?: ""))
} }
File(outputFolder, "$baseClassSerializerName.kt").apply { File(outputFolder, "$baseClassSerializerName.kt").apply {
delete() delete()
createNewFile() createNewFile()
writeText(createSerializerCode(tags)) writeText(targetPackagePrefix + createSerializerCode(tags, targetPackage ?: ""))
} }
} }

View File

@ -0,0 +1,20 @@
package dev.inmo.micro_utils.language_codes
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object IetfLangSerializer : KSerializer<IetfLang> {
override val descriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): IetfLang {
return IetfLang(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: IetfLang) {
encoder.encodeString(value.code)
}
}
@Deprecated("Renamed", ReplaceWith("IetfLangSerializer", "dev.inmo.micro_utils.language_codes.IetfLangSerializer"))
typealias IetfLanguageCodeSerializer = IetfLangSerializer

View File

@ -1,18 +0,0 @@
package dev.inmo.micro_utils.language_codes
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object IetfLanguageCodeSerializer : KSerializer<IetfLanguageCode> {
override val descriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): IetfLanguageCode {
return IetfLanguageCode(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: IetfLanguageCode) {
encoder.encodeString(value.code)
}
}

View File

@ -0,0 +1,16 @@
package dev.inmo.micro_utils.language_codes
import kotlin.test.Test
import kotlin.test.assertEquals
class UnknownIetfLanguageTests {
@Test
fun commonTestOfParentCode() {
knownLanguageCodes.forEach {
val language = IetfLang.UnknownIetfLang(it.code)
assertEquals(it.code, language.code)
assertEquals(it.withoutDialect, language.withoutDialect)
assertEquals(it.parentLang ?.code, language.parentLang ?.code)
}
}
}

View File

@ -2,7 +2,9 @@ package dev.inmo.micro_utils.language_codes
import java.util.Locale import java.util.Locale
fun IetfLanguageCode.toJavaLocale(): Locale = Locale.forLanguageTag(code) fun IetfLang.toJavaLocale(): Locale = Locale.forLanguageTag(code)
fun IetfLanguageCode?.toJavaLocaleOrDefault(): Locale = this ?.toJavaLocale() ?: Locale.getDefault() fun IetfLang?.toJavaLocaleOrDefault(): Locale = this?.toJavaLocale() ?: Locale.getDefault()
fun Locale.toIetfLanguageCode(): IetfLanguageCode = IetfLanguageCode(toLanguageTag()) fun Locale.toIetfLang(): IetfLang = IetfLang(toLanguageTag())
@Deprecated("Renamed", ReplaceWith("this.toIetfLang()", "dev.inmo.micro_utils.language_codes.toIetfLang"))
fun Locale.toIetfLanguageCode(): IetfLang = toIetfLang()

View File

@ -60,6 +60,12 @@ kotlin {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
linuxArm64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
} }
} }

View File

@ -45,6 +45,13 @@ kotlin {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
linuxArm64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
} }
} }

View File

@ -55,6 +55,10 @@ kotlin {
} }
} }
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain androidMain.dependsOn jvmMain
} }
} }

View File

@ -28,8 +28,8 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
implementation kotlin('stdlib') implementation kotlin('stdlib')
api libs.kt.serialization
implementation compose.runtime implementation compose.runtime
api libs.kt.serialization
} }
} }
commonTest { commonTest {
@ -47,6 +47,7 @@ kotlin {
jvmTest { jvmTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation compose.uiTest
} }
} }
jsMain { jsMain {
@ -61,14 +62,20 @@ kotlin {
} }
} }
androidUnitTest { androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
implementation compose.uiTest
}
}
androidInstrumentedTest {
dependencies { dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
implementation libs.android.test.junit implementation libs.android.test.junit
implementation libs.android.espresso implementation libs.android.espresso
} }
} }
androidMain.dependsOn jvmMain
} }
} }

View File

@ -20,7 +20,13 @@ data class PaginationResult<T>(
* Amount of pages for current pagination * Amount of pages for current pagination
*/ */
@EncodeDefault @EncodeDefault
val pagesNumber: Int = ceil(objectsNumber / size.toFloat()).toInt() @SerialName("pagesNumber")
val pagesNumberLong: Long = ceil(objectsNumber / size.toFloat()).toLong()
/**
* Amount of pages for current pagination
*/
@Transient
val pagesNumber: Int = pagesNumberLong.toInt()
constructor( constructor(
page: Int, page: Int,
@ -35,31 +41,58 @@ data class PaginationResult<T>(
) )
} }
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L) val PaginationResult<*>.lastPageLong
get() = pagesNumberLong - 1
val PaginationResult<*>.lastPage
get() = lastPageLong.toInt()
val PaginationResult<*>.isLastPage
get() = page.toLong() == lastPageLong
fun <T> emptyPaginationResult( fun <T> emptyPaginationResult(
basePagination: Pagination basePagination: Pagination,
objectsNumber: Number
) = PaginationResult<T>( ) = PaginationResult<T>(
basePagination.page, basePagination.page,
basePagination.size, basePagination.size,
emptyList(), emptyList(),
0L objectsNumber.toLong()
) )
fun <T> emptyPaginationResult(
basePagination: Pagination,
) = emptyPaginationResult<T>(basePagination, 0)
fun <T> emptyPaginationResult() = emptyPaginationResult<T>(FirstPagePagination(0))
/**
* @return New [PaginationResult] with [data] without checking of data sizes equality
*/
inline fun <I, O> PaginationResult<I>.changeResultsUnchecked(
block: PaginationResult<I>.() -> List<O>
): PaginationResult<O> = PaginationResult(page, size, block(), objectsNumber)
/** /**
* @return New [PaginationResult] with [data] without checking of data sizes equality * @return New [PaginationResult] with [data] without checking of data sizes equality
*/ */
fun <I, O> PaginationResult<I>.changeResultsUnchecked( fun <I, O> PaginationResult<I>.changeResultsUnchecked(
data: List<O> data: List<O>
): PaginationResult<O> = PaginationResult(page, size, data, objectsNumber) ): PaginationResult<O> = changeResultsUnchecked { data }
/**
* @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality
*/
inline fun <I, O> PaginationResult<I>.changeResults(
block: PaginationResult<I>.() -> List<O>
): PaginationResult<O> {
val data = block()
require(data.size == results.size)
return changeResultsUnchecked(data)
}
/** /**
* @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality * @return New [PaginationResult] with [data] <b>with</b> checking of data sizes equality
*/ */
fun <I, O> PaginationResult<I>.changeResults( fun <I, O> PaginationResult<I>.changeResults(
data: List<O> data: List<O>
): PaginationResult<O> { ): PaginationResult<O> = changeResults { data }
require(data.size == results.size)
return changeResultsUnchecked(data)
}
fun <T> List<T>.createPaginationResult( fun <T> List<T>.createPaginationResult(
pagination: Pagination, pagination: Pagination,

View File

@ -10,8 +10,7 @@ inline fun doWithPagination(
} }
} }
@Suppress("NOTHING_TO_INLINE") inline fun <T, PR: PaginationResult<T>> PR.nextPageIfTrue(condition: PR.() -> Boolean) = if (condition()) {
inline fun PaginationResult<*>.nextPageIfNotEmpty() = if (results.isNotEmpty()) {
SimplePagination( SimplePagination(
page + 1, page + 1,
size size
@ -20,12 +19,28 @@ inline fun PaginationResult<*>.nextPageIfNotEmpty() = if (results.isNotEmpty())
null null
} }
@Suppress("NOTHING_TO_INLINE") inline fun <T, PR: PaginationResult<T>> PR.thisPageIfTrue(condition: PR.() -> Boolean): PR? = if (condition()) {
inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? = if (results.isNotEmpty()) {
this this
} else { } else {
null null
} }
@Suppress("NOTHING_TO_INLINE") fun PaginationResult<*>.nextPageIfNotEmpty() = nextPageIfTrue { results.isNotEmpty() }
inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()
fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? = thisPageIfTrue { results.isNotEmpty() }
fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty()
fun PaginationResult<*>.nextPageIfNotEmptyOrLastPage() = nextPageIfTrue { results.isNotEmpty() && !this.isLastPage }
fun <T> PaginationResult<T>.thisPageIfNotEmptyOrLastPage(): PaginationResult<T>? = thisPageIfTrue { results.isNotEmpty() && !this.isLastPage }
fun <T> PaginationResult<T>.currentPageIfNotEmptyOrLastPage() = thisPageIfNotEmptyOrLastPage()
fun PaginationResult<*>.nextPageIfNotLastPage() = nextPageIfTrue { !this.isLastPage }
fun <T> PaginationResult<T>.thisPageIfNotLastPage(): PaginationResult<T>? = thisPageIfTrue { !this.isLastPage }
fun <T> PaginationResult<T>.currentPageIfNotLastPage() = thisPageIfNotLastPage()

View File

@ -18,7 +18,7 @@ inline fun <T> doForAllWithNextPaging(
) { ) {
doForAll( doForAll(
initialPagination, initialPagination,
{ it.nextPageIfNotEmpty() }, { it.nextPageIfNotEmptyOrLastPage() },
block block
) )
} }
@ -29,7 +29,7 @@ inline fun <T> doAllWithCurrentPaging(
) { ) {
doForAll( doForAll(
initialPagination, initialPagination,
{ it.currentPageIfNotEmpty() }, { it.nextPageIfNotEmptyOrLastPage() },
block block
) )
} }

View File

@ -31,7 +31,7 @@ inline fun <T> getAllWithNextPaging(
block: (Pagination) -> PaginationResult<T> block: (Pagination) -> PaginationResult<T>
): List<T> = getAll( ): List<T> = getAll(
initialPagination, initialPagination,
{ it.nextPageIfNotEmpty() }, { it.nextPageIfNotEmptyOrLastPage() },
block block
) )
@ -48,7 +48,7 @@ inline fun <T> getAllWithCurrentPaging(
block: (Pagination) -> PaginationResult<T> block: (Pagination) -> PaginationResult<T>
): List<T> = getAll( ): List<T> = getAll(
initialPagination, initialPagination,
{ it.currentPageIfNotEmpty() }, { it.thisPageIfNotEmptyOrLastPage() },
block block
) )

View File

@ -21,33 +21,25 @@ fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> {
} }
fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> { fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> {
val firstIndex = maxOf(with.firstIndex, 0) if (with.firstIndex >= size || with.lastIndex < 0) {
val lastIndex = minOf(with.lastIndexExclusive, size) return emptyPaginationResult(with, size.toLong())
if (firstIndex > lastIndex) {
return emptyPaginationResult()
} }
return subList(firstIndex, lastIndex).createPaginationResult( return asSequence().drop(with.firstIndex).take(with.size).toList().createPaginationResult(
with, with,
size.toLong() size.toLong()
) )
} }
fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> { fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> {
val actualPagination = with.optionallyReverse( return if (reversed) {
size, val actualPagination = with.optionallyReverse(
reversed size,
) reversed
)
val firstIndex = maxOf(actualPagination.firstIndex, 0) paginate(actualPagination).changeResultsUnchecked { results.reversed() }
val lastIndex = minOf(actualPagination.lastIndexExclusive, size) } else {
if (firstIndex > lastIndex) { paginate(with)
return emptyPaginationResult()
} }
return subList(firstIndex, lastIndex).optionallyReverse(reversed).createPaginationResult(
with,
size.toLong()
)
} }
fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> { fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> {

View File

@ -0,0 +1,72 @@
package dev.inmo.micro_utils.pagination.utils
import dev.inmo.micro_utils.pagination.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class PaginationPaging {
@Test
fun testPaginateOnList() {
val list = (0 until 7).toList()
val startPagination = FirstPagePagination(2)
var lastPageHappened = false
doForAllWithNextPaging(startPagination) {
val result = list.paginate(it)
if (result.isLastPage) {
lastPageHappened = true
assertTrue(result.results.size == 1)
}
val testSublist = list.subList(it.firstIndex, minOf(it.lastIndexExclusive, list.size))
assertEquals(result.results, testSublist)
result
}
assertTrue(lastPageHappened)
}
@Test
fun testEmptyPaginateOnList() {
val list = listOf<Int>()
val startPagination = FirstPagePagination(2)
var paginationHappend = false
doForAllWithNextPaging(startPagination) {
val resultPagination = list.paginate(it)
assertEquals(resultPagination, emptyPaginationResult(it, list.size))
assertFalse(paginationHappend)
paginationHappend = true
resultPagination
}
assertTrue(paginationHappend)
}
@Test
fun testRightOutPaginateOnList() {
val list = (0 until 7).toList()
val startPagination = SimplePagination(page = 4, size = 2)
var paginationHappend = false
doForAllWithNextPaging(startPagination) {
val resultPagination = list.paginate(it)
assertEquals(resultPagination, emptyPaginationResult(it, list.size))
assertFalse(paginationHappend)
paginationHappend = true
resultPagination
}
assertTrue(paginationHappend)
}
}

View File

@ -57,18 +57,14 @@ publishing {
} }
} }
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) { if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven { maven {
name = "Gitea" name = "InmoNexus"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials(HttpHeaderCredentials) { credentials {
name = "Authorization" username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
authentication {
header(HttpHeaderAuthentication)
} }
} }
@ -109,4 +105,21 @@ if (project.hasProperty("signing.gnupg.keyName")) {
def signingTasks = project.getTasks().withType(Sign.class) def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks) mustRunAfter(signingTasks)
} }
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
} }

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","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}} {"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":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}

View File

@ -5,6 +5,7 @@ import dev.inmo.micro_utils.coroutines.withReadAcquire
import dev.inmo.micro_utils.coroutines.withWriteLock import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -29,7 +30,7 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>(
kvCache.getAll() kvCache.getAll()
}.takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also { }.takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also {
locker.withWriteLock { locker.withWriteLock {
kvCache.actualizeAll(true) { it } kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it }
} }
} }
} }
@ -148,7 +149,9 @@ open class CRUDCacheRepo<ObjectType, IdType, InputValueType>(
locker, locker,
idGetter idGetter
), ),
CRUDRepo<ObjectType, IdType, InputValueType> CRUDRepo<ObjectType, IdType, InputValueType> {
override suspend fun invalidate() = kvCache.actualizeAll(parentRepo, locker = locker)
}
fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached( fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached(
kvCache: KVCache<IdType, ObjectType>, kvCache: KVCache<IdType, ObjectType>,

View File

@ -1,5 +1,11 @@
package dev.inmo.micro_utils.repos.cache package dev.inmo.micro_utils.repos.cache
interface CacheRepo { interface InvalidatableRepo {
/**
* Invalidates its internal data. It __may__ lead to autoreload of data. In case when repo makes autoreload,
* it must do loading of data __before__ clear
*/
suspend fun invalidate() suspend fun invalidate()
} }
typealias CacheRepo = InvalidatableRepo

View File

@ -6,6 +6,7 @@ import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -48,9 +49,7 @@ open class ReadKeyValueCacheRepo<Key,Value>(
} }
} }
override suspend fun invalidate() = locker.withWriteLock { override suspend fun invalidate() = kvCache.actualizeAll(parentRepo, locker = locker)
kvCache.clear()
}
} }
fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached(
@ -75,10 +74,6 @@ open class KeyValueCacheRepo<Key,Value>(
} }
}.launchIn(scope) }.launchIn(scope)
override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
override suspend fun clear() { override suspend fun clear() {
parentRepo.clear() parentRepo.clear()
locker.withWriteLock { locker.withWriteLock {

View File

@ -7,6 +7,7 @@ import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.* import dev.inmo.micro_utils.pagination.utils.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.cache.KVCache import dev.inmo.micro_utils.repos.cache.cache.KVCache
import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -47,9 +48,7 @@ open class ReadKeyValuesCacheRepo<Key,Value>(
kvCache.contains(k) kvCache.contains(k)
} || parentRepo.contains(k) } || parentRepo.contains(k)
override suspend fun invalidate() = locker.withWriteLock { override suspend fun invalidate() = kvCache.actualizeAll(parentRepo, locker = locker)
kvCache.clear()
}
} }
fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached( fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached(
@ -84,10 +83,6 @@ open class KeyValuesCacheRepo<Key,Value>(
kvCache.unset(it) kvCache.unset(it)
} }
}.launchIn(scope) }.launchIn(scope)
override suspend fun invalidate() = locker.withWriteLock {
kvCache.clear()
}
} }
fun <Key, Value> KeyValuesRepo<Key, Value>.cached( fun <Key, Value> KeyValuesRepo<Key, Value>.cached(

View File

@ -9,6 +9,7 @@ import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.set import dev.inmo.micro_utils.repos.set
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -56,7 +57,7 @@ open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>(
override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap { override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap {
originalRepo.getAll() originalRepo.getAll()
}.onSuccess { }.onSuccess {
kvCache.actualizeAll(clear = true) { it } kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it }
}.getOrElse { }.getOrElse {
kvCache.getAll() kvCache.getAll()
} }

View File

@ -9,6 +9,7 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper import dev.inmo.micro_utils.repos.cache.fallback.ActionWrapper
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.set import dev.inmo.micro_utils.repos.set
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -56,7 +57,7 @@ open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>(
override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap { override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap {
originalRepo.getAll() originalRepo.getAll()
}.onSuccess { }.onSuccess {
kvCache.actualizeAll(clear = true) { it } kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it }
}.getOrElse { }.getOrElse {
kvCache.getAll() kvCache.getAll()
} }

View File

@ -9,6 +9,7 @@ import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.* import dev.inmo.micro_utils.repos.cache.*
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -42,7 +43,7 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
) )
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo) } kvCache.actualizeAll(parentRepo, locker = locker)
} }
override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize( override suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> = doOrTakeAndActualize(
@ -72,7 +73,7 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualizeWithWriteLock( override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualizeWithWriteLock(
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull }, { getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
{ getAll() }, { getAll() },
{ kvCache.actualizeAll(clear = true) { it } } { kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } }
) )
override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualizeWithWriteLock( override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualizeWithWriteLock(

View File

@ -8,8 +8,8 @@ import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.Pagination import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.PaginationResult import dev.inmo.micro_utils.pagination.PaginationResult
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import dev.inmo.micro_utils.repos.pagination.getAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@ -41,10 +41,7 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
actualize = { locker.withWriteLock { actualize(it) } } actualize = { locker.withWriteLock { actualize(it) } }
) )
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo, locker)
kvCache.clear()
kvCache.set(parentRepo.getAll())
}
} }
override suspend fun get(k: Key): Value? = doOrTakeAndActualizeWithWriteLock( override suspend fun get(k: Key): Value? = doOrTakeAndActualizeWithWriteLock(
@ -74,7 +71,7 @@ open class FullReadKeyValueCacheRepo<Key,Value>(
override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualizeWithWriteLock( override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualizeWithWriteLock(
{ getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull }, { getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull },
{ getAll() }, { getAll() },
{ kvCache.actualizeAll(clear = true) { it } } { kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } }
) )
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize( override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize(
@ -150,9 +147,7 @@ open class FullKeyValueCacheRepo<Key,Value>(
override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset) override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset)
override suspend fun invalidate() { override suspend fun invalidate() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo, locker)
kvCache.actualizeAll(parentRepo)
}
} }
override suspend fun clear() { override suspend fun clear() {

View File

@ -8,6 +8,7 @@ import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.* import dev.inmo.micro_utils.pagination.utils.*
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.cache.util.ActualizeAllClearMode
import dev.inmo.micro_utils.repos.cache.util.actualizeAll import dev.inmo.micro_utils.repos.cache.util.actualizeAll
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -41,15 +42,13 @@ open class FullReadKeyValuesCacheRepo<Key,Value>(
) )
protected open suspend fun actualizeKey(k: Key) { protected open suspend fun actualizeKey(k: Key) {
locker.withWriteLock { kvCache.actualizeAll(locker = locker, clearMode = ActualizeAllClearMode.Never) {
kvCache.set(k, parentRepo.getAll(k)) mapOf(k to parentRepo.getAll(k))
} }
} }
protected open suspend fun actualizeAll() { protected open suspend fun actualizeAll() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo, locker = locker)
kvCache.actualizeAll(parentRepo)
}
} }
override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> { override suspend fun get(k: Key, pagination: Pagination, reversed: Boolean): PaginationResult<Value> {
@ -187,9 +186,11 @@ open class FullKeyValuesCacheRepo<Key,Value>(
} }
override suspend fun invalidate() { override suspend fun invalidate() {
locker.withWriteLock { kvCache.actualizeAll(parentRepo, locker = locker)
kvCache.actualizeAll(parentRepo) }
}
override suspend fun set(toSet: Map<Key, List<Value>>) {
super<KeyValuesRepo>.set(toSet)
} }
override suspend fun removeWithValue(v: Value) { override suspend fun removeWithValue(v: Value) {

View File

@ -1,43 +1,169 @@
package dev.inmo.micro_utils.repos.cache.util package dev.inmo.micro_utils.repos.cache.util
import dev.inmo.micro_utils.coroutines.SmartRWLocker
import dev.inmo.micro_utils.coroutines.withWriteLock
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import kotlinx.serialization.Serializable
import kotlin.js.JsName
import kotlin.jvm.JvmName
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll( /**
clear: Boolean = true, * `invalidate`/`actualizeAll` clearing mode. Declare when data in original repo will be cleared
*/
@Serializable
sealed interface ActualizeAllClearMode {
/**
* Instruct user of this mode to clear internal data __before load__ of external data
*/
@Serializable
data object BeforeLoad : ActualizeAllClearMode
/**
* Instruct user of this mode to clear internal data __after load__ of external data and __before set__ of internal data
*/
@Serializable
data object BeforeSet : ActualizeAllClearMode
/**
* Instruct user of this mode to never clear internal data
*/
@Serializable
data object Never : ActualizeAllClearMode
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeLoad(
getAll: () -> Map<K, V> getAll: () -> Map<K, V>
) { ) {
set( clear()
getAll().also { val newData = getAll()
if (clear) { set(newData)
clear() }
}
} suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeSet(
) getAll: () -> Map<K, V>
) {
val newData = getAll()
clear()
set(newData)
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithoutClear(
getAll: () -> Map<K, V>
) {
val newData = getAll()
set(newData)
}
@JvmName("actualizeAllWithClearBeforeLoadWithLocker")
@JsName("actualizeAllWithClearBeforeLoadWithLocker")
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeLoad(
locker: SmartRWLocker,
getAll: () -> Map<K, V>
) {
locker.withWriteLock {
clear()
}
val newData = getAll()
locker.withWriteLock {
set(newData)
}
}
@JvmName("actualizeAllWithClearBeforeSetWithLocker")
@JsName("actualizeAllWithClearBeforeSetWithLocker")
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeSet(
locker: SmartRWLocker,
getAll: () -> Map<K, V>
) {
val newData = getAll()
locker.withWriteLock {
clear()
set(newData)
}
}
@JvmName("actualizeAllWithoutClearWithLocker")
@JsName("actualizeAllWithoutClearWithLocker")
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithoutClear(
locker: SmartRWLocker,
getAll: () -> Map<K, V>
) {
val newData = getAll()
locker.withWriteLock {
set(newData)
}
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeLoad(
locker: SmartRWLocker? = null,
getAll: () -> Map<K, V>
) {
locker ?.let {
actualizeAllWithClearBeforeLoad(locker = locker, getAll)
} ?: actualizeAllWithClearBeforeLoad(getAll)
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithClearBeforeSet(
locker: SmartRWLocker? = null,
getAll: () -> Map<K, V>
) {
locker ?.let {
actualizeAllWithClearBeforeSet(locker = locker, getAll)
} ?: actualizeAllWithClearBeforeSet(getAll)
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAllWithoutClear(
locker: SmartRWLocker? = null,
getAll: () -> Map<K, V>
) {
locker ?.let {
actualizeAllWithoutClear(locker = locker, getAll)
} ?: actualizeAllWithoutClear(getAll)
} }
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll( suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
repo: ReadKeyValueRepo<K, V>, locker: SmartRWLocker? = null,
clear: Boolean = true, clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
getAll: () -> Map<K, V>
) { ) {
actualizeAll(clear) { when (clearMode) {
repo.getAll() ActualizeAllClearMode.BeforeLoad -> locker ?.let {
actualizeAllWithClearBeforeLoad(locker = locker, getAll)
} ?: actualizeAllWithClearBeforeLoad(getAll)
ActualizeAllClearMode.BeforeSet -> locker ?.let {
actualizeAllWithClearBeforeSet(locker = locker, getAll)
} ?: actualizeAllWithClearBeforeSet(getAll)
ActualizeAllClearMode.Never -> locker ?.let {
actualizeAllWithoutClear(locker = locker, getAll)
} ?: actualizeAllWithoutClear(getAll)
}
}
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
parentRepo: ReadKeyValueRepo<K, V>,
locker: SmartRWLocker? = null,
clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
) {
actualizeAll(locker, clearMode) {
parentRepo.getAll()
} }
} }
suspend inline fun <K, V> KeyValueRepo<K, List<V>>.actualizeAll( suspend inline fun <K, V> KeyValueRepo<K, List<V>>.actualizeAll(
repo: ReadKeyValuesRepo<K, V>, parentRepo: ReadKeyValuesRepo<K, V>,
clear: Boolean = true, locker: SmartRWLocker? = null,
clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
) { ) {
actualizeAll(clear) { actualizeAll(locker, clearMode) {
repo.getAll() parentRepo.getAll()
} }
} }
suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll( suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll(
repo: ReadCRUDRepo<V, K>, parentRepo: ReadCRUDRepo<V, K>,
clear: Boolean = true, locker: SmartRWLocker? = null,
clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet,
) { ) {
actualizeAll(clear) { actualizeAll(locker, clearMode) {
repo.getAll() parentRepo.getAll()
} }
} }

View File

@ -28,7 +28,6 @@ kotlin {
api internalProject("micro_utils.common") api internalProject("micro_utils.common")
api internalProject("micro_utils.coroutines") api internalProject("micro_utils.coroutines")
} }
dependsOn jvmMain
} }
} }
} }

View File

@ -1,5 +1,6 @@
package dev.inmo.micro_utils.repos package dev.inmo.micro_utils.repos
import dev.inmo.micro_utils.common.diff
import dev.inmo.micro_utils.pagination.* import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging
@ -130,6 +131,14 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal
remove(toRemove) remove(toRemove)
} }
override suspend fun set(toSet: Map<Key, List<Value>>) {
toSet.forEach { (k, v) ->
val diff = getAll(k).diff(v)
remove(k, diff.removed.map { it.value })
add(k, diff.added.map { it.value })
}
}
} }
typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value> typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value>

View File

@ -42,6 +42,22 @@ interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo {
} }
typealias WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> = WriteCRUDRepo<ObjectType, IdType, InputValueType> typealias WriteStandardCRUDRepo<ObjectType, IdType, InputValueType> = WriteCRUDRepo<ObjectType, IdType, InputValueType>
/**
* Just mirroring [WriteCRUDRepo.newObjectsFlow] to be same as in KV repos
*/
val <ObjectType> WriteCRUDRepo<ObjectType, *, *>.onNewObjects: Flow<ObjectType>
get() = newObjectsFlow
/**
* Just mirroring [WriteCRUDRepo.updatedObjectsFlow] to be same as in KV repos
*/
val <ObjectType> WriteCRUDRepo<ObjectType, *, *>.onUpdatedObjects: Flow<ObjectType>
get() = updatedObjectsFlow
/**
* Just mirroring [WriteCRUDRepo.deletedObjectsIdsFlow] to be same as in KV repos
*/
val <IdType> WriteCRUDRepo<*, IdType, *>.onDeletedObjectsIds: Flow<IdType>
get() = deletedObjectsIdsFlow
suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.create( suspend fun <ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>.create(
vararg values: InputValueType vararg values: InputValueType
): List<ObjectType> = create(values.toList()) ): List<ObjectType> = create(values.toList())

View File

@ -0,0 +1,20 @@
package dev.inmo.micro_utils.repos.diff
import dev.inmo.micro_utils.common.MapDiff
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.common.diff
import dev.inmo.micro_utils.repos.CRUDRepo
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.unset
suspend fun <Id, Registered> ReadCRUDRepo<Registered, Id>.diff(other: Map<Id, Registered>): MapDiff<Id, Registered> {
return getAll().diff(other)
}
suspend fun <Id, Registered> Map<Id, Registered>.diff(other: ReadCRUDRepo<Registered, Id>): MapDiff<Id, Registered> {
return diff(other.getAll())
}
suspend fun <Id, Registered> MutableMap<Id, Registered>.applyDiff(other: ReadCRUDRepo<Registered, Id>) {
applyDiff(diff(other))
}

View File

@ -0,0 +1,33 @@
package dev.inmo.micro_utils.repos.diff
import dev.inmo.micro_utils.common.MapDiff
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.common.diff
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import dev.inmo.micro_utils.repos.unset
suspend fun <Id, Registered> ReadKeyValueRepo<Id, Registered>.diff(other: Map<Id, Registered>): MapDiff<Id, Registered> {
return getAll().diff(other)
}
suspend fun <Id, Registered> Map<Id, Registered>.diff(other: ReadKeyValueRepo<Id, Registered>): MapDiff<Id, Registered> {
return diff(other.getAll())
}
suspend fun <Id, Registered> KeyValueRepo<Id, Registered>.applyDiff(diff: MapDiff<Id, Registered>) {
unset(diff.removed.map { it.key })
set(
diff.changed.map { (k, oldNew) ->
k to oldNew.second
}.toMap() + diff.added
)
}
suspend fun <Id, Registered> KeyValueRepo<Id, Registered>.applyDiff(other: Map<Id, Registered>) {
applyDiff(diff(other))
}
suspend fun <Id, Registered> MutableMap<Id, Registered>.applyDiff(other: ReadKeyValueRepo<Id, Registered>) {
applyDiff(diff(other))
}

View File

@ -0,0 +1,33 @@
package dev.inmo.micro_utils.repos.diff
import dev.inmo.micro_utils.common.MapDiff
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.common.diff
import dev.inmo.micro_utils.repos.*
suspend fun <Id, Registered> ReadKeyValuesRepo<Id, Registered>.diff(other: Map<Id, List<Registered>>): MapDiff<Id, List<Registered>> {
return getAll().diff(other)
}
suspend fun <Id, Registered> Map<Id, List<Registered>>.diff(other: ReadKeyValuesRepo<Id, Registered>): MapDiff<Id, List<Registered>> {
return diff(other.getAll())
}
suspend fun <Id, Registered> KeyValuesRepo<Id, Registered>.applyDiff(diff: MapDiff<Id, List<Registered>>) {
diff.removed.forEach {
clear(it.key)
}
set(
diff.changed.map { (k, oldNew) ->
k to oldNew.second
}.toMap() + diff.added
)
}
suspend fun <Id, Registered> KeyValuesRepo<Id, Registered>.applyDiff(other: Map<Id, List<Registered>>) {
applyDiff(diff(other))
}
suspend fun <Id, Registered> MutableMap<Id, List<Registered>>.applyDiff(other: ReadKeyValuesRepo<Id, Registered>) {
applyDiff(diff(other))
}

View File

@ -1,14 +1,19 @@
package dev.inmo.micro_utils.repos.exposed package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.CRUDRepo import dev.inmo.micro_utils.repos.CRUDRepo
import kotlinx.coroutines.channels.BufferOverflow
abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>( abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize: Int = 0, flowsChannelsSize: Int = 0,
tableName: String = "" tableName: String = "",
replyCacheInFlows: Int = 0,
onBufferOverflowBehaviour: BufferOverflow = BufferOverflow.SUSPEND
) : ) :
AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
flowsChannelsSize, flowsChannelsSize,
tableName tableName,
replyCacheInFlows,
onBufferOverflowBehaviour
), ),
ExposedCRUDRepo<ObjectType, IdType>, ExposedCRUDRepo<ObjectType, IdType>,
CRUDRepo<ObjectType, IdType, InputValueType> CRUDRepo<ObjectType, IdType, InputValueType>

View File

@ -35,14 +35,14 @@ abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
} }
override suspend fun getById(id: IdType): ObjectType? { override suspend fun getById(id: IdType): ObjectType? {
return transaction(db = database) { return transaction(db = database) {
select { selectAll().where {
selectById(id) selectById(id)
}.limit(1).firstOrNull() ?.asObject }.limit(1).firstOrNull() ?.asObject
} }
} }
override suspend fun contains(id: IdType): Boolean = transaction(db = database) { override suspend fun contains(id: IdType): Boolean = transaction(db = database) {
select { selectById(id) }.limit(1).any() selectAll().where { selectById(id) }.limit(1).any()
} }
override suspend fun getAll(): Map<IdType, ObjectType> = transaction(database) { override suspend fun getAll(): Map<IdType, ObjectType> = transaction(database) {

View File

@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.exposed
import dev.inmo.micro_utils.repos.UpdatedValuePair import dev.inmo.micro_utils.repos.UpdatedValuePair
import dev.inmo.micro_utils.repos.WriteCRUDRepo import dev.inmo.micro_utils.repos.WriteCRUDRepo
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.* import org.jetbrains.exposed.sql.statements.*
@ -11,19 +12,26 @@ import java.util.Objects
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 replyCacheInFlows: Int = 0,
onBufferOverflowBehaviour: BufferOverflow = BufferOverflow.SUSPEND
) : ) :
AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName), AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName),
ExposedCRUDRepo<ObjectType, IdType>, ExposedCRUDRepo<ObjectType, IdType>,
WriteCRUDRepo<ObjectType, IdType, InputValueType> WriteCRUDRepo<ObjectType, IdType, InputValueType>
{ {
protected val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize) protected open val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize, onBufferOverflowBehaviour)
protected val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize) protected open val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize, onBufferOverflowBehaviour)
protected val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize) protected open val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize, onBufferOverflowBehaviour)
override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow() override val newObjectsFlow: Flow<ObjectType> by lazy {
override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow() _newObjectsFlow.asSharedFlow()
override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow() }
override val updatedObjectsFlow: Flow<ObjectType> by lazy {
_updatedObjectsFlow.asSharedFlow()
}
override val deletedObjectsIdsFlow: Flow<IdType> by lazy {
_deletedObjectsIdsFlow.asSharedFlow()
}
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
@ -93,7 +101,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
}.let { }.let {
if (it > 0) { if (it > 0) {
transaction(db = database) { transaction(db = database) {
select { selectAll().where {
selectById(this, id) selectById(this, id)
}.limit(1).firstOrNull() ?.asObject }.limit(1).firstOrNull() ?.asObject
} }
@ -134,7 +142,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
ids ids
} else { } else {
ids.filter { ids.filter {
select { selectById(it) }.limit(1).none() selectAll().where { selectById(it) }.limit(1).none()
} }
} }
}.forEach { }.forEach {

View File

@ -1,6 +1,7 @@
package dev.inmo.micro_utils.repos.exposed.keyvalue package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.* import org.jetbrains.exposed.sql.statements.*
@ -8,13 +9,15 @@ import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedKeyValueRepo<Key, Value>( abstract class AbstractExposedKeyValueRepo<Key, Value>(
override val database: Database, override val database: Database,
tableName: String? = null tableName: String? = null,
flowsExtraBufferCapacity: Int = Int.MAX_VALUE,
flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
) : KeyValueRepo<Key, Value>, AbstractExposedReadKeyValueRepo<Key, Value>( ) : KeyValueRepo<Key, Value>, AbstractExposedReadKeyValueRepo<Key, Value>(
database, database,
tableName tableName
) { ) {
protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>() protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
protected val _onValueRemoved = MutableSharedFlow<Key>() protected val _onValueRemoved = MutableSharedFlow<Key>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
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()
@ -65,7 +68,7 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
override suspend fun unsetWithValues(toUnset: List<Value>) { override suspend fun unsetWithValues(toUnset: List<Value>) {
transaction(database) { transaction(database) {
toUnset.flatMap { toUnset.flatMap {
val keys = select { selectByValue(it) }.mapNotNull { it.asKey } val keys = selectAll().where { selectByValue(it) }.mapNotNull { it.asKey }
deleteWhere { selectByIds(it, keys) } deleteWhere { selectByIds(it, keys) }
keys keys
} }

View File

@ -24,11 +24,11 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean>
override suspend fun get(k: Key): Value? = transaction(database) { override suspend fun get(k: Key): Value? = transaction(database) {
select { selectById(k) }.limit(1).firstOrNull() ?.asObject selectAll().where { selectById(k) }.limit(1).firstOrNull() ?.asObject
} }
override suspend fun contains(key: Key): Boolean = transaction(database) { override suspend fun contains(key: Key): Boolean = transaction(database) {
select { selectById(key) }.limit(1).any() selectAll().where { selectById(key) }.limit(1).any()
} }
override suspend fun getAll(): Map<Key, Value> = transaction(database) { selectAll().associate { it.asKey to it.asObject } } override suspend fun getAll(): Map<Key, Value> = transaction(database) { selectAll().associate { it.asKey to it.asObject } }
@ -46,7 +46,7 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
} }
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) { override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
select { selectByValue(v) }.selectPaginated( selectAll().where { selectByValue(v) }.selectPaginated(
pagination, pagination,
keyColumn, keyColumn,
reversed reversed

View File

@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
@ -13,15 +14,17 @@ open class ExposedKeyValueRepo<Key, Value>(
database: Database, database: Database,
keyColumnAllocator: ColumnAllocator<Key>, keyColumnAllocator: ColumnAllocator<Key>,
valueColumnAllocator: ColumnAllocator<Value>, valueColumnAllocator: ColumnAllocator<Value>,
tableName: String? = null tableName: String? = null,
flowsExtraBufferCapacity: Int = Int.MAX_VALUE,
flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>( ) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>(
database, database,
keyColumnAllocator, keyColumnAllocator,
valueColumnAllocator, valueColumnAllocator,
tableName tableName
) { ) {
protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>() protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
protected val _onValueRemoved = MutableSharedFlow<Key>() protected val _onValueRemoved = MutableSharedFlow<Key>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
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()
@ -65,7 +68,7 @@ open class ExposedKeyValueRepo<Key, Value>(
override suspend fun unsetWithValues(toUnset: List<Value>) { override suspend fun unsetWithValues(toUnset: List<Value>) {
transaction(database) { transaction(database) {
toUnset.flatMap { toUnset.flatMap {
val keys = select { valueColumn.eq(it) }.mapNotNull { it[keyColumn] } val keys = selectAll().where { valueColumn.eq(it) }.mapNotNull { it[keyColumn] }
deleteWhere { keyColumn.inList(keys) } deleteWhere { keyColumn.inList(keys) }
keys keys
} }

View File

@ -1,6 +1,7 @@
package dev.inmo.micro_utils.repos.exposed.onetomany package dev.inmo.micro_utils.repos.exposed.onetomany
import dev.inmo.micro_utils.repos.KeyValuesRepo import dev.inmo.micro_utils.repos.KeyValuesRepo
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.InsertStatement import org.jetbrains.exposed.sql.statements.InsertStatement
@ -8,18 +9,20 @@ import org.jetbrains.exposed.sql.transactions.transaction
abstract class AbstractExposedKeyValuesRepo<Key, Value>( abstract class AbstractExposedKeyValuesRepo<Key, Value>(
override val database: Database, override val database: Database,
tableName: String? = null tableName: String? = null,
flowsExtraBufferCapacity: Int = Int.MAX_VALUE,
flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
) : KeyValuesRepo<Key, Value>, AbstractExposedReadKeyValuesRepo<Key, Value>( ) : KeyValuesRepo<Key, Value>, AbstractExposedReadKeyValuesRepo<Key, Value>(
database, database,
tableName tableName
) { ) {
protected val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow() protected val _onNewValue: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
override val onNewValue: Flow<Pair<Key, Value>> override val onNewValue: Flow<Pair<Key, Value>>
get() = _onNewValue.asSharedFlow() get() = _onNewValue.asSharedFlow()
protected val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow() protected val _onValueRemoved: MutableSharedFlow<Pair<Key, Value>> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
override val onValueRemoved: Flow<Pair<Key, Value>> override val onValueRemoved: Flow<Pair<Key, Value>>
get() = _onValueRemoved.asSharedFlow() get() = _onValueRemoved.asSharedFlow()
protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow() protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow)
override val onDataCleared: Flow<Key> override val onDataCleared: Flow<Key>
get() = _onDataCleared.asSharedFlow() get() = _onDataCleared.asSharedFlow()
@ -29,7 +32,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>(
transaction(database) { transaction(database) {
toAdd.keys.flatMap { k -> toAdd.keys.flatMap { k ->
toAdd[k] ?.mapNotNull { v -> toAdd[k] ?.mapNotNull { v ->
if (select { selectById(k).and(selectByValue(v)) }.limit(1).any()) { if (selectAll().where { selectById(k).and(selectByValue(v)) }.limit(1).any()) {
return@mapNotNull null return@mapNotNull null
} }
val insertResult = insert { val insertResult = insert {

View File

@ -19,7 +19,7 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
get() = asKey get() = asKey
abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean>
override suspend fun count(k: Key): Long = transaction(database) { select { selectById(k) }.count() } override suspend fun count(k: Key): Long = transaction(database) { selectAll().where { selectById(k) }.count() }
override suspend fun count(): Long = transaction(database) { selectAll().count() } override suspend fun count(): Long = transaction(database) { selectAll().count() }
@ -28,7 +28,7 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
pagination: Pagination, pagination: Pagination,
reversed: Boolean reversed: Boolean
): PaginationResult<Value> = transaction(database) { ): PaginationResult<Value> = transaction(database) {
select { selectById(k) }.selectPaginated( selectAll().where { selectById(k) }.selectPaginated(
pagination, pagination,
keyColumn, keyColumn,
reversed reversed
@ -55,7 +55,7 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
pagination: Pagination, pagination: Pagination,
reversed: Boolean reversed: Boolean
): PaginationResult<Key> = transaction(database) { ): PaginationResult<Key> = transaction(database) {
select { selectByValue(v) }.selectPaginated( selectAll().where { selectByValue(v) }.selectPaginated(
pagination, pagination,
keyColumn, keyColumn,
reversed reversed
@ -65,11 +65,11 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
} }
override suspend fun contains(k: Key): Boolean = transaction(database) { override suspend fun contains(k: Key): Boolean = transaction(database) {
select { selectById(k) }.limit(1).any() selectAll().where { selectById(k) }.limit(1).any()
} }
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) { override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
select { selectById(k).and(selectByValue(v)) }.limit(1).any() selectAll().where { selectById(k).and(selectByValue(v)) }.limit(1).any()
} }
override suspend fun getAll(reverseLists: Boolean): Map<Key, List<Value>> = transaction(database) { override suspend fun getAll(reverseLists: Boolean): Map<Key, List<Value>> = transaction(database) {
@ -85,9 +85,9 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
override suspend fun getAll(k: Key, reverseLists: Boolean): List<Value> = transaction(database) { override suspend fun getAll(k: Key, reverseLists: Boolean): List<Value> = transaction(database) {
val query = if (reverseLists) { val query = if (reverseLists) {
select { selectById(k) }.orderBy(keyColumn, SortOrder.DESC) selectAll().where { selectById(k) }.orderBy(keyColumn, SortOrder.DESC)
} else { } else {
select { selectById(k) } selectAll().where { selectById(k) }
} }
query.map { query.map {
it.asObject it.asObject

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