mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-11-04 06:00:22 +00:00 
			
		
		
		
	Compare commits
	
		
			297 Commits
		
	
	
		
			v0.12.12
			...
			revert-245
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 940ee3df06 | |||
| 2e7bab10fd | |||
| 
						 | 
					3ed70a37ea | ||
| fe8f80b9d9 | |||
| d81fb32fb9 | |||
| 2877b5532c | |||
| b938b21395 | |||
| 58836359cc | |||
| 5edb0e1331 | |||
| 0f0d0b5d58 | |||
| 46c1887cbe | |||
| 5f231c2212 | |||
| 4e97ce86aa | |||
| 315a7cb29e | |||
| aa7cc503f2 | |||
| 4bbe7e5a80 | |||
| d9c05f38d2 | |||
| cd0c4c9650 | |||
| fc3407f104 | |||
| 3a5544206b | |||
| e17e2f7fb8 | |||
| 
						 | 
					d32c95f143 | ||
| 6d8a8ab018 | |||
| a7dce8fa78 | |||
| ca73ff8e19 | |||
| d01ad10d7d | |||
| 81041ee43c | |||
| 
						 | 
					6e004c2ae4 | ||
| 0e2fac5b22 | |||
| 269da7f155 | |||
| 3cb6b73ee0 | |||
| a938ee1efb | |||
| 6ea5e2e5a6 | |||
| 617dfb54e0 | |||
| d23e005985 | |||
| e5207f5bc5 | |||
| c96cea8db0 | |||
| 0a8e71d76a | |||
| cf1fd32b08 | |||
| cc4224ea1f | |||
| f4c148bc58 | |||
| 022297ad3f | |||
| 5180d6fc3e | |||
| eeebbff70d | |||
| afc6aeea15 | |||
| 486515eddd | |||
| 0e21699cd1 | |||
| f1678ef7cf | |||
| cea65fc76e | |||
| c9e320b72a | |||
| 555956087d | |||
| b3f468f901 | |||
| f5f7511781 | |||
| 4be1d93f60 | |||
| 7d684608ef | |||
| 2c7fd320eb | |||
| 88ee82e1c6 | |||
| d6402c624e | |||
| 8b9c93bc10 | |||
| 4f5e261d01 | |||
| cf455aebe6 | |||
| 1380d5f8e1 | |||
| 5f65260bfe | |||
| 11f0dcfc01 | |||
| 555b7886a4 | |||
| 3707a6c0ce | |||
| 4c8d92b4c3 | |||
| 8bbd33c896 | |||
| ac33a3580f | |||
| a64a32fbe6 | |||
| 9493e97a38 | |||
| 88bd770260 | |||
| a7bd33b7bf | |||
| 73c724a2e5 | |||
| d8cf3c6726 | |||
| 15dee238b5 | |||
| c70626734e | |||
| 5a765ea1bc | |||
| 8215f9d2c6 | |||
| d2e6d2ec80 | |||
| 3718181e8f | |||
| 0d825cf424 | |||
| 28a804d988 | |||
| 9e4bb9d678 | |||
| 9c40d7da3d | |||
| 2b76ad0aa9 | |||
| e4b619e050 | |||
| 36c09feaf2 | |||
| 2d68321503 | |||
| 85455ab21c | |||
| 18d63eb980 | |||
| 2e429e9704 | |||
| f4af28059b | |||
| c1476bd075 | |||
| 16c720fddd | |||
| 8b4b4a5eca | |||
| 32e6e5b7e2 | |||
| a9f7fd8e32 | |||
| 95be1a26f2 | |||
| ef9b31aee0 | |||
| df3c01ff0a | |||
| 4704c5a33d | |||
| 225c06550a | |||
| f0987614c6 | |||
| 269c2876f3 | |||
| 168d6acf7c | |||
| a5f718e257 | |||
| 4f68459582 | |||
| 442db122cf | |||
| 580d757be2 | |||
| 47b0f6d2d8 | |||
| 3f6f6ebc2b | |||
| 2645ea29d6 | |||
| 79f2041565 | |||
| 4a7567f288 | |||
| 8a890ed6ed | |||
| 3d90df6897 | |||
| 681c13144a | |||
| b64f2e6d32 | |||
| 428eabb1bd | |||
| 2162e83bce | |||
| 6142022283 | |||
| e6d9c8250f | |||
| 46178e723b | |||
| 605f55acd2 | |||
| 0f8b69aa60 | |||
| 551d8ec480 | |||
| fc48446ec4 | |||
| 3644b83ac6 | |||
| cd73791b6f | |||
| 03de71df2e | |||
| 83d5d3faf4 | |||
| 0c8bec4c89 | |||
| 7fc93817c1 | |||
| d0a00031a1 | |||
| 6ebc5aa0c2 | |||
| 8a6b4bb49e | |||
| 20799b9a3e | |||
| ec3afc615c | |||
| da692ccfc3 | |||
| 53b89f3a18 | |||
| 58cded28d3 | |||
| 592c5f3732 | |||
| f44a78a5f5 | |||
| e0bdd5dfdc | |||
| 99c0f06b72 | |||
| 66fc6df3d7 | |||
| a36425a905 | |||
| d920fee6d4 | |||
| 23590be5de | |||
| 94acc3c93b | |||
| 5616326a3b | |||
| 7601860c5c | |||
| 8b43d785cc | |||
| b62d3a0b7d | |||
| fad73c7213 | |||
| 2403c7c2b0 | |||
| fa090bf920 | |||
| a83ee86340 | |||
| 204955bcce | |||
| ee56e9543a | |||
| 96fdff6ffd | |||
| 58b007cbb3 | |||
| 4f0c139889 | |||
| c584c24fce | |||
| 85e5cee154 | |||
| 5af91981f1 | |||
| 2fe4f08059 | |||
| 83796f345a | |||
| c1e21364a6 | |||
| 067d9d0d3b | |||
| 03f527d83e | |||
| ced05a4586 | |||
| 43fe06206a | |||
| 023657558e | |||
| 9b0b726c80 | |||
| 4ee67321c4 | |||
| 59f1f2e59b | |||
| 0766d48b7c | |||
| e18903b9e9 | |||
| d0eecdead2 | |||
| cc4a83a033 | |||
| 1cf911bbde | |||
| 36d73d5023 | |||
| c395242e3e | |||
| cd9cd7cc5d | |||
| acbb8a0c07 | |||
| b9d8528599 | |||
| 4971326eca | |||
| 09d1047260 | |||
| 02dbd493c2 | |||
| b17931e7bd | |||
| 2a4570eafc | |||
| c9514d3a6d | |||
| 072805efc7 | |||
| 369ff26627 | |||
| c5abbbbd2d | |||
| d974639f1e | |||
| 26efde316b | |||
| fafe50f80a | |||
| 41504469db | |||
| 03b3ddd98b | |||
| 89d919f2be | |||
| b53cfd5504 | |||
| 31022733ac | |||
| f9a8c39879 | |||
| a812c2dd2f | |||
| 217e977f0d | |||
| 04c301d1ac | |||
| 7f0c425389 | |||
| 1ede1c423b | |||
| 7cb064896a | |||
| 0c5e2862ca | |||
| 30d4507f54 | |||
| b6c6d89455 | |||
| 9d218ee534 | |||
| 11116d8cab | |||
| 58d754bbde | |||
| 8f25c123dd | |||
| 76e214fc08 | |||
| 2b5380f8d6 | |||
| 844a129094 | |||
| a3090cca9d | |||
| b7b5159e9c | |||
| 0f8bc2c950 | |||
| 69f5c49f45 | |||
| 9b308e6fb8 | |||
| 3e3f91128b | |||
| 0d1aae0ef7 | |||
| 4d022f0480 | |||
| 153e20d00e | |||
| a9a8171dd6 | |||
| bf5c3b59b2 | |||
| 607c432bdb | |||
| ae5c010770 | |||
| d5e432437f | |||
| 9f99ebce01 | |||
| 64ee899b84 | |||
| e0e0c1658b | |||
| 2c586f667c | |||
| 64164ef6c1 | |||
| 22343c0731 | |||
| f4ec1a4c60 | |||
| c1c33cceb1 | |||
| a3e975b2ba | |||
| 06e705a687 | |||
| d56eb6c867 | |||
| 9cbca864e3 | |||
| abb4378694 | |||
| 0eb698d9a4 | |||
| 15ea9f2093 | |||
| d47aca0923 | |||
| 1ac50e9959 | |||
| 6adfbe3a96 | |||
| 59f36e62e9 | |||
| 54af116009 | |||
| 38fbec8e3b | |||
| babbfc55e4 | |||
| 2511e18d69 | |||
| 29658c70a0 | |||
| 96311ee43d | |||
| bd33b09052 | |||
| 8055003b47 | |||
| 1257492f85 | |||
| 1107b7f4ef | |||
| a1a1171240 | |||
| 46c02e5df1 | |||
| 2e9efc57de | |||
| acecadef17 | |||
| 19394b5e69 | |||
| 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
 | 
					      - uses: actions/setup-java@v1
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          java-version: 11
 | 
					          java-version: 11
 | 
				
			||||||
      - name: Fix android 32.0.0 dx
 | 
					 | 
				
			||||||
        continue-on-error: true
 | 
					 | 
				
			||||||
        run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib  && mv d8.jar dx.jar
 | 
					 | 
				
			||||||
      - name: Rewrite version
 | 
					      - name: Rewrite version
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
 | 
					          branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
 | 
				
			||||||
@@ -20,9 +17,8 @@ jobs:
 | 
				
			|||||||
          mv gradle.properties.tmp gradle.properties
 | 
					          mv gradle.properties.tmp gradle.properties
 | 
				
			||||||
      - name: Build
 | 
					      - name: Build
 | 
				
			||||||
        run: ./gradlew build
 | 
					        run: ./gradlew build
 | 
				
			||||||
#      - name: Publish
 | 
					      - name: Publish
 | 
				
			||||||
#        continue-on-error: true
 | 
					        continue-on-error: true
 | 
				
			||||||
#        run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
 | 
					        run: ./gradlew publishAllPublicationsToGiteaRepository
 | 
				
			||||||
#        env:
 | 
					        env:
 | 
				
			||||||
#          GITHUBPACKAGES_USER: ${{ github.actor }}
 | 
					          GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
 | 
				
			||||||
#          GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/dokka_push.yml
									
									
									
									
										vendored
									
									
								
							@@ -11,9 +11,6 @@ jobs:
 | 
				
			|||||||
      - uses: actions/setup-java@v1
 | 
					      - uses: actions/setup-java@v1
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          java-version: 11
 | 
					          java-version: 11
 | 
				
			||||||
      - name: Fix android 32.0.0 dx
 | 
					 | 
				
			||||||
        continue-on-error: true
 | 
					 | 
				
			||||||
        run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib  && mv d8.jar dx.jar
 | 
					 | 
				
			||||||
      - name: Build
 | 
					      - name: Build
 | 
				
			||||||
        run: ./gradlew build && ./gradlew dokkaHtml
 | 
					        run: ./gradlew build && ./gradlew dokkaHtml
 | 
				
			||||||
      - name: Publish KDocs
 | 
					      - name: Publish KDocs
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +0,0 @@
 | 
				
			|||||||
