mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-31 12:10:29 +00:00 
			
		
		
		
	Compare commits
	
		
			227 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| de999e197f | |||
| 9d95687d3c | |||
| aa9dfb4ab8 | |||
| 9c5b44efb3 | |||
| ac587a67e6 | |||
| 59428140a8 | |||
| 60bdb59d71 | |||
| be52871de8 | |||
| b7934cf357 | |||
| dbfbeef90a | |||
| 00943c9cdf | |||
| 8745c6a16a | |||
| 433ba4b58f | |||
| d40376e524 | |||
| a2982f88f5 | |||
| 1642f7abd9 | |||
| a10d2184ff | |||
| 522435f096 | |||
| 79b30290c0 | |||
| f8b8626859 | |||
| b061b85a08 | |||
| 3870db1c88 | |||
| 1be1070eb4 | |||
| 2696e663cf | |||
| 1e1f7df86d | |||
| 1d8ded8fd3 | |||
| 197825123a | 
							
								
								
									
										14
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,9 +9,6 @@ jobs: | ||||
|       - uses: actions/setup-java@v1 | ||||
|         with: | ||||
|           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 | ||||
|         run: | | ||||
|           branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`" | ||||
| @@ -20,9 +17,8 @@ jobs: | ||||
|           mv gradle.properties.tmp gradle.properties | ||||
|       - name: Build | ||||
|         run: ./gradlew build | ||||
| #      - name: Publish | ||||
| #        continue-on-error: true | ||||
| #        run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository | ||||
| #        env: | ||||
| #          GITHUBPACKAGES_USER: ${{ github.actor }} | ||||
| #          GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} | ||||
|       - name: Publish | ||||
|         continue-on-error: true | ||||
|         run: ./gradlew publishAllPublicationsToGiteaRepository | ||||
|         env: | ||||
|           GITEA_TOKEN: ${{ secrets.GITEA_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 | ||||
|         with: | ||||
|           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 | ||||
|         run: ./gradlew build && ./gradlew dokkaHtml | ||||
|       - 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") | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										284
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										284
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,289 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 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 | ||||
|  | ||||
| * `Coroutines`: | ||||
|   * `Android`: | ||||
|     * Add class `FlowOnHierarchyChangeListener` | ||||
|     * Add `ViewGroup#setOnHierarchyChangeListenerRecursively(OnHierarchyChangeListener)` | ||||
|  | ||||
| ## 0.12.15 | ||||
|  | ||||
| * `Common`: | ||||
|     * `applyDiff` will return `Diff` object since this release | ||||
|     * `Android`: | ||||
|       * New functions/extensions `findViewsByTag` and `findViewsByTagInActivity` | ||||
| * `Coroutines`: | ||||
|     * Add `Flow` extensions `flatMap`, `flatMapNotNull` and `flatten` | ||||
|     * Add `Flow` extensions `takeNotNull` and `filterNotNull` | ||||
|  | ||||
| ## 0.12.14 | ||||
|  | ||||
| * `Versions`: | ||||
|   * `Android CoreKTX`: `1.8.0` -> `1.9.0` | ||||
|   * `Android AppCompat`: `1.4.2` -> `1.5.1` | ||||
|   * Android Compile SDK: 32 -> 33 | ||||
|   * Android Build Tools: 32.0.0 -> 33.0.0 | ||||
| * `Common`: | ||||
|   * `Android`: | ||||
|     * Add `argumentOrNull`/`argumentOrThrow` delegates for fragments | ||||
| * `Coroutines`: | ||||
|   * Rewrite `awaitFirstWithDeferred` onto `CompletableDeferred` instead of coroutines suspending | ||||
|  | ||||
| ## 0.12.13 | ||||
|  | ||||
| * `Coroutines`: | ||||
|   * Add opportunity to use markers in actors (solution of [#160](https://github.com/InsanusMokrassar/MicroUtils/issues/160)) | ||||
| * `Koin`: | ||||
|   * Module inited :) | ||||
| * `Repos`: | ||||
|   * `Android`: | ||||
|     * Add typealias `KeyValueSPRepo` and opportunity to create shared preferences `KeyValue` repo with `KeyValueStore(...)` (fix of [#155](https://github.com/InsanusMokrassar/MicroUtils/issues/155)) | ||||
|  | ||||
| ## 0.12.12 | ||||
|  | ||||
| * `Common`: | ||||
|   | ||||
| @@ -9,6 +9,7 @@ buildscript { | ||||
|     dependencies { | ||||
|         classpath libs.buildscript.kt.gradle | ||||
|         classpath libs.buildscript.kt.serialization | ||||
|         classpath libs.buildscript.kt.ksp | ||||
|         classpath libs.buildscript.jb.dokka | ||||
|         classpath libs.buildscript.gh.release | ||||
|         classpath libs.buildscript.android.gradle | ||||
|   | ||||
| @@ -16,7 +16,9 @@ kotlin { | ||||
|         androidMain { | ||||
|             dependencies { | ||||
|                 api project(":micro_utils.coroutines") | ||||
|                 api libs.android.fragment | ||||
|             } | ||||
|             dependsOn jvmMain | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
| import kotlinx.serialization.Serializable | ||||
|  | ||||
| private inline fun <T> getObject( | ||||
|     additional: MutableList<T>, | ||||
|     iterator: Iterator<T> | ||||
| @@ -14,15 +16,24 @@ private inline fun <T> getObject( | ||||
| /** | ||||
|  * Diff object which contains information about differences between two [Iterable]s | ||||
|  * | ||||
|  * See tests for more info | ||||
|  * | ||||
|  * @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection | ||||
|  * @param added The object which appear in new collection only. Indexes here show the index in the new collection | ||||
|  * @param replaced Pair of old-new changes. First object has been presented in the old collection on its | ||||
|  * [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in | ||||
|  * case when some value has been replaced after adds or removes in original collection the object index will be changed | ||||
|  * | ||||
|  * @see calculateDiff | ||||
|  */ | ||||
| @Serializable | ||||
| data class Diff<T> internal constructor( | ||||
|     val removed: List<IndexedValue<T>>, | ||||
|     val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>, | ||||
|     /** | ||||
|      * Old-New values pairs | ||||
|      */ | ||||
|     val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>, | ||||
|     val added: List<IndexedValue<T>> | ||||
|     val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>, | ||||
|     val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>> | ||||
| ) | ||||
|  | ||||
| private inline fun <T> performChanges( | ||||
| @@ -165,7 +176,7 @@ inline fun <T> Iterable<T>.calculateStrictDiff( | ||||
| fun <T> MutableList<T>.applyDiff( | ||||
|     source: Iterable<T>, | ||||
|     strictComparison: Boolean = false | ||||
| ) = calculateDiff(source, strictComparison).let { | ||||
| ): Diff<T> = calculateDiff(source, strictComparison).also { | ||||
|     for (i in it.removed.indices.sortedDescending()) { | ||||
|         removeAt(it.removed[i].index) | ||||
|     } | ||||
|   | ||||
| @@ -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,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 | ||||
|  | ||||
| /** | ||||
|  * 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]. | ||||
|  * | ||||
| @@ -10,12 +32,23 @@ inline fun <R> repeatOnFailure( | ||||
|     onEachFailure: (Throwable) -> Unit = {}, | ||||
|     action: (Int) -> R | ||||
| ): Optional<R> { | ||||
|     repeat(times) { | ||||
|         runCatching { | ||||
|             action(it) | ||||
|         }.onFailure(onEachFailure).onSuccess { | ||||
|             return Optional.presented(it) | ||||
|     var i = 0 | ||||
|     val result = repeatOnFailure( | ||||
|         { | ||||
|             onEachFailure(it) | ||||
|             if (i < times) { | ||||
|                 i++ | ||||
|                 true | ||||
|             } else { | ||||
|                 false | ||||
|             } | ||||
|         } | ||||
|     ) { | ||||
|         action(i) | ||||
|     } | ||||
|     return if (result.isSuccess) { | ||||
|         Optional.presented(result.getOrThrow()) | ||||
|     } else { | ||||
|         Optional.absent() | ||||
|     } | ||||
|     return Optional.absent() | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,20 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import java.io.File | ||||
| import java.io.InputStream | ||||
| import java.util.UUID | ||||
|  | ||||
| fun InputStream.downloadToTempFile( | ||||
|     fileName: String = UUID.randomUUID().toString(), | ||||
|     fileExtension: String? = ".temp", | ||||
|     folder: File? = null | ||||
| ) = File.createTempFile( | ||||
|     fileName, | ||||
|     fileExtension, | ||||
|     folder | ||||
| ).apply { | ||||
|     outputStream().use { | ||||
|         copyTo(it) | ||||
|     } | ||||
|     deleteOnExit() | ||||
| } | ||||
| @@ -0,0 +1,76 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.os.Parcelable | ||||
| import androidx.fragment.app.Fragment | ||||
| import java.io.Serializable | ||||
| import kotlin.reflect.KProperty | ||||
|  | ||||
| object ArgumentPropertyNullableDelegate { | ||||
|     operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T? { | ||||
|         val arguments = thisRef.arguments ?: return null | ||||
|         val key = property.name | ||||
|         return when (property.getter.returnType.classifier) { | ||||
|             // Scalars | ||||
|             String::class -> arguments.getString(key) | ||||
|             Boolean::class -> arguments.getBoolean(key) | ||||
|             Byte::class -> arguments.getByte(key) | ||||
|             Char::class -> arguments.getChar(key) | ||||
|             Double::class -> arguments.getDouble(key) | ||||
|             Float::class -> arguments.getFloat(key) | ||||
|             Int::class -> arguments.getInt(key) | ||||
|             Long::class -> arguments.getLong(key) | ||||
|             Short::class -> arguments.getShort(key) | ||||
|  | ||||
|             // References | ||||
|             Bundle::class -> arguments.getBundle(key) | ||||
|             CharSequence::class -> arguments.getCharSequence(key) | ||||
|             Parcelable::class -> arguments.getParcelable(key) | ||||
|  | ||||
|             // Scalar arrays | ||||
|             BooleanArray::class -> arguments.getBooleanArray(key) | ||||
|             ByteArray::class -> arguments.getByteArray(key) | ||||
|             CharArray::class -> arguments.getCharArray(key) | ||||
|             DoubleArray::class -> arguments.getDoubleArray(key) | ||||
|             FloatArray::class -> arguments.getFloatArray(key) | ||||
|             IntArray::class -> arguments.getIntArray(key) | ||||
|             LongArray::class -> arguments.getLongArray(key) | ||||
|             ShortArray::class -> arguments.getShortArray(key) | ||||
|             Array::class -> { | ||||
|                 val componentType = property.returnType.classifier ?.javaClass ?.componentType!! | ||||
|                 @Suppress("UNCHECKED_CAST") // Checked by reflection. | ||||
|                 when { | ||||
|                     Parcelable::class.java.isAssignableFrom(componentType) -> { | ||||
|                         arguments.getParcelableArray(key) | ||||
|                     } | ||||
|                     String::class.java.isAssignableFrom(componentType) -> { | ||||
|                         arguments.getStringArray(key) | ||||
|                     } | ||||
|                     CharSequence::class.java.isAssignableFrom(componentType) -> { | ||||
|                         arguments.getCharSequenceArray(key) | ||||
|                     } | ||||
|                     Serializable::class.java.isAssignableFrom(componentType) -> { | ||||
|                         arguments.getSerializable(key) | ||||
|                     } | ||||
|                     else -> { | ||||
|                         val valueType = componentType.canonicalName | ||||
|                         throw IllegalArgumentException( | ||||
|                             "Illegal value array type $valueType for key \"$key\"" | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Serializable::class -> arguments.getSerializable(key) | ||||
|             else -> null | ||||
|         } as? T | ||||
|     } | ||||
| } | ||||
|  | ||||
| object ArgumentPropertyNonNullableDelegate { | ||||
|     operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T { | ||||
|         return ArgumentPropertyNullableDelegate.getValue<T>(thisRef, property)!! | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun argumentOrNull() = ArgumentPropertyNullableDelegate | ||||
| fun argumentOrThrow() = ArgumentPropertyNonNullableDelegate | ||||
| @@ -0,0 +1,61 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.core.view.children | ||||
| import androidx.fragment.app.Fragment | ||||
|  | ||||
| fun findViewsByTag(viewGroup: ViewGroup, tag: Any?): List<View> { | ||||
|     return viewGroup.children.flatMap { | ||||
|         findViewsByTag(it, tag) | ||||
|     }.toList() | ||||
| } | ||||
|  | ||||
| fun findViewsByTag(viewGroup: ViewGroup, key: Int, tag: Any?): List<View> { | ||||
|     return viewGroup.children.flatMap { | ||||
|         findViewsByTag(it, key, tag) | ||||
|     }.toList() | ||||
| } | ||||
|  | ||||
| fun findViewsByTag(view: View, tag: Any?): List<View> { | ||||
|     val result = mutableListOf<View>() | ||||
|     if (view.tag == tag) { | ||||
|         result.add(view) | ||||
|     } | ||||
|     if (view is ViewGroup) { | ||||
|         result.addAll(findViewsByTag(view, tag)) | ||||
|     } | ||||
|     return result.toList() | ||||
| } | ||||
|  | ||||
| fun findViewsByTag(view: View, key: Int, tag: Any?): List<View> { | ||||
|     val result = mutableListOf<View>() | ||||
|     if (view.getTag(key) == tag) { | ||||
|         result.add(view) | ||||
|     } | ||||
|     if (view is ViewGroup) { | ||||
|         result.addAll(findViewsByTag(view, key, tag)) | ||||
|     } | ||||
|     return result.toList() | ||||
| } | ||||
|  | ||||
| fun Activity.findViewsByTag(tag: Any?) = rootView ?.let { | ||||
|     findViewsByTag(it, tag) | ||||
| } | ||||
|  | ||||
| fun Activity.findViewsByTag(key: Int, tag: Any?) = rootView ?.let { | ||||
|     findViewsByTag(it, key, tag) | ||||
| } | ||||
|  | ||||
| fun Fragment.findViewsByTag(tag: Any?) = view ?.let { | ||||
|     findViewsByTag(it, tag) | ||||
| } | ||||
|  | ||||
| fun Fragment.findViewsByTag(key: Int, tag: Any?) = view ?.let { | ||||
|     findViewsByTag(it, key, tag) | ||||
| } | ||||
|  | ||||
| fun Fragment.findViewsByTagInActivity(tag: Any?) = activity ?.findViewsByTag(tag) | ||||
|  | ||||
| fun Fragment.findViewsByTagInActivity(key: Int, tag: Any?) = activity ?.findViewsByTag(key, tag) | ||||
| @@ -0,0 +1,7 @@ | ||||
| package dev.inmo.micro_utils.common | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.view.View | ||||
|  | ||||
| val Activity.rootView: View? | ||||
|     get() = findViewById<View?>(android.R.id.content) ?.rootView ?: window.decorView.findViewById<View?>(android.R.id.content) ?.rootView | ||||
| @@ -22,6 +22,7 @@ kotlin { | ||||
|             dependencies { | ||||
|                 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.snapshots.SnapshotStateList | ||||
| 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 kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| 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") | ||||
| inline fun <reified T> Flow<List<T>>.asMutableComposeListState( | ||||
|     scope: CoroutineScope | ||||
|     scope: CoroutineScope, | ||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||
|     noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
| ): SnapshotStateList<T> { | ||||
|     val state = mutableStateListOf<T>() | ||||
|     subscribeSafelyWithoutExceptions(scope) { | ||||
|     val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let { | ||||
|         { | ||||
|             withContext(useContextOnChange) { | ||||
|                 state.applyDiff(it) | ||||
|             } | ||||
|         } | ||||
|     } ?: { | ||||
|         state.applyDiff(it) | ||||
|     } | ||||
|     subscribeSafelyWithoutExceptions(scope, onException, changeBlock) | ||||
|     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") | ||||
| inline fun <reified T> Flow<List<T>>.asComposeList( | ||||
|     scope: CoroutineScope | ||||
| ): List<T> = asMutableComposeListState(scope) | ||||
|     scope: CoroutineScope, | ||||
|     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 | ||||
|  | ||||
| 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 kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| 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( | ||||
|     initial: T, | ||||
|     scope: CoroutineScope | ||||
|     scope: CoroutineScope, | ||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||
|     onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
| ): MutableState<T> { | ||||
|     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 | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 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") | ||||
| inline fun <T> StateFlow<T>.asMutableComposeState( | ||||
|     scope: CoroutineScope | ||||
| ): MutableState<T> = asMutableComposeState(value, scope) | ||||
|     scope: CoroutineScope, | ||||
|     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( | ||||
|     initial: T, | ||||
|     scope: CoroutineScope | ||||
|     scope: CoroutineScope, | ||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||
|     onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
| ): State<T> { | ||||
|     val state = asMutableComposeState(initial, scope) | ||||
|     return derivedStateOf { state.value } | ||||
|     val state = asMutableComposeState(initial, scope, useContextOnChange, onException) | ||||
|     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") | ||||
| inline fun <T> StateFlow<T>.asComposeState( | ||||
|     scope: CoroutineScope | ||||
| ): State<T> = asComposeState(value, scope) | ||||
|     scope: CoroutineScope, | ||||
|     useContextOnChange: CoroutineContext? = Dispatchers.Main, | ||||
|     noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull, | ||||
| ): State<T> = asComposeState(value, scope, useContextOnChange, onException) | ||||
|  | ||||
|   | ||||
| @@ -7,17 +7,15 @@ import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.StateFlow | ||||
|  | ||||
| @Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(initial, scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState")) | ||||
| fun <T> Flow<T>.toMutableState( | ||||
|     initial: T, | ||||
|     scope: CoroutineScope | ||||
| ): MutableState<T> { | ||||
|     val state = mutableStateOf(initial) | ||||
|     subscribeSafelyWithoutExceptions(scope) { state.value = it } | ||||
|     return state | ||||
| } | ||||
| ): MutableState<T> = asMutableComposeState(initial, scope) | ||||
|  | ||||
| @Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState")) | ||||
| @Suppress("NOTHING_TO_INLINE") | ||||
| inline fun <T> StateFlow<T>.toMutableState( | ||||
|     scope: CoroutineScope | ||||
| ): MutableState<T> = toMutableState(value, scope) | ||||
| ): MutableState<T> = asMutableComposeState(scope) | ||||
|  | ||||
|   | ||||
| @@ -1,19 +1,15 @@ | ||||
| package dev.inmo.micro_utils.coroutines | ||||
|  | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.channels.Channel | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.flow.consumeAsFlow | ||||
|  | ||||
| fun <T> CoroutineScope.actor( | ||||
|     channelCapacity: Int = Channel.UNLIMITED, | ||||
|     block: suspend (T) -> Unit | ||||
| ): Channel<T> { | ||||
|     val channel = Channel<T>(channelCapacity) | ||||
|     launch { | ||||
|         for (data in channel) { | ||||
|             block(data) | ||||
|         } | ||||
|     } | ||||
|     channel.consumeAsFlow().subscribe(this, block) | ||||
|     return channel | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,30 @@ | ||||
| package dev.inmo.micro_utils.coroutines | ||||
|  | ||||
| import kotlinx.coroutines.* | ||||
| import kotlinx.coroutines.channels.Channel | ||||
| import kotlinx.coroutines.flow.consumeAsFlow | ||||
|  | ||||
| fun <T> CoroutineScope.actorAsync( | ||||
|     channelCapacity: Int = Channel.UNLIMITED, | ||||
|     markerFactory: suspend (T) -> Any? = { null }, | ||||
|     block: suspend (T) -> Unit | ||||
| ): Channel<T> { | ||||
|     val channel = Channel<T>(channelCapacity) | ||||
|     channel.consumeAsFlow().subscribeAsync(this, markerFactory, block) | ||||
|     return channel | ||||
| } | ||||
|  | ||||
| inline fun <T> CoroutineScope.safeActorAsync( | ||||
|     channelCapacity: Int = Channel.UNLIMITED, | ||||
|     noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler, | ||||
|     noinline markerFactory: suspend (T) -> Any? = { null }, | ||||
|     crossinline block: suspend (T) -> Unit | ||||
| ): Channel<T> = actorAsync( | ||||
|     channelCapacity, | ||||
|     markerFactory | ||||
| ) { | ||||
|     safely(onException) { | ||||
|         block(it) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -6,23 +6,19 @@ import kotlin.coroutines.* | ||||
| suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred( | ||||
|     scope: CoroutineScope, | ||||
|     cancelOnResult: Boolean = true | ||||
| ): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation -> | ||||
|     scope.launch(SupervisorJob()) { | ||||
|         val scope = this | ||||
|         forEach { | ||||
|             scope.launch { | ||||
|                 continuation.resume(it to it.await()) | ||||
|                 scope.cancel() | ||||
|             } | ||||
| ): Pair<Deferred<T>, T> { | ||||
|     val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>() | ||||
|     val scope = scope.LinkedSupervisorScope() | ||||
|     forEach { | ||||
|         scope.launch { | ||||
|             resultDeferred.complete(it to it.await()) | ||||
|             scope.cancel() | ||||
|         } | ||||
|     } | ||||
| }.also { | ||||
|     if (cancelOnResult) { | ||||
|         forEach { | ||||
|             try { | ||||
|                 it.cancel() | ||||
|             } catch (e: IllegalStateException) { | ||||
|                 e.printStackTrace() | ||||
|     return resultDeferred.await().also { | ||||
|         if (cancelOnResult) { | ||||
|             forEach { | ||||
|                 runCatchingSafely { it.cancel() } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -0,0 +1,39 @@ | ||||
| package dev.inmo.micro_utils.coroutines | ||||
|  | ||||
| import kotlinx.coroutines.flow.* | ||||
| import kotlin.js.JsName | ||||
| import kotlin.jvm.JvmName | ||||
|  | ||||
| inline fun <T, R> Flow<Flow<T>>.flatMap( | ||||
|     crossinline mapper: suspend (T) -> R | ||||
| ) = flow { | ||||
|     collect { | ||||
|         it.collect { | ||||
|             emit(mapper(it)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @JsName("flatMapIterable") | ||||
| @JvmName("flatMapIterable") | ||||
| inline fun <T, R> Flow<Iterable<T>>.flatMap( | ||||
|     crossinline mapper: suspend (T) -> R | ||||
| ) = map { | ||||
|     it.asFlow() | ||||
| }.flatMap(mapper) | ||||
|  | ||||
| inline fun <T, R> Flow<Flow<T>>.flatMapNotNull( | ||||
|     crossinline mapper: suspend (T) -> R | ||||
| ) = flatMap(mapper).takeNotNull() | ||||
|  | ||||
| @JsName("flatMapNotNullIterable") | ||||
| @JvmName("flatMapNotNullIterable") | ||||
| inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull( | ||||
|     crossinline mapper: suspend (T) -> R | ||||
| ) = flatMap(mapper).takeNotNull() | ||||
|  | ||||
| fun <T> Flow<Flow<T>>.flatten() = flatMap { it } | ||||
|  | ||||
| @JsName("flattenIterable") | ||||
| @JvmName("flattenIterable") | ||||
| fun <T> Flow<Iterable<T>>.flatten() = flatMap { it } | ||||
| @@ -0,0 +1,6 @@ | ||||
| package dev.inmo.micro_utils.coroutines | ||||
|  | ||||
| import kotlinx.coroutines.flow.* | ||||
|  | ||||
| fun <T> Flow<T>.takeNotNull() = mapNotNull { it } | ||||
| fun <T> Flow<T>.filterNotNull() = takeNotNull() | ||||
| @@ -115,10 +115,21 @@ suspend inline fun <T> runCatchingSafely( | ||||
|     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( | ||||
|     noinline block: suspend CoroutineScope.() -> T | ||||
| ): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block) | ||||
|  | ||||
| suspend inline fun <T, R> T.safelyWithResult( | ||||
|     noinline block: suspend T.() -> R | ||||
| ): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block) | ||||
|  | ||||
| /** | ||||
|  * Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and | ||||
|  * returning null at one time | ||||
|   | ||||
| @@ -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 | ||||
|     val objectToSynchronize = Object() | ||||
|     synchronized(objectToSynchronize) { | ||||
|         launch { | ||||
|         launch(start = CoroutineStart.UNDISPATCHED) { | ||||
|             result = safelyWithResult(block) | ||||
|         }.invokeOnCompletion { | ||||
|             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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										50
									
								
								coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								coroutines/src/main/kotlin/FlowOnHierarchyChangeListener.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| package dev.inmo.micro_utils.coroutines | ||||
|  | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import kotlinx.coroutines.flow.MutableSharedFlow | ||||
| import kotlinx.coroutines.flow.asSharedFlow | ||||
|  | ||||
| /** | ||||
|  * [kotlinx.coroutines.flow.Flow]-based [android.view.ViewGroup.OnHierarchyChangeListener] | ||||
|  * | ||||
|  * @param recursive If set, any call of [onChildViewAdded] will check if child [View] is [ViewGroup] and subscribe to this | ||||
|  * [ViewGroup] too | ||||
|  * @param [_onChildViewAdded] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewAdded] flow | ||||
|  * @param [_onChildViewRemoved] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewRemoved] flow | ||||
|  */ | ||||
| class FlowOnHierarchyChangeListener( | ||||
|     private val recursive: Boolean = false, | ||||
|     private val _onChildViewAdded: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE), | ||||
|     private val _onChildViewRemoved: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE) | ||||
| ) : ViewGroup.OnHierarchyChangeListener { | ||||
|     val onChildViewAdded = _onChildViewAdded.asSharedFlow() | ||||
|     val onChildViewRemoved = _onChildViewRemoved.asSharedFlow() | ||||
|  | ||||
|     /** | ||||
|      * Will emit data into [onChildViewAdded] flow. If [recursive] is true and [child] is [ViewGroup] will also | ||||
|      * subscribe to [child] hierarchy changes. | ||||
|      * | ||||
|      * Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use | ||||
|      * [MutableSharedFlow.tryEmit] to send data into [_onChildViewAdded]. That is why its default extraBufferCapacity is | ||||
|      * [Int.MAX_VALUE] | ||||
|      */ | ||||
|     override fun onChildViewAdded(parent: View, child: View) { | ||||
|         _onChildViewAdded.tryEmit(parent to child) | ||||
|  | ||||
|         if (recursive && child is ViewGroup) { | ||||
|             child.setOnHierarchyChangeListener(this) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Just emit data into [onChildViewRemoved] | ||||
|      * | ||||
|      * Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use | ||||
|      * [MutableSharedFlow.tryEmit] to send data into [_onChildViewRemoved]. That is why its default extraBufferCapacity is | ||||
|      * [Int.MAX_VALUE] | ||||
|      */ | ||||
|     override fun onChildViewRemoved(parent: View, child: View) { | ||||
|         _onChildViewRemoved.tryEmit(parent to child) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								coroutines/src/main/kotlin/RecursiveHierarchySubscriber.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| package dev.inmo.micro_utils.coroutines | ||||
|  | ||||
| import android.view.ViewGroup | ||||
| import android.view.ViewGroup.OnHierarchyChangeListener | ||||
|  | ||||
| /** | ||||
|  * Use [ViewGroup.setOnHierarchyChangeListener] recursively for all available [ViewGroup]s starting with [this]. | ||||
|  * This extension DO NOT guarantee that recursive subscription will happen after this method call | ||||
|  */ | ||||
| fun ViewGroup.setOnHierarchyChangeListenerRecursively( | ||||
|     listener: OnHierarchyChangeListener | ||||
| ) { | ||||
|     setOnHierarchyChangeListener(listener) | ||||
|     (0 until childCount).forEach { | ||||
|         (getChildAt(it) as? ViewGroup) ?.setOnHierarchyChangeListenerRecursively(listener) | ||||
|     } | ||||
| } | ||||
| @@ -1,30 +1,5 @@ | ||||
| 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 { | ||||
|     compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger() | ||||
|     buildToolsVersion libs.versions.android.props.buildTools.get() | ||||
| @@ -58,10 +33,4 @@ android { | ||||
|     kotlinOptions { | ||||
|         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") { | ||||
|             sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain")) | ||||
|             sourceRoots.setFrom(findSourcesWithName("jvmMain")) | ||||
|         } | ||||
|  | ||||
|         named("androidMain") { | ||||
|             sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain")) | ||||
|             sourceRoots.setFrom(findSourcesWithName("androidMain")) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -23,11 +23,12 @@ allprojects { | ||||
|         mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" | ||||
|         mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle" | ||||
|         mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" | ||||
|         mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle" | ||||
|         mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" | ||||
|  | ||||
|         defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" | ||||
|  | ||||
|         publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle" | ||||
|         publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle" | ||||
|         publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,5 +14,5 @@ crypto_js_version=4.1.1 | ||||
| # Project data | ||||
|  | ||||
| group=dev.inmo | ||||
| version=0.12.12 | ||||
| android_code_version=151 | ||||
| version=0.17.0 | ||||
| android_code_version=182 | ||||
|   | ||||
| @@ -1,36 +1,45 @@ | ||||
| [versions] | ||||
|  | ||||
| kt = "1.7.10" | ||||
| kt-serialization = "1.4.0" | ||||
| kt = "1.8.10" | ||||
| kt-serialization = "1.5.0" | ||||
| kt-coroutines = "1.6.4" | ||||
|  | ||||
| jb-compose = "1.2.0-alpha01-dev774" | ||||
| jb-exposed = "0.39.2" | ||||
| jb-dokka = "1.7.10" | ||||
| kslog = "1.0.0" | ||||
|  | ||||
| klock = "3.1.0" | ||||
| uuid = "0.5.0" | ||||
| jb-compose = "1.3.1-rc01" | ||||
| jb-exposed = "0.41.1" | ||||
| jb-dokka = "1.7.20" | ||||
|  | ||||
| ktor = "2.1.1" | ||||
| klock = "3.4.0" | ||||
| uuid = "0.7.0" | ||||
|  | ||||
| ktor = "2.2.3" | ||||
|  | ||||
| gh-release = "2.4.1" | ||||
|  | ||||
| android-gradle = "7.2.2" | ||||
| dexcount = "3.1.0" | ||||
| koin = "3.3.2" | ||||
|  | ||||
| android-coreKtx = "1.8.0" | ||||
| ksp = "1.8.10-1.0.9" | ||||
| kotlin-poet = "1.12.0" | ||||
|  | ||||
| android-gradle = "7.3.1" | ||||
| dexcount = "4.0.0" | ||||
|  | ||||
| android-coreKtx = "1.9.0" | ||||
| android-recyclerView = "1.2.1" | ||||
| android-appCompat = "1.4.2" | ||||
| android-espresso = "3.4.0" | ||||
| android-test = "1.1.3" | ||||
| android-appCompat = "1.6.1" | ||||
| android-fragment = "1.5.5" | ||||
| android-espresso = "3.5.1" | ||||
| android-test = "1.1.5" | ||||
|  | ||||
| android-props-minSdk = "21" | ||||
| android-props-compileSdk = "32" | ||||
| android-props-buildTools = "32.0.0" | ||||
| android-props-compileSdk = "33" | ||||
| android-props-buildTools = "33.0.1" | ||||
|  | ||||
| [libraries] | ||||
|  | ||||
| kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" } | ||||
| kt-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kt" } | ||||
|  | ||||
| kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" } | ||||
| kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" } | ||||
| @@ -56,9 +65,11 @@ ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.re | ||||
| ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" } | ||||
| ktor-server-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" } | ||||
| uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | ||||
| koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | ||||
|  | ||||
|  | ||||
| jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" } | ||||
| @@ -67,6 +78,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb- | ||||
| android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" } | ||||
| android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" } | ||||
| android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" } | ||||
| android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" } | ||||
| android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" } | ||||
| android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" } | ||||
|  | ||||
| @@ -74,9 +86,16 @@ 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-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" } | ||||
|  | ||||
| # Buildscript | ||||
|  | ||||
| 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-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-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" } | ||||
|   | ||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| 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"} | ||||
							
								
								
									
										28
									
								
								koin/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								koin/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| plugins { | ||||
|     id "org.jetbrains.kotlin.multiplatform" | ||||
|     id "org.jetbrains.kotlin.plugin.serialization" | ||||
|     id "com.android.library" | ||||
| } | ||||
|  | ||||
| apply from: "$mppProjectWithSerializationPresetPath" | ||||
|  | ||||
| kotlin { | ||||
|     sourceSets { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 api libs.koin | ||||
|                 api libs.uuid | ||||
|             } | ||||
|         } | ||||
|         jvmMain { | ||||
|             dependencies { | ||||
|                 api libs.kt.reflect | ||||
|             } | ||||
|         } | ||||
|         androidMain { | ||||
|             dependencies { | ||||
|                 api libs.kt.reflect | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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"/> | ||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.definition.Definition | ||||
| import org.koin.core.module.Module | ||||
|  | ||||
| /** | ||||
|  * Will be useful in case you need to declare some singles with one type several types, but need to separate them and do | ||||
|  * not care about how :) | ||||
|  */ | ||||
| inline fun <reified T : Any> Module.factoryWithRandomQualifier( | ||||
|     noinline definition: Definition<T> | ||||
| ) = factory(RandomQualifier(), definition) | ||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/FactoryWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.definition.Definition | ||||
| import org.koin.core.module.Module | ||||
| import org.koin.core.qualifier.StringQualifier | ||||
|  | ||||
| inline fun <reified T : Any> Module.factory( | ||||
|     qualifier: String, | ||||
|     noinline definition: Definition<T> | ||||
| ) = factory(StringQualifier(qualifier), definition) | ||||
|  | ||||
|  | ||||
							
								
								
									
										8
									
								
								koin/src/commonMain/kotlin/GetAllDistinct.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								koin/src/commonMain/kotlin/GetAllDistinct.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.Koin | ||||
| import org.koin.core.scope.Scope | ||||
|  | ||||
| inline fun <reified T : Any> Scope.getAllDistinct() = getAll<T>().distinct() | ||||
| inline fun <reified T : Any> Koin.getAllDistinct() = getAll<T>().distinct() | ||||
|  | ||||
							
								
								
									
										8
									
								
								koin/src/commonMain/kotlin/GetAny.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								koin/src/commonMain/kotlin/GetAny.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.Koin | ||||
| import org.koin.core.scope.Scope | ||||
|  | ||||
| inline fun <reified T : Any> Scope.getAny() = getAll<T>().first() | ||||
| inline fun <reified T : Any> Koin.getAny() = getAll<T>().first() | ||||
|  | ||||
							
								
								
									
										6
									
								
								koin/src/commonMain/kotlin/RandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								koin/src/commonMain/kotlin/RandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import com.benasher44.uuid.uuid4 | ||||
| import org.koin.core.qualifier.StringQualifier | ||||
|  | ||||
| fun RandomQualifier(randomFun: () -> String = { uuid4().toString() }) = StringQualifier(randomFun()) | ||||
							
								
								
									
										13
									
								
								koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								koin/src/commonMain/kotlin/SingleWithRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.definition.Definition | ||||
| import org.koin.core.module.Module | ||||
|  | ||||
| /** | ||||
|  * Will be useful in case you need to declare some singles with one type several types, but need to separate them and do | ||||
|  * not care about how :) | ||||
|  */ | ||||
| inline fun <reified T : Any> Module.singleWithRandomQualifier( | ||||
|     createdAtStart: Boolean = false, | ||||
|     noinline definition: Definition<T> | ||||
| ) = single(RandomQualifier(), createdAtStart, definition) | ||||
							
								
								
									
										12
									
								
								koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								koin/src/commonMain/kotlin/SingleWithStringQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.definition.Definition | ||||
| import org.koin.core.module.Module | ||||
| import org.koin.core.qualifier.StringQualifier | ||||
|  | ||||
| inline fun <reified T : Any> Module.single( | ||||
|     qualifier: String, | ||||
|     createdAtStart: Boolean = false, | ||||
|     noinline definition: Definition<T> | ||||
| ) = single(StringQualifier(qualifier), createdAtStart, definition) | ||||
|  | ||||
| @@ -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 | ||||
| ) | ||||
							
								
								
									
										28
									
								
								koin/src/jvmMain/kotlin/FactoryWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								koin/src/jvmMain/kotlin/FactoryWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.definition.Definition | ||||
| import org.koin.core.definition.KoinDefinition | ||||
| import org.koin.core.instance.InstanceFactory | ||||
| import org.koin.core.module.Module | ||||
| import org.koin.core.qualifier.Qualifier | ||||
| import org.koin.dsl.binds | ||||
| import kotlin.reflect.KClass | ||||
| import kotlin.reflect.full.allSuperclasses | ||||
|  | ||||
|  | ||||
| inline fun <reified T : Any> Module.factoryWithBinds( | ||||
|     qualifier: Qualifier? = null, | ||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||
|     noinline definition: Definition<T> | ||||
| ): KoinDefinition<*> { | ||||
|     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||
| } | ||||
|  | ||||
| inline fun <reified T : Any> Module.factoryWithBinds( | ||||
|     qualifier: String, | ||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||
|     noinline definition: Definition<T> | ||||
| ): KoinDefinition<*> { | ||||
|     return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,14 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.definition.Definition | ||||
| import org.koin.core.definition.KoinDefinition | ||||
| import org.koin.core.instance.InstanceFactory | ||||
| import org.koin.core.module.Module | ||||
| import kotlin.reflect.KClass | ||||
|  | ||||
| inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds( | ||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||
|     noinline definition: Definition<T> | ||||
| ): KoinDefinition<*> { | ||||
|     return factoryWithBinds(RandomQualifier(), bindFilter, definition) | ||||
| } | ||||
							
								
								
									
										31
									
								
								koin/src/jvmMain/kotlin/SignleWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								koin/src/jvmMain/kotlin/SignleWithBinds.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.definition.Definition | ||||
| import org.koin.core.definition.KoinDefinition | ||||
| import org.koin.core.instance.InstanceFactory | ||||
| import org.koin.core.module.Module | ||||
| import org.koin.core.qualifier.Qualifier | ||||
| import org.koin.dsl.binds | ||||
| import kotlin.reflect.KClass | ||||
| import kotlin.reflect.full.allSuperclasses | ||||
|  | ||||
|  | ||||
| inline fun <reified T : Any> Module.singleWithBinds( | ||||
|     qualifier: Qualifier? = null, | ||||
|     createdAtStart: Boolean = false, | ||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||
|     noinline definition: Definition<T> | ||||
| ): KoinDefinition<*> { | ||||
|     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||
| } | ||||
|  | ||||
|  | ||||
| inline fun <reified T : Any> Module.singleWithBinds( | ||||
|     qualifier: String, | ||||
|     createdAtStart: Boolean = false, | ||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||
|     noinline definition: Definition<T> | ||||
| ): KoinDefinition<*> { | ||||
|     return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray()) | ||||
| } | ||||
|  | ||||
							
								
								
									
										15
									
								
								koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								koin/src/jvmMain/kotlin/SingleWithBindsAndRandomQualifier.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| package dev.inmo.micro_utils.koin | ||||
|  | ||||
| import org.koin.core.definition.Definition | ||||
| import org.koin.core.definition.KoinDefinition | ||||
| import org.koin.core.instance.InstanceFactory | ||||
| import org.koin.core.module.Module | ||||
| import kotlin.reflect.KClass | ||||
|  | ||||
| inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds( | ||||
|     createdAtStart: Boolean = false, | ||||
|     bindFilter: (KClass<*>) -> Boolean = { true }, | ||||
|     noinline definition: Definition<T> | ||||
| ): KoinDefinition<*> { | ||||
|     return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition) | ||||
| } | ||||
							
								
								
									
										1
									
								
								koin/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								koin/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <manifest package="dev.inmo.micro_utils.koin"/> | ||||
| @@ -15,5 +15,8 @@ kotlin { | ||||
|                 api libs.ktor.client | ||||
|             } | ||||
|         } | ||||
|         androidMain { | ||||
|             dependsOn jvmMain | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,10 @@ import io.ktor.client.call.body | ||||
| import io.ktor.client.statement.HttpResponse | ||||
| import io.ktor.http.HttpStatusCode | ||||
|  | ||||
| suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf { | ||||
|     status == HttpStatusCode.OK | ||||
| } ?.body<T>() | ||||
| suspend inline fun <reified T : Any> HttpResponse.bodyOrNull( | ||||
|     statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK } | ||||
| ) = 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) | ||||
| } | ||||
| @@ -19,7 +19,7 @@ import kotlinx.coroutines.isActive | ||||
|  * 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") | ||||
| inline fun <reified T : Any> openBaseWebSocketFlow( | ||||
| inline fun <T : Any> openBaseWebSocketFlow( | ||||
|     noinline checkReconnection: suspend (Throwable?) -> Boolean = { true }, | ||||
|     noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit | ||||
| ): Flow<T> { | ||||
| @@ -57,7 +57,7 @@ inline fun <reified T : Any> HttpClient.openWebSocketFlow( | ||||
| ): Flow<T> { | ||||
|     pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow") | ||||
|  | ||||
|     return openBaseWebSocketFlow<T>(checkReconnection) { | ||||
|     return openBaseWebSocketFlow(checkReconnection) { | ||||
|         val block: suspend DefaultClientWebSocketSession.() -> Unit = { | ||||
|             while (isActive) { | ||||
|                 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( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> } | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
| ): TemporalFileId | ||||
|  | ||||
| suspend fun UnifiedRequester.tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> } | ||||
| ): TemporalFileId = client.tempUpload( | ||||
|     fullTempUploadDraftPath, file, onUpload | ||||
| ) | ||||
|   | ||||
| @@ -0,0 +1,105 @@ | ||||
| package dev.inmo.micro_utils.ktor.client | ||||
|  | ||||
| import dev.inmo.micro_utils.common.FileName | ||||
| import dev.inmo.micro_utils.common.MPPFile | ||||
| import dev.inmo.micro_utils.ktor.common.LambdaInputProvider | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.http.Headers | ||||
| import io.ktor.utils.io.core.Input | ||||
| import kotlinx.serialization.DeserializationStrategy | ||||
| import kotlinx.serialization.StringFormat | ||||
| import kotlinx.serialization.json.Json | ||||
|  | ||||
| data class UniUploadFileInfo( | ||||
|     val fileName: FileName, | ||||
|     val mimeType: String, | ||||
|     val inputAllocator: LambdaInputProvider | ||||
| ) | ||||
|  | ||||
| /** | ||||
|  * Will execute submitting of multipart data request | ||||
|  * | ||||
|  * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass | ||||
|  * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value | ||||
|  * in case you wish to pass other source of multipart binary data than regular file | ||||
|  * | ||||
|  * @see dev.inmo.micro_utils.ktor.server.handleUniUpload | ||||
|  */ | ||||
| expect suspend fun <T> HttpClient.uniUpload( | ||||
|     url: String, | ||||
|     data: Map<String, Any>, | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     headers: Headers = Headers.Empty, | ||||
|     stringFormat: StringFormat = Json, | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
| ): T? | ||||
|  | ||||
| /** | ||||
|  * Additional variant of [uniUpload] which will unify sending of some [MPPFile] with the server | ||||
|  * | ||||
|  * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile | ||||
|  */ | ||||
| suspend fun <T> HttpClient.uniUpload( | ||||
|     url: String, | ||||
|     file: MPPFile, | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     additionalData: Map<String, Any> = emptyMap(), | ||||
|     headers: Headers = Headers.Empty, | ||||
|     stringFormat: StringFormat = Json, | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
| ): T? = uniUpload( | ||||
|     url, | ||||
|     additionalData + ("bytes" to file), | ||||
|     resultDeserializer, | ||||
|     headers, | ||||
|     stringFormat, | ||||
|     onUpload | ||||
| ) | ||||
|  | ||||
| /** | ||||
|  * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] with the server | ||||
|  * | ||||
|  * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile | ||||
|  */ | ||||
| suspend fun <T> HttpClient.uniUpload( | ||||
|     url: String, | ||||
|     info: UniUploadFileInfo, | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     additionalData: Map<String, Any> = emptyMap(), | ||||
|     headers: Headers = Headers.Empty, | ||||
|     stringFormat: StringFormat = Json, | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
| ): T? = uniUpload( | ||||
|     url, | ||||
|     additionalData + ("bytes" to info), | ||||
|     resultDeserializer, | ||||
|     headers, | ||||
|     stringFormat, | ||||
|     onUpload | ||||
| ) | ||||
|  | ||||
| /** | ||||
|  * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] (built from [fileName], | ||||
|  * [mimeType] and [inputAllocator]) with the server | ||||
|  * | ||||
|  * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile | ||||
|  */ | ||||
| suspend fun <T> HttpClient.uniUpload( | ||||
|     url: String, | ||||
|     fileName: FileName, | ||||
|     mimeType: String, | ||||
|     inputAllocator: LambdaInputProvider, | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     additionalData: Map<String, Any> = emptyMap(), | ||||
|     headers: Headers = Headers.Empty, | ||||
|     stringFormat: StringFormat = Json, | ||||
|     onUpload: OnUploadCallback = { _, _ -> } | ||||
| ): T? = uniUpload( | ||||
|     url, | ||||
|     UniUploadFileInfo(fileName, mimeType, inputAllocator), | ||||
|     resultDeserializer, | ||||
|     additionalData, | ||||
|     headers, | ||||
|     stringFormat, | ||||
|     onUpload | ||||
| ) | ||||
| @@ -1,6 +1,8 @@ | ||||
| package dev.inmo.micro_utils.ktor.client | ||||
|  | ||||
| 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 io.ktor.client.HttpClient | ||||
| import kotlinx.coroutines.* | ||||
| @@ -12,10 +14,11 @@ import org.w3c.xhr.XMLHttpRequest.Companion.DONE | ||||
| suspend fun tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: (Long, Long) -> Unit | ||||
|     onUpload: OnUploadCallback | ||||
| ): TemporalFileId { | ||||
|     val formData = FormData() | ||||
|     val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job) | ||||
|     val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob()) | ||||
|  | ||||
|     formData.append( | ||||
|         "data", | ||||
| @@ -25,7 +28,7 @@ suspend fun tempUpload( | ||||
|     val request = XMLHttpRequest() | ||||
|     request.responseType = XMLHttpRequestResponseType.TEXT | ||||
|     request.upload.onprogress = { | ||||
|         onUpload(it.loaded.toLong(), it.total.toLong()) | ||||
|         subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) } | ||||
|     } | ||||
|     request.onload = { | ||||
|         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( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: (uploaded: Long, count: Long) -> Unit | ||||
|     onUpload: OnUploadCallback | ||||
| ): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload) | ||||
|   | ||||
| @@ -0,0 +1,97 @@ | ||||
| package dev.inmo.micro_utils.ktor.client | ||||
|  | ||||
| import dev.inmo.micro_utils.common.MPPFile | ||||
| import dev.inmo.micro_utils.common.Progress | ||||
| import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob | ||||
| import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.http.Headers | ||||
| import io.ktor.utils.io.core.readBytes | ||||
| import kotlinx.coroutines.CompletableDeferred | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.cancel | ||||
| import kotlinx.coroutines.currentCoroutineContext | ||||
| import kotlinx.coroutines.job | ||||
| import kotlinx.serialization.DeserializationStrategy | ||||
| import kotlinx.serialization.StringFormat | ||||
| import kotlinx.serialization.encodeToString | ||||
| import org.khronos.webgl.Int8Array | ||||
| import org.w3c.files.Blob | ||||
| import org.w3c.xhr.FormData | ||||
| import org.w3c.xhr.TEXT | ||||
| import org.w3c.xhr.XMLHttpRequest | ||||
| import org.w3c.xhr.XMLHttpRequestResponseType | ||||
|  | ||||
| /** | ||||
|  * Will execute submitting of multipart data request | ||||
|  * | ||||
|  * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass | ||||
|  * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value | ||||
|  * in case you wish to pass other source of multipart binary data than regular file | ||||
|  * @suppress | ||||
|  */ | ||||
| actual suspend fun <T> HttpClient.uniUpload( | ||||
|     url: String, | ||||
|     data: Map<String, Any>, | ||||
|     resultDeserializer: DeserializationStrategy<T>, | ||||
|     headers: Headers, | ||||
|     stringFormat: StringFormat, | ||||
|     onUpload: OnUploadCallback | ||||
| ): T? { | ||||
|     val formData = FormData() | ||||
|     val answer = CompletableDeferred<T?>(currentCoroutineContext().job) | ||||
|     val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob()) | ||||
|  | ||||
|     data.forEach { (k, v) -> | ||||
|         when (v) { | ||||
|             is MPPFile -> formData.append( | ||||
|                 k, | ||||
|                 v | ||||
|             ) | ||||
|             is UniUploadFileInfo -> formData.append( | ||||
|                 k, | ||||
|                 Blob(arrayOf(Int8Array(v.inputAllocator().readBytes().toTypedArray()))), | ||||
|                 v.fileName.name | ||||
|             ) | ||||
|             else -> formData.append( | ||||
|                 k, | ||||
|                 stringFormat.encodeToString(v) | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     val request = XMLHttpRequest() | ||||
|     headers.forEach { s, strings -> | ||||
|         request.setRequestHeader(s, strings.joinToString()) | ||||
|     } | ||||
|     request.responseType = XMLHttpRequestResponseType.TEXT | ||||
|     request.upload.onprogress = { | ||||
|         subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) } | ||||
|     } | ||||
|     request.onload = { | ||||
|         if (request.status == 200.toShort()) { | ||||
|             answer.complete( | ||||
|                 stringFormat.decodeFromString(resultDeserializer, request.responseText) | ||||
|             ) | ||||
|         } else { | ||||
|             answer.completeExceptionally(Exception("Something went wrong: $it")) | ||||
|         } | ||||
|     } | ||||
|     request.onerror = { | ||||
|         answer.completeExceptionally(Exception("Something went wrong: $it")) | ||||
|     } | ||||
|     request.open("POST", url, true) | ||||
|     request.send(formData) | ||||
|  | ||||
|     answer.invokeOnCompletion { | ||||
|         runCatching { | ||||
|             if (request.readyState != XMLHttpRequest.DONE) { | ||||
|                 request.abort() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return answer.await().also { | ||||
|         subscope.cancel() | ||||
|     } | ||||
| } | ||||
| @@ -4,6 +4,8 @@ import dev.inmo.micro_utils.common.MPPFile | ||||
| import io.ktor.client.request.forms.InputProvider | ||||
| import io.ktor.utils.io.streams.asInput | ||||
|  | ||||
| actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) { | ||||
| fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) { | ||||
|     inputStream().asInput() | ||||
| } | ||||
|  | ||||
| actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync() | ||||
|   | ||||
| @@ -18,7 +18,7 @@ internal val MPPFile.mimeType: String | ||||
| actual suspend fun HttpClient.tempUpload( | ||||
|     fullTempUploadDraftPath: String, | ||||
|     file: MPPFile, | ||||
|     onUpload: (Long, Long) -> Unit | ||||
|     onUpload: OnUploadCallback | ||||
| ): TemporalFileId { | ||||
|     val inputProvider = file.inputProvider() | ||||
|     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 | ||||
|     } | ||||
| } | ||||
| @@ -17,5 +17,8 @@ kotlin { | ||||
|                 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() | ||||
| } | ||||
| @@ -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,25 @@ | ||||
| package dev.inmo.micro_utils.ktor.server | ||||
|  | ||||
| import io.ktor.http.HttpStatusCode | ||||
| import io.ktor.server.application.ApplicationCall | ||||
| import io.ktor.server.response.respond | ||||
|  | ||||
| suspend fun ApplicationCall.getParameterOrSendError( | ||||
|     field: String | ||||
| ) = parameters[field].also { | ||||
|     if (it == null) { | ||||
|         respond(HttpStatusCode.BadRequest, "Request must contains $field") | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun ApplicationCall.getQueryParameter( | ||||
|     field: String | ||||
| ) = request.queryParameters[field] | ||||
|  | ||||
| suspend fun ApplicationCall.getQueryParameterOrSendError( | ||||
|     field: String | ||||
| ) = getQueryParameter(field).also { | ||||
|     if (it == null) { | ||||
|         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") | ||||
|     } | ||||
| } | ||||
| @@ -1,308 +0,0 @@ | ||||
| package dev.inmo.micro_utils.ktor.server | ||||
|  | ||||
| import dev.inmo.micro_utils.common.* | ||||
| import dev.inmo.micro_utils.coroutines.safely | ||||
| import dev.inmo.micro_utils.ktor.common.* | ||||
| import io.ktor.http.* | ||||
| import io.ktor.http.content.* | ||||
| import io.ktor.server.application.ApplicationCall | ||||
| import io.ktor.server.application.call | ||||
| import io.ktor.server.request.receive | ||||
| import io.ktor.server.request.receiveMultipart | ||||
| import io.ktor.server.response.respond | ||||
| import io.ktor.server.response.respondBytes | ||||
| import io.ktor.server.routing.Route | ||||
| import io.ktor.server.websocket.WebSocketServerSession | ||||
| import io.ktor.util.pipeline.PipelineContext | ||||
| import io.ktor.utils.io.core.* | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.serialization.DeserializationStrategy | ||||
| import kotlinx.serialization.SerializationStrategy | ||||
|  | ||||
| @Deprecated("This class method will be removed soon. It is now recommended to use built-in ktor features instead") | ||||
| class UnifiedRouter( | ||||
|     val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat, | ||||
|     val serialFormatContentType: ContentType = standardKtorSerialFormatContentType | ||||
| ) { | ||||
|     fun <T> Route.includeWebsocketHandling( | ||||
|         suburl: String, | ||||
|         flow: Flow<T>, | ||||
|         serializer: SerializationStrategy<T>, | ||||
|         protocol: URLProtocol? = null, | ||||
|         filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null | ||||
|     ) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter) | ||||
|  | ||||
|     suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer( | ||||
|         answerSerializer: SerializationStrategy<T>, | ||||
|         answer: T | ||||
|     ) { | ||||
|         call.respondBytes ( | ||||
|             serialFormat.encodeDefault(answerSerializer, answer), | ||||
|             serialFormatContentType | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     suspend fun <T> PipelineContext<*, ApplicationCall>.uniload( | ||||
|         deserializer: DeserializationStrategy<T> | ||||
|     ) = safely { | ||||
|         serialFormat.decodeDefault( | ||||
|             deserializer, | ||||
|             call.receive() | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError( | ||||
|         field: String | ||||
|     ) = call.parameters[field].also { | ||||
|         if (it == null) { | ||||
|             call.respond(HttpStatusCode.BadRequest, "Request must contains $field") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun PipelineContext<*, ApplicationCall>.getQueryParameter( | ||||
|         field: String | ||||
|     ) = call.request.queryParameters[field] | ||||
|  | ||||
|     suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError( | ||||
|         field: String | ||||
|     ) = getQueryParameter(field).also { | ||||
|         if (it == null) { | ||||
|             call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue( | ||||
|         field: String, | ||||
|         deserializer: DeserializationStrategy<T> | ||||
|     ) = getQueryParameter(field) ?.let { | ||||
|         serialFormat.decodeHex( | ||||
|             deserializer, | ||||
|             it | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError( | ||||
|         field: String, | ||||
|         deserializer: DeserializationStrategy<T> | ||||
|     ) = decodeUrlQueryValue(field, deserializer).also { | ||||
|         if (it == null) { | ||||
|             call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         val default | ||||
|             get() = defaultUnifiedRouter | ||||
|     } | ||||
| } | ||||
|  | ||||
| val defaultUnifiedRouter = UnifiedRouter() | ||||
|  | ||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") | ||||
| suspend fun <T> ApplicationCall.unianswer( | ||||
|     answerSerializer: SerializationStrategy<T>, | ||||
|     answer: T | ||||
| ) { | ||||
|     respondBytes ( | ||||
|         standardKtorSerialFormat.encodeDefault(answerSerializer, answer), | ||||
|         standardKtorSerialFormatContentType | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") | ||||
| suspend fun <T> ApplicationCall.uniload( | ||||
|     deserializer: DeserializationStrategy<T> | ||||
| ) = safely { | ||||
|     standardKtorSerialFormat.decodeDefault( | ||||
|         deserializer, | ||||
|         receive() | ||||
|     ) | ||||
| } | ||||
|  | ||||
| suspend fun ApplicationCall.uniloadMultipart( | ||||
|     onFormItem: (PartData.FormItem) -> Unit = {}, | ||||
|     onCustomFileItem: (PartData.FileItem) -> Unit = {}, | ||||
|     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, | ||||
|     onBinaryContent: (PartData.BinaryItem) -> Unit = {} | ||||
| ) = safely { | ||||
|     val multipartData = receiveMultipart() | ||||
|  | ||||
|     var resultInput: Input? = null | ||||
|  | ||||
|     multipartData.forEachPart { | ||||
|         when (it) { | ||||
|             is PartData.FormItem -> onFormItem(it) | ||||
|             is PartData.FileItem -> { | ||||
|                 when (it.name) { | ||||
|                     "bytes" -> resultInput = it.provider() | ||||
|                     else -> onCustomFileItem(it) | ||||
|                 } | ||||
|             } | ||||
|             is PartData.BinaryItem -> onBinaryContent(it) | ||||
|             is PartData.BinaryChannelItem -> onBinaryChannelItem(it) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     resultInput ?: error("Bytes has not been received") | ||||
| } | ||||
|  | ||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") | ||||
| suspend fun <T> ApplicationCall.uniloadMultipart( | ||||
|     deserializer: DeserializationStrategy<T>, | ||||
|     onFormItem: (PartData.FormItem) -> Unit = {}, | ||||
|     onCustomFileItem: (PartData.FileItem) -> Unit = {}, | ||||
|     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, | ||||
|     onBinaryContent: (PartData.BinaryItem) -> Unit = {} | ||||
| ): Pair<Input, T> { | ||||
|     var data: Optional<T>? = null | ||||
|     val resultInput = uniloadMultipart( | ||||
|         onFormItem, | ||||
|         { | ||||
|             if (it.name == "data") { | ||||
|                 data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional | ||||
|             } else { | ||||
|                 onCustomFileItem(it) | ||||
|             } | ||||
|         }, | ||||
|         onBinaryChannelItem, | ||||
|         onBinaryContent | ||||
|     ) | ||||
|  | ||||
|     val completeData = data ?: error("Data has not been received") | ||||
|     return resultInput to (completeData.dataOrNull().let { it as T }) | ||||
| } | ||||
|  | ||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") | ||||
| suspend fun <T> ApplicationCall.uniloadMultipartFile( | ||||
|     deserializer: DeserializationStrategy<T>, | ||||
|     onFormItem: (PartData.FormItem) -> Unit = {}, | ||||
|     onCustomFileItem: (PartData.FileItem) -> Unit = {}, | ||||
|     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, | ||||
|     onBinaryContent: (PartData.BinaryItem) -> Unit = {}, | ||||
| ) = safely { | ||||
|     val multipartData = receiveMultipart() | ||||
|  | ||||
|     var resultInput: MPPFile? = null | ||||
|     var data: Optional<T>? = null | ||||
|  | ||||
|     multipartData.forEachPart { | ||||
|         when (it) { | ||||
|             is PartData.FormItem -> onFormItem(it) | ||||
|             is PartData.FileItem -> { | ||||
|                 when (it.name) { | ||||
|                     "bytes" -> { | ||||
|                         val name = FileName(it.originalFileName ?: error("File name is unknown for default part")) | ||||
|                         resultInput = MPPFile.createTempFile( | ||||
|                             name.nameWithoutExtension.let { | ||||
|                                 var resultName = it | ||||
|                                 while (resultName.length < 3) { | ||||
|                                     resultName += "_" | ||||
|                                 } | ||||
|                                 resultName | ||||
|                             }, | ||||
|                             ".${name.extension}" | ||||
|                         ).apply { | ||||
|                             outputStream().use { fileStream -> | ||||
|                                 it.streamProvider().use { | ||||
|                                     it.copyTo(fileStream) | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     "data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional | ||||
|                     else -> onCustomFileItem(it) | ||||
|                 } | ||||
|             } | ||||
|             is PartData.BinaryItem -> onBinaryContent(it) | ||||
|             is PartData.BinaryChannelItem -> onBinaryChannelItem(it) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     val completeData = data ?: error("Data has not been received") | ||||
|     (resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T }) | ||||
| } | ||||
|  | ||||
| suspend fun ApplicationCall.uniloadMultipartFile( | ||||
|     onFormItem: (PartData.FormItem) -> Unit = {}, | ||||
|     onCustomFileItem: (PartData.FileItem) -> Unit = {}, | ||||
|     onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, | ||||
|     onBinaryContent: (PartData.BinaryItem) -> Unit = {}, | ||||
| ) = safely { | ||||
|     val multipartData = receiveMultipart() | ||||
|  | ||||
|     var resultInput: MPPFile? = null | ||||
|  | ||||
|     multipartData.forEachPart { | ||||
|         when (it) { | ||||
|             is PartData.FormItem -> onFormItem(it) | ||||
|             is PartData.FileItem -> { | ||||
|                 if (it.name == "bytes") { | ||||
|                     val name = FileName(it.originalFileName ?: error("File name is unknown for default part")) | ||||
|                     resultInput = MPPFile.createTempFile( | ||||
|                         name.nameWithoutExtension.let { | ||||
|                             var resultName = it | ||||
|                             while (resultName.length < 3) { | ||||
|                                 resultName += "_" | ||||
|                             } | ||||
|                             resultName | ||||
|                         }, | ||||
|                         ".${name.extension}" | ||||
|                     ).apply { | ||||
|                         outputStream().use { fileStream -> | ||||
|                             it.streamProvider().use { | ||||
|                                 it.copyTo(fileStream) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     onCustomFileItem(it) | ||||
|                 } | ||||
|             } | ||||
|             is PartData.BinaryItem -> onBinaryContent(it) | ||||
|             is PartData.BinaryChannelItem -> onBinaryChannelItem(it) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     resultInput ?: error("Bytes has not been received") | ||||
| } | ||||
|  | ||||
| suspend fun ApplicationCall.getParameterOrSendError( | ||||
|     field: String | ||||
| ) = parameters[field].also { | ||||
|     if (it == null) { | ||||
|         respond(HttpStatusCode.BadRequest, "Request must contains $field") | ||||
|     } | ||||
| } | ||||
|  | ||||
| fun ApplicationCall.getQueryParameter( | ||||
|     field: String | ||||
| ) = request.queryParameters[field] | ||||
|  | ||||
| suspend fun ApplicationCall.getQueryParameterOrSendError( | ||||
|     field: String | ||||
| ) = getQueryParameter(field).also { | ||||
|     if (it == null) { | ||||
|         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") | ||||
| fun <T> ApplicationCall.decodeUrlQueryValue( | ||||
|     field: String, | ||||
|     deserializer: DeserializationStrategy<T> | ||||
| ) = getQueryParameter(field) ?.let { | ||||
|     standardKtorSerialFormat.decodeHex( | ||||
|         deserializer, | ||||
|         it | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead") | ||||
| suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError( | ||||
|     field: String, | ||||
|     deserializer: DeserializationStrategy<T> | ||||
| ) = decodeUrlQueryValue(field, deserializer).also { | ||||
|     if (it == null) { | ||||
|         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") | ||||
|     } | ||||
| } | ||||
| @@ -26,7 +26,6 @@ import java.nio.file.attribute.FileTime | ||||
|  | ||||
| class TemporalFilesRoutingConfigurator( | ||||
|     private val subpath: String = DefaultTemporalFilesSubPath, | ||||
|     private val unifiedRouter: UnifiedRouter = UnifiedRouter.default, | ||||
|     private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer | ||||
| ) : ApplicationRoutingConfigurator.Element { | ||||
|     interface TemporalFilesUtilizer { | ||||
| @@ -80,42 +79,40 @@ class TemporalFilesRoutingConfigurator( | ||||
|  | ||||
|     override fun Route.invoke() { | ||||
|         post(subpath) { | ||||
|             unifiedRouter.apply { | ||||
|                 val multipart = call.receiveMultipart() | ||||
|             val multipart = call.receiveMultipart() | ||||
|  | ||||
|                 var fileInfo: Pair<TemporalFileId, MPPFile>? = null | ||||
|                 var part = multipart.readPart() | ||||
|             var fileInfo: Pair<TemporalFileId, MPPFile>? = null | ||||
|             var part = multipart.readPart() | ||||
|  | ||||
|                 while (part != null) { | ||||
|                     if (part is PartData.FileItem) { | ||||
|                         break | ||||
|                     } | ||||
|                     part = multipart.readPart() | ||||
|             while (part != null) { | ||||
|                 if (part is PartData.FileItem) { | ||||
|                     break | ||||
|                 } | ||||
|  | ||||
|                 part ?.let { | ||||
|                     if (it is PartData.FileItem) { | ||||
|                         val fileId = TemporalFileId(uuid4().toString()) | ||||
|                         val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let | ||||
|                         fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply { | ||||
|                             outputStream().use { outputStream -> | ||||
|                                 it.streamProvider().use { | ||||
|                                     it.copyTo(outputStream) | ||||
|                                 } | ||||
|                             } | ||||
|                             deleteOnExit() | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 fileInfo ?.also { (fileId, file) -> | ||||
|                     temporalFilesMutex.withLock { | ||||
|                         temporalFilesMap[fileId] = file | ||||
|                     } | ||||
|                     call.respondText(fileId.string) | ||||
|                     launchSafelyWithoutExceptions { filesFlow.emit(fileId) } | ||||
|                 } ?: call.respond(HttpStatusCode.BadRequest) | ||||
|                 part = multipart.readPart() | ||||
|             } | ||||
|  | ||||
|             part ?.let { | ||||
|                 if (it is PartData.FileItem) { | ||||
|                     val fileId = TemporalFileId(uuid4().toString()) | ||||
|                     val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let | ||||
|                     fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply { | ||||
|                         outputStream().use { outputStream -> | ||||
|                             it.streamProvider().use { | ||||
|                                 it.copyTo(outputStream) | ||||
|                             } | ||||
|                         } | ||||
|                         deleteOnExit() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             fileInfo ?.also { (fileId, file) -> | ||||
|                 temporalFilesMutex.withLock { | ||||
|                     temporalFilesMap[fileId] = file | ||||
|                 } | ||||
|                 call.respondText(fileId.string) | ||||
|                 launchSafelyWithoutExceptions { filesFlow.emit(fileId) } | ||||
|             } ?: call.respond(HttpStatusCode.BadRequest) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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" | ||||
|  | ||||
| kotlin { | ||||
|     sourceSets { | ||||
|         androidMain { | ||||
|             dependsOn jvmMain | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -78,12 +78,14 @@ private fun printLanguageCodeAndTags( | ||||
|     indents: String = "    " | ||||
| ): String = if (tag.subtags.isEmpty()) { | ||||
| """${indents}${baseClassSerializerAnnotationName} | ||||
| ${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}" }""" | ||||
| ${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 { | ||||
| """ | ||||
| ${indents}${baseClassSerializerAnnotationName} | ||||
| ${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() { | ||||
| ${indents}    override val code: String = "${tag.tag}" | ||||
| ${indents}    override val withoutDialect: String | ||||
| ${indents}        get() = code | ||||
|  | ||||
| ${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents}    ") }} | ||||
|  | ||||
| @@ -104,11 +106,14 @@ import kotlinx.serialization.Serializable | ||||
| ${baseClassSerializerAnnotationName} | ||||
| sealed class $baseClassName { | ||||
|     abstract val code: String | ||||
|     abstract val withoutDialect: String | ||||
|  | ||||
| ${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = "    ") } } | ||||
|  | ||||
|     $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 | ||||
| } | ||||
|   | ||||
										
											
												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()) | ||||
| @@ -17,10 +17,11 @@ def fix_name(category, raw_name): | ||||
|         result += out1 | ||||
|     return result | ||||
|  | ||||
| # https://www.freeformatter.com/mime-types-list.html | ||||
| if __name__ == '__main__': | ||||
|     df = pd.read_html(open('table.html', 'r')) | ||||
|     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_category = mime.split('/', 1)[0] | ||||
|         mime_name = mime.split('/', 1)[1] | ||||
| @@ -37,7 +38,7 @@ if __name__ == '__main__': | ||||
|     code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n' | ||||
|     code2 += '    KnownMimeTypes.Any,\n' | ||||
|     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)\n    object Any: %s ("%s/*")\n' % (group_name, key) | ||||
|         for mime in group: | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| package dev.inmo.micro_utils.mime_types | ||||
|  | ||||
| data class CustomMimeType(override val raw: String) : MimeType | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,705 @@ | ||||
| package dev.inmo.micro_utils.mime_types | ||||
|  | ||||
| internal val knownMimeTypes: Set<MimeType> = setOf( | ||||
|     KnownMimeTypes.Any, | ||||
|     KnownMimeTypes.Application.VndHzn3dCrossword, | ||||
|     KnownMimeTypes.Application.VndMseq, | ||||
|     KnownMimeTypes.Application.Vnd3mPostItNotes, | ||||
|     KnownMimeTypes.Application.Vnd3gppPicBwLarge, | ||||
|     KnownMimeTypes.Application.Vnd3gppPicBwSmall, | ||||
|     KnownMimeTypes.Application.Vnd3gppPicBwVar, | ||||
|     KnownMimeTypes.Application.Vnd3gpp2Tcap, | ||||
|     KnownMimeTypes.Application.X7zCompressed, | ||||
|     KnownMimeTypes.Application.XAbiword, | ||||
|     KnownMimeTypes.Application.XAceCompressed, | ||||
|     KnownMimeTypes.Application.VndAmericandynamicsAcc, | ||||
|     KnownMimeTypes.Application.VndAcucobol, | ||||
|     KnownMimeTypes.Application.VndAcucorp, | ||||
|     KnownMimeTypes.Application.XAuthorwareBin, | ||||
|     KnownMimeTypes.Application.XAuthorwareMap, | ||||
|     KnownMimeTypes.Application.XAuthorwareSeg, | ||||
|     KnownMimeTypes.Application.VndAdobeAirApplicationInstallerPackageZip, | ||||
|     KnownMimeTypes.Application.XShockwaveFlash, | ||||
|     KnownMimeTypes.Application.VndAdobeFxp, | ||||
|     KnownMimeTypes.Application.Pdf, | ||||
|     KnownMimeTypes.Application.VndCupsPpd, | ||||
|     KnownMimeTypes.Application.XDirector, | ||||
|     KnownMimeTypes.Application.VndAdobeXdpXml, | ||||
|     KnownMimeTypes.Application.VndAdobeXfdf, | ||||
|     KnownMimeTypes.Application.VndAheadSpace, | ||||
|     KnownMimeTypes.Application.VndAirzipFilesecureAzf, | ||||
|     KnownMimeTypes.Application.VndAirzipFilesecureAzs, | ||||
|     KnownMimeTypes.Application.VndAmazonEbook, | ||||
|     KnownMimeTypes.Application.VndAmigaAmi, | ||||
|     KnownMimeTypes.Application.AndrewInset, | ||||
|     KnownMimeTypes.Application.VndAndroidPackageArchive, | ||||
|     KnownMimeTypes.Application.VndAnserWebCertificateIssueInitiation, | ||||
|     KnownMimeTypes.Application.VndAnserWebFundsTransferInitiation, | ||||
|     KnownMimeTypes.Application.VndAntixGameComponent, | ||||
|     KnownMimeTypes.Application.XAppleDiskimage, | ||||
|     KnownMimeTypes.Application.VndAppleInstallerXml, | ||||
|     KnownMimeTypes.Application.Applixware, | ||||
|     KnownMimeTypes.Application.VndHheLessonPlayer, | ||||
|     KnownMimeTypes.Application.XFreearc, | ||||
|     KnownMimeTypes.Application.VndAristanetworksSwi, | ||||
|     KnownMimeTypes.Application.AtomcatXml, | ||||
|     KnownMimeTypes.Application.AtomsvcXml, | ||||
|     KnownMimeTypes.Application.AtomXml, | ||||
|     KnownMimeTypes.Application.PkixAttrCert, | ||||
|     KnownMimeTypes.Application.VndAudiograph, | ||||
|     KnownMimeTypes.Application.XBcpio, | ||||
|     KnownMimeTypes.Application.OctetStream, | ||||
|     KnownMimeTypes.Application.XBittorrent, | ||||
|     KnownMimeTypes.Application.VndRimCod, | ||||
|     KnownMimeTypes.Application.VndBlueiceMultipass, | ||||
|     KnownMimeTypes.Application.VndBmi, | ||||
|     KnownMimeTypes.Application.XSh, | ||||
|     KnownMimeTypes.Application.VndBusinessobjects, | ||||
|     KnownMimeTypes.Application.XBzip, | ||||
|     KnownMimeTypes.Application.XBzip2, | ||||
|     KnownMimeTypes.Application.XCsh, | ||||
|     KnownMimeTypes.Application.VndChemdrawXml, | ||||
|     KnownMimeTypes.Application.XCdf, | ||||
|     KnownMimeTypes.Application.VndContactCmsg, | ||||
|     KnownMimeTypes.Application.VndClaymore, | ||||
|     KnownMimeTypes.Application.VndClonkC4group, | ||||
|     KnownMimeTypes.Application.CdmiCapability, | ||||
|     KnownMimeTypes.Application.CdmiContainer, | ||||
|     KnownMimeTypes.Application.CdmiDomain, | ||||
|     KnownMimeTypes.Application.CdmiObject, | ||||
|     KnownMimeTypes.Application.CdmiQueue, | ||||
|     KnownMimeTypes.Application.VndCluetrustCartomobileConfig, | ||||
|     KnownMimeTypes.Application.VndCluetrustCartomobileConfigPkg, | ||||
|     KnownMimeTypes.Application.MacCompactpro, | ||||
|     KnownMimeTypes.Application.VndWapWmlc, | ||||
|     KnownMimeTypes.Application.VndXara, | ||||
|     KnownMimeTypes.Application.VndCosmocaller, | ||||
|     KnownMimeTypes.Application.XCpio, | ||||
|     KnownMimeTypes.Application.VndCrickClicker, | ||||
|     KnownMimeTypes.Application.VndCrickClickerKeyboard, | ||||
|     KnownMimeTypes.Application.VndCrickClickerPalette, | ||||
|     KnownMimeTypes.Application.VndCrickClickerTemplate, | ||||
|     KnownMimeTypes.Application.VndCrickClickerWordbank, | ||||
|     KnownMimeTypes.Application.VndCriticaltoolsWbsXml, | ||||
|     KnownMimeTypes.Application.VndRigCryptonote, | ||||
|     KnownMimeTypes.Application.CuSeeme, | ||||
|     KnownMimeTypes.Application.PrsCww, | ||||
|     KnownMimeTypes.Application.VndCurlCar, | ||||
|     KnownMimeTypes.Application.VndCurlPcurl, | ||||
|     KnownMimeTypes.Application.VndYellowriverCustomMenu, | ||||
|     KnownMimeTypes.Application.DsscDer, | ||||
|     KnownMimeTypes.Application.DsscXml, | ||||
|     KnownMimeTypes.Application.XDebianPackage, | ||||
|     KnownMimeTypes.Application.XDvi, | ||||
|     KnownMimeTypes.Application.VndFdsnSeed, | ||||
|     KnownMimeTypes.Application.XDtbookXml, | ||||
|     KnownMimeTypes.Application.XDtbresourceXml, | ||||
|     KnownMimeTypes.Application.VndDvbAit, | ||||
|     KnownMimeTypes.Application.VndDvbService, | ||||
|     KnownMimeTypes.Application.XmlDtd, | ||||
|     KnownMimeTypes.Application.VndDolbyMlp, | ||||
|     KnownMimeTypes.Application.XDoom, | ||||
|     KnownMimeTypes.Application.VndDpgraph, | ||||
|     KnownMimeTypes.Application.VndDreamfactory, | ||||
|     KnownMimeTypes.Application.VndDynageo, | ||||
|     KnownMimeTypes.Application.Ecmascript, | ||||
|     KnownMimeTypes.Application.VndEcowinChart, | ||||
|     KnownMimeTypes.Application.Exi, | ||||
|     KnownMimeTypes.Application.VndProteusMagazine, | ||||
|     KnownMimeTypes.Application.EpubZip, | ||||
|     KnownMimeTypes.Application.VndEnliven, | ||||
|     KnownMimeTypes.Application.VndIsXpr, | ||||
|     KnownMimeTypes.Application.VndXfdl, | ||||
|     KnownMimeTypes.Application.EmmaXml, | ||||
|     KnownMimeTypes.Application.VndEzpixAlbum, | ||||
|     KnownMimeTypes.Application.VndEzpixPackage, | ||||
|     KnownMimeTypes.Application.VndDenovoFcselayoutLink, | ||||
|     KnownMimeTypes.Application.VndFluxtimeClip, | ||||
|     KnownMimeTypes.Application.VndFdf, | ||||
|     KnownMimeTypes.Application.VndMif, | ||||
|     KnownMimeTypes.Application.VndFramemaker, | ||||
|     KnownMimeTypes.Application.VndFscWeblaunch, | ||||
|     KnownMimeTypes.Application.VndFrogansFnc, | ||||
|     KnownMimeTypes.Application.VndFrogansLtf, | ||||
|     KnownMimeTypes.Application.VndFujixeroxDdd, | ||||
|     KnownMimeTypes.Application.VndFujixeroxDocuworks, | ||||
|     KnownMimeTypes.Application.VndFujixeroxDocuworksBinder, | ||||
|     KnownMimeTypes.Application.VndFujitsuOasys, | ||||
|     KnownMimeTypes.Application.VndFujitsuOasys2, | ||||
|     KnownMimeTypes.Application.VndFujitsuOasys3, | ||||
|     KnownMimeTypes.Application.VndFujitsuOasysgp, | ||||
|     KnownMimeTypes.Application.VndFujitsuOasysprs, | ||||
|     KnownMimeTypes.Application.XFuturesplash, | ||||
|     KnownMimeTypes.Application.VndFuzzysheet, | ||||
|     KnownMimeTypes.Application.VndGmx, | ||||
|     KnownMimeTypes.Application.VndGenomatixTuxedo, | ||||
|     KnownMimeTypes.Application.VndGeogebraFile, | ||||
|     KnownMimeTypes.Application.VndGeogebraTool, | ||||
|     KnownMimeTypes.Application.VndGeometryExplorer, | ||||
|     KnownMimeTypes.Application.VndGeonext, | ||||
|     KnownMimeTypes.Application.VndGeoplan, | ||||
|     KnownMimeTypes.Application.VndGeospace, | ||||
|     KnownMimeTypes.Application.XFontGhostscript, | ||||
|     KnownMimeTypes.Application.XFontBdf, | ||||
|     KnownMimeTypes.Application.XGtar, | ||||
|     KnownMimeTypes.Application.XTexinfo, | ||||
|     KnownMimeTypes.Application.XGnumeric, | ||||
|     KnownMimeTypes.Application.VndGoogleEarthKmlXml, | ||||
|     KnownMimeTypes.Application.VndGoogleEarthKmz, | ||||
|     KnownMimeTypes.Application.GpxXml, | ||||
|     KnownMimeTypes.Application.VndGrafeq, | ||||
|     KnownMimeTypes.Application.VndGrooveAccount, | ||||
|     KnownMimeTypes.Application.VndGrooveHelp, | ||||
|     KnownMimeTypes.Application.VndGrooveIdentityMessage, | ||||
|     KnownMimeTypes.Application.VndGrooveInjector, | ||||
|     KnownMimeTypes.Application.VndGrooveToolMessage, | ||||
|     KnownMimeTypes.Application.VndGrooveToolTemplate, | ||||
|     KnownMimeTypes.Application.VndGrooveVcard, | ||||
|     KnownMimeTypes.Application.Gzip, | ||||
|     KnownMimeTypes.Application.VndHpHpid, | ||||
|     KnownMimeTypes.Application.VndHpHps, | ||||
|     KnownMimeTypes.Application.XHdf, | ||||
|     KnownMimeTypes.Application.VndHbci, | ||||
|     KnownMimeTypes.Application.VndHpJlyt, | ||||
|     KnownMimeTypes.Application.VndHpPcl, | ||||
|     KnownMimeTypes.Application.VndHpHpgl, | ||||
|     KnownMimeTypes.Application.VndYamahaHvScript, | ||||
|     KnownMimeTypes.Application.VndYamahaHvDic, | ||||
|     KnownMimeTypes.Application.VndYamahaHvVoice, | ||||
|     KnownMimeTypes.Application.VndHydrostatixSofData, | ||||
|     KnownMimeTypes.Application.Hyperstudio, | ||||
|     KnownMimeTypes.Application.VndHalXml, | ||||
|     KnownMimeTypes.Application.VndIbmRightsManagement, | ||||
|     KnownMimeTypes.Application.VndIbmSecureContainer, | ||||
|     KnownMimeTypes.Application.VndIccprofile, | ||||
|     KnownMimeTypes.Application.VndIgloader, | ||||
|     KnownMimeTypes.Application.VndImmervisionIvp, | ||||
|     KnownMimeTypes.Application.VndImmervisionIvu, | ||||
|     KnownMimeTypes.Application.ReginfoXml, | ||||
|     KnownMimeTypes.Application.VndIntergeo, | ||||
|     KnownMimeTypes.Application.VndCinderella, | ||||
|     KnownMimeTypes.Application.VndInterconFormnet, | ||||
|     KnownMimeTypes.Application.VndIsacFcs, | ||||
|     KnownMimeTypes.Application.Ipfix, | ||||
|     KnownMimeTypes.Application.PkixCert, | ||||
|     KnownMimeTypes.Application.Pkixcmp, | ||||
|     KnownMimeTypes.Application.PkixCrl, | ||||
|     KnownMimeTypes.Application.PkixPkipath, | ||||
|     KnownMimeTypes.Application.VndInsorsIgm, | ||||
|     KnownMimeTypes.Application.VndIpunpluggedRcprofile, | ||||
|     KnownMimeTypes.Application.VndIrepositoryPackageXml, | ||||
|     KnownMimeTypes.Application.JavaArchive, | ||||
|     KnownMimeTypes.Application.JavaVm, | ||||
|     KnownMimeTypes.Application.XJavaJnlpFile, | ||||
|     KnownMimeTypes.Application.JavaSerializedObject, | ||||
|     KnownMimeTypes.Application.Javascript, | ||||
|     KnownMimeTypes.Application.Json, | ||||
|     KnownMimeTypes.Application.VndJoostJodaArchive, | ||||
|     KnownMimeTypes.Application.LdJson, | ||||
|     KnownMimeTypes.Application.VndKahootz, | ||||
|     KnownMimeTypes.Application.VndChipnutsKaraokeMmd, | ||||
|     KnownMimeTypes.Application.VndKdeKarbon, | ||||
|     KnownMimeTypes.Application.VndKdeKchart, | ||||
|     KnownMimeTypes.Application.VndKdeKformula, | ||||
|     KnownMimeTypes.Application.VndKdeKivio, | ||||
|     KnownMimeTypes.Application.VndKdeKontour, | ||||
|     KnownMimeTypes.Application.VndKdeKpresenter, | ||||
|     KnownMimeTypes.Application.VndKdeKspread, | ||||
|     KnownMimeTypes.Application.VndKdeKword, | ||||
|     KnownMimeTypes.Application.VndKenameaapp, | ||||
|     KnownMimeTypes.Application.VndKidspiration, | ||||
|     KnownMimeTypes.Application.VndKinar, | ||||
|     KnownMimeTypes.Application.VndKodakDescriptor, | ||||
|     KnownMimeTypes.Application.VndLasLasXml, | ||||
|     KnownMimeTypes.Application.XLatex, | ||||
|     KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceDesktop, | ||||
|     KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceExchangeXml, | ||||
|     KnownMimeTypes.Application.VndJam, | ||||
|     KnownMimeTypes.Application.VndLotus123, | ||||
|     KnownMimeTypes.Application.VndLotusApproach, | ||||
|     KnownMimeTypes.Application.VndLotusFreelance, | ||||
|     KnownMimeTypes.Application.VndLotusNotes, | ||||
|     KnownMimeTypes.Application.VndLotusOrganizer, | ||||
|     KnownMimeTypes.Application.VndLotusScreencam, | ||||
|     KnownMimeTypes.Application.VndLotusWordpro, | ||||
|     KnownMimeTypes.Application.MacBinhex40, | ||||
|     KnownMimeTypes.Application.VndMacportsPortpkg, | ||||
|     KnownMimeTypes.Application.VndOsgeoMapguidePackage, | ||||
|     KnownMimeTypes.Application.Marc, | ||||
|     KnownMimeTypes.Application.MarcxmlXml, | ||||
|     KnownMimeTypes.Application.Mxf, | ||||
|     KnownMimeTypes.Application.VndWolframPlayer, | ||||
|     KnownMimeTypes.Application.Mathematica, | ||||
|     KnownMimeTypes.Application.MathmlXml, | ||||
|     KnownMimeTypes.Application.Mbox, | ||||
|     KnownMimeTypes.Application.VndMedcalcdata, | ||||
|     KnownMimeTypes.Application.MediaservercontrolXml, | ||||
|     KnownMimeTypes.Application.VndMediastationCdkey, | ||||
|     KnownMimeTypes.Application.VndMfer, | ||||
|     KnownMimeTypes.Application.VndMfmp, | ||||
|     KnownMimeTypes.Application.MadsXml, | ||||
|     KnownMimeTypes.Application.MetsXml, | ||||
|     KnownMimeTypes.Application.ModsXml, | ||||
|     KnownMimeTypes.Application.Metalink4Xml, | ||||
|     KnownMimeTypes.Application.VndMcd, | ||||
|     KnownMimeTypes.Application.VndMicrografxFlo, | ||||
|     KnownMimeTypes.Application.VndMicrografxIgx, | ||||
|     KnownMimeTypes.Application.VndEszigno3Xml, | ||||
|     KnownMimeTypes.Application.XMsaccess, | ||||
|     KnownMimeTypes.Application.XMsdownload, | ||||
|     KnownMimeTypes.Application.VndMsArtgalry, | ||||
|     KnownMimeTypes.Application.VndMsCabCompressed, | ||||
|     KnownMimeTypes.Application.VndMsIms, | ||||
|     KnownMimeTypes.Application.XMsApplication, | ||||
|     KnownMimeTypes.Application.XMsclip, | ||||
|     KnownMimeTypes.Application.VndMsFontobject, | ||||
|     KnownMimeTypes.Application.VndMsExcel, | ||||
|     KnownMimeTypes.Application.VndMsExcelAddinMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsExcelSheetBinaryMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsExcelTemplateMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsExcelSheetMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsHtmlhelp, | ||||
|     KnownMimeTypes.Application.XMscardfile, | ||||
|     KnownMimeTypes.Application.VndMsLrm, | ||||
|     KnownMimeTypes.Application.XMsmediaview, | ||||
|     KnownMimeTypes.Application.XMsmoney, | ||||
|     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlPresentation, | ||||
|     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlide, | ||||
|     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlideshow, | ||||
|     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlTemplate, | ||||
|     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlSheet, | ||||
|     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlTemplate, | ||||
|     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlDocument, | ||||
|     KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlTemplate, | ||||
|     KnownMimeTypes.Application.XMsbinder, | ||||
|     KnownMimeTypes.Application.VndMsOfficetheme, | ||||
|     KnownMimeTypes.Application.Onenote, | ||||
|     KnownMimeTypes.Application.VndMsPowerpoint, | ||||
|     KnownMimeTypes.Application.VndMsPowerpointAddinMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsPowerpointSlideMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsPowerpointPresentationMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsPowerpointSlideshowMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsPowerpointTemplateMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsProject, | ||||
|     KnownMimeTypes.Application.XMspublisher, | ||||
|     KnownMimeTypes.Application.XMsschedule, | ||||
|     KnownMimeTypes.Application.XSilverlightApp, | ||||
|     KnownMimeTypes.Application.VndMsPkiStl, | ||||
|     KnownMimeTypes.Application.VndMsPkiSeccat, | ||||
|     KnownMimeTypes.Application.VndVisio, | ||||
|     KnownMimeTypes.Application.VndVisio2013, | ||||
|     KnownMimeTypes.Application.XMsWmd, | ||||
|     KnownMimeTypes.Application.VndMsWpl, | ||||
|     KnownMimeTypes.Application.XMsWmz, | ||||
|     KnownMimeTypes.Application.XMsmetafile, | ||||
|     KnownMimeTypes.Application.XMsterminal, | ||||
|     KnownMimeTypes.Application.Msword, | ||||
|     KnownMimeTypes.Application.VndMsWordDocumentMacroenabled12, | ||||
|     KnownMimeTypes.Application.VndMsWordTemplateMacroenabled12, | ||||
|     KnownMimeTypes.Application.XMswrite, | ||||
|     KnownMimeTypes.Application.VndMsWorks, | ||||
|     KnownMimeTypes.Application.XMsXbap, | ||||
|     KnownMimeTypes.Application.VndMsXpsdocument, | ||||
|     KnownMimeTypes.Application.VndIbmMinipay, | ||||
|     KnownMimeTypes.Application.VndIbmModcap, | ||||
|     KnownMimeTypes.Application.VndJcpJavameMidletRms, | ||||
|     KnownMimeTypes.Application.VndTmobileLivetv, | ||||
|     KnownMimeTypes.Application.XMobipocketEbook, | ||||
|     KnownMimeTypes.Application.VndMobiusMbk, | ||||
|     KnownMimeTypes.Application.VndMobiusDis, | ||||
|     KnownMimeTypes.Application.VndMobiusPlc, | ||||
|     KnownMimeTypes.Application.VndMobiusMqy, | ||||
|     KnownMimeTypes.Application.VndMobiusMsl, | ||||
|     KnownMimeTypes.Application.VndMobiusTxf, | ||||
|     KnownMimeTypes.Application.VndMobiusDaf, | ||||
|     KnownMimeTypes.Application.VndMophunCertificate, | ||||
|     KnownMimeTypes.Application.VndMophunApplication, | ||||
|     KnownMimeTypes.Application.Mp21, | ||||
|     KnownMimeTypes.Application.Mp4, | ||||
|     KnownMimeTypes.Application.VndAppleMpegurl, | ||||
|     KnownMimeTypes.Application.VndMusician, | ||||
|     KnownMimeTypes.Application.VndMuveeStyle, | ||||
|     KnownMimeTypes.Application.XvXml, | ||||
|     KnownMimeTypes.Application.VndNokiaNGageData, | ||||
|     KnownMimeTypes.Application.VndNokiaNGageSymbianInstall, | ||||
|     KnownMimeTypes.Application.XDtbncxXml, | ||||
|     KnownMimeTypes.Application.XNetcdf, | ||||
|     KnownMimeTypes.Application.VndNeurolanguageNlu, | ||||
|     KnownMimeTypes.Application.VndDna, | ||||
|     KnownMimeTypes.Application.VndNoblenetDirectory, | ||||
|     KnownMimeTypes.Application.VndNoblenetSealer, | ||||
|     KnownMimeTypes.Application.VndNoblenetWeb, | ||||
|     KnownMimeTypes.Application.VndNokiaRadioPreset, | ||||
|     KnownMimeTypes.Application.VndNokiaRadioPresets, | ||||
|     KnownMimeTypes.Application.VndNovadigmEdm, | ||||
|     KnownMimeTypes.Application.VndNovadigmEdx, | ||||
|     KnownMimeTypes.Application.VndNovadigmExt, | ||||
|     KnownMimeTypes.Application.VndFlographit, | ||||
|     KnownMimeTypes.Application.Oda, | ||||
|     KnownMimeTypes.Application.Ogg, | ||||
|     KnownMimeTypes.Application.VndOmaDd2Xml, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentTextWeb, | ||||
|     KnownMimeTypes.Application.OebpsPackageXml, | ||||
|     KnownMimeTypes.Application.VndIntuQbo, | ||||
|     KnownMimeTypes.Application.VndOpenofficeorgExtension, | ||||
|     KnownMimeTypes.Application.VndYamahaOpenscoreformat, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentChart, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentChartTemplate, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentDatabase, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentFormula, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentFormulaTemplate, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentGraphics, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentGraphicsTemplate, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentImage, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentImageTemplate, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentPresentation, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentPresentationTemplate, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheet, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheetTemplate, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentText, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentTextMaster, | ||||
|     KnownMimeTypes.Application.VndOasisOpendocumentTextTemplate, | ||||
|     KnownMimeTypes.Application.VndSunXmlCalc, | ||||
|     KnownMimeTypes.Application.VndSunXmlCalcTemplate, | ||||
|     KnownMimeTypes.Application.VndSunXmlDraw, | ||||
|     KnownMimeTypes.Application.VndSunXmlDrawTemplate, | ||||
|     KnownMimeTypes.Application.VndSunXmlImpress, | ||||
|     KnownMimeTypes.Application.VndSunXmlImpressTemplate, | ||||
|     KnownMimeTypes.Application.VndSunXmlMath, | ||||
|     KnownMimeTypes.Application.VndSunXmlWriter, | ||||
|     KnownMimeTypes.Application.VndSunXmlWriterGlobal, | ||||
|     KnownMimeTypes.Application.VndSunXmlWriterTemplate, | ||||
|     KnownMimeTypes.Application.XFontOtf, | ||||
|     KnownMimeTypes.Application.VndYamahaOpenscoreformatOsfpvgXml, | ||||
|     KnownMimeTypes.Application.VndOsgiDp, | ||||
|     KnownMimeTypes.Application.VndPalm, | ||||
|     KnownMimeTypes.Application.VndPawaafile, | ||||
|     KnownMimeTypes.Application.VndHpPclxl, | ||||
|     KnownMimeTypes.Application.VndPicsel, | ||||
|     KnownMimeTypes.Application.PicsRules, | ||||
|     KnownMimeTypes.Application.XChat, | ||||
|     KnownMimeTypes.Application.Pkcs10, | ||||
|     KnownMimeTypes.Application.XPkcs12, | ||||
|     KnownMimeTypes.Application.Pkcs7Mime, | ||||
|     KnownMimeTypes.Application.Pkcs7Signature, | ||||
|     KnownMimeTypes.Application.XPkcs7Certreqresp, | ||||
|     KnownMimeTypes.Application.XPkcs7Certificates, | ||||
|     KnownMimeTypes.Application.Pkcs8, | ||||
|     KnownMimeTypes.Application.VndPocketlearn, | ||||
|     KnownMimeTypes.Application.XFontPcf, | ||||
|     KnownMimeTypes.Application.FontTdpfr, | ||||
|     KnownMimeTypes.Application.XChessPgn, | ||||
|     KnownMimeTypes.Application.PskcXml, | ||||
|     KnownMimeTypes.Application.VndCtcPosml, | ||||
|     KnownMimeTypes.Application.Postscript, | ||||
|     KnownMimeTypes.Application.XFontType1, | ||||
|     KnownMimeTypes.Application.VndPowerbuilder6, | ||||
|     KnownMimeTypes.Application.PgpEncrypted, | ||||
|     KnownMimeTypes.Application.PgpSignature, | ||||
|     KnownMimeTypes.Application.VndPreviewsystemsBox, | ||||
|     KnownMimeTypes.Application.VndPviPtid1, | ||||
|     KnownMimeTypes.Application.PlsXml, | ||||
|     KnownMimeTypes.Application.VndPgFormat, | ||||
|     KnownMimeTypes.Application.VndPgOsasli, | ||||
|     KnownMimeTypes.Application.XFontLinuxPsf, | ||||
|     KnownMimeTypes.Application.VndPublishareDeltaTree, | ||||
|     KnownMimeTypes.Application.VndPmiWidget, | ||||
|     KnownMimeTypes.Application.VndQuarkQuarkxpress, | ||||
|     KnownMimeTypes.Application.VndEpsonEsf, | ||||
|     KnownMimeTypes.Application.VndEpsonMsf, | ||||
|     KnownMimeTypes.Application.VndEpsonSsf, | ||||
|     KnownMimeTypes.Application.VndEpsonQuickanime, | ||||
|     KnownMimeTypes.Application.VndIntuQfx, | ||||
|     KnownMimeTypes.Application.XRarCompressed, | ||||
|     KnownMimeTypes.Application.RsdXml, | ||||
|     KnownMimeTypes.Application.VndRnRealmedia, | ||||
|     KnownMimeTypes.Application.VndRealvncBed, | ||||
|     KnownMimeTypes.Application.VndRecordareMusicxml, | ||||
|     KnownMimeTypes.Application.VndRecordareMusicxmlXml, | ||||
|     KnownMimeTypes.Application.RelaxNgCompactSyntax, | ||||
|     KnownMimeTypes.Application.VndDataVisionRdz, | ||||
|     KnownMimeTypes.Application.RdfXml, | ||||
|     KnownMimeTypes.Application.VndCloantoRp9, | ||||
|     KnownMimeTypes.Application.VndJisp, | ||||
|     KnownMimeTypes.Application.Rtf, | ||||
|     KnownMimeTypes.Application.VndRoute66Link66Xml, | ||||
|     KnownMimeTypes.Application.RssXml, | ||||
|     KnownMimeTypes.Application.ShfXml, | ||||
|     KnownMimeTypes.Application.VndSailingtrackerTrack, | ||||
|     KnownMimeTypes.Application.VndSusCalendar, | ||||
|     KnownMimeTypes.Application.SruXml, | ||||
|     KnownMimeTypes.Application.SetPaymentInitiation, | ||||
|     KnownMimeTypes.Application.SetRegistrationInitiation, | ||||
|     KnownMimeTypes.Application.VndSema, | ||||
|     KnownMimeTypes.Application.VndSemd, | ||||
|     KnownMimeTypes.Application.VndSemf, | ||||
|     KnownMimeTypes.Application.VndSeemail, | ||||
|     KnownMimeTypes.Application.XFontSnf, | ||||
|     KnownMimeTypes.Application.ScvpVpRequest, | ||||
|     KnownMimeTypes.Application.ScvpVpResponse, | ||||
|     KnownMimeTypes.Application.ScvpCvRequest, | ||||
|     KnownMimeTypes.Application.ScvpCvResponse, | ||||
|     KnownMimeTypes.Application.Sdp, | ||||
|     KnownMimeTypes.Application.VndShanaInformedFormdata, | ||||
|     KnownMimeTypes.Application.VndShanaInformedFormtemplate, | ||||
|     KnownMimeTypes.Application.VndShanaInformedInterchange, | ||||
|     KnownMimeTypes.Application.VndShanaInformedPackage, | ||||
|     KnownMimeTypes.Application.ThraudXml, | ||||
|     KnownMimeTypes.Application.XShar, | ||||
|     KnownMimeTypes.Application.VndEpsonSalt, | ||||
|     KnownMimeTypes.Application.VndAccpacSimplyAso, | ||||
|     KnownMimeTypes.Application.VndAccpacSimplyImp, | ||||
|     KnownMimeTypes.Application.VndSimtechMindmapper, | ||||
|     KnownMimeTypes.Application.VndCommonspace, | ||||
|     KnownMimeTypes.Application.VndYamahaSmafAudio, | ||||
|     KnownMimeTypes.Application.VndSmaf, | ||||
|     KnownMimeTypes.Application.VndYamahaSmafPhrase, | ||||
|     KnownMimeTypes.Application.VndSmartTeacher, | ||||
|     KnownMimeTypes.Application.VndSvd, | ||||
|     KnownMimeTypes.Application.SparqlQuery, | ||||
|     KnownMimeTypes.Application.SparqlResultsXml, | ||||
|     KnownMimeTypes.Application.Srgs, | ||||
|     KnownMimeTypes.Application.SrgsXml, | ||||
|     KnownMimeTypes.Application.SsmlXml, | ||||
|     KnownMimeTypes.Application.VndKoan, | ||||
|     KnownMimeTypes.Application.VndStardivisionCalc, | ||||
|     KnownMimeTypes.Application.VndStardivisionDraw, | ||||
|     KnownMimeTypes.Application.VndStardivisionImpress, | ||||
|     KnownMimeTypes.Application.VndStardivisionMath, | ||||
|     KnownMimeTypes.Application.VndStardivisionWriter, | ||||
|     KnownMimeTypes.Application.VndStardivisionWriterGlobal, | ||||
|     KnownMimeTypes.Application.VndStepmaniaStepchart, | ||||
|     KnownMimeTypes.Application.XStuffit, | ||||
|     KnownMimeTypes.Application.XStuffitx, | ||||
|     KnownMimeTypes.Application.VndSolentSdkmXml, | ||||
|     KnownMimeTypes.Application.VndOlpcSugar, | ||||
|     KnownMimeTypes.Application.VndWqd, | ||||
|     KnownMimeTypes.Application.VndSymbianInstall, | ||||
|     KnownMimeTypes.Application.SmilXml, | ||||
|     KnownMimeTypes.Application.VndSyncmlXml, | ||||
|     KnownMimeTypes.Application.VndSyncmlDmWbxml, | ||||
|     KnownMimeTypes.Application.VndSyncmlDmXml, | ||||
|     KnownMimeTypes.Application.XSv4cpio, | ||||
|     KnownMimeTypes.Application.XSv4crc, | ||||
|     KnownMimeTypes.Application.SbmlXml, | ||||
|     KnownMimeTypes.Application.VndTaoIntentModuleArchive, | ||||
|     KnownMimeTypes.Application.XTar, | ||||
|     KnownMimeTypes.Application.XTcl, | ||||
|     KnownMimeTypes.Application.XTex, | ||||
|     KnownMimeTypes.Application.XTexTfm, | ||||
|     KnownMimeTypes.Application.TeiXml, | ||||
|     KnownMimeTypes.Application.VndSpotfireDxp, | ||||
|     KnownMimeTypes.Application.VndSpotfireSfs, | ||||
|     KnownMimeTypes.Application.TimestampedData, | ||||
|     KnownMimeTypes.Application.VndTridTpt, | ||||
|     KnownMimeTypes.Application.VndTriscapeMxs, | ||||
|     KnownMimeTypes.Application.VndTrueapp, | ||||
|     KnownMimeTypes.Application.XFontTtf, | ||||
|     KnownMimeTypes.Application.VndUmajin, | ||||
|     KnownMimeTypes.Application.VndUomlXml, | ||||
|     KnownMimeTypes.Application.VndUnity, | ||||
|     KnownMimeTypes.Application.VndUfdl, | ||||
|     KnownMimeTypes.Application.VndUiqTheme, | ||||
|     KnownMimeTypes.Application.XUstar, | ||||
|     KnownMimeTypes.Application.XCdlink, | ||||
|     KnownMimeTypes.Application.VndVsf, | ||||
|     KnownMimeTypes.Application.VndVcx, | ||||
|     KnownMimeTypes.Application.VndVisionary, | ||||
|     KnownMimeTypes.Application.CcxmlXml, | ||||
|     KnownMimeTypes.Application.VoicexmlXml, | ||||
|     KnownMimeTypes.Application.XWaisSource, | ||||
|     KnownMimeTypes.Application.VndWapWbxml, | ||||
|     KnownMimeTypes.Application.DavmountXml, | ||||
|     KnownMimeTypes.Application.XFontWoff, | ||||
|     KnownMimeTypes.Application.WspolicyXml, | ||||
|     KnownMimeTypes.Application.VndWebturbo, | ||||
|     KnownMimeTypes.Application.Widget, | ||||
|     KnownMimeTypes.Application.Winhlp, | ||||
|     KnownMimeTypes.Application.VndWapWmlscriptc, | ||||
|     KnownMimeTypes.Application.VndWordperfect, | ||||
|     KnownMimeTypes.Application.VndWtStf, | ||||
|     KnownMimeTypes.Application.WsdlXml, | ||||
|     KnownMimeTypes.Application.XX509CaCert, | ||||
|     KnownMimeTypes.Application.XXfig, | ||||
|     KnownMimeTypes.Application.XhtmlXml, | ||||
|     KnownMimeTypes.Application.Xml, | ||||
|     KnownMimeTypes.Application.XcapDiffXml, | ||||
|     KnownMimeTypes.Application.XencXml, | ||||
|     KnownMimeTypes.Application.PatchOpsErrorXml, | ||||
|     KnownMimeTypes.Application.ResourceListsXml, | ||||
|     KnownMimeTypes.Application.RlsServicesXml, | ||||
|     KnownMimeTypes.Application.ResourceListsDiffXml, | ||||
|     KnownMimeTypes.Application.XsltXml, | ||||
|     KnownMimeTypes.Application.XopXml, | ||||
|     KnownMimeTypes.Application.XXpinstall, | ||||
|     KnownMimeTypes.Application.XspfXml, | ||||
|     KnownMimeTypes.Application.VndMozillaXulXml, | ||||
|     KnownMimeTypes.Application.Yang, | ||||
|     KnownMimeTypes.Application.YinXml, | ||||
|     KnownMimeTypes.Application.VndZul, | ||||
|     KnownMimeTypes.Application.Zip, | ||||
|     KnownMimeTypes.Application.VndHandheldEntertainmentXml, | ||||
|     KnownMimeTypes.Application.VndZzazzDeckXml, | ||||
|     KnownMimeTypes.Audio.Adpcm, | ||||
|     KnownMimeTypes.Audio.XAac, | ||||
|     KnownMimeTypes.Audio.XAiff, | ||||
|     KnownMimeTypes.Audio.VndDeceAudio, | ||||
|     KnownMimeTypes.Audio.VndDigitalWinds, | ||||
|     KnownMimeTypes.Audio.VndDra, | ||||
|     KnownMimeTypes.Audio.VndDts, | ||||
|     KnownMimeTypes.Audio.VndDtsHd, | ||||
|     KnownMimeTypes.Audio.VndRip, | ||||
|     KnownMimeTypes.Audio.VndLucentVoice, | ||||
|     KnownMimeTypes.Audio.XMpegurl, | ||||
|     KnownMimeTypes.Audio.VndMsPlayreadyMediaPya, | ||||
|     KnownMimeTypes.Audio.XMsWma, | ||||
|     KnownMimeTypes.Audio.XMsWax, | ||||
|     KnownMimeTypes.Audio.Midi, | ||||
|     KnownMimeTypes.Audio.Mpeg, | ||||
|     KnownMimeTypes.Audio.Mp4, | ||||
|     KnownMimeTypes.Audio.VndNueraEcelp4800, | ||||
|     KnownMimeTypes.Audio.VndNueraEcelp7470, | ||||
|     KnownMimeTypes.Audio.VndNueraEcelp9600, | ||||
|     KnownMimeTypes.Audio.Ogg, | ||||
|     KnownMimeTypes.Audio.Webm, | ||||
|     KnownMimeTypes.Audio.Opus, | ||||
|     KnownMimeTypes.Audio.XPnRealaudio, | ||||
|     KnownMimeTypes.Audio.XPnRealaudioPlugin, | ||||
|     KnownMimeTypes.Audio.Basic, | ||||
|     KnownMimeTypes.Audio.XWav, | ||||
|     KnownMimeTypes.Chemical.XCdx, | ||||
|     KnownMimeTypes.Chemical.XCml, | ||||
|     KnownMimeTypes.Chemical.XCsml, | ||||
|     KnownMimeTypes.Chemical.XCif, | ||||
|     KnownMimeTypes.Chemical.XCmdf, | ||||
|     KnownMimeTypes.Chemical.XXyz, | ||||
|     KnownMimeTypes.Image.VndDxf, | ||||
|     KnownMimeTypes.Image.Avif, | ||||
|     KnownMimeTypes.Image.Bmp, | ||||
|     KnownMimeTypes.Image.PrsBtif, | ||||
|     KnownMimeTypes.Image.VndDvbSubtitle, | ||||
|     KnownMimeTypes.Image.XCmuRaster, | ||||
|     KnownMimeTypes.Image.Cgm, | ||||
|     KnownMimeTypes.Image.XCmx, | ||||
|     KnownMimeTypes.Image.VndDeceGraphic, | ||||
|     KnownMimeTypes.Image.VndDjvu, | ||||
|     KnownMimeTypes.Image.VndDwg, | ||||
|     KnownMimeTypes.Image.VndFujixeroxEdmicsMmr, | ||||
|     KnownMimeTypes.Image.VndFujixeroxEdmicsRlc, | ||||
|     KnownMimeTypes.Image.VndXiff, | ||||
|     KnownMimeTypes.Image.VndFst, | ||||
|     KnownMimeTypes.Image.VndFastbidsheet, | ||||
|     KnownMimeTypes.Image.VndFpx, | ||||
|     KnownMimeTypes.Image.VndNetFpx, | ||||
|     KnownMimeTypes.Image.XFreehand, | ||||
|     KnownMimeTypes.Image.G3fax, | ||||
|     KnownMimeTypes.Image.Gif, | ||||
|     KnownMimeTypes.Image.XIcon, | ||||
|     KnownMimeTypes.Image.Ief, | ||||
|     KnownMimeTypes.Image.Jpeg, | ||||
|     KnownMimeTypes.Image.XCitrixJpeg, | ||||
|     KnownMimeTypes.Image.Pjpeg, | ||||
|     KnownMimeTypes.Image.VndMsModi, | ||||
|     KnownMimeTypes.Image.Ktx, | ||||
|     KnownMimeTypes.Image.XPcx, | ||||
|     KnownMimeTypes.Image.VndAdobePhotoshop, | ||||
|     KnownMimeTypes.Image.XPict, | ||||
|     KnownMimeTypes.Image.XPortableAnymap, | ||||
|     KnownMimeTypes.Image.XPortableBitmap, | ||||
|     KnownMimeTypes.Image.XPortableGraymap, | ||||
|     KnownMimeTypes.Image.Png, | ||||
|     KnownMimeTypes.Image.XCitrixPng, | ||||
|     KnownMimeTypes.Image.XPng, | ||||
|     KnownMimeTypes.Image.XPortablePixmap, | ||||
|     KnownMimeTypes.Image.SvgXml, | ||||
|     KnownMimeTypes.Image.XRgb, | ||||
|     KnownMimeTypes.Image.Tiff, | ||||
|     KnownMimeTypes.Image.VndWapWbmp, | ||||
|     KnownMimeTypes.Image.Webp, | ||||
|     KnownMimeTypes.Image.XXbitmap, | ||||
|     KnownMimeTypes.Image.XXpixmap, | ||||
|     KnownMimeTypes.Image.XXwindowdump, | ||||
|     KnownMimeTypes.Message.Rfc822, | ||||
|     KnownMimeTypes.Model.VndDwf, | ||||
|     KnownMimeTypes.Model.VndColladaXml, | ||||
|     KnownMimeTypes.Model.VndGtw, | ||||
|     KnownMimeTypes.Model.VndGdl, | ||||
|     KnownMimeTypes.Model.Iges, | ||||
|     KnownMimeTypes.Model.Mesh, | ||||
|     KnownMimeTypes.Model.Vrml, | ||||
|     KnownMimeTypes.Model.VndMts, | ||||
|     KnownMimeTypes.Model.VndVtu, | ||||
|     KnownMimeTypes.Text.XAsm, | ||||
|     KnownMimeTypes.Text.PlainBas, | ||||
|     KnownMimeTypes.Text.XC, | ||||
|     KnownMimeTypes.Text.Css, | ||||
|     KnownMimeTypes.Text.Csv, | ||||
|     KnownMimeTypes.Text.VndCurl, | ||||
|     KnownMimeTypes.Text.VndCurlDcurl, | ||||
|     KnownMimeTypes.Text.VndCurlMcurl, | ||||
|     KnownMimeTypes.Text.VndCurlScurl, | ||||
|     KnownMimeTypes.Text.VndFmiFlexstor, | ||||
|     KnownMimeTypes.Text.XFortran, | ||||
|     KnownMimeTypes.Text.VndGraphviz, | ||||
|     KnownMimeTypes.Text.Html, | ||||
|     KnownMimeTypes.Text.Calendar, | ||||
|     KnownMimeTypes.Text.VndIn3d3dml, | ||||
|     KnownMimeTypes.Text.VndIn3dSpot, | ||||
|     KnownMimeTypes.Text.VndSunJ2meAppDescriptor, | ||||
|     KnownMimeTypes.Text.XJavaSourceJava, | ||||
|     KnownMimeTypes.Text.Javascript, | ||||
|     KnownMimeTypes.Text.VndFly, | ||||
|     KnownMimeTypes.Text.N3, | ||||
|     KnownMimeTypes.Text.XPascal, | ||||
|     KnownMimeTypes.Text.PrsLinesTag, | ||||
|     KnownMimeTypes.Text.Richtext, | ||||
|     KnownMimeTypes.Text.XSetext, | ||||
|     KnownMimeTypes.Text.Sgml, | ||||
|     KnownMimeTypes.Text.TabSeparatedValues, | ||||
|     KnownMimeTypes.Text.Plain, | ||||
|     KnownMimeTypes.Text.Troff, | ||||
|     KnownMimeTypes.Text.Turtle, | ||||
|     KnownMimeTypes.Text.UriList, | ||||
|     KnownMimeTypes.Text.XUuencode, | ||||
|     KnownMimeTypes.Text.XVcalendar, | ||||
|     KnownMimeTypes.Text.XVcard, | ||||
|     KnownMimeTypes.Text.VndWapWml, | ||||
|     KnownMimeTypes.Text.VndWapWmlscript, | ||||
|     KnownMimeTypes.Text.Yaml, | ||||
|     KnownMimeTypes.Video.V3gpp, | ||||
|     KnownMimeTypes.Video.V3gpp2, | ||||
|     KnownMimeTypes.Video.XMsvideo, | ||||
|     KnownMimeTypes.Video.VndDeceHd, | ||||
|     KnownMimeTypes.Video.VndDeceMobile, | ||||
|     KnownMimeTypes.Video.VndUvvuMp4, | ||||
|     KnownMimeTypes.Video.VndDecePd, | ||||
|     KnownMimeTypes.Video.VndDeceSd, | ||||
|     KnownMimeTypes.Video.VndDeceVideo, | ||||
|     KnownMimeTypes.Video.VndFvt, | ||||
|     KnownMimeTypes.Video.XF4v, | ||||
|     KnownMimeTypes.Video.XFlv, | ||||
|     KnownMimeTypes.Video.XFli, | ||||
|     KnownMimeTypes.Video.H261, | ||||
|     KnownMimeTypes.Video.H263, | ||||
|     KnownMimeTypes.Video.H264, | ||||
|     KnownMimeTypes.Video.Jpm, | ||||
|     KnownMimeTypes.Video.Jpeg, | ||||
|     KnownMimeTypes.Video.XM4v, | ||||
|     KnownMimeTypes.Video.XMsAsf, | ||||
|     KnownMimeTypes.Video.VndMsPlayreadyMediaPyv, | ||||
|     KnownMimeTypes.Video.XMsWm, | ||||
|     KnownMimeTypes.Video.XMsWmx, | ||||
|     KnownMimeTypes.Video.XMsWmv, | ||||
|     KnownMimeTypes.Video.XMsWvx, | ||||
|     KnownMimeTypes.Video.Mj2, | ||||
|     KnownMimeTypes.Video.Mp2t, | ||||
|     KnownMimeTypes.Video.VndMpegurl, | ||||
|     KnownMimeTypes.Video.Mpeg, | ||||
|     KnownMimeTypes.Video.Mp4, | ||||
|     KnownMimeTypes.Video.Ogg, | ||||
|     KnownMimeTypes.Video.Webm, | ||||
|     KnownMimeTypes.Video.Quicktime, | ||||
|     KnownMimeTypes.Video.XSgiMovie, | ||||
|     KnownMimeTypes.Video.VndVivo, | ||||
|     KnownMimeTypes.XConference.XCooltalk, | ||||
| ) | ||||
| @@ -0,0 +1,8 @@ | ||||
| package dev.inmo.micro_utils.mime_types | ||||
|  | ||||
| import kotlinx.serialization.Serializable | ||||
|  | ||||
| @Serializable(MimeTypeSerializer::class) | ||||
| interface MimeType { | ||||
|     val raw: String | ||||
| } | ||||
| @@ -0,0 +1,24 @@ | ||||
| package dev.inmo.micro_utils.mime_types | ||||
|  | ||||
| import kotlinx.serialization.KSerializer | ||||
| import kotlinx.serialization.Serializer | ||||
| import kotlinx.serialization.descriptors.PrimitiveKind | ||||
| import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor | ||||
| import kotlinx.serialization.descriptors.SerialDescriptor | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
|  | ||||
| @Suppress("OPT_IN_USAGE") | ||||
| @Serializer(MimeType::class) | ||||
| object MimeTypeSerializer : KSerializer<MimeType> { | ||||
|     override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING) | ||||
|  | ||||
|     override fun deserialize(decoder: Decoder): MimeType { | ||||
|         val mimeType = decoder.decodeString() | ||||
|         return mimeType(mimeType) | ||||
|     } | ||||
|  | ||||
|     override fun serialize(encoder: Encoder, value: MimeType) { | ||||
|         encoder.encodeString(value.raw) | ||||
|     } | ||||
| } | ||||
| @@ -1,10 +1,5 @@ | ||||
| package dev.inmo.micro_utils.mime_types | ||||
|  | ||||
| import kotlinx.serialization.* | ||||
| import kotlinx.serialization.descriptors.* | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
|  | ||||
| private val mimesCache = mutableMapOf<String, MimeType>().also { | ||||
|     knownMimeTypes.forEach { mimeType -> it[mimeType.raw] = mimeType } | ||||
| } | ||||
| @@ -15,17 +10,3 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) { | ||||
|  | ||||
| internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw) | ||||
|  | ||||
| @Suppress("OPT_IN_USAGE") | ||||
| @Serializer(MimeType::class) | ||||
| object MimeTypeSerializer : KSerializer<MimeType> { | ||||
|     override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING) | ||||
|  | ||||
|     override fun deserialize(decoder: Decoder): MimeType { | ||||
|         val mimeType = decoder.decodeString() | ||||
|         return mimeType(mimeType) | ||||
|     } | ||||
|  | ||||
|     override fun serialize(encoder: Encoder, value: MimeType) { | ||||
|         encoder.encodeString(value.raw) | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										49
									
								
								mppJsAndJavaProject.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								mppJsAndJavaProject.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| project.version = "$version" | ||||
| project.group = "$group" | ||||
|  | ||||
| apply from: "$publishGradlePath" | ||||
|  | ||||
| kotlin { | ||||
|     jvm { | ||||
|         compilations.main { | ||||
|             kotlinOptions { | ||||
|                 jvmTarget = "1.8" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     js (IR) { | ||||
|         browser() | ||||
|         nodejs() | ||||
|     } | ||||
|  | ||||
|     sourceSets { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 implementation kotlin('stdlib') | ||||
|             } | ||||
|         } | ||||
|         commonTest { | ||||
|             dependencies { | ||||
|                 implementation kotlin('test-common') | ||||
|                 implementation kotlin('test-annotations-common') | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         jvmTest { | ||||
|             dependencies { | ||||
|                 implementation kotlin('test-junit') | ||||
|             } | ||||
|         } | ||||
|         jsTest { | ||||
|             dependencies { | ||||
|                 implementation kotlin('test-js') | ||||
|                 implementation kotlin('test-junit') | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| java { | ||||
|     sourceCompatibility = JavaVersion.VERSION_1_8 | ||||
|     targetCompatibility = JavaVersion.VERSION_1_8 | ||||
| } | ||||
| @@ -23,7 +23,7 @@ kotlin { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 implementation kotlin('stdlib') | ||||
|                 implementation libs.kt.serialization | ||||
|                 api libs.kt.serialization | ||||
|             } | ||||
|         } | ||||
|         commonTest { | ||||
|   | ||||
| @@ -23,7 +23,7 @@ kotlin { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 implementation kotlin('stdlib') | ||||
|                 implementation libs.kt.serialization | ||||
|                 api libs.kt.serialization | ||||
|                 implementation compose.runtime | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -33,18 +33,6 @@ data class PaginationResult<T>( | ||||
|         results, | ||||
|         (pagesNumber * size).toLong() | ||||
|     ) | ||||
|     @Deprecated("Replace with The other order of incoming parameters or objectsCount parameter") | ||||
|     constructor( | ||||
|         page: Int, | ||||
|         pagesNumber: Int, | ||||
|         results: List<T>, | ||||
|         size: Int | ||||
|     ) : this( | ||||
|         page, | ||||
|         results, | ||||
|         pagesNumber, | ||||
|         size | ||||
|     ) | ||||
| } | ||||
|  | ||||
| fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L) | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user