mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-30 03:30:19 +00:00 
			
		
		
		
	Compare commits
	
		
			186 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a6905e73cb | |||
| 93b054d55e | |||
| 5db4c5c717 | |||
| 5e04521929 | |||
| 30440b4ed1 | |||
| 09bb90604d | |||
| 4d55ec6f36 | |||
| f373524f34 | |||
| 0398a7bebd | |||
| fa8a5bcd97 | |||
| 758a92410b | |||
| 77f56c5dda | |||
| 72f2fe3cc3 | |||
| 7e0e520f03 | |||
| 92c4784e42 | |||
| a634229dc0 | |||
| de3f36ef2a | |||
| c623a265ee | |||
| e75125f6df | |||
| 4901a8844c | |||
| a1854b68d8 | |||
| aee4a6243b | |||
| 4c0cb73d69 | |||
| c6eab182f6 | |||
| 3e3fbd97eb | |||
| feb695caa7 | |||
| 1bd46d9651 | |||
| 32eabb6b36 | |||
| 9bfe6dc6d8 | |||
| 3f366aeea4 | |||
| 4338fd46f2 | |||
| 36974f5b49 | |||
| d48f767408 | |||
| bd2558e852 | |||
| 9f7c963cd5 | |||
| 2db0eadbfe | |||
| 580c7b8842 | |||
| 88fba347ea | |||
| c8c5255e62 | |||
| 01a1a2760a | |||
| 6d34df8d48 | |||
| b124ea65e1 | |||
| f3214347a9 | |||
| 6b8724b59f | |||
| ac3e378edf | |||
| 5d31fd1c91 | |||
| 046fe1ec08 | |||
| 721873c843 | |||
| f6ffbfc10a | |||
| 4e91649e0a | |||
| 4f7f8abec4 | |||
| 6370562dbc | |||
| 94e2e67522 | |||
| 1d8330015d | |||
| e5017b0258 | |||
| cd412ca31b | |||
| d2b6473095 | |||
| ab42507275 | |||
| dcef844e20 | |||
| 3244708c1b | |||
| 73ac1f1741 | |||
| f104e9f352 | |||
| 393c9a7d06 | |||
| ea497ea488 | |||
|  | 5add89cad0 | ||
| 2db447d2ef | |||
| 31c83813e6 | |||
| 27483a282d | |||
| 48b816aa22 | |||
| 0065f94f52 | |||
| ccc0002eb2 | |||
| 15a2eee141 | |||
| b9faac71e5 | |||
| 0a4465de33 | |||
| f9dfd09628 | |||
| 8638d7afce | |||
| 8311793a43 | |||
| 0d552cfcd2 | |||
| 4b0f20dbd1 | |||
| cf531c949d | |||
| ba5c5f17d5 | |||
| 35825ad9b7 | |||
| b1eb26a89e | |||
| c9d04b6698 | |||
| 496133e014 | |||
| f2857ee2be | |||
| 22541f2d5e | |||
| e235c52b6c | |||
| e89b8c72dd | |||
| a374e53a3a | |||
| afb066c4ee | |||
| f05761d4a5 | |||
| 76adc9ea33 | |||
| 99dd291413 | |||
| f904eb27e1 | |||
| eeb8214812 | |||
| f7215b039e | |||
| c07fe5a0f9 | |||
| 0d28cb6e20 | |||
| a1a17bfd1f | |||
| f386f09592 | |||
| a47e17fe6e | |||
| 01dc3b63ff | |||
| 2d97e0699e | |||
| 75f514d99b | |||
| 9a687cfc1c | |||
| 14edf8b6b7 | |||
| 23aa2d8917 | |||
| 7651388b5c | |||
| 805ab32b24 | |||
| cc623b1097 | |||
| 1420416b3e | |||
| 9a0b67f938 | |||
| 303e1e6281 | |||
| ff59b0cc9c | |||
| be5d2ee715 | |||
| 8dd2e3f6f9 | |||
| 2eedd196d2 | |||
| 759a3f2784 | |||
| 386fa830c3 | |||
| c382423d77 | |||
| bb466ce66c | |||
| 72cd3dd8a1 | |||
|  | 595cedaaf1 | ||
| eeaceb6cf5 | |||
| 1bd671685b | |||
| 48d3fe41f2 | |||
| 7ab21871cd | |||
| 65d01b1fb3 | |||
| 6230accb68 | |||
| c6ed821934 | |||
| 10e03bb951 | |||
| aa4f392948 | |||
| f51b59ec02 | |||
| 8c76834ae4 | |||
| 4a454f3d67 | |||
| 151aa1863d | |||
| 3bf6896296 | |||
| 0d01561476 | |||
| f6ded92251 | |||
| d01b735cc6 | |||
| 6c12001080 | |||
| 1afbf03606 | |||
| f6ef5c61c5 | |||
| c18fee8107 | |||
| d9df7a4384 | |||
| 87c2230e8e | |||
| da7eb6de0a | |||
| ed3815118f | |||
| be726f42bd | |||
| a91006132f | |||
| 9a9f741a0b | |||
| 5028f130e9 | |||
| 77fa019651 | |||
| 9715da9384 | |||
| f6d5035c1a | |||
| 43e782ab6f | |||
| f3f9920bfb | |||
| 2bfd615812 | |||
| ebfacb3659 | |||
| c71d557eec | |||
| e0398cef21 | |||
| f91599e9c6 | |||
| f8f9f93c97 | |||
| a8a5340d8b | |||
| 871b27f37d | |||
| 6f174cae1d | |||
| 22d7ac3e22 | |||
| 9b30c3a155 | |||
| 915bac64b1 | |||
| 9d2b50e55d | |||
| bde100f63d | |||
| 05b035a13d | |||
| eefb56bed7 | |||
| fcc0dc4189 | |||
| 47ff20317f | |||
| 1558b9103d | |||
| 7a78742162 | |||
| c01e240f66 | |||
| fef4fcbac6 | |||
| 5ab18bce4b | |||
| 24aec7271a | |||
| 9b19a2cb95 | |||
| efdd7b8a57 | |||
| 6df4cc9c3b | |||
| b9d93db0f5 | 
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -14,3 +14,8 @@ local.properties | ||||
| kotlin-js-store | ||||
|  | ||||
| publishing.sh | ||||
|  | ||||
| local.* | ||||
| local/ | ||||
|  | ||||
| .kotlin/ | ||||
|   | ||||
							
								
								
									
										274
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										274
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,279 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 0.20.53 | ||||
|  | ||||
| ## 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 | ||||
|  | ||||
| * `Coroutines`: | ||||
|     * `SpecialMutableStateFlow` now extends `MutableStateFlow` | ||||
|     * `Compose`: | ||||
|         * Deprecate `FlowState` due to its complexity in fixes | ||||
|  | ||||
| ## 0.20.17 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Serialization`: `1.6.1` -> `1.6.2` | ||||
|  | ||||
| ## 0.20.16 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Exposed`: `0.44.1` -> `0.45.0` | ||||
| * `Coroutines`: | ||||
|     * Add `SpecialMutableStateFlow` | ||||
|     * `Compose`: | ||||
|         * Add `FlowState` | ||||
|  | ||||
| ## 0.20.15 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Kotlin`: `1.9.20` -> `1.9.21` | ||||
|     * `KSLog`: `1.3.0` -> `1.3.1` | ||||
|     * `Compose`: `1.5.10` -> `1.5.11` | ||||
|  | ||||
| ## 0.20.14 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Serialization`: `1.6.0` -> `1.6.1` | ||||
|     * `KSLog`: `1.2.4` -> `1.3.0` | ||||
|  | ||||
| ## 0.20.13 | ||||
|  | ||||
| * `Versions`: | ||||
|     * `Ktor`: `2.3.5` -> `2.3.6` | ||||
|     * `UUID`: `0.8.1` -> `0.8.2` | ||||
|  | ||||
| ## 0.20.12 | ||||
|  | ||||
| **It is experimental migration onto new gradle version. Be careful in use of this version** | ||||
|   | ||||
| @@ -7,9 +7,6 @@ import androidx.compose.foundation.gestures.* | ||||
| import androidx.compose.foundation.layout.* | ||||
| import androidx.compose.foundation.text.KeyboardActions | ||||
| 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.filled.KeyboardArrowDown | ||||
| import androidx.compose.material.icons.filled.KeyboardArrowUp | ||||
| @@ -75,7 +72,7 @@ fun NumberPicker( | ||||
|     } | ||||
|     val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx | ||||
|     val animatedStateValue = animatedStateValue(animatedOffset.value) | ||||
|     val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled) | ||||
|     val disabledArrowsColor = arrowsColor.copy(alpha = 0f) | ||||
|  | ||||
|     val inputFieldShown = if (allowUseManualInput) { | ||||
|         remember { mutableStateOf(false) } | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import androidx.compose.animation.core.exponentialDecay | ||||
| import androidx.compose.foundation.gestures.* | ||||
| import androidx.compose.foundation.layout.* | ||||
| import androidx.compose.foundation.rememberScrollState | ||||
| import androidx.compose.material.ContentAlpha | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.filled.KeyboardArrowDown | ||||
| import androidx.compose.material.icons.filled.KeyboardArrowUp | ||||
| @@ -62,9 +61,7 @@ fun <T> SetPicker( | ||||
|         (index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt()) | ||||
|     } | ||||
|     val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx | ||||
|     val boxOffset = (indexAnimatedOffset * halvedNumbersColumnHeightPx) - coercedAnimatedOffset | ||||
|     val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled) | ||||
|     val scrollState = rememberScrollState() | ||||
|     val disabledArrowsColor = arrowsColor.copy(alpha = 0f) | ||||
|  | ||||
|     Column( | ||||
|         modifier = modifier | ||||
|   | ||||
| @@ -27,7 +27,7 @@ allprojects { | ||||
|         mavenCentral() | ||||
|         google() | ||||
|         maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } | ||||
|         maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" } | ||||
|         maven { url "https://nexus.inmo.dev/repository/maven-releases/" } | ||||
|     } | ||||
|  | ||||
|     // temporal crutch until legacy tests will be stabled or legacy target will be removed | ||||
|   | ||||
							
								
								
									
										7
									
								
								colors/common/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								colors/common/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| plugins { | ||||
|     id "org.jetbrains.kotlin.multiplatform" | ||||
|     id "org.jetbrains.kotlin.plugin.serialization" | ||||
|     id "com.android.library" | ||||
| } | ||||
|  | ||||
| apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | ||||
							
								
								
									
										174
									
								
								colors/common/src/commonMain/kotlin/HEXAColor.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								colors/common/src/commonMain/kotlin/HEXAColor.kt
									
									
									
									
									
										Normal 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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										209
									
								
								colors/common/src/commonTest/kotlin/HexColorTests.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								colors/common/src/commonTest/kotlin/HexColorTests.kt
									
									
									
									
									
										Normal 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)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -8,6 +8,11 @@ apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | ||||