job("Build and run tests") {
 | 
					 | 
				
			||||||
    container(displayName = "Run gradle build", image = "openjdk:11") {
 | 
					 | 
				
			||||||
        kotlinScript { api ->
 | 
					 | 
				
			||||||
            // here can be your complex logic
 | 
					 | 
				
			||||||
            api.gradlew("build")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										358
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										358
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,5 +1,363 @@
 | 
				
			|||||||
# Changelog
 | 
					# Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.18.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Android Fragments`: `1.5.6` -> `1.5.7`
 | 
				
			||||||
 | 
					* `Ktor`:
 | 
				
			||||||
 | 
					    * `Server`:
 | 
				
			||||||
 | 
					        * Now it is possible to take query parameters as list
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `Common`:
 | 
				
			||||||
 | 
					        * New `WriteKeyValuesRepo.removeWithValue`
 | 
				
			||||||
 | 
					    * `Cache`:
 | 
				
			||||||
 | 
					        * Rename full caching factories functions to `fullyCached`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.17.8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Ktor`: `2.2.4` -> `2.3.0`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.17.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Android CoreKtx`: `1.9.0` -> `1.10.0`
 | 
				
			||||||
 | 
					* `Startup`:
 | 
				
			||||||
 | 
					    * Add support of `linuxX64` and `mingwX64` platforms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.17.6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Kotlin`: `1.8.10` -> `1.8.20`
 | 
				
			||||||
 | 
					    * `KSLog`: `1.0.0` -> `1.1.1`
 | 
				
			||||||
 | 
					    * `Compose`: `1.3.1` -> `1.4.0`
 | 
				
			||||||
 | 
					    * `Koin`: `3.3.2` -> `3.4.0`
 | 
				
			||||||
 | 
					    * `RecyclerView`: `1.2.1` -> `1.3.0`
 | 
				
			||||||
 | 
					    * `Fragment`: `1.5.5` -> `1.5.6`
 | 
				
			||||||
 | 
					* Experimentally (`!!!`) add `linuxX64` and `mingwX64` targets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.17.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Common`:
 | 
				
			||||||
 | 
					    * Conversations of number primitives with bounds care
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `Common`:
 | 
				
			||||||
 | 
					        * By default, `getAll` for repos will take all the size of repo as page size
 | 
				
			||||||
 | 
					        * New extension for all built-in repos `maxPagePagination`
 | 
				
			||||||
 | 
					    * All the repos got `getAll` functions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.17.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Serialization`:
 | 
				
			||||||
 | 
					    * `Mapper`:
 | 
				
			||||||
 | 
					        * Module inited
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Compose`: `1.3.1-rc02` -> `1.3.1`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.17.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Common`:
 | 
				
			||||||
 | 
					    * Add `fixed` extensions for `Float` and `Double`
 | 
				
			||||||
 | 
					    * New function `emptyDiff`
 | 
				
			||||||
 | 
					    * Now you may pass custom `comparisonFun` to all `diff` functions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.17.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `FSM`:
 | 
				
			||||||
 | 
					    * `DefaultStatesManager.onUpdateContextsConflictResolver` and `DefaultStatesManager.onStartContextsConflictResolver` now return `false` by default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.17.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **Hotfix** for absence of jvm dependencies in android modules
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Ktor`: `2.2.3` -> `2.2.4`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.17.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Kotlin`: `1.7.20` -> `1.8.10`
 | 
				
			||||||
 | 
					    * `Serialization`: `1.4.1` -> `1.5.0`
 | 
				
			||||||
 | 
					    * `KSLog`: `0.5.4` -> `1.0.0`
 | 
				
			||||||
 | 
					    * `AppCompat`: `1.6.0` -> `1.6.1`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.13
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `Generator`:
 | 
				
			||||||
 | 
					        * Module has been created
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `Exposed`:
 | 
				
			||||||
 | 
					        * `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach
 | 
				
			||||||
 | 
					* `Koin`:
 | 
				
			||||||
 | 
					    * `Generator`:
 | 
				
			||||||
 | 
					        * Module has been created
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.11
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `LanguageCodes`:
 | 
				
			||||||
 | 
					    * In android and JVM targets now available `toJavaLocale` and from Java `Locale` conversations from/to
 | 
				
			||||||
 | 
					      `IetfLanguageCode`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `Cache`:
 | 
				
			||||||
 | 
					        * New transformer type: `ReadCRUDFromKeyValueRepo`
 | 
				
			||||||
 | 
					        * New transformer type: `ReadKeyValueFromCRUDRepo`
 | 
				
			||||||
 | 
					* `Pagination`:
 | 
				
			||||||
 | 
					    * New `paginate` extensions with `reversed` support for `List`/`Set`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.9
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Koin`: `3.2.2` -> `3.3.2`
 | 
				
			||||||
 | 
					    * `AppCompat`: `1.5.1` -> `1.6.0`
 | 
				
			||||||
 | 
					* `Ktor`:
 | 
				
			||||||
 | 
					    * `Client`
 | 
				
			||||||
 | 
					        * `HttpResponse.bodyOrNull` now retrieve callback to check if body should be received or null
 | 
				
			||||||
 | 
					        * New extension `HttpResponse.bodyOrNullOnNoContent`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Ktor`: `2.2.2` -> `2.2.3`
 | 
				
			||||||
 | 
					* `Ktor`:
 | 
				
			||||||
 | 
					    * `Client`
 | 
				
			||||||
 | 
					        * Fixes in `HttpClient.uniUpload`
 | 
				
			||||||
 | 
					    * `Server`
 | 
				
			||||||
 | 
					        * Fixes in `PartData.FileItem.download`
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `Cache`:
 | 
				
			||||||
 | 
					        * New type of caches: `FallbackCacheRepo`
 | 
				
			||||||
 | 
					        * Fixes in `Write*` variants of cached repos
 | 
				
			||||||
 | 
					        * New type `ActionWrapper`
 | 
				
			||||||
 | 
					        * New `AutoRecache*` classes for all types of repos as `FallbackCacheRepo`s
 | 
				
			||||||
 | 
					    * `Common`:
 | 
				
			||||||
 | 
					        * New transformations for key-value and key-values vice-verse
 | 
				
			||||||
 | 
					        * Fixes in `FileReadKeyValueRepo`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Common`:
 | 
				
			||||||
 | 
					    * New extensions `ifTrue`/`ifFalse`/`alsoIfTrue`/`alsoIfFalse`/`letIfTrue`/`letIfFalse`
 | 
				
			||||||
 | 
					    * `Diff` now is serializable
 | 
				
			||||||
 | 
					    * Add `IndexedValue` serializer
 | 
				
			||||||
 | 
					    * `repeatOnFailure` extending: now you may pass any lambda to check if continue to try/do something
 | 
				
			||||||
 | 
					    * `Compose`:
 | 
				
			||||||
 | 
					        * New extension `MutableState.asState`
 | 
				
			||||||
 | 
					* `Coroutines`:
 | 
				
			||||||
 | 
					    * `Compose`:
 | 
				
			||||||
 | 
					        * All the `Flow` conversations to compose `State`/`MutableState`/`SnapshotStateList`/`List` got several new
 | 
				
			||||||
 | 
					        parameters
 | 
				
			||||||
 | 
					        * `Flow.toMutableState` now is deprecated in favor to `asMutableComposeState`
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `Cache`:
 | 
				
			||||||
 | 
					        * New type `FullCacheRepo`
 | 
				
			||||||
 | 
					        * New type `CommonCacheRepo`
 | 
				
			||||||
 | 
					        * `CacheRepo` got `invalidate` method. It will fully reload `FullCacheRepo` and just clear `CommonCacheRepo`
 | 
				
			||||||
 | 
					        * New extensions `KVCache.actualizeAll`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Startup`:
 | 
				
			||||||
 | 
					    * `Launcher`:
 | 
				
			||||||
 | 
					        * Improvements in `StartLauncherPlugin#start` methods
 | 
				
			||||||
 | 
					        * Add opportunity to pass second argument on `JVM` platform as log level
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `Ktor`:
 | 
				
			||||||
 | 
					        * `Client`:
 | 
				
			||||||
 | 
					            * All clients repos got opportunity to customize their flows
 | 
				
			||||||
 | 
					    * `Exposed`:
 | 
				
			||||||
 | 
					        * Extensions `eqOrIsNull` and `neqOrIsNotNull` for `Column`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Ktor`: `2.2.1` -> `2.2.2`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Coroutines`:
 | 
				
			||||||
 | 
					    * Create `launchInCurrentThread`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Startup`:
 | 
				
			||||||
 | 
					    * `Launcher`:
 | 
				
			||||||
 | 
					        * All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Compose`: `1.2.1` -> `1.2.2`
 | 
				
			||||||
 | 
					* `Startup`:
 | 
				
			||||||
 | 
					    * Module become available on `JS` target
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Coroutines`:
 | 
				
			||||||
 | 
					    * New `runCatchingSafely`/`safelyWithResult` with receivers
 | 
				
			||||||
 | 
					* `SafeWrapper`:
 | 
				
			||||||
 | 
					    * Module inited
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.16.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Ktor`: `2.1.3` -> `2.2.1`
 | 
				
			||||||
 | 
					    * `Android Fragment`: `1.5.3` -> `1.5.5`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.15.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Startup`:
 | 
				
			||||||
 | 
					    * Inited :)
 | 
				
			||||||
 | 
					    * `Plugin`:
 | 
				
			||||||
 | 
					        * Inited :)
 | 
				
			||||||
 | 
					    * `Launcher`:
 | 
				
			||||||
 | 
					        * Inited :)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.15.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `CRUD`:
 | 
				
			||||||
 | 
					        * `Common`:
 | 
				
			||||||
 | 
					            * New method `ReadCRUDRepo#getIdsByPagination`
 | 
				
			||||||
 | 
					        * `Android`:
 | 
				
			||||||
 | 
					            * `AbstractAndroidCRUDRepo` got new abstract method `toId`
 | 
				
			||||||
 | 
					        * `Exposed`:
 | 
				
			||||||
 | 
					            * `CommonExposedRepo` new abstract property `asId`
 | 
				
			||||||
 | 
					        * `Ktor`:
 | 
				
			||||||
 | 
					            * `Client`:
 | 
				
			||||||
 | 
					                * `KtorReadCRUDRepoClient` now requires `paginationIdType`
 | 
				
			||||||
 | 
					* `LanguageCodes`:
 | 
				
			||||||
 | 
					    * Updates and fixes in generation
 | 
				
			||||||
 | 
					* `MimeTypes`:
 | 
				
			||||||
 | 
					    * Updates and fixes in generation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.14.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Common`:
 | 
				
			||||||
 | 
					    * `JVM`:
 | 
				
			||||||
 | 
					        * New extension `downloadToTempFile`
 | 
				
			||||||
 | 
					* `Ktor`:
 | 
				
			||||||
 | 
					    * `Server`:
 | 
				
			||||||
 | 
					        * Small fix in `handleUniUpload`
 | 
				
			||||||
 | 
					        * `ApplicationCall#uniloadMultipartFile` now uses `uniloadMultipart`
 | 
				
			||||||
 | 
					    * `Common`:
 | 
				
			||||||
 | 
					        * New extension `downloadToTempFile`
 | 
				
			||||||
 | 
					    * `Client`:
 | 
				
			||||||
 | 
					        * New extensions on top of `uniUpload`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.14.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Common`:
 | 
				
			||||||
 | 
					    * New type `Progress`
 | 
				
			||||||
 | 
					* `Ktor`:
 | 
				
			||||||
 | 
					    * `Client`:
 | 
				
			||||||
 | 
					        * New universal `uniUpload` extension for `HttpClient`
 | 
				
			||||||
 | 
					    * `Server`:
 | 
				
			||||||
 | 
					        * New universal `handleUniUpload` extension for `ApplicationCall`
 | 
				
			||||||
 | 
					        * Add extensions `download` and `downloadToTemporalFile`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.14.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Exposed`: `0.40.1` -> `0.41.1`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.14.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Klock`: `3.3.1` -> `3.4.0`
 | 
				
			||||||
 | 
					    * `UUID`: `0.5.0` -> `0.6.0`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.14.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**ALL DEPRECATIONS HAVE BEEN REMOVED**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Kotlin`: `1.7.10` -> `1.7.20`
 | 
				
			||||||
 | 
					    * `Klock`: `3.3.0` -> `3.3.1`
 | 
				
			||||||
 | 
					    * `Compose`: `1.2.0` -> `1.2.1`
 | 
				
			||||||
 | 
					    * `Exposed`: `0.39.2` -> `0.40.1`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.13.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `Klock`: `3.1.0` -> `3.3.0`
 | 
				
			||||||
 | 
					    * `Ktor`: `2.1.2` -> `2.1.3`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.13.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					    * `Exposed`:
 | 
				
			||||||
 | 
					      * `AbstractExposedWriteCRUDRepo#createAndInsertId` now is optional and returns nullable value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.13.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**ALL DEPRECATIONS HAVE BEEN REMOVED**
 | 
				
			||||||
 | 
					**A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Repos`:
 | 
				
			||||||
 | 
					  * `Exposed`:
 | 
				
			||||||
 | 
					    * `AbstractExposedWriteCRUDRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `createAndInsertId`
 | 
				
			||||||
 | 
					      * Old `update` method has been deprecated and not recommended to override anymore in realizations
 | 
				
			||||||
 | 
					      * Old `insert` method now is `open` instead of `abstract` and can be omitted
 | 
				
			||||||
 | 
					    * `AbstractExposedKeyValueRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `insertKey`
 | 
				
			||||||
 | 
					      * Old `update` method has been deprecated and not recommended to override anymore
 | 
				
			||||||
 | 
					      * Old `insert` method now is `open` instead of `abstract` and can be omitted in realizations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.12.17
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `Versions`:
 | 
				
			||||||
 | 
					    * `JB Compose`: `1.2.0-alpha01-dev774` -> `1.2.0-beta02`
 | 
				
			||||||
 | 
					    * `Ktor`: `2.1.1` -> `2.1.2`
 | 
				
			||||||
 | 
					    * `Koin`: `3.2.0` -> `3.2.2`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.12.16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `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
 | 
					## 0.12.12
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* `Common`:
 | 
					* `Common`:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ buildscript {
 | 
				
			|||||||
    dependencies {
 | 
					    dependencies {
 | 
				
			||||||
        classpath libs.buildscript.kt.gradle
 | 
					        classpath libs.buildscript.kt.gradle
 | 
				
			||||||
        classpath libs.buildscript.kt.serialization
 | 
					        classpath libs.buildscript.kt.serialization
 | 
				
			||||||
 | 
					        classpath libs.buildscript.kt.ksp
 | 
				
			||||||
        classpath libs.buildscript.jb.dokka
 | 
					        classpath libs.buildscript.jb.dokka
 | 
				
			||||||
        classpath libs.buildscript.gh.release
 | 
					        classpath libs.buildscript.gh.release
 | 
				
			||||||
        classpath libs.buildscript.android.gradle
 | 
					        classpath libs.buildscript.android.gradle
 | 
				
			||||||
@@ -22,6 +23,7 @@ allprojects {
 | 
				
			|||||||
        mavenCentral()
 | 
					        mavenCentral()
 | 
				
			||||||
        google()
 | 
					        google()
 | 
				
			||||||
        maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
 | 
					        maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
 | 
				
			||||||
 | 
					        maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // temporal crutch until legacy tests will be stabled or legacy target will be removed
 | 
					    // temporal crutch until legacy tests will be stabled or legacy target will be removed
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,19 @@ kotlin {
 | 
				
			|||||||
        androidMain {
 | 
					        androidMain {
 | 
				
			||||||
            dependencies {
 | 
					            dependencies {
 | 
				
			||||||
                api project(":micro_utils.coroutines")
 | 
					                api project(":micro_utils.coroutines")
 | 
				
			||||||
 | 
					                api libs.android.fragment
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            dependsOn jvmMain
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        linuxX64Main {
 | 
				
			||||||
 | 
					            dependencies {
 | 
				
			||||||
 | 
					                api libs.okio
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        mingwX64Main {
 | 
				
			||||||
 | 
					            dependencies {
 | 
				
			||||||
 | 
					                api libs.okio
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common.compose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.compose.runtime.MutableState
 | 
				
			||||||
 | 
					import androidx.compose.runtime.State
 | 
				
			||||||
 | 
					import androidx.compose.runtime.derivedStateOf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Converts current [MutableState] to immutable [State] using [derivedStateOf]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					fun <T> MutableState<T>.asState(): State<T> = derivedStateOf { this.value }
 | 
				
			||||||
@@ -2,6 +2,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package dev.inmo.micro_utils.common
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private inline fun <T> getObject(
 | 
					private inline fun <T> getObject(
 | 
				
			||||||
    additional: MutableList<T>,
 | 
					    additional: MutableList<T>,
 | 
				
			||||||
    iterator: Iterator<T>
 | 
					    iterator: Iterator<T>
 | 
				
			||||||
@@ -14,16 +16,29 @@ private inline fun <T> getObject(
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Diff object which contains information about differences between two [Iterable]s
 | 
					 * Diff object which contains information about differences between two [Iterable]s
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 | 
					 * See tests for more info
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection
 | 
				
			||||||
 | 
					 * @param added The object which appear in new collection only. Indexes here show the index in the new collection
 | 
				
			||||||
 | 
					 * @param replaced Pair of old-new changes. First object has been presented in the old collection on its
 | 
				
			||||||
 | 
					 * [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in
 | 
				
			||||||
 | 
					 * case when some value has been replaced after adds or removes in original collection the object index will be changed
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 * @see calculateDiff
 | 
					 * @see calculateDiff
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					@Serializable
 | 
				
			||||||
data class Diff<T> internal constructor(
 | 
					data class Diff<T> internal constructor(
 | 
				
			||||||
    val removed: List<IndexedValue<T>>,
 | 
					    val removed: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>,
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Old-New values pairs
 | 
					     * Old-New values pairs
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    val replaced: List<Pair<IndexedValue<T>, IndexedValue<T>>>,
 | 
					    val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>,
 | 
				
			||||||
    val added: List<IndexedValue<T>>
 | 
					    val added: List<@Serializable(IndexedValueSerializer::class) IndexedValue<T>>
 | 
				
			||||||
)
 | 
					) {
 | 
				
			||||||
 | 
					    fun isEmpty(): Boolean = removed.isEmpty() && replaced.isEmpty() && added.isEmpty()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun <T> emptyDiff(): Diff<T> = Diff(emptyList(), emptyList(), emptyList())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private inline fun <T> performChanges(
 | 
					private inline fun <T> performChanges(
 | 
				
			||||||
    potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
 | 
					    potentialChanges: MutableList<Pair<IndexedValue<T>?, IndexedValue<T>?>>,
 | 
				
			||||||
@@ -32,14 +47,14 @@ private inline fun <T> performChanges(
 | 
				
			|||||||
    changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
 | 
					    changedList: MutableList<Pair<IndexedValue<T>, IndexedValue<T>>>,
 | 
				
			||||||
    removedList: MutableList<IndexedValue<T>>,
 | 
					    removedList: MutableList<IndexedValue<T>>,
 | 
				
			||||||
    addedList: MutableList<IndexedValue<T>>,
 | 
					    addedList: MutableList<IndexedValue<T>>,
 | 
				
			||||||
    strictComparison: Boolean
 | 
					    comparisonFun: (T?, T?) -> Boolean
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    var i = -1
 | 
					    var i = -1
 | 
				
			||||||
    val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
 | 
					    val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return
 | 
				
			||||||
    for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
 | 
					    for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) {
 | 
				
			||||||
        i++
 | 
					        i++
 | 
				
			||||||
        val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison)
 | 
					        val oldOneEqualToNewObject = comparisonFun(old ?.value, newObject ?.value)
 | 
				
			||||||
        val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison)
 | 
					        val newOneEqualToOldObject = comparisonFun(new ?.value, oldObject ?.value)
 | 
				
			||||||
        if (oldOneEqualToNewObject || newOneEqualToOldObject) {
 | 
					        if (oldOneEqualToNewObject || newOneEqualToOldObject) {
 | 
				
			||||||
            changedList.addAll(
 | 
					            changedList.addAll(
 | 
				
			||||||
                potentialChanges.take(i).mapNotNull {
 | 
					                potentialChanges.take(i).mapNotNull {
 | 
				
			||||||
@@ -93,7 +108,7 @@ private inline fun <T> performChanges(
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
fun <T> Iterable<T>.calculateDiff(
 | 
					fun <T> Iterable<T>.calculateDiff(
 | 
				
			||||||
    other: Iterable<T>,
 | 
					    other: Iterable<T>,
 | 
				
			||||||
    strictComparison: Boolean = false
 | 
					    comparisonFun: (T?, T?) -> Boolean
 | 
				
			||||||
): Diff<T> {
 | 
					): Diff<T> {
 | 
				
			||||||
    var i = -1
 | 
					    var i = -1
 | 
				
			||||||
    var j = -1
 | 
					    var j = -1
 | 
				
			||||||
@@ -121,7 +136,7 @@ fun <T> Iterable<T>.calculateDiff(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        when {
 | 
					        when {
 | 
				
			||||||
            oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
 | 
					            comparisonFun(oldObject, newObject) -> {
 | 
				
			||||||
                changedObjects.addAll(potentiallyChangedObjects.map {
 | 
					                changedObjects.addAll(potentiallyChangedObjects.map {
 | 
				
			||||||
                    @Suppress("UNCHECKED_CAST")
 | 
					                    @Suppress("UNCHECKED_CAST")
 | 
				
			||||||
                    it as Pair<IndexedValue<T>, IndexedValue<T>>
 | 
					                    it as Pair<IndexedValue<T>, IndexedValue<T>>
 | 
				
			||||||
@@ -132,23 +147,49 @@ fun <T> Iterable<T>.calculateDiff(
 | 
				
			|||||||
                potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
 | 
					                potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) })
 | 
				
			||||||
                val previousOldsAdditionsSize = additionalInOld.size
 | 
					                val previousOldsAdditionsSize = additionalInOld.size
 | 
				
			||||||
                val previousNewsAdditionsSize = additionalInNew.size
 | 
					                val previousNewsAdditionsSize = additionalInNew.size
 | 
				
			||||||
                performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
 | 
					                performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
 | 
				
			||||||
                i -= (additionalInOld.size - previousOldsAdditionsSize)
 | 
					                i -= (additionalInOld.size - previousOldsAdditionsSize)
 | 
				
			||||||
                j -= (additionalInNew.size - previousNewsAdditionsSize)
 | 
					                j -= (additionalInNew.size - previousNewsAdditionsSize)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    potentiallyChangedObjects.add(null to null)
 | 
					    potentiallyChangedObjects.add(null to null)
 | 
				
			||||||
    performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison)
 | 
					    performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, comparisonFun)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
 | 
					    return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Calculating [Diff] object
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different
 | 
				
			||||||
 | 
					 * objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					fun <T> Iterable<T>.calculateDiff(
 | 
				
			||||||
 | 
					    other: Iterable<T>,
 | 
				
			||||||
 | 
					    strictComparison: Boolean = false
 | 
				
			||||||
 | 
					): Diff<T> = calculateDiff(
 | 
				
			||||||
 | 
					    other,
 | 
				
			||||||
 | 
					    comparisonFun = if (strictComparison) {
 | 
				
			||||||
 | 
					        { t1, t2 ->
 | 
				
			||||||
 | 
					            t1 === t2
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        { t1, t2 ->
 | 
				
			||||||
 | 
					            t1 === t2 || t1 == t2 // small optimization for cases when t1 and t2 are the same - comparison will be faster potentially
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
inline fun <T> Iterable<T>.diff(
 | 
					inline fun <T> Iterable<T>.diff(
 | 
				
			||||||
    other: Iterable<T>,
 | 
					    other: Iterable<T>,
 | 
				
			||||||
    strictComparison: Boolean = false
 | 
					    strictComparison: Boolean = false
 | 
				
			||||||
): Diff<T> = calculateDiff(other, strictComparison)
 | 
					): Diff<T> = calculateDiff(other, strictComparison)
 | 
				
			||||||
 | 
					inline fun <T> Iterable<T>.diff(
 | 
				
			||||||
 | 
					    other: Iterable<T>,
 | 
				
			||||||
 | 
					    noinline comparisonFun: (T?, T?) -> Boolean
 | 
				
			||||||
 | 
					): Diff<T> = calculateDiff(other, comparisonFun)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new)
 | 
					inline fun <T> Diff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, strictComparison = false)
 | 
				
			||||||
inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
 | 
					inline fun <T> StrictDiff(old: Iterable<T>, new: Iterable<T>) = old.calculateDiff(new, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -165,7 +206,26 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
 | 
				
			|||||||
fun <T> MutableList<T>.applyDiff(
 | 
					fun <T> MutableList<T>.applyDiff(
 | 
				
			||||||
    source: Iterable<T>,
 | 
					    source: Iterable<T>,
 | 
				
			||||||
    strictComparison: Boolean = false
 | 
					    strictComparison: Boolean = false
 | 
				
			||||||
) = calculateDiff(source, strictComparison).let {
 | 
					): Diff<T> = calculateDiff(source, strictComparison).also {
 | 
				
			||||||
 | 
					    for (i in it.removed.indices.sortedDescending()) {
 | 
				
			||||||
 | 
					        removeAt(it.removed[i].index)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    it.added.forEach { (i, t) ->
 | 
				
			||||||
 | 
					        add(i, t)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    it.replaced.forEach { (_, new) ->
 | 
				
			||||||
 | 
					        set(new.index, new.value)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this]
 | 
				
			||||||
 | 
					 * mutable list
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					fun <T> MutableList<T>.applyDiff(
 | 
				
			||||||
 | 
					    source: Iterable<T>,
 | 
				
			||||||
 | 
					    comparisonFun: (T?, T?) -> Boolean
 | 
				
			||||||
 | 
					): Diff<T> = calculateDiff(source, comparisonFun).also {
 | 
				
			||||||
    for (i in it.removed.indices.sortedDescending()) {
 | 
					    for (i in it.removed.indices.sortedDescending()) {
 | 
				
			||||||
        removeAt(it.removed[i].index)
 | 
					        removeAt(it.removed[i].index)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,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,28 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Convert [this] [Long] to [Int] with bounds of [Int.MIN_VALUE] and [Int.MAX_VALUE]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					fun Long.toCoercedInt(): Int = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt()
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Convert [this] [Long] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					fun Long.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()).toShort()
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Convert [this] [Long] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					fun Long.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()).toByte()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Convert [this] [Int] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					fun Int.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort()
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Convert [this] [Int] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					fun Int.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()).toByte()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Convert [this] [Short] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					fun Short.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toShort(), Byte.MAX_VALUE.toShort()).toByte()
 | 
				
			||||||
@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					import kotlin.jvm.JvmInline
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Serializable
 | 
				
			||||||
 | 
					@JvmInline
 | 
				
			||||||
 | 
					value class Progress private constructor(
 | 
				
			||||||
 | 
					    val of1: Double
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    val of1Float
 | 
				
			||||||
 | 
					        get() = of1.toFloat()
 | 
				
			||||||
 | 
					    val of100
 | 
				
			||||||
 | 
					        get() = of1 * 100
 | 
				
			||||||
 | 
					    val of100Float
 | 
				
			||||||
 | 
					        get() = of100.toFloat()
 | 
				
			||||||
 | 
					    val of100Int
 | 
				
			||||||
 | 
					        get() = of100.toInt()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init {
 | 
				
			||||||
 | 
					        require(of1 in rangeOfValues) {
 | 
				
			||||||
 | 
					            "Progress main value should be in $rangeOfValues, but incoming value is $of1"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        val rangeOfValues = 0.0 .. 1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val START = Progress(rangeOfValues.start)
 | 
				
			||||||
 | 
					        val COMPLETED = Progress(rangeOfValues.endInclusive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        operator fun invoke(of1: Double) = Progress(of1.coerceIn(rangeOfValues))
 | 
				
			||||||
 | 
					        operator fun invoke(part: Number, total: Number) = Progress(
 | 
				
			||||||
 | 
					            part.toDouble() / total.toDouble()
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					@file:Suppress(
 | 
				
			||||||
 | 
					  "RemoveRedundantCallsOfConversionMethods",
 | 
				
			||||||
 | 
					  "RedundantVisibilityModifier",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlin.Byte
 | 
				
			||||||
 | 
					import kotlin.Double
 | 
				
			||||||
 | 
					import kotlin.Float
 | 
				
			||||||
 | 
					import kotlin.Int
 | 
				
			||||||
 | 
					import kotlin.Long
 | 
				
			||||||
 | 
					import kotlin.Short
 | 
				
			||||||
 | 
					import kotlin.Suppress
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.plus(other: Progress): Progress = Progress(of1 + other.of1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.minus(other: Progress): Progress = Progress(of1 - other.of1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.plus(i: Byte): Progress = Progress((of1 + i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.minus(i: Byte): Progress = Progress((of1 - i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.times(i: Byte): Progress = Progress((of1 * i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.div(i: Byte): Progress = Progress((of1 / i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.rem(i: Byte): Progress = Progress((of1 % i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.plus(i: Short): Progress = Progress((of1 + i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.minus(i: Short): Progress = Progress((of1 - i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.times(i: Short): Progress = Progress((of1 * i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.div(i: Short): Progress = Progress((of1 / i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.rem(i: Short): Progress = Progress((of1 % i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.plus(i: Int): Progress = Progress((of1 + i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.minus(i: Int): Progress = Progress((of1 - i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.times(i: Int): Progress = Progress((of1 * i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.div(i: Int): Progress = Progress((of1 / i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.rem(i: Int): Progress = Progress((of1 % i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.plus(i: Long): Progress = Progress((of1 + i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.minus(i: Long): Progress = Progress((of1 - i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.times(i: Long): Progress = Progress((of1 * i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.div(i: Long): Progress = Progress((of1 / i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.rem(i: Long): Progress = Progress((of1 % i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.plus(i: Float): Progress = Progress((of1 + i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.minus(i: Float): Progress = Progress((of1 - i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.times(i: Float): Progress = Progress((of1 * i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.div(i: Float): Progress = Progress((of1 / i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.rem(i: Float): Progress = Progress((of1 % i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.plus(i: Double): Progress = Progress((of1 + i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.minus(i: Double): Progress = Progress((of1 - i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.times(i: Double): Progress = Progress((of1 * i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.div(i: Double): Progress = Progress((of1 / i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.rem(i: Double): Progress = Progress((of1 % i).toDouble())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public operator fun Progress.compareTo(other: Progress): Int = (of1 - other.of1).toInt()
 | 
				
			||||||
@@ -1,5 +1,27 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.common
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Executes the given [action] until getting of successful result specified number of [times].
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * A zero-based index of current iteration is passed as a parameter to [action].
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					inline fun <R> repeatOnFailure(
 | 
				
			||||||
 | 
					    onFailure: (Throwable) -> Boolean,
 | 
				
			||||||
 | 
					    action: () -> R
 | 
				
			||||||
 | 
					): Result<R> {
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        runCatching {
 | 
				
			||||||
 | 
					            action()
 | 
				
			||||||
 | 
					        }.onFailure {
 | 
				
			||||||
 | 
					            if (!onFailure(it)) {
 | 
				
			||||||
 | 
					                return Result.failure(it)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }.onSuccess {
 | 
				
			||||||
 | 
					            return Result.success(it)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } while (true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Executes the given [action] until getting of successful result specified number of [times].
 | 
					 * Executes the given [action] until getting of successful result specified number of [times].
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -10,12 +32,23 @@ inline fun <R> repeatOnFailure(
 | 
				
			|||||||
    onEachFailure: (Throwable) -> Unit = {},
 | 
					    onEachFailure: (Throwable) -> Unit = {},
 | 
				
			||||||
    action: (Int) -> R
 | 
					    action: (Int) -> R
 | 
				
			||||||
): Optional<R> {
 | 
					): Optional<R> {
 | 
				
			||||||
    repeat(times) {
 | 
					    var i = 0
 | 
				
			||||||
        runCatching {
 | 
					    val result = repeatOnFailure(
 | 
				
			||||||
            action(it)
 | 
					        {
 | 
				
			||||||
        }.onFailure(onEachFailure).onSuccess {
 | 
					            onEachFailure(it)
 | 
				
			||||||
            return Optional.presented(it)
 | 
					            if (i < times) {
 | 
				
			||||||
 | 
					                i++
 | 
				
			||||||
 | 
					                true
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                false
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    return Optional.absent()
 | 
					    ) {
 | 
				
			||||||
 | 
					        action(i)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return if (result.isSuccess) {
 | 
				
			||||||
 | 
					        Optional.presented(result.getOrThrow())
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        Optional.absent()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val FixedSignsRange = 0 .. 100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					expect fun Float.fixed(signs: Int): Float
 | 
				
			||||||
 | 
					expect fun Double.fixed(signs: Int): Double
 | 
				
			||||||
@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual fun Float.fixed(signs: Int): Float = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toFloat()
 | 
				
			||||||
 | 
					actual fun Double.fixed(signs: Int): Double = this.asDynamic().toFixed(signs.coerceIn(FixedSignsRange)).unsafeCast<String>().toDouble()
 | 
				
			||||||
@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.File
 | 
				
			||||||
 | 
					import java.io.InputStream
 | 
				
			||||||
 | 
					import java.util.UUID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun InputStream.downloadToTempFile(
 | 
				
			||||||
 | 
					    fileName: String = UUID.randomUUID().toString(),
 | 
				
			||||||
 | 
					    fileExtension: String? = ".temp",
 | 
				
			||||||
 | 
					    folder: File? = null
 | 
				
			||||||
 | 
					) = File.createTempFile(
 | 
				
			||||||
 | 
					    fileName,
 | 
				
			||||||
 | 
					    fileExtension,
 | 
				
			||||||
 | 
					    folder
 | 
				
			||||||
 | 
					).apply {
 | 
				
			||||||
 | 
					    outputStream().use {
 | 
				
			||||||
 | 
					        copyTo(it)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    deleteOnExit()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.math.BigDecimal
 | 
				
			||||||
 | 
					import java.math.RoundingMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual fun Float.fixed(signs: Int): Float = BigDecimal.valueOf(this.toDouble())
 | 
				
			||||||
 | 
					    .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
 | 
				
			||||||
 | 
					    .toFloat();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual fun Double.fixed(signs: Int): Double = BigDecimal.valueOf(this)
 | 
				
			||||||
 | 
					    .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP)
 | 
				
			||||||
 | 
					    .toDouble();
 | 
				
			||||||
							
								
								
									
										36
									
								
								common/src/linuxX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								common/src/linuxX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import okio.FileSystem
 | 
				
			||||||
 | 
					import okio.Path
 | 
				
			||||||
 | 
					import okio.use
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual typealias MPPFile = Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					actual val MPPFile.filename: FileName
 | 
				
			||||||
 | 
					    get() = FileName(toString())
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					actual val MPPFile.filesize: Long
 | 
				
			||||||
 | 
					    get() = FileSystem.SYSTEM.openReadOnly(this).use {
 | 
				
			||||||
 | 
					        it.size()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
 | 
				
			||||||
 | 
					    get() = {
 | 
				
			||||||
 | 
					        FileSystem.SYSTEM.read(this) {
 | 
				
			||||||
 | 
					            readByteArray()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
 | 
				
			||||||
 | 
					    get() = {
 | 
				
			||||||
 | 
					        bytesAllocatorSync()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
							
								
								
									
										26
									
								
								common/src/linuxX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								common/src/linuxX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.cinterop.ByteVar
 | 
				
			||||||
 | 
					import kotlinx.cinterop.allocArray
 | 
				
			||||||
 | 
					import kotlinx.cinterop.memScoped
 | 
				
			||||||
 | 
					import kotlinx.cinterop.toKString
 | 
				
			||||||
 | 
					import platform.posix.snprintf
 | 
				
			||||||
 | 
					import platform.posix.sprintf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual fun Float.fixed(signs: Int): Float {
 | 
				
			||||||
 | 
					    return memScoped {
 | 
				
			||||||
 | 
					        val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sprintf(buff, "%.${signs}f", this@fixed)
 | 
				
			||||||
 | 
					        buff.toKString().toFloat()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual fun Double.fixed(signs: Int): Double {
 | 
				
			||||||
 | 
					    return memScoped {
 | 
				
			||||||
 | 
					        val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sprintf(buff, "%.${signs}f", this@fixed)
 | 
				
			||||||
 | 
					        buff.toKString().toDouble()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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
 | 
				
			||||||
							
								
								
									
										36
									
								
								common/src/mingwX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								common/src/mingwX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import okio.FileSystem
 | 
				
			||||||
 | 
					import okio.Path
 | 
				
			||||||
 | 
					import okio.use
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual typealias MPPFile = Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					actual val MPPFile.filename: FileName
 | 
				
			||||||
 | 
					    get() = FileName(toString())
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					actual val MPPFile.filesize: Long
 | 
				
			||||||
 | 
					    get() = FileSystem.SYSTEM.openReadOnly(this).use {
 | 
				
			||||||
 | 
					        it.size()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
 | 
				
			||||||
 | 
					    get() = {
 | 
				
			||||||
 | 
					        FileSystem.SYSTEM.read(this) {
 | 
				
			||||||
 | 
					            readByteArray()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
 | 
				
			||||||
 | 
					    get() = {
 | 
				
			||||||
 | 
					        bytesAllocatorSync()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
							
								
								
									
										26
									
								
								common/src/mingwX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								common/src/mingwX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.cinterop.ByteVar
 | 
				
			||||||
 | 
					import kotlinx.cinterop.allocArray
 | 
				
			||||||
 | 
					import kotlinx.cinterop.memScoped
 | 
				
			||||||
 | 
					import kotlinx.cinterop.toKString
 | 
				
			||||||
 | 
					import platform.posix.snprintf
 | 
				
			||||||
 | 
					import platform.posix.sprintf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual fun Float.fixed(signs: Int): Float {
 | 
				
			||||||
 | 
					    return memScoped {
 | 
				
			||||||
 | 
					        val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sprintf(buff, "%.${signs}f", this@fixed)
 | 
				
			||||||
 | 
					        buff.toKString().toFloat()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual fun Double.fixed(signs: Int): Double {
 | 
				
			||||||
 | 
					    return memScoped {
 | 
				
			||||||
 | 
					        val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sprintf(buff, "%.${signs}f", this@fixed)
 | 
				
			||||||
 | 
					        buff.toKString().toDouble()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -22,6 +22,7 @@ kotlin {
 | 
				
			|||||||
            dependencies {
 | 
					            dependencies {
 | 
				
			||||||
                api libs.kt.coroutines.android
 | 
					                api libs.kt.coroutines.android
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            dependsOn(jvmMain)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,24 +3,58 @@ package dev.inmo.micro_utils.coroutines.compose
 | 
				
			|||||||
import androidx.compose.runtime.*
 | 
					import androidx.compose.runtime.*
 | 
				
			||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
 | 
					import androidx.compose.runtime.snapshots.SnapshotStateList
 | 
				
			||||||
import dev.inmo.micro_utils.common.applyDiff
 | 
					import dev.inmo.micro_utils.common.applyDiff
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.ExceptionHandler
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
 | 
				
			||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 | 
					import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
import kotlinx.coroutines.flow.Flow
 | 
					import kotlinx.coroutines.flow.Flow
 | 
				
			||||||
import kotlinx.coroutines.flow.StateFlow
 | 
					import kotlinx.coroutines.flow.StateFlow
 | 
				
			||||||
 | 
					import kotlinx.coroutines.withContext
 | 
				
			||||||
 | 
					import kotlin.coroutines.CoroutineContext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Each value of [this] [Flow] will trigger [applyDiff] to the result [SnapshotStateList]
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [SnapshotStateList]
 | 
				
			||||||
 | 
					 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
				
			||||||
 | 
					 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
				
			||||||
 | 
					 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
@Suppress("NOTHING_TO_INLINE")
 | 
					@Suppress("NOTHING_TO_INLINE")
 | 
				
			||||||
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
 | 
					inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
 | 
				
			||||||
    scope: CoroutineScope
 | 
					    scope: CoroutineScope,
 | 
				
			||||||
 | 
					    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
				
			||||||
 | 
					    noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
				
			||||||
): SnapshotStateList<T> {
 | 
					): SnapshotStateList<T> {
 | 
				
			||||||
    val state = mutableStateListOf<T>()
 | 
					    val state = mutableStateListOf<T>()
 | 
				
			||||||
    subscribeSafelyWithoutExceptions(scope) {
 | 
					    val changeBlock: suspend (List<T>) -> Unit = useContextOnChange ?.let {
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            withContext(useContextOnChange) {
 | 
				
			||||||
                state.applyDiff(it)
 | 
					                state.applyDiff(it)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } ?: {
 | 
				
			||||||
 | 
					        state.applyDiff(it)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    subscribeSafelyWithoutExceptions(scope, onException, changeBlock)
 | 
				
			||||||
    return state
 | 
					    return state
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * In fact, it is just classcast of [asMutableComposeListState] to [List]
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [List]
 | 
				
			||||||
 | 
					 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
				
			||||||
 | 
					 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
				
			||||||
 | 
					 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @return Changing in time [List] which follow [Flow] values
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
@Suppress("NOTHING_TO_INLINE")
 | 
					@Suppress("NOTHING_TO_INLINE")
 | 
				
			||||||
inline fun <reified T> Flow<List<T>>.asComposeList(
 | 
					inline fun <reified T> Flow<List<T>>.asComposeList(
 | 
				
			||||||
    scope: CoroutineScope
 | 
					    scope: CoroutineScope,
 | 
				
			||||||
): List<T> = asMutableComposeListState(scope)
 | 
					    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
				
			||||||
 | 
					    noinline onException: ExceptionHandler<List<T>?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
				
			||||||
 | 
					): List<T> = asMutableComposeListState(scope, useContextOnChange, onException)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,35 +1,94 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.coroutines.compose
 | 
					package dev.inmo.micro_utils.coroutines.compose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import androidx.compose.runtime.*
 | 
					import androidx.compose.runtime.*
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.compose.asState
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.ExceptionHandler
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandlerWithNull
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.doInUI
 | 
				
			||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 | 
					import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
import kotlinx.coroutines.flow.Flow
 | 
					import kotlinx.coroutines.flow.Flow
 | 
				
			||||||
import kotlinx.coroutines.flow.StateFlow
 | 
					import kotlinx.coroutines.flow.StateFlow
 | 
				
			||||||
 | 
					import kotlinx.coroutines.withContext
 | 
				
			||||||
 | 
					import kotlin.coroutines.CoroutineContext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will map [this] [Flow] as [MutableState]. Returned [MutableState] WILL NOT change source [Flow]
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param initial First value which will be passed to the result [MutableState]
 | 
				
			||||||
 | 
					 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
 | 
				
			||||||
 | 
					 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
				
			||||||
 | 
					 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
				
			||||||
 | 
					 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
fun <T> Flow<T>.asMutableComposeState(
 | 
					fun <T> Flow<T>.asMutableComposeState(
 | 
				
			||||||
    initial: T,
 | 
					    initial: T,
 | 
				
			||||||
    scope: CoroutineScope
 | 
					    scope: CoroutineScope,
 | 
				
			||||||
 | 
					    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
				
			||||||
 | 
					    onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
				
			||||||
): MutableState<T> {
 | 
					): MutableState<T> {
 | 
				
			||||||
    val state = mutableStateOf(initial)
 | 
					    val state = mutableStateOf(initial)
 | 
				
			||||||
    subscribeSafelyWithoutExceptions(scope) { state.value = it }
 | 
					    val changeBlock: suspend (T) -> Unit = useContextOnChange ?.let {
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            withContext(useContextOnChange) {
 | 
				
			||||||
 | 
					                state.value = it
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } ?: {
 | 
				
			||||||
 | 
					        state.value = it
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    subscribeSafelyWithoutExceptions(scope, onException, block = changeBlock)
 | 
				
			||||||
    return state
 | 
					    return state
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will map [this] [StateFlow] as [MutableState]. Returned [MutableState] WILL NOT change source [StateFlow].
 | 
				
			||||||
 | 
					 * This conversation will pass its [StateFlow.value] as the first value
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [MutableState]
 | 
				
			||||||
 | 
					 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
				
			||||||
 | 
					 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
				
			||||||
 | 
					 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
@Suppress("NOTHING_TO_INLINE")
 | 
					@Suppress("NOTHING_TO_INLINE")
 | 
				
			||||||
inline fun <T> StateFlow<T>.asMutableComposeState(
 | 
					inline fun <T> StateFlow<T>.asMutableComposeState(
 | 
				
			||||||
    scope: CoroutineScope
 | 
					    scope: CoroutineScope,
 | 
				
			||||||
): MutableState<T> = asMutableComposeState(value, scope)
 | 
					    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
				
			||||||
 | 
					    noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
				
			||||||
 | 
					): MutableState<T> = asMutableComposeState(value, scope, useContextOnChange, onException)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will create [MutableState] using [asMutableComposeState] and use [asState] to convert it as immutable state
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param initial First value which will be passed to the result [State]
 | 
				
			||||||
 | 
					 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
 | 
				
			||||||
 | 
					 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
				
			||||||
 | 
					 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
				
			||||||
 | 
					 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
fun <T> Flow<T>.asComposeState(
 | 
					fun <T> Flow<T>.asComposeState(
 | 
				
			||||||
    initial: T,
 | 
					    initial: T,
 | 
				
			||||||
    scope: CoroutineScope
 | 
					    scope: CoroutineScope,
 | 
				
			||||||
 | 
					    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
				
			||||||
 | 
					    onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
				
			||||||
): State<T> {
 | 
					): State<T> {
 | 
				
			||||||
    val state = asMutableComposeState(initial, scope)
 | 
					    val state = asMutableComposeState(initial, scope, useContextOnChange, onException)
 | 
				
			||||||
    return derivedStateOf { state.value }
 | 
					    return state.asState()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will map [this] [StateFlow] as [State]. This conversation will pass its [StateFlow.value] as the first value
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param scope Will be used to [subscribeSafelyWithoutExceptions] on [this] to update returned [State]
 | 
				
			||||||
 | 
					 * @param useContextOnChange Will be used to change context inside of [subscribeSafelyWithoutExceptions] to ensure that
 | 
				
			||||||
 | 
					 * change will happen in the required [CoroutineContext]. [Dispatchers.Main] by default
 | 
				
			||||||
 | 
					 * @param onException Will be passed to the [subscribeSafelyWithoutExceptions] as uncaught exceptions handler
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
@Suppress("NOTHING_TO_INLINE")
 | 
					@Suppress("NOTHING_TO_INLINE")
 | 
				
			||||||
inline fun <T> StateFlow<T>.asComposeState(
 | 
					inline fun <T> StateFlow<T>.asComposeState(
 | 
				
			||||||
    scope: CoroutineScope
 | 
					    scope: CoroutineScope,
 | 
				
			||||||
): State<T> = asComposeState(value, scope)
 | 
					    useContextOnChange: CoroutineContext? = Dispatchers.Main,
 | 
				
			||||||
 | 
					    noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
 | 
				
			||||||
 | 
					): State<T> = asComposeState(value, scope, useContextOnChange, onException)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.coroutines.compose
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import androidx.compose.runtime.MutableState
 | 
					 | 
				
			||||||
import androidx.compose.runtime.mutableStateOf
 | 
					 | 
				
			||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
 | 
					 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					 | 
				
			||||||
import kotlinx.coroutines.flow.Flow
 | 
					 | 
				
			||||||
import kotlinx.coroutines.flow.StateFlow
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fun <T> Flow<T>.toMutableState(
 | 
					 | 
				
			||||||
    initial: T,
 | 
					 | 
				
			||||||
    scope: CoroutineScope
 | 
					 | 
				
			||||||
): MutableState<T> {
 | 
					 | 
				
			||||||
    val state = mutableStateOf(initial)
 | 
					 | 
				
			||||||
    subscribeSafelyWithoutExceptions(scope) { state.value = it }
 | 
					 | 
				
			||||||
    return state
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Suppress("NOTHING_TO_INLINE")
 | 
					 | 
				
			||||||
inline fun <T> StateFlow<T>.toMutableState(
 | 
					 | 
				
			||||||
    scope: CoroutineScope
 | 
					 | 
				
			||||||
): MutableState<T> = toMutableState(value, scope)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -1,19 +1,15 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.coroutines
 | 
					package dev.inmo.micro_utils.coroutines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.*
 | 
				
			||||||
import kotlinx.coroutines.channels.Channel
 | 
					import kotlinx.coroutines.channels.Channel
 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					import kotlinx.coroutines.flow.consumeAsFlow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun <T> CoroutineScope.actor(
 | 
					fun <T> CoroutineScope.actor(
 | 
				
			||||||
    channelCapacity: Int = Channel.UNLIMITED,
 | 
					    channelCapacity: Int = Channel.UNLIMITED,
 | 
				
			||||||
    block: suspend (T) -> Unit
 | 
					    block: suspend (T) -> Unit
 | 
				
			||||||
): Channel<T> {
 | 
					): Channel<T> {
 | 
				
			||||||
    val channel = Channel<T>(channelCapacity)
 | 
					    val channel = Channel<T>(channelCapacity)
 | 
				
			||||||
    launch {
 | 
					    channel.consumeAsFlow().subscribe(this, block)
 | 
				
			||||||
        for (data in channel) {
 | 
					 | 
				
			||||||
            block(data)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return channel
 | 
					    return channel
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.coroutines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.coroutines.*
 | 
				
			||||||
 | 
					import kotlinx.coroutines.channels.Channel
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.consumeAsFlow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun <T> CoroutineScope.actorAsync(
 | 
				
			||||||
 | 
					    channelCapacity: Int = Channel.UNLIMITED,
 | 
				
			||||||
 | 
					    markerFactory: suspend (T) -> Any? = { null },
 | 
				
			||||||
 | 
					    block: suspend (T) -> Unit
 | 
				
			||||||
 | 
					): Channel<T> {
 | 
				
			||||||
 | 
					    val channel = Channel<T>(channelCapacity)
 | 
				
			||||||
 | 
					    channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
 | 
				
			||||||
 | 
					    return channel
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline fun <T> CoroutineScope.safeActorAsync(
 | 
				
			||||||
 | 
					    channelCapacity: Int = Channel.UNLIMITED,
 | 
				
			||||||
 | 
					    noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
 | 
				
			||||||
 | 
					    noinline markerFactory: suspend (T) -> Any? = { null },
 | 
				
			||||||
 | 
					    crossinline block: suspend (T) -> Unit
 | 
				
			||||||
 | 
					): Channel<T> = actorAsync(
 | 
				
			||||||
 | 
					    channelCapacity,
 | 
				
			||||||
 | 
					    markerFactory
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    safely(onException) {
 | 
				
			||||||
 | 
					        block(it)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -6,23 +6,19 @@ import kotlin.coroutines.*
 | 
				
			|||||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
 | 
					suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
 | 
				
			||||||
    scope: CoroutineScope,
 | 
					    scope: CoroutineScope,
 | 
				
			||||||
    cancelOnResult: Boolean = true
 | 
					    cancelOnResult: Boolean = true
 | 
				
			||||||
): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation ->
 | 
					): Pair<Deferred<T>, T> {
 | 
				
			||||||
    scope.launch(SupervisorJob()) {
 | 
					    val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>()
 | 
				
			||||||
        val scope = this
 | 
					    val scope = scope.LinkedSupervisorScope()
 | 
				
			||||||
    forEach {
 | 
					    forEach {
 | 
				
			||||||
        scope.launch {
 | 
					        scope.launch {
 | 
				
			||||||
                continuation.resume(it to it.await())
 | 
					            resultDeferred.complete(it to it.await())
 | 
				
			||||||
            scope.cancel()
 | 
					            scope.cancel()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    }
 | 
					    return resultDeferred.await().also {
 | 
				
			||||||
}.also {
 | 
					 | 
				
			||||||
        if (cancelOnResult) {
 | 
					        if (cancelOnResult) {
 | 
				
			||||||
            forEach {
 | 
					            forEach {
 | 
				
			||||||
            try {
 | 
					                runCatchingSafely { it.cancel() }
 | 
				
			||||||
                it.cancel()
 | 
					 | 
				
			||||||
            } catch (e: IllegalStateException) {
 | 
					 | 
				
			||||||
                e.printStackTrace()
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.coroutines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.*
 | 
				
			||||||
 | 
					import kotlin.js.JsName
 | 
				
			||||||
 | 
					import kotlin.jvm.JvmName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline fun <T, R> Flow<Flow<T>>.flatMap(
 | 
				
			||||||
 | 
					    crossinline mapper: suspend (T) -> R
 | 
				
			||||||
 | 
					) = flow {
 | 
				
			||||||
 | 
					    collect {
 | 
				
			||||||
 | 
					        it.collect {
 | 
				
			||||||
 | 
					            emit(mapper(it))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsName("flatMapIterable")
 | 
				
			||||||
 | 
					@JvmName("flatMapIterable")
 | 
				
			||||||
 | 
					inline fun <T, R> Flow<Iterable<T>>.flatMap(
 | 
				
			||||||
 | 
					    crossinline mapper: suspend (T) -> R
 | 
				
			||||||
 | 
					) = map {
 | 
				
			||||||
 | 
					    it.asFlow()
 | 
				
			||||||
 | 
					}.flatMap(mapper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline fun <T, R> Flow<Flow<T>>.flatMapNotNull(
 | 
				
			||||||
 | 
					    crossinline mapper: suspend (T) -> R
 | 
				
			||||||
 | 
					) = flatMap(mapper).takeNotNull()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsName("flatMapNotNullIterable")
 | 
				
			||||||
 | 
					@JvmName("flatMapNotNullIterable")
 | 
				
			||||||
 | 
					inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull(
 | 
				
			||||||
 | 
					    crossinline mapper: suspend (T) -> R
 | 
				
			||||||
 | 
					) = flatMap(mapper).takeNotNull()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun <T> Flow<Flow<T>>.flatten() = flatMap { it }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@JsName("flattenIterable")
 | 
				
			||||||
 | 
					@JvmName("flattenIterable")
 | 
				
			||||||
 | 
					fun <T> Flow<Iterable<T>>.flatten() = flatMap { it }
 | 
				
			||||||
@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.coroutines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
 | 
				
			||||||
 | 
					fun <T> Flow<T>.filterNotNull() = takeNotNull()
 | 
				
			||||||
@@ -115,10 +115,21 @@ suspend inline fun <T> runCatchingSafely(
 | 
				
			|||||||
    safely(onException, block)
 | 
					    safely(onException, block)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend inline fun <T, R> T.runCatchingSafely(
 | 
				
			||||||
 | 
					    noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
 | 
				
			||||||
 | 
					    noinline block: suspend T.() -> R
 | 
				
			||||||
 | 
					): Result<R> = runCatching {
 | 
				
			||||||
 | 
					    safely(onException) { block() }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suspend inline fun <T> safelyWithResult(
 | 
					suspend inline fun <T> safelyWithResult(
 | 
				
			||||||
    noinline block: suspend CoroutineScope.() -> T
 | 
					    noinline block: suspend CoroutineScope.() -> T
 | 
				
			||||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
 | 
					): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend inline fun <T, R> T.safelyWithResult(
 | 
				
			||||||
 | 
					    noinline block: suspend T.() -> R
 | 
				
			||||||
 | 
					): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
 | 
					 * Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
 | 
				
			||||||
 * returning null at one time
 | 
					 * returning null at one time
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.coroutines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.coroutines.CancellationException
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Job
 | 
				
			||||||
 | 
					import org.w3c.dom.Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend fun preloadImage(src: String): Image {
 | 
				
			||||||
 | 
					    val image = Image()
 | 
				
			||||||
 | 
					    image.src = src
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val job = Job()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    image.addEventListener("load", {
 | 
				
			||||||
 | 
					        runCatching { job.complete() }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    runCatchingSafely {
 | 
				
			||||||
 | 
					        job.join()
 | 
				
			||||||
 | 
					    }.onFailure {
 | 
				
			||||||
 | 
					        if (it is CancellationException) {
 | 
				
			||||||
 | 
					            image.src = ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }.getOrThrow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return image
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.coroutines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T {
 | 
				
			||||||
 | 
					    val scope = CoroutineScope(Dispatchers.Unconfined)
 | 
				
			||||||
 | 
					    return scope.launchSynchronously(block)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,7 +6,7 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T
 | 
				
			|||||||
    var result: Result<T>? = null
 | 
					    var result: Result<T>? = null
 | 
				
			||||||
    val objectToSynchronize = Object()
 | 
					    val objectToSynchronize = Object()
 | 
				
			||||||
    synchronized(objectToSynchronize) {
 | 
					    synchronized(objectToSynchronize) {
 | 
				
			||||||
        launch {
 | 
					        launch(start = CoroutineStart.UNDISPATCHED) {
 | 
				
			||||||
            result = safelyWithResult(block)
 | 
					            result = safelyWithResult(block)
 | 
				
			||||||
        }.invokeOnCompletion {
 | 
					        }.invokeOnCompletion {
 | 
				
			||||||
            synchronized(objectToSynchronize) {
 | 
					            synchronized(objectToSynchronize) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.coroutines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
 | 
					import kotlinx.coroutines.delay
 | 
				
			||||||
 | 
					import kotlinx.coroutines.withContext
 | 
				
			||||||
 | 
					import kotlin.test.Test
 | 
				
			||||||
 | 
					import kotlin.test.assertEquals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LaunchInCurrentThreadTests {
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun simpleTestThatLaunchInCurrentThreadWorks() {
 | 
				
			||||||
 | 
					        val expectedResult = 10
 | 
				
			||||||
 | 
					        val result = launchInCurrentThread {
 | 
				
			||||||
 | 
					            expectedResult
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        assertEquals(expectedResult, result)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun simpleTestThatSeveralLaunchInCurrentThreadWorks() {
 | 
				
			||||||
 | 
					        val testData = 0 until 100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        testData.forEach {
 | 
				
			||||||
 | 
					            val result = launchInCurrentThread {
 | 
				
			||||||
 | 
					                it
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            assertEquals(it, result)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    fun simpleTestThatLaunchInCurrentThreadWillCorrectlyHandleSuspensionsWorks() {
 | 
				
			||||||
 | 
					        val testData = 0 until 100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        suspend fun test(data: Any): Any {
 | 
				
			||||||
 | 
					            return withContext(Dispatchers.Default) {
 | 
				
			||||||
 | 
					                delay(1)
 | 
				
			||||||
 | 
					                data
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        testData.forEach {
 | 
				
			||||||
 | 
					            val result = launchInCurrentThread {
 | 
				
			||||||
 | 
					                test(it)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            assertEquals(it, result)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										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)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -11,6 +11,7 @@ kotlin {
 | 
				
			|||||||
        commonMain {
 | 
					        commonMain {
 | 
				
			||||||
            dependencies {
 | 
					            dependencies {
 | 
				
			||||||
                api project(":micro_utils.common")
 | 
					                api project(":micro_utils.common")
 | 
				
			||||||
 | 
					                api libs.krypto
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        jsMain {
 | 
					        jsMain {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.crypto
 | 
					package dev.inmo.micro_utils.crypto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.soywiz.krypto.md5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typealias MD5 = String
 | 
					typealias MD5 = String
 | 
				
			||||||
 | 
					
 | 
				
			||||||
expect fun SourceBytes.md5(): MD5
 | 
					fun SourceBytes.md5(): MD5 = md5().hexLower
 | 
				
			||||||
fun SourceString.md5(): MD5 = encodeToByteArray().md5()
 | 
					fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +0,0 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.crypto
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @suppress
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())
 | 
					 | 
				
			||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.crypto
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.math.BigInteger
 | 
					 | 
				
			||||||
import java.security.MessageDigest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @suppress
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
actual fun SourceBytes.md5(): MD5 = BigInteger(
 | 
					 | 
				
			||||||
    1,
 | 
					 | 
				
			||||||
    MessageDigest.getInstance("MD5").digest(this)
 | 
					 | 
				
			||||||
).toString(16)
 | 
					 | 
				
			||||||
@@ -1,30 +1,5 @@
 | 
				
			|||||||
apply plugin: 'com.getkeepsafe.dexcount'
 | 
					apply plugin: 'com.getkeepsafe.dexcount'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ext {
 | 
					 | 
				
			||||||
    jvmKotlinFolderFile = {
 | 
					 | 
				
			||||||
        String sep = File.separator
 | 
					 | 
				
			||||||
        return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    enableIncludingJvmCodeInAndroidPart = {
 | 
					 | 
				
			||||||
        File jvmKotlinFolder = jvmKotlinFolderFile()
 | 
					 | 
				
			||||||
        if (jvmKotlinFolder.exists()) {
 | 
					 | 
				
			||||||
            android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    disableIncludingJvmCodeInAndroidPart = {
 | 
					 | 
				
			||||||
        File jvmKotlinFolder = jvmKotlinFolderFile()
 | 
					 | 
				
			||||||
        String[] oldDirs = android.sourceSets.main.java.srcDirs
 | 
					 | 
				
			||||||
        android.sourceSets.main.java.srcDirs = []
 | 
					 | 
				
			||||||
        for (oldDir in oldDirs) {
 | 
					 | 
				
			||||||
            if (oldDir != jvmKotlinFolder.path) {
 | 
					 | 
				
			||||||
                android.sourceSets.main.java.srcDirs += oldDir
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
android {
 | 
					android {
 | 
				
			||||||
    compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
 | 
					    compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
 | 
				
			||||||
    buildToolsVersion libs.versions.android.props.buildTools.get()
 | 
					    buildToolsVersion libs.versions.android.props.buildTools.get()
 | 
				
			||||||
@@ -58,10 +33,4 @@ android {
 | 
				
			|||||||
    kotlinOptions {
 | 
					    kotlinOptions {
 | 
				
			||||||
        jvmTarget = JavaVersion.VERSION_1_8.toString()
 | 
					        jvmTarget = JavaVersion.VERSION_1_8.toString()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    sourceSets {
 | 
					 | 
				
			||||||
        String sep = File.separator
 | 
					 | 
				
			||||||
        main.java.srcDirs += "src${sep}main${sep}kotlin"
 | 
					 | 
				
			||||||
        enableIncludingJvmCodeInAndroidPart()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -121,11 +121,11 @@ tasks.dokkaHtml {
 | 
				
			|||||||
//        }
 | 
					//        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        named("jvmMain") {
 | 
					        named("jvmMain") {
 | 
				
			||||||
            sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
 | 
					            sourceRoots.setFrom(findSourcesWithName("jvmMain"))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        named("androidMain") {
 | 
					        named("androidMain") {
 | 
				
			||||||
            sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
 | 
					            sourceRoots.setFrom(findSourcesWithName("androidMain"))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,11 +23,12 @@ allprojects {
 | 
				
			|||||||
        mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
 | 
					        mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
 | 
				
			||||||
        mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
 | 
					        mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
 | 
				
			||||||
        mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
 | 
					        mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
 | 
				
			||||||
 | 
					        mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
 | 
				
			||||||
        mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
 | 
					        mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
 | 
					        defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
 | 
					        publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
 | 
				
			||||||
        publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle"
 | 
					        publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,8 +48,8 @@ interface DefaultStatesManagerRepo<T : State> {
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
open class DefaultStatesManager<T : State>(
 | 
					open class DefaultStatesManager<T : State>(
 | 
				
			||||||
    protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
 | 
					    protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
 | 
				
			||||||
    protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true },
 | 
					    protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> false },
 | 
				
			||||||
    protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
 | 
					    protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> false }
 | 
				
			||||||
) : StatesManager<T> {
 | 
					) : StatesManager<T> {
 | 
				
			||||||
    protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
 | 
					    protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
 | 
				
			||||||
    override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
 | 
					    override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
 | 
				
			|||||||
# Project data
 | 
					# Project data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
group=dev.inmo
 | 
					group=dev.inmo
 | 
				
			||||||
version=0.12.12
 | 
					version=0.18.0
 | 
				
			||||||
android_code_version=151
 | 
					android_code_version=191
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,36 +1,47 @@
 | 
				
			|||||||
[versions]
 | 
					[versions]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
kt = "1.7.10"
 | 
					kt = "1.8.20"
 | 
				
			||||||
kt-serialization = "1.4.0"
 | 
					kt-serialization = "1.5.0"
 | 
				
			||||||
kt-coroutines = "1.6.4"
 | 
					kt-coroutines = "1.6.4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jb-compose = "1.2.0-alpha01-dev774"
 | 
					kslog = "1.1.1"
 | 
				
			||||||
jb-exposed = "0.39.2"
 | 
					 | 
				
			||||||
jb-dokka = "1.7.10"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
klock = "3.1.0"
 | 
					jb-compose = "1.4.0"
 | 
				
			||||||
uuid = "0.5.0"
 | 
					jb-exposed = "0.41.1"
 | 
				
			||||||
 | 
					jb-dokka = "1.8.10"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ktor = "2.1.1"
 | 
					korlibs = "3.4.0"
 | 
				
			||||||
 | 
					uuid = "0.7.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ktor = "2.3.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gh-release = "2.4.1"
 | 
					gh-release = "2.4.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
android-gradle = "7.2.2"
 | 
					koin = "3.4.0"
 | 
				
			||||||
dexcount = "3.1.0"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
android-coreKtx = "1.8.0"
 | 
					okio = "3.3.0"
 | 
				
			||||||
android-recyclerView = "1.2.1"
 | 
					
 | 
				
			||||||
android-appCompat = "1.4.2"
 | 
					ksp = "1.8.20-1.0.11"
 | 
				
			||||||
android-espresso = "3.4.0"
 | 
					kotlin-poet = "1.13.0"
 | 
				
			||||||
android-test = "1.1.3"
 | 
					
 | 
				
			||||||
 | 
					android-gradle = "7.4.2"
 | 
				
			||||||
 | 
					dexcount = "4.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					android-coreKtx = "1.10.0"
 | 
				
			||||||
 | 
					android-recyclerView = "1.3.0"
 | 
				
			||||||
 | 
					android-appCompat = "1.6.1"
 | 
				
			||||||
 | 
					android-fragment = "1.5.7"
 | 
				
			||||||
 | 
					android-espresso = "3.5.1"
 | 
				
			||||||
 | 
					android-test = "1.1.5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
android-props-minSdk = "21"
 | 
					android-props-minSdk = "21"
 | 
				
			||||||
android-props-compileSdk = "32"
 | 
					android-props-compileSdk = "33"
 | 
				
			||||||
android-props-buildTools = "32.0.0"
 | 
					android-props-buildTools = "33.0.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[libraries]
 | 
					[libraries]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
 | 
					kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
 | 
				
			||||||
 | 
					kt-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kt" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
 | 
					kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
 | 
				
			||||||
kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" }
 | 
					kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" }
 | 
				
			||||||
@@ -56,9 +67,12 @@ ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.re
 | 
				
			|||||||
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
 | 
					ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
 | 
				
			||||||
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
 | 
					ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
 | 
					klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" }
 | 
				
			||||||
 | 
					krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" }
 | 
				
			||||||
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
 | 
					uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
 | 
				
			||||||
 | 
					koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
 | 
					jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
 | 
				
			||||||
@@ -67,6 +81,7 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
 | 
				
			|||||||
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
 | 
					android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
 | 
				
			||||||
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
 | 
					android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
 | 
				
			||||||
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
 | 
					android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
 | 
				
			||||||
 | 
					android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
 | 
				
			||||||
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
 | 
					android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
 | 
				
			||||||
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
 | 
					android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,9 +89,18 @@ android-test-junit = { module = "androidx.test.ext:junit", version.ref = "androi
 | 
				
			|||||||
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
 | 
					kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
 | 
				
			||||||
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
 | 
					kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ksp dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
 | 
				
			||||||
 | 
					ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Buildscript
 | 
				
			||||||
 | 
					
 | 
				
			||||||
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
 | 
					buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
 | 
				
			||||||
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
 | 
					buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
 | 
				
			||||||
 | 
					buildscript-kt-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
 | 
				
			||||||
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
 | 
					buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
 | 
				
			||||||
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
 | 
					buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
 | 
				
			||||||
buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
 | 
					buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,5 @@
 | 
				
			|||||||
distributionBase=GRADLE_USER_HOME
 | 
					distributionBase=GRADLE_USER_HOME
 | 
				
			||||||
distributionPath=wrapper/dists
 | 
					distributionPath=wrapper/dists
 | 
				
			||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
 | 
					distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
 | 
				
			||||||
zipStoreBase=GRADLE_USER_HOME
 | 
					zipStoreBase=GRADLE_USER_HOME
 | 
				
			||||||
zipStorePath=wrapper/dists
 | 
					zipStorePath=wrapper/dists
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										118
									
								
								jvm.publish.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								jvm.publish.gradle
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					apply plugin: 'maven-publish'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					task javadocJar(type: Jar) {
 | 
				
			||||||
 | 
					    from javadoc
 | 
				
			||||||
 | 
					    classifier = 'javadoc'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					task sourcesJar(type: Jar) {
 | 
				
			||||||
 | 
					    from sourceSets.main.allSource
 | 
				
			||||||
 | 
					    classifier = 'sources'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					publishing {
 | 
				
			||||||
 | 
					    publications {
 | 
				
			||||||
 | 
					        maven(MavenPublication) {
 | 
				
			||||||
 | 
					            from components.java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            artifact javadocJar
 | 
				
			||||||
 | 
					            artifact sourcesJar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            pom {
 | 
				
			||||||
 | 
					                resolveStrategy = Closure.DELEGATE_FIRST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                description = "It is set of projects with micro tools for avoiding of routines coding"
 | 
				
			||||||
 | 
					                name = "${project.name}"
 | 
				
			||||||
 | 
					                url = "https://github.com/InsanusMokrassar/MicroUtils/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                scm {
 | 
				
			||||||
 | 
					                    developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git"
 | 
				
			||||||
 | 
					                    url = "https://github.com/InsanusMokrassar/MicroUtils.git"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                developers {
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                        developer {
 | 
				
			||||||
 | 
					                            id = "InsanusMokrassar"
 | 
				
			||||||
 | 
					                            name = "Aleksei Ovsiannikov"
 | 
				
			||||||
 | 
					                            email = "ovsyannikov.alexey95@gmail.com"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        developer {
 | 
				
			||||||
 | 
					                            id = "000Sanya"
 | 
				
			||||||
 | 
					                            name = "Syrov Aleksandr"
 | 
				
			||||||
 | 
					                            email = "000sanya.000sanya@gmail.com"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                licenses {
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                        license {
 | 
				
			||||||
 | 
					                            name = "Apache Software License 2.0"
 | 
				
			||||||
 | 
					                            url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            repositories {
 | 
				
			||||||
 | 
					                if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
 | 
				
			||||||
 | 
					                    maven {
 | 
				
			||||||
 | 
					                        name = "GithubPackages"
 | 
				
			||||||
 | 
					                        url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                        credentials {
 | 
				
			||||||
 | 
					                            username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
 | 
				
			||||||
 | 
					                            password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
 | 
				
			||||||
 | 
					                    maven {
 | 
				
			||||||
 | 
					                        name = "Gitea"
 | 
				
			||||||
 | 
					                        url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                        credentials(HttpHeaderCredentials) {
 | 
				
			||||||
 | 
					                            name = "Authorization"
 | 
				
			||||||
 | 
					                            value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                        authentication {
 | 
				
			||||||
 | 
					                            header(HttpHeaderAuthentication)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
 | 
				
			||||||
 | 
					                    maven {
 | 
				
			||||||
 | 
					                        name = "sonatype"
 | 
				
			||||||
 | 
					                        url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                        credentials {
 | 
				
			||||||
 | 
					                            username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
 | 
				
			||||||
 | 
					                            password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (project.hasProperty("signing.gnupg.keyName")) {
 | 
				
			||||||
 | 
					    apply plugin: 'signing'
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    signing {
 | 
				
			||||||
 | 
					        useGpgCmd()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        sign publishing.publications
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    task signAll {
 | 
				
			||||||
 | 
					        tasks.withType(Sign).forEach {
 | 
				
			||||||
 | 
					            dependsOn(it)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								jvm.publish.kpsb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								jvm.publish.kpsb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}
 | 
				
			||||||
							
								
								
									
										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,20 @@ kotlin {
 | 
				
			|||||||
                api libs.ktor.client
 | 
					                api libs.ktor.client
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        androidMain {
 | 
				
			||||||
 | 
					            dependsOn jvmMain
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        linuxX64Main {
 | 
				
			||||||
 | 
					            dependencies {
 | 
				
			||||||
 | 
					                api internalProject("micro_utils.mime_types")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mingwX64Main {
 | 
				
			||||||
 | 
					            dependencies {
 | 
				
			||||||
 | 
					                api internalProject("micro_utils.mime_types")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,10 @@ import io.ktor.client.call.body
 | 
				
			|||||||
import io.ktor.client.statement.HttpResponse
 | 
					import io.ktor.client.statement.HttpResponse
 | 
				
			||||||
import io.ktor.http.HttpStatusCode
 | 
					import io.ktor.http.HttpStatusCode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf {
 | 
					suspend inline fun <reified T : Any> HttpResponse.bodyOrNull(
 | 
				
			||||||
    status == HttpStatusCode.OK
 | 
					    statusFilter: (HttpResponse) -> Boolean = { it.status == HttpStatusCode.OK }
 | 
				
			||||||
} ?.body<T>()
 | 
					) = takeIf(statusFilter) ?.body<T>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					suspend inline fun <reified T : Any> HttpResponse.bodyOrNullOnNoContent() = bodyOrNull<T> {
 | 
				
			||||||
 | 
					    it.status != HttpStatusCode.NoContent
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,82 +0,0 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.ktor.client
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
 | 
					 | 
				
			||||||
import dev.inmo.micro_utils.coroutines.safely
 | 
					 | 
				
			||||||
import dev.inmo.micro_utils.ktor.common.*
 | 
					 | 
				
			||||||
import io.ktor.client.HttpClient
 | 
					 | 
				
			||||||
import io.ktor.client.plugins.pluginOrNull
 | 
					 | 
				
			||||||
import io.ktor.client.plugins.websocket.WebSockets
 | 
					 | 
				
			||||||
import io.ktor.client.plugins.websocket.ws
 | 
					 | 
				
			||||||
import io.ktor.client.request.HttpRequestBuilder
 | 
					 | 
				
			||||||
import io.ktor.websocket.Frame
 | 
					 | 
				
			||||||
import io.ktor.websocket.readBytes
 | 
					 | 
				
			||||||
import kotlinx.coroutines.flow.Flow
 | 
					 | 
				
			||||||
import kotlinx.coroutines.flow.channelFlow
 | 
					 | 
				
			||||||
import kotlinx.coroutines.isActive
 | 
					 | 
				
			||||||
import kotlinx.serialization.DeserializationStrategy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
 | 
					 | 
				
			||||||
 * connection. Must return true in case if must be reconnected. By default always reconnecting
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
 | 
					 | 
				
			||||||
    url: String,
 | 
					 | 
				
			||||||
    crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
 | 
					 | 
				
			||||||
    noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    crossinline conversation: suspend (StandardKtorSerialInputData) -> T
 | 
					 | 
				
			||||||
): Flow<T> {
 | 
					 | 
				
			||||||
    pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    val correctedUrl = url.asCorrectWebSocketUrl
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return channelFlow {
 | 
					 | 
				
			||||||
        do {
 | 
					 | 
				
			||||||
            val reconnect = runCatchingSafely {
 | 
					 | 
				
			||||||
                ws(correctedUrl, requestBuilder) {
 | 
					 | 
				
			||||||
                    for (received in incoming) {
 | 
					 | 
				
			||||||
                        when (received) {
 | 
					 | 
				
			||||||
                            is Frame.Binary -> send(conversation(received.data))
 | 
					 | 
				
			||||||
                            else -> {
 | 
					 | 
				
			||||||
                                close()
 | 
					 | 
				
			||||||
                                return@ws
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                checkReconnection(null)
 | 
					 | 
				
			||||||
            }.getOrElse { e ->
 | 
					 | 
				
			||||||
                checkReconnection(e).also {
 | 
					 | 
				
			||||||
                    if (!it) {
 | 
					 | 
				
			||||||
                        close(e)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } while (reconnect && isActive)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (isActive) {
 | 
					 | 
				
			||||||
            safely {
 | 
					 | 
				
			||||||
                close()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
 | 
					 | 
				
			||||||
 * connection. Must return true in case if must be reconnected. By default always reconnecting
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
 | 
					 | 
				
			||||||
    url: String,
 | 
					 | 
				
			||||||
    deserializer: DeserializationStrategy<T>,
 | 
					 | 
				
			||||||
    crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
 | 
					 | 
				
			||||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
 | 
					 | 
				
			||||||
    noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
) = createStandardWebsocketFlow(
 | 
					 | 
				
			||||||
    url,
 | 
					 | 
				
			||||||
    checkReconnection,
 | 
					 | 
				
			||||||
    requestBuilder
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
    serialFormat.decodeDefault(deserializer, it)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,6 +1,10 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.ktor.client
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.inmo.micro_utils.common.MPPFile
 | 
					import dev.inmo.micro_utils.common.MPPFile
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.filesize
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.ktor.common.input
 | 
				
			||||||
import io.ktor.client.request.forms.InputProvider
 | 
					import io.ktor.client.request.forms.InputProvider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
expect suspend fun MPPFile.inputProvider(): InputProvider
 | 
					fun MPPFile.inputProvider(): InputProvider = InputProvider(filesize) {
 | 
				
			||||||
 | 
					    input()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,7 @@ import kotlinx.coroutines.isActive
 | 
				
			|||||||
 * connection. Must return true in case if must be reconnected. By default always reconnecting
 | 
					 * connection. Must return true in case if must be reconnected. By default always reconnecting
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
 | 
					@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
 | 
				
			||||||
inline fun <reified T : Any> openBaseWebSocketFlow(
 | 
					inline fun <T : Any> openBaseWebSocketFlow(
 | 
				
			||||||
    noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
 | 
					    noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
 | 
				
			||||||
    noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
 | 
					    noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
 | 
				
			||||||
): Flow<T> {
 | 
					): Flow<T> {
 | 
				
			||||||
@@ -57,7 +57,7 @@ inline fun <reified T : Any> HttpClient.openWebSocketFlow(
 | 
				
			|||||||
): Flow<T> {
 | 
					): Flow<T> {
 | 
				
			||||||
    pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
 | 
					    pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return openBaseWebSocketFlow<T>(checkReconnection) {
 | 
					    return openBaseWebSocketFlow(checkReconnection) {
 | 
				
			||||||
        val block: suspend DefaultClientWebSocketSession.() -> Unit = {
 | 
					        val block: suspend DefaultClientWebSocketSession.() -> Unit = {
 | 
				
			||||||
            while (isActive) {
 | 
					            while (isActive) {
 | 
				
			||||||
                send(receiveDeserialized<T>())
 | 
					                send(receiveDeserialized<T>())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit
 | 
				
			||||||
@@ -1,260 +0,0 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.ktor.client
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.inmo.micro_utils.common.MPPFile
 | 
					 | 
				
			||||||
import dev.inmo.micro_utils.common.filename
 | 
					 | 
				
			||||||
import dev.inmo.micro_utils.ktor.common.*
 | 
					 | 
				
			||||||
import io.ktor.client.HttpClient
 | 
					 | 
				
			||||||
import io.ktor.client.call.body
 | 
					 | 
				
			||||||
import io.ktor.client.request.*
 | 
					 | 
				
			||||||
import io.ktor.client.request.forms.*
 | 
					 | 
				
			||||||
import io.ktor.client.statement.readBytes
 | 
					 | 
				
			||||||
import io.ktor.http.*
 | 
					 | 
				
			||||||
import io.ktor.utils.io.core.ByteReadPacket
 | 
					 | 
				
			||||||
import kotlinx.serialization.*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Deprecated("This class will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
class UnifiedRequester(
 | 
					 | 
				
			||||||
    val client: HttpClient = HttpClient(),
 | 
					 | 
				
			||||||
    val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
    suspend fun <ResultType> uniget(
 | 
					 | 
				
			||||||
        url: String,
 | 
					 | 
				
			||||||
        resultDeserializer: DeserializationStrategy<ResultType>
 | 
					 | 
				
			||||||
    ): ResultType = client.uniget(url, resultDeserializer, serialFormat)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun <T> encodeUrlQueryValue(
 | 
					 | 
				
			||||||
        serializationStrategy: SerializationStrategy<T>,
 | 
					 | 
				
			||||||
        value: T
 | 
					 | 
				
			||||||
    ) = serializationStrategy.encodeUrlQueryValue(
 | 
					 | 
				
			||||||
        value,
 | 
					 | 
				
			||||||
        serialFormat
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    suspend fun <BodyType, ResultType> unipost(
 | 
					 | 
				
			||||||
        url: String,
 | 
					 | 
				
			||||||
        bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
					 | 
				
			||||||
        resultDeserializer: DeserializationStrategy<ResultType>
 | 
					 | 
				
			||||||
    ) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    suspend fun <ResultType> unimultipart(
 | 
					 | 
				
			||||||
        url: String,
 | 
					 | 
				
			||||||
        filename: String,
 | 
					 | 
				
			||||||
        inputProvider: InputProvider,
 | 
					 | 
				
			||||||
        resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
        mimetype: String = "*/*",
 | 
					 | 
				
			||||||
        additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
        dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    ): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    suspend fun <BodyType, ResultType> unimultipart(
 | 
					 | 
				
			||||||
        url: String,
 | 
					 | 
				
			||||||
        filename: String,
 | 
					 | 
				
			||||||
        inputProvider: InputProvider,
 | 
					 | 
				
			||||||
        otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
					 | 
				
			||||||
        resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
        mimetype: String = "*/*",
 | 
					 | 
				
			||||||
        additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
        dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    ): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    suspend fun <ResultType> unimultipart(
 | 
					 | 
				
			||||||
        url: String,
 | 
					 | 
				
			||||||
        mppFile: MPPFile,
 | 
					 | 
				
			||||||
        resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
        mimetype: String = "*/*",
 | 
					 | 
				
			||||||
        additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
        dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {}
 | 
					 | 
				
			||||||
    ): ResultType = client.unimultipart(
 | 
					 | 
				
			||||||
        url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    suspend fun <BodyType, ResultType> unimultipart(
 | 
					 | 
				
			||||||
        url: String,
 | 
					 | 
				
			||||||
        mppFile: MPPFile,
 | 
					 | 
				
			||||||
        otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
					 | 
				
			||||||
        resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
        mimetype: String = "*/*",
 | 
					 | 
				
			||||||
        additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
        dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {}
 | 
					 | 
				
			||||||
    ): ResultType = client.unimultipart(
 | 
					 | 
				
			||||||
        url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun <T> createStandardWebsocketFlow(
 | 
					 | 
				
			||||||
        url: String,
 | 
					 | 
				
			||||||
        checkReconnection: suspend (Throwable?) -> Boolean,
 | 
					 | 
				
			||||||
        deserializer: DeserializationStrategy<T>,
 | 
					 | 
				
			||||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    ) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun <T> createStandardWebsocketFlow(
 | 
					 | 
				
			||||||
        url: String,
 | 
					 | 
				
			||||||
        deserializer: DeserializationStrategy<T>,
 | 
					 | 
				
			||||||
        requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    ) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Deprecated("This property will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
val defaultRequester = UnifiedRequester()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
suspend fun <ResultType> HttpClient.uniget(
 | 
					 | 
				
			||||||
    url: String,
 | 
					 | 
				
			||||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
					 | 
				
			||||||
) = get(url).let {
 | 
					 | 
				
			||||||
    serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
 | 
					 | 
				
			||||||
    value: T,
 | 
					 | 
				
			||||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
					 | 
				
			||||||
) = serialFormat.encodeHex(
 | 
					 | 
				
			||||||
    this,
 | 
					 | 
				
			||||||
    value
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
suspend fun <BodyType, ResultType> HttpClient.unipost(
 | 
					 | 
				
			||||||
    url: String,
 | 
					 | 
				
			||||||
    bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
					 | 
				
			||||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
					 | 
				
			||||||
) = post(url) {
 | 
					 | 
				
			||||||
    setBody(
 | 
					 | 
				
			||||||
        serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
}.let {
 | 
					 | 
				
			||||||
    serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
suspend fun <ResultType> HttpClient.unimultipart(
 | 
					 | 
				
			||||||
    url: String,
 | 
					 | 
				
			||||||
    filename: String,
 | 
					 | 
				
			||||||
    inputProvider: InputProvider,
 | 
					 | 
				
			||||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
    mimetype: String = "*/*",
 | 
					 | 
				
			||||||
    additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
					 | 
				
			||||||
): ResultType = submitFormWithBinaryData(
 | 
					 | 
				
			||||||
    url,
 | 
					 | 
				
			||||||
    formData = formData {
 | 
					 | 
				
			||||||
        append(
 | 
					 | 
				
			||||||
            "bytes",
 | 
					 | 
				
			||||||
            inputProvider,
 | 
					 | 
				
			||||||
            Headers.build {
 | 
					 | 
				
			||||||
                append(HttpHeaders.ContentType, mimetype)
 | 
					 | 
				
			||||||
                append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
 | 
					 | 
				
			||||||
                dataHeadersBuilder()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        additionalParametersBuilder()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
    requestBuilder()
 | 
					 | 
				
			||||||
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
 | 
					 | 
				
			||||||
    url: String,
 | 
					 | 
				
			||||||
    filename: String,
 | 
					 | 
				
			||||||
    otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
					 | 
				
			||||||
    inputProvider: InputProvider,
 | 
					 | 
				
			||||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
    mimetype: String = "*/*",
 | 
					 | 
				
			||||||
    additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
					 | 
				
			||||||
): ResultType = unimultipart(
 | 
					 | 
				
			||||||
    url,
 | 
					 | 
				
			||||||
    filename,
 | 
					 | 
				
			||||||
    inputProvider,
 | 
					 | 
				
			||||||
    resultDeserializer,
 | 
					 | 
				
			||||||
    mimetype,
 | 
					 | 
				
			||||||
    additionalParametersBuilder = {
 | 
					 | 
				
			||||||
        val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
 | 
					 | 
				
			||||||
        append(
 | 
					 | 
				
			||||||
            "data",
 | 
					 | 
				
			||||||
            InputProvider(serialized.size.toLong()) {
 | 
					 | 
				
			||||||
                ByteReadPacket(serialized)
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            Headers.build {
 | 
					 | 
				
			||||||
                append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
 | 
					 | 
				
			||||||
                append(HttpHeaders.ContentDisposition, "filename=data.bytes")
 | 
					 | 
				
			||||||
                dataHeadersBuilder()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        additionalParametersBuilder()
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    dataHeadersBuilder,
 | 
					 | 
				
			||||||
    requestBuilder,
 | 
					 | 
				
			||||||
    serialFormat
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
suspend fun <ResultType> HttpClient.unimultipart(
 | 
					 | 
				
			||||||
    url: String,
 | 
					 | 
				
			||||||
    mppFile: MPPFile,
 | 
					 | 
				
			||||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
    mimetype: String = "*/*",
 | 
					 | 
				
			||||||
    additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
					 | 
				
			||||||
): ResultType = unimultipart(
 | 
					 | 
				
			||||||
    url,
 | 
					 | 
				
			||||||
    mppFile.filename.string,
 | 
					 | 
				
			||||||
    mppFile.inputProvider(),
 | 
					 | 
				
			||||||
    resultDeserializer,
 | 
					 | 
				
			||||||
    mimetype,
 | 
					 | 
				
			||||||
    additionalParametersBuilder,
 | 
					 | 
				
			||||||
    dataHeadersBuilder,
 | 
					 | 
				
			||||||
    requestBuilder,
 | 
					 | 
				
			||||||
    serialFormat
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
 | 
					 | 
				
			||||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
 | 
					 | 
				
			||||||
    url: String,
 | 
					 | 
				
			||||||
    mppFile: MPPFile,
 | 
					 | 
				
			||||||
    otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
 | 
					 | 
				
			||||||
    resultDeserializer: DeserializationStrategy<ResultType>,
 | 
					 | 
				
			||||||
    mimetype: String = "*/*",
 | 
					 | 
				
			||||||
    additionalParametersBuilder: FormBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    requestBuilder: HttpRequestBuilder.() -> Unit = {},
 | 
					 | 
				
			||||||
    serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
 | 
					 | 
				
			||||||
): ResultType = unimultipart(
 | 
					 | 
				
			||||||
    url,
 | 
					 | 
				
			||||||
    mppFile,
 | 
					 | 
				
			||||||
    resultDeserializer,
 | 
					 | 
				
			||||||
    mimetype,
 | 
					 | 
				
			||||||
    additionalParametersBuilder = {
 | 
					 | 
				
			||||||
        val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
 | 
					 | 
				
			||||||
        append(
 | 
					 | 
				
			||||||
            "data",
 | 
					 | 
				
			||||||
            InputProvider(serialized.size.toLong()) {
 | 
					 | 
				
			||||||
                ByteReadPacket(serialized)
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            Headers.build {
 | 
					 | 
				
			||||||
                append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
 | 
					 | 
				
			||||||
                append(HttpHeaders.ContentDisposition, "filename=data.bytes")
 | 
					 | 
				
			||||||
                dataHeadersBuilder()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        additionalParametersBuilder()
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    dataHeadersBuilder,
 | 
					 | 
				
			||||||
    requestBuilder,
 | 
					 | 
				
			||||||
    serialFormat
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
@@ -7,13 +7,5 @@ import io.ktor.client.HttpClient
 | 
				
			|||||||
expect suspend fun HttpClient.tempUpload(
 | 
					expect suspend fun HttpClient.tempUpload(
 | 
				
			||||||
    fullTempUploadDraftPath: String,
 | 
					    fullTempUploadDraftPath: String,
 | 
				
			||||||
    file: MPPFile,
 | 
					    file: MPPFile,
 | 
				
			||||||
    onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
 | 
					    onUpload: OnUploadCallback = { _, _ -> }
 | 
				
			||||||
): TemporalFileId
 | 
					): TemporalFileId
 | 
				
			||||||
 | 
					 | 
				
			||||||
suspend fun UnifiedRequester.tempUpload(
 | 
					 | 
				
			||||||
    fullTempUploadDraftPath: String,
 | 
					 | 
				
			||||||
    file: MPPFile,
 | 
					 | 
				
			||||||
    onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
 | 
					 | 
				
			||||||
): TemporalFileId = client.tempUpload(
 | 
					 | 
				
			||||||
    fullTempUploadDraftPath, file, onUpload
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.FileName
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.MPPFile
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.ktor.common.LambdaInputProvider
 | 
				
			||||||
 | 
					import io.ktor.client.HttpClient
 | 
				
			||||||
 | 
					import io.ktor.http.Headers
 | 
				
			||||||
 | 
					import io.ktor.utils.io.core.Input
 | 
				
			||||||
 | 
					import kotlinx.serialization.DeserializationStrategy
 | 
				
			||||||
 | 
					import kotlinx.serialization.StringFormat
 | 
				
			||||||
 | 
					import kotlinx.serialization.json.Json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class UniUploadFileInfo(
 | 
				
			||||||
 | 
					    val fileName: FileName,
 | 
				
			||||||
 | 
					    val mimeType: String,
 | 
				
			||||||
 | 
					    val inputAllocator: LambdaInputProvider
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will execute submitting of multipart data request
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
				
			||||||
 | 
					 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
				
			||||||
 | 
					 * in case you wish to pass other source of multipart binary data than regular file
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see dev.inmo.micro_utils.ktor.server.handleUniUpload
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					expect suspend fun <T> HttpClient.uniUpload(
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					    data: Map<String, Any>,
 | 
				
			||||||
 | 
					    resultDeserializer: DeserializationStrategy<T>,
 | 
				
			||||||
 | 
					    headers: Headers = Headers.Empty,
 | 
				
			||||||
 | 
					    stringFormat: StringFormat = Json,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback = { _, _ -> }
 | 
				
			||||||
 | 
					): T?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Additional variant of [uniUpload] which will unify sending of some [MPPFile] with the server
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					suspend fun <T> HttpClient.uniUpload(
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					    file: MPPFile,
 | 
				
			||||||
 | 
					    resultDeserializer: DeserializationStrategy<T>,
 | 
				
			||||||
 | 
					    additionalData: Map<String, Any> = emptyMap(),
 | 
				
			||||||
 | 
					    headers: Headers = Headers.Empty,
 | 
				
			||||||
 | 
					    stringFormat: StringFormat = Json,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback = { _, _ -> }
 | 
				
			||||||
 | 
					): T? = uniUpload(
 | 
				
			||||||
 | 
					    url,
 | 
				
			||||||
 | 
					    additionalData + ("bytes" to file),
 | 
				
			||||||
 | 
					    resultDeserializer,
 | 
				
			||||||
 | 
					    headers,
 | 
				
			||||||
 | 
					    stringFormat,
 | 
				
			||||||
 | 
					    onUpload
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] with the server
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					suspend fun <T> HttpClient.uniUpload(
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					    info: UniUploadFileInfo,
 | 
				
			||||||
 | 
					    resultDeserializer: DeserializationStrategy<T>,
 | 
				
			||||||
 | 
					    additionalData: Map<String, Any> = emptyMap(),
 | 
				
			||||||
 | 
					    headers: Headers = Headers.Empty,
 | 
				
			||||||
 | 
					    stringFormat: StringFormat = Json,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback = { _, _ -> }
 | 
				
			||||||
 | 
					): T? = uniUpload(
 | 
				
			||||||
 | 
					    url,
 | 
				
			||||||
 | 
					    additionalData + ("bytes" to info),
 | 
				
			||||||
 | 
					    resultDeserializer,
 | 
				
			||||||
 | 
					    headers,
 | 
				
			||||||
 | 
					    stringFormat,
 | 
				
			||||||
 | 
					    onUpload
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] (built from [fileName],
 | 
				
			||||||
 | 
					 * [mimeType] and [inputAllocator]) with the server
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					suspend fun <T> HttpClient.uniUpload(
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					    fileName: FileName,
 | 
				
			||||||
 | 
					    mimeType: String,
 | 
				
			||||||
 | 
					    inputAllocator: LambdaInputProvider,
 | 
				
			||||||
 | 
					    resultDeserializer: DeserializationStrategy<T>,
 | 
				
			||||||
 | 
					    additionalData: Map<String, Any> = emptyMap(),
 | 
				
			||||||
 | 
					    headers: Headers = Headers.Empty,
 | 
				
			||||||
 | 
					    stringFormat: StringFormat = Json,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback = { _, _ -> }
 | 
				
			||||||
 | 
					): T? = uniUpload(
 | 
				
			||||||
 | 
					    url,
 | 
				
			||||||
 | 
					    UniUploadFileInfo(fileName, mimeType, inputAllocator),
 | 
				
			||||||
 | 
					    resultDeserializer,
 | 
				
			||||||
 | 
					    additionalData,
 | 
				
			||||||
 | 
					    headers,
 | 
				
			||||||
 | 
					    stringFormat,
 | 
				
			||||||
 | 
					    onUpload
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -1,11 +0,0 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.ktor.client
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.inmo.micro_utils.common.*
 | 
					 | 
				
			||||||
import io.ktor.client.request.forms.InputProvider
 | 
					 | 
				
			||||||
import io.ktor.utils.io.core.ByteReadPacket
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
actual suspend fun MPPFile.inputProvider(): InputProvider = bytes().let {
 | 
					 | 
				
			||||||
    InputProvider(it.size.toLong()) {
 | 
					 | 
				
			||||||
        ByteReadPacket(it)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
package dev.inmo.micro_utils.ktor.client
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.inmo.micro_utils.common.MPPFile
 | 
					import dev.inmo.micro_utils.common.MPPFile
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
 | 
				
			||||||
import dev.inmo.micro_utils.ktor.common.TemporalFileId
 | 
					import dev.inmo.micro_utils.ktor.common.TemporalFileId
 | 
				
			||||||
import io.ktor.client.HttpClient
 | 
					import io.ktor.client.HttpClient
 | 
				
			||||||
import kotlinx.coroutines.*
 | 
					import kotlinx.coroutines.*
 | 
				
			||||||
@@ -12,10 +14,11 @@ import org.w3c.xhr.XMLHttpRequest.Companion.DONE
 | 
				
			|||||||
suspend fun tempUpload(
 | 
					suspend fun tempUpload(
 | 
				
			||||||
    fullTempUploadDraftPath: String,
 | 
					    fullTempUploadDraftPath: String,
 | 
				
			||||||
    file: MPPFile,
 | 
					    file: MPPFile,
 | 
				
			||||||
    onUpload: (Long, Long) -> Unit
 | 
					    onUpload: OnUploadCallback
 | 
				
			||||||
): TemporalFileId {
 | 
					): TemporalFileId {
 | 
				
			||||||
    val formData = FormData()
 | 
					    val formData = FormData()
 | 
				
			||||||
    val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
 | 
					    val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
 | 
				
			||||||
 | 
					    val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    formData.append(
 | 
					    formData.append(
 | 
				
			||||||
        "data",
 | 
					        "data",
 | 
				
			||||||
@@ -25,7 +28,7 @@ suspend fun tempUpload(
 | 
				
			|||||||
    val request = XMLHttpRequest()
 | 
					    val request = XMLHttpRequest()
 | 
				
			||||||
    request.responseType = XMLHttpRequestResponseType.TEXT
 | 
					    request.responseType = XMLHttpRequestResponseType.TEXT
 | 
				
			||||||
    request.upload.onprogress = {
 | 
					    request.upload.onprogress = {
 | 
				
			||||||
        onUpload(it.loaded.toLong(), it.total.toLong())
 | 
					        subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    request.onload = {
 | 
					    request.onload = {
 | 
				
			||||||
        if (request.status == 200.toShort()) {
 | 
					        if (request.status == 200.toShort()) {
 | 
				
			||||||
@@ -48,12 +51,14 @@ suspend fun tempUpload(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return answer.await()
 | 
					    return answer.await().also {
 | 
				
			||||||
 | 
					        subscope.cancel()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
actual suspend fun HttpClient.tempUpload(
 | 
					actual suspend fun HttpClient.tempUpload(
 | 
				
			||||||
    fullTempUploadDraftPath: String,
 | 
					    fullTempUploadDraftPath: String,
 | 
				
			||||||
    file: MPPFile,
 | 
					    file: MPPFile,
 | 
				
			||||||
    onUpload: (uploaded: Long, count: Long) -> Unit
 | 
					    onUpload: OnUploadCallback
 | 
				
			||||||
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)
 | 
					): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.MPPFile
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.Progress
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
 | 
				
			||||||
 | 
					import io.ktor.client.HttpClient
 | 
				
			||||||
 | 
					import io.ktor.http.Headers
 | 
				
			||||||
 | 
					import io.ktor.utils.io.core.readBytes
 | 
				
			||||||
 | 
					import kotlinx.coroutines.CompletableDeferred
 | 
				
			||||||
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
 | 
					import kotlinx.coroutines.cancel
 | 
				
			||||||
 | 
					import kotlinx.coroutines.currentCoroutineContext
 | 
				
			||||||
 | 
					import kotlinx.coroutines.job
 | 
				
			||||||
 | 
					import kotlinx.serialization.DeserializationStrategy
 | 
				
			||||||
 | 
					import kotlinx.serialization.StringFormat
 | 
				
			||||||
 | 
					import kotlinx.serialization.encodeToString
 | 
				
			||||||
 | 
					import org.khronos.webgl.Int8Array
 | 
				
			||||||
 | 
					import org.w3c.files.Blob
 | 
				
			||||||
 | 
					import org.w3c.xhr.FormData
 | 
				
			||||||
 | 
					import org.w3c.xhr.TEXT
 | 
				
			||||||
 | 
					import org.w3c.xhr.XMLHttpRequest
 | 
				
			||||||
 | 
					import org.w3c.xhr.XMLHttpRequestResponseType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will execute submitting of multipart data request
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
				
			||||||
 | 
					 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
				
			||||||
 | 
					 * in case you wish to pass other source of multipart binary data than regular file
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					actual suspend fun <T> HttpClient.uniUpload(
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					    data: Map<String, Any>,
 | 
				
			||||||
 | 
					    resultDeserializer: DeserializationStrategy<T>,
 | 
				
			||||||
 | 
					    headers: Headers,
 | 
				
			||||||
 | 
					    stringFormat: StringFormat,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback
 | 
				
			||||||
 | 
					): T? {
 | 
				
			||||||
 | 
					    val formData = FormData()
 | 
				
			||||||
 | 
					    val answer = CompletableDeferred<T?>(currentCoroutineContext().job)
 | 
				
			||||||
 | 
					    val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data.forEach { (k, v) ->
 | 
				
			||||||
 | 
					        when (v) {
 | 
				
			||||||
 | 
					            is MPPFile -> formData.append(
 | 
				
			||||||
 | 
					                k,
 | 
				
			||||||
 | 
					                v
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            is UniUploadFileInfo -> formData.append(
 | 
				
			||||||
 | 
					                k,
 | 
				
			||||||
 | 
					                Blob(arrayOf(Int8Array(v.inputAllocator().readBytes().toTypedArray()))),
 | 
				
			||||||
 | 
					                v.fileName.name
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            else -> formData.append(
 | 
				
			||||||
 | 
					                k,
 | 
				
			||||||
 | 
					                stringFormat.encodeToString(v)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val request = XMLHttpRequest()
 | 
				
			||||||
 | 
					    headers.forEach { s, strings ->
 | 
				
			||||||
 | 
					        request.setRequestHeader(s, strings.joinToString())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    request.responseType = XMLHttpRequestResponseType.TEXT
 | 
				
			||||||
 | 
					    request.upload.onprogress = {
 | 
				
			||||||
 | 
					        subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    request.onload = {
 | 
				
			||||||
 | 
					        if (request.status == 200.toShort()) {
 | 
				
			||||||
 | 
					            answer.complete(
 | 
				
			||||||
 | 
					                stringFormat.decodeFromString(resultDeserializer, request.responseText)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            answer.completeExceptionally(Exception("Something went wrong: $it"))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    request.onerror = {
 | 
				
			||||||
 | 
					        answer.completeExceptionally(Exception("Something went wrong: $it"))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    request.open("POST", url, true)
 | 
				
			||||||
 | 
					    request.send(formData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    answer.invokeOnCompletion {
 | 
				
			||||||
 | 
					        runCatching {
 | 
				
			||||||
 | 
					            if (request.readyState != XMLHttpRequest.DONE) {
 | 
				
			||||||
 | 
					                request.abort()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return answer.await().also {
 | 
				
			||||||
 | 
					        subscope.cancel()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,6 +4,6 @@ import dev.inmo.micro_utils.common.MPPFile
 | 
				
			|||||||
import io.ktor.client.request.forms.InputProvider
 | 
					import io.ktor.client.request.forms.InputProvider
 | 
				
			||||||
import io.ktor.utils.io.streams.asInput
 | 
					import io.ktor.utils.io.streams.asInput
 | 
				
			||||||
 | 
					
 | 
				
			||||||
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
 | 
					fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
 | 
				
			||||||
    inputStream().asInput()
 | 
					    inputStream().asInput()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@ internal val MPPFile.mimeType: String
 | 
				
			|||||||
actual suspend fun HttpClient.tempUpload(
 | 
					actual suspend fun HttpClient.tempUpload(
 | 
				
			||||||
    fullTempUploadDraftPath: String,
 | 
					    fullTempUploadDraftPath: String,
 | 
				
			||||||
    file: MPPFile,
 | 
					    file: MPPFile,
 | 
				
			||||||
    onUpload: (Long, Long) -> Unit
 | 
					    onUpload: OnUploadCallback
 | 
				
			||||||
): TemporalFileId {
 | 
					): TemporalFileId {
 | 
				
			||||||
    val inputProvider = file.inputProvider()
 | 
					    val inputProvider = file.inputProvider()
 | 
				
			||||||
    val fileId = submitFormWithBinaryData(
 | 
					    val fileId = submitFormWithBinaryData(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.Progress
 | 
				
			||||||
 | 
					import io.ktor.client.HttpClient
 | 
				
			||||||
 | 
					import io.ktor.client.engine.mergeHeaders
 | 
				
			||||||
 | 
					import io.ktor.client.plugins.onUpload
 | 
				
			||||||
 | 
					import io.ktor.client.request.HttpRequestBuilder
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.InputProvider
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.formData
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.submitForm
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
				
			||||||
 | 
					import io.ktor.client.request.headers
 | 
				
			||||||
 | 
					import io.ktor.client.statement.bodyAsText
 | 
				
			||||||
 | 
					import io.ktor.http.Headers
 | 
				
			||||||
 | 
					import io.ktor.http.HttpHeaders
 | 
				
			||||||
 | 
					import io.ktor.http.HttpStatusCode
 | 
				
			||||||
 | 
					import io.ktor.http.Parameters
 | 
				
			||||||
 | 
					import io.ktor.http.content.PartData
 | 
				
			||||||
 | 
					import kotlinx.serialization.DeserializationStrategy
 | 
				
			||||||
 | 
					import kotlinx.serialization.InternalSerializationApi
 | 
				
			||||||
 | 
					import kotlinx.serialization.SerializationStrategy
 | 
				
			||||||
 | 
					import kotlinx.serialization.StringFormat
 | 
				
			||||||
 | 
					import kotlinx.serialization.encodeToString
 | 
				
			||||||
 | 
					import kotlinx.serialization.serializer
 | 
				
			||||||
 | 
					import java.io.File
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will execute submitting of multipart data request
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
				
			||||||
 | 
					 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
				
			||||||
 | 
					 * in case you wish to pass other source of multipart binary data than regular file
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@OptIn(InternalSerializationApi::class)
 | 
				
			||||||
 | 
					actual suspend fun <T> HttpClient.uniUpload(
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					    data: Map<String, Any>,
 | 
				
			||||||
 | 
					    resultDeserializer: DeserializationStrategy<T>,
 | 
				
			||||||
 | 
					    headers: Headers,
 | 
				
			||||||
 | 
					    stringFormat: StringFormat,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback
 | 
				
			||||||
 | 
					): T? {
 | 
				
			||||||
 | 
					    val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val formData = formData {
 | 
				
			||||||
 | 
					        for (k in data.keys) {
 | 
				
			||||||
 | 
					            val v = data[k] ?: continue
 | 
				
			||||||
 | 
					            when (v) {
 | 
				
			||||||
 | 
					                is File -> append(
 | 
				
			||||||
 | 
					                    k,
 | 
				
			||||||
 | 
					                    v.inputProviderSync(),
 | 
				
			||||||
 | 
					                    Headers.build {
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentType, v.mimeType)
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                is UniUploadFileInfo -> append(
 | 
				
			||||||
 | 
					                    k,
 | 
				
			||||||
 | 
					                    InputProvider(block = v.inputAllocator),
 | 
				
			||||||
 | 
					                    Headers.build {
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentType, v.mimeType)
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                else -> append(
 | 
				
			||||||
 | 
					                    k,
 | 
				
			||||||
 | 
					                    stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val requestBuilder: HttpRequestBuilder.() -> Unit = {
 | 
				
			||||||
 | 
					        headers {
 | 
				
			||||||
 | 
					            appendAll(headers)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        onUpload { bytesSentTotal, contentLength ->
 | 
				
			||||||
 | 
					            onUpload(bytesSentTotal, contentLength)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val response = if (withBinary) {
 | 
				
			||||||
 | 
					        submitFormWithBinaryData(
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            formData,
 | 
				
			||||||
 | 
					            block = requestBuilder
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        submitForm(
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            Parameters.build {
 | 
				
			||||||
 | 
					                for (it in formData) {
 | 
				
			||||||
 | 
					                    val formItem = (it as PartData.FormItem)
 | 
				
			||||||
 | 
					                    append(it.name!!, it.value)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            block = requestBuilder
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if (response.status == HttpStatusCode.OK) {
 | 
				
			||||||
 | 
					        stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.MPPFile
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.filename
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.ktor.common.TemporalFileId
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
 | 
				
			||||||
 | 
					import io.ktor.client.HttpClient
 | 
				
			||||||
 | 
					import io.ktor.client.plugins.onUpload
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.formData
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
				
			||||||
 | 
					import io.ktor.client.statement.bodyAsText
 | 
				
			||||||
 | 
					import io.ktor.http.Headers
 | 
				
			||||||
 | 
					import io.ktor.http.HttpHeaders
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal val MPPFile.mimeType: String
 | 
				
			||||||
 | 
					    get() = getMimeTypeOrAny(filename.extension).raw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual suspend fun HttpClient.tempUpload(
 | 
				
			||||||
 | 
					    fullTempUploadDraftPath: String,
 | 
				
			||||||
 | 
					    file: MPPFile,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback
 | 
				
			||||||
 | 
					): TemporalFileId {
 | 
				
			||||||
 | 
					    val inputProvider = file.inputProvider()
 | 
				
			||||||
 | 
					    val fileId = submitFormWithBinaryData(
 | 
				
			||||||
 | 
					        fullTempUploadDraftPath,
 | 
				
			||||||
 | 
					        formData = formData {
 | 
				
			||||||
 | 
					            append(
 | 
				
			||||||
 | 
					                "data",
 | 
				
			||||||
 | 
					                inputProvider,
 | 
				
			||||||
 | 
					                Headers.build {
 | 
				
			||||||
 | 
					                    append(HttpHeaders.ContentType, file.mimeType)
 | 
				
			||||||
 | 
					                    append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        onUpload(onUpload)
 | 
				
			||||||
 | 
					    }.bodyAsText()
 | 
				
			||||||
 | 
					    return TemporalFileId(fileId)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								ktor/client/src/linuxX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.MPPFile
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.Progress
 | 
				
			||||||
 | 
					import io.ktor.client.HttpClient
 | 
				
			||||||
 | 
					import io.ktor.client.engine.mergeHeaders
 | 
				
			||||||
 | 
					import io.ktor.client.plugins.onUpload
 | 
				
			||||||
 | 
					import io.ktor.client.request.HttpRequestBuilder
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.InputProvider
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.formData
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.submitForm
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
				
			||||||
 | 
					import io.ktor.client.request.headers
 | 
				
			||||||
 | 
					import io.ktor.client.statement.bodyAsText
 | 
				
			||||||
 | 
					import io.ktor.http.Headers
 | 
				
			||||||
 | 
					import io.ktor.http.HttpHeaders
 | 
				
			||||||
 | 
					import io.ktor.http.HttpStatusCode
 | 
				
			||||||
 | 
					import io.ktor.http.Parameters
 | 
				
			||||||
 | 
					import io.ktor.http.content.PartData
 | 
				
			||||||
 | 
					import kotlinx.serialization.DeserializationStrategy
 | 
				
			||||||
 | 
					import kotlinx.serialization.InternalSerializationApi
 | 
				
			||||||
 | 
					import kotlinx.serialization.SerializationStrategy
 | 
				
			||||||
 | 
					import kotlinx.serialization.StringFormat
 | 
				
			||||||
 | 
					import kotlinx.serialization.encodeToString
 | 
				
			||||||
 | 
					import kotlinx.serialization.serializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will execute submitting of multipart data request
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
				
			||||||
 | 
					 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
				
			||||||
 | 
					 * in case you wish to pass other source of multipart binary data than regular file
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@OptIn(InternalSerializationApi::class)
 | 
				
			||||||
 | 
					actual suspend fun <T> HttpClient.uniUpload(
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					    data: Map<String, Any>,
 | 
				
			||||||
 | 
					    resultDeserializer: DeserializationStrategy<T>,
 | 
				
			||||||
 | 
					    headers: Headers,
 | 
				
			||||||
 | 
					    stringFormat: StringFormat,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback
 | 
				
			||||||
 | 
					): T? {
 | 
				
			||||||
 | 
					    val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val formData = formData {
 | 
				
			||||||
 | 
					        for (k in data.keys) {
 | 
				
			||||||
 | 
					            val v = data[k] ?: continue
 | 
				
			||||||
 | 
					            when (v) {
 | 
				
			||||||
 | 
					                is MPPFile -> append(
 | 
				
			||||||
 | 
					                    k,
 | 
				
			||||||
 | 
					                    v.inputProvider(),
 | 
				
			||||||
 | 
					                    Headers.build {
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentType, v.mimeType)
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                is UniUploadFileInfo -> append(
 | 
				
			||||||
 | 
					                    k,
 | 
				
			||||||
 | 
					                    InputProvider(block = v.inputAllocator),
 | 
				
			||||||
 | 
					                    Headers.build {
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentType, v.mimeType)
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                else -> append(
 | 
				
			||||||
 | 
					                    k,
 | 
				
			||||||
 | 
					                    stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val requestBuilder: HttpRequestBuilder.() -> Unit = {
 | 
				
			||||||
 | 
					        headers {
 | 
				
			||||||
 | 
					            appendAll(headers)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        onUpload { bytesSentTotal, contentLength ->
 | 
				
			||||||
 | 
					            onUpload(bytesSentTotal, contentLength)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val response = if (withBinary) {
 | 
				
			||||||
 | 
					        submitFormWithBinaryData(
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            formData,
 | 
				
			||||||
 | 
					            block = requestBuilder
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        submitForm(
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            Parameters.build {
 | 
				
			||||||
 | 
					                for (it in formData) {
 | 
				
			||||||
 | 
					                    val formItem = (it as PartData.FormItem)
 | 
				
			||||||
 | 
					                    append(it.name!!, it.value)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            block = requestBuilder
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if (response.status == HttpStatusCode.OK) {
 | 
				
			||||||
 | 
					        stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualTemporalUpload.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.MPPFile
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.filename
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.ktor.common.TemporalFileId
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
 | 
				
			||||||
 | 
					import io.ktor.client.HttpClient
 | 
				
			||||||
 | 
					import io.ktor.client.plugins.onUpload
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.formData
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
				
			||||||
 | 
					import io.ktor.client.statement.bodyAsText
 | 
				
			||||||
 | 
					import io.ktor.http.Headers
 | 
				
			||||||
 | 
					import io.ktor.http.HttpHeaders
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal val MPPFile.mimeType: String
 | 
				
			||||||
 | 
					    get() = getMimeTypeOrAny(filename.extension).raw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual suspend fun HttpClient.tempUpload(
 | 
				
			||||||
 | 
					    fullTempUploadDraftPath: String,
 | 
				
			||||||
 | 
					    file: MPPFile,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback
 | 
				
			||||||
 | 
					): TemporalFileId {
 | 
				
			||||||
 | 
					    val inputProvider = file.inputProvider()
 | 
				
			||||||
 | 
					    val fileId = submitFormWithBinaryData(
 | 
				
			||||||
 | 
					        fullTempUploadDraftPath,
 | 
				
			||||||
 | 
					        formData = formData {
 | 
				
			||||||
 | 
					            append(
 | 
				
			||||||
 | 
					                "data",
 | 
				
			||||||
 | 
					                inputProvider,
 | 
				
			||||||
 | 
					                Headers.build {
 | 
				
			||||||
 | 
					                    append(HttpHeaders.ContentType, file.mimeType)
 | 
				
			||||||
 | 
					                    append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        onUpload(onUpload)
 | 
				
			||||||
 | 
					    }.bodyAsText()
 | 
				
			||||||
 | 
					    return TemporalFileId(fileId)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								ktor/client/src/mingwX64Main/kotlin/ActualUniUpload.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.MPPFile
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.Progress
 | 
				
			||||||
 | 
					import io.ktor.client.HttpClient
 | 
				
			||||||
 | 
					import io.ktor.client.engine.mergeHeaders
 | 
				
			||||||
 | 
					import io.ktor.client.plugins.onUpload
 | 
				
			||||||
 | 
					import io.ktor.client.request.HttpRequestBuilder
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.InputProvider
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.formData
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.submitForm
 | 
				
			||||||
 | 
					import io.ktor.client.request.forms.submitFormWithBinaryData
 | 
				
			||||||
 | 
					import io.ktor.client.request.headers
 | 
				
			||||||
 | 
					import io.ktor.client.statement.bodyAsText
 | 
				
			||||||
 | 
					import io.ktor.http.Headers
 | 
				
			||||||
 | 
					import io.ktor.http.HttpHeaders
 | 
				
			||||||
 | 
					import io.ktor.http.HttpStatusCode
 | 
				
			||||||
 | 
					import io.ktor.http.Parameters
 | 
				
			||||||
 | 
					import io.ktor.http.content.PartData
 | 
				
			||||||
 | 
					import kotlinx.serialization.DeserializationStrategy
 | 
				
			||||||
 | 
					import kotlinx.serialization.InternalSerializationApi
 | 
				
			||||||
 | 
					import kotlinx.serialization.SerializationStrategy
 | 
				
			||||||
 | 
					import kotlinx.serialization.StringFormat
 | 
				
			||||||
 | 
					import kotlinx.serialization.encodeToString
 | 
				
			||||||
 | 
					import kotlinx.serialization.serializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Will execute submitting of multipart data request
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
 | 
				
			||||||
 | 
					 * [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
 | 
				
			||||||
 | 
					 * in case you wish to pass other source of multipart binary data than regular file
 | 
				
			||||||
 | 
					 * @suppress
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@OptIn(InternalSerializationApi::class)
 | 
				
			||||||
 | 
					actual suspend fun <T> HttpClient.uniUpload(
 | 
				
			||||||
 | 
					    url: String,
 | 
				
			||||||
 | 
					    data: Map<String, Any>,
 | 
				
			||||||
 | 
					    resultDeserializer: DeserializationStrategy<T>,
 | 
				
			||||||
 | 
					    headers: Headers,
 | 
				
			||||||
 | 
					    stringFormat: StringFormat,
 | 
				
			||||||
 | 
					    onUpload: OnUploadCallback
 | 
				
			||||||
 | 
					): T? {
 | 
				
			||||||
 | 
					    val withBinary = data.values.any { it is MPPFile || it is UniUploadFileInfo }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val formData = formData {
 | 
				
			||||||
 | 
					        for (k in data.keys) {
 | 
				
			||||||
 | 
					            val v = data[k] ?: continue
 | 
				
			||||||
 | 
					            when (v) {
 | 
				
			||||||
 | 
					                is MPPFile -> append(
 | 
				
			||||||
 | 
					                    k,
 | 
				
			||||||
 | 
					                    v.inputProvider(),
 | 
				
			||||||
 | 
					                    Headers.build {
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentType, v.mimeType)
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                is UniUploadFileInfo -> append(
 | 
				
			||||||
 | 
					                    k,
 | 
				
			||||||
 | 
					                    InputProvider(block = v.inputAllocator),
 | 
				
			||||||
 | 
					                    Headers.build {
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentType, v.mimeType)
 | 
				
			||||||
 | 
					                        append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                else -> append(
 | 
				
			||||||
 | 
					                    k,
 | 
				
			||||||
 | 
					                    stringFormat.encodeToString(v::class.serializer() as SerializationStrategy<in Any>, v)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val requestBuilder: HttpRequestBuilder.() -> Unit = {
 | 
				
			||||||
 | 
					        headers {
 | 
				
			||||||
 | 
					            appendAll(headers)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        onUpload { bytesSentTotal, contentLength ->
 | 
				
			||||||
 | 
					            onUpload(bytesSentTotal, contentLength)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val response = if (withBinary) {
 | 
				
			||||||
 | 
					        submitFormWithBinaryData(
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            formData,
 | 
				
			||||||
 | 
					            block = requestBuilder
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        submitForm(
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            Parameters.build {
 | 
				
			||||||
 | 
					                for (it in formData) {
 | 
				
			||||||
 | 
					                    val formItem = (it as PartData.FormItem)
 | 
				
			||||||
 | 
					                    append(it.name!!, it.value)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            block = requestBuilder
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return if (response.status == HttpStatusCode.OK) {
 | 
				
			||||||
 | 
					        stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -17,5 +17,8 @@ kotlin {
 | 
				
			|||||||
                api libs.ktor.io
 | 
					                api libs.ktor.io
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        androidMain {
 | 
				
			||||||
 | 
					            dependsOn jvmMain
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.ktor.utils.io.core.Input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typealias LambdaInputProvider = () -> Input
 | 
				
			||||||
@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import io.ktor.utils.io.core.Input
 | 
				
			||||||
 | 
					import io.ktor.utils.io.core.copyTo
 | 
				
			||||||
 | 
					import io.ktor.utils.io.streams.asOutput
 | 
				
			||||||
 | 
					import java.io.File
 | 
				
			||||||
 | 
					import java.io.InputStream
 | 
				
			||||||
 | 
					import java.util.UUID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun Input.downloadToTempFile(
 | 
				
			||||||
 | 
					    fileName: String = UUID.randomUUID().toString(),
 | 
				
			||||||
 | 
					    fileExtension: String? = ".temp",
 | 
				
			||||||
 | 
					    folder: File? = null
 | 
				
			||||||
 | 
					) = File.createTempFile(
 | 
				
			||||||
 | 
					    fileName,
 | 
				
			||||||
 | 
					    fileExtension,
 | 
				
			||||||
 | 
					    folder
 | 
				
			||||||
 | 
					).apply {
 | 
				
			||||||
 | 
					    outputStream().use {
 | 
				
			||||||
 | 
					        copyTo(it.asOutput())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    deleteOnExit()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								ktor/common/src/linuxX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ktor/common/src/linuxX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package dev.inmo.micro_utils.ktor.common
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.MPPFile
 | 
				
			||||||
 | 
					import dev.inmo.micro_utils.common.bytesAllocatorSync
 | 
				
			||||||
 | 
					import io.ktor.utils.io.core.ByteReadPacket
 | 
				
			||||||
 | 
					import io.ktor.utils.io.core.Input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					actual fun MPPFile.input(): Input {
 | 
				
			||||||
 | 
					    return ByteReadPacket(bytesAllocatorSync())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user