mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-30 03:30:19 +00:00 
			
		
		
		
	Compare commits
	
		
			117 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| acbb8a0c07 | |||
| b9d8528599 | |||
| 4971326eca | |||
| 09d1047260 | |||
| 02dbd493c2 | |||
| b17931e7bd | |||
| 2a4570eafc | |||
| c9514d3a6d | |||
| 072805efc7 | |||
| 369ff26627 | |||
| c5abbbbd2d | |||
| d974639f1e | |||
| 26efde316b | |||
| fafe50f80a | |||
| 41504469db | |||
| 03b3ddd98b | |||
| 89d919f2be | |||
| b53cfd5504 | |||
| 31022733ac | |||
| f9a8c39879 | |||
| a812c2dd2f | |||
| 217e977f0d | |||
| 04c301d1ac | |||
| 7f0c425389 | |||
| 1ede1c423b | |||
| 7cb064896a | |||
| 0c5e2862ca | |||
| 30d4507f54 | |||
| b6c6d89455 | |||
| 9d218ee534 | |||
| 11116d8cab | |||
| 58d754bbde | |||
| 8f25c123dd | |||
| 76e214fc08 | |||
| 2b5380f8d6 | |||
| 844a129094 | |||
| a3090cca9d | |||
| b7b5159e9c | |||
| 0f8bc2c950 | |||
| 69f5c49f45 | |||
| 9b308e6fb8 | |||
| 3e3f91128b | |||
| 0d1aae0ef7 | |||
| 4d022f0480 | |||
| 153e20d00e | |||
| a9a8171dd6 | |||
| bf5c3b59b2 | |||
| 607c432bdb | |||
| ae5c010770 | |||
| d5e432437f | |||
| 9f99ebce01 | |||
| 64ee899b84 | |||
| e0e0c1658b | |||
| 2c586f667c | |||
| 64164ef6c1 | |||
| 22343c0731 | |||
| f4ec1a4c60 | |||
| c1c33cceb1 | |||
| a3e975b2ba | |||
| 06e705a687 | |||
| d56eb6c867 | |||
| 9cbca864e3 | |||
| abb4378694 | |||
| 0eb698d9a4 | |||
| 15ea9f2093 | |||
| d47aca0923 | |||
| 1ac50e9959 | |||
| 6adfbe3a96 | |||
| 59f36e62e9 | |||
| 54af116009 | |||
| 38fbec8e3b | |||
| babbfc55e4 | |||
| 2511e18d69 | |||
| 29658c70a0 | |||
| 96311ee43d | |||
| bd33b09052 | |||
| 8055003b47 | |||
| 1257492f85 | |||
| 1107b7f4ef | |||
| a1a1171240 | |||
| 46c02e5df1 | |||
| 2e9efc57de | |||
| acecadef17 | |||
| 19394b5e69 | |||
| de999e197f | |||
| 9d95687d3c | |||
| aa9dfb4ab8 | |||
| 9c5b44efb3 | |||
| ac587a67e6 | |||
| 59428140a8 | |||
| 60bdb59d71 | |||
| be52871de8 | |||
| b7934cf357 | |||
| dbfbeef90a | |||
| 00943c9cdf | |||
| 8745c6a16a | |||
| 433ba4b58f | |||
| d40376e524 | |||
| a2982f88f5 | |||
| 1642f7abd9 | |||
| a10d2184ff | |||
| 522435f096 | |||
| 79b30290c0 | |||
| f8b8626859 | |||
| b061b85a08 | |||
| 3870db1c88 | |||
| 1be1070eb4 | |||
| 2696e663cf | |||
| 1e1f7df86d | |||
| 1d8ded8fd3 | |||
| 197825123a | |||
| 422b2e6db1 | |||
| 1973e0b5bf | |||
| 8258cf93a9 | |||
| 1d49bd5947 | |||
| 44317d1519 | |||
| 48e08fcc69 | 
							
								
								
									
										14
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,9 +9,6 @@ jobs: | |||||||
|       - uses: actions/setup-java@v1 |       - uses: actions/setup-java@v1 | ||||||
|         with: |         with: | ||||||
|           java-version: 11 |           java-version: 11 | ||||||
|       - name: Fix android 32.0.0 dx |  | ||||||
|         continue-on-error: true |  | ||||||
|         run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib  && mv d8.jar dx.jar |  | ||||||
|       - name: Rewrite version |       - name: Rewrite version | ||||||
|         run: | |         run: | | ||||||
|           branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`" |           branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`" | ||||||
| @@ -20,9 +17,8 @@ jobs: | |||||||
|           mv gradle.properties.tmp gradle.properties |           mv gradle.properties.tmp gradle.properties | ||||||
|       - name: Build |       - name: Build | ||||||
|         run: ./gradlew build |         run: ./gradlew build | ||||||
| #      - name: Publish |       - name: Publish | ||||||
| #        continue-on-error: true |         continue-on-error: true | ||||||
| #        run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository |         run: ./gradlew publishAllPublicationsToGiteaRepository | ||||||
| #        env: |         env: | ||||||
| #          GITHUBPACKAGES_USER: ${{ github.actor }} |           GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} | ||||||
| #          GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} |  | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,9 +11,6 @@ jobs: | |||||||
|       - uses: actions/setup-java@v1 |       - uses: actions/setup-java@v1 | ||||||
|         with: |         with: | ||||||
|           java-version: 11 |           java-version: 11 | ||||||
|       - name: Fix android 32.0.0 dx |  | ||||||
|         continue-on-error: true |  | ||||||
|         run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib  && mv d8.jar dx.jar |  | ||||||
|       - name: Build |       - name: Build | ||||||
|         run: ./gradlew build && ./gradlew dokkaHtml |         run: ./gradlew build && ./gradlew dokkaHtml | ||||||
|       - name: Publish KDocs |       - name: Publish KDocs | ||||||
|   | |||||||
| @@ -1,8 +0,0 @@ | |||||||
| job("Build and run tests") { |  | ||||||
|     container(displayName = "Run gradle build", image = "openjdk:11") { |  | ||||||
|         kotlinScript { api -> |  | ||||||
|             // here can be your complex logic |  | ||||||
|             api.gradlew("build") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										160
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,165 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 0.15.1 | ||||||
|  |  | ||||||
|  | * `Startup`: | ||||||
|  |     * Inited :) | ||||||
|  |     * `Plugin`: | ||||||
|  |         * Inited :) | ||||||
|  |     * `Launcher`: | ||||||
|  |         * Inited :) | ||||||
|  |  | ||||||
|  | ## 0.15.0 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `CRUD`: | ||||||
|  |         * `Common`: | ||||||
|  |             * New method `ReadCRUDRepo#getIdsByPagination` | ||||||
|  |         * `Android`: | ||||||
|  |             * `AbstractAndroidCRUDRepo` got new abstract method `toId` | ||||||
|  |         * `Exposed`: | ||||||
|  |             * `CommonExposedRepo` new abstract property `asId` | ||||||
|  |         * `Ktor`: | ||||||
|  |             * `Client`: | ||||||
|  |                 * `KtorReadCRUDRepoClient` now requires `paginationIdType` | ||||||
|  | * `LanguageCodes`: | ||||||
|  |     * Updates and fixes in generation | ||||||
|  | * `MimeTypes`: | ||||||
|  |     * Updates and fixes in generation | ||||||
|  |  | ||||||
|  | ## 0.14.4 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * `JVM`: | ||||||
|  |         * New extension `downloadToTempFile` | ||||||
|  | * `Ktor`: | ||||||
|  |     * `Server`: | ||||||
|  |         * Small fix in `handleUniUpload` | ||||||
|  |         * `ApplicationCall#uniloadMultipartFile` now uses `uniloadMultipart` | ||||||
|  |     * `Common`: | ||||||
|  |         * New extension `downloadToTempFile` | ||||||
|  |     * `Client`: | ||||||
|  |         * New extensions on top of `uniUpload` | ||||||
|  |  | ||||||
|  | ## 0.14.3 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * New type `Progress` | ||||||
|  | * `Ktor`: | ||||||
|  |     * `Client`: | ||||||
|  |         * New universal `uniUpload` extension for `HttpClient` | ||||||
|  |     * `Server`: | ||||||
|  |         * New universal `handleUniUpload` extension for `ApplicationCall` | ||||||
|  |         * Add extensions `download` and `downloadToTemporalFile` | ||||||
|  |  | ||||||
|  | ## 0.14.2 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Exposed`: `0.40.1` -> `0.41.1` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## 0.14.1 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Klock`: `3.3.1` -> `3.4.0` | ||||||
|  |     * `UUID`: `0.5.0` -> `0.6.0` | ||||||
|  |  | ||||||
|  | ## 0.14.0 | ||||||
|  |  | ||||||
|  | **ALL DEPRECATIONS HAVE BEEN REMOVED** | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Kotlin`: `1.7.10` -> `1.7.20` | ||||||
|  |     * `Klock`: `3.3.0` -> `3.3.1` | ||||||
|  |     * `Compose`: `1.2.0` -> `1.2.1` | ||||||
|  |     * `Exposed`: `0.39.2` -> `0.40.1` | ||||||
|  |  | ||||||
|  | ## 0.13.2 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Klock`: `3.1.0` -> `3.3.0` | ||||||
|  |     * `Ktor`: `2.1.2` -> `2.1.3` | ||||||
|  |  | ||||||
|  | ## 0.13.1 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Exposed`: | ||||||
|  |       * `AbstractExposedWriteCRUDRepo#createAndInsertId` now is optional and returns nullable value | ||||||
|  |  | ||||||
|  | ## 0.13.0 | ||||||
|  |  | ||||||
|  | **ALL DEPRECATIONS HAVE BEEN REMOVED** | ||||||
|  | **A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED** | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |   * `Exposed`: | ||||||
|  |     * `AbstractExposedWriteCRUDRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `createAndInsertId` | ||||||
|  |       * Old `update` method has been deprecated and not recommended to override anymore in realizations | ||||||
|  |       * Old `insert` method now is `open` instead of `abstract` and can be omitted | ||||||
|  |     * `AbstractExposedKeyValueRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `insertKey` | ||||||
|  |       * Old `update` method has been deprecated and not recommended to override anymore | ||||||
|  |       * Old `insert` method now is `open` instead of `abstract` and can be omitted in realizations | ||||||
|  |  | ||||||
|  | ## 0.12.17 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `JB Compose`: `1.2.0-alpha01-dev774` -> `1.2.0-beta02` | ||||||
|  |     * `Ktor`: `2.1.1` -> `2.1.2` | ||||||
|  |     * `Koin`: `3.2.0` -> `3.2.2` | ||||||
|  |  | ||||||
|  | ## 0.12.16 | ||||||
|  |  | ||||||
|  | * `Coroutines`: | ||||||
|  |   * `Android`: | ||||||
|  |     * Add class `FlowOnHierarchyChangeListener` | ||||||
|  |     * Add `ViewGroup#setOnHierarchyChangeListenerRecursively(OnHierarchyChangeListener)` | ||||||
|  |  | ||||||
|  | ## 0.12.15 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * `applyDiff` will return `Diff` object since this release | ||||||
|  |     * `Android`: | ||||||
|  |       * New functions/extensions `findViewsByTag` and `findViewsByTagInActivity` | ||||||
|  | * `Coroutines`: | ||||||
|  |     * Add `Flow` extensions `flatMap`, `flatMapNotNull` and `flatten` | ||||||
|  |     * Add `Flow` extensions `takeNotNull` and `filterNotNull` | ||||||
|  |  | ||||||
|  | ## 0.12.14 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |   * `Android CoreKTX`: `1.8.0` -> `1.9.0` | ||||||
|  |   * `Android AppCompat`: `1.4.2` -> `1.5.1` | ||||||
|  |   * Android Compile SDK: 32 -> 33 | ||||||
|  |   * Android Build Tools: 32.0.0 -> 33.0.0 | ||||||
|  | * `Common`: | ||||||
|  |   * `Android`: | ||||||
|  |     * Add `argumentOrNull`/`argumentOrThrow` delegates for fragments | ||||||
|  | * `Coroutines`: | ||||||
|  |   * Rewrite `awaitFirstWithDeferred` onto `CompletableDeferred` instead of coroutines suspending | ||||||
|  |  | ||||||
|  | ## 0.12.13 | ||||||
|  |  | ||||||
|  | * `Coroutines`: | ||||||
|  |   * Add opportunity to use markers in actors (solution of [#160](https://github.com/InsanusMokrassar/MicroUtils/issues/160)) | ||||||
|  | * `Koin`: | ||||||
|  |   * Module inited :) | ||||||
|  | * `Repos`: | ||||||
|  |   * `Android`: | ||||||
|  |     * Add typealias `KeyValueSPRepo` and opportunity to create shared preferences `KeyValue` repo with `KeyValueStore(...)` (fix of [#155](https://github.com/InsanusMokrassar/MicroUtils/issues/155)) | ||||||
|  |  | ||||||
|  | ## 0.12.12 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |   * `Compose`: | ||||||
|  |     * `JS`: | ||||||
|  |       * Add `SkeletonAnimation` stylesheet | ||||||
|  |  | ||||||
|  | ## 0.12.11 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Cache`: | ||||||
|  |         * Override `KeyValue` cache method `values` | ||||||
|  |  | ||||||
| ## 0.12.10 | ## 0.12.10 | ||||||
|  |  | ||||||
| * `Repos`: | * `Repos`: | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ kotlin { | |||||||
|         androidMain { |         androidMain { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 api project(":micro_utils.coroutines") |                 api project(":micro_utils.coroutines") | ||||||
|  |                 api libs.android.fragment | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package dev.inmo.micro_utils.common.compose | ||||||
|  |  | ||||||
|  | import org.jetbrains.compose.web.css.* | ||||||
|  |  | ||||||
|  | object SkeletonAnimation : StyleSheet() { | ||||||
|  |     val skeletonKeyFrames: CSSNamedKeyframes by keyframes { | ||||||
|  |         to { backgroundPosition("-20% 0") } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun CSSBuilder.includeSkeletonStyle( | ||||||
|  |         duration: CSSSizeValue<out CSSUnitTime> = 2.s, | ||||||
|  |         timingFunction: AnimationTimingFunction = AnimationTimingFunction.EaseInOut, | ||||||
|  |         iterationCount: Int? = null, | ||||||
|  |         direction: AnimationDirection = AnimationDirection.Normal, | ||||||
|  |         keyFrames: CSSNamedKeyframes = skeletonKeyFrames, | ||||||
|  |         hideChildren: Boolean = true, | ||||||
|  |         hideText: Boolean = hideChildren | ||||||
|  |     ) { | ||||||
|  |         backgroundImage("linear-gradient(110deg, rgb(236, 236, 236) 40%, rgb(245, 245, 245) 50%, rgb(236, 236, 236) 65%)") | ||||||
|  |         backgroundSize("200% 100%") | ||||||
|  |         backgroundPosition("180% 0") | ||||||
|  |         animation(keyFrames) { | ||||||
|  |             duration(duration) | ||||||
|  |             timingFunction(timingFunction) | ||||||
|  |             iterationCount(iterationCount) | ||||||
|  |             direction(direction) | ||||||
|  |         } | ||||||
|  |         if (hideText) { | ||||||
|  |             property("color", "${Color.transparent} !important") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (hideChildren) { | ||||||
|  |             child(self, universal) style { | ||||||
|  |                 property("visibility", "hidden") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val skeleton by style { | ||||||
|  |         includeSkeletonStyle() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -14,6 +14,14 @@ private inline fun <T> getObject( | |||||||
| /** | /** | ||||||
|  * Diff object which contains information about differences between two [Iterable]s |  * Diff object which contains information about differences between two [Iterable]s | ||||||
|  * |  * | ||||||
|  |  * See tests for more info | ||||||
|  |  * | ||||||
|  |  * @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection | ||||||
|  |  * @param added The object which appear in new collection only. Indexes here show the index in the new collection | ||||||
|  |  * @param replaced Pair of old-new changes. First object has been presented in the old collection on its | ||||||
|  |  * [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in | ||||||
|  |  * case when some value has been replaced after adds or removes in original collection the object index will be changed | ||||||
|  |  * | ||||||
|  * @see calculateDiff |  * @see calculateDiff | ||||||
|  */ |  */ | ||||||
| data class Diff<T> internal constructor( | data class Diff<T> internal constructor( | ||||||
| @@ -165,7 +173,7 @@ inline fun <T> Iterable<T>.calculateStrictDiff( | |||||||
| fun <T> MutableList<T>.applyDiff( | fun <T> MutableList<T>.applyDiff( | ||||||
|     source: Iterable<T>, |     source: Iterable<T>, | ||||||
|     strictComparison: Boolean = false |     strictComparison: Boolean = false | ||||||
| ) = calculateDiff(source, strictComparison).let { | ): Diff<T> = calculateDiff(source, strictComparison).also { | ||||||
|     for (i in it.removed.indices.sortedDescending()) { |     for (i in it.removed.indices.sortedDescending()) { | ||||||
|         removeAt(it.removed[i].index) |         removeAt(it.removed[i].index) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | 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() | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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 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,20 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import java.io.File | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.util.UUID | ||||||
|  |  | ||||||
|  | fun InputStream.downloadToTempFile( | ||||||
|  |     fileName: String = UUID.randomUUID().toString(), | ||||||
|  |     fileExtension: String? = ".temp", | ||||||
|  |     folder: File? = null | ||||||
|  | ) = File.createTempFile( | ||||||
|  |     fileName, | ||||||
|  |     fileExtension, | ||||||
|  |     folder | ||||||
|  | ).apply { | ||||||
|  |     outputStream().use { | ||||||
|  |         copyTo(it) | ||||||
|  |     } | ||||||
|  |     deleteOnExit() | ||||||
|  | } | ||||||
| @@ -0,0 +1,76 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import android.os.Bundle | ||||||
|  | import android.os.Parcelable | ||||||
|  | import androidx.fragment.app.Fragment | ||||||
|  | import java.io.Serializable | ||||||
|  | import kotlin.reflect.KProperty | ||||||
|  |  | ||||||
|  | object ArgumentPropertyNullableDelegate { | ||||||
|  |     operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T? { | ||||||
|  |         val arguments = thisRef.arguments ?: return null | ||||||
|  |         val key = property.name | ||||||
|  |         return when (property.getter.returnType.classifier) { | ||||||
|  |             // Scalars | ||||||
|  |             String::class -> arguments.getString(key) | ||||||
|  |             Boolean::class -> arguments.getBoolean(key) | ||||||
|  |             Byte::class -> arguments.getByte(key) | ||||||
|  |             Char::class -> arguments.getChar(key) | ||||||
|  |             Double::class -> arguments.getDouble(key) | ||||||
|  |             Float::class -> arguments.getFloat(key) | ||||||
|  |             Int::class -> arguments.getInt(key) | ||||||
|  |             Long::class -> arguments.getLong(key) | ||||||
|  |             Short::class -> arguments.getShort(key) | ||||||
|  |  | ||||||
|  |             // References | ||||||
|  |             Bundle::class -> arguments.getBundle(key) | ||||||
|  |             CharSequence::class -> arguments.getCharSequence(key) | ||||||
|  |             Parcelable::class -> arguments.getParcelable(key) | ||||||
|  |  | ||||||
|  |             // Scalar arrays | ||||||
|  |             BooleanArray::class -> arguments.getBooleanArray(key) | ||||||
|  |             ByteArray::class -> arguments.getByteArray(key) | ||||||
|  |             CharArray::class -> arguments.getCharArray(key) | ||||||
|  |             DoubleArray::class -> arguments.getDoubleArray(key) | ||||||
|  |             FloatArray::class -> arguments.getFloatArray(key) | ||||||
|  |             IntArray::class -> arguments.getIntArray(key) | ||||||
|  |             LongArray::class -> arguments.getLongArray(key) | ||||||
|  |             ShortArray::class -> arguments.getShortArray(key) | ||||||
|  |             Array::class -> { | ||||||
|  |                 val componentType = property.returnType.classifier ?.javaClass ?.componentType!! | ||||||
|  |                 @Suppress("UNCHECKED_CAST") // Checked by reflection. | ||||||
|  |                 when { | ||||||
|  |                     Parcelable::class.java.isAssignableFrom(componentType) -> { | ||||||
|  |                         arguments.getParcelableArray(key) | ||||||
|  |                     } | ||||||
|  |                     String::class.java.isAssignableFrom(componentType) -> { | ||||||
|  |                         arguments.getStringArray(key) | ||||||
|  |                     } | ||||||
|  |                     CharSequence::class.java.isAssignableFrom(componentType) -> { | ||||||
|  |                         arguments.getCharSequenceArray(key) | ||||||
|  |                     } | ||||||
|  |                     Serializable::class.java.isAssignableFrom(componentType) -> { | ||||||
|  |                         arguments.getSerializable(key) | ||||||
|  |                     } | ||||||
|  |                     else -> { | ||||||
|  |                         val valueType = componentType.canonicalName | ||||||
|  |                         throw IllegalArgumentException( | ||||||
|  |                             "Illegal value array type $valueType for key \"$key\"" | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Serializable::class -> arguments.getSerializable(key) | ||||||
|  |             else -> null | ||||||
|  |         } as? T | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | object ArgumentPropertyNonNullableDelegate { | ||||||
|  |     operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T { | ||||||
|  |         return ArgumentPropertyNullableDelegate.getValue<T>(thisRef, property)!! | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun argumentOrNull() = ArgumentPropertyNullableDelegate | ||||||
|  | fun argumentOrThrow() = ArgumentPropertyNonNullableDelegate | ||||||
| @@ -0,0 +1,61 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import android.app.Activity | ||||||
|  | import android.view.View | ||||||
|  | import android.view.ViewGroup | ||||||
|  | import androidx.core.view.children | ||||||
|  | import androidx.fragment.app.Fragment | ||||||
|  |  | ||||||
|  | fun findViewsByTag(viewGroup: ViewGroup, tag: Any?): List<View> { | ||||||
|  |     return viewGroup.children.flatMap { | ||||||
|  |         findViewsByTag(it, tag) | ||||||
|  |     }.toList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun findViewsByTag(viewGroup: ViewGroup, key: Int, tag: Any?): List<View> { | ||||||
|  |     return viewGroup.children.flatMap { | ||||||
|  |         findViewsByTag(it, key, tag) | ||||||
|  |     }.toList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun findViewsByTag(view: View, tag: Any?): List<View> { | ||||||
|  |     val result = mutableListOf<View>() | ||||||
|  |     if (view.tag == tag) { | ||||||
|  |         result.add(view) | ||||||
|  |     } | ||||||
|  |     if (view is ViewGroup) { | ||||||
|  |         result.addAll(findViewsByTag(view, tag)) | ||||||
|  |     } | ||||||
|  |     return result.toList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun findViewsByTag(view: View, key: Int, tag: Any?): List<View> { | ||||||
|  |     val result = mutableListOf<View>() | ||||||
|  |     if (view.getTag(key) == tag) { | ||||||
|  |         result.add(view) | ||||||
|  |     } | ||||||
|  |     if (view is ViewGroup) { | ||||||
|  |         result.addAll(findViewsByTag(view, key, tag)) | ||||||
|  |     } | ||||||
|  |     return result.toList() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Activity.findViewsByTag(tag: Any?) = rootView ?.let { | ||||||
|  |     findViewsByTag(it, tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Activity.findViewsByTag(key: Int, tag: Any?) = rootView ?.let { | ||||||
|  |     findViewsByTag(it, key, tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Fragment.findViewsByTag(tag: Any?) = view ?.let { | ||||||
|  |     findViewsByTag(it, tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Fragment.findViewsByTag(key: Int, tag: Any?) = view ?.let { | ||||||
|  |     findViewsByTag(it, key, tag) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun Fragment.findViewsByTagInActivity(tag: Any?) = activity ?.findViewsByTag(tag) | ||||||
|  |  | ||||||
|  | fun Fragment.findViewsByTagInActivity(key: Int, tag: Any?) = activity ?.findViewsByTag(key, tag) | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import android.app.Activity | ||||||
|  | import android.view.View | ||||||
|  |  | ||||||
|  | val Activity.rootView: View? | ||||||
|  |     get() = findViewById<View?>(android.R.id.content) ?.rootView ?: window.decorView.findViewById<View?>(android.R.id.content) ?.rootView | ||||||
| @@ -1,19 +1,15 @@ | |||||||
| package dev.inmo.micro_utils.coroutines | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.* | ||||||
| import kotlinx.coroutines.channels.Channel | import kotlinx.coroutines.channels.Channel | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.flow.consumeAsFlow | ||||||
|  |  | ||||||
| fun <T> CoroutineScope.actor( | fun <T> CoroutineScope.actor( | ||||||
|     channelCapacity: Int = Channel.UNLIMITED, |     channelCapacity: Int = Channel.UNLIMITED, | ||||||
|     block: suspend (T) -> Unit |     block: suspend (T) -> Unit | ||||||
| ): Channel<T> { | ): Channel<T> { | ||||||
|     val channel = Channel<T>(channelCapacity) |     val channel = Channel<T>(channelCapacity) | ||||||
|     launch { |     channel.consumeAsFlow().subscribe(this, block) | ||||||
|         for (data in channel) { |  | ||||||
|             block(data) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return channel |     return channel | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.* | ||||||
|  | import kotlinx.coroutines.channels.Channel | ||||||
|  | import kotlinx.coroutines.flow.consumeAsFlow | ||||||
|  |  | ||||||
|  | fun <T> CoroutineScope.actorAsync( | ||||||
|  |     channelCapacity: Int = Channel.UNLIMITED, | ||||||
|  |     markerFactory: suspend (T) -> Any? = { null }, | ||||||
|  |     block: suspend (T) -> Unit | ||||||
|  | ): Channel<T> { | ||||||
|  |     val channel = Channel<T>(channelCapacity) | ||||||
|  |     channel.consumeAsFlow().subscribeAsync(this, markerFactory, block) | ||||||
|  |     return channel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun <T> CoroutineScope.safeActorAsync( | ||||||
|  |     channelCapacity: Int = Channel.UNLIMITED, | ||||||
|  |     noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, | ||||||
|  |     noinline markerFactory: suspend (T) -> Any? = { null }, | ||||||
|  |     crossinline block: suspend (T) -> Unit | ||||||
|  | ): Channel<T> = actorAsync( | ||||||
|  |     channelCapacity, | ||||||
|  |     markerFactory | ||||||
|  | ) { | ||||||
|  |     safely(onException) { | ||||||
|  |         block(it) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -6,23 +6,19 @@ import kotlin.coroutines.* | |||||||
| suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred( | suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred( | ||||||
|     scope: CoroutineScope, |     scope: CoroutineScope, | ||||||
|     cancelOnResult: Boolean = true |     cancelOnResult: Boolean = true | ||||||
| ): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation -> | ): Pair<Deferred<T>, T> { | ||||||
|     scope.launch(SupervisorJob()) { |     val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>() | ||||||
|         val scope = this |     val scope = scope.LinkedSupervisorScope() | ||||||
|         forEach { |     forEach { | ||||||
|             scope.launch { |         scope.launch { | ||||||
|                 continuation.resume(it to it.await()) |             resultDeferred.complete(it to it.await()) | ||||||
|                 scope.cancel() |             scope.cancel() | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| }.also { |     return resultDeferred.await().also { | ||||||
|     if (cancelOnResult) { |         if (cancelOnResult) { | ||||||
|         forEach { |             forEach { | ||||||
|             try { |                 runCatchingSafely { it.cancel() } | ||||||
|                 it.cancel() |  | ||||||
|             } catch (e: IllegalStateException) { |  | ||||||
|                 e.printStackTrace() |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.flow.* | ||||||
|  | import kotlin.js.JsName | ||||||
|  | import kotlin.jvm.JvmName | ||||||
|  |  | ||||||
|  | inline fun <T, R> Flow<Flow<T>>.flatMap( | ||||||
|  |     crossinline mapper: suspend (T) -> R | ||||||
|  | ) = flow { | ||||||
|  |     collect { | ||||||
|  |         it.collect { | ||||||
|  |             emit(mapper(it)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @JsName("flatMapIterable") | ||||||
|  | @JvmName("flatMapIterable") | ||||||
|  | inline fun <T, R> Flow<Iterable<T>>.flatMap( | ||||||
|  |     crossinline mapper: suspend (T) -> R | ||||||
|  | ) = map { | ||||||
|  |     it.asFlow() | ||||||
|  | }.flatMap(mapper) | ||||||
|  |  | ||||||
|  | inline fun <T, R> Flow<Flow<T>>.flatMapNotNull( | ||||||
|  |     crossinline mapper: suspend (T) -> R | ||||||
|  | ) = flatMap(mapper).takeNotNull() | ||||||
|  |  | ||||||
|  | @JsName("flatMapNotNullIterable") | ||||||
|  | @JvmName("flatMapNotNullIterable") | ||||||
|  | inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull( | ||||||
|  |     crossinline mapper: suspend (T) -> R | ||||||
|  | ) = flatMap(mapper).takeNotNull() | ||||||
|  |  | ||||||
|  | fun <T> Flow<Flow<T>>.flatten() = flatMap { it } | ||||||
|  |  | ||||||
|  | @JsName("flattenIterable") | ||||||
|  | @JvmName("flattenIterable") | ||||||
|  | fun <T> Flow<Iterable<T>>.flatten() = flatMap { it } | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.flow.* | ||||||
|  |  | ||||||
|  | fun <T> Flow<T>.takeNotNull() = mapNotNull { it } | ||||||
|  | fun <T> Flow<T>.filterNotNull() = takeNotNull() | ||||||
							
								
								
									
										50
									
								
								coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import android.view.View | ||||||
|  | import android.view.ViewGroup | ||||||
|  | import kotlinx.coroutines.flow.MutableSharedFlow | ||||||
|  | import kotlinx.coroutines.flow.asSharedFlow | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * [kotlinx.coroutines.flow.Flow]-based [android.view.ViewGroup.OnHierarchyChangeListener] | ||||||
|  |  * | ||||||
|  |  * @param recursive If set, any call of [onChildViewAdded] will check if child [View] is [ViewGroup] and subscribe to this | ||||||
|  |  * [ViewGroup] too | ||||||
|  |  * @param [_onChildViewAdded] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewAdded] flow | ||||||
|  |  * @param [_onChildViewRemoved] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewRemoved] flow | ||||||
|  |  */ | ||||||
|  | class FlowOnHierarchyChangeListener( | ||||||
|  |     private val recursive: Boolean = false, | ||||||
|  |     private val _onChildViewAdded: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE), | ||||||
|  |     private val _onChildViewRemoved: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE) | ||||||
|  | ) : ViewGroup.OnHierarchyChangeListener { | ||||||
|  |     val onChildViewAdded = _onChildViewAdded.asSharedFlow() | ||||||
|  |     val onChildViewRemoved = _onChildViewRemoved.asSharedFlow() | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Will emit data into [onChildViewAdded] flow. If [recursive] is true and [child] is [ViewGroup] will also | ||||||
|  |      * subscribe to [child] hierarchy changes. | ||||||
|  |      * | ||||||
|  |      * Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use | ||||||
|  |      * [MutableSharedFlow.tryEmit] to send data into [_onChildViewAdded]. That is why its default extraBufferCapacity is | ||||||
|  |      * [Int.MAX_VALUE] | ||||||
|  |      */ | ||||||
|  |     override fun onChildViewAdded(parent: View, child: View) { | ||||||
|  |         _onChildViewAdded.tryEmit(parent to child) | ||||||
|  |  | ||||||
|  |         if (recursive && child is ViewGroup) { | ||||||
|  |             child.setOnHierarchyChangeListener(this) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Just emit data into [onChildViewRemoved] | ||||||
|  |      * | ||||||
|  |      * Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use | ||||||
|  |      * [MutableSharedFlow.tryEmit] to send data into [_onChildViewRemoved]. That is why its default extraBufferCapacity is | ||||||
|  |      * [Int.MAX_VALUE] | ||||||
|  |      */ | ||||||
|  |     override fun onChildViewRemoved(parent: View, child: View) { | ||||||
|  |         _onChildViewRemoved.tryEmit(parent to child) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import android.view.ViewGroup | ||||||
|  | import android.view.ViewGroup.OnHierarchyChangeListener | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Use [ViewGroup.setOnHierarchyChangeListener] recursively for all available [ViewGroup]s starting with [this]. | ||||||
|  |  * This extension DO NOT guarantee that recursive subscription will happen after this method call | ||||||
|  |  */ | ||||||
|  | fun ViewGroup.setOnHierarchyChangeListenerRecursively( | ||||||
|  |     listener: OnHierarchyChangeListener | ||||||
|  | ) { | ||||||
|  |     setOnHierarchyChangeListener(listener) | ||||||
|  |     (0 until childCount).forEach { | ||||||
|  |         (getChildAt(it) as? ViewGroup) ?.setOnHierarchyChangeListenerRecursively(listener) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -121,11 +121,11 @@ tasks.dokkaHtml { | |||||||
| //        } | //        } | ||||||
|  |  | ||||||
|         named("jvmMain") { |         named("jvmMain") { | ||||||
|             sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain")) |             sourceRoots.setFrom(findSourcesWithName("jvmMain")) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         named("androidMain") { |         named("androidMain") { | ||||||
|             sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain")) |             sourceRoots.setFrom(findSourcesWithName("androidMain")) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,5 +14,5 @@ crypto_js_version=4.1.1 | |||||||
| # Project data | # Project data | ||||||
|  |  | ||||||
| group=dev.inmo | group=dev.inmo | ||||||
| version=0.12.10 | version=0.15.1 | ||||||
| android_code_version=149 | android_code_version=167 | ||||||
|   | |||||||
| @@ -1,36 +1,42 @@ | |||||||
| [versions] | [versions] | ||||||
|  |  | ||||||
| kt = "1.7.10" | kt = "1.7.20" | ||||||
| kt-serialization = "1.4.0" | kt-serialization = "1.4.1" | ||||||
| kt-coroutines = "1.6.4" | kt-coroutines = "1.6.4" | ||||||
|  |  | ||||||
| jb-compose = "1.2.0-alpha01-dev774" | kslog = "0.5.4" | ||||||
| jb-exposed = "0.39.2" |  | ||||||
| jb-dokka = "1.7.10" |  | ||||||
|  |  | ||||||
| klock = "3.1.0" | jb-compose = "1.2.1" | ||||||
| uuid = "0.5.0" | jb-exposed = "0.41.1" | ||||||
|  | jb-dokka = "1.7.20" | ||||||
|  |  | ||||||
| ktor = "2.1.1" | klock = "3.4.0" | ||||||
|  | uuid = "0.6.0" | ||||||
|  |  | ||||||
|  | ktor = "2.1.3" | ||||||
|  |  | ||||||
| gh-release = "2.4.1" | gh-release = "2.4.1" | ||||||
|  |  | ||||||
|  | koin = "3.2.2" | ||||||
|  |  | ||||||
| android-gradle = "7.2.2" | android-gradle = "7.2.2" | ||||||
| dexcount = "3.1.0" | dexcount = "3.1.0" | ||||||
|  |  | ||||||
| android-coreKtx = "1.8.0" | android-coreKtx = "1.9.0" | ||||||
| android-recyclerView = "1.2.1" | android-recyclerView = "1.2.1" | ||||||
| android-appCompat = "1.4.2" | android-appCompat = "1.5.1" | ||||||
|  | android-fragment = "1.5.3" | ||||||
| android-espresso = "3.4.0" | android-espresso = "3.4.0" | ||||||
| android-test = "1.1.3" | android-test = "1.1.3" | ||||||
|  |  | ||||||
| android-props-minSdk = "21" | android-props-minSdk = "21" | ||||||
| android-props-compileSdk = "32" | android-props-compileSdk = "33" | ||||||
| android-props-buildTools = "32.0.0" | android-props-buildTools = "33.0.0" | ||||||
|  |  | ||||||
| [libraries] | [libraries] | ||||||
|  |  | ||||||
| kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" } | kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" } | ||||||
|  | kt-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kt" } | ||||||
|  |  | ||||||
| kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" } | kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" } | ||||||
| kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" } | kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" } | ||||||
| @@ -56,9 +62,11 @@ ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.re | |||||||
| ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" } | ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" } | ||||||
| ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } | ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } | ||||||
|  |  | ||||||
|  | kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } | ||||||
|  |  | ||||||
| klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" } | klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" } | ||||||
| uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | ||||||
|  | koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | ||||||
|  |  | ||||||
|  |  | ||||||
| jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" } | jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" } | ||||||
| @@ -67,6 +75,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb- | |||||||
| android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" } | android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" } | ||||||
| android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" } | android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" } | ||||||
| android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" } | android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" } | ||||||
|  | android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" } | ||||||
| android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" } | android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" } | ||||||
| android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" } | android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								koin/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								koin/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | plugins { | ||||||
|  |     id "org.jetbrains.kotlin.multiplatform" | ||||||
|  |     id "org.jetbrains.kotlin.plugin.serialization" | ||||||
|  |     id "com.android.library" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
|  | kotlin { | ||||||
|  |     sourceSets { | ||||||
|  |         commonMain { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.koin | ||||||
|  |                 api libs.uuid | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         jvmMain { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.kt.reflect | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         androidMain { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.kt.reflect | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.module.Module | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will be useful in case you need to declare some singles with one type several types, but need to separate them and do | ||||||
|  |  * not care about how :) | ||||||
|  |  */ | ||||||
|  | inline fun <reified T : Any> Module.factoryWithRandomQualifier( | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ) = factory(RandomQualifier(), definition) | ||||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.qualifier.StringQualifier | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.factory( | ||||||
|  |     qualifier: String, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ) = factory(StringQualifier(qualifier), definition) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								koin/src/commonMain/kotlin/GetAllDistinct.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								koin/src/commonMain/kotlin/GetAllDistinct.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.Koin | ||||||
|  | import org.koin.core.scope.Scope | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Scope.getAllDistinct() = getAll<T>().distinct() | ||||||
|  | inline fun <reified T : Any> Koin.getAllDistinct() = getAll<T>().distinct() | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								koin/src/commonMain/kotlin/GetAny.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								koin/src/commonMain/kotlin/GetAny.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.Koin | ||||||
|  | import org.koin.core.scope.Scope | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Scope.getAny() = getAll<T>().first() | ||||||
|  | inline fun <reified T : Any> Koin.getAny() = getAll<T>().first() | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								koin/src/commonMain/kotlin/RandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								koin/src/commonMain/kotlin/RandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import com.benasher44.uuid.uuid4 | ||||||
|  | import org.koin.core.qualifier.StringQualifier | ||||||
|  |  | ||||||
|  | fun RandomQualifier(randomFun: () -> String = { uuid4().toString() }) = StringQualifier(randomFun()) | ||||||
							
								
								
									
										13
									
								
								koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.module.Module | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will be useful in case you need to declare some singles with one type several types, but need to separate them and do | ||||||
|  |  * not care about how :) | ||||||
|  |  */ | ||||||
|  | inline fun <reified T : Any> Module.singleWithRandomQualifier( | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ) = single(RandomQualifier(), createdAtStart, definition) | ||||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.qualifier.StringQualifier | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.single( | ||||||
|  |     qualifier: String, | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ) = single(StringQualifier(qualifier), createdAtStart, definition) | ||||||
|  |  | ||||||
							
								
								
									
										27
									
								
								koin/src/jvmMain/kotlin/FactoryWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								koin/src/jvmMain/kotlin/FactoryWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.instance.InstanceFactory | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.qualifier.Qualifier | ||||||
|  | import org.koin.dsl.binds | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  | import kotlin.reflect.full.allSuperclasses | ||||||
|  |  | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.factoryWithBinds( | ||||||
|  |     qualifier: Qualifier? = null, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.factoryWithBinds( | ||||||
|  |     qualifier: String, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
|  | } | ||||||
|  |  | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.instance.InstanceFactory | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds( | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return factoryWithBinds(RandomQualifier(), bindFilter, definition) | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								koin/src/jvmMain/kotlin/SignleWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								koin/src/jvmMain/kotlin/SignleWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.instance.InstanceFactory | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.qualifier.Qualifier | ||||||
|  | import org.koin.dsl.binds | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  | import kotlin.reflect.full.allSuperclasses | ||||||
|  |  | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.singleWithBinds( | ||||||
|  |     qualifier: Qualifier? = null, | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.singleWithBinds( | ||||||
|  |     qualifier: String, | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.instance.InstanceFactory | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  |  | ||||||
|  | inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds( | ||||||
|  |     createdAtStart: Boolean = false, | ||||||
|  |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|  |     noinline definition: Definition<T> | ||||||
|  | ): Pair<Module, InstanceFactory<*>> { | ||||||
|  |     return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition) | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								koin/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								koin/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.koin"/> | ||||||
| @@ -15,5 +15,9 @@ kotlin { | |||||||
|                 api libs.ktor.client |                 api libs.ktor.client | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         androidMain { | ||||||
|  |             dependsOn jvmMain | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,82 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.ktor.client |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.coroutines.runCatchingSafely |  | ||||||
| import dev.inmo.micro_utils.coroutines.safely |  | ||||||
| import dev.inmo.micro_utils.ktor.common.* |  | ||||||
| import io.ktor.client.HttpClient |  | ||||||
| import io.ktor.client.plugins.pluginOrNull |  | ||||||
| import io.ktor.client.plugins.websocket.WebSockets |  | ||||||
| import io.ktor.client.plugins.websocket.ws |  | ||||||
| import io.ktor.client.request.HttpRequestBuilder |  | ||||||
| import io.ktor.websocket.Frame |  | ||||||
| import io.ktor.websocket.readBytes |  | ||||||
| import kotlinx.coroutines.flow.Flow |  | ||||||
| import kotlinx.coroutines.flow.channelFlow |  | ||||||
| import kotlinx.coroutines.isActive |  | ||||||
| import kotlinx.serialization.DeserializationStrategy |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish |  | ||||||
|  * connection. Must return true in case if must be reconnected. By default always reconnecting |  | ||||||
|  */ |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| inline fun <T> HttpClient.createStandardWebsocketFlow( |  | ||||||
|     url: String, |  | ||||||
|     crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, |  | ||||||
|     noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
|     crossinline conversation: suspend (StandardKtorSerialInputData) -> T |  | ||||||
| ): Flow<T> { |  | ||||||
|     pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow") |  | ||||||
|  |  | ||||||
|     val correctedUrl = url.asCorrectWebSocketUrl |  | ||||||
|  |  | ||||||
|     return channelFlow { |  | ||||||
|         do { |  | ||||||
|             val reconnect = runCatchingSafely { |  | ||||||
|                 ws(correctedUrl, requestBuilder) { |  | ||||||
|                     for (received in incoming) { |  | ||||||
|                         when (received) { |  | ||||||
|                             is Frame.Binary -> send(conversation(received.data)) |  | ||||||
|                             else -> { |  | ||||||
|                                 close() |  | ||||||
|                                 return@ws |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 checkReconnection(null) |  | ||||||
|             }.getOrElse { e -> |  | ||||||
|                 checkReconnection(e).also { |  | ||||||
|                     if (!it) { |  | ||||||
|                         close(e) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } while (reconnect && isActive) |  | ||||||
|  |  | ||||||
|         if (isActive) { |  | ||||||
|             safely { |  | ||||||
|                 close() |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish |  | ||||||
|  * connection. Must return true in case if must be reconnected. By default always reconnecting |  | ||||||
|  */ |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| inline fun <T> HttpClient.createStandardWebsocketFlow( |  | ||||||
|     url: String, |  | ||||||
|     deserializer: DeserializationStrategy<T>, |  | ||||||
|     crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, |  | ||||||
|     noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
| ) = createStandardWebsocketFlow( |  | ||||||
|     url, |  | ||||||
|     checkReconnection, |  | ||||||
|     requestBuilder |  | ||||||
| ) { |  | ||||||
|     serialFormat.decodeDefault(deserializer, it) |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.client | ||||||
|  |  | ||||||
|  | typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit | ||||||
| @@ -1,260 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.ktor.client |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.common.MPPFile |  | ||||||
| import dev.inmo.micro_utils.common.filename |  | ||||||
| import dev.inmo.micro_utils.ktor.common.* |  | ||||||
| import io.ktor.client.HttpClient |  | ||||||
| import io.ktor.client.call.body |  | ||||||
| import io.ktor.client.request.* |  | ||||||
| import io.ktor.client.request.forms.* |  | ||||||
| import io.ktor.client.statement.readBytes |  | ||||||
| import io.ktor.http.* |  | ||||||
| import io.ktor.utils.io.core.ByteReadPacket |  | ||||||
| import kotlinx.serialization.* |  | ||||||
|  |  | ||||||
| @Deprecated("This class will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| class UnifiedRequester( |  | ||||||
|     val client: HttpClient = HttpClient(), |  | ||||||
|     val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat |  | ||||||
| ) { |  | ||||||
|     suspend fun <ResultType> uniget( |  | ||||||
|         url: String, |  | ||||||
|         resultDeserializer: DeserializationStrategy<ResultType> |  | ||||||
|     ): ResultType = client.uniget(url, resultDeserializer, serialFormat) |  | ||||||
|  |  | ||||||
|     fun <T> encodeUrlQueryValue( |  | ||||||
|         serializationStrategy: SerializationStrategy<T>, |  | ||||||
|         value: T |  | ||||||
|     ) = serializationStrategy.encodeUrlQueryValue( |  | ||||||
|         value, |  | ||||||
|         serialFormat |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     suspend fun <BodyType, ResultType> unipost( |  | ||||||
|         url: String, |  | ||||||
|         bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>, |  | ||||||
|         resultDeserializer: DeserializationStrategy<ResultType> |  | ||||||
|     ) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat) |  | ||||||
|  |  | ||||||
|     suspend fun <ResultType> unimultipart( |  | ||||||
|         url: String, |  | ||||||
|         filename: String, |  | ||||||
|         inputProvider: InputProvider, |  | ||||||
|         resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|         mimetype: String = "*/*", |  | ||||||
|         additionalParametersBuilder: FormBuilder.() -> Unit = {}, |  | ||||||
|         dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, |  | ||||||
|         requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
|     ): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat) |  | ||||||
|  |  | ||||||
|     suspend fun <BodyType, ResultType> unimultipart( |  | ||||||
|         url: String, |  | ||||||
|         filename: String, |  | ||||||
|         inputProvider: InputProvider, |  | ||||||
|         otherData: Pair<SerializationStrategy<BodyType>, BodyType>, |  | ||||||
|         resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|         mimetype: String = "*/*", |  | ||||||
|         additionalParametersBuilder: FormBuilder.() -> Unit = {}, |  | ||||||
|         dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, |  | ||||||
|         requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
|     ): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat) |  | ||||||
|  |  | ||||||
|     suspend fun <ResultType> unimultipart( |  | ||||||
|         url: String, |  | ||||||
|         mppFile: MPPFile, |  | ||||||
|         resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|         mimetype: String = "*/*", |  | ||||||
|         additionalParametersBuilder: FormBuilder.() -> Unit = {}, |  | ||||||
|         dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, |  | ||||||
|         requestBuilder: HttpRequestBuilder.() -> Unit = {} |  | ||||||
|     ): ResultType = client.unimultipart( |  | ||||||
|         url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     suspend fun <BodyType, ResultType> unimultipart( |  | ||||||
|         url: String, |  | ||||||
|         mppFile: MPPFile, |  | ||||||
|         otherData: Pair<SerializationStrategy<BodyType>, BodyType>, |  | ||||||
|         resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|         mimetype: String = "*/*", |  | ||||||
|         additionalParametersBuilder: FormBuilder.() -> Unit = {}, |  | ||||||
|         dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, |  | ||||||
|         requestBuilder: HttpRequestBuilder.() -> Unit = {} |  | ||||||
|     ): ResultType = client.unimultipart( |  | ||||||
|         url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     fun <T> createStandardWebsocketFlow( |  | ||||||
|         url: String, |  | ||||||
|         checkReconnection: suspend (Throwable?) -> Boolean, |  | ||||||
|         deserializer: DeserializationStrategy<T>, |  | ||||||
|         requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
|     ) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder) |  | ||||||
|  |  | ||||||
|     fun <T> createStandardWebsocketFlow( |  | ||||||
|         url: String, |  | ||||||
|         deserializer: DeserializationStrategy<T>, |  | ||||||
|         requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
|     ) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Deprecated("This property will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| val defaultRequester = UnifiedRequester() |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <ResultType> HttpClient.uniget( |  | ||||||
|     url: String, |  | ||||||
|     resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat |  | ||||||
| ) = get(url).let { |  | ||||||
|     serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| fun <T> SerializationStrategy<T>.encodeUrlQueryValue( |  | ||||||
|     value: T, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat |  | ||||||
| ) = serialFormat.encodeHex( |  | ||||||
|     this, |  | ||||||
|     value |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <BodyType, ResultType> HttpClient.unipost( |  | ||||||
|     url: String, |  | ||||||
|     bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>, |  | ||||||
|     resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat |  | ||||||
| ) = post(url) { |  | ||||||
|     setBody( |  | ||||||
|         serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second) |  | ||||||
|     ) |  | ||||||
| }.let { |  | ||||||
|     serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <ResultType> HttpClient.unimultipart( |  | ||||||
|     url: String, |  | ||||||
|     filename: String, |  | ||||||
|     inputProvider: InputProvider, |  | ||||||
|     resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|     mimetype: String = "*/*", |  | ||||||
|     additionalParametersBuilder: FormBuilder.() -> Unit = {}, |  | ||||||
|     dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, |  | ||||||
|     requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat |  | ||||||
| ): ResultType = submitFormWithBinaryData( |  | ||||||
|     url, |  | ||||||
|     formData = formData { |  | ||||||
|         append( |  | ||||||
|             "bytes", |  | ||||||
|             inputProvider, |  | ||||||
|             Headers.build { |  | ||||||
|                 append(HttpHeaders.ContentType, mimetype) |  | ||||||
|                 append(HttpHeaders.ContentDisposition, "filename=\"$filename\"") |  | ||||||
|                 dataHeadersBuilder() |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|         additionalParametersBuilder() |  | ||||||
|     } |  | ||||||
| ) { |  | ||||||
|     requestBuilder() |  | ||||||
| }.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) } |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <BodyType, ResultType> HttpClient.unimultipart( |  | ||||||
|     url: String, |  | ||||||
|     filename: String, |  | ||||||
|     otherData: Pair<SerializationStrategy<BodyType>, BodyType>, |  | ||||||
|     inputProvider: InputProvider, |  | ||||||
|     resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|     mimetype: String = "*/*", |  | ||||||
|     additionalParametersBuilder: FormBuilder.() -> Unit = {}, |  | ||||||
|     dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, |  | ||||||
|     requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat |  | ||||||
| ): ResultType = unimultipart( |  | ||||||
|     url, |  | ||||||
|     filename, |  | ||||||
|     inputProvider, |  | ||||||
|     resultDeserializer, |  | ||||||
|     mimetype, |  | ||||||
|     additionalParametersBuilder = { |  | ||||||
|         val serialized = serialFormat.encodeDefault(otherData.first, otherData.second) |  | ||||||
|         append( |  | ||||||
|             "data", |  | ||||||
|             InputProvider(serialized.size.toLong()) { |  | ||||||
|                 ByteReadPacket(serialized) |  | ||||||
|             }, |  | ||||||
|             Headers.build { |  | ||||||
|                 append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType) |  | ||||||
|                 append(HttpHeaders.ContentDisposition, "filename=data.bytes") |  | ||||||
|                 dataHeadersBuilder() |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|         additionalParametersBuilder() |  | ||||||
|     }, |  | ||||||
|     dataHeadersBuilder, |  | ||||||
|     requestBuilder, |  | ||||||
|     serialFormat |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <ResultType> HttpClient.unimultipart( |  | ||||||
|     url: String, |  | ||||||
|     mppFile: MPPFile, |  | ||||||
|     resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|     mimetype: String = "*/*", |  | ||||||
|     additionalParametersBuilder: FormBuilder.() -> Unit = {}, |  | ||||||
|     dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, |  | ||||||
|     requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat |  | ||||||
| ): ResultType = unimultipart( |  | ||||||
|     url, |  | ||||||
|     mppFile.filename.string, |  | ||||||
|     mppFile.inputProvider(), |  | ||||||
|     resultDeserializer, |  | ||||||
|     mimetype, |  | ||||||
|     additionalParametersBuilder, |  | ||||||
|     dataHeadersBuilder, |  | ||||||
|     requestBuilder, |  | ||||||
|     serialFormat |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <BodyType, ResultType> HttpClient.unimultipart( |  | ||||||
|     url: String, |  | ||||||
|     mppFile: MPPFile, |  | ||||||
|     otherData: Pair<SerializationStrategy<BodyType>, BodyType>, |  | ||||||
|     resultDeserializer: DeserializationStrategy<ResultType>, |  | ||||||
|     mimetype: String = "*/*", |  | ||||||
|     additionalParametersBuilder: FormBuilder.() -> Unit = {}, |  | ||||||
|     dataHeadersBuilder: HeadersBuilder.() -> Unit = {}, |  | ||||||
|     requestBuilder: HttpRequestBuilder.() -> Unit = {}, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat |  | ||||||
| ): ResultType = unimultipart( |  | ||||||
|     url, |  | ||||||
|     mppFile, |  | ||||||
|     resultDeserializer, |  | ||||||
|     mimetype, |  | ||||||
|     additionalParametersBuilder = { |  | ||||||
|         val serialized = serialFormat.encodeDefault(otherData.first, otherData.second) |  | ||||||
|         append( |  | ||||||
|             "data", |  | ||||||
|             InputProvider(serialized.size.toLong()) { |  | ||||||
|                 ByteReadPacket(serialized) |  | ||||||
|             }, |  | ||||||
|             Headers.build { |  | ||||||
|                 append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType) |  | ||||||
|                 append(HttpHeaders.ContentDisposition, "filename=data.bytes") |  | ||||||
|                 dataHeadersBuilder() |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|         additionalParametersBuilder() |  | ||||||
|     }, |  | ||||||
|     dataHeadersBuilder, |  | ||||||
|     requestBuilder, |  | ||||||
|     serialFormat |  | ||||||
| ) |  | ||||||
| @@ -7,13 +7,5 @@ import io.ktor.client.HttpClient | |||||||
| expect suspend fun HttpClient.tempUpload( | expect suspend fun HttpClient.tempUpload( | ||||||
|     fullTempUploadDraftPath: String, |     fullTempUploadDraftPath: String, | ||||||
|     file: MPPFile, |     file: MPPFile, | ||||||
|     onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> } |     onUpload: OnUploadCallback = { _, _ -> } | ||||||
| ): TemporalFileId | ): TemporalFileId | ||||||
|  |  | ||||||
| suspend fun UnifiedRequester.tempUpload( |  | ||||||
|     fullTempUploadDraftPath: String, |  | ||||||
|     file: MPPFile, |  | ||||||
|     onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> } |  | ||||||
| ): TemporalFileId = client.tempUpload( |  | ||||||
|     fullTempUploadDraftPath, file, onUpload |  | ||||||
| ) |  | ||||||
|   | |||||||
| @@ -0,0 +1,105 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.client | ||||||
|  |  | ||||||
|  | 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.http.Headers | ||||||
|  | import io.ktor.utils.io.core.Input | ||||||
|  | import kotlinx.serialization.DeserializationStrategy | ||||||
|  | import kotlinx.serialization.StringFormat | ||||||
|  | import kotlinx.serialization.json.Json | ||||||
|  |  | ||||||
|  | data class UniUploadFileInfo( | ||||||
|  |     val fileName: FileName, | ||||||
|  |     val mimeType: String, | ||||||
|  |     val inputAllocator: LambdaInputProvider | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will execute submitting of multipart data request | ||||||
|  |  * | ||||||
|  |  * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass | ||||||
|  |  * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value | ||||||
|  |  * in case you wish to pass other source of multipart binary data than regular file | ||||||
|  |  * | ||||||
|  |  * @see dev.inmo.micro_utils.ktor.server.handleUniUpload | ||||||
|  |  */ | ||||||
|  | expect suspend fun <T> HttpClient.uniUpload( | ||||||
|  |     url: String, | ||||||
|  |     data: Map<String, Any>, | ||||||
|  |     resultDeserializer: DeserializationStrategy<T>, | ||||||
|  |     headers: Headers = Headers.Empty, | ||||||
|  |     stringFormat: StringFormat = Json, | ||||||
|  |     onUpload: OnUploadCallback = { _, _ -> } | ||||||
|  | ): T? | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Additional variant of [uniUpload] which will unify sending of some [MPPFile] with the server | ||||||
|  |  * | ||||||
|  |  * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile | ||||||
|  |  */ | ||||||
|  | suspend fun <T> HttpClient.uniUpload( | ||||||
|  |     url: String, | ||||||
|  |     file: MPPFile, | ||||||
|  |     resultDeserializer: DeserializationStrategy<T>, | ||||||
|  |     additionalData: Map<String, Any> = emptyMap(), | ||||||
|  |     headers: Headers = Headers.Empty, | ||||||
|  |     stringFormat: StringFormat = Json, | ||||||
|  |     onUpload: OnUploadCallback = { _, _ -> } | ||||||
|  | ): T? = uniUpload( | ||||||
|  |     url, | ||||||
|  |     additionalData + ("bytes" to file), | ||||||
|  |     resultDeserializer, | ||||||
|  |     headers, | ||||||
|  |     stringFormat, | ||||||
|  |     onUpload | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] with the server | ||||||
|  |  * | ||||||
|  |  * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile | ||||||
|  |  */ | ||||||
|  | suspend fun <T> HttpClient.uniUpload( | ||||||
|  |     url: String, | ||||||
|  |     info: UniUploadFileInfo, | ||||||
|  |     resultDeserializer: DeserializationStrategy<T>, | ||||||
|  |     additionalData: Map<String, Any> = emptyMap(), | ||||||
|  |     headers: Headers = Headers.Empty, | ||||||
|  |     stringFormat: StringFormat = Json, | ||||||
|  |     onUpload: OnUploadCallback = { _, _ -> } | ||||||
|  | ): T? = uniUpload( | ||||||
|  |     url, | ||||||
|  |     additionalData + ("bytes" to info), | ||||||
|  |     resultDeserializer, | ||||||
|  |     headers, | ||||||
|  |     stringFormat, | ||||||
|  |     onUpload | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] (built from [fileName], | ||||||
|  |  * [mimeType] and [inputAllocator]) with the server | ||||||
|  |  * | ||||||
|  |  * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile | ||||||
|  |  */ | ||||||
|  | suspend fun <T> HttpClient.uniUpload( | ||||||
|  |     url: String, | ||||||
|  |     fileName: FileName, | ||||||
|  |     mimeType: String, | ||||||
|  |     inputAllocator: LambdaInputProvider, | ||||||
|  |     resultDeserializer: DeserializationStrategy<T>, | ||||||
|  |     additionalData: Map<String, Any> = emptyMap(), | ||||||
|  |     headers: Headers = Headers.Empty, | ||||||
|  |     stringFormat: StringFormat = Json, | ||||||
|  |     onUpload: OnUploadCallback = { _, _ -> } | ||||||
|  | ): T? = uniUpload( | ||||||
|  |     url, | ||||||
|  |     UniUploadFileInfo(fileName, mimeType, inputAllocator), | ||||||
|  |     resultDeserializer, | ||||||
|  |     additionalData, | ||||||
|  |     headers, | ||||||
|  |     stringFormat, | ||||||
|  |     onUpload | ||||||
|  | ) | ||||||
| @@ -1,6 +1,8 @@ | |||||||
| package dev.inmo.micro_utils.ktor.client | package dev.inmo.micro_utils.ktor.client | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.common.MPPFile | import dev.inmo.micro_utils.common.MPPFile | ||||||
|  | import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob | ||||||
|  | import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions | ||||||
| import dev.inmo.micro_utils.ktor.common.TemporalFileId | import dev.inmo.micro_utils.ktor.common.TemporalFileId | ||||||
| import io.ktor.client.HttpClient | import io.ktor.client.HttpClient | ||||||
| import kotlinx.coroutines.* | import kotlinx.coroutines.* | ||||||
| @@ -12,10 +14,11 @@ import org.w3c.xhr.XMLHttpRequest.Companion.DONE | |||||||
| suspend fun tempUpload( | suspend fun tempUpload( | ||||||
|     fullTempUploadDraftPath: String, |     fullTempUploadDraftPath: String, | ||||||
|     file: MPPFile, |     file: MPPFile, | ||||||
|     onUpload: (Long, Long) -> Unit |     onUpload: OnUploadCallback | ||||||
| ): TemporalFileId { | ): TemporalFileId { | ||||||
|     val formData = FormData() |     val formData = FormData() | ||||||
|     val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job) |     val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job) | ||||||
|  |     val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob()) | ||||||
|  |  | ||||||
|     formData.append( |     formData.append( | ||||||
|         "data", |         "data", | ||||||
| @@ -25,7 +28,7 @@ suspend fun tempUpload( | |||||||
|     val request = XMLHttpRequest() |     val request = XMLHttpRequest() | ||||||
|     request.responseType = XMLHttpRequestResponseType.TEXT |     request.responseType = XMLHttpRequestResponseType.TEXT | ||||||
|     request.upload.onprogress = { |     request.upload.onprogress = { | ||||||
|         onUpload(it.loaded.toLong(), it.total.toLong()) |         subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) } | ||||||
|     } |     } | ||||||
|     request.onload = { |     request.onload = { | ||||||
|         if (request.status == 200.toShort()) { |         if (request.status == 200.toShort()) { | ||||||
| @@ -48,12 +51,14 @@ suspend fun tempUpload( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return answer.await() |     return answer.await().also { | ||||||
|  |         subscope.cancel() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| actual suspend fun HttpClient.tempUpload( | actual suspend fun HttpClient.tempUpload( | ||||||
|     fullTempUploadDraftPath: String, |     fullTempUploadDraftPath: String, | ||||||
|     file: MPPFile, |     file: MPPFile, | ||||||
|     onUpload: (uploaded: Long, count: Long) -> Unit |     onUpload: OnUploadCallback | ||||||
| ): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload) | ): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload) | ||||||
|   | |||||||
| @@ -0,0 +1,97 @@ | |||||||
|  | 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.http.Headers | ||||||
|  | import io.ktor.utils.io.core.readBytes | ||||||
|  | import kotlinx.coroutines.CompletableDeferred | ||||||
|  | import kotlinx.coroutines.CoroutineScope | ||||||
|  | import kotlinx.coroutines.cancel | ||||||
|  | import kotlinx.coroutines.currentCoroutineContext | ||||||
|  | import kotlinx.coroutines.job | ||||||
|  | import kotlinx.serialization.DeserializationStrategy | ||||||
|  | import kotlinx.serialization.StringFormat | ||||||
|  | import kotlinx.serialization.encodeToString | ||||||
|  | import org.khronos.webgl.Int8Array | ||||||
|  | import org.w3c.files.Blob | ||||||
|  | import org.w3c.xhr.FormData | ||||||
|  | import org.w3c.xhr.TEXT | ||||||
|  | import org.w3c.xhr.XMLHttpRequest | ||||||
|  | import org.w3c.xhr.XMLHttpRequestResponseType | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will execute submitting of multipart data request | ||||||
|  |  * | ||||||
|  |  * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass | ||||||
|  |  * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value | ||||||
|  |  * in case you wish to pass other source of multipart binary data than regular file | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual suspend fun <T> HttpClient.uniUpload( | ||||||
|  |     url: String, | ||||||
|  |     data: Map<String, Any>, | ||||||
|  |     resultDeserializer: DeserializationStrategy<T>, | ||||||
|  |     headers: Headers, | ||||||
|  |     stringFormat: StringFormat, | ||||||
|  |     onUpload: OnUploadCallback | ||||||
|  | ): T? { | ||||||
|  |     val formData = FormData() | ||||||
|  |     val answer = CompletableDeferred<T?>(currentCoroutineContext().job) | ||||||
|  |     val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob()) | ||||||
|  |  | ||||||
|  |     data.forEach { (k, v) -> | ||||||
|  |         when (v) { | ||||||
|  |             is MPPFile -> formData.append( | ||||||
|  |                 k, | ||||||
|  |                 v | ||||||
|  |             ) | ||||||
|  |             is UniUploadFileInfo -> formData.append( | ||||||
|  |                 k, | ||||||
|  |                 Blob(arrayOf(Int8Array(v.inputAllocator().readBytes().toTypedArray()))), | ||||||
|  |                 v.fileName.name | ||||||
|  |             ) | ||||||
|  |             else -> formData.append( | ||||||
|  |                 k, | ||||||
|  |                 stringFormat.encodeToString(v) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val request = XMLHttpRequest() | ||||||
|  |     headers.forEach { s, strings -> | ||||||
|  |         request.setRequestHeader(s, strings.joinToString()) | ||||||
|  |     } | ||||||
|  |     request.responseType = XMLHttpRequestResponseType.TEXT | ||||||
|  |     request.upload.onprogress = { | ||||||
|  |         subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) } | ||||||
|  |     } | ||||||
|  |     request.onload = { | ||||||
|  |         if (request.status == 200.toShort()) { | ||||||
|  |             answer.complete( | ||||||
|  |                 stringFormat.decodeFromString(resultDeserializer, request.responseText) | ||||||
|  |             ) | ||||||
|  |         } else { | ||||||
|  |             answer.completeExceptionally(Exception("Something went wrong: $it")) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     request.onerror = { | ||||||
|  |         answer.completeExceptionally(Exception("Something went wrong: $it")) | ||||||
|  |     } | ||||||
|  |     request.open("POST", url, true) | ||||||
|  |     request.send(formData) | ||||||
|  |  | ||||||
|  |     answer.invokeOnCompletion { | ||||||
|  |         runCatching { | ||||||
|  |             if (request.readyState != XMLHttpRequest.DONE) { | ||||||
|  |                 request.abort() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return answer.await().also { | ||||||
|  |         subscope.cancel() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -4,6 +4,8 @@ import dev.inmo.micro_utils.common.MPPFile | |||||||
| import io.ktor.client.request.forms.InputProvider | import io.ktor.client.request.forms.InputProvider | ||||||
| import io.ktor.utils.io.streams.asInput | import io.ktor.utils.io.streams.asInput | ||||||
|  |  | ||||||
| actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) { | fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) { | ||||||
|     inputStream().asInput() |     inputStream().asInput() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync() | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ internal val MPPFile.mimeType: String | |||||||
| actual suspend fun HttpClient.tempUpload( | actual suspend fun HttpClient.tempUpload( | ||||||
|     fullTempUploadDraftPath: String, |     fullTempUploadDraftPath: String, | ||||||
|     file: MPPFile, |     file: MPPFile, | ||||||
|     onUpload: (Long, Long) -> Unit |     onUpload: OnUploadCallback | ||||||
| ): TemporalFileId { | ): TemporalFileId { | ||||||
|     val inputProvider = file.inputProvider() |     val inputProvider = file.inputProvider() | ||||||
|     val fileId = submitFormWithBinaryData( |     val fileId = submitFormWithBinaryData( | ||||||
|   | |||||||
| @@ -0,0 +1,102 @@ | |||||||
|  | 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.plugins.onUpload | ||||||
|  | import io.ktor.client.request.HttpRequestBuilder | ||||||
|  | import io.ktor.client.request.forms.InputProvider | ||||||
|  | import io.ktor.client.request.forms.formData | ||||||
|  | import io.ktor.client.request.forms.submitForm | ||||||
|  | import io.ktor.client.request.forms.submitFormWithBinaryData | ||||||
|  | import io.ktor.client.request.headers | ||||||
|  | import io.ktor.client.statement.bodyAsText | ||||||
|  | import io.ktor.http.Headers | ||||||
|  | import io.ktor.http.HttpHeaders | ||||||
|  | import io.ktor.http.HttpStatusCode | ||||||
|  | import io.ktor.http.Parameters | ||||||
|  | import io.ktor.http.content.PartData | ||||||
|  | import kotlinx.serialization.DeserializationStrategy | ||||||
|  | import kotlinx.serialization.StringFormat | ||||||
|  | import kotlinx.serialization.encodeToString | ||||||
|  | import java.io.File | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will execute submitting of multipart data request | ||||||
|  |  * | ||||||
|  |  * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass | ||||||
|  |  * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value | ||||||
|  |  * in case you wish to pass other source of multipart binary data than regular file | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual suspend fun <T> HttpClient.uniUpload( | ||||||
|  |     url: String, | ||||||
|  |     data: Map<String, Any>, | ||||||
|  |     resultDeserializer: DeserializationStrategy<T>, | ||||||
|  |     headers: Headers, | ||||||
|  |     stringFormat: StringFormat, | ||||||
|  |     onUpload: OnUploadCallback | ||||||
|  | ): T? { | ||||||
|  |     val withBinary = data.values.any { it is File || it is UniUploadFileInfo } | ||||||
|  |  | ||||||
|  |     val formData = formData { | ||||||
|  |         data.forEach { (k, v) -> | ||||||
|  |             when (v) { | ||||||
|  |                 is File -> append( | ||||||
|  |                     k, | ||||||
|  |                     v.inputProviderSync(), | ||||||
|  |                     Headers.build { | ||||||
|  |                         append(HttpHeaders.ContentType, v.mimeType) | ||||||
|  |                         append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"") | ||||||
|  |                     } | ||||||
|  |                 ) | ||||||
|  |                 is UniUploadFileInfo -> append( | ||||||
|  |                     k, | ||||||
|  |                     InputProvider(block = v.inputAllocator), | ||||||
|  |                     Headers.build { | ||||||
|  |                         append(HttpHeaders.ContentType, v.mimeType) | ||||||
|  |                         append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"") | ||||||
|  |                     } | ||||||
|  |                 ) | ||||||
|  |                 else -> append( | ||||||
|  |                     k, | ||||||
|  |                     stringFormat.encodeToString(v) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val requestBuilder: HttpRequestBuilder.() -> Unit = { | ||||||
|  |         headers { | ||||||
|  |             appendAll(headers) | ||||||
|  |         } | ||||||
|  |         onUpload { bytesSentTotal, contentLength -> | ||||||
|  |             onUpload(bytesSentTotal, contentLength) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val response = if (withBinary) { | ||||||
|  |         submitFormWithBinaryData( | ||||||
|  |             url, | ||||||
|  |             formData, | ||||||
|  |             block = requestBuilder | ||||||
|  |         ) | ||||||
|  |     } else { | ||||||
|  |         submitForm( | ||||||
|  |             url, | ||||||
|  |             Parameters.build { | ||||||
|  |                 formData.forEach { | ||||||
|  |                     val formItem = (it as PartData.FormItem) | ||||||
|  |                     append(it.name!!, it.value) | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             block = requestBuilder | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return if (response.status == HttpStatusCode.OK) { | ||||||
|  |         stringFormat.decodeFromString(resultDeserializer, response.bodyAsText()) | ||||||
|  |     } else { | ||||||
|  |         null | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.common | ||||||
|  |  | ||||||
|  | import io.ktor.utils.io.core.Input | ||||||
|  |  | ||||||
|  | typealias LambdaInputProvider = () -> Input | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | 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 java.io.File | ||||||
|  | import java.io.InputStream | ||||||
|  | import java.util.UUID | ||||||
|  |  | ||||||
|  | fun Input.downloadToTempFile( | ||||||
|  |     fileName: String = UUID.randomUUID().toString(), | ||||||
|  |     fileExtension: String? = ".temp", | ||||||
|  |     folder: File? = null | ||||||
|  | ) = File.createTempFile( | ||||||
|  |     fileName, | ||||||
|  |     fileExtension, | ||||||
|  |     folder | ||||||
|  | ).apply { | ||||||
|  |     outputStream().use { | ||||||
|  |         copyTo(it.asOutput()) | ||||||
|  |     } | ||||||
|  |     deleteOnExit() | ||||||
|  | } | ||||||
| @@ -0,0 +1,56 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.server | ||||||
|  |  | ||||||
|  | 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 java.io.File | ||||||
|  |  | ||||||
|  | fun PartData.FileItem.download(target: File) { | ||||||
|  |     provider().use { input -> | ||||||
|  |         target.outputStream().use { | ||||||
|  |             input.copyTo(it.asOutput()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun PartData.FileItem.downloadToTemporalFile(): File { | ||||||
|  |     val outputFile = File.createTempFile(uuid4().toString(), ".temp").apply { | ||||||
|  |         deleteOnExit() | ||||||
|  |     } | ||||||
|  |     download(outputFile) | ||||||
|  |     return outputFile | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun PartData.BinaryItem.download(target: File) { | ||||||
|  |     provider().use { input -> | ||||||
|  |         target.outputStream().use { | ||||||
|  |             input.copyTo(it.asOutput()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun PartData.BinaryItem.downloadToTemporalFile(): File { | ||||||
|  |     val outputFile = File.createTempFile(uuid4().toString(), ".temp").apply { | ||||||
|  |         deleteOnExit() | ||||||
|  |     } | ||||||
|  |     download(outputFile) | ||||||
|  |     return outputFile | ||||||
|  | } | ||||||
|  |  | ||||||
|  | suspend fun PartData.BinaryChannelItem.download(target: File) { | ||||||
|  |     val input = provider() | ||||||
|  |     target.outputStream().use { | ||||||
|  |         input.copyTo(it) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | suspend fun PartData.BinaryChannelItem.downloadToTemporalFile(): File { | ||||||
|  |     val outputFile = File.createTempFile(uuid4().toString(), ".temp").apply { | ||||||
|  |         deleteOnExit() | ||||||
|  |     } | ||||||
|  |     download(outputFile) | ||||||
|  |     return outputFile | ||||||
|  | } | ||||||
| @@ -1,61 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.ktor.server |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.coroutines.safely |  | ||||||
| import dev.inmo.micro_utils.ktor.common.* |  | ||||||
| import io.ktor.http.URLProtocol |  | ||||||
| import io.ktor.server.application.install |  | ||||||
| import io.ktor.server.application.pluginOrNull |  | ||||||
| import io.ktor.server.routing.Route |  | ||||||
| import io.ktor.server.routing.application |  | ||||||
| import io.ktor.server.websocket.* |  | ||||||
| import io.ktor.websocket.send |  | ||||||
| import kotlinx.coroutines.flow.Flow |  | ||||||
| import kotlinx.serialization.SerializationStrategy |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon") |  | ||||||
| fun <T> Route.includeWebsocketHandling( |  | ||||||
|     suburl: String, |  | ||||||
|     flow: Flow<T>, |  | ||||||
|     protocol: URLProtocol? = null, |  | ||||||
|     converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData? |  | ||||||
| ) { |  | ||||||
|     application.apply { |  | ||||||
|         pluginOrNull(WebSockets) ?: install(WebSockets) |  | ||||||
|     } |  | ||||||
|     webSocket(suburl, protocol ?.name) { |  | ||||||
|         safely { |  | ||||||
|             flow.collect { |  | ||||||
|                 converter(it) ?.let { data -> |  | ||||||
|                     send(data) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon") |  | ||||||
| fun <T> Route.includeWebsocketHandling( |  | ||||||
|     suburl: String, |  | ||||||
|     flow: Flow<T>, |  | ||||||
|     serializer: SerializationStrategy<T>, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, |  | ||||||
|     protocol: URLProtocol? = null, |  | ||||||
|     filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null |  | ||||||
| ) = includeWebsocketHandling( |  | ||||||
|     suburl, |  | ||||||
|     flow, |  | ||||||
|     protocol, |  | ||||||
|     converter = if (filter == null) { |  | ||||||
|         { |  | ||||||
|             serialFormat.encodeDefault(serializer, it) |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         { |  | ||||||
|             if (filter(it)) { |  | ||||||
|                 serialFormat.encodeDefault(serializer, it) |  | ||||||
|             } else { |  | ||||||
|                 null |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| ) |  | ||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.server | ||||||
|  |  | ||||||
|  | import io.ktor.http.HttpStatusCode | ||||||
|  | import io.ktor.server.application.ApplicationCall | ||||||
|  | import io.ktor.server.response.respond | ||||||
|  |  | ||||||
|  | suspend fun ApplicationCall.getParameterOrSendError( | ||||||
|  |     field: String | ||||||
|  | ) = parameters[field].also { | ||||||
|  |     if (it == null) { | ||||||
|  |         respond(HttpStatusCode.BadRequest, "Request must contains $field") | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun ApplicationCall.getQueryParameter( | ||||||
|  |     field: String | ||||||
|  | ) = request.queryParameters[field] | ||||||
|  |  | ||||||
|  | suspend fun ApplicationCall.getQueryParameterOrSendError( | ||||||
|  |     field: String | ||||||
|  | ) = getQueryParameter(field).also { | ||||||
|  |     if (it == null) { | ||||||
|  |         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,308 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.ktor.server |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.common.* |  | ||||||
| import dev.inmo.micro_utils.coroutines.safely |  | ||||||
| import dev.inmo.micro_utils.ktor.common.* |  | ||||||
| import io.ktor.http.* |  | ||||||
| import io.ktor.http.content.* |  | ||||||
| import io.ktor.server.application.ApplicationCall |  | ||||||
| import io.ktor.server.application.call |  | ||||||
| import io.ktor.server.request.receive |  | ||||||
| import io.ktor.server.request.receiveMultipart |  | ||||||
| import io.ktor.server.response.respond |  | ||||||
| import io.ktor.server.response.respondBytes |  | ||||||
| import io.ktor.server.routing.Route |  | ||||||
| import io.ktor.server.websocket.WebSocketServerSession |  | ||||||
| import io.ktor.util.pipeline.PipelineContext |  | ||||||
| import io.ktor.utils.io.core.* |  | ||||||
| import kotlinx.coroutines.flow.Flow |  | ||||||
| import kotlinx.serialization.DeserializationStrategy |  | ||||||
| import kotlinx.serialization.SerializationStrategy |  | ||||||
|  |  | ||||||
| @Deprecated("This class method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| class UnifiedRouter( |  | ||||||
|     val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, |  | ||||||
|     val serialFormatContentType: ContentType = standardKtorSerialFormatContentType |  | ||||||
| ) { |  | ||||||
|     fun <T> Route.includeWebsocketHandling( |  | ||||||
|         suburl: String, |  | ||||||
|         flow: Flow<T>, |  | ||||||
|         serializer: SerializationStrategy<T>, |  | ||||||
|         protocol: URLProtocol? = null, |  | ||||||
|         filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null |  | ||||||
|     ) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter) |  | ||||||
|  |  | ||||||
|     suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer( |  | ||||||
|         answerSerializer: SerializationStrategy<T>, |  | ||||||
|         answer: T |  | ||||||
|     ) { |  | ||||||
|         call.respondBytes ( |  | ||||||
|             serialFormat.encodeDefault(answerSerializer, answer), |  | ||||||
|             serialFormatContentType |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     suspend fun <T> PipelineContext<*, ApplicationCall>.uniload( |  | ||||||
|         deserializer: DeserializationStrategy<T> |  | ||||||
|     ) = safely { |  | ||||||
|         serialFormat.decodeDefault( |  | ||||||
|             deserializer, |  | ||||||
|             call.receive() |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError( |  | ||||||
|         field: String |  | ||||||
|     ) = call.parameters[field].also { |  | ||||||
|         if (it == null) { |  | ||||||
|             call.respond(HttpStatusCode.BadRequest, "Request must contains $field") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun PipelineContext<*, ApplicationCall>.getQueryParameter( |  | ||||||
|         field: String |  | ||||||
|     ) = call.request.queryParameters[field] |  | ||||||
|  |  | ||||||
|     suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError( |  | ||||||
|         field: String |  | ||||||
|     ) = getQueryParameter(field).also { |  | ||||||
|         if (it == null) { |  | ||||||
|             call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue( |  | ||||||
|         field: String, |  | ||||||
|         deserializer: DeserializationStrategy<T> |  | ||||||
|     ) = getQueryParameter(field) ?.let { |  | ||||||
|         serialFormat.decodeHex( |  | ||||||
|             deserializer, |  | ||||||
|             it |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError( |  | ||||||
|         field: String, |  | ||||||
|         deserializer: DeserializationStrategy<T> |  | ||||||
|     ) = decodeUrlQueryValue(field, deserializer).also { |  | ||||||
|         if (it == null) { |  | ||||||
|             call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         val default |  | ||||||
|             get() = defaultUnifiedRouter |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| val defaultUnifiedRouter = UnifiedRouter() |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <T> ApplicationCall.unianswer( |  | ||||||
|     answerSerializer: SerializationStrategy<T>, |  | ||||||
|     answer: T |  | ||||||
| ) { |  | ||||||
|     respondBytes ( |  | ||||||
|         standardKtorSerialFormat.encodeDefault(answerSerializer, answer), |  | ||||||
|         standardKtorSerialFormatContentType |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <T> ApplicationCall.uniload( |  | ||||||
|     deserializer: DeserializationStrategy<T> |  | ||||||
| ) = safely { |  | ||||||
|     standardKtorSerialFormat.decodeDefault( |  | ||||||
|         deserializer, |  | ||||||
|         receive() |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| suspend fun ApplicationCall.uniloadMultipart( |  | ||||||
|     onFormItem: (PartData.FormItem) -> Unit = {}, |  | ||||||
|     onCustomFileItem: (PartData.FileItem) -> Unit = {}, |  | ||||||
|     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, |  | ||||||
|     onBinaryContent: (PartData.BinaryItem) -> Unit = {} |  | ||||||
| ) = safely { |  | ||||||
|     val multipartData = receiveMultipart() |  | ||||||
|  |  | ||||||
|     var resultInput: Input? = null |  | ||||||
|  |  | ||||||
|     multipartData.forEachPart { |  | ||||||
|         when (it) { |  | ||||||
|             is PartData.FormItem -> onFormItem(it) |  | ||||||
|             is PartData.FileItem -> { |  | ||||||
|                 when (it.name) { |  | ||||||
|                     "bytes" -> resultInput = it.provider() |  | ||||||
|                     else -> onCustomFileItem(it) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             is PartData.BinaryItem -> onBinaryContent(it) |  | ||||||
|             is PartData.BinaryChannelItem -> onBinaryChannelItem(it) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     resultInput ?: error("Bytes has not been received") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <T> ApplicationCall.uniloadMultipart( |  | ||||||
|     deserializer: DeserializationStrategy<T>, |  | ||||||
|     onFormItem: (PartData.FormItem) -> Unit = {}, |  | ||||||
|     onCustomFileItem: (PartData.FileItem) -> Unit = {}, |  | ||||||
|     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, |  | ||||||
|     onBinaryContent: (PartData.BinaryItem) -> Unit = {} |  | ||||||
| ): Pair<Input, T> { |  | ||||||
|     var data: Optional<T>? = null |  | ||||||
|     val resultInput = uniloadMultipart( |  | ||||||
|         onFormItem, |  | ||||||
|         { |  | ||||||
|             if (it.name == "data") { |  | ||||||
|                 data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional |  | ||||||
|             } else { |  | ||||||
|                 onCustomFileItem(it) |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         onBinaryChannelItem, |  | ||||||
|         onBinaryContent |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     val completeData = data ?: error("Data has not been received") |  | ||||||
|     return resultInput to (completeData.dataOrNull().let { it as T }) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <T> ApplicationCall.uniloadMultipartFile( |  | ||||||
|     deserializer: DeserializationStrategy<T>, |  | ||||||
|     onFormItem: (PartData.FormItem) -> Unit = {}, |  | ||||||
|     onCustomFileItem: (PartData.FileItem) -> Unit = {}, |  | ||||||
|     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, |  | ||||||
|     onBinaryContent: (PartData.BinaryItem) -> Unit = {}, |  | ||||||
| ) = safely { |  | ||||||
|     val multipartData = receiveMultipart() |  | ||||||
|  |  | ||||||
|     var resultInput: MPPFile? = null |  | ||||||
|     var data: Optional<T>? = null |  | ||||||
|  |  | ||||||
|     multipartData.forEachPart { |  | ||||||
|         when (it) { |  | ||||||
|             is PartData.FormItem -> onFormItem(it) |  | ||||||
|             is PartData.FileItem -> { |  | ||||||
|                 when (it.name) { |  | ||||||
|                     "bytes" -> { |  | ||||||
|                         val name = FileName(it.originalFileName ?: error("File name is unknown for default part")) |  | ||||||
|                         resultInput = MPPFile.createTempFile( |  | ||||||
|                             name.nameWithoutExtension.let { |  | ||||||
|                                 var resultName = it |  | ||||||
|                                 while (resultName.length < 3) { |  | ||||||
|                                     resultName += "_" |  | ||||||
|                                 } |  | ||||||
|                                 resultName |  | ||||||
|                             }, |  | ||||||
|                             ".${name.extension}" |  | ||||||
|                         ).apply { |  | ||||||
|                             outputStream().use { fileStream -> |  | ||||||
|                                 it.streamProvider().use { |  | ||||||
|                                     it.copyTo(fileStream) |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     "data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional |  | ||||||
|                     else -> onCustomFileItem(it) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             is PartData.BinaryItem -> onBinaryContent(it) |  | ||||||
|             is PartData.BinaryChannelItem -> onBinaryChannelItem(it) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val completeData = data ?: error("Data has not been received") |  | ||||||
|     (resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T }) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| suspend fun ApplicationCall.uniloadMultipartFile( |  | ||||||
|     onFormItem: (PartData.FormItem) -> Unit = {}, |  | ||||||
|     onCustomFileItem: (PartData.FileItem) -> Unit = {}, |  | ||||||
|     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, |  | ||||||
|     onBinaryContent: (PartData.BinaryItem) -> Unit = {}, |  | ||||||
| ) = safely { |  | ||||||
|     val multipartData = receiveMultipart() |  | ||||||
|  |  | ||||||
|     var resultInput: MPPFile? = null |  | ||||||
|  |  | ||||||
|     multipartData.forEachPart { |  | ||||||
|         when (it) { |  | ||||||
|             is PartData.FormItem -> onFormItem(it) |  | ||||||
|             is PartData.FileItem -> { |  | ||||||
|                 if (it.name == "bytes") { |  | ||||||
|                     val name = FileName(it.originalFileName ?: error("File name is unknown for default part")) |  | ||||||
|                     resultInput = MPPFile.createTempFile( |  | ||||||
|                         name.nameWithoutExtension.let { |  | ||||||
|                             var resultName = it |  | ||||||
|                             while (resultName.length < 3) { |  | ||||||
|                                 resultName += "_" |  | ||||||
|                             } |  | ||||||
|                             resultName |  | ||||||
|                         }, |  | ||||||
|                         ".${name.extension}" |  | ||||||
|                     ).apply { |  | ||||||
|                         outputStream().use { fileStream -> |  | ||||||
|                             it.streamProvider().use { |  | ||||||
|                                 it.copyTo(fileStream) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     onCustomFileItem(it) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             is PartData.BinaryItem -> onBinaryContent(it) |  | ||||||
|             is PartData.BinaryChannelItem -> onBinaryChannelItem(it) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     resultInput ?: error("Bytes has not been received") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| suspend fun ApplicationCall.getParameterOrSendError( |  | ||||||
|     field: String |  | ||||||
| ) = parameters[field].also { |  | ||||||
|     if (it == null) { |  | ||||||
|         respond(HttpStatusCode.BadRequest, "Request must contains $field") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fun ApplicationCall.getQueryParameter( |  | ||||||
|     field: String |  | ||||||
| ) = request.queryParameters[field] |  | ||||||
|  |  | ||||||
| suspend fun ApplicationCall.getQueryParameterOrSendError( |  | ||||||
|     field: String |  | ||||||
| ) = getQueryParameter(field).also { |  | ||||||
|     if (it == null) { |  | ||||||
|         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| fun <T> ApplicationCall.decodeUrlQueryValue( |  | ||||||
|     field: String, |  | ||||||
|     deserializer: DeserializationStrategy<T> |  | ||||||
| ) = getQueryParameter(field) ?.let { |  | ||||||
|     standardKtorSerialFormat.decodeHex( |  | ||||||
|         deserializer, |  | ||||||
|         it |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") |  | ||||||
| suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError( |  | ||||||
|     field: String, |  | ||||||
|     deserializer: DeserializationStrategy<T> |  | ||||||
| ) = decodeUrlQueryValue(field, deserializer).also { |  | ||||||
|     if (it == null) { |  | ||||||
|         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -26,7 +26,6 @@ import java.nio.file.attribute.FileTime | |||||||
|  |  | ||||||
| class TemporalFilesRoutingConfigurator( | class TemporalFilesRoutingConfigurator( | ||||||
|     private val subpath: String = DefaultTemporalFilesSubPath, |     private val subpath: String = DefaultTemporalFilesSubPath, | ||||||
|     private val unifiedRouter: UnifiedRouter = UnifiedRouter.default, |  | ||||||
|     private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer |     private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer | ||||||
| ) : ApplicationRoutingConfigurator.Element { | ) : ApplicationRoutingConfigurator.Element { | ||||||
|     interface TemporalFilesUtilizer { |     interface TemporalFilesUtilizer { | ||||||
| @@ -80,42 +79,40 @@ class TemporalFilesRoutingConfigurator( | |||||||
|  |  | ||||||
|     override fun Route.invoke() { |     override fun Route.invoke() { | ||||||
|         post(subpath) { |         post(subpath) { | ||||||
|             unifiedRouter.apply { |             val multipart = call.receiveMultipart() | ||||||
|                 val multipart = call.receiveMultipart() |  | ||||||
|  |  | ||||||
|                 var fileInfo: Pair<TemporalFileId, MPPFile>? = null |             var fileInfo: Pair<TemporalFileId, MPPFile>? = null | ||||||
|                 var part = multipart.readPart() |             var part = multipart.readPart() | ||||||
|  |  | ||||||
|                 while (part != null) { |             while (part != null) { | ||||||
|                     if (part is PartData.FileItem) { |                 if (part is PartData.FileItem) { | ||||||
|                         break |                     break | ||||||
|                     } |  | ||||||
|                     part = multipart.readPart() |  | ||||||
|                 } |                 } | ||||||
|  |                 part = multipart.readPart() | ||||||
|                 part ?.let { |  | ||||||
|                     if (it is PartData.FileItem) { |  | ||||||
|                         val fileId = TemporalFileId(uuid4().toString()) |  | ||||||
|                         val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let |  | ||||||
|                         fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply { |  | ||||||
|                             outputStream().use { outputStream -> |  | ||||||
|                                 it.streamProvider().use { |  | ||||||
|                                     it.copyTo(outputStream) |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                             deleteOnExit() |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 fileInfo ?.also { (fileId, file) -> |  | ||||||
|                     temporalFilesMutex.withLock { |  | ||||||
|                         temporalFilesMap[fileId] = file |  | ||||||
|                     } |  | ||||||
|                     call.respondText(fileId.string) |  | ||||||
|                     launchSafelyWithoutExceptions { filesFlow.emit(fileId) } |  | ||||||
|                 } ?: call.respond(HttpStatusCode.BadRequest) |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             part ?.let { | ||||||
|  |                 if (it is PartData.FileItem) { | ||||||
|  |                     val fileId = TemporalFileId(uuid4().toString()) | ||||||
|  |                     val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let | ||||||
|  |                     fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply { | ||||||
|  |                         outputStream().use { outputStream -> | ||||||
|  |                             it.streamProvider().use { | ||||||
|  |                                 it.copyTo(outputStream) | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         deleteOnExit() | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             fileInfo ?.also { (fileId, file) -> | ||||||
|  |                 temporalFilesMutex.withLock { | ||||||
|  |                     temporalFilesMap[fileId] = file | ||||||
|  |                 } | ||||||
|  |                 call.respondText(fileId.string) | ||||||
|  |                 launchSafelyWithoutExceptions { filesFlow.emit(fileId) } | ||||||
|  |             } ?: call.respond(HttpStatusCode.BadRequest) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,70 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.server | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.* | ||||||
|  | import dev.inmo.micro_utils.coroutines.safely | ||||||
|  | import dev.inmo.micro_utils.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.core.* | ||||||
|  | import kotlinx.coroutines.currentCoroutineContext | ||||||
|  | import kotlinx.coroutines.isActive | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Server-side part which receives [dev.inmo.micro_utils.ktor.client.uniUpload] request | ||||||
|  |  */ | ||||||
|  | suspend inline fun ApplicationCall.handleUniUpload( | ||||||
|  |     onFormItem: (PartData.FormItem) -> Unit = {}, | ||||||
|  |     onBinaryContent: (PartData.BinaryItem) -> Unit = {}, | ||||||
|  |     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, | ||||||
|  |     onFileItem: (PartData.FileItem) -> Unit = {} | ||||||
|  | ) { | ||||||
|  |     val multipartData = receiveMultipart() | ||||||
|  |  | ||||||
|  |     while (currentCoroutineContext().isActive) { | ||||||
|  |         val partData = multipartData.readPart() ?: break | ||||||
|  |         when (partData) { | ||||||
|  |             is PartData.FormItem -> onFormItem(partData) | ||||||
|  |             is PartData.FileItem -> onFileItem(partData) | ||||||
|  |             is PartData.BinaryItem -> onBinaryContent(partData) | ||||||
|  |             is PartData.BinaryChannelItem -> onBinaryChannelItem(partData) | ||||||
|  |         } | ||||||
|  |         partData.dispose() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | suspend fun ApplicationCall.uniloadMultipart( | ||||||
|  |     onFormItem: (PartData.FormItem) -> Unit = {}, | ||||||
|  |     onCustomFileItem: (PartData.FileItem) -> Unit = {}, | ||||||
|  |     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, | ||||||
|  |     onBinaryContent: (PartData.BinaryItem) -> Unit = {} | ||||||
|  | ): Input = safely { | ||||||
|  |     var resultInput: Input? = null | ||||||
|  |  | ||||||
|  |     handleUniUpload( | ||||||
|  |         onFormItem, | ||||||
|  |         onBinaryContent, | ||||||
|  |         onBinaryChannelItem | ||||||
|  |     ) { | ||||||
|  |         when (it.name) { | ||||||
|  |             "bytes" -> resultInput = it.provider() | ||||||
|  |             else -> onCustomFileItem(it) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     resultInput ?: error("Bytes has not been received") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | suspend fun ApplicationCall.uniloadMultipartFile( | ||||||
|  |     onFormItem: (PartData.FormItem) -> Unit = {}, | ||||||
|  |     onCustomFileItem: (PartData.FileItem) -> Unit = {}, | ||||||
|  |     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, | ||||||
|  |     onBinaryContent: (PartData.BinaryItem) -> Unit = {}, | ||||||
|  | ): MPPFile = safely { | ||||||
|  |     uniloadMultipart( | ||||||
|  |         onFormItem, | ||||||
|  |         onCustomFileItem, | ||||||
|  |         onBinaryChannelItem, | ||||||
|  |         onBinaryContent | ||||||
|  |     ).downloadToTempFile() | ||||||
|  | } | ||||||
| @@ -78,12 +78,14 @@ private fun printLanguageCodeAndTags( | |||||||
|     indents: String = "    " |     indents: String = "    " | ||||||
| ): String = if (tag.subtags.isEmpty()) { | ): String = if (tag.subtags.isEmpty()) { | ||||||
| """${indents}${baseClassSerializerAnnotationName} | """${indents}${baseClassSerializerAnnotationName} | ||||||
| ${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}" }""" | ${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"; override val withoutDialect: String get() = ${parent ?.title ?.let { "$it.code" } ?: "code"} }""" | ||||||
| } else { | } else { | ||||||
| """ | """ | ||||||
| ${indents}${baseClassSerializerAnnotationName} | ${indents}${baseClassSerializerAnnotationName} | ||||||
| ${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() { | ${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() { | ||||||
| ${indents}    override val code: String = "${tag.tag}" | ${indents}    override val code: String = "${tag.tag}" | ||||||
|  | ${indents}    override val withoutDialect: String | ||||||
|  | ${indents}        get() = code | ||||||
|  |  | ||||||
| ${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents}    ") }} | ${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents}    ") }} | ||||||
|  |  | ||||||
| @@ -104,11 +106,14 @@ import kotlinx.serialization.Serializable | |||||||
| ${baseClassSerializerAnnotationName} | ${baseClassSerializerAnnotationName} | ||||||
| sealed class $baseClassName { | sealed class $baseClassName { | ||||||
|     abstract val code: String |     abstract val code: String | ||||||
|  |     abstract val withoutDialect: String | ||||||
|  |  | ||||||
| ${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = "    ") } } | ${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = "    ") } } | ||||||
|  |  | ||||||
|     $baseClassSerializerAnnotationName |     $baseClassSerializerAnnotationName | ||||||
|     data class $unknownBaseClassName (override val code: String) : $baseClassName() |     data class $unknownBaseClassName (override val code: String) : $baseClassName() { | ||||||
|  |         override val withoutDialect: String = code.takeWhile { it != '-' } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override fun toString() = code |     override fun toString() = code | ||||||
| } | } | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -17,10 +17,11 @@ def fix_name(category, raw_name): | |||||||
|         result += out1 |         result += out1 | ||||||
|     return result |     return result | ||||||
|  |  | ||||||
|  | # https://www.freeformatter.com/mime-types-list.html | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     df = pd.read_html(open('table.html', 'r')) |     df = pd.read_html(open('table.html', 'r')) | ||||||
|     mimes = [] |     mimes = [] | ||||||
|     for row in df[0].iterrows(): |     for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows(): | ||||||
|         mime = row[1][1] |         mime = row[1][1] | ||||||
|         mime_category = mime.split('/', 1)[0] |         mime_category = mime.split('/', 1)[0] | ||||||
|         mime_name = mime.split('/', 1)[1] |         mime_name = mime.split('/', 1)[1] | ||||||
| @@ -37,7 +38,7 @@ if __name__ == '__main__': | |||||||
|     code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n' |     code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n' | ||||||
|     code2 += '    KnownMimeTypes.Any,\n' |     code2 += '    KnownMimeTypes.Any,\n' | ||||||
|     for key, group in grouped: |     for key, group in grouped: | ||||||
|         group_name = key.capitalize() |         group_name = fix_name(group, key) | ||||||
|         code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name |         code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name | ||||||
|         code += '    @Serializable(MimeTypeSerializer::class)\n    object Any: %s ("%s/*")\n' % (group_name, key) |         code += '    @Serializable(MimeTypeSerializer::class)\n    object Any: %s ("%s/*")\n' % (group_name, key) | ||||||
|         for mime in group: |         for mime in group: | ||||||
|   | |||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | package dev.inmo.micro_utils.mime_types | ||||||
|  |  | ||||||
|  | data class CustomMimeType(override val raw: String) : MimeType | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,705 @@ | |||||||
|  | package dev.inmo.micro_utils.mime_types | ||||||
|  |  | ||||||
|  | internal val knownMimeTypes: Set<MimeType> = setOf( | ||||||
|  |     KnownMimeTypes.Any, | ||||||
|  |     KnownMimeTypes.Application.VndHzn3dCrossword, | ||||||
|  |     KnownMimeTypes.Application.VndMseq, | ||||||
|  |     KnownMimeTypes.Application.Vnd3mPostItNotes, | ||||||
|  |     KnownMimeTypes.Application.Vnd3gppPicBwLarge, | ||||||
|  |     KnownMimeTypes.Application.Vnd3gppPicBwSmall, | ||||||
|  |     KnownMimeTypes.Application.Vnd3gppPicBwVar, | ||||||
|  |     KnownMimeTypes.Application.Vnd3gpp2Tcap, | ||||||
|  |     KnownMimeTypes.Application.X7zCompressed, | ||||||
|  |     KnownMimeTypes.Application.XAbiword, | ||||||
|  |     KnownMimeTypes.Application.XAceCompressed, | ||||||
|  |     KnownMimeTypes.Application.VndAmericandynamicsAcc, | ||||||
|  |     KnownMimeTypes.Application.VndAcucobol, | ||||||
|  |     KnownMimeTypes.Application.VndAcucorp, | ||||||
|  |     KnownMimeTypes.Application.XAuthorwareBin, | ||||||
|  |     KnownMimeTypes.Application.XAuthorwareMap, | ||||||
|  |     KnownMimeTypes.Application.XAuthorwareSeg, | ||||||
|  |     KnownMimeTypes.Application.VndAdobeAirApplicationInstallerPackageZip, | ||||||
|  |     KnownMimeTypes.Application.XShockwaveFlash, | ||||||
|  |     KnownMimeTypes.Application.VndAdobeFxp, | ||||||
|  |     KnownMimeTypes.Application.Pdf, | ||||||
|  |     KnownMimeTypes.Application.VndCupsPpd, | ||||||
|  |     KnownMimeTypes.Application.XDirector, | ||||||
|  |     KnownMimeTypes.Application.VndAdobeXdpXml, | ||||||
|  |     KnownMimeTypes.Application.VndAdobeXfdf, | ||||||
|  |     KnownMimeTypes.Application.VndAheadSpace, | ||||||
|  |     KnownMimeTypes.Application.VndAirzipFilesecureAzf, | ||||||
|  |     KnownMimeTypes.Application.VndAirzipFilesecureAzs, | ||||||
|  |     KnownMimeTypes.Application.VndAmazonEbook, | ||||||
|  |     KnownMimeTypes.Application.VndAmigaAmi, | ||||||
|  |     KnownMimeTypes.Application.AndrewInset, | ||||||
|  |     KnownMimeTypes.Application.VndAndroidPackageArchive, | ||||||
|  |     KnownMimeTypes.Application.VndAnserWebCertificateIssueInitiation, | ||||||
|  |     KnownMimeTypes.Application.VndAnserWebFundsTransferInitiation, | ||||||
|  |     KnownMimeTypes.Application.VndAntixGameComponent, | ||||||
|  |     KnownMimeTypes.Application.XAppleDiskimage, | ||||||
|  |     KnownMimeTypes.Application.VndAppleInstallerXml, | ||||||
|  |     KnownMimeTypes.Application.Applixware, | ||||||
|  |     KnownMimeTypes.Application.VndHheLessonPlayer, | ||||||
|  |     KnownMimeTypes.Application.XFreearc, | ||||||
|  |     KnownMimeTypes.Application.VndAristanetworksSwi, | ||||||
|  |     KnownMimeTypes.Application.AtomcatXml, | ||||||
|  |     KnownMimeTypes.Application.AtomsvcXml, | ||||||
|  |     KnownMimeTypes.Application.AtomXml, | ||||||
|  |     KnownMimeTypes.Application.PkixAttrCert, | ||||||
|  |     KnownMimeTypes.Application.VndAudiograph, | ||||||
|  |     KnownMimeTypes.Application.XBcpio, | ||||||
|  |     KnownMimeTypes.Application.OctetStream, | ||||||
|  |     KnownMimeTypes.Application.XBittorrent, | ||||||
|  |     KnownMimeTypes.Application.VndRimCod, | ||||||
|  |     KnownMimeTypes.Application.VndBlueiceMultipass, | ||||||
|  |     KnownMimeTypes.Application.VndBmi, | ||||||
|  |     KnownMimeTypes.Application.XSh, | ||||||
|  |     KnownMimeTypes.Application.VndBusinessobjects, | ||||||
|  |     KnownMimeTypes.Application.XBzip, | ||||||
|  |     KnownMimeTypes.Application.XBzip2, | ||||||
|  |     KnownMimeTypes.Application.XCsh, | ||||||
|  |     KnownMimeTypes.Application.VndChemdrawXml, | ||||||
|  |     KnownMimeTypes.Application.XCdf, | ||||||
|  |     KnownMimeTypes.Application.VndContactCmsg, | ||||||
|  |     KnownMimeTypes.Application.VndClaymore, | ||||||
|  |     KnownMimeTypes.Application.VndClonkC4group, | ||||||
|  |     KnownMimeTypes.Application.CdmiCapability, | ||||||
|  |     KnownMimeTypes.Application.CdmiContainer, | ||||||
|  |     KnownMimeTypes.Application.CdmiDomain, | ||||||
|  |     KnownMimeTypes.Application.CdmiObject, | ||||||
|  |     KnownMimeTypes.Application.CdmiQueue, | ||||||
|  |     KnownMimeTypes.Application.VndCluetrustCartomobileConfig, | ||||||
|  |     KnownMimeTypes.Application.VndCluetrustCartomobileConfigPkg, | ||||||
|  |     KnownMimeTypes.Application.MacCompactpro, | ||||||
|  |     KnownMimeTypes.Application.VndWapWmlc, | ||||||
|  |     KnownMimeTypes.Application.VndXara, | ||||||
|  |     KnownMimeTypes.Application.VndCosmocaller, | ||||||
|  |     KnownMimeTypes.Application.XCpio, | ||||||
|  |     KnownMimeTypes.Application.VndCrickClicker, | ||||||
|  |     KnownMimeTypes.Application.VndCrickClickerKeyboard, | ||||||
|  |     KnownMimeTypes.Application.VndCrickClickerPalette, | ||||||
|  |     KnownMimeTypes.Application.VndCrickClickerTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndCrickClickerWordbank, | ||||||
|  |     KnownMimeTypes.Application.VndCriticaltoolsWbsXml, | ||||||
|  |     KnownMimeTypes.Application.VndRigCryptonote, | ||||||
|  |     KnownMimeTypes.Application.CuSeeme, | ||||||
|  |     KnownMimeTypes.Application.PrsCww, | ||||||
|  |     KnownMimeTypes.Application.VndCurlCar, | ||||||
|  |     KnownMimeTypes.Application.VndCurlPcurl, | ||||||
|  |     KnownMimeTypes.Application.VndYellowriverCustomMenu, | ||||||
|  |     KnownMimeTypes.Application.DsscDer, | ||||||
|  |     KnownMimeTypes.Application.DsscXml, | ||||||
|  |     KnownMimeTypes.Application.XDebianPackage, | ||||||
|  |     KnownMimeTypes.Application.XDvi, | ||||||
|  |     KnownMimeTypes.Application.VndFdsnSeed, | ||||||
|  |     KnownMimeTypes.Application.XDtbookXml, | ||||||
|  |     KnownMimeTypes.Application.XDtbresourceXml, | ||||||
|  |     KnownMimeTypes.Application.VndDvbAit, | ||||||
|  |     KnownMimeTypes.Application.VndDvbService, | ||||||
|  |     KnownMimeTypes.Application.XmlDtd, | ||||||
|  |     KnownMimeTypes.Application.VndDolbyMlp, | ||||||
|  |     KnownMimeTypes.Application.XDoom, | ||||||
|  |     KnownMimeTypes.Application.VndDpgraph, | ||||||
|  |     KnownMimeTypes.Application.VndDreamfactory, | ||||||
|  |     KnownMimeTypes.Application.VndDynageo, | ||||||
|  |     KnownMimeTypes.Application.Ecmascript, | ||||||
|  |     KnownMimeTypes.Application.VndEcowinChart, | ||||||
|  |     KnownMimeTypes.Application.Exi, | ||||||
|  |     KnownMimeTypes.Application.VndProteusMagazine, | ||||||
|  |     KnownMimeTypes.Application.EpubZip, | ||||||
|  |     KnownMimeTypes.Application.VndEnliven, | ||||||
|  |     KnownMimeTypes.Application.VndIsXpr, | ||||||
|  |     KnownMimeTypes.Application.VndXfdl, | ||||||
|  |     KnownMimeTypes.Application.EmmaXml, | ||||||
|  |     KnownMimeTypes.Application.VndEzpixAlbum, | ||||||
|  |     KnownMimeTypes.Application.VndEzpixPackage, | ||||||
|  |     KnownMimeTypes.Application.VndDenovoFcselayoutLink, | ||||||
|  |     KnownMimeTypes.Application.VndFluxtimeClip, | ||||||
|  |     KnownMimeTypes.Application.VndFdf, | ||||||
|  |     KnownMimeTypes.Application.VndMif, | ||||||
|  |     KnownMimeTypes.Application.VndFramemaker, | ||||||
|  |     KnownMimeTypes.Application.VndFscWeblaunch, | ||||||
|  |     KnownMimeTypes.Application.VndFrogansFnc, | ||||||
|  |     KnownMimeTypes.Application.VndFrogansLtf, | ||||||
|  |     KnownMimeTypes.Application.VndFujixeroxDdd, | ||||||
|  |     KnownMimeTypes.Application.VndFujixeroxDocuworks, | ||||||
|  |     KnownMimeTypes.Application.VndFujixeroxDocuworksBinder, | ||||||
|  |     KnownMimeTypes.Application.VndFujitsuOasys, | ||||||
|  |     KnownMimeTypes.Application.VndFujitsuOasys2, | ||||||
|  |     KnownMimeTypes.Application.VndFujitsuOasys3, | ||||||
|  |     KnownMimeTypes.Application.VndFujitsuOasysgp, | ||||||
|  |     KnownMimeTypes.Application.VndFujitsuOasysprs, | ||||||
|  |     KnownMimeTypes.Application.XFuturesplash, | ||||||
|  |     KnownMimeTypes.Application.VndFuzzysheet, | ||||||
|  |     KnownMimeTypes.Application.VndGmx, | ||||||
|  |     KnownMimeTypes.Application.VndGenomatixTuxedo, | ||||||
|  |     KnownMimeTypes.Application.VndGeogebraFile, | ||||||
|  |     KnownMimeTypes.Application.VndGeogebraTool, | ||||||
|  |     KnownMimeTypes.Application.VndGeometryExplorer, | ||||||
|  |     KnownMimeTypes.Application.VndGeonext, | ||||||
|  |     KnownMimeTypes.Application.VndGeoplan, | ||||||
|  |     KnownMimeTypes.Application.VndGeospace, | ||||||
|  |     KnownMimeTypes.Application.XFontGhostscript, | ||||||
|  |     KnownMimeTypes.Application.XFontBdf, | ||||||
|  |     KnownMimeTypes.Application.XGtar, | ||||||
|  |     KnownMimeTypes.Application.XTexinfo, | ||||||
|  |     KnownMimeTypes.Application.XGnumeric, | ||||||
|  |     KnownMimeTypes.Application.VndGoogleEarthKmlXml, | ||||||
|  |     KnownMimeTypes.Application.VndGoogleEarthKmz, | ||||||
|  |     KnownMimeTypes.Application.GpxXml, | ||||||
|  |     KnownMimeTypes.Application.VndGrafeq, | ||||||
|  |     KnownMimeTypes.Application.VndGrooveAccount, | ||||||
|  |     KnownMimeTypes.Application.VndGrooveHelp, | ||||||
|  |     KnownMimeTypes.Application.VndGrooveIdentityMessage, | ||||||
|  |     KnownMimeTypes.Application.VndGrooveInjector, | ||||||
|  |     KnownMimeTypes.Application.VndGrooveToolMessage, | ||||||
|  |     KnownMimeTypes.Application.VndGrooveToolTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndGrooveVcard, | ||||||
|  |     KnownMimeTypes.Application.Gzip, | ||||||
|  |     KnownMimeTypes.Application.VndHpHpid, | ||||||
|  |     KnownMimeTypes.Application.VndHpHps, | ||||||
|  |     KnownMimeTypes.Application.XHdf, | ||||||
|  |     KnownMimeTypes.Application.VndHbci, | ||||||
|  |     KnownMimeTypes.Application.VndHpJlyt, | ||||||
|  |     KnownMimeTypes.Application.VndHpPcl, | ||||||
|  |     KnownMimeTypes.Application.VndHpHpgl, | ||||||
|  |     KnownMimeTypes.Application.VndYamahaHvScript, | ||||||
|  |     KnownMimeTypes.Application.VndYamahaHvDic, | ||||||
|  |     KnownMimeTypes.Application.VndYamahaHvVoice, | ||||||
|  |     KnownMimeTypes.Application.VndHydrostatixSofData, | ||||||
|  |     KnownMimeTypes.Application.Hyperstudio, | ||||||
|  |     KnownMimeTypes.Application.VndHalXml, | ||||||
|  |     KnownMimeTypes.Application.VndIbmRightsManagement, | ||||||
|  |     KnownMimeTypes.Application.VndIbmSecureContainer, | ||||||
|  |     KnownMimeTypes.Application.VndIccprofile, | ||||||
|  |     KnownMimeTypes.Application.VndIgloader, | ||||||
|  |     KnownMimeTypes.Application.VndImmervisionIvp, | ||||||
|  |     KnownMimeTypes.Application.VndImmervisionIvu, | ||||||
|  |     KnownMimeTypes.Application.ReginfoXml, | ||||||
|  |     KnownMimeTypes.Application.VndIntergeo, | ||||||
|  |     KnownMimeTypes.Application.VndCinderella, | ||||||
|  |     KnownMimeTypes.Application.VndInterconFormnet, | ||||||
|  |     KnownMimeTypes.Application.VndIsacFcs, | ||||||
|  |     KnownMimeTypes.Application.Ipfix, | ||||||
|  |     KnownMimeTypes.Application.PkixCert, | ||||||
|  |     KnownMimeTypes.Application.Pkixcmp, | ||||||
|  |     KnownMimeTypes.Application.PkixCrl, | ||||||
|  |     KnownMimeTypes.Application.PkixPkipath, | ||||||
|  |     KnownMimeTypes.Application.VndInsorsIgm, | ||||||
|  |     KnownMimeTypes.Application.VndIpunpluggedRcprofile, | ||||||
|  |     KnownMimeTypes.Application.VndIrepositoryPackageXml, | ||||||
|  |     KnownMimeTypes.Application.JavaArchive, | ||||||
|  |     KnownMimeTypes.Application.JavaVm, | ||||||
|  |     KnownMimeTypes.Application.XJavaJnlpFile, | ||||||
|  |     KnownMimeTypes.Application.JavaSerializedObject, | ||||||
|  |     KnownMimeTypes.Application.Javascript, | ||||||
|  |     KnownMimeTypes.Application.Json, | ||||||
|  |     KnownMimeTypes.Application.VndJoostJodaArchive, | ||||||
|  |     KnownMimeTypes.Application.LdJson, | ||||||
|  |     KnownMimeTypes.Application.VndKahootz, | ||||||
|  |     KnownMimeTypes.Application.VndChipnutsKaraokeMmd, | ||||||
|  |     KnownMimeTypes.Application.VndKdeKarbon, | ||||||
|  |     KnownMimeTypes.Application.VndKdeKchart, | ||||||
|  |     KnownMimeTypes.Application.VndKdeKformula, | ||||||
|  |     KnownMimeTypes.Application.VndKdeKivio, | ||||||
|  |     KnownMimeTypes.Application.VndKdeKontour, | ||||||
|  |     KnownMimeTypes.Application.VndKdeKpresenter, | ||||||
|  |     KnownMimeTypes.Application.VndKdeKspread, | ||||||
|  |     KnownMimeTypes.Application.VndKdeKword, | ||||||
|  |     KnownMimeTypes.Application.VndKenameaapp, | ||||||
|  |     KnownMimeTypes.Application.VndKidspiration, | ||||||
|  |     KnownMimeTypes.Application.VndKinar, | ||||||
|  |     KnownMimeTypes.Application.VndKodakDescriptor, | ||||||
|  |     KnownMimeTypes.Application.VndLasLasXml, | ||||||
|  |     KnownMimeTypes.Application.XLatex, | ||||||
|  |     KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceDesktop, | ||||||
|  |     KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceExchangeXml, | ||||||
|  |     KnownMimeTypes.Application.VndJam, | ||||||
|  |     KnownMimeTypes.Application.VndLotus123, | ||||||
|  |     KnownMimeTypes.Application.VndLotusApproach, | ||||||
|  |     KnownMimeTypes.Application.VndLotusFreelance, | ||||||
|  |     KnownMimeTypes.Application.VndLotusNotes, | ||||||
|  |     KnownMimeTypes.Application.VndLotusOrganizer, | ||||||
|  |     KnownMimeTypes.Application.VndLotusScreencam, | ||||||
|  |     KnownMimeTypes.Application.VndLotusWordpro, | ||||||
|  |     KnownMimeTypes.Application.MacBinhex40, | ||||||
|  |     KnownMimeTypes.Application.VndMacportsPortpkg, | ||||||
|  |     KnownMimeTypes.Application.VndOsgeoMapguidePackage, | ||||||
|  |     KnownMimeTypes.Application.Marc, | ||||||
|  |     KnownMimeTypes.Application.MarcxmlXml, | ||||||
|  |     KnownMimeTypes.Application.Mxf, | ||||||
|  |     KnownMimeTypes.Application.VndWolframPlayer, | ||||||
|  |     KnownMimeTypes.Application.Mathematica, | ||||||
|  |     KnownMimeTypes.Application.MathmlXml, | ||||||
|  |     KnownMimeTypes.Application.Mbox, | ||||||
|  |     KnownMimeTypes.Application.VndMedcalcdata, | ||||||
|  |     KnownMimeTypes.Application.MediaservercontrolXml, | ||||||
|  |     KnownMimeTypes.Application.VndMediastationCdkey, | ||||||
|  |     KnownMimeTypes.Application.VndMfer, | ||||||
|  |     KnownMimeTypes.Application.VndMfmp, | ||||||
|  |     KnownMimeTypes.Application.MadsXml, | ||||||
|  |     KnownMimeTypes.Application.MetsXml, | ||||||
|  |     KnownMimeTypes.Application.ModsXml, | ||||||
|  |     KnownMimeTypes.Application.Metalink4Xml, | ||||||
|  |     KnownMimeTypes.Application.VndMcd, | ||||||
|  |     KnownMimeTypes.Application.VndMicrografxFlo, | ||||||
|  |     KnownMimeTypes.Application.VndMicrografxIgx, | ||||||
|  |     KnownMimeTypes.Application.VndEszigno3Xml, | ||||||
|  |     KnownMimeTypes.Application.XMsaccess, | ||||||
|  |     KnownMimeTypes.Application.XMsdownload, | ||||||
|  |     KnownMimeTypes.Application.VndMsArtgalry, | ||||||
|  |     KnownMimeTypes.Application.VndMsCabCompressed, | ||||||
|  |     KnownMimeTypes.Application.VndMsIms, | ||||||
|  |     KnownMimeTypes.Application.XMsApplication, | ||||||
|  |     KnownMimeTypes.Application.XMsclip, | ||||||
|  |     KnownMimeTypes.Application.VndMsFontobject, | ||||||
|  |     KnownMimeTypes.Application.VndMsExcel, | ||||||
|  |     KnownMimeTypes.Application.VndMsExcelAddinMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsExcelSheetBinaryMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsExcelTemplateMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsExcelSheetMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsHtmlhelp, | ||||||
|  |     KnownMimeTypes.Application.XMscardfile, | ||||||
|  |     KnownMimeTypes.Application.VndMsLrm, | ||||||
|  |     KnownMimeTypes.Application.XMsmediaview, | ||||||
|  |     KnownMimeTypes.Application.XMsmoney, | ||||||
|  |     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlPresentation, | ||||||
|  |     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlide, | ||||||
|  |     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlideshow, | ||||||
|  |     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlSheet, | ||||||
|  |     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlDocument, | ||||||
|  |     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlTemplate, | ||||||
|  |     KnownMimeTypes.Application.XMsbinder, | ||||||
|  |     KnownMimeTypes.Application.VndMsOfficetheme, | ||||||
|  |     KnownMimeTypes.Application.Onenote, | ||||||
|  |     KnownMimeTypes.Application.VndMsPowerpoint, | ||||||
|  |     KnownMimeTypes.Application.VndMsPowerpointAddinMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsPowerpointSlideMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsPowerpointPresentationMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsPowerpointSlideshowMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsPowerpointTemplateMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsProject, | ||||||
|  |     KnownMimeTypes.Application.XMspublisher, | ||||||
|  |     KnownMimeTypes.Application.XMsschedule, | ||||||
|  |     KnownMimeTypes.Application.XSilverlightApp, | ||||||
|  |     KnownMimeTypes.Application.VndMsPkiStl, | ||||||
|  |     KnownMimeTypes.Application.VndMsPkiSeccat, | ||||||
|  |     KnownMimeTypes.Application.VndVisio, | ||||||
|  |     KnownMimeTypes.Application.VndVisio2013, | ||||||
|  |     KnownMimeTypes.Application.XMsWmd, | ||||||
|  |     KnownMimeTypes.Application.VndMsWpl, | ||||||
|  |     KnownMimeTypes.Application.XMsWmz, | ||||||
|  |     KnownMimeTypes.Application.XMsmetafile, | ||||||
|  |     KnownMimeTypes.Application.XMsterminal, | ||||||
|  |     KnownMimeTypes.Application.Msword, | ||||||
|  |     KnownMimeTypes.Application.VndMsWordDocumentMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.VndMsWordTemplateMacroenabled12, | ||||||
|  |     KnownMimeTypes.Application.XMswrite, | ||||||
|  |     KnownMimeTypes.Application.VndMsWorks, | ||||||
|  |     KnownMimeTypes.Application.XMsXbap, | ||||||
|  |     KnownMimeTypes.Application.VndMsXpsdocument, | ||||||
|  |     KnownMimeTypes.Application.VndIbmMinipay, | ||||||
|  |     KnownMimeTypes.Application.VndIbmModcap, | ||||||
|  |     KnownMimeTypes.Application.VndJcpJavameMidletRms, | ||||||
|  |     KnownMimeTypes.Application.VndTmobileLivetv, | ||||||
|  |     KnownMimeTypes.Application.XMobipocketEbook, | ||||||
|  |     KnownMimeTypes.Application.VndMobiusMbk, | ||||||
|  |     KnownMimeTypes.Application.VndMobiusDis, | ||||||
|  |     KnownMimeTypes.Application.VndMobiusPlc, | ||||||
|  |     KnownMimeTypes.Application.VndMobiusMqy, | ||||||
|  |     KnownMimeTypes.Application.VndMobiusMsl, | ||||||
|  |     KnownMimeTypes.Application.VndMobiusTxf, | ||||||
|  |     KnownMimeTypes.Application.VndMobiusDaf, | ||||||
|  |     KnownMimeTypes.Application.VndMophunCertificate, | ||||||
|  |     KnownMimeTypes.Application.VndMophunApplication, | ||||||
|  |     KnownMimeTypes.Application.Mp21, | ||||||
|  |     KnownMimeTypes.Application.Mp4, | ||||||
|  |     KnownMimeTypes.Application.VndAppleMpegurl, | ||||||
|  |     KnownMimeTypes.Application.VndMusician, | ||||||
|  |     KnownMimeTypes.Application.VndMuveeStyle, | ||||||
|  |     KnownMimeTypes.Application.XvXml, | ||||||
|  |     KnownMimeTypes.Application.VndNokiaNGageData, | ||||||
|  |     KnownMimeTypes.Application.VndNokiaNGageSymbianInstall, | ||||||
|  |     KnownMimeTypes.Application.XDtbncxXml, | ||||||
|  |     KnownMimeTypes.Application.XNetcdf, | ||||||
|  |     KnownMimeTypes.Application.VndNeurolanguageNlu, | ||||||
|  |     KnownMimeTypes.Application.VndDna, | ||||||
|  |     KnownMimeTypes.Application.VndNoblenetDirectory, | ||||||
|  |     KnownMimeTypes.Application.VndNoblenetSealer, | ||||||
|  |     KnownMimeTypes.Application.VndNoblenetWeb, | ||||||
|  |     KnownMimeTypes.Application.VndNokiaRadioPreset, | ||||||
|  |     KnownMimeTypes.Application.VndNokiaRadioPresets, | ||||||
|  |     KnownMimeTypes.Application.VndNovadigmEdm, | ||||||
|  |     KnownMimeTypes.Application.VndNovadigmEdx, | ||||||
|  |     KnownMimeTypes.Application.VndNovadigmExt, | ||||||
|  |     KnownMimeTypes.Application.VndFlographit, | ||||||
|  |     KnownMimeTypes.Application.Oda, | ||||||
|  |     KnownMimeTypes.Application.Ogg, | ||||||
|  |     KnownMimeTypes.Application.VndOmaDd2Xml, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentTextWeb, | ||||||
|  |     KnownMimeTypes.Application.OebpsPackageXml, | ||||||
|  |     KnownMimeTypes.Application.VndIntuQbo, | ||||||
|  |     KnownMimeTypes.Application.VndOpenofficeorgExtension, | ||||||
|  |     KnownMimeTypes.Application.VndYamahaOpenscoreformat, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentChart, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentChartTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentDatabase, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentFormula, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentFormulaTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentGraphics, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentGraphicsTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentImage, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentImageTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentPresentation, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentPresentationTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheet, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheetTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentText, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentTextMaster, | ||||||
|  |     KnownMimeTypes.Application.VndOasisOpendocumentTextTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlCalc, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlCalcTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlDraw, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlDrawTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlImpress, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlImpressTemplate, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlMath, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlWriter, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlWriterGlobal, | ||||||
|  |     KnownMimeTypes.Application.VndSunXmlWriterTemplate, | ||||||
|  |     KnownMimeTypes.Application.XFontOtf, | ||||||
|  |     KnownMimeTypes.Application.VndYamahaOpenscoreformatOsfpvgXml, | ||||||
|  |     KnownMimeTypes.Application.VndOsgiDp, | ||||||
|  |     KnownMimeTypes.Application.VndPalm, | ||||||
|  |     KnownMimeTypes.Application.VndPawaafile, | ||||||
|  |     KnownMimeTypes.Application.VndHpPclxl, | ||||||
|  |     KnownMimeTypes.Application.VndPicsel, | ||||||
|  |     KnownMimeTypes.Application.PicsRules, | ||||||
|  |     KnownMimeTypes.Application.XChat, | ||||||
|  |     KnownMimeTypes.Application.Pkcs10, | ||||||
|  |     KnownMimeTypes.Application.XPkcs12, | ||||||
|  |     KnownMimeTypes.Application.Pkcs7Mime, | ||||||
|  |     KnownMimeTypes.Application.Pkcs7Signature, | ||||||
|  |     KnownMimeTypes.Application.XPkcs7Certreqresp, | ||||||
|  |     KnownMimeTypes.Application.XPkcs7Certificates, | ||||||
|  |     KnownMimeTypes.Application.Pkcs8, | ||||||
|  |     KnownMimeTypes.Application.VndPocketlearn, | ||||||
|  |     KnownMimeTypes.Application.XFontPcf, | ||||||
|  |     KnownMimeTypes.Application.FontTdpfr, | ||||||
|  |     KnownMimeTypes.Application.XChessPgn, | ||||||
|  |     KnownMimeTypes.Application.PskcXml, | ||||||
|  |     KnownMimeTypes.Application.VndCtcPosml, | ||||||
|  |     KnownMimeTypes.Application.Postscript, | ||||||
|  |     KnownMimeTypes.Application.XFontType1, | ||||||
|  |     KnownMimeTypes.Application.VndPowerbuilder6, | ||||||
|  |     KnownMimeTypes.Application.PgpEncrypted, | ||||||
|  |     KnownMimeTypes.Application.PgpSignature, | ||||||
|  |     KnownMimeTypes.Application.VndPreviewsystemsBox, | ||||||
|  |     KnownMimeTypes.Application.VndPviPtid1, | ||||||
|  |     KnownMimeTypes.Application.PlsXml, | ||||||
|  |     KnownMimeTypes.Application.VndPgFormat, | ||||||
|  |     KnownMimeTypes.Application.VndPgOsasli, | ||||||
|  |     KnownMimeTypes.Application.XFontLinuxPsf, | ||||||
|  |     KnownMimeTypes.Application.VndPublishareDeltaTree, | ||||||
|  |     KnownMimeTypes.Application.VndPmiWidget, | ||||||
|  |     KnownMimeTypes.Application.VndQuarkQuarkxpress, | ||||||
|  |     KnownMimeTypes.Application.VndEpsonEsf, | ||||||
|  |     KnownMimeTypes.Application.VndEpsonMsf, | ||||||
|  |     KnownMimeTypes.Application.VndEpsonSsf, | ||||||
|  |     KnownMimeTypes.Application.VndEpsonQuickanime, | ||||||
|  |     KnownMimeTypes.Application.VndIntuQfx, | ||||||
|  |     KnownMimeTypes.Application.XRarCompressed, | ||||||
|  |     KnownMimeTypes.Application.RsdXml, | ||||||
|  |     KnownMimeTypes.Application.VndRnRealmedia, | ||||||
|  |     KnownMimeTypes.Application.VndRealvncBed, | ||||||
|  |     KnownMimeTypes.Application.VndRecordareMusicxml, | ||||||
|  |     KnownMimeTypes.Application.VndRecordareMusicxmlXml, | ||||||
|  |     KnownMimeTypes.Application.RelaxNgCompactSyntax, | ||||||
|  |     KnownMimeTypes.Application.VndDataVisionRdz, | ||||||
|  |     KnownMimeTypes.Application.RdfXml, | ||||||
|  |     KnownMimeTypes.Application.VndCloantoRp9, | ||||||
|  |     KnownMimeTypes.Application.VndJisp, | ||||||
|  |     KnownMimeTypes.Application.Rtf, | ||||||
|  |     KnownMimeTypes.Application.VndRoute66Link66Xml, | ||||||
|  |     KnownMimeTypes.Application.RssXml, | ||||||
|  |     KnownMimeTypes.Application.ShfXml, | ||||||
|  |     KnownMimeTypes.Application.VndSailingtrackerTrack, | ||||||
|  |     KnownMimeTypes.Application.VndSusCalendar, | ||||||
|  |     KnownMimeTypes.Application.SruXml, | ||||||
|  |     KnownMimeTypes.Application.SetPaymentInitiation, | ||||||
|  |     KnownMimeTypes.Application.SetRegistrationInitiation, | ||||||
|  |     KnownMimeTypes.Application.VndSema, | ||||||
|  |     KnownMimeTypes.Application.VndSemd, | ||||||
|  |     KnownMimeTypes.Application.VndSemf, | ||||||
|  |     KnownMimeTypes.Application.VndSeemail, | ||||||
|  |     KnownMimeTypes.Application.XFontSnf, | ||||||
|  |     KnownMimeTypes.Application.ScvpVpRequest, | ||||||
|  |     KnownMimeTypes.Application.ScvpVpResponse, | ||||||
|  |     KnownMimeTypes.Application.ScvpCvRequest, | ||||||
|  |     KnownMimeTypes.Application.ScvpCvResponse, | ||||||
|  |     KnownMimeTypes.Application.Sdp, | ||||||
|  |     KnownMimeTypes.Application.VndShanaInformedFormdata, | ||||||
|  |     KnownMimeTypes.Application.VndShanaInformedFormtemplate, | ||||||
|  |     KnownMimeTypes.Application.VndShanaInformedInterchange, | ||||||
|  |     KnownMimeTypes.Application.VndShanaInformedPackage, | ||||||
|  |     KnownMimeTypes.Application.ThraudXml, | ||||||
|  |     KnownMimeTypes.Application.XShar, | ||||||
|  |     KnownMimeTypes.Application.VndEpsonSalt, | ||||||
|  |     KnownMimeTypes.Application.VndAccpacSimplyAso, | ||||||
|  |     KnownMimeTypes.Application.VndAccpacSimplyImp, | ||||||
|  |     KnownMimeTypes.Application.VndSimtechMindmapper, | ||||||
|  |     KnownMimeTypes.Application.VndCommonspace, | ||||||
|  |     KnownMimeTypes.Application.VndYamahaSmafAudio, | ||||||
|  |     KnownMimeTypes.Application.VndSmaf, | ||||||
|  |     KnownMimeTypes.Application.VndYamahaSmafPhrase, | ||||||
|  |     KnownMimeTypes.Application.VndSmartTeacher, | ||||||
|  |     KnownMimeTypes.Application.VndSvd, | ||||||
|  |     KnownMimeTypes.Application.SparqlQuery, | ||||||
|  |     KnownMimeTypes.Application.SparqlResultsXml, | ||||||
|  |     KnownMimeTypes.Application.Srgs, | ||||||
|  |     KnownMimeTypes.Application.SrgsXml, | ||||||
|  |     KnownMimeTypes.Application.SsmlXml, | ||||||
|  |     KnownMimeTypes.Application.VndKoan, | ||||||
|  |     KnownMimeTypes.Application.VndStardivisionCalc, | ||||||
|  |     KnownMimeTypes.Application.VndStardivisionDraw, | ||||||
|  |     KnownMimeTypes.Application.VndStardivisionImpress, | ||||||
|  |     KnownMimeTypes.Application.VndStardivisionMath, | ||||||
|  |     KnownMimeTypes.Application.VndStardivisionWriter, | ||||||
|  |     KnownMimeTypes.Application.VndStardivisionWriterGlobal, | ||||||
|  |     KnownMimeTypes.Application.VndStepmaniaStepchart, | ||||||
|  |     KnownMimeTypes.Application.XStuffit, | ||||||
|  |     KnownMimeTypes.Application.XStuffitx, | ||||||
|  |     KnownMimeTypes.Application.VndSolentSdkmXml, | ||||||
|  |     KnownMimeTypes.Application.VndOlpcSugar, | ||||||
|  |     KnownMimeTypes.Application.VndWqd, | ||||||
|  |     KnownMimeTypes.Application.VndSymbianInstall, | ||||||
|  |     KnownMimeTypes.Application.SmilXml, | ||||||
|  |     KnownMimeTypes.Application.VndSyncmlXml, | ||||||
|  |     KnownMimeTypes.Application.VndSyncmlDmWbxml, | ||||||
|  |     KnownMimeTypes.Application.VndSyncmlDmXml, | ||||||
|  |     KnownMimeTypes.Application.XSv4cpio, | ||||||
|  |     KnownMimeTypes.Application.XSv4crc, | ||||||
|  |     KnownMimeTypes.Application.SbmlXml, | ||||||
|  |     KnownMimeTypes.Application.VndTaoIntentModuleArchive, | ||||||
|  |     KnownMimeTypes.Application.XTar, | ||||||
|  |     KnownMimeTypes.Application.XTcl, | ||||||
|  |     KnownMimeTypes.Application.XTex, | ||||||
|  |     KnownMimeTypes.Application.XTexTfm, | ||||||
|  |     KnownMimeTypes.Application.TeiXml, | ||||||
|  |     KnownMimeTypes.Application.VndSpotfireDxp, | ||||||
|  |     KnownMimeTypes.Application.VndSpotfireSfs, | ||||||
|  |     KnownMimeTypes.Application.TimestampedData, | ||||||
|  |     KnownMimeTypes.Application.VndTridTpt, | ||||||
|  |     KnownMimeTypes.Application.VndTriscapeMxs, | ||||||
|  |     KnownMimeTypes.Application.VndTrueapp, | ||||||
|  |     KnownMimeTypes.Application.XFontTtf, | ||||||
|  |     KnownMimeTypes.Application.VndUmajin, | ||||||
|  |     KnownMimeTypes.Application.VndUomlXml, | ||||||
|  |     KnownMimeTypes.Application.VndUnity, | ||||||
|  |     KnownMimeTypes.Application.VndUfdl, | ||||||
|  |     KnownMimeTypes.Application.VndUiqTheme, | ||||||
|  |     KnownMimeTypes.Application.XUstar, | ||||||
|  |     KnownMimeTypes.Application.XCdlink, | ||||||
|  |     KnownMimeTypes.Application.VndVsf, | ||||||
|  |     KnownMimeTypes.Application.VndVcx, | ||||||
|  |     KnownMimeTypes.Application.VndVisionary, | ||||||
|  |     KnownMimeTypes.Application.CcxmlXml, | ||||||
|  |     KnownMimeTypes.Application.VoicexmlXml, | ||||||
|  |     KnownMimeTypes.Application.XWaisSource, | ||||||
|  |     KnownMimeTypes.Application.VndWapWbxml, | ||||||
|  |     KnownMimeTypes.Application.DavmountXml, | ||||||
|  |     KnownMimeTypes.Application.XFontWoff, | ||||||
|  |     KnownMimeTypes.Application.WspolicyXml, | ||||||
|  |     KnownMimeTypes.Application.VndWebturbo, | ||||||
|  |     KnownMimeTypes.Application.Widget, | ||||||
|  |     KnownMimeTypes.Application.Winhlp, | ||||||
|  |     KnownMimeTypes.Application.VndWapWmlscriptc, | ||||||
|  |     KnownMimeTypes.Application.VndWordperfect, | ||||||
|  |     KnownMimeTypes.Application.VndWtStf, | ||||||
|  |     KnownMimeTypes.Application.WsdlXml, | ||||||
|  |     KnownMimeTypes.Application.XX509CaCert, | ||||||
|  |     KnownMimeTypes.Application.XXfig, | ||||||
|  |     KnownMimeTypes.Application.XhtmlXml, | ||||||
|  |     KnownMimeTypes.Application.Xml, | ||||||
|  |     KnownMimeTypes.Application.XcapDiffXml, | ||||||
|  |     KnownMimeTypes.Application.XencXml, | ||||||
|  |     KnownMimeTypes.Application.PatchOpsErrorXml, | ||||||
|  |     KnownMimeTypes.Application.ResourceListsXml, | ||||||
|  |     KnownMimeTypes.Application.RlsServicesXml, | ||||||
|  |     KnownMimeTypes.Application.ResourceListsDiffXml, | ||||||
|  |     KnownMimeTypes.Application.XsltXml, | ||||||
|  |     KnownMimeTypes.Application.XopXml, | ||||||
|  |     KnownMimeTypes.Application.XXpinstall, | ||||||
|  |     KnownMimeTypes.Application.XspfXml, | ||||||
|  |     KnownMimeTypes.Application.VndMozillaXulXml, | ||||||
|  |     KnownMimeTypes.Application.Yang, | ||||||
|  |     KnownMimeTypes.Application.YinXml, | ||||||
|  |     KnownMimeTypes.Application.VndZul, | ||||||
|  |     KnownMimeTypes.Application.Zip, | ||||||
|  |     KnownMimeTypes.Application.VndHandheldEntertainmentXml, | ||||||
|  |     KnownMimeTypes.Application.VndZzazzDeckXml, | ||||||
|  |     KnownMimeTypes.Audio.Adpcm, | ||||||
|  |     KnownMimeTypes.Audio.XAac, | ||||||
|  |     KnownMimeTypes.Audio.XAiff, | ||||||
|  |     KnownMimeTypes.Audio.VndDeceAudio, | ||||||
|  |     KnownMimeTypes.Audio.VndDigitalWinds, | ||||||
|  |     KnownMimeTypes.Audio.VndDra, | ||||||
|  |     KnownMimeTypes.Audio.VndDts, | ||||||
|  |     KnownMimeTypes.Audio.VndDtsHd, | ||||||
|  |     KnownMimeTypes.Audio.VndRip, | ||||||
|  |     KnownMimeTypes.Audio.VndLucentVoice, | ||||||
|  |     KnownMimeTypes.Audio.XMpegurl, | ||||||
|  |     KnownMimeTypes.Audio.VndMsPlayreadyMediaPya, | ||||||
|  |     KnownMimeTypes.Audio.XMsWma, | ||||||
|  |     KnownMimeTypes.Audio.XMsWax, | ||||||
|  |     KnownMimeTypes.Audio.Midi, | ||||||
|  |     KnownMimeTypes.Audio.Mpeg, | ||||||
|  |     KnownMimeTypes.Audio.Mp4, | ||||||
|  |     KnownMimeTypes.Audio.VndNueraEcelp4800, | ||||||
|  |     KnownMimeTypes.Audio.VndNueraEcelp7470, | ||||||
|  |     KnownMimeTypes.Audio.VndNueraEcelp9600, | ||||||
|  |     KnownMimeTypes.Audio.Ogg, | ||||||
|  |     KnownMimeTypes.Audio.Webm, | ||||||
|  |     KnownMimeTypes.Audio.Opus, | ||||||
|  |     KnownMimeTypes.Audio.XPnRealaudio, | ||||||
|  |     KnownMimeTypes.Audio.XPnRealaudioPlugin, | ||||||
|  |     KnownMimeTypes.Audio.Basic, | ||||||
|  |     KnownMimeTypes.Audio.XWav, | ||||||
|  |     KnownMimeTypes.Chemical.XCdx, | ||||||
|  |     KnownMimeTypes.Chemical.XCml, | ||||||
|  |     KnownMimeTypes.Chemical.XCsml, | ||||||
|  |     KnownMimeTypes.Chemical.XCif, | ||||||
|  |     KnownMimeTypes.Chemical.XCmdf, | ||||||
|  |     KnownMimeTypes.Chemical.XXyz, | ||||||
|  |     KnownMimeTypes.Image.VndDxf, | ||||||
|  |     KnownMimeTypes.Image.Avif, | ||||||
|  |     KnownMimeTypes.Image.Bmp, | ||||||
|  |     KnownMimeTypes.Image.PrsBtif, | ||||||
|  |     KnownMimeTypes.Image.VndDvbSubtitle, | ||||||
|  |     KnownMimeTypes.Image.XCmuRaster, | ||||||
|  |     KnownMimeTypes.Image.Cgm, | ||||||
|  |     KnownMimeTypes.Image.XCmx, | ||||||
|  |     KnownMimeTypes.Image.VndDeceGraphic, | ||||||
|  |     KnownMimeTypes.Image.VndDjvu, | ||||||
|  |     KnownMimeTypes.Image.VndDwg, | ||||||
|  |     KnownMimeTypes.Image.VndFujixeroxEdmicsMmr, | ||||||
|  |     KnownMimeTypes.Image.VndFujixeroxEdmicsRlc, | ||||||
|  |     KnownMimeTypes.Image.VndXiff, | ||||||
|  |     KnownMimeTypes.Image.VndFst, | ||||||
|  |     KnownMimeTypes.Image.VndFastbidsheet, | ||||||
|  |     KnownMimeTypes.Image.VndFpx, | ||||||
|  |     KnownMimeTypes.Image.VndNetFpx, | ||||||
|  |     KnownMimeTypes.Image.XFreehand, | ||||||
|  |     KnownMimeTypes.Image.G3fax, | ||||||
|  |     KnownMimeTypes.Image.Gif, | ||||||
|  |     KnownMimeTypes.Image.XIcon, | ||||||
|  |     KnownMimeTypes.Image.Ief, | ||||||
|  |     KnownMimeTypes.Image.Jpeg, | ||||||
|  |     KnownMimeTypes.Image.XCitrixJpeg, | ||||||
|  |     KnownMimeTypes.Image.Pjpeg, | ||||||
|  |     KnownMimeTypes.Image.VndMsModi, | ||||||
|  |     KnownMimeTypes.Image.Ktx, | ||||||
|  |     KnownMimeTypes.Image.XPcx, | ||||||
|  |     KnownMimeTypes.Image.VndAdobePhotoshop, | ||||||
|  |     KnownMimeTypes.Image.XPict, | ||||||
|  |     KnownMimeTypes.Image.XPortableAnymap, | ||||||
|  |     KnownMimeTypes.Image.XPortableBitmap, | ||||||
|  |     KnownMimeTypes.Image.XPortableGraymap, | ||||||
|  |     KnownMimeTypes.Image.Png, | ||||||
|  |     KnownMimeTypes.Image.XCitrixPng, | ||||||
|  |     KnownMimeTypes.Image.XPng, | ||||||
|  |     KnownMimeTypes.Image.XPortablePixmap, | ||||||
|  |     KnownMimeTypes.Image.SvgXml, | ||||||
|  |     KnownMimeTypes.Image.XRgb, | ||||||
|  |     KnownMimeTypes.Image.Tiff, | ||||||
|  |     KnownMimeTypes.Image.VndWapWbmp, | ||||||
|  |     KnownMimeTypes.Image.Webp, | ||||||
|  |     KnownMimeTypes.Image.XXbitmap, | ||||||
|  |     KnownMimeTypes.Image.XXpixmap, | ||||||
|  |     KnownMimeTypes.Image.XXwindowdump, | ||||||
|  |     KnownMimeTypes.Message.Rfc822, | ||||||
|  |     KnownMimeTypes.Model.VndDwf, | ||||||
|  |     KnownMimeTypes.Model.VndColladaXml, | ||||||
|  |     KnownMimeTypes.Model.VndGtw, | ||||||
|  |     KnownMimeTypes.Model.VndGdl, | ||||||
|  |     KnownMimeTypes.Model.Iges, | ||||||
|  |     KnownMimeTypes.Model.Mesh, | ||||||
|  |     KnownMimeTypes.Model.Vrml, | ||||||
|  |     KnownMimeTypes.Model.VndMts, | ||||||
|  |     KnownMimeTypes.Model.VndVtu, | ||||||
|  |     KnownMimeTypes.Text.XAsm, | ||||||
|  |     KnownMimeTypes.Text.PlainBas, | ||||||
|  |     KnownMimeTypes.Text.XC, | ||||||
|  |     KnownMimeTypes.Text.Css, | ||||||
|  |     KnownMimeTypes.Text.Csv, | ||||||
|  |     KnownMimeTypes.Text.VndCurl, | ||||||
|  |     KnownMimeTypes.Text.VndCurlDcurl, | ||||||
|  |     KnownMimeTypes.Text.VndCurlMcurl, | ||||||
|  |     KnownMimeTypes.Text.VndCurlScurl, | ||||||
|  |     KnownMimeTypes.Text.VndFmiFlexstor, | ||||||
|  |     KnownMimeTypes.Text.XFortran, | ||||||
|  |     KnownMimeTypes.Text.VndGraphviz, | ||||||
|  |     KnownMimeTypes.Text.Html, | ||||||
|  |     KnownMimeTypes.Text.Calendar, | ||||||
|  |     KnownMimeTypes.Text.VndIn3d3dml, | ||||||
|  |     KnownMimeTypes.Text.VndIn3dSpot, | ||||||
|  |     KnownMimeTypes.Text.VndSunJ2meAppDescriptor, | ||||||
|  |     KnownMimeTypes.Text.XJavaSourceJava, | ||||||
|  |     KnownMimeTypes.Text.Javascript, | ||||||
|  |     KnownMimeTypes.Text.VndFly, | ||||||
|  |     KnownMimeTypes.Text.N3, | ||||||
|  |     KnownMimeTypes.Text.XPascal, | ||||||
|  |     KnownMimeTypes.Text.PrsLinesTag, | ||||||
|  |     KnownMimeTypes.Text.Richtext, | ||||||
|  |     KnownMimeTypes.Text.XSetext, | ||||||
|  |     KnownMimeTypes.Text.Sgml, | ||||||
|  |     KnownMimeTypes.Text.TabSeparatedValues, | ||||||
|  |     KnownMimeTypes.Text.Plain, | ||||||
|  |     KnownMimeTypes.Text.Troff, | ||||||
|  |     KnownMimeTypes.Text.Turtle, | ||||||
|  |     KnownMimeTypes.Text.UriList, | ||||||
|  |     KnownMimeTypes.Text.XUuencode, | ||||||
|  |     KnownMimeTypes.Text.XVcalendar, | ||||||
|  |     KnownMimeTypes.Text.XVcard, | ||||||
|  |     KnownMimeTypes.Text.VndWapWml, | ||||||
|  |     KnownMimeTypes.Text.VndWapWmlscript, | ||||||
|  |     KnownMimeTypes.Text.Yaml, | ||||||
|  |     KnownMimeTypes.Video.V3gpp, | ||||||
|  |     KnownMimeTypes.Video.V3gpp2, | ||||||
|  |     KnownMimeTypes.Video.XMsvideo, | ||||||
|  |     KnownMimeTypes.Video.VndDeceHd, | ||||||
|  |     KnownMimeTypes.Video.VndDeceMobile, | ||||||
|  |     KnownMimeTypes.Video.VndUvvuMp4, | ||||||
|  |     KnownMimeTypes.Video.VndDecePd, | ||||||
|  |     KnownMimeTypes.Video.VndDeceSd, | ||||||
|  |     KnownMimeTypes.Video.VndDeceVideo, | ||||||
|  |     KnownMimeTypes.Video.VndFvt, | ||||||
|  |     KnownMimeTypes.Video.XF4v, | ||||||
|  |     KnownMimeTypes.Video.XFlv, | ||||||
|  |     KnownMimeTypes.Video.XFli, | ||||||
|  |     KnownMimeTypes.Video.H261, | ||||||
|  |     KnownMimeTypes.Video.H263, | ||||||
|  |     KnownMimeTypes.Video.H264, | ||||||
|  |     KnownMimeTypes.Video.Jpm, | ||||||
|  |     KnownMimeTypes.Video.Jpeg, | ||||||
|  |     KnownMimeTypes.Video.XM4v, | ||||||
|  |     KnownMimeTypes.Video.XMsAsf, | ||||||
|  |     KnownMimeTypes.Video.VndMsPlayreadyMediaPyv, | ||||||
|  |     KnownMimeTypes.Video.XMsWm, | ||||||
|  |     KnownMimeTypes.Video.XMsWmx, | ||||||
|  |     KnownMimeTypes.Video.XMsWmv, | ||||||
|  |     KnownMimeTypes.Video.XMsWvx, | ||||||
|  |     KnownMimeTypes.Video.Mj2, | ||||||
|  |     KnownMimeTypes.Video.Mp2t, | ||||||
|  |     KnownMimeTypes.Video.VndMpegurl, | ||||||
|  |     KnownMimeTypes.Video.Mpeg, | ||||||
|  |     KnownMimeTypes.Video.Mp4, | ||||||
|  |     KnownMimeTypes.Video.Ogg, | ||||||
|  |     KnownMimeTypes.Video.Webm, | ||||||
|  |     KnownMimeTypes.Video.Quicktime, | ||||||
|  |     KnownMimeTypes.Video.XSgiMovie, | ||||||
|  |     KnownMimeTypes.Video.VndVivo, | ||||||
|  |     KnownMimeTypes.XConference.XCooltalk, | ||||||
|  | ) | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | package dev.inmo.micro_utils.mime_types | ||||||
|  |  | ||||||
|  | import kotlinx.serialization.Serializable | ||||||
|  |  | ||||||
|  | @Serializable(MimeTypeSerializer::class) | ||||||
|  | interface MimeType { | ||||||
|  |     val raw: String | ||||||
|  | } | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | package dev.inmo.micro_utils.mime_types | ||||||
|  |  | ||||||
|  | import kotlinx.serialization.KSerializer | ||||||
|  | import kotlinx.serialization.Serializer | ||||||
|  | import kotlinx.serialization.descriptors.PrimitiveKind | ||||||
|  | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor | ||||||
|  | import kotlinx.serialization.descriptors.SerialDescriptor | ||||||
|  | import kotlinx.serialization.encoding.Decoder | ||||||
|  | import kotlinx.serialization.encoding.Encoder | ||||||
|  |  | ||||||
|  | @Suppress("OPT_IN_USAGE") | ||||||
|  | @Serializer(MimeType::class) | ||||||
|  | object MimeTypeSerializer : KSerializer<MimeType> { | ||||||
|  |     override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING) | ||||||
|  |  | ||||||
|  |     override fun deserialize(decoder: Decoder): MimeType { | ||||||
|  |         val mimeType = decoder.decodeString() | ||||||
|  |         return mimeType(mimeType) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun serialize(encoder: Encoder, value: MimeType) { | ||||||
|  |         encoder.encodeString(value.raw) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,10 +1,5 @@ | |||||||
| package dev.inmo.micro_utils.mime_types | package dev.inmo.micro_utils.mime_types | ||||||
|  |  | ||||||
| import kotlinx.serialization.* |  | ||||||
| import kotlinx.serialization.descriptors.* |  | ||||||
| import kotlinx.serialization.encoding.Decoder |  | ||||||
| import kotlinx.serialization.encoding.Encoder |  | ||||||
|  |  | ||||||
| private val mimesCache = mutableMapOf<String, MimeType>().also { | private val mimesCache = mutableMapOf<String, MimeType>().also { | ||||||
|     knownMimeTypes.forEach { mimeType -> it[mimeType.raw] = mimeType } |     knownMimeTypes.forEach { mimeType -> it[mimeType.raw] = mimeType } | ||||||
| } | } | ||||||
| @@ -15,17 +10,3 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) { | |||||||
|  |  | ||||||
| internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw) | internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw) | ||||||
|  |  | ||||||
| @Suppress("OPT_IN_USAGE") |  | ||||||
| @Serializer(MimeType::class) |  | ||||||
| object MimeTypeSerializer : KSerializer<MimeType> { |  | ||||||
|     override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING) |  | ||||||
|  |  | ||||||
|     override fun deserialize(decoder: Decoder): MimeType { |  | ||||||
|         val mimeType = decoder.decodeString() |  | ||||||
|         return mimeType(mimeType) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun serialize(encoder: Encoder, value: MimeType) { |  | ||||||
|         encoder.encodeString(value.raw) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ kotlin { | |||||||
|         commonMain { |         commonMain { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 implementation kotlin('stdlib') |                 implementation kotlin('stdlib') | ||||||
|                 implementation libs.kt.serialization |                 api libs.kt.serialization | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         commonTest { |         commonTest { | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ kotlin { | |||||||
|         commonMain { |         commonMain { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 implementation kotlin('stdlib') |                 implementation kotlin('stdlib') | ||||||
|                 implementation libs.kt.serialization |                 api libs.kt.serialization | ||||||
|                 implementation compose.runtime |                 implementation compose.runtime | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -33,18 +33,6 @@ data class PaginationResult<T>( | |||||||
|         results, |         results, | ||||||
|         (pagesNumber * size).toLong() |         (pagesNumber * size).toLong() | ||||||
|     ) |     ) | ||||||
|     @Deprecated("Replace with The other order of incoming parameters or objectsCount parameter") |  | ||||||
|     constructor( |  | ||||||
|         page: Int, |  | ||||||
|         pagesNumber: Int, |  | ||||||
|         results: List<T>, |  | ||||||
|         size: Int |  | ||||||
|     ) : this( |  | ||||||
|         page, |  | ||||||
|         results, |  | ||||||
|         pagesNumber, |  | ||||||
|         size |  | ||||||
|     ) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L) | fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L) | ||||||
|   | |||||||
| @@ -49,20 +49,39 @@ publishing { | |||||||
|                 maven { |                 maven { | ||||||
|                     name = "GithubPackages" |                     name = "GithubPackages" | ||||||
|                     url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils") |                     url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils") | ||||||
|  |  | ||||||
|                     credentials { |                     credentials { | ||||||
|                         username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER') |                         username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER') | ||||||
|                         password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD') |                         password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD') | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) { | ||||||
|  |                 maven { | ||||||
|  |                     name = "Gitea" | ||||||
|  |                     url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") | ||||||
|  |  | ||||||
|  |                     credentials(HttpHeaderCredentials) { | ||||||
|  |                         name = "Authorization" | ||||||
|  |                         value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     authentication { | ||||||
|  |                         header(HttpHeaderAuthentication) | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { |             if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { | ||||||
|                 maven { |                 maven { | ||||||
|                     name = "sonatype" |                     name = "sonatype" | ||||||
|                     url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") |                     url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") | ||||||
|  |  | ||||||
|                     credentials { |                     credentials { | ||||||
|                         username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') |                         username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') | ||||||
|                         password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') |                         password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -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":"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":"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"}}} | ||||||
| @@ -1,7 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.repos.cache | package dev.inmo.micro_utils.repos.cache | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.pagination.Pagination | import dev.inmo.micro_utils.pagination.* | ||||||
| import dev.inmo.micro_utils.pagination.PaginationResult |  | ||||||
| import dev.inmo.micro_utils.repos.* | import dev.inmo.micro_utils.repos.* | ||||||
| import dev.inmo.micro_utils.repos.cache.cache.KVCache | import dev.inmo.micro_utils.repos.cache.cache.KVCache | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| @@ -14,6 +13,16 @@ open class ReadKeyValueCacheRepo<Key,Value>( | |||||||
| ) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo { | ) : ReadKeyValueRepo<Key,Value> by parentRepo, CacheRepo { | ||||||
|     override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) } |     override suspend fun get(k: Key): Value? = kvCache.get(k) ?: parentRepo.get(k) ?.also { kvCache.set(k, it) } | ||||||
|     override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key) |     override suspend fun contains(key: Key): Boolean = kvCache.contains(key) || parentRepo.contains(key) | ||||||
|  |  | ||||||
|  |     override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> { | ||||||
|  |         return keys(pagination, reversed).let { | ||||||
|  |             it.changeResultsUnchecked( | ||||||
|  |                 it.results.mapNotNull { | ||||||
|  |                     get(it) | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( | fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( | ||||||
|   | |||||||
| @@ -46,6 +46,12 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>( | |||||||
|         { if (it.results.isNotEmpty()) actualizeAll() } |         { if (it.results.isNotEmpty()) actualizeAll() } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = doOrTakeAndActualize( | ||||||
|  |         { keys(pagination).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, | ||||||
|  |         { getIdsByPagination(pagination) }, | ||||||
|  |         { if (it.results.isNotEmpty()) actualizeAll() } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     override suspend fun count(): Long = doOrTakeAndActualize( |     override suspend fun count(): Long = doOrTakeAndActualize( | ||||||
|         { count().takeIf { it != 0L }.optionalOrAbsentIfNull }, |         { count().takeIf { it != 0L }.optionalOrAbsentIfNull }, | ||||||
|         { count() }, |         { count() }, | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ kotlin { | |||||||
|                 api internalProject("micro_utils.common") |                 api internalProject("micro_utils.common") | ||||||
|                 api internalProject("micro_utils.coroutines") |                 api internalProject("micro_utils.coroutines") | ||||||
|             } |             } | ||||||
|  |             dependsOn jvmMain | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,138 @@ | |||||||
|  | package dev.inmo.micro_utils.repos | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.pagination.* | ||||||
|  | import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging | ||||||
|  | import kotlinx.coroutines.flow.Flow | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Read part of [KeyValueRepo] | ||||||
|  |  * | ||||||
|  |  * @param Key This type will be used as key in all operations related to searches of data | ||||||
|  |  * @param Value This type will be used as returning data in most "get" operations | ||||||
|  |  */ | ||||||
|  | interface ReadKeyValueRepo<Key, Value> : Repo { | ||||||
|  |     /** | ||||||
|  |      * @return Result [Value] in case when it is presented in repo by its [k] or null otherwise | ||||||
|  |      */ | ||||||
|  |     suspend fun get(k: Key): Value? | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use | ||||||
|  |      * ascending sort for [Key]s | ||||||
|  |      */ | ||||||
|  |     suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use | ||||||
|  |      * ascending sort for [Key]s | ||||||
|  |      */ | ||||||
|  |     suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use | ||||||
|  |      * ascending sort for [Key]s | ||||||
|  |      * | ||||||
|  |      * @param v This value should be used to exclude from search the items with different [Value]s | ||||||
|  |      */ | ||||||
|  |     suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @return true if [key] is presented in current collection or false otherwise | ||||||
|  |      */ | ||||||
|  |     suspend fun contains(key: Key): Boolean | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @return count of all collection objects | ||||||
|  |      */ | ||||||
|  |     suspend fun count(): Long | ||||||
|  | } | ||||||
|  | typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value> | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Write part of [KeyValueRepo] | ||||||
|  |  * | ||||||
|  |  * @param Key This type will be used as key in all operations related to changes of data | ||||||
|  |  * @param Value This type will be used as incoming data in most operations | ||||||
|  |  */ | ||||||
|  | interface WriteKeyValueRepo<Key, Value> : Repo { | ||||||
|  |     /** | ||||||
|  |      * This flow must emit data each time when data by [Key] has been changed with [set] method or in any other way | ||||||
|  |      * excluding cases of data removing | ||||||
|  |      * | ||||||
|  |      * @see onValueRemoved | ||||||
|  |      */ | ||||||
|  |     val onNewValue: Flow<Pair<Key, Value>> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * This flow must emit data each time when data by [Key] has been removed with [unset]/[unsetWithValues] methods or | ||||||
|  |      * in any other way | ||||||
|  |      * | ||||||
|  |      * @see onNewValue | ||||||
|  |      */ | ||||||
|  |     val onValueRemoved: Flow<Key> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Will set as batch [toSet] data in current repo. Must pass the data which were successfully updated in repo to | ||||||
|  |      * [onNewValue] | ||||||
|  |      */ | ||||||
|  |     suspend fun set(toSet: Map<Key, Value>) | ||||||
|  |     /** | ||||||
|  |      * Will unset as batch data with keys from [toUnset]. Must pass the [Key]s which were successfully removed in repo to | ||||||
|  |      * [onValueRemoved] | ||||||
|  |      */ | ||||||
|  |     suspend fun unset(toUnset: List<Key>) | ||||||
|  |     /** | ||||||
|  |      * Will unset as batch data with values from [toUnset]. Must pass the [Key]s which were successfully removed in repo | ||||||
|  |      * to [onValueRemoved] | ||||||
|  |      */ | ||||||
|  |     suspend fun unsetWithValues(toUnset: List<Value>) | ||||||
|  | } | ||||||
|  | typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value> | ||||||
|  |  | ||||||
|  | suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set( | ||||||
|  |     vararg toSet: Pair<Key, Value> | ||||||
|  | ) = set(toSet.toMap()) | ||||||
|  |  | ||||||
|  | suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set( | ||||||
|  |     k: Key, v: Value | ||||||
|  | ) = set(k to v) | ||||||
|  |  | ||||||
|  | suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset( | ||||||
|  |     vararg k: Key | ||||||
|  | ) = unset(k.toList()) | ||||||
|  |  | ||||||
|  | suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues( | ||||||
|  |     vararg v: Value | ||||||
|  | ) = unsetWithValues(v.toList()) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Full version of standard key-value repository with all set/unset/clear/get methods | ||||||
|  |  */ | ||||||
|  | interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> { | ||||||
|  |     /** | ||||||
|  |      * By default, will walk throw all the [keys] with [Value]s from [toUnset] and run [doAllWithCurrentPaging] with | ||||||
|  |      * [unset] of found data [Key]s | ||||||
|  |      */ | ||||||
|  |     override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v -> | ||||||
|  |         doAllWithCurrentPaging { | ||||||
|  |             keys(v, it).also { | ||||||
|  |                 unset(it.results) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * By default, will remove all the data of current repo using [doAllWithCurrentPaging], [keys] and [unset] | ||||||
|  |      */ | ||||||
|  |     suspend fun clear() { | ||||||
|  |         doAllWithCurrentPaging { keys(it).also { unset(it.results) } } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value> | ||||||
|  |  | ||||||
|  | class DelegateBasedKeyValueRepo<Key, Value>( | ||||||
|  |     readDelegate: ReadKeyValueRepo<Key, Value>, | ||||||
|  |     writeDelegate: WriteKeyValueRepo<Key, Value> | ||||||
|  | ) : KeyValueRepo<Key, Value>, | ||||||
|  |     ReadKeyValueRepo<Key, Value> by readDelegate, | ||||||
|  |     WriteKeyValueRepo<Key, Value> by writeDelegate | ||||||
| @@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.Flow | |||||||
|  |  | ||||||
| interface ReadCRUDRepo<ObjectType, IdType> : Repo { | interface ReadCRUDRepo<ObjectType, IdType> : Repo { | ||||||
|     suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> |     suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType> | ||||||
|  |     suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> | ||||||
|     suspend fun getById(id: IdType): ObjectType? |     suspend fun getById(id: IdType): ObjectType? | ||||||
|     suspend fun contains(id: IdType): Boolean |     suspend fun contains(id: IdType): Boolean | ||||||
|     suspend fun count(): Long |     suspend fun count(): Long | ||||||
|   | |||||||
| @@ -1,63 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.repos |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.pagination.* |  | ||||||
| import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging |  | ||||||
| import kotlinx.coroutines.flow.Flow |  | ||||||
|  |  | ||||||
| interface ReadKeyValueRepo<Key, Value> : Repo { |  | ||||||
|     suspend fun get(k: Key): Value? |  | ||||||
|     suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value> |  | ||||||
|     suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> |  | ||||||
|     suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> |  | ||||||
|     suspend fun contains(key: Key): Boolean |  | ||||||
|     suspend fun count(): Long |  | ||||||
| } |  | ||||||
| typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value> |  | ||||||
|  |  | ||||||
| interface WriteKeyValueRepo<Key, Value> : Repo { |  | ||||||
|     val onNewValue: Flow<Pair<Key, Value>> |  | ||||||
|     val onValueRemoved: Flow<Key> |  | ||||||
|  |  | ||||||
|     suspend fun set(toSet: Map<Key, Value>) |  | ||||||
|     suspend fun unset(toUnset: List<Key>) |  | ||||||
|     suspend fun unsetWithValues(toUnset: List<Value>) |  | ||||||
| } |  | ||||||
| typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value> |  | ||||||
|  |  | ||||||
| suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set( |  | ||||||
|     vararg toSet: Pair<Key, Value> |  | ||||||
| ) = set(toSet.toMap()) |  | ||||||
|  |  | ||||||
| suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set( |  | ||||||
|     k: Key, v: Value |  | ||||||
| ) = set(k to v) |  | ||||||
|  |  | ||||||
| suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset( |  | ||||||
|     vararg k: Key |  | ||||||
| ) = unset(k.toList()) |  | ||||||
|  |  | ||||||
| suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues( |  | ||||||
|     vararg v: Value |  | ||||||
| ) = unsetWithValues(v.toList()) |  | ||||||
|  |  | ||||||
| interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> { |  | ||||||
|     override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v -> |  | ||||||
|         doAllWithCurrentPaging { |  | ||||||
|             keys(v, it).also { |  | ||||||
|                 unset(it.results) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     suspend fun clear() { |  | ||||||
|         doAllWithCurrentPaging { keys(it).also { unset(it.results) } } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value> |  | ||||||
|  |  | ||||||
| class DelegateBasedKeyValueRepo<Key, Value>( |  | ||||||
|     readDelegate: ReadKeyValueRepo<Key, Value>, |  | ||||||
|     writeDelegate: WriteKeyValueRepo<Key, Value> |  | ||||||
| ) : KeyValueRepo<Key, Value>, |  | ||||||
|     ReadKeyValueRepo<Key, Value> by readDelegate, |  | ||||||
|     WriteKeyValueRepo<Key, Value> by writeDelegate |  | ||||||
| @@ -20,6 +20,14 @@ open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>( | |||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<FromId> = to.getIdsByPagination( | ||||||
|  |         pagination | ||||||
|  |     ).let { | ||||||
|  |         it.changeResultsUnchecked( | ||||||
|  |             it.results.map { it.toInnerKey() } | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun count(): Long = to.count() |     override suspend fun count(): Long = to.count() | ||||||
|  |  | ||||||
|     override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey()) |     override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey()) | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>( | |||||||
|     protected abstract val tableName: String |     protected abstract val tableName: String | ||||||
|     protected abstract val idColumnName: String |     protected abstract val idColumnName: String | ||||||
|     protected abstract suspend fun Cursor.toObject(): ObjectType |     protected abstract suspend fun Cursor.toObject(): ObjectType | ||||||
|  |     protected abstract suspend fun Cursor.toId(): IdType | ||||||
|     protected fun SQLiteDatabase.count(): Long = select(tableName).use { |     protected fun SQLiteDatabase.count(): Long = select(tableName).use { | ||||||
|         it.count |         it.count | ||||||
|     }.toLong() |     }.toLong() | ||||||
| @@ -64,4 +65,23 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>( | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> { | ||||||
|  |         return helper.readableTransaction { | ||||||
|  |             select( | ||||||
|  |                 tableName, | ||||||
|  |                 limit = pagination.limitClause() | ||||||
|  |             ).use { | ||||||
|  |                 if (it.moveToFirst()) { | ||||||
|  |                     val resultList = mutableListOf(it.toId()) | ||||||
|  |                     while (it.moveToNext()) { | ||||||
|  |                         resultList.add(it.toId()) | ||||||
|  |                     } | ||||||
|  |                     resultList.createPaginationResult(pagination, count()) | ||||||
|  |                 } else { | ||||||
|  |                     emptyList<IdType>().createPaginationResult(pagination, 0) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ fun <T : Any> Context.keyValueStore( | |||||||
| ): KeyValueRepo<String, T> { | ): KeyValueRepo<String, T> { | ||||||
|     @Suppress("UNCHECKED_CAST") |     @Suppress("UNCHECKED_CAST") | ||||||
|     return cache.getOrPut(name) { |     return cache.getOrPut(name) { | ||||||
|         KeyValueStore<T>(this, name, cacheValues) |         KeyValueStore<T>(c = this, preferencesName = name, useCache = cacheValues) | ||||||
|     } as KeyValueStore<T> |     } as KeyValueStore<T> | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -149,6 +149,14 @@ class KeyValueStore<T : Any> internal constructor ( | |||||||
|             _onValueRemovedFlow.emit(it) |             _onValueRemovedFlow.emit(it) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         operator fun <T : Any> invoke( | ||||||
|  |             context: Context, | ||||||
|  |             name: String = "default", | ||||||
|  |             cacheValues: Boolean = false | ||||||
|  |         ) = context.keyValueStore<T>(name, cacheValues) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| inline fun <T : Any> SharedPreferencesKeyValueRepo( | inline fun <T : Any> SharedPreferencesKeyValueRepo( | ||||||
| @@ -156,3 +164,5 @@ inline fun <T : Any> SharedPreferencesKeyValueRepo( | |||||||
|     name: String = "default", |     name: String = "default", | ||||||
|     cacheValues: Boolean = false |     cacheValues: Boolean = false | ||||||
| ) = context.keyValueStore<T>(name, cacheValues) | ) = context.keyValueStore<T>(name, cacheValues) | ||||||
|  |  | ||||||
|  | typealias KeyValueSPRepo<T> = KeyValueStore<T> | ||||||
|   | |||||||
| @@ -22,6 +22,17 @@ abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>( | |||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> { | ||||||
|  |         return transaction(db = database) { | ||||||
|  |             selectAll().paginate(pagination).map { | ||||||
|  |                 it.asId | ||||||
|  |             }.createPaginationResult( | ||||||
|  |                 pagination, | ||||||
|  |                 selectAll().count() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|     override suspend fun getById(id: IdType): ObjectType? { |     override suspend fun getById(id: IdType): ObjectType? { | ||||||
|         return transaction(db = database) { |         return transaction(db = database) { | ||||||
|             select { |             select { | ||||||
|   | |||||||
| @@ -4,9 +4,9 @@ import dev.inmo.micro_utils.repos.UpdatedValuePair | |||||||
| import dev.inmo.micro_utils.repos.WriteCRUDRepo | import dev.inmo.micro_utils.repos.WriteCRUDRepo | ||||||
| import kotlinx.coroutines.flow.* | import kotlinx.coroutines.flow.* | ||||||
| import org.jetbrains.exposed.sql.* | import org.jetbrains.exposed.sql.* | ||||||
| import org.jetbrains.exposed.sql.statements.InsertStatement | import org.jetbrains.exposed.sql.statements.* | ||||||
| import org.jetbrains.exposed.sql.statements.UpdateStatement |  | ||||||
| import org.jetbrains.exposed.sql.transactions.transaction | import org.jetbrains.exposed.sql.transactions.transaction | ||||||
|  | import java.util.Objects | ||||||
|  |  | ||||||
| abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | ||||||
|     flowsChannelsSize: Int = 0, |     flowsChannelsSize: Int = 0, | ||||||
| @@ -27,10 +27,39 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | |||||||
|  |  | ||||||
|     protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType |     protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType | ||||||
|  |  | ||||||
|     protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>) |     /** | ||||||
|     protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement) |      * @param id Can be null only if [createAndInsertId] have returned null (it can be useful when you have | ||||||
|  |      * autoincrement identifier) | ||||||
|  |      * @param it Will be [UpdateStatement] when it is called from [update] method or [InsertStatement] from the [create] | ||||||
|  |      * one. Anyway, it is main method where you should put the logic of table filling by [value] data | ||||||
|  |      * | ||||||
|  |      * @see createAndInsertId | ||||||
|  |      */ | ||||||
|  |     protected abstract fun update(id: IdType?, value: InputValueType, it: UpdateBuilder<Int>) | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Override this method to interact with [it] ([InsertStatement]) and put there new id with [IdType]. | ||||||
|  |      * | ||||||
|  |      * By default, have null value due to the fact that in the most cases users have [autoIncrement]ing id columns | ||||||
|  |      * | ||||||
|  |      * @return In case when id for the model has been created new [IdType] should be returned | ||||||
|  |      */ | ||||||
|  |     protected open fun createAndInsertId(value: InputValueType, it: InsertStatement<Number>): IdType? = null | ||||||
|  |  | ||||||
|  |     protected open fun insert(value: InputValueType, it: InsertStatement<Number>) { | ||||||
|  |         val id = createAndInsertId(value, it) | ||||||
|  |         update(id, value, it as UpdateBuilder<Int>) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     protected open suspend fun onBeforeCreate(value: List<InputValueType>) {} |     protected open suspend fun onBeforeCreate(value: List<InputValueType>) {} | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Use this method to do the something with [values]. You may change and output values in that list and return | ||||||
|  |      * changed list of pairs | ||||||
|  |      */ | ||||||
|  |     protected open suspend fun onAfterCreate( | ||||||
|  |         values: List<Pair<InputValueType, ObjectType>> | ||||||
|  |     ): List<ObjectType> = values.map { it.second } | ||||||
|     private fun createWithoutNotification(value: InputValueType): ObjectType { |     private fun createWithoutNotification(value: InputValueType): ObjectType { | ||||||
|         return transaction(database) { |         return transaction(database) { | ||||||
|             insert { insert(value, it) }.asObject(value) |             insert { insert(value, it) }.asObject(value) | ||||||
| @@ -40,13 +69,18 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | |||||||
|     override suspend fun create(values: List<InputValueType>): List<ObjectType> { |     override suspend fun create(values: List<InputValueType>): List<ObjectType> { | ||||||
|         onBeforeCreate(values) |         onBeforeCreate(values) | ||||||
|         return transaction(db = database) { |         return transaction(db = database) { | ||||||
|             values.map { value -> createWithoutNotification(value) } |             values.map { value -> value to createWithoutNotification(value) } | ||||||
|  |         }.let { | ||||||
|  |             onAfterCreate(it) | ||||||
|         }.onEach { |         }.onEach { | ||||||
|             _newObjectsFlow.emit(it) |             _newObjectsFlow.emit(it) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected open suspend fun onBeforeUpdate(value: List<UpdatedValuePair<IdType, InputValueType>>) {} |     protected open suspend fun onBeforeUpdate(value: List<UpdatedValuePair<IdType, InputValueType>>) {} | ||||||
|  |     protected open suspend fun onAfterUpdate( | ||||||
|  |         value: List<UpdatedValuePair<InputValueType, ObjectType>> | ||||||
|  |     ): List<ObjectType> = value.map { it.second } | ||||||
|     private fun updateWithoutNotification(id: IdType, value: InputValueType): ObjectType? { |     private fun updateWithoutNotification(id: IdType, value: InputValueType): ObjectType? { | ||||||
|         return transaction(db = database) { |         return transaction(db = database) { | ||||||
|             update( |             update( | ||||||
| @@ -54,7 +88,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | |||||||
|                     selectById(this, id) |                     selectById(this, id) | ||||||
|                 } |                 } | ||||||
|             ) { |             ) { | ||||||
|                 update(id, value, it) |                 update(id, value, it as UpdateBuilder<Int>) | ||||||
|             } |             } | ||||||
|         }.let { |         }.let { | ||||||
|             if (it > 0) { |             if (it > 0) { | ||||||
| @@ -71,7 +105,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | |||||||
|  |  | ||||||
|     override suspend fun update(id: IdType, value: InputValueType): ObjectType? { |     override suspend fun update(id: IdType, value: InputValueType): ObjectType? { | ||||||
|         onBeforeUpdate(listOf(id to value)) |         onBeforeUpdate(listOf(id to value)) | ||||||
|         return updateWithoutNotification(id, value).also { |         return updateWithoutNotification(id, value).let { | ||||||
|  |             onAfterUpdate(listOf(value to (it ?: return@let emptyList()))) | ||||||
|  |         }.firstOrNull().also { | ||||||
|             if (it != null) { |             if (it != null) { | ||||||
|                 _updatedObjectsFlow.emit(it) |                 _updatedObjectsFlow.emit(it) | ||||||
|             } |             } | ||||||
| @@ -81,9 +117,11 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | |||||||
|         onBeforeUpdate(values) |         onBeforeUpdate(values) | ||||||
|         return ( |         return ( | ||||||
|             transaction(db = database) { |             transaction(db = database) { | ||||||
|                 values.map { (id, value) -> updateWithoutNotification(id, value) } |                 values.mapNotNull { (id, value) -> value to (updateWithoutNotification(id, value) ?: return@mapNotNull null) } | ||||||
|             }.filterNotNull() |             } | ||||||
|         ).onEach { |         ).let { | ||||||
|  |             onAfterUpdate(it) | ||||||
|  |         }.onEach { | ||||||
|             _updatedObjectsFlow.emit(it) |             _updatedObjectsFlow.emit(it) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -91,9 +129,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>( | |||||||
|     override suspend fun deleteById(ids: List<IdType>) { |     override suspend fun deleteById(ids: List<IdType>) { | ||||||
|         onBeforeDelete(ids) |         onBeforeDelete(ids) | ||||||
|         transaction(db = database) { |         transaction(db = database) { | ||||||
|             val deleted = deleteWhere(null, null) { |             val deleted = deleteWhere(null, null) { selectByIds(it, ids) } | ||||||
|                 selectByIds(ids) |  | ||||||
|             } |  | ||||||
|             if (deleted == ids.size) { |             if (deleted == ids.size) { | ||||||
|                 ids |                 ids | ||||||
|             } else { |             } else { | ||||||
|   | |||||||
| @@ -4,8 +4,9 @@ import org.jetbrains.exposed.sql.* | |||||||
|  |  | ||||||
| interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo { | interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo { | ||||||
|     val ResultRow.asObject: ObjectType |     val ResultRow.asObject: ObjectType | ||||||
|     val selectById: SqlExpressionBuilder.(IdType) -> Op<Boolean> |     val ResultRow.asId: IdType | ||||||
|     val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean> |     val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean> | ||||||
|  |     val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean> | ||||||
|         get() = { list -> |         get() = { list -> | ||||||
|             if (list.isEmpty()) { |             if (list.isEmpty()) { | ||||||
|                 Op.FALSE |                 Op.FALSE | ||||||
|   | |||||||
| @@ -3,8 +3,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue | |||||||
| import dev.inmo.micro_utils.repos.KeyValueRepo | import dev.inmo.micro_utils.repos.KeyValueRepo | ||||||
| import kotlinx.coroutines.flow.* | import kotlinx.coroutines.flow.* | ||||||
| import org.jetbrains.exposed.sql.* | import org.jetbrains.exposed.sql.* | ||||||
| import org.jetbrains.exposed.sql.statements.InsertStatement | import org.jetbrains.exposed.sql.statements.* | ||||||
| import org.jetbrains.exposed.sql.statements.UpdateStatement |  | ||||||
| import org.jetbrains.exposed.sql.transactions.transaction | import org.jetbrains.exposed.sql.transactions.transaction | ||||||
|  |  | ||||||
| abstract class AbstractExposedKeyValueRepo<Key, Value>( | abstract class AbstractExposedKeyValueRepo<Key, Value>( | ||||||
| @@ -20,13 +19,18 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>( | |||||||
|     override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow() |     override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow() | ||||||
|     override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow() |     override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow() | ||||||
|  |  | ||||||
|     protected abstract fun update(k: Key, v: Value, it: UpdateStatement) |     protected abstract fun update(k: Key, v: Value, it: UpdateBuilder<Int>) | ||||||
|     protected abstract fun insert(k: Key, v: Value, it: InsertStatement<Number>) |     protected abstract fun insertKey(k: Key, v: Value, it: InsertStatement<Number>) | ||||||
|  |  | ||||||
|  |     protected open fun insert(k: Key, v: Value, it: InsertStatement<Number>) { | ||||||
|  |         insertKey(k, v, it) | ||||||
|  |         update(k, v, it as UpdateBuilder<Int>) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun set(toSet: Map<Key, Value>) { |     override suspend fun set(toSet: Map<Key, Value>) { | ||||||
|         transaction(database) { |         transaction(database) { | ||||||
|             toSet.mapNotNull { (k, v) -> |             toSet.mapNotNull { (k, v) -> | ||||||
|                 if (update({ selectById(k) }) { update(k, v, it) } > 0) { |                 if (update({ selectById(k) }) { update(k, v, it as UpdateBuilder<Int>) } > 0) { | ||||||
|                     k to v |                     k to v | ||||||
|                 } else { |                 } else { | ||||||
|                     val inserted = insert { |                     val inserted = insert { | ||||||
| @@ -46,9 +50,9 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>( | |||||||
|  |  | ||||||
|     override suspend fun unset(toUnset: List<Key>) { |     override suspend fun unset(toUnset: List<Key>) { | ||||||
|         transaction(database) { |         transaction(database) { | ||||||
|             toUnset.mapNotNull { |             toUnset.mapNotNull { item -> | ||||||
|                 if (deleteWhere { selectById(it) } > 0) { |                 if (deleteWhere { selectById(it, item) } > 0) { | ||||||
|                     it |                     item | ||||||
|                 } else { |                 } else { | ||||||
|                     null |                     null | ||||||
|                 } |                 } | ||||||
| @@ -62,7 +66,7 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>( | |||||||
|         transaction(database) { |         transaction(database) { | ||||||
|             toUnset.flatMap { |             toUnset.flatMap { | ||||||
|                 val keys = select { selectByValue(it) }.mapNotNull { it.asKey } |                 val keys = select { selectByValue(it) }.mapNotNull { it.asKey } | ||||||
|                 deleteWhere { selectByIds(keys) } |                 deleteWhere { selectByIds(it, keys) } | ||||||
|                 keys |                 keys | ||||||
|             } |             } | ||||||
|         }.distinct().forEach { |         }.distinct().forEach { | ||||||
|   | |||||||
| @@ -15,8 +15,14 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>( | |||||||
|     CommonExposedRepo<Key, Value>, |     CommonExposedRepo<Key, Value>, | ||||||
|     Table(tableName ?: "") { |     Table(tableName ?: "") { | ||||||
|     abstract val keyColumn: Column<*> |     abstract val keyColumn: Column<*> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Same as [asId] in context of KeyValue repo | ||||||
|  |      */ | ||||||
|     abstract val ResultRow.asKey: Key |     abstract val ResultRow.asKey: Key | ||||||
|     abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean> |     override val ResultRow.asId: Key | ||||||
|  |         get() = asKey | ||||||
|  |     abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> | ||||||
|  |  | ||||||
|     override suspend fun get(k: Key): Value? = transaction(database) { |     override suspend fun get(k: Key): Value? = transaction(database) { | ||||||
|         select { selectById(k) }.limit(1).firstOrNull() ?.asObject |         select { selectById(k) }.limit(1).firstOrNull() ?.asObject | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ import dev.inmo.micro_utils.repos.KeyValueRepo | |||||||
| import dev.inmo.micro_utils.repos.exposed.ColumnAllocator | import dev.inmo.micro_utils.repos.exposed.ColumnAllocator | ||||||
| import kotlinx.coroutines.flow.* | import kotlinx.coroutines.flow.* | ||||||
| import org.jetbrains.exposed.sql.* | import org.jetbrains.exposed.sql.* | ||||||
|  | import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq | ||||||
|  | import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList | ||||||
| import org.jetbrains.exposed.sql.transactions.transaction | import org.jetbrains.exposed.sql.transactions.transaction | ||||||
|  |  | ||||||
| open class ExposedKeyValueRepo<Key, Value>( | open class ExposedKeyValueRepo<Key, Value>( | ||||||
| @@ -47,9 +49,9 @@ open class ExposedKeyValueRepo<Key, Value>( | |||||||
|  |  | ||||||
|     override suspend fun unset(toUnset: List<Key>) { |     override suspend fun unset(toUnset: List<Key>) { | ||||||
|         transaction(database) { |         transaction(database) { | ||||||
|             toUnset.mapNotNull { |             toUnset.mapNotNull { item -> | ||||||
|                 if (deleteWhere { keyColumn.eq(it) } > 0) { |                 if (deleteWhere { keyColumn.eq(item) } > 0) { | ||||||
|                     it |                     item | ||||||
|                 } else { |                 } else { | ||||||
|                     null |                     null | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -5,57 +5,27 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo | |||||||
| import dev.inmo.micro_utils.repos.exposed.* | import dev.inmo.micro_utils.repos.exposed.* | ||||||
| import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated | import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated | ||||||
| import org.jetbrains.exposed.sql.* | import org.jetbrains.exposed.sql.* | ||||||
|  | import org.jetbrains.exposed.sql.statements.InsertStatement | ||||||
|  | import org.jetbrains.exposed.sql.statements.UpdateBuilder | ||||||
| import org.jetbrains.exposed.sql.transactions.transaction | import org.jetbrains.exposed.sql.transactions.transaction | ||||||
|  |  | ||||||
| open class ExposedReadKeyValueRepo<Key, Value>( | open class ExposedReadKeyValueRepo<Key, Value>( | ||||||
|     override val database: Database, |     database: Database, | ||||||
|     keyColumnAllocator: ColumnAllocator<Key>, |     keyColumnAllocator: ColumnAllocator<Key>, | ||||||
|     valueColumnAllocator: ColumnAllocator<Value>, |     valueColumnAllocator: ColumnAllocator<Value>, | ||||||
|     tableName: String? = null |     tableName: String? = null | ||||||
| ) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") { | ) : ReadKeyValueRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValueRepo<Key, Value>(database, tableName) { | ||||||
|     val keyColumn: Column<Key> = keyColumnAllocator() |  | ||||||
|  |     override val keyColumn: Column<Key> = keyColumnAllocator() | ||||||
|     val valueColumn: Column<Value> = valueColumnAllocator() |     val valueColumn: Column<Value> = valueColumnAllocator() | ||||||
|     override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn) |     override val ResultRow.asKey: Key | ||||||
|  |         get() = get(keyColumn) | ||||||
|  |     override val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) } | ||||||
|  |     override val ResultRow.asObject: Value | ||||||
|  |         get() = get(valueColumn) | ||||||
|  |     override val selectById: ISqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) } | ||||||
|  |     override val primaryKey: Table.PrimaryKey | ||||||
|  |         get() = PrimaryKey(keyColumn, valueColumn) | ||||||
|  |  | ||||||
|     init { initTable() } |     init { initTable() } | ||||||
|  |  | ||||||
|     override suspend fun get(k: Key): Value? = transaction(database) { |  | ||||||
|         select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override suspend fun contains(key: Key): Boolean = transaction(database) { |  | ||||||
|         select { keyColumn.eq(key) }.limit(1).any() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override suspend fun count(): Long = transaction(database) { selectAll().count() } |  | ||||||
|  |  | ||||||
|     override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) { |  | ||||||
|         selectAll().selectPaginated( |  | ||||||
|             pagination, |  | ||||||
|             keyColumn, |  | ||||||
|             reversed |  | ||||||
|         ) { |  | ||||||
|             it[keyColumn] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) { |  | ||||||
|         select { valueColumn.eq(v) }.selectPaginated( |  | ||||||
|             pagination, |  | ||||||
|             keyColumn, |  | ||||||
|             reversed |  | ||||||
|         ) { |  | ||||||
|             it[keyColumn] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) { |  | ||||||
|         selectAll().selectPaginated( |  | ||||||
|             pagination, |  | ||||||
|             keyColumn, |  | ||||||
|             reversed |  | ||||||
|         ) { |  | ||||||
|             it[valueColumn] |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -49,7 +49,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>( | |||||||
|         transaction(database) { |         transaction(database) { | ||||||
|             toRemove.keys.flatMap { k -> |             toRemove.keys.flatMap { k -> | ||||||
|                 toRemove[k] ?.mapNotNull { v -> |                 toRemove[k] ?.mapNotNull { v -> | ||||||
|                     if (deleteWhere { selectById(k).and(selectByValue(v)) } > 0 ) { |                     if (deleteWhere { selectById(it, k).and(SqlExpressionBuilder.selectByValue(v)) } > 0 ) { | ||||||
|                         k to v |                         k to v | ||||||
|                     } else { |                     } else { | ||||||
|                         null |                         null | ||||||
| @@ -63,7 +63,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>( | |||||||
|  |  | ||||||
|     override suspend fun clear(k: Key) { |     override suspend fun clear(k: Key) { | ||||||
|         transaction(database) { |         transaction(database) { | ||||||
|             deleteWhere { selectById(k) } |             deleteWhere { selectById(it, k) } | ||||||
|         }.also { _onDataCleared.emit(k) } |         }.also { _onDataCleared.emit(k) } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,7 +17,9 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>( | |||||||
|     Table(tableName ?: "") { |     Table(tableName ?: "") { | ||||||
|     abstract val keyColumn: Column<*> |     abstract val keyColumn: Column<*> | ||||||
|     abstract val ResultRow.asKey: Key |     abstract val ResultRow.asKey: Key | ||||||
|     abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean> |     override val ResultRow.asId: Key | ||||||
|  |         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) { select { selectById(k) }.count() } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import dev.inmo.micro_utils.repos.KeyValuesRepo | |||||||
| import dev.inmo.micro_utils.repos.exposed.ColumnAllocator | import dev.inmo.micro_utils.repos.exposed.ColumnAllocator | ||||||
| import kotlinx.coroutines.flow.* | import kotlinx.coroutines.flow.* | ||||||
| import org.jetbrains.exposed.sql.* | import org.jetbrains.exposed.sql.* | ||||||
|  | import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq | ||||||
| import org.jetbrains.exposed.sql.transactions.transaction | import org.jetbrains.exposed.sql.transactions.transaction | ||||||
|  |  | ||||||
| typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value> | typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value> | ||||||
|   | |||||||
| @@ -1,10 +1,8 @@ | |||||||
| package dev.inmo.micro_utils.repos.exposed.onetomany | package dev.inmo.micro_utils.repos.exposed.onetomany | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.pagination.* |  | ||||||
| import dev.inmo.micro_utils.repos.ReadKeyValuesRepo | import dev.inmo.micro_utils.repos.ReadKeyValuesRepo | ||||||
| import dev.inmo.micro_utils.repos.exposed.* | import dev.inmo.micro_utils.repos.exposed.* | ||||||
| import org.jetbrains.exposed.sql.* | import org.jetbrains.exposed.sql.* | ||||||
| import org.jetbrains.exposed.sql.transactions.transaction |  | ||||||
|  |  | ||||||
| typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value> | typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value> | ||||||
|  |  | ||||||
| @@ -13,54 +11,15 @@ open class ExposedReadKeyValuesRepo<Key, Value>( | |||||||
|     keyColumnAllocator: ColumnAllocator<Key>, |     keyColumnAllocator: ColumnAllocator<Key>, | ||||||
|     valueColumnAllocator: ColumnAllocator<Value>, |     valueColumnAllocator: ColumnAllocator<Value>, | ||||||
|     tableName: String? = null |     tableName: String? = null | ||||||
| ) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") { | ) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValuesRepo<Key, Value>(database, tableName) { | ||||||
|     val keyColumn: Column<Key> = keyColumnAllocator() |     override val keyColumn: Column<Key> = keyColumnAllocator() | ||||||
|  |     override val ResultRow.asKey: Key | ||||||
|  |         get() = get(keyColumn) | ||||||
|  |     override val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) } | ||||||
|  |     override val ResultRow.asObject: Value | ||||||
|  |         get() = get(valueColumn) | ||||||
|  |     override val selectById: ISqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) } | ||||||
|     val valueColumn: Column<Value> = valueColumnAllocator() |     val valueColumn: Column<Value> = valueColumnAllocator() | ||||||
|  |  | ||||||
|     init { initTable() } |     init { initTable() } | ||||||
|  |  | ||||||
|     override suspend fun count(k: Key): Long = transaction(database) { select { keyColumn.eq(k) }.count() } |  | ||||||
|  |  | ||||||
|     override suspend fun count(): Long = transaction(database) { selectAll().count() } |  | ||||||
|  |  | ||||||
|     override suspend fun get( |  | ||||||
|         k: Key, |  | ||||||
|         pagination: Pagination, |  | ||||||
|         reversed: Boolean |  | ||||||
|     ): PaginationResult<Value> = transaction(database) { |  | ||||||
|         select { keyColumn.eq(k) }.paginate(pagination, keyColumn, reversed).map { it[valueColumn] } |  | ||||||
|     }.createPaginationResult( |  | ||||||
|         pagination, |  | ||||||
|         count(k) |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) { |  | ||||||
|         selectAll().paginate(pagination, keyColumn, reversed).map { it[keyColumn] } |  | ||||||
|     }.createPaginationResult( |  | ||||||
|         pagination, |  | ||||||
|         count() |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     override suspend fun keys( |  | ||||||
|         v: Value, |  | ||||||
|         pagination: Pagination, |  | ||||||
|         reversed: Boolean |  | ||||||
|     ): PaginationResult<Key> = transaction(database) { |  | ||||||
|         select { valueColumn.eq(v) }.let { |  | ||||||
|             it.count() to it.paginate(pagination, keyColumn, reversed).map { it[keyColumn] } |  | ||||||
|         } |  | ||||||
|     }.let { (count, list) -> |  | ||||||
|         list.createPaginationResult( |  | ||||||
|             pagination, |  | ||||||
|             count |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override suspend fun contains(k: Key): Boolean = transaction(database) { |  | ||||||
|         select { keyColumn.eq(k) }.limit(1).any() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) { |  | ||||||
|         select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).any() |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,6 +15,13 @@ class ReadMapCRUDRepo<ObjectType, IdType>( | |||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> { | ||||||
|  |         return map.keys.drop(pagination.firstIndex).take(pagination.size).createPaginationResult( | ||||||
|  |             pagination, | ||||||
|  |             count() | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun getById(id: IdType): ObjectType? = map[id] |     override suspend fun getById(id: IdType): ObjectType? = map[id] | ||||||
|  |  | ||||||
|     override suspend fun contains(id: IdType): Boolean = map.containsKey(id) |     override suspend fun contains(id: IdType): Boolean = map.containsKey(id) | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> ( | |||||||
|                 httpClient, |                 httpClient, | ||||||
|                 typeInfo<ObjectType>(), |                 typeInfo<ObjectType>(), | ||||||
|                 typeInfo<PaginationResult<ObjectType>>(), |                 typeInfo<PaginationResult<ObjectType>>(), | ||||||
|  |                 typeInfo<PaginationResult<IdType>>(), | ||||||
|                 contentType, |                 contentType, | ||||||
|                 idSerializer |                 idSerializer | ||||||
|             ), |             ), | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ class KtorReadCRUDRepoClient<ObjectType, IdType> ( | |||||||
|     private val httpClient: HttpClient, |     private val httpClient: HttpClient, | ||||||
|     private val objectType: TypeInfo, |     private val objectType: TypeInfo, | ||||||
|     private val paginationObjectType: TypeInfo, |     private val paginationObjectType: TypeInfo, | ||||||
|  |     private val paginationIdType: TypeInfo, | ||||||
|     private val contentType: ContentType, |     private val contentType: ContentType, | ||||||
|     private val idSerializer: suspend (IdType) -> String |     private val idSerializer: suspend (IdType) -> String | ||||||
| ) : ReadCRUDRepo<ObjectType, IdType> { | ) : ReadCRUDRepo<ObjectType, IdType> { | ||||||
| @@ -27,6 +28,11 @@ class KtorReadCRUDRepoClient<ObjectType, IdType> ( | |||||||
|     ) { |     ) { | ||||||
|         contentType(contentType) |         contentType(contentType) | ||||||
|     }.body(paginationObjectType) |     }.body(paginationObjectType) | ||||||
|  |     override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = httpClient.get( | ||||||
|  |         buildStandardUrl(baseUrl, getIdsByPaginationRouting, pagination.asUrlQueryParts) | ||||||
|  |     ) { | ||||||
|  |         contentType(contentType) | ||||||
|  |     }.body(paginationIdType) | ||||||
|  |  | ||||||
|     override suspend fun getById(id: IdType): ObjectType? = httpClient.get( |     override suspend fun getById(id: IdType): ObjectType? = httpClient.get( | ||||||
|         buildStandardUrl( |         buildStandardUrl( | ||||||
| @@ -72,6 +78,7 @@ inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient( | |||||||
|     httpClient, |     httpClient, | ||||||
|     typeInfo<ObjectType>(), |     typeInfo<ObjectType>(), | ||||||
|     typeInfo<PaginationResult<ObjectType>>(), |     typeInfo<PaginationResult<ObjectType>>(), | ||||||
|  |     typeInfo<PaginationResult<IdType>>(), | ||||||
|     contentType, |     contentType, | ||||||
|     idSerializer |     idSerializer | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.repos.ktor.common.crud | package dev.inmo.micro_utils.repos.ktor.common.crud | ||||||
|  |  | ||||||
| const val getByPaginationRouting = "getByPagination" | const val getByPaginationRouting = "getByPagination" | ||||||
|  | const val getIdsByPaginationRouting = "getIdsByPagination" | ||||||
| const val getByIdRouting = "getById" | const val getByIdRouting = "getById" | ||||||
| const val containsRouting = "contains" | const val containsRouting = "contains" | ||||||
|   | |||||||
| @@ -23,6 +23,11 @@ inline fun <reified ObjectType, reified IdType> Route.configureReadCRUDRepoRoute | |||||||
|  |  | ||||||
|         call.respond(originalRepo.getByPagination(pagination)) |         call.respond(originalRepo.getByPagination(pagination)) | ||||||
|     } |     } | ||||||
|  |     get(getIdsByPaginationRouting) { | ||||||
|  |         val pagination = call.request.queryParameters.extractPagination | ||||||
|  |  | ||||||
|  |         call.respond(originalRepo.getIdsByPagination(pagination)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     get(getByIdRouting) { |     get(getByIdRouting) { | ||||||
|         val id = idDeserializer( |         val id = idDeserializer( | ||||||
|   | |||||||
| @@ -1,83 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.repos.ktor.server.crud |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.server.* |  | ||||||
| import dev.inmo.micro_utils.pagination.PaginationResult |  | ||||||
| import dev.inmo.micro_utils.pagination.extractPagination |  | ||||||
| import dev.inmo.micro_utils.repos.ReadCRUDRepo |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.countRouting |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.crud.* |  | ||||||
| import io.ktor.http.ContentType |  | ||||||
| import io.ktor.server.application.call |  | ||||||
| import io.ktor.server.routing.Route |  | ||||||
| import io.ktor.server.routing.get |  | ||||||
| import kotlinx.serialization.KSerializer |  | ||||||
| import kotlinx.serialization.builtins.serializer |  | ||||||
|  |  | ||||||
| fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes( |  | ||||||
|     originalRepo: ReadCRUDRepo<ObjectType, IdType>, |  | ||||||
|     objectsSerializer: KSerializer<ObjectType>, |  | ||||||
|     objectsNullableSerializer: KSerializer<ObjectType?>, |  | ||||||
|     idsSerializer: KSerializer<IdType>, |  | ||||||
|     unifiedRouter: UnifiedRouter |  | ||||||
| ) { |  | ||||||
|     val paginationResultSerializer = PaginationResult.serializer(objectsSerializer) |  | ||||||
|  |  | ||||||
|     get(getByPaginationRouting) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val pagination = call.request.queryParameters.extractPagination |  | ||||||
|  |  | ||||||
|             unianswer( |  | ||||||
|                 paginationResultSerializer, |  | ||||||
|                 originalRepo.getByPagination(pagination) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get(getByIdRouting) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val id = decodeUrlQueryValueOrSendError( |  | ||||||
|                 "id", |  | ||||||
|                 idsSerializer |  | ||||||
|             ) ?: return@get |  | ||||||
|  |  | ||||||
|             unianswer( |  | ||||||
|                 objectsNullableSerializer, |  | ||||||
|                 originalRepo.getById(id) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get(containsRouting) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val id = decodeUrlQueryValueOrSendError( |  | ||||||
|                 "id", |  | ||||||
|                 idsSerializer |  | ||||||
|             ) ?: return@get |  | ||||||
|  |  | ||||||
|             unianswer( |  | ||||||
|                 Boolean.serializer(), |  | ||||||
|                 originalRepo.contains(id) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get(countRouting) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             unianswer( |  | ||||||
|                 Long.serializer(), |  | ||||||
|                 originalRepo.count() |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes( |  | ||||||
|     originalRepo: ReadCRUDRepo<ObjectType, IdType>, |  | ||||||
|     objectsSerializer: KSerializer<ObjectType>, |  | ||||||
|     objectsNullableSerializer: KSerializer<ObjectType?>, |  | ||||||
|     idsSerializer: KSerializer<IdType>, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, |  | ||||||
|     serialFormatContentType: ContentType = standardKtorSerialFormatContentType |  | ||||||
| ) = configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)) |  | ||||||
| @@ -1,39 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.repos.ktor.server.crud |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.server.UnifiedRouter |  | ||||||
| import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType |  | ||||||
| import dev.inmo.micro_utils.repos.CRUDRepo |  | ||||||
| import io.ktor.http.ContentType |  | ||||||
| import io.ktor.server.routing.Route |  | ||||||
| import io.ktor.server.routing.route |  | ||||||
| import kotlinx.serialization.KSerializer |  | ||||||
|  |  | ||||||
| fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes( |  | ||||||
|     baseSubpart: String, |  | ||||||
|     originalRepo: CRUDRepo<ObjectType, IdType, InputValue>, |  | ||||||
|     objectsSerializer: KSerializer<ObjectType>, |  | ||||||
|     objectsNullableSerializer: KSerializer<ObjectType?>, |  | ||||||
|     inputsSerializer: KSerializer<InputValue>, |  | ||||||
|     idsSerializer: KSerializer<IdType>, |  | ||||||
|     unifiedRouter: UnifiedRouter |  | ||||||
| ) { |  | ||||||
|     route(baseSubpart) { |  | ||||||
|         configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, unifiedRouter) |  | ||||||
|         configureWriteCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, unifiedRouter) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes( |  | ||||||
|     baseSubpart: String, |  | ||||||
|     originalRepo: CRUDRepo<ObjectType, IdType, InputValue>, |  | ||||||
|     objectsSerializer: KSerializer<ObjectType>, |  | ||||||
|     objectsNullableSerializer: KSerializer<ObjectType?>, |  | ||||||
|     inputsSerializer: KSerializer<InputValue>, |  | ||||||
|     idsSerializer: KSerializer<IdType>, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, |  | ||||||
|     serialFormatContentType: ContentType = standardKtorSerialFormatContentType |  | ||||||
| ) = configureCRUDRepoRoutes( |  | ||||||
|     baseSubpart, originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType) |  | ||||||
| ) |  | ||||||
| @@ -1,107 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.repos.ktor.server.crud |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.server.* |  | ||||||
| import dev.inmo.micro_utils.repos.WriteCRUDRepo |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.crud.* |  | ||||||
| import io.ktor.http.ContentType |  | ||||||
| import io.ktor.server.routing.Route |  | ||||||
| import io.ktor.server.routing.post |  | ||||||
| import kotlinx.serialization.KSerializer |  | ||||||
| import kotlinx.serialization.builtins.* |  | ||||||
|  |  | ||||||
| fun <ObjectType, IdType, InputValue> Route.configureWriteCRUDRepoRoutes( |  | ||||||
|     originalRepo: WriteCRUDRepo<ObjectType, IdType, InputValue>, |  | ||||||
|     objectsSerializer: KSerializer<ObjectType>, |  | ||||||
|     objectsNullableSerializer: KSerializer<ObjectType?>, |  | ||||||
|     inputsSerializer: KSerializer<InputValue>, |  | ||||||
|     idsSerializer: KSerializer<IdType>, |  | ||||||
|     unifiedRouter: UnifiedRouter |  | ||||||
| ) { |  | ||||||
|     val listObjectsSerializer = ListSerializer(objectsSerializer) |  | ||||||
|     val listInputSerializer = ListSerializer(inputsSerializer) |  | ||||||
|     val listIdsSerializer = ListSerializer(idsSerializer) |  | ||||||
|     val inputUpdateSerializer = PairSerializer( |  | ||||||
|         idsSerializer, |  | ||||||
|         inputsSerializer |  | ||||||
|     ) |  | ||||||
|     val listInputUpdateSerializer = ListSerializer(inputUpdateSerializer) |  | ||||||
|  |  | ||||||
|     unifiedRouter.apply { |  | ||||||
|         includeWebsocketHandling( |  | ||||||
|             newObjectsFlowRouting, |  | ||||||
|             originalRepo.newObjectsFlow, |  | ||||||
|             objectsSerializer |  | ||||||
|         ) |  | ||||||
|         includeWebsocketHandling( |  | ||||||
|             updatedObjectsFlowRouting, |  | ||||||
|             originalRepo.updatedObjectsFlow, |  | ||||||
|             objectsSerializer |  | ||||||
|         ) |  | ||||||
|         includeWebsocketHandling( |  | ||||||
|             deletedObjectsIdsFlowRouting, |  | ||||||
|             originalRepo.deletedObjectsIdsFlow, |  | ||||||
|             idsSerializer |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     post(createRouting) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             unianswer( |  | ||||||
|                 listObjectsSerializer, |  | ||||||
|                 originalRepo.create( |  | ||||||
|                     uniload(listInputSerializer) |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     post(updateRouting) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val (id, input) = uniload(inputUpdateSerializer) |  | ||||||
|             unianswer( |  | ||||||
|                 objectsNullableSerializer, |  | ||||||
|                 originalRepo.update( |  | ||||||
|                     id, input |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     post(updateManyRouting) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val updates = uniload(listInputUpdateSerializer) |  | ||||||
|             unianswer( |  | ||||||
|                 listObjectsSerializer, |  | ||||||
|                 originalRepo.update( |  | ||||||
|                     updates |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     post(deleteByIdRouting) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val ids = uniload(listIdsSerializer) |  | ||||||
|             unianswer( |  | ||||||
|                 Unit.serializer(), |  | ||||||
|                 originalRepo.deleteById( |  | ||||||
|                     ids |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fun <ObjectType, IdType, InputValue> Route.configureWriteCRUDRepoRoutes( |  | ||||||
|     originalRepo: WriteCRUDRepo<ObjectType, IdType, InputValue>, |  | ||||||
|     objectsSerializer: KSerializer<ObjectType>, |  | ||||||
|     objectsNullableSerializer: KSerializer<ObjectType?>, |  | ||||||
|     inputsSerializer: KSerializer<InputValue>, |  | ||||||
|     idsSerializer: KSerializer<IdType>, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, |  | ||||||
|     serialFormatContentType: ContentType = standardKtorSerialFormatContentType |  | ||||||
| ) = configureWriteCRUDRepoRoutes( |  | ||||||
|     originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType) |  | ||||||
| ) |  | ||||||
| @@ -1,46 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.repos.ktor.server.key_value |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.server.UnifiedRouter |  | ||||||
| import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType |  | ||||||
| import dev.inmo.micro_utils.repos.KeyValueRepo |  | ||||||
| import io.ktor.http.ContentType |  | ||||||
| import io.ktor.server.routing.Route |  | ||||||
| import io.ktor.server.routing.route |  | ||||||
| import kotlinx.serialization.KSerializer |  | ||||||
|  |  | ||||||
| fun <K, V> Route.configureKeyValueRepoRoutes( |  | ||||||
|     baseSubpart: String, |  | ||||||
|     originalRepo: KeyValueRepo<K, V>, |  | ||||||
|     keySerializer: KSerializer<K>, |  | ||||||
|     valueSerializer: KSerializer<V>, |  | ||||||
|     valueNullableSerializer: KSerializer<V?>, |  | ||||||
|     unifiedRouter: UnifiedRouter |  | ||||||
| ) { |  | ||||||
|     route(baseSubpart) { |  | ||||||
|         configureReadStandartKeyValueRepoRoutes( |  | ||||||
|             originalRepo, |  | ||||||
|             keySerializer, |  | ||||||
|             valueSerializer, |  | ||||||
|             valueNullableSerializer, |  | ||||||
|             unifiedRouter |  | ||||||
|         ) |  | ||||||
|         configureWriteKeyValueRepoRoutes( |  | ||||||
|             originalRepo, |  | ||||||
|             keySerializer, |  | ||||||
|             valueSerializer, |  | ||||||
|             unifiedRouter |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fun <K, V> Route.configureStandartKeyValueRepoRoutes( |  | ||||||
|     baseSubpart: String, |  | ||||||
|     originalRepo: KeyValueRepo<K, V>, |  | ||||||
|     keySerializer: KSerializer<K>, |  | ||||||
|     valueSerializer: KSerializer<V>, |  | ||||||
|     valueNullableSerializer: KSerializer<V?>, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, |  | ||||||
|     serialFormatContentType: ContentType = standardKtorSerialFormatContentType |  | ||||||
| ) = configureKeyValueRepoRoutes(baseSubpart, originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType)) |  | ||||||
| @@ -1,107 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.repos.ktor.server.key_value |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.server.* |  | ||||||
| import dev.inmo.micro_utils.pagination.PaginationResult |  | ||||||
| import dev.inmo.micro_utils.pagination.extractPagination |  | ||||||
| import dev.inmo.micro_utils.repos.ReadKeyValueRepo |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.* |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.containsRoute |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.countRoute |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.key_value.* |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.key_value.keyParameterName |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.key_value.reversedParameterName |  | ||||||
| import io.ktor.http.ContentType |  | ||||||
| import io.ktor.server.application.call |  | ||||||
| import io.ktor.server.routing.Route |  | ||||||
| import io.ktor.server.routing.get |  | ||||||
| import kotlinx.serialization.KSerializer |  | ||||||
| import kotlinx.serialization.builtins.serializer |  | ||||||
|  |  | ||||||
| fun <K, V> Route.configureReadStandartKeyValueRepoRoutes ( |  | ||||||
|     originalRepo: ReadKeyValueRepo<K, V>, |  | ||||||
|     keySerializer: KSerializer<K>, |  | ||||||
|     valueSerializer: KSerializer<V>, |  | ||||||
|     valueNullableSerializer: KSerializer<V?>, |  | ||||||
|     unifiedRouter: UnifiedRouter |  | ||||||
| ) { |  | ||||||
|     get(getRoute) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val key = decodeUrlQueryValueOrSendError( |  | ||||||
|                 keyParameterName, |  | ||||||
|                 keySerializer |  | ||||||
|             ) ?: return@get |  | ||||||
|  |  | ||||||
|             unianswer( |  | ||||||
|                 valueNullableSerializer, |  | ||||||
|                 originalRepo.get(key) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get(valuesRoute) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val parination = call.request.queryParameters.extractPagination; |  | ||||||
|             val reversed = decodeUrlQueryValueOrSendError( |  | ||||||
|                 reversedParameterName, |  | ||||||
|                 Boolean.serializer() |  | ||||||
|             ) ?: return@get |  | ||||||
|  |  | ||||||
|             unianswer( |  | ||||||
|                 PaginationResult.serializer(valueSerializer), |  | ||||||
|                 originalRepo.values(parination, reversed) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get(keysRoute) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val parination = call.request.queryParameters.extractPagination; |  | ||||||
|             val reversed = decodeUrlQueryValueOrSendError( |  | ||||||
|                 reversedParameterName, |  | ||||||
|                 Boolean.serializer() |  | ||||||
|             ) ?: return@get |  | ||||||
|             val value = decodeUrlQueryValue(valueParameterName, valueSerializer) |  | ||||||
|  |  | ||||||
|             unianswer( |  | ||||||
|                 PaginationResult.serializer(keySerializer), |  | ||||||
|                 value?.let { originalRepo.keys(value, parination, reversed) } ?: originalRepo.keys(parination, reversed) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get(containsRoute) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val key = decodeUrlQueryValueOrSendError( |  | ||||||
|                 keyParameterName, |  | ||||||
|                 keySerializer |  | ||||||
|             ) ?: return@get |  | ||||||
|  |  | ||||||
|             unianswer( |  | ||||||
|                 Boolean.serializer(), |  | ||||||
|                 originalRepo.contains(key) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get(countRoute) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             unianswer( |  | ||||||
|                 Long.serializer(), |  | ||||||
|                 originalRepo.count() |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| inline fun <K, V> Route.configureReadStandartKeyValueRepoRoutes ( |  | ||||||
|     originalRepo: ReadKeyValueRepo<K, V>, |  | ||||||
|     keySerializer: KSerializer<K>, |  | ||||||
|     valueSerializer: KSerializer<V>, |  | ||||||
|     valueNullableSerializer: KSerializer<V?>, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, |  | ||||||
|     serialFormatContentType: ContentType = standardKtorSerialFormatContentType |  | ||||||
| ) = configureReadStandartKeyValueRepoRoutes( |  | ||||||
|     originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType) |  | ||||||
| ) |  | ||||||
| @@ -1,70 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.repos.ktor.server.key_value |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat |  | ||||||
| import dev.inmo.micro_utils.ktor.server.* |  | ||||||
| import dev.inmo.micro_utils.repos.WriteKeyValueRepo |  | ||||||
| import dev.inmo.micro_utils.repos.ktor.common.key_value.* |  | ||||||
| import io.ktor.http.ContentType |  | ||||||
| import io.ktor.server.routing.Route |  | ||||||
| import io.ktor.server.routing.post |  | ||||||
| import kotlinx.serialization.KSerializer |  | ||||||
| import kotlinx.serialization.builtins.* |  | ||||||
|  |  | ||||||
| fun <K, V> Route.configureWriteKeyValueRepoRoutes ( |  | ||||||
|     originalRepo: WriteKeyValueRepo<K, V>, |  | ||||||
|     keySerializer: KSerializer<K>, |  | ||||||
|     valueSerializer: KSerializer<V>, |  | ||||||
|     unifiedRouter: UnifiedRouter |  | ||||||
| ) { |  | ||||||
|     val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer) |  | ||||||
|     val keysListSerializer = ListSerializer(keySerializer) |  | ||||||
|     val valuesListSerializer = ListSerializer(valueSerializer) |  | ||||||
|     unifiedRouter.apply { |  | ||||||
|         includeWebsocketHandling( |  | ||||||
|             onNewValueRoute, |  | ||||||
|             originalRepo.onNewValue, |  | ||||||
|             PairSerializer(keySerializer, valueSerializer) |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         includeWebsocketHandling( |  | ||||||
|             onValueRemovedRoute, |  | ||||||
|             originalRepo.onValueRemoved, |  | ||||||
|             keySerializer |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     post(setRoute) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val toSet = uniload( |  | ||||||
|                 keyValueMapSerializer |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|             unianswer(Unit.serializer(), originalRepo.set(toSet)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     post(unsetRoute) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val toUnset = uniload(keysListSerializer) |  | ||||||
|  |  | ||||||
|             unianswer(Unit.serializer(), originalRepo.unset(toUnset)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     post(unsetWithValuesRoute) { |  | ||||||
|         unifiedRouter.apply { |  | ||||||
|             val toUnset = uniload(valuesListSerializer) |  | ||||||
|  |  | ||||||
|             unianswer(Unit.serializer(), originalRepo.unsetWithValues(toUnset)) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fun <K, V> Route.configureWriteStandartKeyValueRepoRoutes ( |  | ||||||
|     originalRepo: WriteKeyValueRepo<K, V>, |  | ||||||
|     keySerializer: KSerializer<K>, |  | ||||||
|     valueSerializer: KSerializer<V>, |  | ||||||
|     serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, |  | ||||||
|     serialFormatContentType: ContentType = standardKtorSerialFormatContentType |  | ||||||
| ) = configureWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType)) |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user