|  | ||||
| kotlin { | ||||
|     sourceSets { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 api libs.klock | ||||
|             } | ||||
|         } | ||||
|         jvmMain { | ||||
|             dependencies { | ||||
|                 api project(":micro_utils.coroutines") | ||||
| @@ -15,23 +20,11 @@ kotlin { | ||||
|         } | ||||
|         androidMain { | ||||
|             dependencies { | ||||
|                 api project(":micro_utils.coroutines") | ||||
|                 api libs.android.fragment | ||||
|             } | ||||
|             dependsOn jvmMain | ||||
|         } | ||||
|  | ||||
|         linuxX64Main { | ||||
|             dependencies { | ||||
|                 api libs.okio | ||||
|             } | ||||
|         } | ||||
|         mingwX64Main { | ||||
|             dependencies { | ||||
|                 api libs.okio | ||||
|             } | ||||
|         } | ||||
|         linuxArm64Main { | ||||
|         nativeMain { | ||||
|             dependencies { | ||||
|                 api libs.okio | ||||
|             } | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| <manifest/> | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -27,7 +27,7 @@ private inline fun <T> getObject( | ||||
|  * @see calculateDiff | ||||
|  */ | ||||
| @Serializable | ||||
| data class Diff<T> internal constructor( | ||||
| data class Diff<T> @Warning(warning) constructor( | ||||
|     val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>, | ||||
|     /** | ||||
|      * Old-New values pairs | ||||
| @@ -36,6 +36,10 @@ data class Diff<T> internal constructor( | ||||
|     val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>> | ||||
| ) { | ||||
|     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()) | ||||
|   | ||||
| @@ -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() | ||||
|     } | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|     } | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
| @@ -22,7 +22,6 @@ kotlin { | ||||
|             dependencies { | ||||
|                 api libs.kt.coroutines.android | ||||
|             } | ||||
|             dependsOn(jvmMain) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| <manifest/> | ||||
							
								
								
									
										24
									
								
								coroutines/compose/src/jvmTest/kotlin/FlowStateTests.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								coroutines/compose/src/jvmTest/kotlin/FlowStateTests.kt
									
									
									
									
									
										Normal 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() | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -16,8 +16,8 @@ class DoWithFirstBuilder<T>( | ||||
|     operator fun plus(block: suspend CoroutineScope.() -> T) { | ||||
|         deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block)) | ||||
|     } | ||||
|     inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block) | ||||
|     inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block) | ||||
|     fun add(block: suspend CoroutineScope.() -> T) = plus(block) | ||||
|     fun include(block: suspend CoroutineScope.() -> T) = plus(block) | ||||
|  | ||||
|     fun build() = deferreds.toList() | ||||
| } | ||||
|   | ||||
| @@ -85,32 +85,32 @@ fun <T, M> Flow<T>.subscribeAsync( | ||||
|     return job | ||||
| } | ||||
|  | ||||
| inline fun <T, M> Flow<T>.subscribeSafelyAsync( | ||||
| fun <T, M> Flow<T>.subscribeSafelyAsync( | ||||
|     scope: CoroutineScope, | ||||
|     noinline markerFactory: suspend (T) -> M, | ||||
|     noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, | ||||
|     noinline block: suspend (T) -> Unit | ||||
|     markerFactory: suspend (T) -> M, | ||||
|     onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, | ||||
|     block: suspend (T) -> Unit | ||||
| ) = subscribeAsync(scope, markerFactory) { | ||||
|     safely(onException) { | ||||
|         block(it) | ||||
|     } | ||||
| } | ||||
|  | ||||
| inline fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync( | ||||
| fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync( | ||||
|     scope: CoroutineScope, | ||||
|     noinline markerFactory: suspend (T) -> M, | ||||
|     noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
|     noinline block: suspend (T) -> Unit | ||||
|     markerFactory: suspend (T) -> M, | ||||
|     onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
|     block: suspend (T) -> Unit | ||||
| ) = subscribeAsync(scope, markerFactory) { | ||||
|     safelyWithoutExceptions(onException) { | ||||
|         block(it) | ||||
|     } | ||||
| } | ||||
|  | ||||
| inline fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync( | ||||
| fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync( | ||||
|     scope: CoroutineScope, | ||||
|     noinline markerFactory: suspend (T) -> M, | ||||
|     noinline block: suspend (T) -> Unit | ||||
|     markerFactory: suspend (T) -> M, | ||||
|     block: suspend (T) -> Unit | ||||
| ) = subscribeAsync(scope, markerFactory) { | ||||
|     safelyWithoutExceptions({ /* do nothing */}) { | ||||
|         block(it) | ||||
|   | ||||
| @@ -4,45 +4,44 @@ import kotlinx.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 | ||||
|  * 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 } | ||||
|  | ||||
| /** | ||||
|  * 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 | ||||
| suspend inline fun <T> runCatchingSafely( | ||||
|     onException: ExceptionHandler<T>, | ||||
|     block: suspend () -> T | ||||
| ): Result<T> { | ||||
|     return runCatching { | ||||
|         block() | ||||
|     }.onFailure { | ||||
|         coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(it) | ||||
|         return runCatching { | ||||
|             onException(it) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 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 | ||||
|  * Launching [runCatchingSafely] with [defaultSafelyExceptionHandler] as `onException` parameter | ||||
|  */ | ||||
| object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler> | ||||
| suspend inline fun <T> runCatchingSafely( | ||||
|     block: suspend () -> T | ||||
| ): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block) | ||||
|  | ||||
| /** | ||||
|  * [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 | ||||
| } | ||||
| //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 | ||||
| @@ -51,7 +50,7 @@ class ContextSafelyExceptionHandler( | ||||
|  * @see ContextSafelyExceptionHandler | ||||
|  * @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] | ||||
| @@ -64,7 +63,7 @@ suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSaf | ||||
| suspend fun <T> safelyWithContextExceptionHandler( | ||||
|     contextExceptionHandler: ExceptionHandler<Unit>, | ||||
|     safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler, | ||||
|     block: suspend CoroutineScope.() -> T | ||||
|     block: suspend () -> T | ||||
| ): T { | ||||
|     val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler -> | ||||
|         ContextSafelyExceptionHandler { | ||||
| @@ -78,58 +77,29 @@ 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: | ||||
|  * | ||||
|  * * [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 | ||||
|  * @see runCatchingSafely | ||||
|  */ | ||||
| suspend inline fun <T> safely( | ||||
|     noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, | ||||
|     noinline block: suspend CoroutineScope.() -> T | ||||
| ): T { | ||||
|     return try { | ||||
|         supervisorScope(block) | ||||
|     } catch (e: Throwable) { | ||||
|         coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e) | ||||
|         onException(e) | ||||
|     } | ||||
| } | ||||
|     onException: ExceptionHandler<T>, | ||||
|     block: suspend () -> T | ||||
| ): T = runCatchingSafely(onException, block).getOrThrow() | ||||
|  | ||||
| suspend inline fun <T> runCatchingSafely( | ||||
|     noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, | ||||
|     noinline block: suspend CoroutineScope.() -> T | ||||
| ): Result<T> = runCatching { | ||||
|     safely(onException, block) | ||||
| } | ||||
| /** | ||||
|  * Calls [safely] with passing of [defaultSafelyExceptionHandler] as `onException` | ||||
|  * | ||||
|  * @see runCatchingSafely | ||||
|  */ | ||||
| suspend inline fun <T> safely( | ||||
|     block: suspend () -> T | ||||
| ): T = safely(defaultSafelyExceptionHandler, block) | ||||
|  | ||||
| suspend inline fun <T, R> T.runCatchingSafely( | ||||
|     noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler, | ||||
|     noinline block: suspend T.() -> R | ||||
| ): Result<R> = runCatching { | ||||
|     safely(onException) { block() } | ||||
| } | ||||
|  | ||||
| suspend inline fun <T> safelyWithResult( | ||||
|     noinline block: suspend CoroutineScope.() -> T | ||||
| @Deprecated("Renamed", ReplaceWith("runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely")) | ||||
| suspend fun <T> safelyWithResult( | ||||
|     block: suspend () -> T | ||||
| ): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block) | ||||
|  | ||||
| suspend inline fun <T, R> T.safelyWithResult( | ||||
|     noinline block: suspend T.() -> R | ||||
| ): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block) | ||||
|  | ||||
| /** | ||||
|  * Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and | ||||
|  * returning null at one time | ||||
| @@ -147,21 +117,23 @@ val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = { | ||||
|  * 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) | ||||
|  */ | ||||
| suspend inline fun <T> safelyWithoutExceptions( | ||||
|     noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
|     noinline block: suspend CoroutineScope.() -> T | ||||
| ): T? = safely(onException, block) | ||||
| suspend fun <T> safelyWithoutExceptions( | ||||
|     onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, | ||||
|     block: suspend () -> T | ||||
| ): T? = runCatchingSafely(onException, block).getOrNull() | ||||
|  | ||||
| suspend inline fun <T> runCatchingSafelyWithoutExceptions( | ||||
|     noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
|     noinline block: suspend CoroutineScope.() -> T | ||||
| ): Result<T?> = runCatching { | ||||
|     safelyWithoutExceptions(onException, block) | ||||
| suspend fun <T> runCatchingSafelyWithoutExceptions( | ||||
|     onException: ExceptionHandler<T?> = defaultSafelyExceptionHandler, | ||||
|     block: suspend () -> T | ||||
| ): Result<T?> = runCatchingSafely(onException, block).let { | ||||
|     if (it.isFailure) return Result.success<T?>(null) | ||||
|  | ||||
|     it | ||||
| } | ||||
|  | ||||
| inline fun CoroutineScope( | ||||
| fun CoroutineScopeWithDefaultFallback( | ||||
|     context: CoroutineContext, | ||||
|     noinline defaultExceptionsHandler: ExceptionHandler<Unit> | ||||
|     defaultExceptionsHandler: ExceptionHandler<Unit> | ||||
| ) = CoroutineScope( | ||||
|     context + ContextSafelyExceptionHandler(defaultExceptionsHandler) | ||||
| ) | ||||
|   | ||||
| @@ -4,38 +4,38 @@ import kotlinx.coroutines.* | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| import kotlin.coroutines.EmptyCoroutineContext | ||||
|  | ||||
| inline fun CoroutineScope.launchSafely( | ||||
| fun CoroutineScope.launchSafely( | ||||
|     context: CoroutineContext = EmptyCoroutineContext, | ||||
|     start: CoroutineStart = CoroutineStart.DEFAULT, | ||||
|     noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, | ||||
|     noinline block: suspend CoroutineScope.() -> Unit | ||||
|     onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, | ||||
|     block: suspend () -> Unit | ||||
| ) = launch(context, start) { | ||||
|     safely(onException, block) | ||||
|     runCatchingSafely(onException, block = block) | ||||
| } | ||||
|  | ||||
| inline fun CoroutineScope.launchSafelyWithoutExceptions( | ||||
| fun CoroutineScope.launchSafelyWithoutExceptions( | ||||
|     context: CoroutineContext = EmptyCoroutineContext, | ||||
|     start: CoroutineStart = CoroutineStart.DEFAULT, | ||||
|     noinline onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
|     noinline block: suspend CoroutineScope.() -> Unit | ||||
|     onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
|     block: suspend () -> Unit | ||||
| ) = launch(context, start) { | ||||
|     safelyWithoutExceptions(onException, block) | ||||
|     runCatchingSafelyWithoutExceptions(onException, block = block) | ||||
| } | ||||
|  | ||||
| inline fun <T> CoroutineScope.asyncSafely( | ||||
| fun <T> CoroutineScope.asyncSafely( | ||||
|     context: CoroutineContext = EmptyCoroutineContext, | ||||
|     start: CoroutineStart = CoroutineStart.DEFAULT, | ||||
|     noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, | ||||
|     noinline block: suspend CoroutineScope.() -> T | ||||
|     onException: ExceptionHandler<T> = defaultSafelyExceptionHandler, | ||||
|     block: suspend () -> T | ||||
| ) = async(context, start) { | ||||
|     safely(onException, block) | ||||
|     runCatchingSafely(onException, block = block) | ||||
| } | ||||
|  | ||||
| inline fun <T> CoroutineScope.asyncSafelyWithoutExceptions( | ||||
| fun <T> CoroutineScope.asyncSafelyWithoutExceptions( | ||||
|     context: CoroutineContext = EmptyCoroutineContext, | ||||
|     start: CoroutineStart = CoroutineStart.DEFAULT, | ||||
|     noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
|     noinline block: suspend CoroutineScope.() -> T | ||||
|     onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
|     block: suspend () -> T | ||||
| ) = async(context, start) { | ||||
|     safelyWithoutExceptions(onException, block) | ||||
|     runCatchingSafelyWithoutExceptions(onException, block = block) | ||||
| } | ||||
|   | ||||
| @@ -44,7 +44,7 @@ sealed interface SmartMutex { | ||||
|      * @param locked Preset state of [isLocked] and its internal [_lockStateFlow] | ||||
|      */ | ||||
|     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() | ||||
|  | ||||
|         private val internalChangesMutex = Mutex() | ||||
|   | ||||
| @@ -45,7 +45,7 @@ sealed interface SmartSemaphore { | ||||
|      * @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow] | ||||
|      */ | ||||
|     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() | ||||
|  | ||||
|         private val internalChangesMutex = Mutex(false) | ||||
|   | ||||
| @@ -0,0 +1,70 @@ | ||||
| package dev.inmo.micro_utils.coroutines | ||||
|  | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.ExperimentalCoroutinesApi | ||||
| import kotlinx.coroutines.InternalCoroutinesApi | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.flow.FlowCollector | ||||
| import kotlinx.coroutines.flow.MutableSharedFlow | ||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||
| import kotlinx.coroutines.flow.StateFlow | ||||
| import kotlinx.coroutines.internal.SynchronizedObject | ||||
| import kotlinx.coroutines.internal.synchronized | ||||
| import kotlin.coroutines.CoroutineContext | ||||
|  | ||||
| /** | ||||
|  * Works like [StateFlow], but guarantee that latest value update will always be delivered to | ||||
|  * each active subscriber | ||||
|  */ | ||||
| open class SpecialMutableStateFlow<T>( | ||||
|     initialValue: T | ||||
| ) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> { | ||||
|     @OptIn(InternalCoroutinesApi::class) | ||||
|     private val syncObject = SynchronizedObject() | ||||
|     protected val sharingFlow: MutableSharedFlow<T> = MutableSharedFlow( | ||||
|         replay = 1, | ||||
|         extraBufferCapacity = 1, | ||||
|         onBufferOverflow = BufferOverflow.DROP_OLDEST | ||||
|     ) | ||||
|  | ||||
|     @OptIn(InternalCoroutinesApi::class) | ||||
|     override var value: T = initialValue | ||||
|         set(value) { | ||||
|             synchronized(syncObject) { | ||||
|                 if (field != value) { | ||||
|                     field = value | ||||
|                     sharingFlow.tryEmit(value) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     override val replayCache: List<T> | ||||
|         get() = sharingFlow.replayCache | ||||
|     override val subscriptionCount: StateFlow<Int> | ||||
|         get() = sharingFlow.subscriptionCount | ||||
|  | ||||
|     init { | ||||
|         sharingFlow.tryEmit(initialValue) | ||||
|     } | ||||
|  | ||||
|     override fun compareAndSet(expect: T, update: T): Boolean { | ||||
|         if (expect == value) { | ||||
|             value = update | ||||
|         } | ||||
|         return expect == value | ||||
|     } | ||||
|  | ||||
|     @ExperimentalCoroutinesApi | ||||
|     override fun resetReplayCache() = sharingFlow.resetReplayCache() | ||||
|  | ||||
|     override fun tryEmit(value: T): Boolean { | ||||
|         return compareAndSet(this.value, value) | ||||
|     } | ||||
|  | ||||
|     override suspend fun emit(value: T) { | ||||
|         compareAndSet(this.value, value) | ||||
|     } | ||||
|  | ||||
|     override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector) | ||||
| } | ||||
| @@ -10,8 +10,12 @@ fun CoroutineScope.LinkedSupervisorJob( | ||||
|     additionalContext: CoroutineContext? = null | ||||
| ) = coroutineContext.LinkedSupervisorJob(additionalContext) | ||||
|  | ||||
| fun CoroutineScope.LinkedSupervisorScope( | ||||
|  | ||||
| fun CoroutineContext.LinkedSupervisorScope( | ||||
|     additionalContext: CoroutineContext? = null | ||||
| ) = CoroutineScope( | ||||
|     coroutineContext + LinkedSupervisorJob(additionalContext) | ||||
|     this + LinkedSupervisorJob(additionalContext) | ||||
| ) | ||||
| fun CoroutineScope.LinkedSupervisorScope( | ||||
|     additionalContext: CoroutineContext? = null | ||||
| ) = coroutineContext.LinkedSupervisorScope(additionalContext) | ||||
|   | ||||
| @@ -4,28 +4,71 @@ import kotlinx.coroutines.* | ||||
| import kotlin.coroutines.CoroutineContext | ||||
| 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, | ||||
|     start: CoroutineStart = CoroutineStart.DEFAULT, | ||||
|     block: suspend CoroutineScope.() -> Unit | ||||
| ): Job { | ||||
|     val scope = createWeakSubScope() | ||||
|     val scope = WeakScope(this) | ||||
|     val job = scope.launch(context, start, block) | ||||
|     job.invokeOnCompletion { scope.cancel() } | ||||
|     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, | ||||
|     start: CoroutineStart = CoroutineStart.DEFAULT, | ||||
|     block: suspend CoroutineScope.() -> T | ||||
| ): Deferred<T> { | ||||
|     val scope = createWeakSubScope() | ||||
|     val scope = WeakScope(this) | ||||
|     val deferred = scope.async(context, start, block) | ||||
|     deferred.invokeOnCompletion { scope.cancel() } | ||||
|     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) | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										64
									
								
								coroutines/src/commonTest/kotlin/WeakJobTests.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								coroutines/src/commonTest/kotlin/WeakJobTests.kt
									
									
									
									
									
										Normal 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) | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,6 @@ | ||||
| package dev.inmo.micro_utils.fsm.common | ||||
|  | ||||
| import dev.inmo.micro_utils.common.Optional | ||||
| import dev.inmo.micro_utils.common.onPresented | ||||
| import dev.inmo.micro_utils.coroutines.* | ||||
| import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler | ||||
| 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 | ||||
|      * [StatesManager.endChain]. | ||||
|      */ | ||||
|     override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { | ||||
|         (statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) { | ||||
|             launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) } | ||||
|         } | ||||
|         statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { | ||||
|             launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) } | ||||
|         } | ||||
|         statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) { removedState -> | ||||
|             launch { | ||||
|                 statesJobsMutex.withLock { | ||||
|                     val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState } | ||||
|                     if (stateInMap === removedState) { | ||||
|                         statesJobs[stateInMap] ?.cancel() | ||||
|     override fun start(scope: CoroutineScope): Job { | ||||
|         val supervisorScope = scope.LinkedSupervisorScope() | ||||
|         supervisorScope.launchSafelyWithoutExceptions { | ||||
|             (statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(supervisorScope) { | ||||
|                 supervisorScope.launch { performStateUpdate(Optional.absent(), it, supervisorScope) } | ||||
|             } | ||||
|             statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(supervisorScope) { | ||||
|                 supervisorScope.launch { performStateUpdate(Optional.presented(it.first), it.second, supervisorScope) } | ||||
|             } | ||||
|             statesManager.onEndChain.subscribeSafelyWithoutExceptions(supervisorScope) { removedState -> | ||||
|                 supervisorScope.launch { | ||||
|                     statesJobsMutex.withLock { | ||||
|                         val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState } | ||||
|                         if (stateInMap === removedState) { | ||||
|                             statesJobs[stateInMap] ?.cancel() | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return supervisorScope.coroutineContext.job | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -18,13 +18,13 @@ if (new File(projectDir, "secret.gradle").exists()) { | ||||
|     githubRelease { | ||||
|         token "${project.property('GITHUB_RELEASE_TOKEN')}" | ||||
|  | ||||
|         owner "InsanusMokrassar" | ||||
|         repo "MicroUtils" | ||||
|         owner = "InsanusMokrassar" | ||||
|         repo = "MicroUtils" | ||||
|  | ||||
|         tagName "v${project.version}" | ||||
|         releaseName "${project.version}" | ||||
|         targetCommitish "${project.version}" | ||||
|         tagName = "v${project.version}" | ||||
|         releaseName = "${project.version}" | ||||
|         targetCommitish = "${project.version}" | ||||
|  | ||||
|         body getCurrentVersionChangelog() | ||||
|         body = getCurrentVersionChangelog() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ kotlin.incremental.js=true | ||||
| #kotlin.experimental.tryK2=true | ||||
| android.useAndroidX=true | ||||
| android.enableJetifier=true | ||||
| org.gradle.jvmargs=-Xmx2500m | ||||
| org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g | ||||
|  | ||||
| # JS NPM | ||||
|  | ||||
| @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 | ||||
| # Project data | ||||
|  | ||||
| group=dev.inmo | ||||
| version=0.20.12 | ||||
| android_code_version=218 | ||||
| version=0.20.53 | ||||
| android_code_version=259 | ||||
|   | ||||
| @@ -1,41 +1,41 @@ | ||||
| [versions] | ||||
|  | ||||
| kt = "1.9.20" | ||||
| kt-serialization = "1.6.0" | ||||
| kt-coroutines = "1.7.3" | ||||
| kt = "1.9.23" | ||||
| kt-serialization = "1.6.3" | ||||
| kt-coroutines = "1.8.1" | ||||
|  | ||||
| kslog = "1.2.4" | ||||
| kslog = "1.3.4" | ||||
|  | ||||
| jb-compose = "1.5.10" | ||||
| jb-exposed = "0.44.1" | ||||
| jb-dokka = "1.9.10" | ||||
| jb-compose = "1.6.2" | ||||
| jb-exposed = "0.50.1" | ||||
| jb-dokka = "1.9.20" | ||||
|  | ||||
| korlibs = "4.0.10" | ||||
| uuid = "0.8.1" | ||||
| korlibs = "5.4.0" | ||||
| uuid = "0.8.4" | ||||
|  | ||||
| ktor = "2.3.5" | ||||
| 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.20-1.0.13" | ||||
| kotlin-poet = "1.14.2" | ||||
| ksp = "1.9.23-1.0.20" | ||||
| kotlin-poet = "1.16.0" | ||||
|  | ||||
| versions = "0.49.0" | ||||
| versions = "0.51.0" | ||||
|  | ||||
| android-gradle = "8.1.2" | ||||
| android-gradle = "8.2.0" | ||||
| dexcount = "4.0.0" | ||||
|  | ||||
| android-coreKtx = "1.12.0" | ||||
| android-coreKtx = "1.13.1" | ||||
| android-recyclerView = "1.3.2" | ||||
| android-appCompat = "1.6.1" | ||||
| android-fragment = "1.6.2" | ||||
| android-fragment = "1.7.1" | ||||
| android-espresso = "3.5.1" | ||||
| android-test = "1.1.5" | ||||
| android-compose-material3 = "1.1.2" | ||||
| android-compose-material3 = "1.2.1" | ||||
|  | ||||
| android-props-minSdk = "21" | ||||
| 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" } | ||||
|  | ||||
| klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" } | ||||
| krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" } | ||||
| klock = { module = "com.soywiz.korge:korlibs-time", version.ref = "korlibs" } | ||||
| krypto = { module = "com.soywiz.korge:korlibs-crypto", version.ref = "korlibs" } | ||||
| uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | ||||
| koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
|   | ||||
| @@ -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 { | ||||
|                         name = "Gitea" | ||||
|                         url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") | ||||
|                         name = "InmoNexus" | ||||
|                         url = uri("https://nexus.inmo.dev/repository/maven-releases/") | ||||
|                  | ||||
|                         credentials(HttpHeaderCredentials) { | ||||
|                             name = "Authorization" | ||||
|                             value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') | ||||
|                         } | ||||
|                  | ||||
|                         authentication { | ||||
|                             header(HttpHeaderAuthentication) | ||||
|                         credentials { | ||||
|                             username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER') | ||||
|                             password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD') | ||||
|                         } | ||||
|                  | ||||
|                     } | ||||
| @@ -121,4 +117,21 @@ if (project.hasProperty("signing.gnupg.keyName")) { | ||||
|         def signingTasks = project.getTasks().withType(Sign.class) | ||||
|         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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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"} | ||||
| @@ -15,9 +15,6 @@ kotlin { | ||||
|                 api libs.ktor.client | ||||
|             } | ||||
|         } | ||||
|         androidMain { | ||||
|             dependsOn jvmMain | ||||
|         } | ||||
|  | ||||
|         linuxX64Main { | ||||
|             dependencies { | ||||
|   | ||||
| @@ -12,13 +12,9 @@ kotlin { | ||||
|             dependencies { | ||||
|                 api internalProject("micro_utils.common") | ||||
|                 api libs.kt.serialization.cbor | ||||
|                 api libs.klock | ||||
|                 api libs.uuid | ||||
|                 api libs.ktor.io | ||||
|             } | ||||
|         } | ||||
|         androidMain { | ||||
|             dependsOn jvmMain | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -41,7 +41,7 @@ class TemporalFilesRoutingConfigurator( | ||||
|                     filesMutex: Mutex, | ||||
|                     onNewFileFlow: Flow<TemporalFileId> | ||||
|                 ): Job = scope.launchSafelyWithoutExceptions { | ||||
|                     while (isActive) { | ||||
|                     while (currentCoroutineContext().isActive) { | ||||
|                         val filesWithCreationInfo = filesMap.mapNotNull { (fileId, file) -> | ||||
|                             fileId to ((Files.getAttribute(file.toPath(), "creationTime") as? FileTime) ?.toMillis() ?: return@mapNotNull null) | ||||
|                         } | ||||
|   | ||||
| @@ -5,11 +5,3 @@ plugins { | ||||
| } | ||||
|  | ||||
| apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | ||||
|  | ||||
| kotlin { | ||||
|     sourceSets { | ||||
|         androidMain { | ||||
|             dependsOn jvmMain | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,9 +12,12 @@ private val json = Json { | ||||
|     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 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)" | ||||
| 
 | ||||
| @Serializable | ||||
| @@ -78,14 +81,12 @@ private fun printLanguageCodeAndTags( | ||||
|     indents: String = "    " | ||||
| ): String = if (tag.subtags.isEmpty()) { | ||||
| """${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 { | ||||
| """ | ||||
| ${indents}${baseClassSerializerAnnotationName} | ||||
| ${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() { | ||||
| ${indents}    override val code: String = "${tag.tag}" | ||||
| ${indents}    override val withoutDialect: String | ||||
| ${indents}        get() = code | ||||
| ${indents}    override val code: String = "${tag.tag}"${parent ?.let { parent -> "\n${indents}    override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""} | ||||
| 
 | ||||
| ${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 | ||||
| 
 | ||||
| /** | ||||
| @@ -106,20 +107,27 @@ import kotlinx.serialization.Serializable | ||||
| ${baseClassSerializerAnnotationName} | ||||
| sealed class $baseClassName { | ||||
|     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 = "    ") } } | ||||
| 
 | ||||
|     $baseClassSerializerAnnotationName | ||||
|     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 | ||||
| } | ||||
| @Deprecated("Renamed", ReplaceWith("$baseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName")) | ||||
| typealias $oldBaseClassName = $baseClassName | ||||
| """.trimIndent() | ||||
| 
 | ||||
| fun createStringConverterCode(tags: List<Tag>): String { | ||||
| fun createStringConverterCode(tags: List<Tag>, prePackage: String): String { | ||||
|     fun createDeserializeVariantForTag( | ||||
|         tag: Tag, | ||||
|         pretitle: String = baseClassName, | ||||
| @@ -128,19 +136,70 @@ fun createStringConverterCode(tags: List<Tag>): String { | ||||
|         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 """fun String.as$baseClassName(): $baseClassName { | ||||
|     return when (this) { | ||||
| ${tags.joinToString("\n") { createDeserializeVariantForTag(it) }} | ||||
|         else -> $baseClassName.${unknownBaseClassName}(this) | ||||
|     fun createInheritorVariantForTag( | ||||
|         tag: Tag, | ||||
|         pretitle: String = baseClassName, | ||||
|         indents: String = "        " | ||||
|     ): 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}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}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 | ||||
| import kotlinx.serialization.builtins.serializer | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| @@ -153,16 +212,20 @@ object $baseClassSerializerName : KSerializer<$baseClassName> { | ||||
|         return $baseClassName(decoder.decodeString()) | ||||
|     } | ||||
| 
 | ||||
|     override fun serialize(encoder: Encoder, value: IetfLanguageCode) { | ||||
|     override fun serialize(encoder: Encoder, value: $baseClassName) { | ||||
|         encoder.encodeString(value.code) | ||||
|     } | ||||
| } | ||||
| @Deprecated("Renamed", ReplaceWith("$baseClassSerializerName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassSerializerName")) | ||||
| typealias $oldBaseClassSerializerName = $baseClassSerializerName | ||||
| """ | ||||
| } | ||||
| 
 | ||||
| suspend fun main(vararg args: String) { | ||||
|     val outputFolder = args.firstOrNull() ?.let { File(it) } | ||||
|     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 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 { | ||||
|         delete() | ||||
|         createNewFile() | ||||
|         writeText(buildKtFileContent(tags)) | ||||
|         writeText(targetPackagePrefix + buildKtFileContent(tags, targetPackage ?: "")) | ||||
|     } | ||||
| 
 | ||||
|     File(outputFolder, "StringToLanguageCodes.kt").apply { | ||||
|         delete() | ||||
|         createNewFile() | ||||
|         writeText(createStringConverterCode(tags)) | ||||
|         writeText(targetPackagePrefix + createStringConverterCode(tags, targetPackage ?: "")) | ||||
|     } | ||||
| 
 | ||||
|     File(outputFolder, "$baseClassSerializerName.kt").apply { | ||||
|         delete() | ||||
|         createNewFile() | ||||
|         writeText(createSerializerCode(tags)) | ||||
|         writeText(targetPackagePrefix + createSerializerCode(tags, targetPackage ?: "")) | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,9 @@ package dev.inmo.micro_utils.language_codes | ||||
|  | ||||
| import java.util.Locale | ||||
|  | ||||
| fun IetfLanguageCode.toJavaLocale(): Locale = Locale.forLanguageTag(code) | ||||
| fun IetfLanguageCode?.toJavaLocaleOrDefault(): Locale = this ?.toJavaLocale() ?: Locale.getDefault() | ||||
| fun IetfLang.toJavaLocale(): Locale = Locale.forLanguageTag(code) | ||||
| 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() | ||||
|   | ||||
| @@ -1,12 +1,15 @@ | ||||
| package dev.inmo.micro_utils.matrix | ||||
|  | ||||
| class MatrixBuilder<T> { | ||||
| open class MatrixBuilder<T> { | ||||
|     private val mutMatrix: MutableList<List<T>> = ArrayList() | ||||
|     val matrix: Matrix<T> | ||||
|         get() = mutMatrix | ||||
|  | ||||
|     fun row(t: List<T>) = mutMatrix.add(t) | ||||
|     fun add(t: List<T>) = mutMatrix.add(t) | ||||
|     operator fun List<T>.unaryPlus() = row(this) | ||||
|     operator fun plus(t: List<T>) = add(t) | ||||
|     operator fun T.unaryPlus() = add(listOf(this)) | ||||
| } | ||||
|  | ||||
| fun <T> MatrixBuilder<T>.row(block: RowBuilder<T>.() -> Unit) = +RowBuilder<T>().also(block).row | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| package dev.inmo.micro_utils.matrix | ||||
|  | ||||
| class RowBuilder<T> { | ||||
| open class RowBuilder<T> { | ||||
|     private val mutRow: MutableList<T> = ArrayList() | ||||
|     val row: Row<T> | ||||
|         get() = mutRow | ||||
|  | ||||
|     fun column(t: T) = mutRow.add(t) | ||||
|     fun add(t: T) = mutRow.add(t) | ||||
|     operator fun T.unaryPlus() = column(this) | ||||
|     fun column(t: T) = mutRow.add(t) | ||||
| } | ||||
|  | ||||
| fun <T> row(block: RowBuilder<T>.() -> Unit): List<T> = RowBuilder<T>().also(block).row | ||||
|   | ||||
| @@ -60,6 +60,12 @@ kotlin { | ||||
|                 implementation kotlin('test-junit') | ||||
|             } | ||||
|         } | ||||
|         nativeMain.dependsOn commonMain | ||||
|         linuxX64Main.dependsOn nativeMain | ||||
|         mingwX64Main.dependsOn nativeMain | ||||
|         linuxArm64Main.dependsOn nativeMain | ||||
|  | ||||
|         androidMain.dependsOn jvmMain | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -45,6 +45,13 @@ kotlin { | ||||
|                 implementation kotlin('test-junit') | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         nativeMain.dependsOn commonMain | ||||
|         linuxX64Main.dependsOn nativeMain | ||||
|         mingwX64Main.dependsOn nativeMain | ||||
|         linuxArm64Main.dependsOn nativeMain | ||||
|  | ||||
|         androidMain.dependsOn jvmMain | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -55,6 +55,10 @@ kotlin { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         nativeMain.dependsOn commonMain | ||||
|         linuxX64Main.dependsOn nativeMain | ||||
|         mingwX64Main.dependsOn nativeMain | ||||
|  | ||||
|         androidMain.dependsOn jvmMain | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,8 +28,8 @@ kotlin { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 implementation kotlin('stdlib') | ||||
|                 api libs.kt.serialization | ||||
|                 implementation compose.runtime | ||||
|                 api libs.kt.serialization | ||||
|             } | ||||
|         } | ||||
|         commonTest { | ||||
| @@ -47,6 +47,7 @@ kotlin { | ||||
|         jvmTest { | ||||
|             dependencies { | ||||
|                 implementation kotlin('test-junit') | ||||
|                 implementation compose.uiTest | ||||
|             } | ||||
|         } | ||||
|         jsMain { | ||||
| @@ -61,14 +62,20 @@ kotlin { | ||||
|             } | ||||
|         } | ||||
|         androidUnitTest { | ||||
|             dependencies { | ||||
|                 implementation kotlin('test-junit') | ||||
|                 implementation libs.android.test.junit | ||||
|                 implementation libs.android.espresso | ||||
|                 implementation compose.uiTest | ||||
|             } | ||||
|         } | ||||
|         androidInstrumentedTest { | ||||
|             dependencies { | ||||
|                 implementation kotlin('test-junit') | ||||
|                 implementation libs.android.test.junit | ||||
|                 implementation libs.android.espresso | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         androidMain.dependsOn jvmMain | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,13 @@ data class PaginationResult<T>( | ||||
|      * Amount of pages for current pagination | ||||
|      */ | ||||
|     @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( | ||||
|         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( | ||||
|     basePagination: Pagination | ||||
|     basePagination: Pagination, | ||||
|     objectsNumber: Number | ||||
| ) = PaginationResult<T>( | ||||
|     basePagination.page, | ||||
|     basePagination.size, | ||||
|     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 | ||||
|  */ | ||||
| fun <I, O> PaginationResult<I>.changeResultsUnchecked( | ||||
|     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 | ||||
|  */ | ||||
| fun <I, O> PaginationResult<I>.changeResults( | ||||
|     data: List<O> | ||||
| ): PaginationResult<O> { | ||||
|     require(data.size == results.size) | ||||
|     return changeResultsUnchecked(data) | ||||
| } | ||||
| ): PaginationResult<O> = changeResults { data } | ||||
|  | ||||
| fun <T> List<T>.createPaginationResult( | ||||
|     pagination: Pagination, | ||||
|   | ||||
| @@ -10,8 +10,7 @@ inline fun doWithPagination( | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Suppress("NOTHING_TO_INLINE") | ||||
| inline fun PaginationResult<*>.nextPageIfNotEmpty() = if (results.isNotEmpty()) { | ||||
| inline fun <T, PR: PaginationResult<T>> PR.nextPageIfTrue(condition: PR.() -> Boolean) = if (condition()) { | ||||
|     SimplePagination( | ||||
|         page + 1, | ||||
|         size | ||||
| @@ -20,12 +19,28 @@ inline fun PaginationResult<*>.nextPageIfNotEmpty() = if (results.isNotEmpty()) | ||||
|     null | ||||
| } | ||||
|  | ||||
| @Suppress("NOTHING_TO_INLINE") | ||||
| inline fun <T> PaginationResult<T>.thisPageIfNotEmpty(): PaginationResult<T>? = if (results.isNotEmpty()) { | ||||
| inline fun <T, PR: PaginationResult<T>> PR.thisPageIfTrue(condition: PR.() -> Boolean): PR? = if (condition()) { | ||||
|     this | ||||
| } else { | ||||
|     null | ||||
| } | ||||
|  | ||||
| @Suppress("NOTHING_TO_INLINE") | ||||
| inline fun <T> PaginationResult<T>.currentPageIfNotEmpty() = thisPageIfNotEmpty() | ||||
| fun PaginationResult<*>.nextPageIfNotEmpty() = nextPageIfTrue { results.isNotEmpty() } | ||||
|  | ||||
| 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() | ||||
|   | ||||
| @@ -18,7 +18,7 @@ inline fun <T> doForAllWithNextPaging( | ||||
| ) { | ||||
|     doForAll( | ||||
|         initialPagination, | ||||
|         { it.nextPageIfNotEmpty() }, | ||||
|         { it.nextPageIfNotEmptyOrLastPage() }, | ||||
|         block | ||||
|     ) | ||||
| } | ||||
| @@ -29,7 +29,7 @@ inline fun <T> doAllWithCurrentPaging( | ||||
| ) { | ||||
|     doForAll( | ||||
|         initialPagination, | ||||
|         { it.currentPageIfNotEmpty() }, | ||||
|         { it.nextPageIfNotEmptyOrLastPage() }, | ||||
|         block | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -31,7 +31,7 @@ inline fun <T> getAllWithNextPaging( | ||||
|     block: (Pagination) -> PaginationResult<T> | ||||
| ): List<T> = getAll( | ||||
|     initialPagination, | ||||
|     { it.nextPageIfNotEmpty() }, | ||||
|     { it.nextPageIfNotEmptyOrLastPage() }, | ||||
|     block | ||||
| ) | ||||
|  | ||||
| @@ -48,7 +48,7 @@ inline fun <T> getAllWithCurrentPaging( | ||||
|     block: (Pagination) -> PaginationResult<T> | ||||
| ): List<T> = getAll( | ||||
|     initialPagination, | ||||
|     { it.currentPageIfNotEmpty() }, | ||||
|     { it.thisPageIfNotEmptyOrLastPage() }, | ||||
|     block | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -21,33 +21,25 @@ fun <T> Iterable<T>.paginate(with: Pagination): PaginationResult<T> { | ||||
| } | ||||
|  | ||||
| fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> { | ||||
|     val firstIndex = maxOf(with.firstIndex, 0) | ||||
|     val lastIndex = minOf(with.lastIndexExclusive, size) | ||||
|     if (firstIndex > lastIndex) { | ||||
|         return emptyPaginationResult() | ||||
|     if (with.firstIndex >= size || with.lastIndex < 0) { | ||||
|         return emptyPaginationResult(with, size.toLong()) | ||||
|     } | ||||
|     return subList(firstIndex, lastIndex).createPaginationResult( | ||||
|     return asSequence().drop(with.firstIndex).take(with.size).toList().createPaginationResult( | ||||
|         with, | ||||
|         size.toLong() | ||||
|     ) | ||||
| } | ||||
|  | ||||
| fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> { | ||||
|     val actualPagination = with.optionallyReverse( | ||||
|         size, | ||||
|         reversed | ||||
|     ) | ||||
|  | ||||
|     val firstIndex = maxOf(actualPagination.firstIndex, 0) | ||||
|     val lastIndex = minOf(actualPagination.lastIndexExclusive, size) | ||||
|     if (firstIndex > lastIndex) { | ||||
|         return emptyPaginationResult() | ||||
|     return if (reversed) { | ||||
|         val actualPagination = with.optionallyReverse( | ||||
|             size, | ||||
|             reversed | ||||
|         ) | ||||
|         paginate(actualPagination).changeResultsUnchecked { results.reversed() } | ||||
|     } else { | ||||
|         paginate(with) | ||||
|     } | ||||
|  | ||||
|     return subList(firstIndex, lastIndex).optionallyReverse(reversed).createPaginationResult( | ||||
|         with, | ||||
|         size.toLong() | ||||
|     ) | ||||
| } | ||||
|  | ||||
| fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> { | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -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 { | ||||
|                     name = "Gitea" | ||||
|                     url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") | ||||
|                     name = "InmoNexus" | ||||
|                     url = uri("https://nexus.inmo.dev/repository/maven-releases/") | ||||
|              | ||||
|                     credentials(HttpHeaderCredentials) { | ||||
|                         name = "Authorization" | ||||
|                         value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') | ||||
|                     } | ||||
|              | ||||
|                     authentication { | ||||
|                         header(HttpHeaderAuthentication) | ||||
|                     credentials { | ||||
|                         username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER') | ||||
|                         password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD') | ||||
|                     } | ||||
|              | ||||
|                 } | ||||
| @@ -109,4 +105,21 @@ if (project.hasProperty("signing.gnupg.keyName")) { | ||||
|         def signingTasks = project.getTasks().withType(Sign.class) | ||||
|         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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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"}}} | ||||
| @@ -5,6 +5,7 @@ import dev.inmo.micro_utils.coroutines.withReadAcquire | ||||
| import dev.inmo.micro_utils.coroutines.withWriteLock | ||||
| import dev.inmo.micro_utils.repos.* | ||||
| 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 kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| @@ -29,7 +30,7 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>( | ||||
|             kvCache.getAll() | ||||
|         }.takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also { | ||||
|             locker.withWriteLock { | ||||
|                 kvCache.actualizeAll(true) { it } | ||||
|                 kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -148,7 +149,9 @@ open class CRUDCacheRepo<ObjectType, IdType, InputValueType>( | ||||
|     locker, | ||||
|     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( | ||||
|     kvCache: KVCache<IdType, ObjectType>, | ||||
|   | ||||
| @@ -1,5 +1,11 @@ | ||||
| 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() | ||||
| } | ||||
|  | ||||
| typealias CacheRepo = InvalidatableRepo | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import dev.inmo.micro_utils.coroutines.withWriteLock | ||||
| import dev.inmo.micro_utils.pagination.* | ||||
| import dev.inmo.micro_utils.repos.* | ||||
| import dev.inmo.micro_utils.repos.cache.cache.KVCache | ||||
| import dev.inmo.micro_utils.repos.cache.util.actualizeAll | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.flow.* | ||||
| @@ -48,9 +49,7 @@ open class ReadKeyValueCacheRepo<Key,Value>( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun invalidate() = locker.withWriteLock { | ||||
|         kvCache.clear() | ||||
|     } | ||||
|     override suspend fun invalidate() = kvCache.actualizeAll(parentRepo, locker = locker) | ||||
| } | ||||
|  | ||||
| fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( | ||||
| @@ -75,10 +74,6 @@ open class KeyValueCacheRepo<Key,Value>( | ||||
|         } | ||||
|     }.launchIn(scope) | ||||
|  | ||||
|     override suspend fun invalidate() = locker.withWriteLock { | ||||
|         kvCache.clear() | ||||
|     } | ||||
|  | ||||
|     override suspend fun clear() { | ||||
|         parentRepo.clear() | ||||
|         locker.withWriteLock { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import dev.inmo.micro_utils.pagination.* | ||||
| import dev.inmo.micro_utils.pagination.utils.* | ||||
| import dev.inmo.micro_utils.repos.* | ||||
| import dev.inmo.micro_utils.repos.cache.cache.KVCache | ||||
| import dev.inmo.micro_utils.repos.cache.util.actualizeAll | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.flow.* | ||||
| @@ -47,9 +48,7 @@ open class ReadKeyValuesCacheRepo<Key,Value>( | ||||
|         kvCache.contains(k) | ||||
|     } || parentRepo.contains(k) | ||||
|  | ||||
|     override suspend fun invalidate() = locker.withWriteLock { | ||||
|         kvCache.clear() | ||||
|     } | ||||
|     override suspend fun invalidate() = kvCache.actualizeAll(parentRepo, locker = locker) | ||||
| } | ||||
|  | ||||
| fun <Key, Value> ReadKeyValuesRepo<Key, Value>.cached( | ||||
| @@ -84,10 +83,6 @@ open class KeyValuesCacheRepo<Key,Value>( | ||||
|             kvCache.unset(it) | ||||
|         } | ||||
|     }.launchIn(scope) | ||||
|  | ||||
|     override suspend fun invalidate() = locker.withWriteLock { | ||||
|         kvCache.clear() | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun <Key, Value> KeyValuesRepo<Key, Value>.cached( | ||||
|   | ||||
| @@ -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.util.actualizeAll | ||||
| 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 kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.delay | ||||
| @@ -56,7 +57,7 @@ open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>( | ||||
|     override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap { | ||||
|         originalRepo.getAll() | ||||
|     }.onSuccess { | ||||
|         kvCache.actualizeAll(clear = true) { it } | ||||
|         kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } | ||||
|     }.getOrElse { | ||||
|         kvCache.getAll() | ||||
|     } | ||||
|   | ||||
| @@ -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.util.actualizeAll | ||||
| 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 kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.delay | ||||
| @@ -56,7 +57,7 @@ open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>( | ||||
|     override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap { | ||||
|         originalRepo.getAll() | ||||
|     }.onSuccess { | ||||
|         kvCache.actualizeAll(clear = true) { it } | ||||
|         kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } | ||||
|     }.getOrElse { | ||||
|         kvCache.getAll() | ||||
|     } | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import dev.inmo.micro_utils.pagination.Pagination | ||||
| import dev.inmo.micro_utils.pagination.PaginationResult | ||||
| import dev.inmo.micro_utils.repos.* | ||||
| 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 kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| @@ -42,7 +43,7 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>( | ||||
|     ) | ||||
|  | ||||
|     protected open suspend fun actualizeAll() { | ||||
|         locker.withWriteLock { kvCache.actualizeAll(parentRepo) } | ||||
|         kvCache.actualizeAll(parentRepo, locker = locker) | ||||
|     } | ||||
|  | ||||
|     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( | ||||
|         { getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull }, | ||||
|         { getAll() }, | ||||
|         { kvCache.actualizeAll(clear = true) { it } } | ||||
|         { kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } } | ||||
|     ) | ||||
|  | ||||
|     override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualizeWithWriteLock( | ||||
|   | ||||
| @@ -8,8 +8,8 @@ import dev.inmo.micro_utils.coroutines.withWriteLock | ||||
| import dev.inmo.micro_utils.pagination.Pagination | ||||
| import dev.inmo.micro_utils.pagination.PaginationResult | ||||
| 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.pagination.getAll | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.flow.* | ||||
| @@ -41,10 +41,7 @@ open class FullReadKeyValueCacheRepo<Key,Value>( | ||||
|         actualize = { locker.withWriteLock { actualize(it) } } | ||||
|     ) | ||||
|     protected open suspend fun actualizeAll() { | ||||
|         locker.withWriteLock { | ||||
|             kvCache.clear() | ||||
|             kvCache.set(parentRepo.getAll()) | ||||
|         } | ||||
|         kvCache.actualizeAll(parentRepo, locker) | ||||
|     } | ||||
|  | ||||
|     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( | ||||
|         { getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull }, | ||||
|         { getAll() }, | ||||
|         { kvCache.actualizeAll(clear = true) { it } } | ||||
|         { kvCache.actualizeAll(clearMode = ActualizeAllClearMode.BeforeSet) { it } } | ||||
|     ) | ||||
|  | ||||
|     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 invalidate() { | ||||
|         locker.withWriteLock { | ||||
|             kvCache.actualizeAll(parentRepo) | ||||
|         } | ||||
|         kvCache.actualizeAll(parentRepo, locker) | ||||
|     } | ||||
|  | ||||
|     override suspend fun clear() { | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import dev.inmo.micro_utils.coroutines.withWriteLock | ||||
| import dev.inmo.micro_utils.pagination.* | ||||
| import dev.inmo.micro_utils.pagination.utils.* | ||||
| 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 kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| @@ -41,15 +42,13 @@ open class FullReadKeyValuesCacheRepo<Key,Value>( | ||||
|     ) | ||||
|  | ||||
|     protected open suspend fun actualizeKey(k: Key) { | ||||
|         locker.withWriteLock { | ||||
|             kvCache.set(k, parentRepo.getAll(k)) | ||||
|         kvCache.actualizeAll(locker = locker, clearMode = ActualizeAllClearMode.Never) { | ||||
|             mapOf(k to parentRepo.getAll(k)) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected open suspend fun actualizeAll() { | ||||
|         locker.withWriteLock { | ||||
|             kvCache.actualizeAll(parentRepo) | ||||
|         } | ||||
|         kvCache.actualizeAll(parentRepo, locker = locker) | ||||
|     } | ||||
|  | ||||
|     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() { | ||||
|         locker.withWriteLock { | ||||
|             kvCache.actualizeAll(parentRepo) | ||||
|         } | ||||
|         kvCache.actualizeAll(parentRepo, locker = locker) | ||||
|     } | ||||
|  | ||||
|     override suspend fun set(toSet: Map<Key, List<Value>>) { | ||||
|         super<KeyValuesRepo>.set(toSet) | ||||
|     } | ||||
|  | ||||
|     override suspend fun removeWithValue(v: Value) { | ||||
|   | ||||
| @@ -1,43 +1,169 @@ | ||||
| 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 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> | ||||
| ) { | ||||
|     set( | ||||
|         getAll().also { | ||||
|             if (clear) { | ||||
|                 clear() | ||||
|             } | ||||
|         } | ||||
|     ) | ||||
|     clear() | ||||
|     val newData = getAll() | ||||
|     set(newData) | ||||
| } | ||||
|  | ||||
| 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( | ||||
|     repo: ReadKeyValueRepo<K, V>, | ||||
|     clear: Boolean = true, | ||||
|     locker: SmartRWLocker? = null, | ||||
|     clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet, | ||||
|     getAll: () -> Map<K, V> | ||||
| ) { | ||||
|     actualizeAll(clear) { | ||||
|         repo.getAll() | ||||
|     when (clearMode) { | ||||
|         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( | ||||
|     repo: ReadKeyValuesRepo<K, V>, | ||||
|     clear: Boolean = true, | ||||
|     parentRepo: ReadKeyValuesRepo<K, V>, | ||||
|     locker: SmartRWLocker? = null, | ||||
|     clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet, | ||||
| ) { | ||||
|     actualizeAll(clear) { | ||||
|         repo.getAll() | ||||
|     actualizeAll(locker, clearMode) { | ||||
|         parentRepo.getAll() | ||||
|     } | ||||
| } | ||||
|  | ||||
| suspend inline fun <K, V> KeyValueRepo<K, V>.actualizeAll( | ||||
|     repo: ReadCRUDRepo<V, K>, | ||||
|     clear: Boolean = true, | ||||
|     parentRepo: ReadCRUDRepo<V, K>, | ||||
|     locker: SmartRWLocker? = null, | ||||
|     clearMode: ActualizeAllClearMode = ActualizeAllClearMode.BeforeSet, | ||||
| ) { | ||||
|     actualizeAll(clear) { | ||||
|         repo.getAll() | ||||
|     actualizeAll(locker, clearMode) { | ||||
|         parentRepo.getAll() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,6 @@ kotlin { | ||||
|                 api internalProject("micro_utils.common") | ||||
|                 api internalProject("micro_utils.coroutines") | ||||
|             } | ||||
|             dependsOn jvmMain | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| 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.utils.doForAllWithNextPaging | ||||
| import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | ||||
| @@ -130,6 +131,14 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal | ||||
|  | ||||
|         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> | ||||
|  | ||||
|   | ||||
| @@ -42,6 +42,22 @@ interface WriteCRUDRepo<ObjectType, IdType, InputValueType> : Repo { | ||||
| } | ||||
| 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( | ||||
|     vararg values: InputValueType | ||||
| ): List<ObjectType> = create(values.toList()) | ||||
|   | ||||
| @@ -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)) | ||||
| } | ||||
| @@ -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)) | ||||
| } | ||||
| @@ -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)) | ||||
| } | ||||
| @@ -1,14 +1,19 @@ | ||||
| package dev.inmo.micro_utils.repos.exposed | ||||
|  | ||||
| import dev.inmo.micro_utils.repos.CRUDRepo | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
|  | ||||
| abstract class AbstractExposedCRUDRepo<ObjectType, IdType, InputValueType>( | ||||
|     flowsChannelsSize: Int = 0, | ||||
|     tableName: String = "" | ||||
|     tableName: String = "", | ||||
|     replyCacheInFlows: Int = 0, | ||||
|     onBufferOverflowBehaviour: BufferOverflow = BufferOverflow.SUSPEND | ||||
| ) : | ||||
|     AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | ||||
|         flowsChannelsSize, | ||||
|         tableName | ||||
|         tableName, | ||||
|         replyCacheInFlows, | ||||
|         onBufferOverflowBehaviour | ||||
|     ), | ||||
|     ExposedCRUDRepo<ObjectType, IdType>, | ||||
|     CRUDRepo<ObjectType, IdType, InputValueType> | ||||
|   | ||||
| @@ -35,14 +35,14 @@ abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>( | ||||
|     } | ||||
|     override suspend fun getById(id: IdType): ObjectType? { | ||||
|         return transaction(db = database) { | ||||
|             select { | ||||
|             selectAll().where { | ||||
|                 selectById(id) | ||||
|             }.limit(1).firstOrNull() ?.asObject | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.exposed | ||||
|  | ||||
| import dev.inmo.micro_utils.repos.UpdatedValuePair | ||||
| import dev.inmo.micro_utils.repos.WriteCRUDRepo | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.flow.* | ||||
| import org.jetbrains.exposed.sql.* | ||||
| import org.jetbrains.exposed.sql.statements.* | ||||
| @@ -11,19 +12,26 @@ import java.util.Objects | ||||
| abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | ||||
|     flowsChannelsSize: Int = 0, | ||||
|     tableName: String = "", | ||||
|     replyCacheInFlows: Int = 0 | ||||
|     replyCacheInFlows: Int = 0, | ||||
|     onBufferOverflowBehaviour: BufferOverflow = BufferOverflow.SUSPEND | ||||
| ) : | ||||
|     AbstractExposedReadCRUDRepo<ObjectType, IdType>(tableName), | ||||
|     ExposedCRUDRepo<ObjectType, IdType>, | ||||
|     WriteCRUDRepo<ObjectType, IdType, InputValueType> | ||||
| { | ||||
|     protected val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize) | ||||
|     protected val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize) | ||||
|     protected val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize) | ||||
|     protected open val _newObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize, onBufferOverflowBehaviour) | ||||
|     protected open val _updatedObjectsFlow = MutableSharedFlow<ObjectType>(replyCacheInFlows, flowsChannelsSize, onBufferOverflowBehaviour) | ||||
|     protected open val _deletedObjectsIdsFlow = MutableSharedFlow<IdType>(replyCacheInFlows, flowsChannelsSize, onBufferOverflowBehaviour) | ||||
|  | ||||
|     override val newObjectsFlow: Flow<ObjectType> = _newObjectsFlow.asSharedFlow() | ||||
|     override val updatedObjectsFlow: Flow<ObjectType> = _updatedObjectsFlow.asSharedFlow() | ||||
|     override val deletedObjectsIdsFlow: Flow<IdType> = _deletedObjectsIdsFlow.asSharedFlow() | ||||
|     override val newObjectsFlow: Flow<ObjectType> by lazy { | ||||
|         _newObjectsFlow.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 | ||||
|  | ||||
| @@ -93,7 +101,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | ||||
|         }.let { | ||||
|             if (it > 0) { | ||||
|                 transaction(db = database) { | ||||
|                     select { | ||||
|                     selectAll().where { | ||||
|                         selectById(this, id) | ||||
|                     }.limit(1).firstOrNull() ?.asObject | ||||
|                 } | ||||
| @@ -134,7 +142,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | ||||
|                 ids | ||||
|             } else { | ||||
|                 ids.filter { | ||||
|                     select { selectById(it) }.limit(1).none() | ||||
|                     selectAll().where { selectById(it) }.limit(1).none() | ||||
|                 } | ||||
|             } | ||||
|         }.forEach { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package dev.inmo.micro_utils.repos.exposed.keyvalue | ||||
|  | ||||
| import dev.inmo.micro_utils.repos.KeyValueRepo | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.flow.* | ||||
| import org.jetbrains.exposed.sql.* | ||||
| import org.jetbrains.exposed.sql.statements.* | ||||
| @@ -8,13 +9,15 @@ import org.jetbrains.exposed.sql.transactions.transaction | ||||
|  | ||||
| abstract class AbstractExposedKeyValueRepo<Key, Value>( | ||||
|     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>( | ||||
|     database, | ||||
|     tableName | ||||
| ) { | ||||
|     protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>() | ||||
|     protected val _onValueRemoved = MutableSharedFlow<Key>() | ||||
|     protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||
|     protected val _onValueRemoved = MutableSharedFlow<Key>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||
|  | ||||
|     override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow() | ||||
|     override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow() | ||||
| @@ -65,7 +68,7 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>( | ||||
|     override suspend fun unsetWithValues(toUnset: List<Value>) { | ||||
|         transaction(database) { | ||||
|             toUnset.flatMap { | ||||
|                 val keys = select { selectByValue(it) }.mapNotNull { it.asKey } | ||||
|                 val keys = selectAll().where { selectByValue(it) }.mapNotNull { it.asKey } | ||||
|                 deleteWhere { selectByIds(it, keys) } | ||||
|                 keys | ||||
|             } | ||||
|   | ||||
| @@ -24,11 +24,11 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>( | ||||
|     abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> | ||||
|  | ||||
|     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) { | ||||
|         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 } } | ||||
| @@ -46,7 +46,7 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>( | ||||
|     } | ||||
|  | ||||
|     override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) { | ||||
|         select { selectByValue(v) }.selectPaginated( | ||||
|         selectAll().where { selectByValue(v) }.selectPaginated( | ||||
|             pagination, | ||||
|             keyColumn, | ||||
|             reversed | ||||
|   | ||||
| @@ -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.exposed.ColumnAllocator | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.flow.* | ||||
| import org.jetbrains.exposed.sql.* | ||||
| import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq | ||||
| @@ -13,15 +14,17 @@ open class ExposedKeyValueRepo<Key, Value>( | ||||
|     database: Database, | ||||
|     keyColumnAllocator: ColumnAllocator<Key>, | ||||
|     valueColumnAllocator: ColumnAllocator<Value>, | ||||
|     tableName: String? = null | ||||
|     tableName: String? = null, | ||||
|     flowsExtraBufferCapacity: Int = Int.MAX_VALUE, | ||||
|     flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND | ||||
| ) : KeyValueRepo<Key, Value>, ExposedReadKeyValueRepo<Key, Value>( | ||||
|     database, | ||||
|     keyColumnAllocator, | ||||
|     valueColumnAllocator, | ||||
|     tableName | ||||
| ) { | ||||
|     protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>() | ||||
|     protected val _onValueRemoved = MutableSharedFlow<Key>() | ||||
|     protected val _onNewValue = MutableSharedFlow<Pair<Key, Value>>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||
|     protected val _onValueRemoved = MutableSharedFlow<Key>(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||
|  | ||||
|     override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow() | ||||
|     override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow() | ||||
| @@ -65,7 +68,7 @@ open class ExposedKeyValueRepo<Key, Value>( | ||||
|     override suspend fun unsetWithValues(toUnset: List<Value>) { | ||||
|         transaction(database) { | ||||
|             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) } | ||||
|                 keys | ||||
|             } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package dev.inmo.micro_utils.repos.exposed.onetomany | ||||
|  | ||||
| import dev.inmo.micro_utils.repos.KeyValuesRepo | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.flow.* | ||||
| import org.jetbrains.exposed.sql.* | ||||
| import org.jetbrains.exposed.sql.statements.InsertStatement | ||||
| @@ -8,18 +9,20 @@ import org.jetbrains.exposed.sql.transactions.transaction | ||||
|  | ||||
| abstract class AbstractExposedKeyValuesRepo<Key, Value>( | ||||
|     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>( | ||||
|     database, | ||||
|     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>> | ||||
|         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>> | ||||
|         get() = _onValueRemoved.asSharedFlow() | ||||
|     protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow() | ||||
|     protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||
|     override val onDataCleared: Flow<Key> | ||||
|         get() = _onDataCleared.asSharedFlow() | ||||
|  | ||||
| @@ -29,7 +32,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>( | ||||
|         transaction(database) { | ||||
|             toAdd.keys.flatMap { k -> | ||||
|                 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 | ||||
|                     } | ||||
|                     val insertResult = insert { | ||||
|   | ||||
| @@ -19,7 +19,7 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>( | ||||
|         get() = asKey | ||||
|     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() } | ||||
|  | ||||
| @@ -28,7 +28,7 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>( | ||||
|         pagination: Pagination, | ||||
|         reversed: Boolean | ||||
|     ): PaginationResult<Value> = transaction(database) { | ||||
|         select { selectById(k) }.selectPaginated( | ||||
|         selectAll().where { selectById(k) }.selectPaginated( | ||||
|             pagination, | ||||
|             keyColumn, | ||||
|             reversed | ||||
| @@ -55,7 +55,7 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>( | ||||
|         pagination: Pagination, | ||||
|         reversed: Boolean | ||||
|     ): PaginationResult<Key> = transaction(database) { | ||||
|         select { selectByValue(v) }.selectPaginated( | ||||
|         selectAll().where { selectByValue(v) }.selectPaginated( | ||||
|             pagination, | ||||
|             keyColumn, | ||||
|             reversed | ||||
| @@ -65,11 +65,11 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>( | ||||
|     } | ||||
|  | ||||
|     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) { | ||||
|         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) { | ||||
| @@ -85,9 +85,9 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>( | ||||
|  | ||||
|     override suspend fun getAll(k: Key, reverseLists: Boolean): List<Value> = transaction(database) { | ||||
|         val query = if (reverseLists) { | ||||
|             select { selectById(k) }.orderBy(keyColumn, SortOrder.DESC) | ||||
|             selectAll().where { selectById(k) }.orderBy(keyColumn, SortOrder.DESC) | ||||
|         } else { | ||||
|             select { selectById(k) } | ||||
|             selectAll().where { selectById(k) } | ||||
|         } | ||||
|         query.map { | ||||
|             it.asObject | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.exposed.onetomany | ||||
|  | ||||
| import dev.inmo.micro_utils.repos.KeyValuesRepo | ||||
| import dev.inmo.micro_utils.repos.exposed.ColumnAllocator | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.flow.* | ||||
| import org.jetbrains.exposed.sql.* | ||||
| import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq | ||||
| @@ -13,20 +14,22 @@ open class ExposedKeyValuesRepo<Key, Value>( | ||||
|     database: Database, | ||||
|     keyColumnAllocator: ColumnAllocator<Key>, | ||||
|     valueColumnAllocator: ColumnAllocator<Value>, | ||||
|     tableName: String? = null | ||||
|     tableName: String? = null, | ||||
|     flowsExtraBufferCapacity: Int = Int.MAX_VALUE, | ||||
|     flowsBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND | ||||
| ) : KeyValuesRepo<Key, Value>, ExposedReadKeyValuesRepo<Key, Value>( | ||||
|     database, | ||||
|     keyColumnAllocator, | ||||
|     valueColumnAllocator, | ||||
|     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>> | ||||
|         get() = _onNewValue | ||||
|     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>> | ||||
|         get() = _onValueRemoved | ||||
|     protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow() | ||||
|     protected val _onDataCleared: MutableSharedFlow<Key> = MutableSharedFlow(extraBufferCapacity = flowsExtraBufferCapacity, onBufferOverflow = flowsBufferOverflow) | ||||
|     override val onDataCleared: Flow<Key> | ||||
|         get() = _onDataCleared | ||||
|  | ||||
| @@ -34,7 +37,7 @@ open class ExposedKeyValuesRepo<Key, Value>( | ||||
|         transaction(database) { | ||||
|             toAdd.keys.flatMap { k -> | ||||
|                 toAdd[k] ?.mapNotNull { v -> | ||||
|                     if (select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) { | ||||
|                     if (selectAll().where { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).count() > 0) { | ||||
|                         return@mapNotNull null | ||||
|                     } | ||||
|                     val insertResult = insert { | ||||
| @@ -69,7 +72,7 @@ open class ExposedKeyValuesRepo<Key, Value>( | ||||
|  | ||||
|     override suspend fun removeWithValue(v: Value) { | ||||
|         transaction(database) { | ||||
|             val keys = select { selectByValue(v) }.map { it.asKey } | ||||
|             val keys = selectAll().where { selectByValue(v) }.map { it.asKey } | ||||
|             deleteWhere { SqlExpressionBuilder.selectByValue(v) } | ||||
|             keys | ||||
|         }.forEach { | ||||
| @@ -85,7 +88,7 @@ open class ExposedKeyValuesRepo<Key, Value>( | ||||
|  | ||||
|     override suspend fun clearWithValue(v: Value) { | ||||
|         transaction(database) { | ||||
|             val toClear = select { selectByValue(v) } | ||||
|             val toClear = selectAll().where { selectByValue(v) } | ||||
|                 .asSequence() | ||||
|                 .map { it.asKey to it.asObject } | ||||
|                 .groupBy { it.first } | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class ExposedStandardVersionsRepoProxy( | ||||
|     } | ||||
|  | ||||
|     override suspend fun getTableVersion(tableName: String): Int? = transaction(database) { | ||||
|         select { tableNameColumn.eq(tableName) }.limit(1).firstOrNull() ?.getOrNull(tableVersionColumn) | ||||
|         selectAll().where { tableNameColumn.eq(tableName) }.limit(1).firstOrNull() ?.getOrNull(tableVersionColumn) | ||||
|     } | ||||
|  | ||||
|     override suspend fun updateTableVersion(tableName: String, version: Int) { | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| package dev.inmo.micro_utils.repos.generator | ||||
|  | ||||
| import com.google.devtools.ksp.KspExperimental | ||||
| import com.google.devtools.ksp.getAnnotationsByType | ||||
| import com.google.devtools.ksp.isAnnotationPresent | ||||
| import com.google.devtools.ksp.* | ||||
| import com.google.devtools.ksp.processing.CodeGenerator | ||||
| import com.google.devtools.ksp.processing.Resolver | ||||
| import com.google.devtools.ksp.processing.SymbolProcessor | ||||
| @@ -36,6 +34,7 @@ import kotlinx.serialization.Serializable | ||||
| import java.io.File | ||||
| import kotlin.reflect.KProperty1 | ||||
| import kotlin.reflect.full.memberProperties | ||||
| import kotlin.reflect.jvm.jvmName | ||||
|  | ||||
| private fun KSClassifierReference.quilifiedName(): String = "${qualifier ?.let { "${it.quilifiedName()}." } ?: ""}${referencedName()}" | ||||
|  | ||||
| @@ -75,16 +74,29 @@ class Processor( | ||||
|                         ORIGINAL FILE: ${ksFile.fileName} | ||||
|                     """.trimIndent() | ||||
|                     ) | ||||
|  | ||||
|                     val newName = "New${ksClassDeclaration.simpleName.getShortName()}" | ||||
|                     val registeredName = "Registered${ksClassDeclaration.simpleName.getShortName()}" | ||||
|  | ||||
|                     val allKSClassProperties = ksClassDeclaration.getAllProperties() | ||||
|                     val excludedKSClassProperties = allKSClassProperties.filter { | ||||
|                         it.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) | ||||
|                     val excludedKSClassProperties = allKSClassProperties.filter { property -> | ||||
|                         property.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) || (property.findOverridee() ?.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) == true) | ||||
|                     } | ||||
|                     val excludedKSClassPropertiesNames = excludedKSClassProperties.map { it.simpleName.asString() } | ||||
|                     val ksClassProperties = allKSClassProperties.filter { | ||||
|                         it !in excludedKSClassProperties | ||||
|                     }.groupBy { it.simpleName.asString() }.map { | ||||
|                         var current = it.value.first() | ||||
|                         var currentType = current.type.resolve() | ||||
|                         it.value.forEach { | ||||
|                             val type = it.type.resolve() | ||||
|  | ||||
|                             if (currentType.isAssignableFrom(type) && !type.isAssignableFrom(currentType)) { | ||||
|                                 current = it | ||||
|                                 currentType = type | ||||
|                             } | ||||
|                         } | ||||
|                         current | ||||
|                     } | ||||
|                     val ksClassPropertiesNames = ksClassProperties.map { it.simpleName.asString() } | ||||
|                     val newNewType = TypeSpec.classBuilder(newName).apply { | ||||
| @@ -99,17 +111,20 @@ class Processor( | ||||
|                         } | ||||
|                         primaryConstructor( | ||||
|                             FunSpec.constructorBuilder().apply { | ||||
|                                 val withoutDefaults = mutableListOf<Pair<ParameterSpec.Builder, PropertySpec.Builder>>() | ||||
|                                 ksClassProperties.forEach { | ||||
|                                     addParameter( | ||||
|                                         ParameterSpec.builder(it.simpleName.getShortName(), it.typeName).apply { | ||||
|                                             annotations += it.annotations.map { it.toAnnotationSpec() } | ||||
|                                         }.build() | ||||
|                                     ) | ||||
|                                     typeBuilder.addProperty( | ||||
|                                         PropertySpec.builder(it.simpleName.getShortName(), it.typeName, KModifier.OVERRIDE).apply { | ||||
|                                             initializer(it.simpleName.getShortName()) | ||||
|                                         }.build() | ||||
|                                     ) | ||||
|                                     val property = PropertySpec.builder(it.simpleName.getShortName(), it.type.toTypeName(), KModifier.OVERRIDE).apply { | ||||
|                                         initializer(it.simpleName.getShortName()) | ||||
|                                     } | ||||
|                                     ParameterSpec.builder(it.simpleName.getShortName(), it.type.toTypeName()).apply { | ||||
|                                         withoutDefaults.add(this to property) | ||||
|                                         annotations += it.annotations.map { it.toAnnotationSpec() } | ||||
|                                     } | ||||
|                                 } | ||||
|  | ||||
|                                 withoutDefaults.forEach { | ||||
|                                     addParameter(it.first.build()) | ||||
|                                     addProperty(it.second.build()) | ||||
|                                 } | ||||
|                             }.build() | ||||
|                         ) | ||||
| @@ -125,14 +140,25 @@ class Processor( | ||||
|                         (it.arguments.first().value as List<KSType>).map { it.declaration as KSClassDeclaration } | ||||
|                     }.toList() | ||||
|  | ||||
|  | ||||
|                     val registeredTypesProperties: List<KSPropertyDeclaration> = registeredSupertypes.flatMap { registeredType -> | ||||
|                         registeredType.getAllProperties() | ||||
|                     }.filter { | ||||
|                         it.simpleName.asString() !in excludedKSClassPropertiesNames && it.getAnnotationsByType(GenerateCRUDModelExcludeOverride::class).none() | ||||
|                         it.simpleName.asString() !in excludedKSClassPropertiesNames && !it.isAnnotationPresent(GenerateCRUDModelExcludeOverride::class) | ||||
|                     } | ||||
|                     val allProperties: List<KSPropertyDeclaration> = ksClassProperties.toList() + registeredTypesProperties | ||||
|                     val propertiesToOverrideInRegistered = allProperties.distinctBy { it.simpleName.asString() }.sortedBy { property -> | ||||
|                     val allProperties: List<KSPropertyDeclaration> = registeredTypesProperties + ksClassProperties.toList() | ||||
|                     val propertiesToOverrideInRegistered = allProperties.groupBy { it.simpleName.asString() }.map { | ||||
|                         var current = it.value.first() | ||||
|                         var currentType = current.type.resolve() | ||||
|                         it.value.forEach { | ||||
|                             val type = it.type.resolve() | ||||
|  | ||||
|                             if (currentType.isAssignableFrom(type) && !type.isAssignableFrom(currentType)) { | ||||
|                                 current = it | ||||
|                                 currentType = type | ||||
|                             } | ||||
|                         } | ||||
|                         current | ||||
|                     }.sortedBy { property -> | ||||
|                         val name = property.simpleName.asString() | ||||
|  | ||||
|                         ksClassPropertiesNames.indexOf(name).takeIf { it > -1 } ?.let { | ||||
| @@ -156,17 +182,20 @@ class Processor( | ||||
|                         addModifiers(KModifier.DATA) | ||||
|                         primaryConstructor( | ||||
|                             FunSpec.constructorBuilder().apply { | ||||
|                                 val withoutDefaults = mutableListOf<Pair<ParameterSpec.Builder, PropertySpec.Builder>>() | ||||
|                                 propertiesToOverrideInRegistered.forEach { | ||||
|                                     addParameter( | ||||
|                                         ParameterSpec.builder(it.simpleName.getShortName(), it.typeName).apply { | ||||
|                                             annotations += it.annotations.map { it.toAnnotationSpec() } | ||||
|                                         }.build() | ||||
|                                     ) | ||||
|                                     typeBuilder.addProperty( | ||||
|                                         PropertySpec.builder(it.simpleName.getShortName(), it.typeName, KModifier.OVERRIDE).apply { | ||||
|                                             initializer(it.simpleName.getShortName()) | ||||
|                                         }.build() | ||||
|                                     ) | ||||
|                                     val property = PropertySpec.builder(it.simpleName.getShortName(), it.type.toTypeName(), KModifier.OVERRIDE).apply { | ||||
|                                         initializer(it.simpleName.getShortName()) | ||||
|                                     } | ||||
|                                     ParameterSpec.builder(it.simpleName.getShortName(), it.type.toTypeName()).apply { | ||||
|                                         withoutDefaults.add(this to property) | ||||
|                                         annotations += it.annotations.map { it.toAnnotationSpec() } | ||||
|                                     } | ||||
|                                 } | ||||
|  | ||||
|                                 withoutDefaults.forEach { | ||||
|                                     addParameter(it.first.build()) | ||||
|                                     addProperty(it.second.build()) | ||||
|                                 } | ||||
|                             }.build() | ||||
|                         ) | ||||
| @@ -191,7 +220,7 @@ class Processor( | ||||
|                         FunSpec.builder("asRegistered").apply { | ||||
|                             receiver(ksClassDeclaration.toClassName()) | ||||
|                             (registeredTypesProperties.filter { it.simpleName.asString() !in ksClassPropertiesNames }).forEach { | ||||
|                                 addParameter(it.simpleName.asString(), it.typeName) | ||||
|                                 addParameter(it.simpleName.asString(), it.type.toTypeName()) | ||||
|                             } | ||||
|                             addCode( | ||||
|                                 CodeBlock.of( | ||||
|   | ||||
| @@ -14,6 +14,7 @@ public data class NewTest( | ||||
|   override val property1: String, | ||||
|   override val property2: Int, | ||||
|   @Serializable | ||||
|   @SerialName(`value` = "custom_parent_name") | ||||
|   override val parent: ParentTypeId?, | ||||
| ) : Test | ||||
|  | ||||
| @@ -24,6 +25,7 @@ public data class RegisteredTest( | ||||
|   override val property1: String, | ||||
|   override val property2: Int, | ||||
|   @Serializable | ||||
|   @SerialName(`value` = "custom_parent_name") | ||||
|   override val parent: ParentTypeId?, | ||||
| ) : Test, IRegisteredTest | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.generator.test | ||||
|  | ||||
| import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModel | ||||
| import dev.inmo.micro_utils.repos.annotations.GenerateCRUDModelExcludeOverride | ||||
| import kotlinx.serialization.SerialName | ||||
| import kotlinx.serialization.Serializable | ||||
| import kotlin.jvm.JvmInline | ||||
|  | ||||
| @@ -18,6 +19,7 @@ sealed interface Test { | ||||
|     val property1: String | ||||
|     val property2: Int | ||||
|     @Serializable | ||||
|     @SerialName("custom_parent_name") | ||||
|     val parent: ParentTypeId? | ||||
|  | ||||
|     @GenerateCRUDModelExcludeOverride | ||||
|   | ||||
							
								
								
									
										17
									
								
								resources/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								resources/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| plugins { | ||||
|     id "org.jetbrains.kotlin.multiplatform" | ||||
|     id "org.jetbrains.kotlin.plugin.serialization" | ||||
|     id "com.android.library" | ||||
| } | ||||
|  | ||||
| apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" | ||||
|  | ||||
| kotlin { | ||||
|     sourceSets { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 api project(":micro_utils.language_codes") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,20 @@ | ||||
| package dev.inmo.micro_utils.strings | ||||
|  | ||||
| import android.content.Context | ||||
| import android.content.res.Configuration | ||||
| import android.content.res.Resources | ||||
| import android.os.Build | ||||
|  | ||||
| fun StringResource.translation(configuration: Configuration): String = translation( | ||||
|     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||||
|         configuration.locales[0] | ||||
|     } else { | ||||
|         configuration.locale | ||||
|     } | ||||
| ) | ||||
| fun StringResource.translation(resources: Resources): String = translation(resources.configuration) | ||||
| fun StringResource.translation(context: Context): String = translation(context.resources) | ||||
|  | ||||
| fun Configuration.translation(resource: StringResource): String = resource.translation(this) | ||||
| fun Resources.translation(resource: StringResource): String = configuration.translation(resource) | ||||
| fun Context.translation(resource: StringResource): String = resources.translation(resource) | ||||
							
								
								
									
										103
									
								
								resources/src/commonMain/kotlin/StringResource.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								resources/src/commonMain/kotlin/StringResource.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| package dev.inmo.micro_utils.strings | ||||
|  | ||||
| import dev.inmo.micro_utils.language_codes.IetfLang | ||||
| import kotlinx.serialization.KSerializer | ||||
| import kotlinx.serialization.Serializable | ||||
| import kotlinx.serialization.descriptors.SerialDescriptor | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
|  | ||||
| /** | ||||
|  * Use this class as a type of your strings object fields. For example: | ||||
|  * | ||||
|  * ```kotlin | ||||
|  * object Strings { | ||||
|  *     val someResource: StringResource | ||||
|  * } | ||||
|  * ``` | ||||
|  * | ||||
|  * Use [buildStringResource] for useful creation of string resource | ||||
|  * | ||||
|  * @see buildStringResource | ||||
|  */ | ||||
| @Serializable(StringResource.Companion::class) | ||||
| data class StringResource( | ||||
|     val default: String, | ||||
|     val translations: Map<IetfLang, Lazy<String>> | ||||
| ) { | ||||
|     class Builder( | ||||
|         var default: String | ||||
|     ) { | ||||
|         private val map = mutableMapOf<IetfLang, Lazy<String>>() | ||||
|  | ||||
|         infix fun IetfLang.variant(value: Lazy<String>) { | ||||
|             map[this] = value | ||||
|         } | ||||
|  | ||||
|         infix fun IetfLang.variant(value: () -> String) = this variant lazy(value) | ||||
|         infix fun IetfLang.variant(value: String) = this variant lazyOf(value) | ||||
|  | ||||
|         operator fun IetfLang.invoke(value: () -> String) = this variant value | ||||
|         operator fun IetfLang.invoke(value: String) = this variant value | ||||
|  | ||||
|  | ||||
|         infix fun String.variant(value: Lazy<String>) = IetfLang(this) variant value | ||||
|         infix fun String.variant(value: () -> String) = IetfLang(this) variant lazy(value) | ||||
|         infix fun String.variant(value: String) = this variant lazyOf(value) | ||||
|  | ||||
|  | ||||
|         operator fun String.invoke(value: () -> String) = this variant value | ||||
|         operator fun String.invoke(value: String) = this variant value | ||||
|  | ||||
|         fun build() = StringResource(default, map.toMap()) | ||||
|     } | ||||
|  | ||||
|     fun translation(languageCode: IetfLang?): String { | ||||
|         if (languageCode == null) { | ||||
|             return default | ||||
|         } | ||||
|         translations[languageCode] ?.let { return it.value } | ||||
|  | ||||
|         return languageCode.parentLang ?.let { | ||||
|             translations[it] ?.value | ||||
|         } ?: default | ||||
|     } | ||||
|  | ||||
|     companion object : KSerializer<StringResource> { | ||||
|         @Serializable | ||||
|         private class Surrogate( | ||||
|             val default: String, | ||||
|             val translations: Map<String, String> | ||||
|         ) | ||||
|  | ||||
|         override val descriptor: SerialDescriptor | ||||
|             get() = Surrogate.serializer().descriptor | ||||
|  | ||||
|         override fun deserialize(decoder: Decoder): StringResource { | ||||
|             val surrogate = Surrogate.serializer().deserialize(decoder) | ||||
|             return StringResource( | ||||
|                 surrogate.default, | ||||
|                 surrogate.translations.map { IetfLang(it.key) to lazyOf(it.value) }.toMap() | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         override fun serialize(encoder: Encoder, value: StringResource) { | ||||
|             Surrogate.serializer().serialize( | ||||
|                 encoder, | ||||
|                 Surrogate( | ||||
|                     value.default, | ||||
|                     value.translations.map { | ||||
|                         it.key.code to it.value.value | ||||
|                     }.toMap() | ||||
|                 ) | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| inline fun buildStringResource( | ||||
|     default: String, | ||||
|     builder: StringResource.Builder.() -> Unit = {} | ||||
| ): StringResource { | ||||
|     return StringResource.Builder(default).apply(builder).build() | ||||
| } | ||||
							
								
								
									
										11
									
								
								resources/src/jsMain/kotlin/StringResourceExtensions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								resources/src/jsMain/kotlin/StringResourceExtensions.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| package dev.inmo.micro_utils.strings | ||||
|  | ||||
| import dev.inmo.micro_utils.language_codes.IetfLang | ||||
| import kotlinx.browser.window | ||||
| import org.w3c.dom.NavigatorLanguage | ||||
|  | ||||
| fun StringResource.translation(language: NavigatorLanguage) = translation( | ||||
|     language.language.unsafeCast<String?>() ?.let { IetfLang(it) } | ||||
| ) | ||||
|  | ||||
| fun StringResource.translation() = translation(window.navigator) | ||||
							
								
								
									
										10
									
								
								resources/src/jvmMain/kotlin/StringResourceLocaleGetter.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								resources/src/jvmMain/kotlin/StringResourceLocaleGetter.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| package dev.inmo.micro_utils.strings | ||||
|  | ||||
| import dev.inmo.micro_utils.language_codes.toIetfLang | ||||
| import java.util.Locale | ||||
|  | ||||
| fun StringResource.translation(locale: Locale = Locale.getDefault()): String { | ||||
|     return translation(locale.toIetfLang()) | ||||
| } | ||||
|  | ||||
| fun Locale.translation(resource: StringResource): String = resource.translation(this) | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user