mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-29 19:20:26 +00:00 
			
		
		
		
	Compare commits
	
		
			45 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f7e98dfd2d | |||
| 61277e92bd | |||
| 32ef9f399f | |||
| 54e6ca5dc3 | |||
| a8e226786d | |||
| ce717a4c9f | |||
| fd41bf0ae7 | |||
| b2b68bf29f | |||
| b87c29c354 | |||
| 24977822c9 | |||
| 647daa8627 | |||
| a372efacb1 | |||
| f40d33db2a | |||
| a5eb0fbd24 | |||
| 307c8030af | |||
| a9016465fa | |||
| 358b70eb5f | |||
| 6fcbb80a71 | |||
|  | 643f6c420b | ||
| ac68b0b941 | |||
| d62f67bd88 | |||
| 8718c5e310 | |||
| e8273ab80c | |||
| 2718605987 | |||
| d99538d80b | |||
| ce7a1e4e21 | |||
| 921734763d | |||
| 18c608f569 | |||
| b915f6ece2 | |||
| 24347b422c | |||
| e5f4ae647f | |||
| 72202b8a21 | |||
| dbc14d41de | |||
| 1dca5ea00d | |||
| f03d1d788c | |||
| fcac6f9fa8 | |||
| ca0cd433c9 | |||
| 39589fdbd0 | |||
| 1f57478d10 | |||
| 7ef4c5d282 | |||
| 25391609b9 | |||
| f0b7b9c5e5 | |||
| 301cdaa2c2 | |||
| fce7ec8912 | |||
| 24bd403549 | 
							
								
								
									
										7
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,8 +17,9 @@ jobs: | ||||
|           mv gradle.properties.tmp gradle.properties | ||||
|       - name: Build | ||||
|         run: ./gradlew build | ||||
|       - name: Publish | ||||
|       - name: Publish to InmoNexus | ||||
|         continue-on-error: true | ||||
|         run: ./gradlew publishAllPublicationsToGiteaRepository | ||||
|         run: ./gradlew publishAllPublicationsToInmoNexusRepository | ||||
|         env: | ||||
|           GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} | ||||
|           INMONEXUS_USER: ${{ secrets.INMONEXUS_USER }} | ||||
|           INMONEXUS_PASSWORD: ${{ secrets.INMONEXUS_PASSWORD }} | ||||
|   | ||||
							
								
								
									
										89
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,94 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 0.24.5 | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Kotlin`: `2.1.0` -> `2.1.10` | ||||
|   * `SQLite`: `3.47.2.0` -> `3.48.0.0` | ||||
|   * `Koin`: `4.0.1` -> `4.0.2` | ||||
|   * `Android RecyclerView`: `1.3.2` -> `1.4.0` | ||||
|  | ||||
| ## 0.24.4 | ||||
|  | ||||
| * `Repos`: | ||||
|   * `Exposed`: | ||||
|     * Improve `CommonExposedRepo.selectByIds` | ||||
| * `FSM`: | ||||
|   * Fixes and improvements | ||||
|  | ||||
| ## 0.24.3 | ||||
|  | ||||
| * `Ksp`: | ||||
|   * `Sealed`: | ||||
|     * Fixes in processing of `GenerateSealedTypesWorkaround` annotations | ||||
|  | ||||
| ## 0.24.2 | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Exposed`: `0.57.0` -> `0.58.0` | ||||
| * `Ksp`: | ||||
|   * `Sealed`: | ||||
|     * Add annotation `GenerateSealedTypesWorkaround` which allow to generate `subtypes` lists | ||||
|  | ||||
| ## 0.24.1 | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Serialization`: `1.7.3` -> `1.8.0` | ||||
|   * `SQLite`: `3.47.1.0` -> `3.47.2.0` | ||||
|   * `Koin`: `4.0.0` -> `3.10.2` | ||||
|   * `OKio`: `3.9.1` -> `3.10.2` | ||||
|  | ||||
| ## 0.24.0 | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Coroutines`: `1.9.0` -> `1.10.1` | ||||
|   * `KSLog`: `1.3.6` -> `1.4.0` | ||||
|   * `Compose`: `1.7.1` -> `1.7.3` | ||||
|   * `Ktor`: `3.0.2` -> `3.0.3` | ||||
| * `Common`: | ||||
|   * Rename `Progress` to more common `Percentage`. `Progress` now is typealias | ||||
|   * Fix of `Progress.compareTo` extension | ||||
|  | ||||
| ## 0.23.2 | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Kotlin`: `2.0.21` -> `2.1.0` | ||||
|   * `Exposed`: `0.56.0` -> `0.57.0` | ||||
|   * `Xerial SQLite`: `3.47.0.0` -> `3.47.1.0` | ||||
|   * `Ktor`: `3.0.1` -> `3.0.2` | ||||
| * `Coroutines`: | ||||
|   * Small refactor in `AccumulatorFlow` to use `runCatching` instead of `runCatchingSafely` | ||||
|  | ||||
| ## 0.23.1 | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Compose`: `1.7.0` -> `1.7.1` | ||||
|   * `Exposed`: `0.55.0` -> `0.56.0` | ||||
|   * `Xerial SQLite`: `3.46.1.3` -> `3.47.0.0` | ||||
|   * `Android CoreKTX`: `1.13.1` -> `1.15.0` | ||||
|   * `Android Fragment`: `1.8.4` -> `1.8.5` | ||||
| * `Coroutines`: | ||||
|   * `Compose`: | ||||
|     * Add `StyleSheetsAggregator` | ||||
|  | ||||
| ## 0.23.0 | ||||
|  | ||||
| **THIS UPDATE MAY CONTAINS SOME BREAKING CHANGES (INCLUDING BREAKING CHANGES IN BYTECODE LAYER) RELATED TO UPDATE OF | ||||
| KTOR DEPENDENCY** | ||||
|  | ||||
| **THIS UPDATE CONTAINS CHANGES ACCORDING TO MIGRATION [GUIDE FROM KTOR](https://ktor.io/docs/migrating-3.html)** | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Ktor`: `2.3.12` -> `3.0.1` | ||||
| * `Ktor`: | ||||
|   * `Common`: | ||||
|     * Extension `Input.downloadToTempFile` has changed its receiver to `Source`. Its API can be broken | ||||
|   * `Client`: | ||||
|     * Extension `HttpClient.tempUpload` has changed type of `onUpload` argument from `OnUploadCallback` to `ProgressListener` | ||||
|     * All extensions `HttpClient.uniUpload` have changed type of `onUpload` argument from `OnUploadCallback` to `ProgressListener` | ||||
|   * `Server`: | ||||
|     * Remove redundant `ApplicationCall.respond` extension due to its presence in the ktor library | ||||
|  | ||||
| ## 0.22.9 | ||||
|  | ||||
| * `Repos`: | ||||
|   | ||||
| @@ -0,0 +1,76 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import kotlinx.serialization.Serializable | ||||
| import kotlin.jvm.JvmInline | ||||
|  | ||||
| /** | ||||
|  * Contains [of1] as main value, where 100% of percentage is when of1 == 1 | ||||
|  * | ||||
|  * @see invoke | ||||
|  * @see partOfTotal | ||||
|  * @see of100 | ||||
|  */ | ||||
| @Serializable | ||||
| @JvmInline | ||||
| value class Percentage private constructor( | ||||
|     /** | ||||
|      * Value of percentage. When it equals to 1, means 100% | ||||
|      */ | ||||
|     val of1: Double | ||||
| ) { | ||||
|     /** | ||||
|      * Same as [of1], but float (using [Double.toFloat]) | ||||
|      */ | ||||
|     val of1Float | ||||
|         get() = of1.toFloat() | ||||
|  | ||||
|     /** | ||||
|      * Represent this percentage as common percentage where 100% is 100% | ||||
|      */ | ||||
|     val of100 | ||||
|         get() = of1 * 100 | ||||
|  | ||||
|     /** | ||||
|      * Same as [of100], but float (using [Double.toFloat]) | ||||
|      */ | ||||
|     val of100Float | ||||
|         get() = of100.toFloat() | ||||
|  | ||||
|     /** | ||||
|      * Same as [of100], but int (using [Double.toInt]) | ||||
|      */ | ||||
|     val of100Int | ||||
|         get() = of100.toInt() | ||||
|  | ||||
|     companion object { | ||||
|         val rangeOfValues = 0.0 .. 1.0 | ||||
|  | ||||
|         val START = Percentage(rangeOfValues.start) | ||||
|         val COMPLETED = Percentage(rangeOfValues.endInclusive) | ||||
|  | ||||
|         operator fun invoke(of1: Double) = Percentage(of1.coerceIn(rangeOfValues)) | ||||
|         operator fun invoke(part: Number, total: Number) = Percentage( | ||||
|             part.toDouble() / total.toDouble() | ||||
|         ) | ||||
|         fun of1(of1: Double) = Percentage(of1 = of1) | ||||
|         fun of100(of100: Double) = Percentage(of1 = of100 / 100) | ||||
|         fun partOfTotal(part: Number, total: Number) = Percentage(part = part, total = total) | ||||
|     } | ||||
| } | ||||
|  | ||||
| typealias Progress = Percentage | ||||
|  | ||||
| /** | ||||
|  * Will return [this] [Progress] if [Percentage.of1] in `0 .. 1` range | ||||
|  */ | ||||
| fun Progress.ensureStrictOrNull(): Progress? = if (of1 in Percentage.rangeOfValues) this else null | ||||
| /** | ||||
|  * Will return [this] [Progress] if [Percentage.of1] in `0 .. 1` range. Otherwise, will throw error | ||||
|  * [IllegalArgumentException] due to [require] failure | ||||
|  */ | ||||
| fun Progress.ensureStrictOrThrow(): Progress { | ||||
|     require(of1 in Percentage.rangeOfValues) { | ||||
|         "For strict checks value of percentage must be in ${Percentage.rangeOfValues}, but actual value is $of1" | ||||
|     } | ||||
|     return this | ||||
| } | ||||
| @@ -0,0 +1,80 @@ | ||||
| @file:Suppress( | ||||
|   "RemoveRedundantCallsOfConversionMethods", | ||||
|   "RedundantVisibilityModifier", | ||||
| ) | ||||
|  | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import kotlin.Byte | ||||
| import kotlin.Double | ||||
| import kotlin.Float | ||||
| import kotlin.Int | ||||
| import kotlin.Long | ||||
| import kotlin.Short | ||||
| import kotlin.Suppress | ||||
|  | ||||
| public operator fun Percentage.plus(other: Percentage): Percentage = Percentage(of1 + other.of1) | ||||
|  | ||||
| public operator fun Percentage.minus(other: Percentage): Percentage = Percentage(of1 - other.of1) | ||||
|  | ||||
| public operator fun Percentage.plus(i: Byte): Percentage = Percentage((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.minus(i: Byte): Percentage = Percentage((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.times(i: Byte): Percentage = Percentage((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.div(i: Byte): Percentage = Percentage((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.rem(i: Byte): Percentage = Percentage((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.plus(i: Short): Percentage = Percentage((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.minus(i: Short): Percentage = Percentage((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.times(i: Short): Percentage = Percentage((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.div(i: Short): Percentage = Percentage((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.rem(i: Short): Percentage = Percentage((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.plus(i: Int): Percentage = Percentage((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.minus(i: Int): Percentage = Percentage((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.times(i: Int): Percentage = Percentage((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.div(i: Int): Percentage = Percentage((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.rem(i: Int): Percentage = Percentage((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.plus(i: Long): Percentage = Percentage((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.minus(i: Long): Percentage = Percentage((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.times(i: Long): Percentage = Percentage((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.div(i: Long): Percentage = Percentage((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.rem(i: Long): Percentage = Percentage((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.plus(i: Float): Percentage = Percentage((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.minus(i: Float): Percentage = Percentage((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.times(i: Float): Percentage = Percentage((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.div(i: Float): Percentage = Percentage((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.rem(i: Float): Percentage = Percentage((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.plus(i: Double): Percentage = Percentage((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.minus(i: Double): Percentage = Percentage((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.times(i: Double): Percentage = Percentage((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.div(i: Double): Percentage = Percentage((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.rem(i: Double): Percentage = Percentage((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Percentage.compareTo(other: Percentage): Int = (of1.compareTo(other.of1)) | ||||
| @@ -1,37 +0,0 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import kotlinx.serialization.Serializable | ||||
| import kotlin.jvm.JvmInline | ||||
|  | ||||
| @Serializable | ||||
| @JvmInline | ||||
| value class Progress private constructor( | ||||
|     val of1: Double | ||||
| ) { | ||||
|     val of1Float | ||||
|         get() = of1.toFloat() | ||||
|     val of100 | ||||
|         get() = of1 * 100 | ||||
|     val of100Float | ||||
|         get() = of100.toFloat() | ||||
|     val of100Int | ||||
|         get() = of100.toInt() | ||||
|  | ||||
|     init { | ||||
|         require(of1 in rangeOfValues) { | ||||
|             "Progress main value should be in $rangeOfValues, but incoming value is $of1" | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         val rangeOfValues = 0.0 .. 1.0 | ||||
|  | ||||
|         val START = Progress(rangeOfValues.start) | ||||
|         val COMPLETED = Progress(rangeOfValues.endInclusive) | ||||
|  | ||||
|         operator fun invoke(of1: Double) = Progress(of1.coerceIn(rangeOfValues)) | ||||
|         operator fun invoke(part: Number, total: Number) = Progress( | ||||
|             part.toDouble() / total.toDouble() | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -1,80 +0,0 @@ | ||||
| @file:Suppress( | ||||
|   "RemoveRedundantCallsOfConversionMethods", | ||||
|   "RedundantVisibilityModifier", | ||||
| ) | ||||
|  | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import kotlin.Byte | ||||
| import kotlin.Double | ||||
| import kotlin.Float | ||||
| import kotlin.Int | ||||
| import kotlin.Long | ||||
| import kotlin.Short | ||||
| import kotlin.Suppress | ||||
|  | ||||
| public operator fun Progress.plus(other: Progress): Progress = Progress(of1 + other.of1) | ||||
|  | ||||
| public operator fun Progress.minus(other: Progress): Progress = Progress(of1 - other.of1) | ||||
|  | ||||
| public operator fun Progress.plus(i: Byte): Progress = Progress((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.minus(i: Byte): Progress = Progress((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.times(i: Byte): Progress = Progress((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.div(i: Byte): Progress = Progress((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.rem(i: Byte): Progress = Progress((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.plus(i: Short): Progress = Progress((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.minus(i: Short): Progress = Progress((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.times(i: Short): Progress = Progress((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.div(i: Short): Progress = Progress((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.rem(i: Short): Progress = Progress((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.plus(i: Int): Progress = Progress((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.minus(i: Int): Progress = Progress((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.times(i: Int): Progress = Progress((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.div(i: Int): Progress = Progress((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.rem(i: Int): Progress = Progress((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.plus(i: Long): Progress = Progress((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.minus(i: Long): Progress = Progress((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.times(i: Long): Progress = Progress((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.div(i: Long): Progress = Progress((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.rem(i: Long): Progress = Progress((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.plus(i: Float): Progress = Progress((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.minus(i: Float): Progress = Progress((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.times(i: Float): Progress = Progress((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.div(i: Float): Progress = Progress((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.rem(i: Float): Progress = Progress((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.plus(i: Double): Progress = Progress((of1 + i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.minus(i: Double): Progress = Progress((of1 - i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.times(i: Double): Progress = Progress((of1 * i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.div(i: Double): Progress = Progress((of1 / i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.rem(i: Double): Progress = Progress((of1 % i).toDouble()) | ||||
|  | ||||
| public operator fun Progress.compareTo(other: Progress): Int = (of1 - other.of1).toInt() | ||||
| @@ -0,0 +1,29 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import kotlin.test.Test | ||||
| import kotlin.test.assertEquals | ||||
|  | ||||
| class PercentageTests { | ||||
|     @Test | ||||
|     fun testCompareTo() { | ||||
|         val step = 0.01 | ||||
|  | ||||
|         var i = Percentage.START.of1 | ||||
|         while (i <= Percentage.COMPLETED.of1) { | ||||
|             val percentageI = Percentage(i) | ||||
|  | ||||
|             var j = Percentage.START.of1 | ||||
|             while (j <= Percentage.COMPLETED.of1) { | ||||
|                 val percentageJ = Percentage(j) | ||||
|  | ||||
|                 assertEquals(percentageI.of1.compareTo(percentageJ.of1), percentageI.compareTo(percentageJ)) | ||||
|                 assertEquals(percentageI.of1 > percentageJ.of1, percentageI > percentageJ) | ||||
|                 assertEquals(percentageI.of1 < percentageJ.of1, percentageI < percentageJ) | ||||
|  | ||||
|                 j += step | ||||
|             } | ||||
|  | ||||
|             i += step | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,66 @@ | ||||
| package dev.inmo.micro_utils.coroutines.compose | ||||
|  | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.collectAsState | ||||
| import androidx.compose.runtime.remember | ||||
| import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow | ||||
| import kotlinx.coroutines.flow.StateFlow | ||||
| import kotlinx.coroutines.flow.asStateFlow | ||||
| import kotlinx.coroutines.flow.debounce | ||||
| import org.jetbrains.compose.web.css.CSSRulesHolder | ||||
| import org.jetbrains.compose.web.css.Style | ||||
| import org.jetbrains.compose.web.css.StyleSheet | ||||
|  | ||||
| /** | ||||
|  * Aggregator of Compose CSS StyleSheet. Allowing to add [StyleSheet] in it and draw it in one place without requiring | ||||
|  * to add `Style(stylesheet)` on every compose function call | ||||
|  */ | ||||
| object StyleSheetsAggregator { | ||||
|     private val _stylesFlow = SpecialMutableStateFlow<Set<CSSRulesHolder>>(emptySet()) | ||||
|     val stylesFlow: StateFlow<Set<CSSRulesHolder>> = _stylesFlow.asStateFlow() | ||||
|  | ||||
|     @Composable | ||||
|     fun draw() { | ||||
|         _stylesFlow.debounce(13L).collectAsState(emptySet()).value.forEach { | ||||
|             Style(it) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Adding [styleSheet] into the [Set] of included stylesheets. If you called [enableStyleSheetsAggregator], | ||||
|      * new styles will be enabled in the document | ||||
|      */ | ||||
|     fun addStyleSheet(styleSheet: CSSRulesHolder) { | ||||
|         _stylesFlow.value += styleSheet | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Removed [styleSheet] into the [Set] of included stylesheets | ||||
|      */ | ||||
|     fun removeStyleSheet(styleSheet: CSSRulesHolder) { | ||||
|         _stylesFlow.value -= styleSheet | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Drawing [StyleSheetsAggregator] in place. You may pass [Set] of [CSSRulesHolder]/[StyleSheet]s as preset of styles | ||||
|  */ | ||||
| @Composable | ||||
| fun enableStyleSheetsAggregator( | ||||
|     stylesPreset: Set<CSSRulesHolder> = emptySet(), | ||||
| ) { | ||||
|     remember { | ||||
|         stylesPreset.forEach { | ||||
|             StyleSheetsAggregator.addStyleSheet(it) | ||||
|         } | ||||
|     } | ||||
|     StyleSheetsAggregator.draw() | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Will include [this] [CSSRulesHolder]/[StyleSheet] in the [StyleSheetsAggregator] using its | ||||
|  * [StyleSheetsAggregator.addStyleSheet] | ||||
|  */ | ||||
| fun CSSRulesHolder.includeInStyleSheetsAggregator() { | ||||
|     StyleSheetsAggregator.addStyleSheet(this) | ||||
| } | ||||
| @@ -68,9 +68,9 @@ class AccumulatorFlow<T>( | ||||
|     override suspend fun collectSafely(collector: FlowCollector<T>) { | ||||
|         val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND) | ||||
|         steps.send(SubscribeAccumulatorFlowStep(channel)) | ||||
|         val result = runCatchingSafely { | ||||
|         val result = runCatching { | ||||
|             for (data in channel) { | ||||
|                 val emitResult = runCatchingSafely { | ||||
|                 val emitResult = runCatching { | ||||
|                     collector.emit(data) | ||||
|                 } | ||||
|                 if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) { | ||||
|   | ||||
| @@ -12,6 +12,7 @@ kotlin { | ||||
|             dependencies { | ||||
|                 api project(":micro_utils.common") | ||||
|                 api project(":micro_utils.coroutines") | ||||
|                 api libs.kslog | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package dev.inmo.micro_utils.fsm.common | ||||
|  | ||||
| import dev.inmo.kslog.common.TagLogger | ||||
| import dev.inmo.kslog.common.e | ||||
| import dev.inmo.micro_utils.common.Optional | ||||
| import dev.inmo.micro_utils.coroutines.* | ||||
| import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler | ||||
| @@ -68,6 +70,7 @@ open class DefaultStatesMachine <T: State>( | ||||
|     protected val handlers: List<CheckableHandlerHolder<in T, T>>, | ||||
|     protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler() | ||||
| ) : StatesMachine<T> { | ||||
|     protected val logger = TagLogger(this::class.simpleName!!) | ||||
|     /** | ||||
|      * Will call [launchStateHandling] for state handling | ||||
|      */ | ||||
| @@ -96,7 +99,13 @@ open class DefaultStatesMachine <T: State>( | ||||
|         statesJobsMutex.withLock { | ||||
|             statesJobs[actualState] ?.cancel() | ||||
|             statesJobs[actualState] = scope.launch { | ||||
|                 performUpdate(actualState) | ||||
|                 runCatching { | ||||
|                     performUpdate(actualState) | ||||
|                 }.onFailure { | ||||
|                     logger.e(it) { | ||||
|                         "Unable to perform update of state from $actualState" | ||||
|                     } | ||||
|                 }.getOrThrow() | ||||
|             }.also { job -> | ||||
|                 job.invokeOnCompletion { _ -> | ||||
|                     scope.launch { | ||||
|   | ||||
| @@ -9,7 +9,12 @@ interface StatesManager<T : State> { | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Must set current set using [State.context] | ||||
|      * It is expected, that [new] state will be saved in manager. | ||||
|      * | ||||
|      * If [new] context will not be equal to [old] one, it must do some check of availability for replacement | ||||
|      * of potentially exists state on [new] context. If this state can't be replaced, it will throw [IllegalStateException] | ||||
|      * | ||||
|      * @throws IllegalStateException - in case when [new] [State] can't be set | ||||
|      */ | ||||
|     suspend fun update(old: T, new: T) | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package dev.inmo.micro_utils.fsm.common | ||||
|  | ||||
| import dev.inmo.kslog.common.e | ||||
| import dev.inmo.micro_utils.common.* | ||||
| import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler | ||||
| import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler | ||||
| @@ -44,7 +45,13 @@ open class DefaultUpdatableStatesMachine<T : State>( | ||||
|             val job = previousState.mapOnPresented { | ||||
|                 statesJobs.remove(it) | ||||
|             } ?.takeIf { it.isActive } ?: scope.launch { | ||||
|                 performUpdate(actualState) | ||||
|                 runCatching { | ||||
|                     performUpdate(actualState) | ||||
|                 }.onFailure { | ||||
|                     logger.e(it) { | ||||
|                         "Unable to perform update of state up to $actualState" | ||||
|                     } | ||||
|                 }.getOrThrow() | ||||
|             }.also { job -> | ||||
|                 job.invokeOnCompletion { _ -> | ||||
|                     scope.launch { | ||||
|   | ||||
| @@ -1,10 +1,11 @@ | ||||
| package dev.inmo.micro_utils.fsm.common.managers | ||||
|  | ||||
| import dev.inmo.micro_utils.coroutines.SmartRWLocker | ||||
| import dev.inmo.micro_utils.coroutines.withReadAcquire | ||||
| import dev.inmo.micro_utils.coroutines.withWriteLock | ||||
| import dev.inmo.micro_utils.fsm.common.State | ||||
| import dev.inmo.micro_utils.fsm.common.StatesManager | ||||
| import kotlinx.coroutines.flow.* | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
| import kotlinx.coroutines.sync.withLock | ||||
|  | ||||
| /** | ||||
|  * Implement this repo if you want to use some custom repo for [DefaultStatesManager] | ||||
| @@ -19,6 +20,14 @@ interface DefaultStatesManagerRepo<T : State> { | ||||
|      * NOT be removed | ||||
|      */ | ||||
|     suspend fun removeState(state: T) | ||||
|  | ||||
|     /** | ||||
|      * Semantically, calls [removeState] and then [set] | ||||
|      */ | ||||
|     suspend fun removeAndSet(toRemove: T, toSet: T) { | ||||
|         removeState(toRemove) | ||||
|         set(toSet) | ||||
|     } | ||||
|     /** | ||||
|      * @return Current list of available and saved states | ||||
|      */ | ||||
| @@ -58,7 +67,7 @@ open class DefaultStatesManager<T : State>( | ||||
|     protected val _onEndChain = MutableSharedFlow<T>(0) | ||||
|     override val onEndChain: Flow<T> = _onEndChain.asSharedFlow() | ||||
|  | ||||
|     protected val mapMutex = Mutex() | ||||
|     protected val internalLocker = SmartRWLocker() | ||||
|  | ||||
|     constructor( | ||||
|         repo: DefaultStatesManagerRepo<T>, | ||||
| @@ -68,28 +77,30 @@ open class DefaultStatesManager<T : State>( | ||||
|         onUpdateContextsConflictResolver = onContextsConflictResolver | ||||
|     ) | ||||
|  | ||||
|     override suspend fun update(old: T, new: T) = mapMutex.withLock { | ||||
|     override suspend fun update(old: T, new: T) = internalLocker.withWriteLock { | ||||
|         val stateByOldContext: T? = repo.getContextState(old.context) | ||||
|         when { | ||||
|             stateByOldContext != old -> return@withLock | ||||
|             stateByOldContext == null || old.context == new.context -> { | ||||
|                 repo.removeState(old) | ||||
|                 repo.set(new) | ||||
|             stateByOldContext != old -> return@withWriteLock | ||||
|             old.context == new.context -> { | ||||
|                 repo.removeAndSet(old, new) | ||||
|                 _onChainStateUpdated.emit(old to new) | ||||
|             } | ||||
|             else -> { | ||||
|             old.context != new.context -> { | ||||
|                 val stateOnNewOneContext = repo.getContextState(new.context) | ||||
|                 if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) { | ||||
|                     stateOnNewOneContext ?.let { endChainWithoutLock(it) } | ||||
|                     repo.removeState(old) | ||||
|                     repo.set(new) | ||||
|                     repo.removeAndSet(old, new) | ||||
|                     _onChainStateUpdated.emit(old to new) | ||||
|                 } else { | ||||
|                     error( | ||||
|                         "Unable to update state from $old to $new due to false answer from $onUpdateContextsConflictResolver and state on old context $stateOnNewOneContext" | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun startChain(state: T) = mapMutex.withLock { | ||||
|     override suspend fun startChain(state: T) = internalLocker.withWriteLock { | ||||
|         val stateOnContext = repo.getContextState(state.context) | ||||
|         if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) { | ||||
|             stateOnContext ?.let { | ||||
| @@ -108,11 +119,13 @@ open class DefaultStatesManager<T : State>( | ||||
|     } | ||||
|  | ||||
|     override suspend fun endChain(state: T) { | ||||
|         mapMutex.withLock { | ||||
|         internalLocker.withWriteLock { | ||||
|             endChainWithoutLock(state) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun getActiveStates(): List<T> = repo.getStates() | ||||
|     override suspend fun getActiveStates(): List<T> = internalLocker.withReadAcquire { | ||||
|         repo.getStates() | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,25 +1,59 @@ | ||||
| package dev.inmo.micro_utils.fsm.repos.common | ||||
|  | ||||
| import dev.inmo.kslog.common.TagLogger | ||||
| import dev.inmo.kslog.common.i | ||||
| import dev.inmo.micro_utils.coroutines.SmartRWLocker | ||||
| import dev.inmo.micro_utils.coroutines.withReadAcquire | ||||
| import dev.inmo.micro_utils.coroutines.withWriteLock | ||||
| import dev.inmo.micro_utils.fsm.common.State | ||||
| import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo | ||||
| import dev.inmo.micro_utils.repos.* | ||||
| import dev.inmo.micro_utils.repos.pagination.getAll | ||||
| import dev.inmo.micro_utils.repos.unset | ||||
|  | ||||
| class KeyValueBasedDefaultStatesManagerRepo<T : State>( | ||||
|     private val keyValueRepo: KeyValueRepo<Any, T> | ||||
| ) : DefaultStatesManagerRepo<T> { | ||||
|     private val locker = SmartRWLocker() | ||||
|     private val logger = TagLogger("KeyValueBasedDefaultStatesManagerRepo") | ||||
|     override suspend fun set(state: T) { | ||||
|         keyValueRepo.set(state.context, state) | ||||
|     } | ||||
|  | ||||
|     override suspend fun removeState(state: T) { | ||||
|         if (keyValueRepo.get(state.context) == state) { | ||||
|             keyValueRepo.unset(state.context) | ||||
|         locker.withWriteLock { | ||||
|             keyValueRepo.set(state.context, state) | ||||
|             logger.i { "Set ${state.context} value to $state" } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun getStates(): List<T> = keyValueRepo.getAll { keys(it) }.map { it.second } | ||||
|     override suspend fun getContextState(context: Any): T? = keyValueRepo.get(context) | ||||
|     override suspend fun removeState(state: T) { | ||||
|         locker.withWriteLock { | ||||
|             if (keyValueRepo.get(state.context) == state) { | ||||
|                 keyValueRepo.unset(state.context) | ||||
|                 logger.i { "Unset $state" } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun contains(context: Any): Boolean = keyValueRepo.contains(context) | ||||
|     override suspend fun removeAndSet(toRemove: T, toSet: T) { | ||||
|         locker.withWriteLock { | ||||
|             when { | ||||
|                 toRemove.context == toSet.context -> { | ||||
|                     keyValueRepo.set(toSet.context, toSet) | ||||
|                 } | ||||
|                 else -> { | ||||
|                     keyValueRepo.set(toSet.context, toSet) | ||||
|                     keyValueRepo.unset(toRemove) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override suspend fun getStates(): List<T> = locker.withReadAcquire { | ||||
|         keyValueRepo.getAll { keys(it) }.map { it.second } | ||||
|     } | ||||
|     override suspend fun getContextState(context: Any): T? = locker.withReadAcquire { | ||||
|         keyValueRepo.get(context) | ||||
|     } | ||||
|  | ||||
|     override suspend fun contains(context: Any): Boolean = locker.withReadAcquire { | ||||
|         keyValueRepo.contains(context) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 | ||||
| # Project data | ||||
|  | ||||
| group=dev.inmo | ||||
| version=0.22.9 | ||||
| android_code_version=275 | ||||
| version=0.24.5 | ||||
| android_code_version=285 | ||||
|   | ||||
| @@ -1,29 +1,29 @@ | ||||
| [versions] | ||||
|  | ||||
| kt = "2.0.21" | ||||
| kt-serialization = "1.7.3" | ||||
| kt-coroutines = "1.9.0" | ||||
| kt = "2.1.10" | ||||
| kt-serialization = "1.8.0" | ||||
| kt-coroutines = "1.10.1" | ||||
|  | ||||
| kslog = "1.3.6" | ||||
| kslog = "1.4.0" | ||||
|  | ||||
| jb-compose = "1.7.0" | ||||
| jb-exposed = "0.55.0" | ||||
| jb-dokka = "1.9.20" | ||||
| jb-compose = "1.7.3" | ||||
| jb-exposed = "0.58.0" | ||||
| jb-dokka = "2.0.0" | ||||
|  | ||||
| sqlite = "3.46.1.3" | ||||
| sqlite = "3.48.0.0" | ||||
|  | ||||
| korlibs = "5.4.0" | ||||
| uuid = "0.8.4" | ||||
|  | ||||
| ktor = "2.3.12" | ||||
| ktor = "3.0.3" | ||||
|  | ||||
| gh-release = "2.5.2" | ||||
|  | ||||
| koin = "4.0.0" | ||||
| koin = "4.0.2" | ||||
|  | ||||
| okio = "3.9.1" | ||||
| okio = "3.10.2" | ||||
|  | ||||
| ksp = "2.0.21-1.0.25" | ||||
| ksp = "2.1.10-1.0.29" | ||||
| kotlin-poet = "1.18.1" | ||||
|  | ||||
| versions = "0.51.0" | ||||
| @@ -31,10 +31,10 @@ versions = "0.51.0" | ||||
| android-gradle = "8.2.2" | ||||
| dexcount = "4.0.0" | ||||
|  | ||||
| android-coreKtx = "1.13.1" | ||||
| android-recyclerView = "1.3.2" | ||||
| android-coreKtx = "1.15.0" | ||||
| android-recyclerView = "1.4.0" | ||||
| android-appCompat = "1.7.0" | ||||
| android-fragment = "1.8.4" | ||||
| android-fragment = "1.8.5" | ||||
| android-espresso = "3.6.1" | ||||
| android-test = "1.2.1" | ||||
| android-compose-material3 = "1.3.0" | ||||
|   | ||||
							
								
								
									
										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.10.2-bin.zip | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
|   | ||||
| @@ -3,9 +3,15 @@ package dev.inmo.micro_utils.ksp.sealed.generator | ||||
| import com.google.devtools.ksp.KspExperimental | ||||
| import com.google.devtools.ksp.getAnnotationsByType | ||||
| import com.google.devtools.ksp.symbol.KSClassDeclaration | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedTypesWorkaround | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround | ||||
| import dev.inmo.microutils.kps.sealed.GenerateSealedWorkaround as OldGenerateSealedWorkaround | ||||
|  | ||||
| @OptIn(KspExperimental::class) | ||||
| val KSClassDeclaration.getGenerateSealedWorkaroundAnnotation | ||||
|     get() = (getAnnotationsByType(GenerateSealedWorkaround::class).firstOrNull() ?: getAnnotationsByType(OldGenerateSealedWorkaround::class).firstOrNull()) | ||||
|  | ||||
|  | ||||
| @OptIn(KspExperimental::class) | ||||
| val KSClassDeclaration.getGenerateSealedTypesWorkaroundAnnotation | ||||
|     get() = getAnnotationsByType(GenerateSealedTypesWorkaround::class).firstOrNull() | ||||
|   | ||||
| @@ -6,21 +6,17 @@ import com.google.devtools.ksp.processing.CodeGenerator | ||||
| import com.google.devtools.ksp.processing.Resolver | ||||
| import com.google.devtools.ksp.processing.SymbolProcessor | ||||
| import com.google.devtools.ksp.symbol.* | ||||
| import com.squareup.kotlinpoet.ClassName | ||||
| import com.squareup.kotlinpoet.CodeBlock | ||||
| import com.squareup.kotlinpoet.FileSpec | ||||
| import com.squareup.kotlinpoet.FunSpec | ||||
| import com.squareup.kotlinpoet.KModifier | ||||
| import com.squareup.kotlinpoet.* | ||||
| import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy | ||||
| import com.squareup.kotlinpoet.PropertySpec | ||||
| import com.squareup.kotlinpoet.asTypeName | ||||
| import com.squareup.kotlinpoet.ksp.toClassName | ||||
| import dev.inmo.micro_ksp.generator.buildSubFileName | ||||
| import dev.inmo.micro_ksp.generator.companion | ||||
| import dev.inmo.micro_ksp.generator.findSubClasses | ||||
| import dev.inmo.micro_ksp.generator.writeFile | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedTypesWorkaround | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround | ||||
| import java.io.File | ||||
| import kotlin.reflect.KClass | ||||
|  | ||||
| class Processor( | ||||
|     private val codeGenerator: CodeGenerator | ||||
| @@ -109,6 +105,62 @@ class Processor( | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @OptIn(KspExperimental::class) | ||||
|     private fun FileSpec.Builder.generateSealedTypesWorkaround( | ||||
|         ksClassDeclaration: KSClassDeclaration, | ||||
|         resolver: Resolver | ||||
|     ) { | ||||
|         val annotation = ksClassDeclaration.getGenerateSealedTypesWorkaroundAnnotation | ||||
|         val subClasses = ksClassDeclaration.resolveSubclasses( | ||||
|             searchIn = resolver.getAllFiles(), | ||||
|             allowNonSealed = annotation ?.includeNonSealedSubTypes ?: false | ||||
|         ).distinct() | ||||
|         val subClassesNames = subClasses.filter { | ||||
|             it.getAnnotationsByType(GenerateSealedTypesWorkaround.Exclude::class).count() == 0 | ||||
|         }.sortedBy { | ||||
|             (it.getAnnotationsByType(GenerateSealedTypesWorkaround.Order::class).firstOrNull()) ?.order ?: 0 | ||||
|         }.map { | ||||
|             it.toClassName() | ||||
|         }.toList() | ||||
|         val className = ksClassDeclaration.toClassName() | ||||
|         val setType = Set::class.asTypeName().parameterizedBy( | ||||
|             KClass::class.asTypeName().parameterizedBy( | ||||
|                 TypeVariableName( | ||||
|                     "out ${ksClassDeclaration.asStarProjectedType().toClassName().simpleNames.joinToString(".")}", | ||||
|                 ) | ||||
|             ) | ||||
|         ) | ||||
|         addProperty( | ||||
|             PropertySpec.builder( | ||||
|                 "subtypes", | ||||
|                 setType | ||||
|             ).apply { | ||||
|                 modifiers.add( | ||||
|                     KModifier.PRIVATE | ||||
|                 ) | ||||
|                 initializer( | ||||
|                     CodeBlock.of( | ||||
|                         """setOf(${subClassesNames.joinToString(",\n") { it.simpleNames.joinToString(".") + "::class" }})""" | ||||
|                     ) | ||||
|                 ) | ||||
|             }.build() | ||||
|         ) | ||||
|         addFunction( | ||||
|             FunSpec.builder("subtypes").apply { | ||||
|                 val companion = ksClassDeclaration.takeIf { it.isCompanionObject } ?.toClassName() | ||||
|                     ?: ksClassDeclaration.companion ?.toClassName() | ||||
|                     ?: ClassName(className.packageName, *className.simpleNames.toTypedArray(), "Companion") | ||||
|                 receiver(companion) | ||||
|                 returns(setType) | ||||
|                 addCode( | ||||
|                     CodeBlock.of( | ||||
|                         """return subtypes""" | ||||
|                     ) | ||||
|                 ) | ||||
|             }.build() | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     @OptIn(KspExperimental::class) | ||||
|     override fun process(resolver: Resolver): List<KSAnnotated> { | ||||
|         (resolver.getSymbolsWithAnnotation(GenerateSealedWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach { | ||||
| @@ -131,6 +183,26 @@ class Processor( | ||||
|                 }.build() | ||||
|             } | ||||
|         } | ||||
|         (resolver.getSymbolsWithAnnotation(GenerateSealedTypesWorkaround::class.qualifiedName!!)).filterIsInstance<KSClassDeclaration>().forEach { | ||||
|             val prefix = (it.getGenerateSealedTypesWorkaroundAnnotation) ?.prefix ?.takeIf { | ||||
|                 it.isNotEmpty() | ||||
|             } ?: it.buildSubFileName.replaceFirst(it.simpleName.asString(), "") | ||||
|             it.writeFile(prefix = prefix, suffix = "SealedTypesWorkaround") { | ||||
|                 FileSpec.builder( | ||||
|                     it.packageName.asString(), | ||||
|                     "${it.simpleName.getShortName()}SealedTypesWorkaround" | ||||
|                 ).apply { | ||||
|                     addFileComment( | ||||
|                         """ | ||||
|                         THIS CODE HAVE BEEN GENERATED AUTOMATICALLY | ||||
|                         TO REGENERATE IT JUST DELETE FILE | ||||
|                         ORIGINAL FILE: ${it.containingFile ?.fileName} | ||||
|                         """.trimIndent() | ||||
|                     ) | ||||
|                     generateSealedTypesWorkaround(it, resolver) | ||||
|                 }.build() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return emptyList() | ||||
|     } | ||||
|   | ||||
| @@ -1,14 +1,19 @@ | ||||
| package dev.inmo.micro_utils.ksp.sealed.generator.test | ||||
|  | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedTypesWorkaround | ||||
| import dev.inmo.micro_utils.ksp.sealed.GenerateSealedWorkaround | ||||
|  | ||||
| @GenerateSealedWorkaround | ||||
| @GenerateSealedTypesWorkaround | ||||
| sealed interface Test { | ||||
|     @GenerateSealedWorkaround.Order(2) | ||||
|     @GenerateSealedTypesWorkaround.Exclude | ||||
|     object A : Test | ||||
|     @GenerateSealedWorkaround.Exclude | ||||
|     @GenerateSealedTypesWorkaround.Order(2) | ||||
|     object B : Test | ||||
|     @GenerateSealedWorkaround.Order(0) | ||||
|     @GenerateSealedTypesWorkaround.Order(0) | ||||
|     object C : Test | ||||
|  | ||||
|     // Required for successful sealed workaround generation | ||||
|   | ||||
| @@ -0,0 +1,12 @@ | ||||
| // THIS CODE HAVE BEEN GENERATED AUTOMATICALLY | ||||
| // TO REGENERATE IT JUST DELETE FILE | ||||
| // ORIGINAL FILE: Test.kt | ||||
| package dev.inmo.micro_utils.ksp.`sealed`.generator.test | ||||
|  | ||||
| import kotlin.collections.Set | ||||
| import kotlin.reflect.KClass | ||||
|  | ||||
| private val subtypes: Set<KClass<out Test>> = setOf(Test.C::class, | ||||
|     Test.B::class) | ||||
|  | ||||
| public fun Test.Companion.subtypes(): Set<KClass<out Test>> = subtypes | ||||
							
								
								
									
										40
									
								
								ksp/sealed/generator/test/src/jvmTest/kotlin/TestTests.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ksp/sealed/generator/test/src/jvmTest/kotlin/TestTests.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| import dev.inmo.micro_utils.ksp.sealed.generator.test.subtypes | ||||
| import dev.inmo.micro_utils.ksp.sealed.generator.test.values | ||||
| import kotlin.test.Test | ||||
| import kotlin.test.assertEquals | ||||
| import kotlin.test.assertTrue | ||||
|  | ||||
| class TestTests { | ||||
|     @Test | ||||
|     fun testThatAfterCompilationTestWorkaroundsHaveCorrectValues() { | ||||
|         val correctValues = arrayOf( | ||||
|             dev.inmo.micro_utils.ksp.sealed.generator.test.Test.C, | ||||
|             dev.inmo.micro_utils.ksp.sealed.generator.test.Test.A, | ||||
|         ) | ||||
|         val correctSubtypes = arrayOf( | ||||
|             dev.inmo.micro_utils.ksp.sealed.generator.test.Test.C::class, | ||||
|             dev.inmo.micro_utils.ksp.sealed.generator.test.Test.B::class, | ||||
|         ) | ||||
|  | ||||
|         assertEquals( | ||||
|             correctValues.size, dev.inmo.micro_utils.ksp.sealed.generator.test.Test.values().size | ||||
|         ) | ||||
|         correctValues.forEachIndexed { index, value -> | ||||
|             assertTrue( | ||||
|                 value === dev.inmo.micro_utils.ksp.sealed.generator.test.Test.values().elementAt(index) | ||||
|             ) | ||||
|         } | ||||
|  | ||||
|         assertEquals( | ||||
|             correctSubtypes.size, dev.inmo.micro_utils.ksp.sealed.generator.test.Test.subtypes().size | ||||
|         ) | ||||
|         correctSubtypes.forEachIndexed { index, value -> | ||||
|             assertTrue( | ||||
|                 value.qualifiedName != null | ||||
|             ) | ||||
|             assertTrue( | ||||
|                 value.qualifiedName === dev.inmo.micro_utils.ksp.sealed.generator.test.Test.subtypes().elementAt(index).qualifiedName | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,15 @@ | ||||
| package dev.inmo.micro_utils.ksp.sealed | ||||
|  | ||||
| @Retention(AnnotationRetention.BINARY) | ||||
| @Target(AnnotationTarget.CLASS) | ||||
| annotation class GenerateSealedTypesWorkaround( | ||||
|     val prefix: String = "", | ||||
|     val includeNonSealedSubTypes: Boolean = false, | ||||
| ) { | ||||
|     @Retention(AnnotationRetention.BINARY) | ||||
|     @Target(AnnotationTarget.CLASS) | ||||
|     annotation class Order(val order: Int) | ||||
|     @Retention(AnnotationRetention.BINARY) | ||||
|     @Target(AnnotationTarget.CLASS) | ||||
|     annotation class Exclude | ||||
| } | ||||
| @@ -4,7 +4,7 @@ package dev.inmo.micro_utils.ksp.sealed | ||||
| @Target(AnnotationTarget.CLASS) | ||||
| annotation class GenerateSealedWorkaround( | ||||
|     val prefix: String = "", | ||||
|     val includeNonSealedSubTypes: Boolean = false | ||||
|     val includeNonSealedSubTypes: Boolean = false, | ||||
| ) { | ||||
|     @Retention(AnnotationRetention.BINARY) | ||||
|     @Target(AnnotationTarget.CLASS) | ||||
|   | ||||
| @@ -3,9 +3,10 @@ package dev.inmo.micro_utils.ktor.client | ||||
| import dev.inmo.micro_utils.common.MPPFile | ||||
| import dev.inmo.micro_utils.ktor.common.* | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.content.* | ||||
|  | ||||
| expect suspend fun HttpClient.tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
|     onUpload: ProgressListener = ProgressListener { _, _ -> } | ||||
| ): TemporalFileId | ||||
|   | ||||
| @@ -4,8 +4,8 @@ import dev.inmo.micro_utils.common.FileName | ||||
| import dev.inmo.micro_utils.common.MPPFile | ||||
| import dev.inmo.micro_utils.ktor.common.LambdaInputProvider | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.http.Headers | ||||
| import io.ktor.utils.io.core.Input | ||||
| import kotlinx.serialization.DeserializationStrategy | ||||
| import kotlinx.serialization.StringFormat | ||||
| import kotlinx.serialization.json.Json | ||||
| @@ -31,7 +31,7 @@ expect suspend fun <T> HttpClient.uniUpload( | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     headers: Headers = Headers.Empty, | ||||
|     stringFormat: StringFormat = Json, | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
|     onUpload: ProgressListener = ProgressListener { _, _ -> } | ||||
| ): T? | ||||
|  | ||||
| /** | ||||
| @@ -46,7 +46,7 @@ suspend fun <T> HttpClient.uniUpload( | ||||
|     additionalData: Map<String, Any> = emptyMap(), | ||||
|     headers: Headers = Headers.Empty, | ||||
|     stringFormat: StringFormat = Json, | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
|     onUpload: ProgressListener = ProgressListener { _, _ -> } | ||||
| ): T? = uniUpload( | ||||
|     url, | ||||
|     additionalData + ("bytes" to file), | ||||
| @@ -68,7 +68,7 @@ suspend fun <T> HttpClient.uniUpload( | ||||
|     additionalData: Map<String, Any> = emptyMap(), | ||||
|     headers: Headers = Headers.Empty, | ||||
|     stringFormat: StringFormat = Json, | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
|     onUpload: ProgressListener = ProgressListener { _, _ -> } | ||||
| ): T? = uniUpload( | ||||
|     url, | ||||
|     additionalData + ("bytes" to info), | ||||
| @@ -93,7 +93,7 @@ suspend fun <T> HttpClient.uniUpload( | ||||
|     additionalData: Map<String, Any> = emptyMap(), | ||||
|     headers: Headers = Headers.Empty, | ||||
|     stringFormat: StringFormat = Json, | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
|     onUpload: ProgressListener = ProgressListener { _, _ -> } | ||||
| ): T? = uniUpload( | ||||
|     url, | ||||
|     UniUploadFileInfo(fileName, mimeType, inputAllocator), | ||||
|   | ||||
| @@ -5,16 +5,15 @@ import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob | ||||
| import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions | ||||
| import dev.inmo.micro_utils.ktor.common.TemporalFileId | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.content.* | ||||
| import kotlinx.coroutines.* | ||||
| import org.w3c.dom.mediasource.ENDED | ||||
| import org.w3c.dom.mediasource.ReadyState | ||||
| import org.w3c.xhr.* | ||||
| import org.w3c.xhr.XMLHttpRequest.Companion.DONE | ||||
|  | ||||
| suspend fun tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): TemporalFileId { | ||||
|     val formData = FormData() | ||||
|     val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job) | ||||
| @@ -28,7 +27,7 @@ suspend fun tempUpload( | ||||
|     val request = XMLHttpRequest() | ||||
|     request.responseType = XMLHttpRequestResponseType.TEXT | ||||
|     request.upload.onprogress = { | ||||
|         subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) } | ||||
|         subscope.launchSafelyWithoutExceptions { onUpload.onProgress(it.loaded.toLong(), it.total.toLong()) } | ||||
|     } | ||||
|     request.onload = { | ||||
|         if (request.status == 200.toShort()) { | ||||
| @@ -60,5 +59,5 @@ suspend fun tempUpload( | ||||
| actual suspend fun HttpClient.tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload) | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| package dev.inmo.micro_utils.ktor.client | ||||
|  | ||||
| import dev.inmo.micro_utils.common.MPPFile | ||||
| import dev.inmo.micro_utils.common.Progress | ||||
| import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob | ||||
| import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.http.Headers | ||||
| import io.ktor.utils.io.core.readBytes | ||||
| import kotlinx.coroutines.CompletableDeferred | ||||
| @@ -36,7 +36,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     headers: Headers, | ||||
|     stringFormat: StringFormat, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): T? { | ||||
|     val formData = FormData() | ||||
|     val answer = CompletableDeferred<T?>(currentCoroutineContext().job) | ||||
| @@ -66,7 +66,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|     } | ||||
|     request.responseType = XMLHttpRequestResponseType.TEXT | ||||
|     request.upload.onprogress = { | ||||
|         subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) } | ||||
|         subscope.launchSafelyWithoutExceptions { onUpload.onProgress(it.loaded.toLong(), it.total.toLong()) } | ||||
|     } | ||||
|     request.onload = { | ||||
|         if (request.status == 200.toShort()) { | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import dev.inmo.micro_utils.common.MPPFile | ||||
| import dev.inmo.micro_utils.common.filename | ||||
| import dev.inmo.micro_utils.ktor.common.TemporalFileId | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.client.plugins.onUpload | ||||
| import io.ktor.client.request.forms.formData | ||||
| import io.ktor.client.request.forms.submitFormWithBinaryData | ||||
| @@ -18,7 +19,7 @@ internal val MPPFile.mimeType: String | ||||
| actual suspend fun HttpClient.tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): TemporalFileId { | ||||
|     val inputProvider = file.inputProvider() | ||||
|     val fileId = submitFormWithBinaryData( | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| package dev.inmo.micro_utils.ktor.client | ||||
|  | ||||
| import dev.inmo.micro_utils.common.Progress | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.engine.mergeHeaders | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.client.plugins.onUpload | ||||
| import io.ktor.client.request.HttpRequestBuilder | ||||
| import io.ktor.client.request.forms.InputProvider | ||||
| @@ -20,7 +19,6 @@ import kotlinx.serialization.DeserializationStrategy | ||||
| import kotlinx.serialization.InternalSerializationApi | ||||
| import kotlinx.serialization.SerializationStrategy | ||||
| import kotlinx.serialization.StringFormat | ||||
| import kotlinx.serialization.encodeToString | ||||
| import kotlinx.serialization.serializer | ||||
| import java.io.File | ||||
|  | ||||
| @@ -39,7 +37,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     headers: Headers, | ||||
|     stringFormat: StringFormat, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): T? { | ||||
|     val withBinary = data.values.any { it is File || it is UniUploadFileInfo } | ||||
|  | ||||
| @@ -76,7 +74,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|             appendAll(headers) | ||||
|         } | ||||
|         onUpload { bytesSentTotal, contentLength -> | ||||
|             onUpload(bytesSentTotal, contentLength) | ||||
|             onUpload.onProgress(bytesSentTotal, contentLength) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import dev.inmo.micro_utils.common.filename | ||||
| import dev.inmo.micro_utils.ktor.common.TemporalFileId | ||||
| import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.client.plugins.onUpload | ||||
| import io.ktor.client.request.forms.formData | ||||
| import io.ktor.client.request.forms.submitFormWithBinaryData | ||||
| @@ -18,7 +19,7 @@ internal val MPPFile.mimeType: String | ||||
| actual suspend fun HttpClient.tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): TemporalFileId { | ||||
|     val inputProvider = file.inputProvider() | ||||
|     val fileId = submitFormWithBinaryData( | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| package dev.inmo.micro_utils.ktor.client | ||||
|  | ||||
| import dev.inmo.micro_utils.common.MPPFile | ||||
| import dev.inmo.micro_utils.common.Progress | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.engine.mergeHeaders | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.client.plugins.onUpload | ||||
| import io.ktor.client.request.HttpRequestBuilder | ||||
| import io.ktor.client.request.forms.InputProvider | ||||
| @@ -21,7 +20,6 @@ import kotlinx.serialization.DeserializationStrategy | ||||
| import kotlinx.serialization.InternalSerializationApi | ||||
| import kotlinx.serialization.SerializationStrategy | ||||
| import kotlinx.serialization.StringFormat | ||||
| import kotlinx.serialization.encodeToString | ||||
| import kotlinx.serialization.serializer | ||||
|  | ||||
| /** | ||||
| @@ -39,7 +37,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     headers: Headers, | ||||
|     stringFormat: StringFormat, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): T? { | ||||
|     val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo } | ||||
|  | ||||
| @@ -75,9 +73,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|         headers { | ||||
|             appendAll(headers) | ||||
|         } | ||||
|         onUpload { bytesSentTotal, contentLength -> | ||||
|             onUpload(bytesSentTotal, contentLength) | ||||
|         } | ||||
|         onUpload(onUpload) | ||||
|     } | ||||
|  | ||||
|     val response = if (withBinary) { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import dev.inmo.micro_utils.common.filename | ||||
| import dev.inmo.micro_utils.ktor.common.TemporalFileId | ||||
| import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.client.plugins.onUpload | ||||
| import io.ktor.client.request.forms.formData | ||||
| import io.ktor.client.request.forms.submitFormWithBinaryData | ||||
| @@ -18,7 +19,7 @@ internal val MPPFile.mimeType: String | ||||
| actual suspend fun HttpClient.tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): TemporalFileId { | ||||
|     val inputProvider = file.inputProvider() | ||||
|     val fileId = submitFormWithBinaryData( | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| package dev.inmo.micro_utils.ktor.client | ||||
|  | ||||
| import dev.inmo.micro_utils.common.MPPFile | ||||
| import dev.inmo.micro_utils.common.Progress | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.engine.mergeHeaders | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.client.plugins.onUpload | ||||
| import io.ktor.client.request.HttpRequestBuilder | ||||
| import io.ktor.client.request.forms.InputProvider | ||||
| @@ -21,7 +20,6 @@ import kotlinx.serialization.DeserializationStrategy | ||||
| import kotlinx.serialization.InternalSerializationApi | ||||
| import kotlinx.serialization.SerializationStrategy | ||||
| import kotlinx.serialization.StringFormat | ||||
| import kotlinx.serialization.encodeToString | ||||
| import kotlinx.serialization.serializer | ||||
|  | ||||
| /** | ||||
| @@ -39,7 +37,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     headers: Headers, | ||||
|     stringFormat: StringFormat, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): T? { | ||||
|     val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo } | ||||
|  | ||||
| @@ -75,9 +73,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|         headers { | ||||
|             appendAll(headers) | ||||
|         } | ||||
|         onUpload { bytesSentTotal, contentLength -> | ||||
|             onUpload(bytesSentTotal, contentLength) | ||||
|         } | ||||
|         onUpload(onUpload) | ||||
|     } | ||||
|  | ||||
|     val response = if (withBinary) { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import dev.inmo.micro_utils.common.filename | ||||
| import dev.inmo.micro_utils.ktor.common.TemporalFileId | ||||
| import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.client.plugins.onUpload | ||||
| import io.ktor.client.request.forms.formData | ||||
| import io.ktor.client.request.forms.submitFormWithBinaryData | ||||
| @@ -18,7 +19,7 @@ internal val MPPFile.mimeType: String | ||||
| actual suspend fun HttpClient.tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): TemporalFileId { | ||||
|     val inputProvider = file.inputProvider() | ||||
|     val fileId = submitFormWithBinaryData( | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| package dev.inmo.micro_utils.ktor.client | ||||
|  | ||||
| import dev.inmo.micro_utils.common.MPPFile | ||||
| import dev.inmo.micro_utils.common.Progress | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.engine.mergeHeaders | ||||
| import io.ktor.client.content.* | ||||
| import io.ktor.client.plugins.onUpload | ||||
| import io.ktor.client.request.HttpRequestBuilder | ||||
| import io.ktor.client.request.forms.InputProvider | ||||
| @@ -21,7 +20,6 @@ import kotlinx.serialization.DeserializationStrategy | ||||
| import kotlinx.serialization.InternalSerializationApi | ||||
| import kotlinx.serialization.SerializationStrategy | ||||
| import kotlinx.serialization.StringFormat | ||||
| import kotlinx.serialization.encodeToString | ||||
| import kotlinx.serialization.serializer | ||||
|  | ||||
| /** | ||||
| @@ -39,7 +37,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     headers: Headers, | ||||
|     stringFormat: StringFormat, | ||||
|     onUpload: OnUploadCallback | ||||
|     onUpload: ProgressListener | ||||
| ): T? { | ||||
|     val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo } | ||||
|  | ||||
| @@ -75,9 +73,7 @@ actual suspend fun <T> HttpClient.uniUpload( | ||||
|         headers { | ||||
|             appendAll(headers) | ||||
|         } | ||||
|         onUpload { bytesSentTotal, contentLength -> | ||||
|             onUpload(bytesSentTotal, contentLength) | ||||
|         } | ||||
|         onUpload(onUpload) | ||||
|     } | ||||
|  | ||||
|     val response = if (withBinary) { | ||||
|   | ||||
| @@ -1,13 +1,12 @@ | ||||
| package dev.inmo.micro_utils.ktor.common | ||||
|  | ||||
| import io.ktor.utils.io.core.Input | ||||
| import io.ktor.utils.io.core.copyTo | ||||
| import io.ktor.utils.io.streams.asOutput | ||||
| import io.ktor.utils.io.streams.* | ||||
| import kotlinx.io.Source | ||||
| import kotlinx.io.readTo | ||||
| import java.io.File | ||||
| import java.io.InputStream | ||||
| import java.util.UUID | ||||
|  | ||||
| fun Input.downloadToTempFile( | ||||
| fun Source.downloadToTempFile( | ||||
|     fileName: String = UUID.randomUUID().toString(), | ||||
|     fileExtension: String? = ".temp", | ||||
|     folder: File? = null | ||||
| @@ -17,7 +16,7 @@ fun Input.downloadToTempFile( | ||||
|     folder | ||||
| ).apply { | ||||
|     outputStream().use { | ||||
|         copyTo(it.asOutput()) | ||||
|         it.writePacket(this@downloadToTempFile) | ||||
|     } | ||||
|     deleteOnExit() | ||||
| } | ||||
|   | ||||
| @@ -1,15 +0,0 @@ | ||||
| package dev.inmo.micro_utils.ktor.server | ||||
|  | ||||
| import io.ktor.server.application.ApplicationCall | ||||
| import io.ktor.server.response.responseType | ||||
| import io.ktor.util.InternalAPI | ||||
| import io.ktor.util.reflect.TypeInfo | ||||
|  | ||||
| @InternalAPI | ||||
| suspend fun <T : Any> ApplicationCall.respond( | ||||
|     message: T, | ||||
|     typeInfo: TypeInfo | ||||
| ) { | ||||
|     response.responseType = typeInfo | ||||
|     response.pipeline.execute(this, message as Any) | ||||
| } | ||||
| @@ -4,13 +4,13 @@ import com.benasher44.uuid.uuid4 | ||||
| import io.ktor.http.content.PartData | ||||
| import io.ktor.utils.io.copyTo | ||||
| import io.ktor.utils.io.core.copyTo | ||||
| import io.ktor.utils.io.jvm.javaio.copyTo | ||||
| import io.ktor.utils.io.streams.asOutput | ||||
| import io.ktor.utils.io.jvm.javaio.* | ||||
| import io.ktor.utils.io.streams.* | ||||
| import java.io.File | ||||
|  | ||||
| fun PartData.FileItem.download(target: File) { | ||||
|     provider().use { input -> | ||||
|         target.outputStream().asOutput().use { | ||||
|     provider().toInputStream().use { input -> | ||||
|         target.outputStream().use { | ||||
|             input.copyTo(it) | ||||
|         } | ||||
|     } | ||||
| @@ -25,9 +25,9 @@ fun PartData.FileItem.downloadToTemporalFile(): File { | ||||
| } | ||||
|  | ||||
| fun PartData.BinaryItem.download(target: File) { | ||||
|     provider().use { input -> | ||||
|     provider().inputStream().use { input -> | ||||
|         target.outputStream().use { | ||||
|             input.copyTo(it.asOutput()) | ||||
|             input.copyTo(it) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package dev.inmo.micro_utils.ktor.server | ||||
|  | ||||
| import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator | ||||
| import io.ktor.server.application.Application | ||||
| import io.ktor.server.application.* | ||||
| import io.ktor.server.cio.CIO | ||||
| import io.ktor.server.cio.CIOApplicationEngine | ||||
| import io.ktor.server.engine.* | ||||
| @@ -11,20 +11,22 @@ fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configurati | ||||
|     engine: ApplicationEngineFactory<TEngine, TConfiguration>, | ||||
|     host: String = "localhost", | ||||
|     port: Int = Random.nextInt(1024, 65535), | ||||
|     additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, | ||||
|     additionalEngineEnvironmentConfigurator: EngineConnectorBuilder.() -> Unit = {}, | ||||
|     additionalConfigurationConfigurator: TConfiguration.() -> Unit = {}, | ||||
|     environment: ApplicationEnvironment = applicationEnvironment(), | ||||
|     block: Application.() -> Unit | ||||
| ): TEngine = embeddedServer( | ||||
| ): EmbeddedServer<TEngine, TConfiguration> = embeddedServer<TEngine, TConfiguration>( | ||||
|     engine, | ||||
|     applicationEngineEnvironment { | ||||
|         module(block) | ||||
|     environment, | ||||
|     { | ||||
|         connector { | ||||
|             this.host = host | ||||
|             this.port = port | ||||
|             additionalEngineEnvironmentConfigurator() | ||||
|         } | ||||
|         additionalEngineEnvironmentConfigurator() | ||||
|         additionalConfigurationConfigurator() | ||||
|     }, | ||||
|     additionalConfigurationConfigurator | ||||
|     module = block | ||||
| ) | ||||
|  | ||||
| /** | ||||
| @@ -35,15 +37,17 @@ fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configurati | ||||
| fun createKtorServer( | ||||
|     host: String = "localhost", | ||||
|     port: Int = Random.nextInt(1024, 65535), | ||||
|     additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, | ||||
|     additionalEngineEnvironmentConfigurator: EngineConnectorBuilder.() -> Unit = {}, | ||||
|     additionalConfigurationConfigurator: CIOApplicationEngine.Configuration.() -> Unit = {}, | ||||
|     environment: ApplicationEnvironment = applicationEnvironment(), | ||||
|     block: Application.() -> Unit | ||||
| ): CIOApplicationEngine = createKtorServer( | ||||
| ): EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration> = createKtorServer( | ||||
|     CIO, | ||||
|     host, | ||||
|     port, | ||||
|     additionalEngineEnvironmentConfigurator, | ||||
|     additionalConfigurationConfigurator, | ||||
|     environment, | ||||
|     block | ||||
| ) | ||||
|  | ||||
| @@ -51,15 +55,17 @@ fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configurati | ||||
|     engine: ApplicationEngineFactory<TEngine, TConfiguration>, | ||||
|     host: String = "localhost", | ||||
|     port: Int = Random.nextInt(1024, 65535), | ||||
|     additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, | ||||
|     additionalEngineEnvironmentConfigurator: EngineConnectorBuilder.() -> Unit = {}, | ||||
|     additionalConfigurationConfigurator: TConfiguration.() -> Unit = {}, | ||||
|     environment: ApplicationEnvironment = applicationEnvironment(), | ||||
|     configurators: List<KtorApplicationConfigurator> | ||||
| ): TEngine = createKtorServer( | ||||
| ): EmbeddedServer<TEngine, TConfiguration> = createKtorServer( | ||||
|     engine, | ||||
|     host, | ||||
|     port, | ||||
|     additionalEngineEnvironmentConfigurator, | ||||
|     additionalConfigurationConfigurator | ||||
|     additionalConfigurationConfigurator, | ||||
|     environment, | ||||
| ) { | ||||
|     configurators.forEach { it.apply { configure() } } | ||||
| } | ||||
| @@ -73,6 +79,7 @@ fun createKtorServer( | ||||
|     host: String = "localhost", | ||||
|     port: Int = Random.nextInt(1024, 65535), | ||||
|     configurators: List<KtorApplicationConfigurator>, | ||||
|     additionalEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, | ||||
|     additionalEngineEnvironmentConfigurator: EngineConnectorBuilder.() -> Unit = {}, | ||||
|     additionalConfigurationConfigurator: CIOApplicationEngine.Configuration.() -> Unit = {}, | ||||
| ): ApplicationEngine = createKtorServer(CIO, host, port, additionalEngineEnvironmentConfigurator, additionalConfigurationConfigurator, configurators) | ||||
|     environment: ApplicationEnvironment = applicationEnvironment(), | ||||
| ): EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration> = createKtorServer(CIO, host, port, additionalEngineEnvironmentConfigurator, additionalConfigurationConfigurator, environment, configurators) | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import io.ktor.server.response.respondText | ||||
| import io.ktor.server.routing.Route | ||||
| import io.ktor.server.routing.post | ||||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.flow.* | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
| import kotlinx.coroutines.sync.withLock | ||||
| @@ -26,7 +27,10 @@ import java.nio.file.attribute.FileTime | ||||
|  | ||||
| class TemporalFilesRoutingConfigurator( | ||||
|     private val subpath: String = DefaultTemporalFilesSubPath, | ||||
|     private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer | ||||
|     private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer, | ||||
|     filesFlowReplay: Int = 0, | ||||
|     filesFlowExtraBufferCapacity: Int = Int.MAX_VALUE, | ||||
|     filesFlowOnBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND | ||||
| ) : ApplicationRoutingConfigurator.Element { | ||||
|     interface TemporalFilesUtilizer { | ||||
|         fun start(filesMap: MutableMap<TemporalFileId, MPPFile>, filesMutex: Mutex, onNewFileFlow: Flow<TemporalFileId>): Job | ||||
| @@ -74,7 +78,11 @@ class TemporalFilesRoutingConfigurator( | ||||
|  | ||||
|     private val temporalFilesMap = mutableMapOf<TemporalFileId, MPPFile>() | ||||
|     private val temporalFilesMutex = Mutex() | ||||
|     private val filesFlow = MutableSharedFlow<TemporalFileId>() | ||||
|     private val filesFlow = MutableSharedFlow<TemporalFileId>( | ||||
|         replay = filesFlowReplay, | ||||
|         extraBufferCapacity = filesFlowExtraBufferCapacity, | ||||
|         onBufferOverflow = filesFlowOnBufferOverflow | ||||
|     ) | ||||
|     val utilizerJob = temporalFilesUtilizer.start(temporalFilesMap, temporalFilesMutex, filesFlow.asSharedFlow()) | ||||
|  | ||||
|     override fun Route.invoke() { | ||||
| @@ -111,7 +119,7 @@ class TemporalFilesRoutingConfigurator( | ||||
|                     temporalFilesMap[fileId] = file | ||||
|                 } | ||||
|                 call.respondText(fileId.string) | ||||
|                 launchSafelyWithoutExceptions { filesFlow.emit(fileId) } | ||||
|                 filesFlow.emit(fileId) | ||||
|             } ?: call.respond(HttpStatusCode.BadRequest) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import dev.inmo.micro_utils.ktor.common.downloadToTempFile | ||||
| import io.ktor.http.content.* | ||||
| import io.ktor.server.application.ApplicationCall | ||||
| import io.ktor.server.request.receiveMultipart | ||||
| import io.ktor.utils.io.* | ||||
| import io.ktor.utils.io.core.* | ||||
| import kotlinx.coroutines.currentCoroutineContext | ||||
| import kotlinx.coroutines.isActive | ||||
| @@ -47,7 +48,7 @@ suspend fun ApplicationCall.uniloadMultipart( | ||||
|         onBinaryChannelItem | ||||
|     ) { | ||||
|         when (it.name) { | ||||
|             "bytes" -> resultInput = it.provider() | ||||
|             "bytes" -> resultInput = it.provider().readBuffer() | ||||
|             else -> onCustomFileItem(it) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -2,8 +2,7 @@ package dev.inmo.micro_utils.ktor.server.configurators | ||||
|  | ||||
| import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator.Element | ||||
| import io.ktor.server.application.* | ||||
| import io.ktor.server.routing.Route | ||||
| import io.ktor.server.routing.Routing | ||||
| import io.ktor.server.routing.* | ||||
| import kotlinx.serialization.Contextual | ||||
| import kotlinx.serialization.Serializable | ||||
|  | ||||
| @@ -19,9 +18,7 @@ class ApplicationRoutingConfigurator( | ||||
|     } | ||||
|  | ||||
|     override fun Application.configure() { | ||||
|         pluginOrNull(Routing) ?.apply { | ||||
|             rootInstaller.apply { invoke() } | ||||
|         } ?: install(Routing) { | ||||
|         routing { | ||||
|             rootInstaller.apply { invoke() } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -23,9 +23,7 @@ interface KtorApplicationConfigurator { | ||||
|         } | ||||
|  | ||||
|         override fun Application.configure() { | ||||
|             pluginOrNull(io.ktor.server.routing.Routing) ?.apply { | ||||
|                 rootInstaller.apply { invoke() } | ||||
|             } ?: install(io.ktor.server.routing.Routing) { | ||||
|             routing { | ||||
|                 rootInstaller.apply { invoke() } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||||
|   "extends": [ | ||||
|     "config:base" | ||||
|     "config:recommended" | ||||
|   ] | ||||
| } | ||||
|   | ||||
| @@ -8,8 +8,16 @@ interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo { | ||||
|     val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean> | ||||
|     val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean> | ||||
|         get() = { | ||||
|             it.foldRight<IdType, Op<Boolean>?>(null) { id, acc -> | ||||
|                 acc ?.or(selectById(id)) ?: selectById(id) | ||||
|             } ?: Op.FALSE | ||||
|             if (it.isEmpty()) { | ||||
|                 Op.FALSE | ||||
|             } else { | ||||
|                 var op = it.firstOrNull() ?.let { selectById(it) } ?: Op.FALSE | ||||
|                 var i = 1 | ||||
|                 while (i < it.size) { | ||||
|                     op = op.or(selectById(it[i])) | ||||
|                     i++ | ||||
|                 } | ||||
|                 op | ||||
|             } | ||||
|         } | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import dev.inmo.micro_utils.repos.ktor.common.key_value.* | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.request.post | ||||
| import io.ktor.http.* | ||||
| import io.ktor.util.InternalAPI | ||||
| import io.ktor.utils.io.InternalAPI | ||||
| import io.ktor.util.reflect.TypeInfo | ||||
| import io.ktor.util.reflect.typeInfo | ||||
| import kotlinx.coroutines.flow.Flow | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import dev.inmo.micro_utils.repos.ktor.common.one_to_many.* | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.request.post | ||||
| import io.ktor.http.* | ||||
| import io.ktor.util.InternalAPI | ||||
| import io.ktor.utils.io.InternalAPI | ||||
| import io.ktor.util.reflect.TypeInfo | ||||
| import io.ktor.util.reflect.typeInfo | ||||
| import kotlinx.coroutines.flow.Flow | ||||
|   | ||||
| @@ -25,7 +25,7 @@ import kotlin.test.Test | ||||
| import kotlin.test.assertEquals | ||||
|  | ||||
| class KtorCRUDRepoTests : CommonCRUDRepoTests() { | ||||
|     private var engine: ApplicationEngine? = null | ||||
|     private var engine: EmbeddedServer<*, *>? = null | ||||
|  | ||||
|     @BeforeTest | ||||
|     fun beforeTest() { | ||||
|   | ||||
| @@ -27,7 +27,7 @@ import kotlinx.serialization.json.Json | ||||
| import kotlin.test.* | ||||
|  | ||||
| class KtorKeyValueRepoTests : CommonKeyValueRepoTests() { | ||||
|     private var engine: ApplicationEngine? = null | ||||
|     private var engine: EmbeddedServer<*, *>? = null | ||||
|  | ||||
|     override val repoCreator: suspend () -> KeyValueRepo<String, String> = { | ||||
|         KtorKeyValueRepoClient( | ||||
| @@ -77,7 +77,7 @@ class KtorKeyValueRepoTests : CommonKeyValueRepoTests() { | ||||
|                         repo, | ||||
|                         Int.serializer(), | ||||
|                         ComplexData.serializer(), | ||||
|                         Json {} | ||||
|                         Json | ||||
|                     ) | ||||
|                 } | ||||
|             }.start(false) | ||||
|   | ||||
| @@ -23,7 +23,7 @@ import kotlinx.serialization.json.Json | ||||
| import kotlin.test.* | ||||
|  | ||||
| class KtorKeyValuesRepoTests : CommonKeyValuesRepoTests() { | ||||
|     private var engine: ApplicationEngine? = null | ||||
|     private var engine: EmbeddedServer<*, *>? = null | ||||
|     override val testSequencesSize: Int | ||||
|         get() = 100 | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,7 @@ import io.ktor.server.websocket.* | ||||
| import kotlinx.serialization.json.Json | ||||
|  | ||||
| object KtorRepoTestsHelper { | ||||
|     fun beforeTest(routingConfigurator: Routing.() -> Unit): ApplicationEngine { | ||||
|     fun beforeTest(routingConfigurator: Routing.() -> Unit): EmbeddedServer<CIOApplicationEngine, CIOApplicationEngine.Configuration> { | ||||
|         return embeddedServer( | ||||
|             CIO, | ||||
|             23456, | ||||
| @@ -28,7 +28,7 @@ object KtorRepoTestsHelper { | ||||
|             routing(routingConfigurator) | ||||
|         }.start(false) | ||||
|     } | ||||
|     fun afterTest(engine: ApplicationEngine) { | ||||
|     fun afterTest(engine: EmbeddedServer<*, *>) { | ||||
|         engine.stop() | ||||
|     } | ||||
|     fun client(): HttpClient = HttpClient { | ||||
|   | ||||
| @@ -16,7 +16,7 @@ import io.ktor.server.application.call | ||||
| import io.ktor.server.response.respond | ||||
| import io.ktor.server.routing.Route | ||||
| import io.ktor.server.routing.get | ||||
| import io.ktor.util.InternalAPI | ||||
| import io.ktor.utils.io.InternalAPI | ||||
| import io.ktor.util.reflect.typeInfo | ||||
| import kotlinx.serialization.* | ||||
|  | ||||
|   | ||||
| @@ -14,7 +14,7 @@ import io.ktor.server.application.call | ||||
| import io.ktor.server.response.respond | ||||
| import io.ktor.server.routing.Route | ||||
| import io.ktor.server.routing.get | ||||
| import io.ktor.util.InternalAPI | ||||
| import io.ktor.utils.io.InternalAPI | ||||
| import io.ktor.util.reflect.typeInfo | ||||
| import kotlinx.serialization.* | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ class TypedSerializerTests { | ||||
|     interface Example { | ||||
|         val number: Number | ||||
|     } | ||||
|     val serialFormat = Json {  } | ||||
|     val serialFormat = Json | ||||
|  | ||||
|     @Serializable | ||||
|     data class Example1(override val number: Long) : Example | ||||
|   | ||||
		Reference in New Issue
	
	Block a user