mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-30 19:50:31 +00:00 
			
		
		
		
	Compare commits
	
		
			283 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 438fefa7a3 | |||
| 5d74eac814 | |||
| 9fb62e1e25 | |||
| 3e366ea73b | |||
| 0ff895bffa | |||
| c5bb120280 | |||
| 4b26a92b37 | |||
| 0a8453b4d2 | |||
| c9872a61b6 | |||
| 149a1aa278 | |||
| 13d0e1b682 | |||
| 6f9be2a9f8 | |||
| 93ba98d993 | |||
| 940ee3df06 | |||
| 2e7bab10fd | |||
|  | 3ed70a37ea | ||
| fe8f80b9d9 | |||
| d81fb32fb9 | |||
| 2877b5532c | |||
| b938b21395 | |||
| 58836359cc | |||
| 5edb0e1331 | |||
| 0f0d0b5d58 | |||
| 46c1887cbe | |||
| 5f231c2212 | |||
| 4e97ce86aa | |||
| 315a7cb29e | |||
| aa7cc503f2 | |||
| 4bbe7e5a80 | |||
| d9c05f38d2 | |||
| cd0c4c9650 | |||
| fc3407f104 | |||
| 3a5544206b | |||
| e17e2f7fb8 | |||
|  | d32c95f143 | ||
| 6d8a8ab018 | |||
| a7dce8fa78 | |||
| ca73ff8e19 | |||
| d01ad10d7d | |||
| 81041ee43c | |||
|  | 6e004c2ae4 | ||
| 0e2fac5b22 | |||
| 269da7f155 | |||
| 3cb6b73ee0 | |||
| a938ee1efb | |||
| 6ea5e2e5a6 | |||
| 617dfb54e0 | |||
| d23e005985 | |||
| e5207f5bc5 | |||
| c96cea8db0 | |||
| 0a8e71d76a | |||
| cf1fd32b08 | |||
| cc4224ea1f | |||
| f4c148bc58 | |||
| 022297ad3f | |||
| 5180d6fc3e | |||
| eeebbff70d | |||
| afc6aeea15 | |||
| 486515eddd | |||
| 0e21699cd1 | |||
| f1678ef7cf | |||
| cea65fc76e | |||
| c9e320b72a | |||
| 555956087d | |||
| b3f468f901 | |||
| f5f7511781 | |||
| 4be1d93f60 | |||
| 7d684608ef | |||
| 2c7fd320eb | |||
| 88ee82e1c6 | |||
| d6402c624e | |||
| 8b9c93bc10 | |||
| 4f5e261d01 | |||
| cf455aebe6 | |||
| 1380d5f8e1 | |||
| 5f65260bfe | |||
| 11f0dcfc01 | |||
| 555b7886a4 | |||
| 3707a6c0ce | |||
| 4c8d92b4c3 | |||
| 8bbd33c896 | |||
| ac33a3580f | |||
| a64a32fbe6 | |||
| 9493e97a38 | |||
| 88bd770260 | |||
| a7bd33b7bf | |||
| 73c724a2e5 | |||
| d8cf3c6726 | |||
| 15dee238b5 | |||
| c70626734e | |||
| 5a765ea1bc | |||
| 8215f9d2c6 | |||
| d2e6d2ec80 | |||
| 3718181e8f | |||
| 0d825cf424 | |||
| 28a804d988 | |||
| 9e4bb9d678 | |||
| 9c40d7da3d | |||
| 2b76ad0aa9 | |||
| e4b619e050 | |||
| 36c09feaf2 | |||
| 2d68321503 | |||
| 85455ab21c | |||
| 18d63eb980 | |||
| 2e429e9704 | |||
| f4af28059b | |||
| c1476bd075 | |||
| 16c720fddd | |||
| 8b4b4a5eca | |||
| 32e6e5b7e2 | |||
| a9f7fd8e32 | |||
| 95be1a26f2 | |||
| ef9b31aee0 | |||
| df3c01ff0a | |||
| 4704c5a33d | |||
| 225c06550a | |||
| f0987614c6 | |||
| 269c2876f3 | |||
| 168d6acf7c | |||
| a5f718e257 | |||
| 4f68459582 | |||
| 442db122cf | |||
| 580d757be2 | |||
| 47b0f6d2d8 | |||
| 3f6f6ebc2b | |||
| 2645ea29d6 | |||
| 79f2041565 | |||
| 4a7567f288 | |||
| 8a890ed6ed | |||
| 3d90df6897 | |||
| 681c13144a | |||
| b64f2e6d32 | |||
| 428eabb1bd | |||
| 2162e83bce | |||
| 6142022283 | |||
| e6d9c8250f | |||
| 46178e723b | |||
| 605f55acd2 | |||
| 0f8b69aa60 | |||
| 551d8ec480 | |||
| fc48446ec4 | |||
| 3644b83ac6 | |||
| cd73791b6f | |||
| 03de71df2e | |||
| 83d5d3faf4 | |||
| 0c8bec4c89 | |||
| 7fc93817c1 | |||
| d0a00031a1 | |||
| 6ebc5aa0c2 | |||
| 8a6b4bb49e | |||
| 20799b9a3e | |||
| ec3afc615c | |||
| da692ccfc3 | |||
| 53b89f3a18 | |||
| 58cded28d3 | |||
| 592c5f3732 | |||
| f44a78a5f5 | |||
| e0bdd5dfdc | |||
| 99c0f06b72 | |||
| 66fc6df3d7 | |||
| a36425a905 | |||
| d920fee6d4 | |||
| 23590be5de | |||
| 94acc3c93b | |||
| 5616326a3b | |||
| 7601860c5c | |||
| 8b43d785cc | |||
| b62d3a0b7d | |||
| fad73c7213 | |||
| 2403c7c2b0 | |||
| fa090bf920 | |||
| a83ee86340 | |||
| 204955bcce | |||
| ee56e9543a | |||
| 96fdff6ffd | |||
| 58b007cbb3 | |||
| 4f0c139889 | |||
| c584c24fce | |||
| 85e5cee154 | |||
| 5af91981f1 | |||
| 2fe4f08059 | |||
| 83796f345a | |||
| c1e21364a6 | |||
| 067d9d0d3b | |||
| 03f527d83e | |||
| ced05a4586 | |||
| 43fe06206a | |||
| 023657558e | |||
| 9b0b726c80 | |||
| 4ee67321c4 | |||
| 59f1f2e59b | |||
| 0766d48b7c | |||
| e18903b9e9 | |||
| d0eecdead2 | |||
| cc4a83a033 | |||
| 1cf911bbde | |||
| 36d73d5023 | |||
| c395242e3e | |||
| cd9cd7cc5d | |||
| 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 | 
							
								
								
									
										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") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										330
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										330
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,335 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 0.18.2 | ||||||
|  |  | ||||||
|  | * `Startup`: | ||||||
|  |     * Now internal `Json` is fully customizable | ||||||
|  |  | ||||||
|  | ## 0.18.1 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * Add `MapDiff` | ||||||
|  | * `Coroutines`: | ||||||
|  |     * Add `SmartMutex` | ||||||
|  |  | ||||||
|  | ## 0.18.0 | ||||||
|  |  | ||||||
|  | **ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED** | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Android Fragments`: `1.5.6` -> `1.5.7` | ||||||
|  | * `Ktor`: | ||||||
|  |     * `Server`: | ||||||
|  |         * Now it is possible to take query parameters as list | ||||||
|  | * `Repos`: | ||||||
|  |     * `Common`: | ||||||
|  |         * New `WriteKeyValuesRepo.removeWithValue` | ||||||
|  |     * `Cache`: | ||||||
|  |         * Rename full caching factories functions to `fullyCached` | ||||||
|  |  | ||||||
|  | ## 0.17.8 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.2.4` -> `2.3.0` | ||||||
|  |  | ||||||
|  | ## 0.17.7 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Android CoreKtx`: `1.9.0` -> `1.10.0` | ||||||
|  | * `Startup`: | ||||||
|  |     * Add support of `linuxX64` and `mingwX64` platforms | ||||||
|  |  | ||||||
|  | ## 0.17.6 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Kotlin`: `1.8.10` -> `1.8.20` | ||||||
|  |     * `KSLog`: `1.0.0` -> `1.1.1` | ||||||
|  |     * `Compose`: `1.3.1` -> `1.4.0` | ||||||
|  |     * `Koin`: `3.3.2` -> `3.4.0` | ||||||
|  |     * `RecyclerView`: `1.2.1` -> `1.3.0` | ||||||
|  |     * `Fragment`: `1.5.5` -> `1.5.6` | ||||||
|  | * Experimentally (`!!!`) add `linuxX64` and `mingwX64` targets | ||||||
|  |  | ||||||
|  | ## 0.17.5 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * Conversations of number primitives with bounds care | ||||||
|  | * `Repos`: | ||||||
|  |     * `Common`: | ||||||
|  |         * By default, `getAll` for repos will take all the size of repo as page size | ||||||
|  |         * New extension for all built-in repos `maxPagePagination` | ||||||
|  |     * All the repos got `getAll` functions | ||||||
|  |  | ||||||
|  | ## 0.17.4 | ||||||
|  |  | ||||||
|  | * `Serialization`: | ||||||
|  |     * `Mapper`: | ||||||
|  |         * Module inited | ||||||
|  | * `Versions`: | ||||||
|  |     * `Compose`: `1.3.1-rc02` -> `1.3.1` | ||||||
|  |  | ||||||
|  | ## 0.17.3 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * Add `fixed` extensions for `Float` and `Double` | ||||||
|  |     * New function `emptyDiff` | ||||||
|  |     * Now you may pass custom `comparisonFun` to all `diff` functions | ||||||
|  |  | ||||||
|  | ## 0.17.2 | ||||||
|  |  | ||||||
|  | * `FSM`: | ||||||
|  |     * `DefaultStatesManager.onUpdateContextsConflictResolver` and `DefaultStatesManager.onStartContextsConflictResolver` now return `false` by default | ||||||
|  |  | ||||||
|  | ## 0.17.1 | ||||||
|  |  | ||||||
|  | * **Hotfix** for absence of jvm dependencies in android modules | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.2.3` -> `2.2.4` | ||||||
|  |  | ||||||
|  | ## 0.17.0 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Kotlin`: `1.7.20` -> `1.8.10` | ||||||
|  |     * `Serialization`: `1.4.1` -> `1.5.0` | ||||||
|  |     * `KSLog`: `0.5.4` -> `1.0.0` | ||||||
|  |     * `AppCompat`: `1.6.0` -> `1.6.1` | ||||||
|  |  | ||||||
|  | ## 0.16.13 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Generator`: | ||||||
|  |         * Module has been created | ||||||
|  |  | ||||||
|  | ## 0.16.12 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Exposed`: | ||||||
|  |         * `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach | ||||||
|  | * `Koin`: | ||||||
|  |     * `Generator`: | ||||||
|  |         * Module has been created | ||||||
|  |  | ||||||
|  | ## 0.16.11 | ||||||
|  |  | ||||||
|  | * `LanguageCodes`: | ||||||
|  |     * In android and JVM targets now available `toJavaLocale` and from Java `Locale` conversations from/to | ||||||
|  |       `IetfLanguageCode` | ||||||
|  |  | ||||||
|  | ## 0.16.10 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Cache`: | ||||||
|  |         * New transformer type: `ReadCRUDFromKeyValueRepo` | ||||||
|  |         * New transformer type: `ReadKeyValueFromCRUDRepo` | ||||||
|  | * `Pagination`: | ||||||
|  |     * New `paginate` extensions with `reversed` support for `List`/`Set` | ||||||
|  |  | ||||||
|  | ## 0.16.9 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Koin`: `3.2.2` -> `3.3.2` | ||||||
|  |     * `AppCompat`: `1.5.1` -> `1.6.0` | ||||||
|  | * `Ktor`: | ||||||
|  |     * `Client` | ||||||
|  |         * `HttpResponse.bodyOrNull` now retrieve callback to check if body should be received or null | ||||||
|  |         * New extension `HttpResponse.bodyOrNullOnNoContent` | ||||||
|  |  | ||||||
|  | ## 0.16.8 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.2.2` -> `2.2.3` | ||||||
|  | * `Ktor`: | ||||||
|  |     * `Client` | ||||||
|  |         * Fixes in `HttpClient.uniUpload` | ||||||
|  |     * `Server` | ||||||
|  |         * Fixes in `PartData.FileItem.download` | ||||||
|  | * `Repos`: | ||||||
|  |     * `Cache`: | ||||||
|  |         * New type of caches: `FallbackCacheRepo` | ||||||
|  |         * Fixes in `Write*` variants of cached repos | ||||||
|  |         * New type `ActionWrapper` | ||||||
|  |         * New `AutoRecache*` classes for all types of repos as `FallbackCacheRepo`s | ||||||
|  |     * `Common`: | ||||||
|  |         * New transformations for key-value and key-values vice-verse | ||||||
|  |         * Fixes in `FileReadKeyValueRepo` | ||||||
|  |  | ||||||
|  | ## 0.16.7 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * New extensions `ifTrue`/`ifFalse`/`alsoIfTrue`/`alsoIfFalse`/`letIfTrue`/`letIfFalse` | ||||||
|  |     * `Diff` now is serializable | ||||||
|  |     * Add `IndexedValue` serializer | ||||||
|  |     * `repeatOnFailure` extending: now you may pass any lambda to check if continue to try/do something | ||||||
|  |     * `Compose`: | ||||||
|  |         * New extension `MutableState.asState` | ||||||
|  | * `Coroutines`: | ||||||
|  |     * `Compose`: | ||||||
|  |         * All the `Flow` conversations to compose `State`/`MutableState`/`SnapshotStateList`/`List` got several new | ||||||
|  |         parameters | ||||||
|  |         * `Flow.toMutableState` now is deprecated in favor to `asMutableComposeState` | ||||||
|  | * `Repos`: | ||||||
|  |     * `Cache`: | ||||||
|  |         * New type `FullCacheRepo` | ||||||
|  |         * New type `CommonCacheRepo` | ||||||
|  |         * `CacheRepo` got `invalidate` method. It will fully reload `FullCacheRepo` and just clear `CommonCacheRepo` | ||||||
|  |         * New extensions `KVCache.actualizeAll` | ||||||
|  |  | ||||||
|  | ## 0.16.6 | ||||||
|  |  | ||||||
|  | * `Startup`: | ||||||
|  |     * `Launcher`: | ||||||
|  |         * Improvements in `StartLauncherPlugin#start` methods | ||||||
|  |         * Add opportunity to pass second argument on `JVM` platform as log level | ||||||
|  | * `Repos`: | ||||||
|  |     * `Ktor`: | ||||||
|  |         * `Client`: | ||||||
|  |             * All clients repos got opportunity to customize their flows | ||||||
|  |     * `Exposed`: | ||||||
|  |         * Extensions `eqOrIsNull` and `neqOrIsNotNull` for `Column` | ||||||
|  |  | ||||||
|  | ## 0.16.5 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.2.1` -> `2.2.2` | ||||||
|  |  | ||||||
|  | ## 0.16.4 | ||||||
|  |  | ||||||
|  | * `Coroutines`: | ||||||
|  |     * Create `launchInCurrentThread` | ||||||
|  |  | ||||||
|  | ## 0.16.3 | ||||||
|  |  | ||||||
|  | * `Startup`: | ||||||
|  |     * `Launcher`: | ||||||
|  |         * All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now | ||||||
|  |  | ||||||
|  | ## 0.16.2 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Compose`: `1.2.1` -> `1.2.2` | ||||||
|  | * `Startup`: | ||||||
|  |     * Module become available on `JS` target | ||||||
|  |  | ||||||
|  | ## 0.16.1 | ||||||
|  |  | ||||||
|  | * `Coroutines`: | ||||||
|  |     * New `runCatchingSafely`/`safelyWithResult` with receivers | ||||||
|  | * `SafeWrapper`: | ||||||
|  |     * Module inited | ||||||
|  |  | ||||||
|  | ## 0.16.0 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.1.3` -> `2.2.1` | ||||||
|  |     * `Android Fragment`: `1.5.3` -> `1.5.5` | ||||||
|  |  | ||||||
|  | ## 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 | ## 0.12.16 | ||||||
|  |  | ||||||
| * `Coroutines`: | * `Coroutines`: | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ buildscript { | |||||||
|     dependencies { |     dependencies { | ||||||
|         classpath libs.buildscript.kt.gradle |         classpath libs.buildscript.kt.gradle | ||||||
|         classpath libs.buildscript.kt.serialization |         classpath libs.buildscript.kt.serialization | ||||||
|  |         classpath libs.buildscript.kt.ksp | ||||||
|         classpath libs.buildscript.jb.dokka |         classpath libs.buildscript.jb.dokka | ||||||
|         classpath libs.buildscript.gh.release |         classpath libs.buildscript.gh.release | ||||||
|         classpath libs.buildscript.android.gradle |         classpath libs.buildscript.android.gradle | ||||||
| @@ -22,6 +23,7 @@ allprojects { | |||||||
|         mavenCentral() |         mavenCentral() | ||||||
|         google() |         google() | ||||||
|         maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } |         maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } | ||||||
|  |         maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // temporal crutch until legacy tests will be stabled or legacy target will be removed |     // temporal crutch until legacy tests will be stabled or legacy target will be removed | ||||||
|   | |||||||
| @@ -18,6 +18,18 @@ kotlin { | |||||||
|                 api project(":micro_utils.coroutines") |                 api project(":micro_utils.coroutines") | ||||||
|                 api libs.android.fragment |                 api libs.android.fragment | ||||||
|             } |             } | ||||||
|  |             dependsOn jvmMain | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         linuxX64Main { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.okio | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         mingwX64Main { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.okio | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | package dev.inmo.micro_utils.common.compose | ||||||
|  |  | ||||||
|  | import androidx.compose.runtime.MutableState | ||||||
|  | import androidx.compose.runtime.State | ||||||
|  | import androidx.compose.runtime.derivedStateOf | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Converts current [MutableState] to immutable [State] using [derivedStateOf] | ||||||
|  |  */ | ||||||
|  | fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value } | ||||||
| @@ -2,6 +2,8 @@ | |||||||
|  |  | ||||||
| package dev.inmo.micro_utils.common | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import kotlinx.serialization.Serializable | ||||||
|  |  | ||||||
| private inline fun <T> getObject( | private inline fun <T> getObject( | ||||||
|     additional: MutableList<T>, |     additional: MutableList<T>, | ||||||
|     iterator: Iterator<T> |     iterator: Iterator<T> | ||||||
| @@ -24,14 +26,19 @@ private inline fun <T> getObject( | |||||||
|  * |  * | ||||||
|  * @see calculateDiff |  * @see calculateDiff | ||||||
|  */ |  */ | ||||||
|  | @Serializable | ||||||
| data class Diff<T> internal constructor( | data class Diff<T> internal constructor( | ||||||
|     val removed: List<IndexedValue<T>>, |     val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>, | ||||||
|     /** |     /** | ||||||
|      * Old-New values pairs |      * Old-New values pairs | ||||||
|      */ |      */ | ||||||
|     val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>, |     val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>, | ||||||
|     val added: List<IndexedValue<T>> |     val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>> | ||||||
| ) | ) { | ||||||
|  |     fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList()) | ||||||
|  |  | ||||||
| private inline fun <T> performChanges( | private inline fun <T> performChanges( | ||||||
|     potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>, |     potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>, | ||||||
| @@ -40,14 +47,14 @@ private inline fun <T> performChanges( | |||||||
|     changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>, |     changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>, | ||||||
|     removedList: MutableList<IndexedValue<T>>, |     removedList: MutableList<IndexedValue<T>>, | ||||||
|     addedList: MutableList<IndexedValue<T>>, |     addedList: MutableList<IndexedValue<T>>, | ||||||
|     strictComparison: Boolean |     comparisonFun: (T?, T?) -> Boolean | ||||||
| ) { | ) { | ||||||
|     var i = -1 |     var i = -1 | ||||||
|     val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return |     val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return | ||||||
|     for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) { |     for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) { | ||||||
|         i++ |         i++ | ||||||
|         val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison) |         val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value) | ||||||
|         val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison) |         val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value) | ||||||
|         if (oldOneEqualToNewObject || newOneEqualToOldObject) { |         if (oldOneEqualToNewObject || newOneEqualToOldObject) { | ||||||
|             changedList.addAll( |             changedList.addAll( | ||||||
|                 potentialChanges.take(i).mapNotNull { |                 potentialChanges.take(i).mapNotNull { | ||||||
| @@ -101,7 +108,7 @@ private inline fun <T> performChanges( | |||||||
|  */ |  */ | ||||||
| fun <T> Iterable<T>.calculateDiff( | fun <T> Iterable<T>.calculateDiff( | ||||||
|     other: Iterable<T>, |     other: Iterable<T>, | ||||||
|     strictComparison: Boolean = false |     comparisonFun: (T?, T?) -> Boolean | ||||||
| ): Diff<T> { | ): Diff<T> { | ||||||
|     var i = -1 |     var i = -1 | ||||||
|     var j = -1 |     var j = -1 | ||||||
| @@ -129,7 +136,7 @@ fun <T> Iterable<T>.calculateDiff( | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         when { |         when { | ||||||
|             oldObject === newObject || (oldObject == newObject && !strictComparison) -> { |             comparisonFun(oldObject, newObject) -> { | ||||||
|                 changedObjects.addAll(potentiallyChangedObjects.map { |                 changedObjects.addAll(potentiallyChangedObjects.map { | ||||||
|                     @Suppress("UNCHECKED_CAST") |                     @Suppress("UNCHECKED_CAST") | ||||||
|                     it as Pair<IndexedValue<T>, IndexedValue<T>> |                     it as Pair<IndexedValue<T>, IndexedValue<T>> | ||||||
| @@ -140,23 +147,49 @@ fun <T> Iterable<T>.calculateDiff( | |||||||
|                 potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) }) |                 potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) }) | ||||||
|                 val previousOldsAdditionsSize = additionalInOld.size |                 val previousOldsAdditionsSize = additionalInOld.size | ||||||
|                 val previousNewsAdditionsSize = additionalInNew.size |                 val previousNewsAdditionsSize = additionalInNew.size | ||||||
|                 performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison) |                 performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun) | ||||||
|                 i -= (additionalInOld.size - previousOldsAdditionsSize) |                 i -= (additionalInOld.size - previousOldsAdditionsSize) | ||||||
|                 j -= (additionalInNew.size - previousNewsAdditionsSize) |                 j -= (additionalInNew.size - previousNewsAdditionsSize) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     potentiallyChangedObjects.add(null to null) |     potentiallyChangedObjects.add(null to null) | ||||||
|     performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison) |     performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun) | ||||||
|  |  | ||||||
|     return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList()) |     return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Calculating [Diff] object | ||||||
|  |  * | ||||||
|  |  * @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different | ||||||
|  |  * objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links | ||||||
|  |  */ | ||||||
|  | fun <T> Iterable<T>.calculateDiff( | ||||||
|  |     other: Iterable<T>, | ||||||
|  |     strictComparison: Boolean = false | ||||||
|  | ): Diff<T> = calculateDiff( | ||||||
|  |     other, | ||||||
|  |     comparisonFun = if (strictComparison) { | ||||||
|  |         { t1, t2 -> | ||||||
|  |             t1 === t2 | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         { t1, t2 -> | ||||||
|  |             t1 === t2 || t1 == t2 // small optimization for cases when t1 and t2 are the same - comparison will be faster potentially | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | ) | ||||||
| inline fun <T> Iterable<T>.diff( | inline fun <T> Iterable<T>.diff( | ||||||
|     other: Iterable<T>, |     other: Iterable<T>, | ||||||
|     strictComparison: Boolean = false |     strictComparison: Boolean = false | ||||||
| ): Diff<T> = calculateDiff(other, strictComparison) | ): Diff<T> = calculateDiff(other, strictComparison) | ||||||
|  | inline fun <T> Iterable<T>.diff( | ||||||
|  |     other: Iterable<T>, | ||||||
|  |     noinline comparisonFun: (T?, T?) -> Boolean | ||||||
|  | ): Diff<T> = calculateDiff(other, comparisonFun) | ||||||
|  |  | ||||||
| inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new) | inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, strictComparison = false) | ||||||
| inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true) | inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true) | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -166,6 +199,23 @@ inline fun <T> Iterable<T>.calculateStrictDiff( | |||||||
|     other: Iterable<T> |     other: Iterable<T> | ||||||
| ) = calculateDiff(other, strictComparison = true) | ) = calculateDiff(other, strictComparison = true) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Applies [diff] to [this] [MutableList] | ||||||
|  |  */ | ||||||
|  | fun <T> MutableList<T>.applyDiff( | ||||||
|  |     diff: Diff<T> | ||||||
|  | ) { | ||||||
|  |     for (i in diff.removed.indices.sortedDescending()) { | ||||||
|  |         removeAt(diff.removed[i].index) | ||||||
|  |     } | ||||||
|  |     diff.added.forEach { (i, t) -> | ||||||
|  |         add(i, t) | ||||||
|  |     } | ||||||
|  |     diff.replaced.forEach { (_, new) -> | ||||||
|  |         set(new.index, new.value) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this] |  * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this] | ||||||
|  * mutable list |  * mutable list | ||||||
| @@ -174,13 +224,26 @@ fun <T> MutableList<T>.applyDiff( | |||||||
|     source: Iterable<T>, |     source: Iterable<T>, | ||||||
|     strictComparison: Boolean = false |     strictComparison: Boolean = false | ||||||
| ): Diff<T> = calculateDiff(source, strictComparison).also { | ): Diff<T> = calculateDiff(source, strictComparison).also { | ||||||
|     for (i in it.removed.indices.sortedDescending()) { |     applyDiff(it) | ||||||
|         removeAt(it.removed[i].index) |  | ||||||
|     } |  | ||||||
|     it.added.forEach { (i, t) -> |  | ||||||
|         add(i, t) |  | ||||||
|     } |  | ||||||
|     it.replaced.forEach { (_, new) -> |  | ||||||
|         set(new.index, new.value) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This method call [calculateDiff] and then apply differences to [this] | ||||||
|  |  * mutable list | ||||||
|  |  */ | ||||||
|  | fun <T> MutableList<T>.applyDiff( | ||||||
|  |     source: Iterable<T>, | ||||||
|  |     comparisonFun: (T?, T?) -> Boolean | ||||||
|  | ): Diff<T> = calculateDiff(source, comparisonFun).also { | ||||||
|  |     applyDiff(it) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Reverse [this] [Diff]. Result will contain [Diff.added] on [Diff.removed] (and vice-verse), all the | ||||||
|  |  * [Diff.replaced] values will be reversed too | ||||||
|  |  */ | ||||||
|  | fun <T> Diff<T>.reversed() = Diff( | ||||||
|  |     removed = added, | ||||||
|  |     replaced = replaced.map { it.second to it.first }, | ||||||
|  |     added = removed | ||||||
|  | ) | ||||||
|   | |||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | inline fun <T> Boolean.letIfTrue(block: () -> T): T? { | ||||||
|  |     return if (this) { | ||||||
|  |         block() | ||||||
|  |     } else { | ||||||
|  |         null | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun <T> Boolean.letIfFalse(block: () -> T): T? { | ||||||
|  |     return if (this) { | ||||||
|  |         null | ||||||
|  |     } else { | ||||||
|  |         block() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun Boolean.alsoIfTrue(block: () -> Unit): Boolean { | ||||||
|  |     letIfTrue(block) | ||||||
|  |     return this | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun Boolean.alsoIfFalse(block: () -> Unit): Boolean { | ||||||
|  |     letIfFalse(block) | ||||||
|  |     return this | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun <T> Boolean.ifTrue(block: () -> T): T? { | ||||||
|  |     return if (this) { | ||||||
|  |         block() | ||||||
|  |     } else { | ||||||
|  |         null | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun <T> Boolean.ifFalse(block: () -> T): T? { | ||||||
|  |     return if (this) { | ||||||
|  |         null | ||||||
|  |     } else { | ||||||
|  |         block() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import kotlinx.serialization.KSerializer | ||||||
|  | import kotlinx.serialization.Serializer | ||||||
|  | import kotlinx.serialization.builtins.PairSerializer | ||||||
|  | import kotlinx.serialization.builtins.serializer | ||||||
|  | import kotlinx.serialization.descriptors.SerialDescriptor | ||||||
|  | import kotlinx.serialization.encoding.Decoder | ||||||
|  | import kotlinx.serialization.encoding.Encoder | ||||||
|  |  | ||||||
|  | class IndexedValueSerializer<T>(private val subSerializer: KSerializer<T>) : KSerializer<IndexedValue<T>> { | ||||||
|  |     private val originalSerializer = PairSerializer(Int.serializer(), subSerializer) | ||||||
|  |     override val descriptor: SerialDescriptor | ||||||
|  |         get() = originalSerializer.descriptor | ||||||
|  |  | ||||||
|  |     override fun deserialize(decoder: Decoder): IndexedValue<T> { | ||||||
|  |         val pair = originalSerializer.deserialize(decoder) | ||||||
|  |         return IndexedValue( | ||||||
|  |             pair.first, | ||||||
|  |             pair.second | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun serialize(encoder: Encoder, value: IndexedValue<T>) { | ||||||
|  |         originalSerializer.serialize( | ||||||
|  |             encoder, | ||||||
|  |             Pair(value.index, value.value) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,135 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Contains diff based on the comparison of objects with the same [K]. | ||||||
|  |  * | ||||||
|  |  * @param removed Contains map with keys removed from parent map | ||||||
|  |  * @param changed Contains map with keys values changed new map in comparison with old one | ||||||
|  |  * @param added Contains map with new keys and values | ||||||
|  |  */ | ||||||
|  | data class MapDiff<K, V> @Warning(warning) constructor( | ||||||
|  |     val removed: Map<K, V>, | ||||||
|  |     val changed: Map<K, Pair<V, V>>, | ||||||
|  |     val added: Map<K, V> | ||||||
|  | ) { | ||||||
|  |     fun isEmpty() = removed.isEmpty() && changed.isEmpty() && added.isEmpty() | ||||||
|  |     inline fun isNotEmpty() = !isEmpty() | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing" | ||||||
|  |         fun <K, V> empty() = MapDiff<K, V>(emptyMap(), emptyMap(), emptyMap()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private inline fun <K, V> createCompareFun( | ||||||
|  |     strictComparison: Boolean | ||||||
|  | ): (K, V, V) -> Boolean = if (strictComparison) { | ||||||
|  |     { _, first, second -> first === second } | ||||||
|  | } else { | ||||||
|  |     { _, first, second -> first == second } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Compare [this] [Map] with the [other] one in principle when [other] is newer than [this] | ||||||
|  |  * | ||||||
|  |  * @param compareFun Will be used to determine changed values | ||||||
|  |  */ | ||||||
|  | fun <K, V> Map<K, V>.diff( | ||||||
|  |     other: Map<K, V>, | ||||||
|  |     compareFun: (K, V, V) -> Boolean | ||||||
|  | ): MapDiff<K, V> { | ||||||
|  |     val removed: Map<K, V> = (keys - other.keys).associateWith { | ||||||
|  |         getValue(it) | ||||||
|  |     } | ||||||
|  |     val added: Map<K, V> = (other.keys - keys).associateWith { | ||||||
|  |         other.getValue(it) | ||||||
|  |     } | ||||||
|  |     val changed = keys.intersect(other.keys).mapNotNull { | ||||||
|  |         val old = getValue(it) | ||||||
|  |         val new = other.getValue(it) | ||||||
|  |         if (compareFun(it, old, new)) { | ||||||
|  |             return@mapNotNull null | ||||||
|  |         } else { | ||||||
|  |             it to (old to new) | ||||||
|  |         } | ||||||
|  |     }.toMap() | ||||||
|  |  | ||||||
|  |     return MapDiff( | ||||||
|  |         removed, | ||||||
|  |         changed, | ||||||
|  |         added | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Compare [this] [Map] with the [other] one in principle when [other] is newer than [this] | ||||||
|  |  * | ||||||
|  |  * @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard | ||||||
|  |  * `equals` will be used | ||||||
|  |  */ | ||||||
|  | fun <K, V> Map<K, V>.diff( | ||||||
|  |     other: Map<K, V>, | ||||||
|  |     strictComparison: Boolean = false | ||||||
|  | ): MapDiff<K, V> = diff( | ||||||
|  |     other, | ||||||
|  |     compareFun = createCompareFun(strictComparison) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will apply [mapDiff] to [this] [MutableMap] | ||||||
|  |  */ | ||||||
|  | fun <K, V> MutableMap<K, V>.applyDiff( | ||||||
|  |     mapDiff: MapDiff<K, V> | ||||||
|  | ) { | ||||||
|  |     mapDiff.apply { | ||||||
|  |         removed.keys.forEach { remove(it) } | ||||||
|  |         changed.forEach { (k, oldNew) -> | ||||||
|  |             put(k, oldNew.second) | ||||||
|  |         } | ||||||
|  |         added.forEach { (k, new) -> | ||||||
|  |             put(k, new) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will apply changes with [from] map into [this] one | ||||||
|  |  * | ||||||
|  |  * @param compareFun Will be used to determine changed values | ||||||
|  |  * | ||||||
|  |  * @return [MapDiff] applied to [this] [MutableMap] | ||||||
|  |  */ | ||||||
|  | fun <K, V> MutableMap<K, V>.applyDiff( | ||||||
|  |     from: Map<K, V>, | ||||||
|  |     compareFun: (K, V, V) -> Boolean | ||||||
|  | ): MapDiff<K, V> { | ||||||
|  |     return diff(from, compareFun).also { | ||||||
|  |         applyDiff(it) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will apply changes with [from] map into [this] one | ||||||
|  |  * | ||||||
|  |  * @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard | ||||||
|  |  * `equals` will be used | ||||||
|  |  * | ||||||
|  |  * @return [MapDiff] applied to [this] [MutableMap] | ||||||
|  |  */ | ||||||
|  | fun <K, V> MutableMap<K, V>.applyDiff( | ||||||
|  |     from: Map<K, V>, | ||||||
|  |     strictComparison: Boolean = false | ||||||
|  | ): MapDiff<K, V> = applyDiff( | ||||||
|  |     from, | ||||||
|  |     compareFun = createCompareFun(strictComparison) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Reverse [this] [MapDiff]. Result will contain [MapDiff.added] on [MapDiff.removed] (and vice-verse), all the | ||||||
|  |  * [MapDiff.changed] values will be reversed too | ||||||
|  |  */ | ||||||
|  | fun <K, V> MapDiff<K, V>.reversed(): MapDiff<K, V> = MapDiff( | ||||||
|  |     removed = added, | ||||||
|  |     changed = changed.mapValues { (_, oldNew) -> oldNew.second to oldNew.first }, | ||||||
|  |     added = removed | ||||||
|  | ) | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Long] to [Int] with bounds of [Int.MIN_VALUE] and [Int.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Long.toCoercedInt(): Int = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt() | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Long] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Long.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()).toShort() | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Long] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Long.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()).toByte() | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Int] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Int.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort() | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Int] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Int.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()).toByte() | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Short] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Short.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toShort(), Byte.MAX_VALUE.toShort()).toByte() | ||||||
| @@ -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() | ||||||
| @@ -1,5 +1,27 @@ | |||||||
| package dev.inmo.micro_utils.common | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Executes the given [action] until getting of successful result specified number of [times]. | ||||||
|  |  * | ||||||
|  |  * A zero-based index of current iteration is passed as a parameter to [action]. | ||||||
|  |  */ | ||||||
|  | inline fun <R> repeatOnFailure( | ||||||
|  |     onFailure: (Throwable) -> Boolean, | ||||||
|  |     action: () -> R | ||||||
|  | ): Result<R> { | ||||||
|  |     do { | ||||||
|  |         runCatching { | ||||||
|  |             action() | ||||||
|  |         }.onFailure { | ||||||
|  |             if (!onFailure(it)) { | ||||||
|  |                 return Result.failure(it) | ||||||
|  |             } | ||||||
|  |         }.onSuccess { | ||||||
|  |             return Result.success(it) | ||||||
|  |         } | ||||||
|  |     } while (true) | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Executes the given [action] until getting of successful result specified number of [times]. |  * Executes the given [action] until getting of successful result specified number of [times]. | ||||||
|  * |  * | ||||||
| @@ -10,12 +32,23 @@ inline fun <R> repeatOnFailure( | |||||||
|     onEachFailure: (Throwable) -> Unit = {}, |     onEachFailure: (Throwable) -> Unit = {}, | ||||||
|     action: (Int) -> R |     action: (Int) -> R | ||||||
| ): Optional<R> { | ): Optional<R> { | ||||||
|     repeat(times) { |     var i = 0 | ||||||
|         runCatching { |     val result = repeatOnFailure( | ||||||
|             action(it) |         { | ||||||
|         }.onFailure(onEachFailure).onSuccess { |             onEachFailure(it) | ||||||
|             return Optional.presented(it) |             if (i < times) { | ||||||
|  |                 i++ | ||||||
|  |                 true | ||||||
|  |             } else { | ||||||
|  |                 false | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     return Optional.absent() |     ) { | ||||||
|  |         action(i) | ||||||
|  |     } | ||||||
|  |     return if (result.isSuccess) { | ||||||
|  |         Optional.presented(result.getOrThrow()) | ||||||
|  |     } else { | ||||||
|  |         Optional.absent() | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | val FixedSignsRange = 0 .. 100 | ||||||
|  |  | ||||||
|  | expect fun Float.fixed(signs: Int): Float | ||||||
|  | expect fun Double.fixed(signs: Int): Double | ||||||
| @@ -0,0 +1,4 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | actual fun Float.fixed(signs: Int): Float = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toFloat() | ||||||
|  | actual fun Double.fixed(signs: Int): Double = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toDouble() | ||||||
| @@ -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,12 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import java.math.BigDecimal | ||||||
|  | import java.math.RoundingMode | ||||||
|  |  | ||||||
|  | actual fun Float.fixed(signs: Int): Float = BigDecimal.valueOf(this.toDouble()) | ||||||
|  |     .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP) | ||||||
|  |     .toFloat(); | ||||||
|  |  | ||||||
|  | actual fun Double.fixed(signs: Int): Double = BigDecimal.valueOf(this) | ||||||
|  |     .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP) | ||||||
|  |     .toDouble(); | ||||||
							
								
								
									
										36
									
								
								common/src/linuxX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								common/src/linuxX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import okio.FileSystem | ||||||
|  | import okio.Path | ||||||
|  | import okio.use | ||||||
|  |  | ||||||
|  | actual typealias MPPFile = Path | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.filename: FileName | ||||||
|  |     get() = FileName(toString()) | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.filesize: Long | ||||||
|  |     get() = FileSystem.SYSTEM.openReadOnly(this).use { | ||||||
|  |         it.size() | ||||||
|  |     } | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator | ||||||
|  |     get() = { | ||||||
|  |         FileSystem.SYSTEM.read(this) { | ||||||
|  |             readByteArray() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator | ||||||
|  |     get() = { | ||||||
|  |         bytesAllocatorSync() | ||||||
|  |     } | ||||||
							
								
								
									
										26
									
								
								common/src/linuxX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								common/src/linuxX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import kotlinx.cinterop.ByteVar | ||||||
|  | import kotlinx.cinterop.allocArray | ||||||
|  | import kotlinx.cinterop.memScoped | ||||||
|  | import kotlinx.cinterop.toKString | ||||||
|  | import platform.posix.snprintf | ||||||
|  | import platform.posix.sprintf | ||||||
|  |  | ||||||
|  | actual fun Float.fixed(signs: Int): Float { | ||||||
|  |     return memScoped { | ||||||
|  |         val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) | ||||||
|  |  | ||||||
|  |         sprintf(buff, "%.${signs}f", this@fixed) | ||||||
|  |         buff.toKString().toFloat() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | actual fun Double.fixed(signs: Int): Double { | ||||||
|  |     return memScoped { | ||||||
|  |         val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) | ||||||
|  |  | ||||||
|  |         sprintf(buff, "%.${signs}f", this@fixed) | ||||||
|  |         buff.toKString().toDouble() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								common/src/mingwX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								common/src/mingwX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import okio.FileSystem | ||||||
|  | import okio.Path | ||||||
|  | import okio.use | ||||||
|  |  | ||||||
|  | actual typealias MPPFile = Path | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.filename: FileName | ||||||
|  |     get() = FileName(toString()) | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.filesize: Long | ||||||
|  |     get() = FileSystem.SYSTEM.openReadOnly(this).use { | ||||||
|  |         it.size() | ||||||
|  |     } | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator | ||||||
|  |     get() = { | ||||||
|  |         FileSystem.SYSTEM.read(this) { | ||||||
|  |             readByteArray() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator | ||||||
|  |     get() = { | ||||||
|  |         bytesAllocatorSync() | ||||||
|  |     } | ||||||
							
								
								
									
										26
									
								
								common/src/mingwX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								common/src/mingwX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import kotlinx.cinterop.ByteVar | ||||||
|  | import kotlinx.cinterop.allocArray | ||||||
|  | import kotlinx.cinterop.memScoped | ||||||
|  | import kotlinx.cinterop.toKString | ||||||
|  | import platform.posix.snprintf | ||||||
|  | import platform.posix.sprintf | ||||||
|  |  | ||||||
|  | actual fun Float.fixed(signs: Int): Float { | ||||||
|  |     return memScoped { | ||||||
|  |         val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) | ||||||
|  |  | ||||||
|  |         sprintf(buff, "%.${signs}f", this@fixed) | ||||||
|  |         buff.toKString().toFloat() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | actual fun Double.fixed(signs: Int): Double { | ||||||
|  |     return memScoped { | ||||||
|  |         val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) | ||||||
|  |  | ||||||
|  |         sprintf(buff, "%.${signs}f", this@fixed) | ||||||
|  |         buff.toKString().toDouble() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -22,6 +22,7 @@ kotlin { | |||||||
|             dependencies { |             dependencies { | ||||||
|                 api libs.kt.coroutines.android |                 api libs.kt.coroutines.android | ||||||
|             } |             } | ||||||
|  |             dependsOn(jvmMain) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,24 +3,58 @@ package dev.inmo.micro_utils.coroutines.compose | |||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.* | ||||||
| import androidx.compose.runtime.snapshots.SnapshotStateList | import androidx.compose.runtime.snapshots.SnapshotStateList | ||||||
| import dev.inmo.micro_utils.common.applyDiff | import dev.inmo.micro_utils.common.applyDiff | ||||||
|  | import dev.inmo.micro_utils.coroutines.ExceptionHandler | ||||||
|  | import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull | ||||||
| import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions | import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
|  | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
| import kotlinx.coroutines.flow.StateFlow | import kotlinx.coroutines.flow.StateFlow | ||||||
|  | import kotlinx.coroutines.withContext | ||||||
|  | import kotlin.coroutines.CoroutineContext | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList] | ||||||
|  |  * | ||||||
|  |  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList] | ||||||
|  |  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that | ||||||
|  |  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default | ||||||
|  |  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler | ||||||
|  |  */ | ||||||
| @Suppress("NOTHING_TO_INLINE") | @Suppress("NOTHING_TO_INLINE") | ||||||
| inline fun <reified T> Flow<List<T>>.asMutableComposeListState( | inline fun <reified T> Flow<List<T>>.asMutableComposeListState( | ||||||
|     scope: CoroutineScope |     scope: CoroutineScope, | ||||||
|  |     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||||
|  |     noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||||
| ): SnapshotStateList<T> { | ): SnapshotStateList<T> { | ||||||
|     val state = mutableStateListOf<T>() |     val state = mutableStateListOf<T>() | ||||||
|     subscribeSafelyWithoutExceptions(scope) { |     val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let { | ||||||
|  |         { | ||||||
|  |             withContext(useContextOnChange) { | ||||||
|                 state.applyDiff(it) |                 state.applyDiff(it) | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |     } ?: { | ||||||
|  |         state.applyDiff(it) | ||||||
|  |     } | ||||||
|  |     subscribeSafelyWithoutExceptions(scope, onException, changeBlock) | ||||||
|     return state |     return state | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * In fact, it is just classcast of [asMutableComposeListState] to [List] | ||||||
|  |  * | ||||||
|  |  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List] | ||||||
|  |  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that | ||||||
|  |  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default | ||||||
|  |  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler | ||||||
|  |  * | ||||||
|  |  * @return Changing in time [List] which follow [Flow] values | ||||||
|  |  */ | ||||||
| @Suppress("NOTHING_TO_INLINE") | @Suppress("NOTHING_TO_INLINE") | ||||||
| inline fun <reified T> Flow<List<T>>.asComposeList( | inline fun <reified T> Flow<List<T>>.asComposeList( | ||||||
|     scope: CoroutineScope |     scope: CoroutineScope, | ||||||
| ): List<T> = asMutableComposeListState(scope) |     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||||
|  |     noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||||
|  | ): List<T> = asMutableComposeListState(scope, useContextOnChange, onException) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,35 +1,94 @@ | |||||||
| package dev.inmo.micro_utils.coroutines.compose | package dev.inmo.micro_utils.coroutines.compose | ||||||
|  |  | ||||||
| import androidx.compose.runtime.* | import androidx.compose.runtime.* | ||||||
|  | import dev.inmo.micro_utils.common.compose.asState | ||||||
|  | import dev.inmo.micro_utils.coroutines.ExceptionHandler | ||||||
|  | import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull | ||||||
|  | import dev.inmo.micro_utils.coroutines.doInUI | ||||||
| import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions | import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
|  | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
| import kotlinx.coroutines.flow.StateFlow | import kotlinx.coroutines.flow.StateFlow | ||||||
|  | import kotlinx.coroutines.withContext | ||||||
|  | import kotlin.coroutines.CoroutineContext | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow] | ||||||
|  |  * | ||||||
|  |  * @param initial First value which will be passed to the result [MutableState] | ||||||
|  |  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState] | ||||||
|  |  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that | ||||||
|  |  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default | ||||||
|  |  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler | ||||||
|  |  */ | ||||||
| fun <T> Flow<T>.asMutableComposeState( | fun <T> Flow<T>.asMutableComposeState( | ||||||
|     initial: T, |     initial: T, | ||||||
|     scope: CoroutineScope |     scope: CoroutineScope, | ||||||
|  |     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||||
|  |     onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||||
| ): MutableState<T> { | ): MutableState<T> { | ||||||
|     val state = mutableStateOf(initial) |     val state = mutableStateOf(initial) | ||||||
|     subscribeSafelyWithoutExceptions(scope) { state.value = it } |     val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let { | ||||||
|  |         { | ||||||
|  |             withContext(useContextOnChange) { | ||||||
|  |                 state.value = it | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } ?: { | ||||||
|  |         state.value = it | ||||||
|  |     } | ||||||
|  |     subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock) | ||||||
|     return state |     return state | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow]. | ||||||
|  |  * This conversation will pass its [StateFlow.value] as the first value | ||||||
|  |  * | ||||||
|  |  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState] | ||||||
|  |  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that | ||||||
|  |  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default | ||||||
|  |  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler | ||||||
|  |  */ | ||||||
| @Suppress("NOTHING_TO_INLINE") | @Suppress("NOTHING_TO_INLINE") | ||||||
| inline fun <T> StateFlow<T>.asMutableComposeState( | inline fun <T> StateFlow<T>.asMutableComposeState( | ||||||
|     scope: CoroutineScope |     scope: CoroutineScope, | ||||||
| ): MutableState<T> = asMutableComposeState(value, scope) |     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||||
|  |     noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||||
|  | ): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state | ||||||
|  |  * | ||||||
|  |  * @param initial First value which will be passed to the result [State] | ||||||
|  |  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State] | ||||||
|  |  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that | ||||||
|  |  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default | ||||||
|  |  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler | ||||||
|  |  */ | ||||||
| fun <T> Flow<T>.asComposeState( | fun <T> Flow<T>.asComposeState( | ||||||
|     initial: T, |     initial: T, | ||||||
|     scope: CoroutineScope |     scope: CoroutineScope, | ||||||
|  |     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||||
|  |     onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||||
| ): State<T> { | ): State<T> { | ||||||
|     val state = asMutableComposeState(initial, scope) |     val state = asMutableComposeState(initial, scope, useContextOnChange, onException) | ||||||
|     return derivedStateOf { state.value } |     return state.asState() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value | ||||||
|  |  * | ||||||
|  |  * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State] | ||||||
|  |  * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that | ||||||
|  |  * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default | ||||||
|  |  * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler | ||||||
|  |  */ | ||||||
| @Suppress("NOTHING_TO_INLINE") | @Suppress("NOTHING_TO_INLINE") | ||||||
| inline fun <T> StateFlow<T>.asComposeState( | inline fun <T> StateFlow<T>.asComposeState( | ||||||
|     scope: CoroutineScope |     scope: CoroutineScope, | ||||||
| ): State<T> = asComposeState(value, scope) |     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||||
|  |     noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||||
|  | ): State<T> = asComposeState(value, scope, useContextOnChange, onException) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,23 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.coroutines.compose |  | ||||||
|  |  | ||||||
| import androidx.compose.runtime.MutableState |  | ||||||
| import androidx.compose.runtime.mutableStateOf |  | ||||||
| import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions |  | ||||||
| import kotlinx.coroutines.CoroutineScope |  | ||||||
| import kotlinx.coroutines.flow.Flow |  | ||||||
| import kotlinx.coroutines.flow.StateFlow |  | ||||||
|  |  | ||||||
| fun <T> Flow<T>.toMutableState( |  | ||||||
|     initial: T, |  | ||||||
|     scope: CoroutineScope |  | ||||||
| ): MutableState<T> { |  | ||||||
|     val state = mutableStateOf(initial) |  | ||||||
|     subscribeSafelyWithoutExceptions(scope) { state.value = it } |  | ||||||
|     return state |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @Suppress("NOTHING_TO_INLINE") |  | ||||||
| inline fun <T> StateFlow<T>.toMutableState( |  | ||||||
|     scope: CoroutineScope |  | ||||||
| ): MutableState<T> = toMutableState(value, scope) |  | ||||||
|  |  | ||||||
| @@ -115,10 +115,21 @@ suspend inline fun <T> runCatchingSafely( | |||||||
|     safely(onException, block) |     safely(onException, block) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | suspend inline fun <T, R> T.runCatchingSafely( | ||||||
|  |     noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler, | ||||||
|  |     noinline block: suspend T.() -> R | ||||||
|  | ): Result<R> = runCatching { | ||||||
|  |     safely(onException) { block() } | ||||||
|  | } | ||||||
|  |  | ||||||
| suspend inline fun <T> safelyWithResult( | suspend inline fun <T> safelyWithResult( | ||||||
|     noinline block: suspend CoroutineScope.() -> T |     noinline block: suspend CoroutineScope.() -> T | ||||||
| ): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block) | ): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block) | ||||||
|  |  | ||||||
|  | suspend inline fun <T, R> T.safelyWithResult( | ||||||
|  |     noinline block: suspend T.() -> R | ||||||
|  | ): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block) | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and |  * Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and | ||||||
|  * returning null at one time |  * returning null at one time | ||||||
|   | |||||||
| @@ -0,0 +1,136 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.currentCoroutineContext | ||||||
|  | import kotlinx.coroutines.flow.MutableStateFlow | ||||||
|  | import kotlinx.coroutines.flow.StateFlow | ||||||
|  | import kotlinx.coroutines.flow.asStateFlow | ||||||
|  | import kotlinx.coroutines.flow.first | ||||||
|  | import kotlinx.coroutines.isActive | ||||||
|  | import kotlinx.coroutines.sync.Mutex | ||||||
|  | import kotlinx.coroutines.sync.withLock | ||||||
|  | import kotlin.contracts.ExperimentalContracts | ||||||
|  | import kotlin.contracts.InvocationKind | ||||||
|  | import kotlin.contracts.contract | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * It is interface which will work like classic [Mutex], but in difference have [lockStateFlow] for listening of the | ||||||
|  |  * [SmartMutex] state. | ||||||
|  |  * | ||||||
|  |  * There is [Mutable] and [Immutable] realizations. In case you are owner and manager current state of lock, you need | ||||||
|  |  * [Mutable] [SmartMutex]. Otherwise, [Immutable]. | ||||||
|  |  * | ||||||
|  |  * Any [Mutable] [SmartMutex] may produce its [Immutable] variant which will contains [lockStateFlow] equal to its | ||||||
|  |  * [Mutable] creator | ||||||
|  |  */ | ||||||
|  | sealed interface SmartMutex { | ||||||
|  |     val lockStateFlow: StateFlow<Boolean> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * * True - locked | ||||||
|  |      * * False - unlocked | ||||||
|  |      */ | ||||||
|  |     val isLocked: Boolean | ||||||
|  |         get() = lockStateFlow.value | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Immutable variant of [SmartMutex]. In fact will depend on the owner of [lockStateFlow] | ||||||
|  |      */ | ||||||
|  |     class Immutable(override val lockStateFlow: StateFlow<Boolean>) : SmartMutex | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Mutable variant of [SmartMutex]. With that variant you may [lock] and [unlock]. Besides, you may create | ||||||
|  |      * [Immutable] variant of [this] instance with [immutable] factory | ||||||
|  |      * | ||||||
|  |      * @param locked Preset state of [isLocked] and its internal [_lockStateFlow] | ||||||
|  |      */ | ||||||
|  |     class Mutable(locked: Boolean = false) : SmartMutex { | ||||||
|  |         private val _lockStateFlow = MutableStateFlow<Boolean>(locked) | ||||||
|  |         override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow() | ||||||
|  |  | ||||||
|  |         private val internalChangesMutex = Mutex() | ||||||
|  |  | ||||||
|  |         fun immutable() = Immutable(lockStateFlow) | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Holds call until this [SmartMutex] will be re-locked. That means that while [isLocked] == true, [holds] will | ||||||
|  |          * wait for [isLocked] == false and then try to lock | ||||||
|  |          */ | ||||||
|  |         suspend fun lock() { | ||||||
|  |             do { | ||||||
|  |                 waitUnlock() | ||||||
|  |                 val shouldContinue = internalChangesMutex.withLock { | ||||||
|  |                     if (_lockStateFlow.value) { | ||||||
|  |                         true | ||||||
|  |                     } else { | ||||||
|  |                         _lockStateFlow.value = true | ||||||
|  |                         false | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } while (shouldContinue && currentCoroutineContext().isActive) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Will try to lock this [SmartMutex] immediataly | ||||||
|  |          * | ||||||
|  |          * @return True if lock was successful. False otherwise | ||||||
|  |          */ | ||||||
|  |         suspend fun tryLock(): Boolean { | ||||||
|  |             return if (!_lockStateFlow.value) { | ||||||
|  |                 internalChangesMutex.withLock { | ||||||
|  |                     if (!_lockStateFlow.value) { | ||||||
|  |                         _lockStateFlow.value = true | ||||||
|  |                         true | ||||||
|  |                     } else { | ||||||
|  |                         false | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 false | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * If [isLocked] == true - will change it to false and return true. If current call will not unlock this | ||||||
|  |          * [SmartMutex] - false | ||||||
|  |          */ | ||||||
|  |         suspend fun unlock(): Boolean { | ||||||
|  |             return if (_lockStateFlow.value) { | ||||||
|  |                 internalChangesMutex.withLock { | ||||||
|  |                     if (_lockStateFlow.value) { | ||||||
|  |                         _lockStateFlow.value = false | ||||||
|  |                         true | ||||||
|  |                     } else { | ||||||
|  |                         false | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 false | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will call [SmartMutex.Mutable.lock], then execute [action] and return the result after [SmartMutex.Mutable.unlock] | ||||||
|  |  */ | ||||||
|  | @OptIn(ExperimentalContracts::class) | ||||||
|  | suspend inline fun <T> SmartMutex.Mutable.withLock(action: () -> T): T { | ||||||
|  |     contract { | ||||||
|  |         callsInPlace(action, InvocationKind.EXACTLY_ONCE) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     lock() | ||||||
|  |     try { | ||||||
|  |         return action() | ||||||
|  |     } finally { | ||||||
|  |         unlock() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will wait until the [SmartMutex.lockStateFlow] of [this] instance will be false. | ||||||
|  |  * | ||||||
|  |  * Anyway, after the end of this block there are no any guaranties that [SmartMutex.isLocked] == false due to the fact | ||||||
|  |  * that some other parties may lock it again | ||||||
|  |  */ | ||||||
|  | suspend fun SmartMutex.waitUnlock() = lockStateFlow.first { !it } | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.CancellationException | ||||||
|  | import kotlinx.coroutines.Job | ||||||
|  | import org.w3c.dom.Image | ||||||
|  |  | ||||||
|  | suspend fun preloadImage(src: String): Image { | ||||||
|  |     val image = Image() | ||||||
|  |     image.src = src | ||||||
|  |  | ||||||
|  |     val job = Job() | ||||||
|  |  | ||||||
|  |     image.addEventListener("load", { | ||||||
|  |         runCatching { job.complete() } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     runCatchingSafely { | ||||||
|  |         job.join() | ||||||
|  |     }.onFailure { | ||||||
|  |         if (it is CancellationException) { | ||||||
|  |             image.src = "" | ||||||
|  |         } | ||||||
|  |     }.getOrThrow() | ||||||
|  |  | ||||||
|  |     return image | ||||||
|  | } | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.CoroutineScope | ||||||
|  | import kotlinx.coroutines.Dispatchers | ||||||
|  |  | ||||||
|  | fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T { | ||||||
|  |     val scope = CoroutineScope(Dispatchers.Unconfined) | ||||||
|  |     return scope.launchSynchronously(block) | ||||||
|  | } | ||||||
| @@ -6,7 +6,7 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T | |||||||
|     var result: Result<T>? = null |     var result: Result<T>? = null | ||||||
|     val objectToSynchronize = Object() |     val objectToSynchronize = Object() | ||||||
|     synchronized(objectToSynchronize) { |     synchronized(objectToSynchronize) { | ||||||
|         launch { |         launch(start = CoroutineStart.UNDISPATCHED) { | ||||||
|             result = safelyWithResult(block) |             result = safelyWithResult(block) | ||||||
|         }.invokeOnCompletion { |         }.invokeOnCompletion { | ||||||
|             synchronized(objectToSynchronize) { |             synchronized(objectToSynchronize) { | ||||||
|   | |||||||
| @@ -0,0 +1,47 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.Dispatchers | ||||||
|  | import kotlinx.coroutines.delay | ||||||
|  | import kotlinx.coroutines.withContext | ||||||
|  | import kotlin.test.Test | ||||||
|  | import kotlin.test.assertEquals | ||||||
|  |  | ||||||
|  | class LaunchInCurrentThreadTests { | ||||||
|  |     @Test | ||||||
|  |     fun simpleTestThatLaunchInCurrentThreadWorks() { | ||||||
|  |         val expectedResult = 10 | ||||||
|  |         val result = launchInCurrentThread { | ||||||
|  |             expectedResult | ||||||
|  |         } | ||||||
|  |         assertEquals(expectedResult, result) | ||||||
|  |     } | ||||||
|  |     @Test | ||||||
|  |     fun simpleTestThatSeveralLaunchInCurrentThreadWorks() { | ||||||
|  |         val testData = 0 until 100 | ||||||
|  |  | ||||||
|  |         testData.forEach { | ||||||
|  |             val result = launchInCurrentThread { | ||||||
|  |                 it | ||||||
|  |             } | ||||||
|  |             assertEquals(it, result) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     @Test | ||||||
|  |     fun simpleTestThatLaunchInCurrentThreadWillCorrectlyHandleSuspensionsWorks() { | ||||||
|  |         val testData = 0 until 100 | ||||||
|  |  | ||||||
|  |         suspend fun test(data: Any): Any { | ||||||
|  |             return withContext(Dispatchers.Default) { | ||||||
|  |                 delay(1) | ||||||
|  |                 data | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         testData.forEach { | ||||||
|  |             val result = launchInCurrentThread { | ||||||
|  |                 test(it) | ||||||
|  |             } | ||||||
|  |             assertEquals(it, result) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -11,6 +11,7 @@ kotlin { | |||||||
|         commonMain { |         commonMain { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 api project(":micro_utils.common") |                 api project(":micro_utils.common") | ||||||
|  |                 api libs.krypto | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         jsMain { |         jsMain { | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| package dev.inmo.micro_utils.crypto | package dev.inmo.micro_utils.crypto | ||||||
|  |  | ||||||
|  | import com.soywiz.krypto.md5 | ||||||
|  |  | ||||||
| typealias MD5 = String | typealias MD5 = String | ||||||
|  |  | ||||||
| expect fun SourceBytes.md5(): MD5 | fun SourceBytes.md5(): MD5 = md5().hexLower | ||||||
| fun SourceString.md5(): MD5 = encodeToByteArray().md5() | fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower | ||||||
|   | |||||||
| @@ -1,6 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.crypto |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString()) |  | ||||||
| @@ -1,12 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.crypto |  | ||||||
|  |  | ||||||
| import java.math.BigInteger |  | ||||||
| import java.security.MessageDigest |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @suppress |  | ||||||
|  */ |  | ||||||
| actual fun SourceBytes.md5(): MD5 = BigInteger( |  | ||||||
|     1, |  | ||||||
|     MessageDigest.getInstance("MD5").digest(this) |  | ||||||
| ).toString(16) |  | ||||||
| @@ -1,30 +1,5 @@ | |||||||
| apply plugin: 'com.getkeepsafe.dexcount' | apply plugin: 'com.getkeepsafe.dexcount' | ||||||
|  |  | ||||||
| ext { |  | ||||||
|     jvmKotlinFolderFile = { |  | ||||||
|         String sep = File.separator |  | ||||||
|         return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     enableIncludingJvmCodeInAndroidPart = { |  | ||||||
|         File jvmKotlinFolder = jvmKotlinFolderFile() |  | ||||||
|         if (jvmKotlinFolder.exists()) { |  | ||||||
|             android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     disableIncludingJvmCodeInAndroidPart = { |  | ||||||
|         File jvmKotlinFolder = jvmKotlinFolderFile() |  | ||||||
|         String[] oldDirs = android.sourceSets.main.java.srcDirs |  | ||||||
|         android.sourceSets.main.java.srcDirs = [] |  | ||||||
|         for (oldDir in oldDirs) { |  | ||||||
|             if (oldDir != jvmKotlinFolder.path) { |  | ||||||
|                 android.sourceSets.main.java.srcDirs += oldDir |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| android { | android { | ||||||
|     compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger() |     compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger() | ||||||
|     buildToolsVersion libs.versions.android.props.buildTools.get() |     buildToolsVersion libs.versions.android.props.buildTools.get() | ||||||
| @@ -58,10 +33,4 @@ android { | |||||||
|     kotlinOptions { |     kotlinOptions { | ||||||
|         jvmTarget = JavaVersion.VERSION_1_8.toString() |         jvmTarget = JavaVersion.VERSION_1_8.toString() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     sourceSets { |  | ||||||
|         String sep = File.separator |  | ||||||
|         main.java.srcDirs += "src${sep}main${sep}kotlin" |  | ||||||
|         enableIncludingJvmCodeInAndroidPart() |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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")) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -23,11 +23,12 @@ allprojects { | |||||||
|         mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" |         mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" | ||||||
|         mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle" |         mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle" | ||||||
|         mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" |         mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" | ||||||
|  |         mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle" | ||||||
|         mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" |         mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" | ||||||
|  |  | ||||||
|         defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" |         defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" | ||||||
|  |  | ||||||
|         publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle" |         publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle" | ||||||
|         publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle" |         publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -48,8 +48,8 @@ interface DefaultStatesManagerRepo<T : State> { | |||||||
|  */ |  */ | ||||||
| open class DefaultStatesManager<T : State>( | open class DefaultStatesManager<T : State>( | ||||||
|     protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(), |     protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(), | ||||||
|     protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true }, |     protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> false }, | ||||||
|     protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true } |     protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> false } | ||||||
| ) : StatesManager<T> { | ) : StatesManager<T> { | ||||||
|     protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0) |     protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0) | ||||||
|     override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow() |     override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow() | ||||||
|   | |||||||
| @@ -14,5 +14,5 @@ crypto_js_version=4.1.1 | |||||||
| # Project data | # Project data | ||||||
|  |  | ||||||
| group=dev.inmo | group=dev.inmo | ||||||
| version=0.12.16 | version=0.18.2 | ||||||
| android_code_version=155 | android_code_version=193 | ||||||
|   | |||||||
| @@ -1,35 +1,42 @@ | |||||||
| [versions] | [versions] | ||||||
|  |  | ||||||
| kt = "1.7.10" | kt = "1.8.20" | ||||||
| kt-serialization = "1.4.0" | kt-serialization = "1.5.0" | ||||||
| kt-coroutines = "1.6.4" | kt-coroutines = "1.6.4" | ||||||
|  |  | ||||||
| jb-compose = "1.2.0-alpha01-dev774" | kslog = "1.1.1" | ||||||
| jb-exposed = "0.39.2" |  | ||||||
| jb-dokka = "1.7.10" |  | ||||||
|  |  | ||||||
| klock = "3.1.0" | jb-compose = "1.4.0" | ||||||
| uuid = "0.5.0" | jb-exposed = "0.41.1" | ||||||
|  | jb-dokka = "1.8.10" | ||||||
|  |  | ||||||
| ktor = "2.1.1" | korlibs = "3.4.0" | ||||||
|  | uuid = "0.7.0" | ||||||
|  |  | ||||||
|  | ktor = "2.3.0" | ||||||
|  |  | ||||||
| gh-release = "2.4.1" | gh-release = "2.4.1" | ||||||
|  |  | ||||||
| koin = "3.2.0" | koin = "3.4.0" | ||||||
|  |  | ||||||
| android-gradle = "7.2.2" | okio = "3.3.0" | ||||||
| dexcount = "3.1.0" |  | ||||||
|  |  | ||||||
| android-coreKtx = "1.9.0" | ksp = "1.8.20-1.0.11" | ||||||
| android-recyclerView = "1.2.1" | kotlin-poet = "1.13.0" | ||||||
| android-appCompat = "1.5.1" |  | ||||||
| android-fragment = "1.5.3" | android-gradle = "7.4.2" | ||||||
| android-espresso = "3.4.0" | dexcount = "4.0.0" | ||||||
| android-test = "1.1.3" |  | ||||||
|  | android-coreKtx = "1.10.0" | ||||||
|  | android-recyclerView = "1.3.0" | ||||||
|  | android-appCompat = "1.6.1" | ||||||
|  | android-fragment = "1.5.7" | ||||||
|  | android-espresso = "3.5.1" | ||||||
|  | android-test = "1.1.5" | ||||||
|  |  | ||||||
| android-props-minSdk = "21" | android-props-minSdk = "21" | ||||||
| android-props-compileSdk = "33" | android-props-compileSdk = "33" | ||||||
| android-props-buildTools = "33.0.0" | android-props-buildTools = "33.0.2" | ||||||
|  |  | ||||||
| [libraries] | [libraries] | ||||||
|  |  | ||||||
| @@ -60,8 +67,10 @@ 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 = "korlibs" } | ||||||
|  | krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" } | ||||||
| uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | ||||||
| koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | ||||||
|  |  | ||||||
| @@ -80,9 +89,18 @@ android-test-junit = { module = "androidx.test.ext:junit", version.ref = "androi | |||||||
| kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" } | kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" } | ||||||
| kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" } | kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" } | ||||||
|  |  | ||||||
|  | # ksp dependencies | ||||||
|  |  | ||||||
|  | kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" } | ||||||
|  | ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } | ||||||
|  |  | ||||||
|  | okio = { module = "com.squareup.okio:okio", version.ref = "okio" } | ||||||
|  |  | ||||||
|  | # Buildscript | ||||||
|  |  | ||||||
| buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" } | buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" } | ||||||
| buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" } | buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" } | ||||||
|  | buildscript-kt-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" } | ||||||
| buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" } | buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" } | ||||||
| buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" } | buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" } | ||||||
| buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } | buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|   | |||||||
							
								
								
									
										118
									
								
								jvm.publish.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								jvm.publish.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | apply plugin: 'maven-publish' | ||||||
|  |  | ||||||
|  | task javadocJar(type: Jar) { | ||||||
|  |     from javadoc | ||||||
|  |     classifier = 'javadoc' | ||||||
|  | } | ||||||
|  | task sourcesJar(type: Jar) { | ||||||
|  |     from sourceSets.main.allSource | ||||||
|  |     classifier = 'sources' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | publishing { | ||||||
|  |     publications { | ||||||
|  |         maven(MavenPublication) { | ||||||
|  |             from components.java | ||||||
|  |  | ||||||
|  |             artifact javadocJar | ||||||
|  |             artifact sourcesJar | ||||||
|  |  | ||||||
|  |             pom { | ||||||
|  |                 resolveStrategy = Closure.DELEGATE_FIRST | ||||||
|  |  | ||||||
|  |                 description = "It is set of projects with micro tools for avoiding of routines coding" | ||||||
|  |                 name = "${project.name}" | ||||||
|  |                 url = "https://github.com/InsanusMokrassar/MicroUtils/" | ||||||
|  |  | ||||||
|  |                 scm { | ||||||
|  |                     developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git" | ||||||
|  |                     url = "https://github.com/InsanusMokrassar/MicroUtils.git" | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 developers { | ||||||
|  |                      | ||||||
|  |                         developer { | ||||||
|  |                             id = "InsanusMokrassar" | ||||||
|  |                             name = "Aleksei Ovsiannikov" | ||||||
|  |                             email = "ovsyannikov.alexey95@gmail.com" | ||||||
|  |                         } | ||||||
|  |                      | ||||||
|  |  | ||||||
|  |                         developer { | ||||||
|  |                             id = "000Sanya" | ||||||
|  |                             name = "Syrov Aleksandr" | ||||||
|  |                             email = "000sanya.000sanya@gmail.com" | ||||||
|  |                         } | ||||||
|  |                      | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 licenses { | ||||||
|  |                      | ||||||
|  |                         license { | ||||||
|  |                             name = "Apache Software License 2.0" | ||||||
|  |                             url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE" | ||||||
|  |                         } | ||||||
|  |                      | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             repositories { | ||||||
|  |                 if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) { | ||||||
|  |                     maven { | ||||||
|  |                         name = "GithubPackages" | ||||||
|  |                         url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils") | ||||||
|  |                  | ||||||
|  |                         credentials { | ||||||
|  |                             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') | ||||||
|  |                         } | ||||||
|  |                  | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 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)) { | ||||||
|  |                     maven { | ||||||
|  |                         name = "sonatype" | ||||||
|  |                         url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") | ||||||
|  |                  | ||||||
|  |                         credentials { | ||||||
|  |                             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') | ||||||
|  |                         } | ||||||
|  |                  | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | if (project.hasProperty("signing.gnupg.keyName")) { | ||||||
|  |     apply plugin: 'signing' | ||||||
|  |      | ||||||
|  |     signing { | ||||||
|  |         useGpgCmd() | ||||||
|  |      | ||||||
|  |         sign publishing.publications | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     task signAll { | ||||||
|  |         tasks.withType(Sign).forEach { | ||||||
|  |             dependsOn(it) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								jvm.publish.kpsb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								jvm.publish.kpsb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | {"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"} | ||||||
							
								
								
									
										100
									
								
								koin/generator/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								koin/generator/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | # Koin generator | ||||||
|  |  | ||||||
|  | It is Kotlin Symbol Processing generator for `Koin` module in `MicroUtils`. | ||||||
|  |  | ||||||
|  | 1. [What may do this generator](#what-may-do-this-generator) | ||||||
|  | 2. [How to add generator](#how-to-add-generator) | ||||||
|  |  | ||||||
|  | ## What may do this generator | ||||||
|  |  | ||||||
|  | Let's imagine you want to have shortcuts in koin, to get something easily: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | val koin: Koin// some initialization | ||||||
|  |  | ||||||
|  | val someUrl = koin.serverUrl | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | So, in that case you may mark containing file with next annotation (in the beginning of file): | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | @file:GenerateKoinDefinition("serverUrl", String::class, nullable = false) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If file is called like `Sample.kt`, will be generated file `GeneratedDefinitionsSample.kt` with next content: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | public val Scope.serverUrl: String | ||||||
|  |   get() = get(named("serverUrl")) | ||||||
|  |  | ||||||
|  | public val Koin.serverUrl: String | ||||||
|  |   get() = get(named("serverUrl")) | ||||||
|  |  | ||||||
|  | public fun Module.serverUrlSingle(createdAtStart: Boolean = false, | ||||||
|  |     definition: Definition<String>): KoinDefinition<String> = | ||||||
|  |     single(named("serverUrl"), createdAtStart = createdAtStart, definition = definition) | ||||||
|  |  | ||||||
|  | public fun Module.serverUrlFactory(definition: Definition<String>): | ||||||
|  |     KoinDefinition<String> = factory(named("serverUrl"), definition = definition) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Besides, you may use the generics: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | @file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Will generate: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | public val Scope.sampleInfo: Sample<G1, G2> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | public val Koin.sampleInfo: Sample<G1, G2> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | public fun Module.sampleInfoSingle(createdAtStart: Boolean = false, | ||||||
|  |     definition: Definition<Sample<G1, G2>>): KoinDefinition<Sample<G1, G2>> = | ||||||
|  |     single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) | ||||||
|  |  | ||||||
|  | public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>): | ||||||
|  |     KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | In case you wish not to generate single: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | @file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false, generateSingle = false) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | And you will take next code: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | public val Scope.sampleInfo: Sample<G1, G2> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | public val Koin.sampleInfo: Sample<G1, G2> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>): | ||||||
|  |     KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## How to add generator | ||||||
|  |  | ||||||
|  | **Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project** | ||||||
|  |  | ||||||
|  | **Note: $microutils_version in the version of MicroUtils library in your project** | ||||||
|  |  | ||||||
|  | 1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`) | ||||||
|  | 2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"` | ||||||
|  | 3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.koin.generator:$microutils_version`: | ||||||
|  |    ```groovy | ||||||
|  |     dependencies { | ||||||
|  |         add("kspCommonMainMetadata", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in commonMain of your multiplatform module | ||||||
|  |         add("kspJvm", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in main of your JVM module | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     ksp { // this generator do not require any arguments and we should left `ksp` empty | ||||||
|  |     } | ||||||
|  |     ``` | ||||||
							
								
								
									
										15
									
								
								koin/generator/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								koin/generator/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | plugins { | ||||||
|  |     id "org.jetbrains.kotlin.jvm" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | apply from: "$publishJvmOnlyPath" | ||||||
|  |  | ||||||
|  | repositories { | ||||||
|  |     mavenCentral() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     api project(":micro_utils.koin") | ||||||
|  |     api libs.kotlin.poet | ||||||
|  |     api libs.ksp | ||||||
|  | } | ||||||
							
								
								
									
										178
									
								
								koin/generator/src/main/kotlin/Processor.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								koin/generator/src/main/kotlin/Processor.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | |||||||
|  | package dev.inmo.micro_utils.koin.generator | ||||||
|  |  | ||||||
|  | import com.google.devtools.ksp.KSTypeNotPresentException | ||||||
|  | import com.google.devtools.ksp.KSTypesNotPresentException | ||||||
|  | import com.google.devtools.ksp.KspExperimental | ||||||
|  | import com.google.devtools.ksp.getAnnotationsByType | ||||||
|  | import com.google.devtools.ksp.processing.CodeGenerator | ||||||
|  | import com.google.devtools.ksp.processing.Resolver | ||||||
|  | import com.google.devtools.ksp.processing.SymbolProcessor | ||||||
|  | import com.google.devtools.ksp.symbol.KSAnnotated | ||||||
|  | import com.google.devtools.ksp.symbol.KSFile | ||||||
|  | import com.squareup.kotlinpoet.ClassName | ||||||
|  | import com.squareup.kotlinpoet.FileSpec | ||||||
|  | import com.squareup.kotlinpoet.FunSpec | ||||||
|  | import com.squareup.kotlinpoet.ParameterSpec | ||||||
|  | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy | ||||||
|  | import com.squareup.kotlinpoet.PropertySpec | ||||||
|  | import com.squareup.kotlinpoet.asTypeName | ||||||
|  | import com.squareup.kotlinpoet.ksp.toClassName | ||||||
|  | import com.squareup.kotlinpoet.ksp.toTypeName | ||||||
|  | import com.squareup.kotlinpoet.ksp.writeTo | ||||||
|  | import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition | ||||||
|  | import org.koin.core.Koin | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.scope.Scope | ||||||
|  | import java.io.File | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  |  | ||||||
|  | class Processor( | ||||||
|  |     private val codeGenerator: CodeGenerator | ||||||
|  | ) : SymbolProcessor { | ||||||
|  |     private val definitionClassName = ClassName("org.koin.core.definition", "Definition") | ||||||
|  |     private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition") | ||||||
|  |  | ||||||
|  |     @OptIn(KspExperimental::class) | ||||||
|  |     override fun process(resolver: Resolver): List<KSAnnotated> { | ||||||
|  |         resolver.getSymbolsWithAnnotation( | ||||||
|  |             GenerateKoinDefinition::class.qualifiedName!! | ||||||
|  |         ).filterIsInstance<KSFile>().forEach { ksFile -> | ||||||
|  |             FileSpec.builder( | ||||||
|  |                 ksFile.packageName.asString(), | ||||||
|  |                 "GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}" | ||||||
|  |             ).apply { | ||||||
|  |                 addFileComment( | ||||||
|  |                     """ | ||||||
|  |                         THIS CODE HAVE BEEN GENERATED AUTOMATICALLY | ||||||
|  |                         TO REGENERATE IT JUST DELETE FILE | ||||||
|  |                         ORIGINAL FILE: ${ksFile.fileName} | ||||||
|  |                     """.trimIndent() | ||||||
|  |                 ) | ||||||
|  |                 ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach { | ||||||
|  |                     val type = runCatching { | ||||||
|  |                         it.type.asTypeName() | ||||||
|  |                     }.getOrElse { e -> | ||||||
|  |                         if (e is KSTypeNotPresentException) { | ||||||
|  |                             e.ksType.toClassName() | ||||||
|  |                         } else { | ||||||
|  |                             throw e | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     val targetType = runCatching { | ||||||
|  |                         type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type)) | ||||||
|  |                     }.getOrElse { e -> | ||||||
|  |                         when (e) { | ||||||
|  |                             is KSTypeNotPresentException -> e.ksType.toClassName() | ||||||
|  |                         } | ||||||
|  |                         if (e is KSTypesNotPresentException) { | ||||||
|  |                             type.parameterizedBy(*e.ksTypes.map { it.toTypeName() }.toTypedArray()) | ||||||
|  |                         } else { | ||||||
|  |                             throw e | ||||||
|  |                         } | ||||||
|  |                     }.copy( | ||||||
|  |                         nullable = it.nullable | ||||||
|  |                     ) | ||||||
|  |                     fun addGetterProperty( | ||||||
|  |                         receiver: KClass<*> | ||||||
|  |                     ) { | ||||||
|  |                         addProperty( | ||||||
|  |                             PropertySpec.builder( | ||||||
|  |                                 it.name, | ||||||
|  |                                 targetType, | ||||||
|  |                             ).apply { | ||||||
|  |                                 addKdoc( | ||||||
|  |                                     """ | ||||||
|  |                                         @return Definition by key "${it.name}" | ||||||
|  |                                     """.trimIndent() | ||||||
|  |                                 ) | ||||||
|  |                                 getter( | ||||||
|  |                                     FunSpec.getterBuilder().apply { | ||||||
|  |                                         addCode( | ||||||
|  |                                             "return " + (if (it.nullable) { | ||||||
|  |                                                 "getOrNull" | ||||||
|  |                                             } else { | ||||||
|  |                                                 "get" | ||||||
|  |                                             }) + "(named(\"${it.name}\"))" | ||||||
|  |                                         ) | ||||||
|  |                                     }.build() | ||||||
|  |                                 ) | ||||||
|  |                                 receiver(receiver) | ||||||
|  |                             }.build() | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     addGetterProperty(Scope::class) | ||||||
|  |                     addGetterProperty(Koin::class) | ||||||
|  |  | ||||||
|  |                     if (it.generateSingle) { | ||||||
|  |                         addFunction( | ||||||
|  |                             FunSpec.builder("${it.name}Single").apply { | ||||||
|  |                                 addKdoc( | ||||||
|  |                                     """ | ||||||
|  |                                         Will register [definition] with [org.koin.core.module.Module.single] and key "${it.name}" | ||||||
|  |                                     """.trimIndent() | ||||||
|  |                                 ) | ||||||
|  |                                 receiver(Module::class) | ||||||
|  |                                 addParameter( | ||||||
|  |                                     ParameterSpec.builder( | ||||||
|  |                                         "createdAtStart", | ||||||
|  |                                         Boolean::class | ||||||
|  |                                     ).apply { | ||||||
|  |                                         defaultValue("false") | ||||||
|  |                                     }.build() | ||||||
|  |                                 ) | ||||||
|  |                                 addParameter( | ||||||
|  |                                     ParameterSpec.builder( | ||||||
|  |                                         "definition", | ||||||
|  |                                         definitionClassName.parameterizedBy(targetType.copy(nullable = false)) | ||||||
|  |                                     ).build() | ||||||
|  |                                 ) | ||||||
|  |                                 returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false))) | ||||||
|  |                                 addCode( | ||||||
|  |                                     "return single(named(\"${it.name}\"), createdAtStart = createdAtStart, definition = definition)" | ||||||
|  |                                 ) | ||||||
|  |                             }.build() | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (it.generateFactory) { | ||||||
|  |                         addFunction( | ||||||
|  |                             FunSpec.builder("${it.name}Factory").apply { | ||||||
|  |                                 addKdoc( | ||||||
|  |                                     """ | ||||||
|  |                                         Will register [definition] with [org.koin.core.module.Module.factory] and key "${it.name}" | ||||||
|  |                                     """.trimIndent() | ||||||
|  |                                 ) | ||||||
|  |                                 receiver(Module::class) | ||||||
|  |                                 addParameter( | ||||||
|  |                                     ParameterSpec.builder( | ||||||
|  |                                         "definition", | ||||||
|  |                                         definitionClassName.parameterizedBy(targetType.copy(nullable = false)) | ||||||
|  |                                     ).build() | ||||||
|  |                                 ) | ||||||
|  |                                 returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false))) | ||||||
|  |                                 addCode( | ||||||
|  |                                     "return factory(named(\"${it.name}\"), definition = definition)" | ||||||
|  |                                 ) | ||||||
|  |                             }.build() | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                     addImport("org.koin.core.qualifier", "named") | ||||||
|  |                 } | ||||||
|  |             }.build().let { | ||||||
|  |                 File( | ||||||
|  |                     File(ksFile.filePath).parent, | ||||||
|  |                     "GeneratedDefinitions${ksFile.fileName}" | ||||||
|  |                 ).takeIf { !it.exists() } ?.apply { | ||||||
|  |                     parentFile.mkdirs() | ||||||
|  |  | ||||||
|  |                     writer().use { writer -> | ||||||
|  |                         it.writeTo(writer) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return emptyList() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								koin/generator/src/main/kotlin/Provider.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								koin/generator/src/main/kotlin/Provider.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | package dev.inmo.micro_utils.koin.generator | ||||||
|  |  | ||||||
|  | import com.google.devtools.ksp.processing.SymbolProcessor | ||||||
|  | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment | ||||||
|  | import com.google.devtools.ksp.processing.SymbolProcessorProvider | ||||||
|  |  | ||||||
|  | class Provider : SymbolProcessorProvider { | ||||||
|  |     override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor( | ||||||
|  |         environment.codeGenerator | ||||||
|  |     ) | ||||||
|  | } | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | dev.inmo.micro_utils.koin.generator.Provider | ||||||
							
								
								
									
										27
									
								
								koin/generator/test/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								koin/generator/test/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | plugins { | ||||||
|  |     id "org.jetbrains.kotlin.multiplatform" | ||||||
|  |     id "org.jetbrains.kotlin.plugin.serialization" | ||||||
|  |     id "com.android.library" | ||||||
|  |     id "com.google.devtools.ksp" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | kotlin { | ||||||
|  |     sourceSets { | ||||||
|  |         commonMain { | ||||||
|  |             dependencies { | ||||||
|  |                 api project(":micro_utils.koin") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     add("kspCommonMainMetadata", project(":micro_utils.koin.generator")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ksp { | ||||||
|  | } | ||||||
| @@ -0,0 +1,38 @@ | |||||||
|  | // THIS CODE HAVE BEEN GENERATED AUTOMATICALLY | ||||||
|  | // TO REGENERATE IT JUST DELETE FILE | ||||||
|  | // ORIGINAL FILE: Test.kt | ||||||
|  | package dev.inmo.micro_utils.koin.generator.test | ||||||
|  |  | ||||||
|  | import kotlin.Boolean | ||||||
|  | import kotlin.String | ||||||
|  | import org.koin.core.Koin | ||||||
|  | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.definition.KoinDefinition | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.qualifier.named | ||||||
|  | import org.koin.core.scope.Scope | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @return Definition by key "sampleInfo" | ||||||
|  |  */ | ||||||
|  | public val Scope.sampleInfo: Test<String> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @return Definition by key "sampleInfo" | ||||||
|  |  */ | ||||||
|  | public val Koin.sampleInfo: Test<String> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo" | ||||||
|  |  */ | ||||||
|  | public fun Module.sampleInfoSingle(createdAtStart: Boolean = false, | ||||||
|  |     definition: Definition<Test<String>>): KoinDefinition<Test<String>> = | ||||||
|  |     single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo" | ||||||
|  |  */ | ||||||
|  | public fun Module.sampleInfoFactory(definition: Definition<Test<String>>): | ||||||
|  |     KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition) | ||||||
							
								
								
									
										13
									
								
								koin/generator/test/src/commonMain/kotlin/Test.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								koin/generator/test/src/commonMain/kotlin/Test.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | @file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false) | ||||||
|  | package dev.inmo.micro_utils.koin.generator.test | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition | ||||||
|  | import org.koin.core.Koin | ||||||
|  |  | ||||||
|  | class Test<T>( | ||||||
|  |     koin: Koin | ||||||
|  | ) { | ||||||
|  |     init { | ||||||
|  |         koin.sampleInfo | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								koin/generator/test/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								koin/generator/test/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <manifest package="dev.inmo.micro_utils.koin.generator.test"/> | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | package dev.inmo.micro_utils.koin.annotations | ||||||
|  |  | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Use this annotation to mark files near to which generator should place generated extensions for koin [org.koin.core.scope.Scope] | ||||||
|  |  * and [org.koin.core.Koin] | ||||||
|  |  * | ||||||
|  |  * @param name Name for definitions. This name will be available as extension for [org.koin.core.scope.Scope] and [org.koin.core.Koin] | ||||||
|  |  * @param type Type of extensions. It is base star-typed class | ||||||
|  |  * @param typeArgs Generic types for [type]. For example, if [type] == `Something::class` and [typeArgs] == `G1::class, | ||||||
|  |  * G2::class`, the result type will be `Something<G1, G2>` | ||||||
|  |  * @param nullable In case when true, extension will not throw error when definition has not been registered in koin | ||||||
|  |  * @param generateSingle Generate definition factory with [org.koin.core.module.Module.single]. You will be able to use | ||||||
|  |  * the extension [org.koin.core.module.Module].[name]Single(createdAtStart/* default false */) { /* your definition */ } | ||||||
|  |  * @param generateFactory Generate definition factory with [org.koin.core.module.Module.factory]. You will be able to use | ||||||
|  |  * the extension [org.koin.core.module.Module].[name]Factory { /* your definition */ } | ||||||
|  |  */ | ||||||
|  | @Target(AnnotationTarget.FILE) | ||||||
|  | @Repeatable | ||||||
|  | annotation class GenerateKoinDefinition( | ||||||
|  |     val name: String, | ||||||
|  |     val type: KClass<*>, | ||||||
|  |     vararg val typeArgs: KClass<*>, | ||||||
|  |     val nullable: Boolean = true, | ||||||
|  |     val generateSingle: Boolean = true, | ||||||
|  |     val generateFactory: Boolean = true | ||||||
|  | ) | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.koin | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
| import org.koin.core.definition.Definition | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.definition.KoinDefinition | ||||||
| import org.koin.core.instance.InstanceFactory | import org.koin.core.instance.InstanceFactory | ||||||
| import org.koin.core.module.Module | import org.koin.core.module.Module | ||||||
| import org.koin.core.qualifier.Qualifier | import org.koin.core.qualifier.Qualifier | ||||||
| @@ -13,7 +14,7 @@ inline fun <reified T : Any> Module.factoryWithBinds( | |||||||
|     qualifier: Qualifier? = null, |     qualifier: Qualifier? = null, | ||||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|     noinline definition: Definition<T> |     noinline definition: Definition<T> | ||||||
| ): Pair<Module, InstanceFactory<*>> { | ): KoinDefinition<*> { | ||||||
|     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) |     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -21,7 +22,7 @@ inline fun <reified T : Any> Module.factoryWithBinds( | |||||||
|     qualifier: String, |     qualifier: String, | ||||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|     noinline definition: Definition<T> |     noinline definition: Definition<T> | ||||||
| ): Pair<Module, InstanceFactory<*>> { | ): KoinDefinition<*> { | ||||||
|     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) |     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.koin | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
| import org.koin.core.definition.Definition | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.definition.KoinDefinition | ||||||
| import org.koin.core.instance.InstanceFactory | import org.koin.core.instance.InstanceFactory | ||||||
| import org.koin.core.module.Module | import org.koin.core.module.Module | ||||||
| import kotlin.reflect.KClass | import kotlin.reflect.KClass | ||||||
| @@ -8,6 +9,6 @@ import kotlin.reflect.KClass | |||||||
| inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds( | inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds( | ||||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|     noinline definition: Definition<T> |     noinline definition: Definition<T> | ||||||
| ): Pair<Module, InstanceFactory<*>> { | ): KoinDefinition<*> { | ||||||
|     return factoryWithBinds(RandomQualifier(), bindFilter, definition) |     return factoryWithBinds(RandomQualifier(), bindFilter, definition) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.koin | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
| import org.koin.core.definition.Definition | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.definition.KoinDefinition | ||||||
| import org.koin.core.instance.InstanceFactory | import org.koin.core.instance.InstanceFactory | ||||||
| import org.koin.core.module.Module | import org.koin.core.module.Module | ||||||
| import org.koin.core.qualifier.Qualifier | import org.koin.core.qualifier.Qualifier | ||||||
| @@ -14,7 +15,7 @@ inline fun <reified T : Any> Module.singleWithBinds( | |||||||
|     createdAtStart: Boolean = false, |     createdAtStart: Boolean = false, | ||||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|     noinline definition: Definition<T> |     noinline definition: Definition<T> | ||||||
| ): Pair<Module, InstanceFactory<*>> { | ): KoinDefinition<*> { | ||||||
|     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) |     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -24,7 +25,7 @@ inline fun <reified T : Any> Module.singleWithBinds( | |||||||
|     createdAtStart: Boolean = false, |     createdAtStart: Boolean = false, | ||||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|     noinline definition: Definition<T> |     noinline definition: Definition<T> | ||||||
| ): Pair<Module, InstanceFactory<*>> { | ): KoinDefinition<*> { | ||||||
|     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) |     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.koin | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
| import org.koin.core.definition.Definition | import org.koin.core.definition.Definition | ||||||
|  | import org.koin.core.definition.KoinDefinition | ||||||
| import org.koin.core.instance.InstanceFactory | import org.koin.core.instance.InstanceFactory | ||||||
| import org.koin.core.module.Module | import org.koin.core.module.Module | ||||||
| import kotlin.reflect.KClass | import kotlin.reflect.KClass | ||||||
| @@ -9,6 +10,6 @@ inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds( | |||||||
|     createdAtStart: Boolean = false, |     createdAtStart: Boolean = false, | ||||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, |     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||||
|     noinline definition: Definition<T> |     noinline definition: Definition<T> | ||||||
| ): Pair<Module, InstanceFactory<*>> { | ): KoinDefinition<*> { | ||||||
|     return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition) |     return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,5 +15,20 @@ kotlin { | |||||||
|                 api libs.ktor.client |                 api libs.ktor.client | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         androidMain { | ||||||
|  |             dependsOn jvmMain | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         linuxX64Main { | ||||||
|  |             dependencies { | ||||||
|  |                 api internalProject("micro_utils.mime_types") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         mingwX64Main { | ||||||
|  |             dependencies { | ||||||
|  |                 api internalProject("micro_utils.mime_types") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,6 +4,10 @@ import io.ktor.client.call.body | |||||||
| import io.ktor.client.statement.HttpResponse | import io.ktor.client.statement.HttpResponse | ||||||
| import io.ktor.http.HttpStatusCode | import io.ktor.http.HttpStatusCode | ||||||
|  |  | ||||||
| suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf { | suspend inline fun <reified T : Any> HttpResponse.bodyOrNull( | ||||||
|     status == HttpStatusCode.OK |     statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK } | ||||||
| } ?.body<T>() | ) = takeIf(statusFilter) ?.body<T>() | ||||||
|  |  | ||||||
|  | suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> { | ||||||
|  |     it.status != HttpStatusCode.NoContent | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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) |  | ||||||
| } |  | ||||||
| @@ -1,6 +1,10 @@ | |||||||
| 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.common.filesize | ||||||
|  | import dev.inmo.micro_utils.ktor.common.input | ||||||
| import io.ktor.client.request.forms.InputProvider | import io.ktor.client.request.forms.InputProvider | ||||||
|  |  | ||||||
| expect suspend fun MPPFile.inputProvider(): InputProvider | fun MPPFile.inputProvider(): InputProvider = InputProvider(filesize) { | ||||||
|  |     input() | ||||||
|  | } | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ import kotlinx.coroutines.isActive | |||||||
|  * connection. Must return true in case if must be reconnected. By default always reconnecting |  * connection. Must return true in case if must be reconnected. By default always reconnecting | ||||||
|  */ |  */ | ||||||
| @Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties") | @Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties") | ||||||
| inline fun <reified T : Any> openBaseWebSocketFlow( | inline fun <T : Any> openBaseWebSocketFlow( | ||||||
|     noinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, |     noinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, | ||||||
|     noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit |     noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit | ||||||
| ): Flow<T> { | ): Flow<T> { | ||||||
| @@ -57,7 +57,7 @@ inline fun <reified T : Any> HttpClient.openWebSocketFlow( | |||||||
| ): Flow<T> { | ): Flow<T> { | ||||||
|     pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow") |     pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow") | ||||||
|  |  | ||||||
|     return openBaseWebSocketFlow<T>(checkReconnection) { |     return openBaseWebSocketFlow(checkReconnection) { | ||||||
|         val block: suspend DefaultClientWebSocketSession.() -> Unit = { |         val block: suspend DefaultClientWebSocketSession.() -> Unit = { | ||||||
|             while (isActive) { |             while (isActive) { | ||||||
|                 send(receiveDeserialized<T>()) |                 send(receiveDeserialized<T>()) | ||||||
|   | |||||||
| @@ -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,11 +0,0 @@ | |||||||
| package dev.inmo.micro_utils.ktor.client |  | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.common.* |  | ||||||
| import io.ktor.client.request.forms.InputProvider |  | ||||||
| import io.ktor.utils.io.core.ByteReadPacket |  | ||||||
|  |  | ||||||
| actual suspend fun MPPFile.inputProvider(): InputProvider = bytes().let { |  | ||||||
|     InputProvider(it.size.toLong()) { |  | ||||||
|         ByteReadPacket(it) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -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,6 @@ 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() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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,107 @@ | |||||||
|  | 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.InternalSerializationApi | ||||||
|  | import kotlinx.serialization.SerializationStrategy | ||||||
|  | import kotlinx.serialization.StringFormat | ||||||
|  | import kotlinx.serialization.encodeToString | ||||||
|  | import kotlinx.serialization.serializer | ||||||
|  | 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 | ||||||
|  |  */ | ||||||
|  | @OptIn(InternalSerializationApi::class) | ||||||
|  | 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 { | ||||||
|  |         for (k in data.keys) { | ||||||
|  |             val v = data[k] ?: continue | ||||||
|  |             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::class.serializer() as SerializationStrategy<in Any>, 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 { | ||||||
|  |                 for (it in formData) { | ||||||
|  |                     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 | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | 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.TemporalFileId | ||||||
|  | import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny | ||||||
|  | import io.ktor.client.HttpClient | ||||||
|  | import io.ktor.client.plugins.onUpload | ||||||
|  | import io.ktor.client.request.forms.formData | ||||||
|  | import io.ktor.client.request.forms.submitFormWithBinaryData | ||||||
|  | import io.ktor.client.statement.bodyAsText | ||||||
|  | import io.ktor.http.Headers | ||||||
|  | import io.ktor.http.HttpHeaders | ||||||
|  |  | ||||||
|  | internal val MPPFile.mimeType: String | ||||||
|  |     get() = getMimeTypeOrAny(filename.extension).raw | ||||||
|  |  | ||||||
|  | actual suspend fun HttpClient.tempUpload( | ||||||
|  |     fullTempUploadDraftPath: String, | ||||||
|  |     file: MPPFile, | ||||||
|  |     onUpload: OnUploadCallback | ||||||
|  | ): TemporalFileId { | ||||||
|  |     val inputProvider = file.inputProvider() | ||||||
|  |     val fileId = submitFormWithBinaryData( | ||||||
|  |         fullTempUploadDraftPath, | ||||||
|  |         formData = formData { | ||||||
|  |             append( | ||||||
|  |                 "data", | ||||||
|  |                 inputProvider, | ||||||
|  |                 Headers.build { | ||||||
|  |                     append(HttpHeaders.ContentType, file.mimeType) | ||||||
|  |                     append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"") | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     ) { | ||||||
|  |         onUpload(onUpload) | ||||||
|  |     }.bodyAsText() | ||||||
|  |     return TemporalFileId(fileId) | ||||||
|  | } | ||||||
							
								
								
									
										107
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.client | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.MPPFile | ||||||
|  | import dev.inmo.micro_utils.common.Progress | ||||||
|  | import io.ktor.client.HttpClient | ||||||
|  | import io.ktor.client.engine.mergeHeaders | ||||||
|  | import io.ktor.client.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.InternalSerializationApi | ||||||
|  | import kotlinx.serialization.SerializationStrategy | ||||||
|  | import kotlinx.serialization.StringFormat | ||||||
|  | import kotlinx.serialization.encodeToString | ||||||
|  | import kotlinx.serialization.serializer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 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 | ||||||
|  |  */ | ||||||
|  | @OptIn(InternalSerializationApi::class) | ||||||
|  | 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 MPPFile || it is UniUploadFileInfo } | ||||||
|  |  | ||||||
|  |     val formData = formData { | ||||||
|  |         for (k in data.keys) { | ||||||
|  |             val v = data[k] ?: continue | ||||||
|  |             when (v) { | ||||||
|  |                 is MPPFile -> append( | ||||||
|  |                     k, | ||||||
|  |                     v.inputProvider(), | ||||||
|  |                     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::class.serializer() as SerializationStrategy<in Any>, 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 { | ||||||
|  |                 for (it in formData) { | ||||||
|  |                     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 | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | 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.TemporalFileId | ||||||
|  | import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny | ||||||
|  | import io.ktor.client.HttpClient | ||||||
|  | import io.ktor.client.plugins.onUpload | ||||||
|  | import io.ktor.client.request.forms.formData | ||||||
|  | import io.ktor.client.request.forms.submitFormWithBinaryData | ||||||
|  | import io.ktor.client.statement.bodyAsText | ||||||
|  | import io.ktor.http.Headers | ||||||
|  | import io.ktor.http.HttpHeaders | ||||||
|  |  | ||||||
|  | internal val MPPFile.mimeType: String | ||||||
|  |     get() = getMimeTypeOrAny(filename.extension).raw | ||||||
|  |  | ||||||
|  | actual suspend fun HttpClient.tempUpload( | ||||||
|  |     fullTempUploadDraftPath: String, | ||||||
|  |     file: MPPFile, | ||||||
|  |     onUpload: OnUploadCallback | ||||||
|  | ): TemporalFileId { | ||||||
|  |     val inputProvider = file.inputProvider() | ||||||
|  |     val fileId = submitFormWithBinaryData( | ||||||
|  |         fullTempUploadDraftPath, | ||||||
|  |         formData = formData { | ||||||
|  |             append( | ||||||
|  |                 "data", | ||||||
|  |                 inputProvider, | ||||||
|  |                 Headers.build { | ||||||
|  |                     append(HttpHeaders.ContentType, file.mimeType) | ||||||
|  |                     append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"") | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     ) { | ||||||
|  |         onUpload(onUpload) | ||||||
|  |     }.bodyAsText() | ||||||
|  |     return TemporalFileId(fileId) | ||||||
|  | } | ||||||
							
								
								
									
										107
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.client | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.MPPFile | ||||||
|  | import dev.inmo.micro_utils.common.Progress | ||||||
|  | import io.ktor.client.HttpClient | ||||||
|  | import io.ktor.client.engine.mergeHeaders | ||||||
|  | import io.ktor.client.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.InternalSerializationApi | ||||||
|  | import kotlinx.serialization.SerializationStrategy | ||||||
|  | import kotlinx.serialization.StringFormat | ||||||
|  | import kotlinx.serialization.encodeToString | ||||||
|  | import kotlinx.serialization.serializer | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 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 | ||||||
|  |  */ | ||||||
|  | @OptIn(InternalSerializationApi::class) | ||||||
|  | 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 MPPFile || it is UniUploadFileInfo } | ||||||
|  |  | ||||||
|  |     val formData = formData { | ||||||
|  |         for (k in data.keys) { | ||||||
|  |             val v = data[k] ?: continue | ||||||
|  |             when (v) { | ||||||
|  |                 is MPPFile -> append( | ||||||
|  |                     k, | ||||||
|  |                     v.inputProvider(), | ||||||
|  |                     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::class.serializer() as SerializationStrategy<in Any>, 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 { | ||||||
|  |                 for (it in formData) { | ||||||
|  |                     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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -17,5 +17,8 @@ kotlin { | |||||||
|                 api libs.ktor.io |                 api libs.ktor.io | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         androidMain { | ||||||
|  |             dependsOn jvmMain | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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() | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								ktor/common/src/linuxX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ktor/common/src/linuxX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.common | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.MPPFile | ||||||
|  | import dev.inmo.micro_utils.common.bytesAllocatorSync | ||||||
|  | import io.ktor.utils.io.core.ByteReadPacket | ||||||
|  | import io.ktor.utils.io.core.Input | ||||||
|  |  | ||||||
|  | actual fun MPPFile.input(): Input { | ||||||
|  |     return ByteReadPacket(bytesAllocatorSync()) | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								ktor/common/src/mingwX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								ktor/common/src/mingwX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.common | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.MPPFile | ||||||
|  | import dev.inmo.micro_utils.common.bytesAllocatorSync | ||||||
|  | import io.ktor.utils.io.core.ByteReadPacket | ||||||
|  | import io.ktor.utils.io.core.Input | ||||||
|  |  | ||||||
|  | actual fun MPPFile.input(): Input { | ||||||
|  |     return ByteReadPacket(bytesAllocatorSync()) | ||||||
|  | } | ||||||
| @@ -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().asOutput().use { | ||||||
|  |             input.copyTo(it) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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,45 @@ | |||||||
|  | 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") | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | suspend fun ApplicationCall.getParametersOrSendError( | ||||||
|  |     field: String | ||||||
|  | ) = parameters.getAll(field).also { | ||||||
|  |     if (it == null) { | ||||||
|  |         respond(HttpStatusCode.BadRequest, "Request must contains $field") | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun ApplicationCall.getQueryParameter( | ||||||
|  |     field: String | ||||||
|  | ) = request.queryParameters[field] | ||||||
|  |  | ||||||
|  | fun ApplicationCall.getQueryParameters( | ||||||
|  |     field: String | ||||||
|  | ) = request.queryParameters.getAll(field) | ||||||
|  |  | ||||||
|  | suspend fun ApplicationCall.getQueryParameterOrSendError( | ||||||
|  |     field: String | ||||||
|  | ) = getQueryParameter(field).also { | ||||||
|  |     if (it == null) { | ||||||
|  |         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | suspend fun ApplicationCall.getQueryParametersOrSendError( | ||||||
|  |     field: String | ||||||
|  | ) = getQueryParameters(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,7 +79,6 @@ 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 | ||||||
| @@ -117,7 +115,6 @@ class TemporalFilesRoutingConfigurator( | |||||||
|             } ?: call.respond(HttpStatusCode.BadRequest) |             } ?: call.respond(HttpStatusCode.BadRequest) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     suspend fun removeTemporalFile(temporalFileId: TemporalFileId) { |     suspend fun removeTemporalFile(temporalFileId: TemporalFileId) { | ||||||
|         temporalFilesMutex.withLock { |         temporalFilesMutex.withLock { | ||||||
|   | |||||||
| @@ -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() | ||||||
|  | } | ||||||
| @@ -5,3 +5,11 @@ plugins { | |||||||
| } | } | ||||||
|  |  | ||||||
| apply from: "$mppProjectWithSerializationPresetPath" | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
|  | kotlin { | ||||||
|  |     sourceSets { | ||||||
|  |         androidMain { | ||||||
|  |             dependsOn jvmMain | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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
											
										
									
								
							
							
								
								
									
										8
									
								
								language_codes/src/jvmMain/kotlin/Locale.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								language_codes/src/jvmMain/kotlin/Locale.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | package dev.inmo.micro_utils.language_codes | ||||||
|  |  | ||||||
|  | import java.util.Locale | ||||||
|  |  | ||||||
|  | fun IetfLanguageCode.toJavaLocale(): Locale = Locale.forLanguageTag(code) | ||||||
|  | fun IetfLanguageCode?.toJavaLocaleOrDefault(): Locale = this ?.toJavaLocale() ?: Locale.getDefault() | ||||||
|  |  | ||||||
|  | fun Locale.toIetfLanguageCode(): IetfLanguageCode = IetfLanguageCode(toLanguageTag()) | ||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import math | ||||||
|  |  | ||||||
| import requests | import requests | ||||||
| from bs4 import BeautifulSoup | from bs4 import BeautifulSoup | ||||||
| import pandas as pd | import pandas as pd | ||||||
| @@ -17,32 +19,45 @@ def fix_name(category, raw_name): | |||||||
|         result += out1 |         result += out1 | ||||||
|     return result |     return result | ||||||
|  |  | ||||||
|  | def remove_prefix(text, prefix): | ||||||
|  |     if text.startswith(prefix): | ||||||
|  |         return text[len(prefix):] | ||||||
|  |     return text  # or whatever | ||||||
|  |  | ||||||
|  | def extensionPreparationFun(extension): | ||||||
|  |     return "\"%s\"" % (remove_prefix(extension, ".")) | ||||||
|  |  | ||||||
|  | # 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('local.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] | ||||||
|  |         extensions = list() | ||||||
|  |         if isinstance(row[1][2], str): | ||||||
|  |             extensions = list(map(extensionPreparationFun, row[1][2].split(", "))) | ||||||
|         mime_category = mime.split('/', 1)[0] |         mime_category = mime.split('/', 1)[0] | ||||||
|         mime_name = mime.split('/', 1)[1] |         mime_name = mime.split('/', 1)[1] | ||||||
|         mimes.append({ |         mimes.append([ | ||||||
|             'mime_category': mime_category, |             mime_category, | ||||||
|             'mime_name': mime_name, |             mime_name, | ||||||
|         }) |             extensions | ||||||
|  |         ]) | ||||||
|  |  | ||||||
|     # codegen |     # codegen | ||||||
|  |  | ||||||
|     mimes.sort(key=lambda x: x['mime_category']) |     mimes.sort(key=lambda x: x[0]) | ||||||
|     grouped = itertools.groupby(mimes, lambda x: x['mime_category']) |     grouped = itertools.groupby(mimes, lambda x: x[0]) | ||||||
|     code = '' |     code = '' | ||||||
|     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, extensions: Array<String> = emptyArray()) : MimeType, KnownMimeTypes(raw, extensions) {\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: | ||||||
|             name = fix_name(mime['mime_category'], mime['mime_name']) |             name = fix_name(mime[0], mime[1]) | ||||||
|             code += '    @Serializable(MimeTypeSerializer::class)\n    object %s: %s ("%s/%s")\n' % (name, group_name, mime['mime_category'], mime['mime_name']) |             code += '    @Serializable(MimeTypeSerializer::class)\n    object %s: %s ("%s/%s", arrayOf(%s))\n' % (name, group_name, mime[0], mime[1], ", ".join(mime[2])) | ||||||
|             code2 += '    KnownMimeTypes.%s.%s,\n' % (group_name, name) |             code2 += '    KnownMimeTypes.%s.%s,\n' % (group_name, name) | ||||||
|         code += '}\n\n' |         code += '}\n\n' | ||||||
|     code2 += ')\n' |     code2 += ')\n' | ||||||
|   | |||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | package dev.inmo.micro_utils.mime_types | ||||||
|  |  | ||||||
|  | data class CustomMimeType(override val raw: String) : MimeType | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | package dev.inmo.micro_utils.mime_types | ||||||
|  |  | ||||||
|  | val mimeTypesByExtensions: Map<String, Array<MimeType>> by lazy { | ||||||
|  |     val extensionsMap = mutableMapOf<String, MutableList<MimeType>>() | ||||||
|  |  | ||||||
|  |     knownMimeTypes.forEach { mimeType -> | ||||||
|  |         mimeType.extensions.forEach { | ||||||
|  |             extensionsMap.getOrPut(it) { mutableListOf() }.add(mimeType) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     extensionsMap.mapValues { | ||||||
|  |         it.value.toTypedArray() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun getMimeType( | ||||||
|  |     stringWithExtension: String, | ||||||
|  |     selector: (Array<MimeType>) -> MimeType? = { it.firstOrNull() } | ||||||
|  | ) = mimeTypesByExtensions[stringWithExtension.takeLastWhile { it != '.' }] ?.takeIf { it.isNotEmpty() } ?.let(selector) | ||||||
|  | inline fun getMimeTypeOrAny( | ||||||
|  |     stringWithExtension: String, | ||||||
|  |     selector: (Array<MimeType>) -> MimeType? = { it.firstOrNull() } | ||||||
|  | ) = getMimeType(stringWithExtension, selector) ?: KnownMimeTypes.Any | ||||||
										
											
												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,10 @@ | |||||||
|  | package dev.inmo.micro_utils.mime_types | ||||||
|  |  | ||||||
|  | import kotlinx.serialization.Serializable | ||||||
|  |  | ||||||
|  | @Serializable(MimeTypeSerializer::class) | ||||||
|  | interface MimeType { | ||||||
|  |     val raw: String | ||||||
|  |     val extensions: Array<String> | ||||||
|  |         get() = emptyArray() | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user