mirror of
				https://github.com/InsanusMokrassar/MicroUtils.git
				synced 2025-10-30 19:50:31 +00:00 
			
		
		
		
	Compare commits
	
		
			164 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4d155d0505 | |||
| a169e733d9 | |||
| f081e237c8 | |||
| f412d387fa | |||
| 67354b43e2 | |||
| 39135a4000 | |||
| eaa014cebd | |||
| 856e657f81 | |||
| 3a609e5b66 | |||
| d0022dd599 | |||
| 7ba6eed453 | |||
| beeb6ecc0a | |||
| 7cdc17a714 | |||
| 4765a950a9 | |||
| 65e8137e08 | |||
| ae546dd9ad | |||
| 8110c42be0 | |||
| bd2b5ae5fc | |||
| 2ddfffa6a9 | |||
| a4b54e861d | |||
| c6785f1a4f | |||
| 83fe621c56 | |||
| b3a93e17eb | |||
| 546a391af3 | |||
| 786cf9bd8b | |||
| dfd6fe062d | |||
| b6ef818613 | |||
| b0f9e9c30a | |||
| 7e5c88ddc3 | |||
| 9824c3e00f | |||
| 9171d5ed11 | |||
| a1830ebb82 | |||
| 22d2a3d9bf | |||
| c9b97fc965 | |||
| 51f85becd5 | |||
| a8a281cfb4 | |||
| ddd1304949 | |||
| 86d70b6c02 | |||
| a22bdb39e7 | |||
| 7ae4d5ef95 | |||
| a2038cbefa | |||
| 992091eade | |||
| e3bfead0c5 | |||
| 0de96141fd | |||
| fa18d15c3c | |||
| ea9dbf2371 | |||
| d34e3ec7a9 | |||
| c8833a36af | |||
| a067cb0c0f | |||
| 999c8327bd | |||
| c2ec73c70a | |||
| 702f782fc1 | |||
| 25dbcaaf83 | |||
| b00d454a24 | |||
| dbc921d56d | |||
| 438fefa7a3 | |||
| 5d74eac814 | |||
| 9fb62e1e25 | |||
| 3e366ea73b | |||
| 0ff895bffa | |||
| c5bb120280 | |||
| 4b26a92b37 | |||
| 0a8453b4d2 | |||
| c9872a61b6 | |||
| 149a1aa278 | |||
| 13d0e1b682 | |||
| 6f9be2a9f8 | |||
| 93ba98d993 | |||
| 940ee3df06 | |||
| 2e7bab10fd | |||
|  | 3ed70a37ea | ||
| fe8f80b9d9 | |||
| d81fb32fb9 | |||
| 2877b5532c | |||
| b938b21395 | |||
| 58836359cc | |||
| 5edb0e1331 | |||
| 0f0d0b5d58 | |||
| 46c1887cbe | |||
| 5f231c2212 | |||
| 4e97ce86aa | |||
| 315a7cb29e | |||
| aa7cc503f2 | |||
| 4bbe7e5a80 | |||
| d9c05f38d2 | |||
| cd0c4c9650 | |||
| fc3407f104 | |||
| 3a5544206b | |||
| e17e2f7fb8 | |||
|  | d32c95f143 | ||
| 6d8a8ab018 | |||
| a7dce8fa78 | |||
| ca73ff8e19 | |||
| d01ad10d7d | |||
| 81041ee43c | |||
|  | 6e004c2ae4 | ||
| 0e2fac5b22 | |||
| 269da7f155 | |||
| 3cb6b73ee0 | |||
| a938ee1efb | |||
| 6ea5e2e5a6 | |||
| 617dfb54e0 | |||
| d23e005985 | |||
| e5207f5bc5 | |||
| c96cea8db0 | |||
| 0a8e71d76a | |||
| cf1fd32b08 | |||
| cc4224ea1f | |||
| f4c148bc58 | |||
| 022297ad3f | |||
| 5180d6fc3e | |||
| eeebbff70d | |||
| afc6aeea15 | |||
| 486515eddd | |||
| 0e21699cd1 | |||
| f1678ef7cf | |||
| cea65fc76e | |||
| c9e320b72a | |||
| 555956087d | |||
| b3f468f901 | |||
| f5f7511781 | |||
| 4be1d93f60 | |||
| 7d684608ef | |||
| 2c7fd320eb | |||
| 88ee82e1c6 | |||
| d6402c624e | |||
| 8b9c93bc10 | |||
| 4f5e261d01 | |||
| cf455aebe6 | |||
| 1380d5f8e1 | |||
| 5f65260bfe | |||
| 11f0dcfc01 | |||
| 555b7886a4 | |||
| 3707a6c0ce | |||
| 4c8d92b4c3 | |||
| 8bbd33c896 | |||
| ac33a3580f | |||
| a64a32fbe6 | |||
| 9493e97a38 | |||
| 88bd770260 | |||
| a7bd33b7bf | |||
| 73c724a2e5 | |||
| d8cf3c6726 | |||
| 15dee238b5 | |||
| c70626734e | |||
| 5a765ea1bc | |||
| 8215f9d2c6 | |||
| d2e6d2ec80 | |||
| 3718181e8f | |||
| 0d825cf424 | |||
| 28a804d988 | |||
| 9e4bb9d678 | |||
| 9c40d7da3d | |||
| 2b76ad0aa9 | |||
| e4b619e050 | |||
| 36c09feaf2 | |||
| 2d68321503 | |||
| 85455ab21c | |||
| 18d63eb980 | |||
| 2e429e9704 | |||
| f4af28059b | |||
| c1476bd075 | |||
| 16c720fddd | |||
| 8b4b4a5eca | 
							
								
								
									
										211
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										211
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,216 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 0.19.9 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Koin`: `3.4.2` -> `3.4.3` | ||||||
|  | * `Startup`: | ||||||
|  |     * Now it is possible to start application in synchronous way | ||||||
|  |  | ||||||
|  | ## 0.19.8 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Coroutines`: `1.7.2` -> `1.7.3` | ||||||
|  |     * `Kotlin`: `1.8.20` -> `1.8.22` | ||||||
|  |     * `Compose`: `1.4.1` -> `1.4.3` | ||||||
|  |     * `Okio`: `3.3.0` -> `3.4.0` | ||||||
|  |     * `RecyclerView`: `1.3.0` -> `1.3.1` | ||||||
|  |     * `Fragment`: `1.6.0` -> `1.6.1` | ||||||
|  | * `Repos`: | ||||||
|  |     * Fixes In `KeyValueRepo.clear()` of almost all inheritors of `KeyValueRepo` | ||||||
|  |     * `Cache`: | ||||||
|  |         * All full caches got `skipStartInvalidate` property. By default, this property is `false` and fully caching repos | ||||||
|  |           will be automatically invalidated on start of their work | ||||||
|  |  | ||||||
|  | ## 0.19.7 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Coroutines`: `1.7.1` -> `1.7.2` | ||||||
|  |  | ||||||
|  | ## 0.19.6 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Coroutines`: `1.6.4` -> `1.7.1` | ||||||
|  |     * `Ktor`: `2.3.1` -> `2.3.2` | ||||||
|  |     * `Compose`: `1.4.0` -> `1.4.1` | ||||||
|  |  | ||||||
|  | ## 0.19.5 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Generator`: | ||||||
|  |         * Fixes in new type generation | ||||||
|  |  | ||||||
|  | ## 0.19.4 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Koin`: `3.4.1` -> `3.4.2` | ||||||
|  |     * `Android Fragments`: `1.5.7` -> `1.6.0` | ||||||
|  | * `Koin` | ||||||
|  |     * `Generator` | ||||||
|  |         * Fixes in new generic generator part | ||||||
|  |  | ||||||
|  | ## 0.19.3 | ||||||
|  |  | ||||||
|  | * `Koin` | ||||||
|  |     * `Generator` | ||||||
|  |         * New getter methods now available with opportunity to use parameters | ||||||
|  |         * Old notation `*Single` and `*Factory` is deprecated since this release. With old | ||||||
|  |           will be generated new `single*` and `factory*` notations for new generations | ||||||
|  |         * Add opportunity to use generic-oriented koin definitions | ||||||
|  |  | ||||||
|  | ## 0.19.2 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.3.0` -> `2.3.1` | ||||||
|  |     * `Koin`: `3.4.0` -> `3.4.1` | ||||||
|  |     * `Uuid`: `0.7.0` -> `0.7.1` | ||||||
|  |  | ||||||
|  | ## 0.19.1 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Korlibs`: `4.0.1` -> `4.0.3` | ||||||
|  |     * `Kotlin Poet`: `1.13.2` -> `1.14.0` | ||||||
|  |  | ||||||
|  | ## 0.19.0 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Korlibs`: `3.4.0` -> `4.0.1` | ||||||
|  |  | ||||||
|  | ## 0.18.4 | ||||||
|  |  | ||||||
|  | * `Koin`: | ||||||
|  |     * New extension `lazyInject` | ||||||
|  |  | ||||||
|  | ## 0.18.3 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Serialization`: `1.5.0` -> `1.5.1` | ||||||
|  |     * `Android Cor Ktx`: `1.10.0` -> `1.10.1` | ||||||
|  |  | ||||||
|  | ## 0.18.2 | ||||||
|  |  | ||||||
|  | * `Startup`: | ||||||
|  |     * Now internal `Json` is fully customizable | ||||||
|  |  | ||||||
|  | ## 0.18.1 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * Add `MapDiff` | ||||||
|  | * `Coroutines`: | ||||||
|  |     * Add `SmartMutex` | ||||||
|  |  | ||||||
|  | ## 0.18.0 | ||||||
|  |  | ||||||
|  | **ALL PREVIOUSLY DEPRECATED FUNCTIONALITY HAVE BEEN REMOVED** | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Android Fragments`: `1.5.6` -> `1.5.7` | ||||||
|  | * `Ktor`: | ||||||
|  |     * `Server`: | ||||||
|  |         * Now it is possible to take query parameters as list | ||||||
|  | * `Repos`: | ||||||
|  |     * `Common`: | ||||||
|  |         * New `WriteKeyValuesRepo.removeWithValue` | ||||||
|  |     * `Cache`: | ||||||
|  |         * Rename full caching factories functions to `fullyCached` | ||||||
|  |  | ||||||
|  | ## 0.17.8 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.2.4` -> `2.3.0` | ||||||
|  |  | ||||||
|  | ## 0.17.7 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Android CoreKtx`: `1.9.0` -> `1.10.0` | ||||||
|  | * `Startup`: | ||||||
|  |     * Add support of `linuxX64` and `mingwX64` platforms | ||||||
|  |  | ||||||
|  | ## 0.17.6 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Kotlin`: `1.8.10` -> `1.8.20` | ||||||
|  |     * `KSLog`: `1.0.0` -> `1.1.1` | ||||||
|  |     * `Compose`: `1.3.1` -> `1.4.0` | ||||||
|  |     * `Koin`: `3.3.2` -> `3.4.0` | ||||||
|  |     * `RecyclerView`: `1.2.1` -> `1.3.0` | ||||||
|  |     * `Fragment`: `1.5.5` -> `1.5.6` | ||||||
|  | * Experimentally (`!!!`) add `linuxX64` and `mingwX64` targets | ||||||
|  |  | ||||||
|  | ## 0.17.5 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * Conversations of number primitives with bounds care | ||||||
|  | * `Repos`: | ||||||
|  |     * `Common`: | ||||||
|  |         * By default, `getAll` for repos will take all the size of repo as page size | ||||||
|  |         * New extension for all built-in repos `maxPagePagination` | ||||||
|  |     * All the repos got `getAll` functions | ||||||
|  |  | ||||||
|  | ## 0.17.4 | ||||||
|  |  | ||||||
|  | * `Serialization`: | ||||||
|  |     * `Mapper`: | ||||||
|  |         * Module inited | ||||||
|  | * `Versions`: | ||||||
|  |     * `Compose`: `1.3.1-rc02` -> `1.3.1` | ||||||
|  |  | ||||||
|  | ## 0.17.3 | ||||||
|  |  | ||||||
|  | * `Common`: | ||||||
|  |     * Add `fixed` extensions for `Float` and `Double` | ||||||
|  |     * New function `emptyDiff` | ||||||
|  |     * Now you may pass custom `comparisonFun` to all `diff` functions | ||||||
|  |  | ||||||
|  | ## 0.17.2 | ||||||
|  |  | ||||||
|  | * `FSM`: | ||||||
|  |     * `DefaultStatesManager.onUpdateContextsConflictResolver` and `DefaultStatesManager.onStartContextsConflictResolver` now return `false` by default | ||||||
|  |  | ||||||
|  | ## 0.17.1 | ||||||
|  |  | ||||||
|  | * **Hotfix** for absence of jvm dependencies in android modules | ||||||
|  | * `Versions`: | ||||||
|  |     * `Ktor`: `2.2.3` -> `2.2.4` | ||||||
|  |  | ||||||
|  | ## 0.17.0 | ||||||
|  |  | ||||||
|  | * `Versions`: | ||||||
|  |     * `Kotlin`: `1.7.20` -> `1.8.10` | ||||||
|  |     * `Serialization`: `1.4.1` -> `1.5.0` | ||||||
|  |     * `KSLog`: `0.5.4` -> `1.0.0` | ||||||
|  |     * `AppCompat`: `1.6.0` -> `1.6.1` | ||||||
|  |  | ||||||
|  | ## 0.16.13 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Generator`: | ||||||
|  |         * Module has been created | ||||||
|  |  | ||||||
|  | ## 0.16.12 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Exposed`: | ||||||
|  |         * `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach | ||||||
|  | * `Koin`: | ||||||
|  |     * `Generator`: | ||||||
|  |         * Module has been created | ||||||
|  |  | ||||||
|  | ## 0.16.11 | ||||||
|  |  | ||||||
|  | * `LanguageCodes`: | ||||||
|  |     * In android and JVM targets now available `toJavaLocale` and from Java `Locale` conversations from/to | ||||||
|  |       `IetfLanguageCode` | ||||||
|  |  | ||||||
|  | ## 0.16.10 | ||||||
|  |  | ||||||
|  | * `Repos`: | ||||||
|  |     * `Cache`: | ||||||
|  |         * New transformer type: `ReadCRUDFromKeyValueRepo` | ||||||
|  |         * New transformer type: `ReadKeyValueFromCRUDRepo` | ||||||
|  | * `Pagination`: | ||||||
|  |     * New `paginate` extensions with `reversed` support for `List`/`Set` | ||||||
|  |  | ||||||
| ## 0.16.9 | ## 0.16.9 | ||||||
|  |  | ||||||
| * `Versions`: | * `Versions`: | ||||||
|   | |||||||
| @@ -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 | ||||||
| @@ -16,12 +17,17 @@ buildscript { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | plugins { | ||||||
|  |     alias(libs.plugins.versions) | ||||||
|  | } | ||||||
|  |  | ||||||
| allprojects { | allprojects { | ||||||
|     repositories { |     repositories { | ||||||
|         mavenLocal() |         mavenLocal() | ||||||
|         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 | ||||||
| @@ -36,3 +42,4 @@ allprojects { | |||||||
|  |  | ||||||
| apply from: "./extensions.gradle" | apply from: "./extensions.gradle" | ||||||
| apply from: "./github_release.gradle" | apply from: "./github_release.gradle" | ||||||
|  | apply from: "./versions_plugin_setup.gradle" | ||||||
|   | |||||||
| @@ -18,6 +18,18 @@ kotlin { | |||||||
|                 api project(":micro_utils.coroutines") |                 api project(":micro_utils.coroutines") | ||||||
|                 api libs.android.fragment |                 api libs.android.fragment | ||||||
|             } |             } | ||||||
|  |             dependsOn jvmMain | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         linuxX64Main { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.okio | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         mingwX64Main { | ||||||
|  |             dependencies { | ||||||
|  |                 api libs.okio | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -34,7 +34,11 @@ data class Diff<T> internal constructor( | |||||||
|      */ |      */ | ||||||
|     val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>, |     val replaced: List<Pair<@Serializable(IndexedValueSerializer::class) IndexedValue<T>, @Serializable(IndexedValueSerializer::class) IndexedValue<T>>>, | ||||||
|     val added: List<@Serializable(IndexedValueSerializer::class) 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>?>>, | ||||||
| @@ -43,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 { | ||||||
| @@ -104,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 | ||||||
| @@ -132,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>> | ||||||
| @@ -143,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) | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -169,6 +199,23 @@ inline fun <T> Iterable<T>.calculateStrictDiff( | |||||||
|     other: Iterable<T> |     other: Iterable<T> | ||||||
| ) = calculateDiff(other, strictComparison = true) | ) = calculateDiff(other, strictComparison = true) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Applies [diff] to [this] [MutableList] | ||||||
|  |  */ | ||||||
|  | fun <T> MutableList<T>.applyDiff( | ||||||
|  |     diff: Diff<T> | ||||||
|  | ) { | ||||||
|  |     for (i in diff.removed.indices.sortedDescending()) { | ||||||
|  |         removeAt(diff.removed[i].index) | ||||||
|  |     } | ||||||
|  |     diff.added.forEach { (i, t) -> | ||||||
|  |         add(i, t) | ||||||
|  |     } | ||||||
|  |     diff.replaced.forEach { (_, new) -> | ||||||
|  |         set(new.index, new.value) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this] |  * This method call [calculateDiff] with strict mode [strictComparison] and then apply differences to [this] | ||||||
|  * mutable list |  * mutable list | ||||||
| @@ -177,13 +224,26 @@ fun <T> MutableList<T>.applyDiff( | |||||||
|     source: Iterable<T>, |     source: Iterable<T>, | ||||||
|     strictComparison: Boolean = false |     strictComparison: Boolean = false | ||||||
| ): Diff<T> = calculateDiff(source, strictComparison).also { | ): Diff<T> = calculateDiff(source, strictComparison).also { | ||||||
|     for (i in it.removed.indices.sortedDescending()) { |     applyDiff(it) | ||||||
|         removeAt(it.removed[i].index) |  | ||||||
|     } |  | ||||||
|     it.added.forEach { (i, t) -> |  | ||||||
|         add(i, t) |  | ||||||
|     } |  | ||||||
|     it.replaced.forEach { (_, new) -> |  | ||||||
|         set(new.index, new.value) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This method call [calculateDiff] and then apply differences to [this] | ||||||
|  |  * mutable list | ||||||
|  |  */ | ||||||
|  | fun <T> MutableList<T>.applyDiff( | ||||||
|  |     source: Iterable<T>, | ||||||
|  |     comparisonFun: (T?, T?) -> Boolean | ||||||
|  | ): Diff<T> = calculateDiff(source, comparisonFun).also { | ||||||
|  |     applyDiff(it) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Reverse [this] [Diff]. Result will contain [Diff.added] on [Diff.removed] (and vice-verse), all the | ||||||
|  |  * [Diff.replaced] values will be reversed too | ||||||
|  |  */ | ||||||
|  | fun <T> Diff<T>.reversed() = Diff( | ||||||
|  |     removed = added, | ||||||
|  |     replaced = replaced.map { it.second to it.first }, | ||||||
|  |     added = removed | ||||||
|  | ) | ||||||
|   | |||||||
| @@ -0,0 +1,135 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Contains diff based on the comparison of objects with the same [K]. | ||||||
|  |  * | ||||||
|  |  * @param removed Contains map with keys removed from parent map | ||||||
|  |  * @param changed Contains map with keys values changed new map in comparison with old one | ||||||
|  |  * @param added Contains map with new keys and values | ||||||
|  |  */ | ||||||
|  | data class MapDiff<K, V> @Warning(warning) constructor( | ||||||
|  |     val removed: Map<K, V>, | ||||||
|  |     val changed: Map<K, Pair<V, V>>, | ||||||
|  |     val added: Map<K, V> | ||||||
|  | ) { | ||||||
|  |     fun isEmpty() = removed.isEmpty() && changed.isEmpty() && added.isEmpty() | ||||||
|  |     inline fun isNotEmpty() = !isEmpty() | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         private const val warning = "This feature can be changed without any warranties. Use with caution and only in case you know what you are doing" | ||||||
|  |         fun <K, V> empty() = MapDiff<K, V>(emptyMap(), emptyMap(), emptyMap()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private inline fun <K, V> createCompareFun( | ||||||
|  |     strictComparison: Boolean | ||||||
|  | ): (K, V, V) -> Boolean = if (strictComparison) { | ||||||
|  |     { _, first, second -> first === second } | ||||||
|  | } else { | ||||||
|  |     { _, first, second -> first == second } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Compare [this] [Map] with the [other] one in principle when [other] is newer than [this] | ||||||
|  |  * | ||||||
|  |  * @param compareFun Will be used to determine changed values | ||||||
|  |  */ | ||||||
|  | fun <K, V> Map<K, V>.diff( | ||||||
|  |     other: Map<K, V>, | ||||||
|  |     compareFun: (K, V, V) -> Boolean | ||||||
|  | ): MapDiff<K, V> { | ||||||
|  |     val removed: Map<K, V> = (keys - other.keys).associateWith { | ||||||
|  |         getValue(it) | ||||||
|  |     } | ||||||
|  |     val added: Map<K, V> = (other.keys - keys).associateWith { | ||||||
|  |         other.getValue(it) | ||||||
|  |     } | ||||||
|  |     val changed = keys.intersect(other.keys).mapNotNull { | ||||||
|  |         val old = getValue(it) | ||||||
|  |         val new = other.getValue(it) | ||||||
|  |         if (compareFun(it, old, new)) { | ||||||
|  |             return@mapNotNull null | ||||||
|  |         } else { | ||||||
|  |             it to (old to new) | ||||||
|  |         } | ||||||
|  |     }.toMap() | ||||||
|  |  | ||||||
|  |     return MapDiff( | ||||||
|  |         removed, | ||||||
|  |         changed, | ||||||
|  |         added | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Compare [this] [Map] with the [other] one in principle when [other] is newer than [this] | ||||||
|  |  * | ||||||
|  |  * @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard | ||||||
|  |  * `equals` will be used | ||||||
|  |  */ | ||||||
|  | fun <K, V> Map<K, V>.diff( | ||||||
|  |     other: Map<K, V>, | ||||||
|  |     strictComparison: Boolean = false | ||||||
|  | ): MapDiff<K, V> = diff( | ||||||
|  |     other, | ||||||
|  |     compareFun = createCompareFun(strictComparison) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will apply [mapDiff] to [this] [MutableMap] | ||||||
|  |  */ | ||||||
|  | fun <K, V> MutableMap<K, V>.applyDiff( | ||||||
|  |     mapDiff: MapDiff<K, V> | ||||||
|  | ) { | ||||||
|  |     mapDiff.apply { | ||||||
|  |         removed.keys.forEach { remove(it) } | ||||||
|  |         changed.forEach { (k, oldNew) -> | ||||||
|  |             put(k, oldNew.second) | ||||||
|  |         } | ||||||
|  |         added.forEach { (k, new) -> | ||||||
|  |             put(k, new) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will apply changes with [from] map into [this] one | ||||||
|  |  * | ||||||
|  |  * @param compareFun Will be used to determine changed values | ||||||
|  |  * | ||||||
|  |  * @return [MapDiff] applied to [this] [MutableMap] | ||||||
|  |  */ | ||||||
|  | fun <K, V> MutableMap<K, V>.applyDiff( | ||||||
|  |     from: Map<K, V>, | ||||||
|  |     compareFun: (K, V, V) -> Boolean | ||||||
|  | ): MapDiff<K, V> { | ||||||
|  |     return diff(from, compareFun).also { | ||||||
|  |         applyDiff(it) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will apply changes with [from] map into [this] one | ||||||
|  |  * | ||||||
|  |  * @param strictComparison If true, will use strict (===) comparison for the values' comparison. Otherwise, standard | ||||||
|  |  * `equals` will be used | ||||||
|  |  * | ||||||
|  |  * @return [MapDiff] applied to [this] [MutableMap] | ||||||
|  |  */ | ||||||
|  | fun <K, V> MutableMap<K, V>.applyDiff( | ||||||
|  |     from: Map<K, V>, | ||||||
|  |     strictComparison: Boolean = false | ||||||
|  | ): MapDiff<K, V> = applyDiff( | ||||||
|  |     from, | ||||||
|  |     compareFun = createCompareFun(strictComparison) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Reverse [this] [MapDiff]. Result will contain [MapDiff.added] on [MapDiff.removed] (and vice-verse), all the | ||||||
|  |  * [MapDiff.changed] values will be reversed too | ||||||
|  |  */ | ||||||
|  | fun <K, V> MapDiff<K, V>.reversed(): MapDiff<K, V> = MapDiff( | ||||||
|  |     removed = added, | ||||||
|  |     changed = changed.mapValues { (_, oldNew) -> oldNew.second to oldNew.first }, | ||||||
|  |     added = removed | ||||||
|  | ) | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Long] to [Int] with bounds of [Int.MIN_VALUE] and [Int.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Long.toCoercedInt(): Int = coerceIn(Int.MIN_VALUE.toLong(), Int.MAX_VALUE.toLong()).toInt() | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Long] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Long.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toLong(), Short.MAX_VALUE.toLong()).toShort() | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Long] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Long.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toLong(), Byte.MAX_VALUE.toLong()).toByte() | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Int] to [Short] with bounds of [Short.MIN_VALUE] and [Short.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Int.toCoercedShort(): Short = coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()).toShort() | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Int] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Int.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()).toByte() | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Convert [this] [Short] to [Byte] with bounds of [Byte.MIN_VALUE] and [Byte.MAX_VALUE] | ||||||
|  |  */ | ||||||
|  | fun Short.toCoercedByte(): Byte = coerceIn(Byte.MIN_VALUE.toShort(), Byte.MAX_VALUE.toShort()).toByte() | ||||||
| @@ -0,0 +1,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,12 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import java.math.BigDecimal | ||||||
|  | import java.math.RoundingMode | ||||||
|  |  | ||||||
|  | actual fun Float.fixed(signs: Int): Float = BigDecimal.valueOf(this.toDouble()) | ||||||
|  |     .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP) | ||||||
|  |     .toFloat(); | ||||||
|  |  | ||||||
|  | actual fun Double.fixed(signs: Int): Double = BigDecimal.valueOf(this) | ||||||
|  |     .setScale(signs.coerceIn(FixedSignsRange), RoundingMode.HALF_UP) | ||||||
|  |     .toDouble(); | ||||||
							
								
								
									
										36
									
								
								common/src/linuxX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								common/src/linuxX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import okio.FileSystem | ||||||
|  | import okio.Path | ||||||
|  | import okio.use | ||||||
|  |  | ||||||
|  | actual typealias MPPFile = Path | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.filename: FileName | ||||||
|  |     get() = FileName(toString()) | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.filesize: Long | ||||||
|  |     get() = FileSystem.SYSTEM.openReadOnly(this).use { | ||||||
|  |         it.size() | ||||||
|  |     } | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator | ||||||
|  |     get() = { | ||||||
|  |         FileSystem.SYSTEM.read(this) { | ||||||
|  |             readByteArray() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator | ||||||
|  |     get() = { | ||||||
|  |         bytesAllocatorSync() | ||||||
|  |     } | ||||||
							
								
								
									
										26
									
								
								common/src/linuxX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								common/src/linuxX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import kotlinx.cinterop.ByteVar | ||||||
|  | import kotlinx.cinterop.allocArray | ||||||
|  | import kotlinx.cinterop.memScoped | ||||||
|  | import kotlinx.cinterop.toKString | ||||||
|  | import platform.posix.snprintf | ||||||
|  | import platform.posix.sprintf | ||||||
|  |  | ||||||
|  | actual fun Float.fixed(signs: Int): Float { | ||||||
|  |     return memScoped { | ||||||
|  |         val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) | ||||||
|  |  | ||||||
|  |         sprintf(buff, "%.${signs}f", this@fixed) | ||||||
|  |         buff.toKString().toFloat() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | actual fun Double.fixed(signs: Int): Double { | ||||||
|  |     return memScoped { | ||||||
|  |         val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) | ||||||
|  |  | ||||||
|  |         sprintf(buff, "%.${signs}f", this@fixed) | ||||||
|  |         buff.toKString().toDouble() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								common/src/mingwX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								common/src/mingwX64Main/kotlin/ActualMPPFile.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import okio.FileSystem | ||||||
|  | import okio.Path | ||||||
|  | import okio.use | ||||||
|  |  | ||||||
|  | actual typealias MPPFile = Path | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.filename: FileName | ||||||
|  |     get() = FileName(toString()) | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.filesize: Long | ||||||
|  |     get() = FileSystem.SYSTEM.openReadOnly(this).use { | ||||||
|  |         it.size() | ||||||
|  |     } | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator | ||||||
|  |     get() = { | ||||||
|  |         FileSystem.SYSTEM.read(this) { | ||||||
|  |             readByteArray() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | /** | ||||||
|  |  * @suppress | ||||||
|  |  */ | ||||||
|  | actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator | ||||||
|  |     get() = { | ||||||
|  |         bytesAllocatorSync() | ||||||
|  |     } | ||||||
							
								
								
									
										26
									
								
								common/src/mingwX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								common/src/mingwX64Main/kotlin/fixed.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | package dev.inmo.micro_utils.common | ||||||
|  |  | ||||||
|  | import kotlinx.cinterop.ByteVar | ||||||
|  | import kotlinx.cinterop.allocArray | ||||||
|  | import kotlinx.cinterop.memScoped | ||||||
|  | import kotlinx.cinterop.toKString | ||||||
|  | import platform.posix.snprintf | ||||||
|  | import platform.posix.sprintf | ||||||
|  |  | ||||||
|  | actual fun Float.fixed(signs: Int): Float { | ||||||
|  |     return memScoped { | ||||||
|  |         val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2) | ||||||
|  |  | ||||||
|  |         sprintf(buff, "%.${signs}f", this@fixed) | ||||||
|  |         buff.toKString().toFloat() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | actual fun Double.fixed(signs: Int): Double { | ||||||
|  |     return memScoped { | ||||||
|  |         val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2) | ||||||
|  |  | ||||||
|  |         sprintf(buff, "%.${signs}f", this@fixed) | ||||||
|  |         buff.toKString().toDouble() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,21 +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 |  | ||||||
|  |  | ||||||
| @Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(initial, scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState")) |  | ||||||
| fun <T> Flow<T>.toMutableState( |  | ||||||
|     initial: T, |  | ||||||
|     scope: CoroutineScope |  | ||||||
| ): MutableState<T> = asMutableComposeState(initial, scope) |  | ||||||
|  |  | ||||||
| @Deprecated("Duplicated functionality", ReplaceWith("asMutableComposeState(scope)", "dev.inmo.micro_utils.coroutines.compose.asMutableComposeState")) |  | ||||||
| @Suppress("NOTHING_TO_INLINE") |  | ||||||
| inline fun <T> StateFlow<T>.toMutableState( |  | ||||||
|     scope: CoroutineScope |  | ||||||
| ): MutableState<T> = asMutableComposeState(scope) |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,136 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.currentCoroutineContext | ||||||
|  | import kotlinx.coroutines.flow.MutableStateFlow | ||||||
|  | import kotlinx.coroutines.flow.StateFlow | ||||||
|  | import kotlinx.coroutines.flow.asStateFlow | ||||||
|  | import kotlinx.coroutines.flow.first | ||||||
|  | import kotlinx.coroutines.isActive | ||||||
|  | import kotlinx.coroutines.sync.Mutex | ||||||
|  | import kotlinx.coroutines.sync.withLock | ||||||
|  | import kotlin.contracts.ExperimentalContracts | ||||||
|  | import kotlin.contracts.InvocationKind | ||||||
|  | import kotlin.contracts.contract | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * It is interface which will work like classic [Mutex], but in difference have [lockStateFlow] for listening of the | ||||||
|  |  * [SmartMutex] state. | ||||||
|  |  * | ||||||
|  |  * There is [Mutable] and [Immutable] realizations. In case you are owner and manager current state of lock, you need | ||||||
|  |  * [Mutable] [SmartMutex]. Otherwise, [Immutable]. | ||||||
|  |  * | ||||||
|  |  * Any [Mutable] [SmartMutex] may produce its [Immutable] variant which will contains [lockStateFlow] equal to its | ||||||
|  |  * [Mutable] creator | ||||||
|  |  */ | ||||||
|  | sealed interface SmartMutex { | ||||||
|  |     val lockStateFlow: StateFlow<Boolean> | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * * True - locked | ||||||
|  |      * * False - unlocked | ||||||
|  |      */ | ||||||
|  |     val isLocked: Boolean | ||||||
|  |         get() = lockStateFlow.value | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Immutable variant of [SmartMutex]. In fact will depend on the owner of [lockStateFlow] | ||||||
|  |      */ | ||||||
|  |     class Immutable(override val lockStateFlow: StateFlow<Boolean>) : SmartMutex | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Mutable variant of [SmartMutex]. With that variant you may [lock] and [unlock]. Besides, you may create | ||||||
|  |      * [Immutable] variant of [this] instance with [immutable] factory | ||||||
|  |      * | ||||||
|  |      * @param locked Preset state of [isLocked] and its internal [_lockStateFlow] | ||||||
|  |      */ | ||||||
|  |     class Mutable(locked: Boolean = false) : SmartMutex { | ||||||
|  |         private val _lockStateFlow = MutableStateFlow<Boolean>(locked) | ||||||
|  |         override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow() | ||||||
|  |  | ||||||
|  |         private val internalChangesMutex = Mutex() | ||||||
|  |  | ||||||
|  |         fun immutable() = Immutable(lockStateFlow) | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Holds call until this [SmartMutex] will be re-locked. That means that while [isLocked] == true, [holds] will | ||||||
|  |          * wait for [isLocked] == false and then try to lock | ||||||
|  |          */ | ||||||
|  |         suspend fun lock() { | ||||||
|  |             do { | ||||||
|  |                 waitUnlock() | ||||||
|  |                 val shouldContinue = internalChangesMutex.withLock { | ||||||
|  |                     if (_lockStateFlow.value) { | ||||||
|  |                         true | ||||||
|  |                     } else { | ||||||
|  |                         _lockStateFlow.value = true | ||||||
|  |                         false | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } while (shouldContinue && currentCoroutineContext().isActive) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * Will try to lock this [SmartMutex] immediataly | ||||||
|  |          * | ||||||
|  |          * @return True if lock was successful. False otherwise | ||||||
|  |          */ | ||||||
|  |         suspend fun tryLock(): Boolean { | ||||||
|  |             return if (!_lockStateFlow.value) { | ||||||
|  |                 internalChangesMutex.withLock { | ||||||
|  |                     if (!_lockStateFlow.value) { | ||||||
|  |                         _lockStateFlow.value = true | ||||||
|  |                         true | ||||||
|  |                     } else { | ||||||
|  |                         false | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 false | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /** | ||||||
|  |          * If [isLocked] == true - will change it to false and return true. If current call will not unlock this | ||||||
|  |          * [SmartMutex] - false | ||||||
|  |          */ | ||||||
|  |         suspend fun unlock(): Boolean { | ||||||
|  |             return if (_lockStateFlow.value) { | ||||||
|  |                 internalChangesMutex.withLock { | ||||||
|  |                     if (_lockStateFlow.value) { | ||||||
|  |                         _lockStateFlow.value = false | ||||||
|  |                         true | ||||||
|  |                     } else { | ||||||
|  |                         false | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 false | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will call [SmartMutex.Mutable.lock], then execute [action] and return the result after [SmartMutex.Mutable.unlock] | ||||||
|  |  */ | ||||||
|  | @OptIn(ExperimentalContracts::class) | ||||||
|  | suspend inline fun <T> SmartMutex.Mutable.withLock(action: () -> T): T { | ||||||
|  |     contract { | ||||||
|  |         callsInPlace(action, InvocationKind.EXACTLY_ONCE) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     lock() | ||||||
|  |     try { | ||||||
|  |         return action() | ||||||
|  |     } finally { | ||||||
|  |         unlock() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will wait until the [SmartMutex.lockStateFlow] of [this] instance will be false. | ||||||
|  |  * | ||||||
|  |  * Anyway, after the end of this block there are no any guaranties that [SmartMutex.isLocked] == false due to the fact | ||||||
|  |  * that some other parties may lock it again | ||||||
|  |  */ | ||||||
|  | suspend fun SmartMutex.waitUnlock() = lockStateFlow.first { !it } | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | package dev.inmo.micro_utils.coroutines | ||||||
|  |  | ||||||
|  | import kotlinx.coroutines.CancellationException | ||||||
|  | import kotlinx.coroutines.Job | ||||||
|  | import org.w3c.dom.Image | ||||||
|  |  | ||||||
|  | suspend fun preloadImage(src: String): Image { | ||||||
|  |     val image = Image() | ||||||
|  |     image.src = src | ||||||
|  |  | ||||||
|  |     val job = Job() | ||||||
|  |  | ||||||
|  |     image.addEventListener("load", { | ||||||
|  |         runCatching { job.complete() } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     runCatchingSafely { | ||||||
|  |         job.join() | ||||||
|  |     }.onFailure { | ||||||
|  |         if (it is CancellationException) { | ||||||
|  |             image.src = "" | ||||||
|  |         } | ||||||
|  |     }.getOrThrow() | ||||||
|  |  | ||||||
|  |     return image | ||||||
|  | } | ||||||
| @@ -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 korlibs.crypto.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() |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,10 +13,10 @@ repositories { | |||||||
|  |  | ||||||
| kotlin { | kotlin { | ||||||
|     jvm() |     jvm() | ||||||
| //    js(IR) { |     js(IR) { | ||||||
| //        browser() |         browser() | ||||||
| //        nodejs() |         nodejs() | ||||||
| //    } |     } | ||||||
|     android {} |     android {} | ||||||
|  |  | ||||||
|     sourceSets { |     sourceSets { | ||||||
| @@ -26,44 +26,44 @@ kotlin { | |||||||
|  |  | ||||||
|                 project.parent.subprojects.forEach { |                 project.parent.subprojects.forEach { | ||||||
|                     if ( |                     if ( | ||||||
|                         it != project |                             it != project | ||||||
|                         && it.hasProperty("kotlin") |                                     && it.hasProperty("kotlin") | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("commonMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | ||||||
| //                        && it.kotlin.sourceSets.any { it.name.contains("jsMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("jvmMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("jvmMain") } | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("androidMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("androidMain") } | ||||||
|                     ) { |                     ) { | ||||||
|                         api it |                         api it | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| //        jsMain { |         jsMain { | ||||||
| //            dependencies { |             dependencies { | ||||||
| //                implementation kotlin('stdlib') |                 implementation kotlin('stdlib') | ||||||
|  |  | ||||||
| //                project.parent.subprojects.forEach { |                 project.parent.subprojects.forEach { | ||||||
| //                    if ( |                     if ( | ||||||
| //                        it != project |                             it != project | ||||||
| //                        && it.hasProperty("kotlin") |                                     && it.hasProperty("kotlin") | ||||||
| //                        && it.kotlin.sourceSets.any { it.name.contains("commonMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | ||||||
| //                        && it.kotlin.sourceSets.any { it.name.contains("jsMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("jsMain") } | ||||||
| //                    ) { |                     ) { | ||||||
| //                        api it |                         api it | ||||||
| //                    } |                     } | ||||||
| //                } |                 } | ||||||
| //            } |             } | ||||||
| //        } |         } | ||||||
|         jvmMain { |         jvmMain { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 implementation kotlin('stdlib') |                 implementation kotlin('stdlib') | ||||||
|  |  | ||||||
|                 project.parent.subprojects.forEach { |                 project.parent.subprojects.forEach { | ||||||
|                     if ( |                     if ( | ||||||
|                         it != project |                             it != project | ||||||
|                         && it.hasProperty("kotlin") |                                     && it.hasProperty("kotlin") | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("commonMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("jvmMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("jvmMain") } | ||||||
|                     ) { |                     ) { | ||||||
|                         api it |                         api it | ||||||
|                     } |                     } | ||||||
| @@ -76,10 +76,10 @@ kotlin { | |||||||
|  |  | ||||||
|                 project.parent.subprojects.forEach { |                 project.parent.subprojects.forEach { | ||||||
|                     if ( |                     if ( | ||||||
|                         it != project |                             it != project | ||||||
|                         && it.hasProperty("kotlin") |                                     && it.hasProperty("kotlin") | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("commonMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("commonMain") } | ||||||
|                         && it.kotlin.sourceSets.any { it.name.contains("androidMain") } |                                     && it.kotlin.sourceSets.any { it.name.contains("androidMain") } | ||||||
|                     ) { |                     ) { | ||||||
|                         api it |                         api it | ||||||
|                     } |                     } | ||||||
| @@ -116,9 +116,9 @@ tasks.dokkaHtml { | |||||||
|             sourceRoots.setFrom(findSourcesWithName("commonMain")) |             sourceRoots.setFrom(findSourcesWithName("commonMain")) | ||||||
|         } |         } | ||||||
|  |  | ||||||
| //        named("jsMain") { |         named("jsMain") { | ||||||
| //            sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain")) |             sourceRoots.setFrom(findSourcesWithName("jsMain")) | ||||||
| //        } |         } | ||||||
|  |  | ||||||
|         named("jvmMain") { |         named("jvmMain") { | ||||||
|             sourceRoots.setFrom(findSourcesWithName("jvmMain")) |             sourceRoots.setFrom(findSourcesWithName("jvmMain")) | ||||||
|   | |||||||
| @@ -23,12 +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" | ||||||
|         mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.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.16.9 | version=0.19.9 | ||||||
| android_code_version=177 | android_code_version=205 | ||||||
|   | |||||||
| @@ -1,37 +1,44 @@ | |||||||
| [versions] | [versions] | ||||||
|  |  | ||||||
| kt = "1.7.20" | kt = "1.8.22" | ||||||
| kt-serialization = "1.4.1" | kt-serialization = "1.5.1" | ||||||
| kt-coroutines = "1.6.4" | kt-coroutines = "1.7.3" | ||||||
|  |  | ||||||
| kslog = "0.5.4" | kslog = "1.1.1" | ||||||
|  |  | ||||||
| jb-compose = "1.2.2" | jb-compose = "1.4.3" | ||||||
| jb-exposed = "0.41.1" | jb-exposed = "0.41.1" | ||||||
| jb-dokka = "1.7.20" | jb-dokka = "1.8.20" | ||||||
|  |  | ||||||
| klock = "3.4.0" | korlibs = "4.0.3" | ||||||
| uuid = "0.6.0" | uuid = "0.7.1" | ||||||
|  |  | ||||||
| ktor = "2.2.3" | ktor = "2.3.2" | ||||||
|  |  | ||||||
| gh-release = "2.4.1" | gh-release = "2.4.1" | ||||||
|  |  | ||||||
| koin = "3.3.2" | koin = "3.4.3" | ||||||
|  |  | ||||||
| android-gradle = "7.3.0" | okio = "3.4.0" | ||||||
| dexcount = "3.1.0" |  | ||||||
|  |  | ||||||
| android-coreKtx = "1.9.0" | ksp = "1.8.22-1.0.11" | ||||||
| android-recyclerView = "1.2.1" | kotlin-poet = "1.14.2" | ||||||
| android-appCompat = "1.6.0" |  | ||||||
| android-fragment = "1.5.5" | versions = "0.47.0" | ||||||
| android-espresso = "3.4.0" |  | ||||||
| android-test = "1.1.3" | android-gradle = "7.4.2" | ||||||
|  | dexcount = "4.0.0" | ||||||
|  |  | ||||||
|  | android-coreKtx = "1.10.1" | ||||||
|  | android-recyclerView = "1.3.1" | ||||||
|  | android-appCompat = "1.6.1" | ||||||
|  | android-fragment = "1.6.1" | ||||||
|  | android-espresso = "3.5.1" | ||||||
|  | android-test = "1.1.5" | ||||||
|  |  | ||||||
| android-props-minSdk = "21" | android-props-minSdk = "21" | ||||||
| android-props-compileSdk = "33" | android-props-compileSdk = "33" | ||||||
| android-props-buildTools = "33.0.1" | android-props-buildTools = "33.0.2" | ||||||
|  |  | ||||||
| [libraries] | [libraries] | ||||||
|  |  | ||||||
| @@ -64,7 +71,8 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti | |||||||
|  |  | ||||||
| kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } | kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } | ||||||
|  |  | ||||||
| klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" } | klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" } | ||||||
|  | krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" } | ||||||
| uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } | ||||||
| koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } | ||||||
|  |  | ||||||
| @@ -83,9 +91,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" } | ||||||
| @@ -94,3 +111,5 @@ buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gra | |||||||
| [plugins] | [plugins] | ||||||
|  |  | ||||||
| jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" } | jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" } | ||||||
|  |  | ||||||
|  | versions = { id = "com.github.ben-manes.versions", version.ref = "versions" } | ||||||
|   | |||||||
							
								
								
									
										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.2-bin.zip | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|   | |||||||
							
								
								
									
										118
									
								
								jvm.publish.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								jvm.publish.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | apply plugin: 'maven-publish' | ||||||
|  |  | ||||||
|  | task javadocJar(type: Jar) { | ||||||
|  |     from javadoc | ||||||
|  |     classifier = 'javadoc' | ||||||
|  | } | ||||||
|  | task sourcesJar(type: Jar) { | ||||||
|  |     from sourceSets.main.allSource | ||||||
|  |     classifier = 'sources' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | publishing { | ||||||
|  |     publications { | ||||||
|  |         maven(MavenPublication) { | ||||||
|  |             from components.java | ||||||
|  |  | ||||||
|  |             artifact javadocJar | ||||||
|  |             artifact sourcesJar | ||||||
|  |  | ||||||
|  |             pom { | ||||||
|  |                 resolveStrategy = Closure.DELEGATE_FIRST | ||||||
|  |  | ||||||
|  |                 description = "It is set of projects with micro tools for avoiding of routines coding" | ||||||
|  |                 name = "${project.name}" | ||||||
|  |                 url = "https://github.com/InsanusMokrassar/MicroUtils/" | ||||||
|  |  | ||||||
|  |                 scm { | ||||||
|  |                     developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git" | ||||||
|  |                     url = "https://github.com/InsanusMokrassar/MicroUtils.git" | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 developers { | ||||||
|  |                      | ||||||
|  |                         developer { | ||||||
|  |                             id = "InsanusMokrassar" | ||||||
|  |                             name = "Aleksei Ovsiannikov" | ||||||
|  |                             email = "ovsyannikov.alexey95@gmail.com" | ||||||
|  |                         } | ||||||
|  |                      | ||||||
|  |  | ||||||
|  |                         developer { | ||||||
|  |                             id = "000Sanya" | ||||||
|  |                             name = "Syrov Aleksandr" | ||||||
|  |                             email = "000sanya.000sanya@gmail.com" | ||||||
|  |                         } | ||||||
|  |                      | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 licenses { | ||||||
|  |                      | ||||||
|  |                         license { | ||||||
|  |                             name = "Apache Software License 2.0" | ||||||
|  |                             url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE" | ||||||
|  |                         } | ||||||
|  |                      | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             repositories { | ||||||
|  |                 if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) { | ||||||
|  |                     maven { | ||||||
|  |                         name = "GithubPackages" | ||||||
|  |                         url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils") | ||||||
|  |                  | ||||||
|  |                         credentials { | ||||||
|  |                             username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER') | ||||||
|  |                             password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD') | ||||||
|  |                         } | ||||||
|  |                  | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) { | ||||||
|  |                     maven { | ||||||
|  |                         name = "Gitea" | ||||||
|  |                         url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") | ||||||
|  |                  | ||||||
|  |                         credentials(HttpHeaderCredentials) { | ||||||
|  |                             name = "Authorization" | ||||||
|  |                             value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') | ||||||
|  |                         } | ||||||
|  |                  | ||||||
|  |                         authentication { | ||||||
|  |                             header(HttpHeaderAuthentication) | ||||||
|  |                         } | ||||||
|  |                  | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { | ||||||
|  |                     maven { | ||||||
|  |                         name = "sonatype" | ||||||
|  |                         url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") | ||||||
|  |                  | ||||||
|  |                         credentials { | ||||||
|  |                             username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') | ||||||
|  |                             password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') | ||||||
|  |                         } | ||||||
|  |                  | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | if (project.hasProperty("signing.gnupg.keyName")) { | ||||||
|  |     apply plugin: 'signing' | ||||||
|  |      | ||||||
|  |     signing { | ||||||
|  |         useGpgCmd() | ||||||
|  |      | ||||||
|  |         sign publishing.publications | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     task signAll { | ||||||
|  |         tasks.withType(Sign).forEach { | ||||||
|  |             dependsOn(it) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								jvm.publish.kpsb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								jvm.publish.kpsb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | {"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"} | ||||||
							
								
								
									
										100
									
								
								koin/generator/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								koin/generator/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | # Koin generator | ||||||
|  |  | ||||||
|  | It is Kotlin Symbol Processing generator for `Koin` module in `MicroUtils`. | ||||||
|  |  | ||||||
|  | 1. [What may do this generator](#what-may-do-this-generator) | ||||||
|  | 2. [How to add generator](#how-to-add-generator) | ||||||
|  |  | ||||||
|  | ## What may do this generator | ||||||
|  |  | ||||||
|  | Let's imagine you want to have shortcuts in koin, to get something easily: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | val koin: Koin// some initialization | ||||||
|  |  | ||||||
|  | val someUrl = koin.serverUrl | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | So, in that case you may mark containing file with next annotation (in the beginning of file): | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | @file:GenerateKoinDefinition("serverUrl", String::class, nullable = false) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | If file is called like `Sample.kt`, will be generated file `GeneratedDefinitionsSample.kt` with next content: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | public val Scope.serverUrl: String | ||||||
|  |   get() = get(named("serverUrl")) | ||||||
|  |  | ||||||
|  | public val Koin.serverUrl: String | ||||||
|  |   get() = get(named("serverUrl")) | ||||||
|  |  | ||||||
|  | public fun Module.serverUrlSingle(createdAtStart: Boolean = false, | ||||||
|  |     definition: Definition<String>): KoinDefinition<String> = | ||||||
|  |     single(named("serverUrl"), createdAtStart = createdAtStart, definition = definition) | ||||||
|  |  | ||||||
|  | public fun Module.serverUrlFactory(definition: Definition<String>): | ||||||
|  |     KoinDefinition<String> = factory(named("serverUrl"), definition = definition) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Besides, you may use the generics: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | @file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Will generate: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | public val Scope.sampleInfo: Sample<G1, G2> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | public val Koin.sampleInfo: Sample<G1, G2> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | public fun Module.sampleInfoSingle(createdAtStart: Boolean = false, | ||||||
|  |     definition: Definition<Sample<G1, G2>>): KoinDefinition<Sample<G1, G2>> = | ||||||
|  |     single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) | ||||||
|  |  | ||||||
|  | public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>): | ||||||
|  |     KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | In case you wish not to generate single: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | @file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false, generateSingle = false) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | And you will take next code: | ||||||
|  |  | ||||||
|  | ```kotlin | ||||||
|  | public val Scope.sampleInfo: Sample<G1, G2> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | public val Koin.sampleInfo: Sample<G1, G2> | ||||||
|  |   get() = get(named("sampleInfo")) | ||||||
|  |  | ||||||
|  | public fun Module.sampleInfoFactory(definition: Definition<Sample<G1, G2>>): | ||||||
|  |     KoinDefinition<Sample<G1, G2>> = factory(named("sampleInfo"), definition = definition) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## How to add generator | ||||||
|  |  | ||||||
|  | **Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project** | ||||||
|  |  | ||||||
|  | **Note: $microutils_version in the version of MicroUtils library in your project** | ||||||
|  |  | ||||||
|  | 1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`) | ||||||
|  | 2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"` | ||||||
|  | 3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.koin.generator:$microutils_version`: | ||||||
|  |    ```groovy | ||||||
|  |     dependencies { | ||||||
|  |         add("kspCommonMainMetadata", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in commonMain of your multiplatform module | ||||||
|  |         add("kspJvm", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in main of your JVM module | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     ksp { // this generator do not require any arguments and we should left `ksp` empty | ||||||
|  |     } | ||||||
|  |     ``` | ||||||
							
								
								
									
										20
									
								
								koin/generator/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								koin/generator/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | plugins { | ||||||
|  |     id "org.jetbrains.kotlin.jvm" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | apply from: "$publishJvmOnlyPath" | ||||||
|  |  | ||||||
|  | repositories { | ||||||
|  |     mavenCentral() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     api project(":micro_utils.koin") | ||||||
|  |     api libs.kotlin.poet | ||||||
|  |     api libs.ksp | ||||||
|  | } | ||||||
|  |  | ||||||
|  | java { | ||||||
|  |     sourceCompatibility = JavaVersion.VERSION_1_8 | ||||||
|  |     targetCompatibility = JavaVersion.VERSION_1_8 | ||||||
|  | } | ||||||
							
								
								
									
										332
									
								
								koin/generator/src/main/kotlin/Processor.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								koin/generator/src/main/kotlin/Processor.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,332 @@ | |||||||
|  | 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.google.devtools.ksp.symbol.Modifier | ||||||
|  | import com.squareup.kotlinpoet.AnnotationSpec | ||||||
|  | import com.squareup.kotlinpoet.ClassName | ||||||
|  | import com.squareup.kotlinpoet.CodeBlock | ||||||
|  | import com.squareup.kotlinpoet.FileSpec | ||||||
|  | import com.squareup.kotlinpoet.FunSpec | ||||||
|  | import com.squareup.kotlinpoet.KModifier | ||||||
|  | import com.squareup.kotlinpoet.ParameterSpec | ||||||
|  | import com.squareup.kotlinpoet.ParameterizedTypeName | ||||||
|  | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy | ||||||
|  | import com.squareup.kotlinpoet.PropertySpec | ||||||
|  | import com.squareup.kotlinpoet.TypeName | ||||||
|  | import com.squareup.kotlinpoet.TypeVariableName | ||||||
|  | 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.GenerateGenericKoinDefinition | ||||||
|  | import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition | ||||||
|  | import org.koin.core.Koin | ||||||
|  | import org.koin.core.module.Module | ||||||
|  | import org.koin.core.parameter.ParametersDefinition | ||||||
|  | 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") | ||||||
|  |  | ||||||
|  |     private fun FileSpec.Builder.addCodeForType( | ||||||
|  |         targetType: TypeName, | ||||||
|  |         name: String, | ||||||
|  |         nullable: Boolean, | ||||||
|  |         generateSingle: Boolean, | ||||||
|  |         generateFactory: Boolean, | ||||||
|  |     ) { | ||||||
|  |         val targetTypeAsGenericType = (targetType as? TypeVariableName) ?.copy(reified = true) | ||||||
|  |  | ||||||
|  |         fun addGetterProperty( | ||||||
|  |             receiver: KClass<*> | ||||||
|  |         ) { | ||||||
|  |             addProperty( | ||||||
|  |                 PropertySpec.builder( | ||||||
|  |                     name, | ||||||
|  |                     targetType, | ||||||
|  |                 ).apply { | ||||||
|  |                     addKdoc( | ||||||
|  |                         """ | ||||||
|  |                             @return Definition by key "${name}" | ||||||
|  |                         """.trimIndent() | ||||||
|  |                     ) | ||||||
|  |                     getter( | ||||||
|  |                         FunSpec.getterBuilder().apply { | ||||||
|  |                             targetTypeAsGenericType ?.let { | ||||||
|  |                                 addModifiers(KModifier.INLINE) | ||||||
|  |                             } | ||||||
|  |                             addCode( | ||||||
|  |                                 "return " + (if (nullable) { | ||||||
|  |                                     "getOrNull" | ||||||
|  |                                 } else { | ||||||
|  |                                     "get" | ||||||
|  |                                 }) + "(named(\"${name}\"))" | ||||||
|  |                             ) | ||||||
|  |                         }.build() | ||||||
|  |                     ) | ||||||
|  |                     targetTypeAsGenericType ?.let { | ||||||
|  |                         addTypeVariable(it) | ||||||
|  |                     } | ||||||
|  |                     receiver(receiver) | ||||||
|  |                 }.build() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (targetTypeAsGenericType == null) { | ||||||
|  |             addGetterProperty(Scope::class) | ||||||
|  |             addGetterProperty(Koin::class) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val parametersDefinitionClassName = ClassName( | ||||||
|  |             "org.koin.core.parameter", | ||||||
|  |             "ParametersDefinition" | ||||||
|  |         ) | ||||||
|  |         fun addGetterMethod( | ||||||
|  |             receiver: KClass<*> | ||||||
|  |         ) { | ||||||
|  |             addFunction( | ||||||
|  |                 FunSpec.builder( | ||||||
|  |                     name | ||||||
|  |                 ).apply { | ||||||
|  |                     addKdoc( | ||||||
|  |                         """ | ||||||
|  |                             @return Definition by key "${name}" with [parameters] | ||||||
|  |                         """.trimIndent() | ||||||
|  |                     ) | ||||||
|  |                     receiver(receiver) | ||||||
|  |                     addParameter( | ||||||
|  |                         ParameterSpec( | ||||||
|  |                             "parameters", | ||||||
|  |                             parametersDefinitionClassName.let { | ||||||
|  |                                 if (targetTypeAsGenericType != null) { | ||||||
|  |                                     it.copy(nullable = true) | ||||||
|  |                                 } else { | ||||||
|  |                                     it | ||||||
|  |                                 } | ||||||
|  |                             }, | ||||||
|  |                             KModifier.NOINLINE | ||||||
|  |                         ).toBuilder().apply { | ||||||
|  |                             if (targetTypeAsGenericType != null) { | ||||||
|  |                                 defaultValue("null") | ||||||
|  |                             } | ||||||
|  |                         }.build() | ||||||
|  |                     ) | ||||||
|  |                     addModifiers(KModifier.INLINE) | ||||||
|  |                     targetTypeAsGenericType ?.let { | ||||||
|  |                         addTypeVariable(it) | ||||||
|  |                         returns(it.copy(nullable = nullable)) | ||||||
|  |                     } ?: returns(targetType) | ||||||
|  |                     addCode( | ||||||
|  |                         "return " + (if (nullable) { | ||||||
|  |                             "getOrNull" | ||||||
|  |                         } else { | ||||||
|  |                             "get" | ||||||
|  |                         }) + "(named(\"${name}\"), parameters)" | ||||||
|  |                     ) | ||||||
|  |                 }.build() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         addGetterMethod(Scope::class) | ||||||
|  |         addGetterMethod(Koin::class) | ||||||
|  |  | ||||||
|  |         fun FunSpec.Builder.addDefinitionParameter() { | ||||||
|  |             val definitionModifiers = if (targetTypeAsGenericType == null) { | ||||||
|  |                 arrayOf() | ||||||
|  |             } else { | ||||||
|  |                 arrayOf(KModifier.NOINLINE) | ||||||
|  |             } | ||||||
|  |             addParameter( | ||||||
|  |                 ParameterSpec.builder( | ||||||
|  |                     "definition", | ||||||
|  |                     definitionClassName.parameterizedBy(targetType.copy(nullable = false)), | ||||||
|  |                     *definitionModifiers | ||||||
|  |                 ).build() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (generateSingle) { | ||||||
|  |             fun FunSpec.Builder.configure( | ||||||
|  |                 useInstead: String? = null | ||||||
|  |             ) { | ||||||
|  |                 addKdoc( | ||||||
|  |                     """ | ||||||
|  |                         Will register [definition] with [org.koin.core.module.Module.single] and key "${name}" | ||||||
|  |                     """.trimIndent() | ||||||
|  |                 ) | ||||||
|  |                 receiver(Module::class) | ||||||
|  |                 addParameter( | ||||||
|  |                     ParameterSpec.builder( | ||||||
|  |                         "createdAtStart", | ||||||
|  |                         Boolean::class | ||||||
|  |                     ).apply { | ||||||
|  |                         defaultValue("false") | ||||||
|  |                     }.build() | ||||||
|  |                 ) | ||||||
|  |                 addDefinitionParameter() | ||||||
|  |                 returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false))) | ||||||
|  |                 addCode( | ||||||
|  |                     "return single(named(\"${name}\"), createdAtStart = createdAtStart, definition = definition)" | ||||||
|  |                 ) | ||||||
|  |                 targetTypeAsGenericType ?.let { | ||||||
|  |                     addTypeVariable(it) | ||||||
|  |                     addModifiers(KModifier.INLINE) | ||||||
|  |                 } | ||||||
|  |                 if (useInstead != null) { | ||||||
|  |                     addAnnotation( | ||||||
|  |                         AnnotationSpec.builder( | ||||||
|  |                             Deprecated::class | ||||||
|  |                         ).apply { | ||||||
|  |                             addMember( | ||||||
|  |                                 CodeBlock.of( | ||||||
|  |                                     """ | ||||||
|  |                                         "This definition is old style and should not be used anymore. Use $useInstead instead" | ||||||
|  |                                     """.trimIndent() | ||||||
|  |                                 ) | ||||||
|  |                             ) | ||||||
|  |                             addMember(CodeBlock.of("ReplaceWith(\"$useInstead\")")) | ||||||
|  |                         }.build() | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             val actualSingleName = "single${name.replaceFirstChar { it.uppercase() }}" | ||||||
|  |             if (targetTypeAsGenericType == null) { // classic type | ||||||
|  |                 addFunction( | ||||||
|  |                     FunSpec.builder("${name}Single").apply { configure(actualSingleName) }.build() | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             addFunction( | ||||||
|  |                 FunSpec.builder(actualSingleName).apply { configure() }.build() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (generateFactory) { | ||||||
|  |             fun FunSpec.Builder.configure( | ||||||
|  |                 useInstead: String? = null | ||||||
|  |             ) { | ||||||
|  |                 addKdoc( | ||||||
|  |                     """ | ||||||
|  |                         Will register [definition] with [org.koin.core.module.Module.factory] and key "${name}" | ||||||
|  |                     """.trimIndent() | ||||||
|  |                 ) | ||||||
|  |                 receiver(Module::class) | ||||||
|  |                 addDefinitionParameter() | ||||||
|  |                 returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false))) | ||||||
|  |                 addCode( | ||||||
|  |                     "return factory(named(\"${name}\"), definition = definition)" | ||||||
|  |                 ) | ||||||
|  |                 targetTypeAsGenericType ?.let { | ||||||
|  |                     addTypeVariable(it) | ||||||
|  |                     addModifiers(KModifier.INLINE) | ||||||
|  |                 } | ||||||
|  |                 if (useInstead != null) { | ||||||
|  |                     addAnnotation( | ||||||
|  |                         AnnotationSpec.builder( | ||||||
|  |                             Deprecated::class | ||||||
|  |                         ).apply { | ||||||
|  |                             addMember( | ||||||
|  |                                 CodeBlock.of( | ||||||
|  |                                     """ | ||||||
|  |                                         "This definition is old style and should not be used anymore. Use $useInstead instead" | ||||||
|  |                                     """.trimIndent() | ||||||
|  |                                 ) | ||||||
|  |                             ) | ||||||
|  |                             addMember(CodeBlock.of("ReplaceWith(\"$useInstead\")")) | ||||||
|  |                         }.build() | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             val actualFactoryName = "factory${name.replaceFirstChar { it.uppercase() }}" | ||||||
|  |             if (targetTypeAsGenericType == null) { // classic type | ||||||
|  |                 addFunction( | ||||||
|  |                     FunSpec.builder("${name}Factory").apply { configure(useInstead = actualFactoryName) }.build() | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             addFunction( | ||||||
|  |                 FunSpec.builder(actualFactoryName).apply { configure() }.build() | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |         addImport("org.koin.core.qualifier", "named") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @OptIn(KspExperimental::class) | ||||||
|  |     override fun process(resolver: Resolver): List<KSAnnotated> { | ||||||
|  |         (resolver.getSymbolsWithAnnotation( | ||||||
|  |             GenerateKoinDefinition::class.qualifiedName!! | ||||||
|  |         ) + resolver.getSymbolsWithAnnotation( | ||||||
|  |             GenerateGenericKoinDefinition::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 | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|  |                     addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory) | ||||||
|  |                 } | ||||||
|  |                 ksFile.getAnnotationsByType(GenerateGenericKoinDefinition::class).forEach { | ||||||
|  |                     val targetType = TypeVariableName("T", Any::class) | ||||||
|  |                     addCodeForType(targetType, it.name, it.nullable, it.generateSingle, it.generateFactory) | ||||||
|  |                 } | ||||||
|  |             }.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,124 @@ | |||||||
|  | // 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.Any | ||||||
|  | import kotlin.Boolean | ||||||
|  | import kotlin.Deprecated | ||||||
|  | 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.parameter.ParametersDefinition | ||||||
|  | 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")) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @return Definition by key "sampleInfo" with [parameters] | ||||||
|  |  */ | ||||||
|  | public inline fun Scope.sampleInfo(noinline parameters: ParametersDefinition): Test<String> = | ||||||
|  |     get(named("sampleInfo"), parameters) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @return Definition by key "sampleInfo" with [parameters] | ||||||
|  |  */ | ||||||
|  | public inline fun Koin.sampleInfo(noinline parameters: ParametersDefinition): Test<String> = | ||||||
|  |     get(named("sampleInfo"), parameters) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo" | ||||||
|  |  */ | ||||||
|  | @Deprecated( | ||||||
|  |   "This definition is old style and should not be used anymore. Use singleSampleInfo instead", | ||||||
|  |   ReplaceWith("singleSampleInfo"), | ||||||
|  | ) | ||||||
|  | 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.single] and key "sampleInfo" | ||||||
|  |  */ | ||||||
|  | public fun Module.singleSampleInfo(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" | ||||||
|  |  */ | ||||||
|  | @Deprecated( | ||||||
|  |   "This definition is old style and should not be used anymore. Use factorySampleInfo instead", | ||||||
|  |   ReplaceWith("factorySampleInfo"), | ||||||
|  | ) | ||||||
|  | public fun Module.sampleInfoFactory(definition: Definition<Test<String>>): | ||||||
|  |     KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo" | ||||||
|  |  */ | ||||||
|  | public fun Module.factorySampleInfo(definition: Definition<Test<String>>): | ||||||
|  |     KoinDefinition<Test<String>> = factory(named("sampleInfo"), definition = definition) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @return Definition by key "test" with [parameters] | ||||||
|  |  */ | ||||||
|  | public inline fun <reified T : Any> Scope.test(noinline parameters: ParametersDefinition? = null): T | ||||||
|  |     = get(named("test"), parameters) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @return Definition by key "test" with [parameters] | ||||||
|  |  */ | ||||||
|  | public inline fun <reified T : Any> Koin.test(noinline parameters: ParametersDefinition? = null): T | ||||||
|  |     = get(named("test"), parameters) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will register [definition] with [org.koin.core.module.Module.single] and key "test" | ||||||
|  |  */ | ||||||
|  | public inline fun <reified T : Any> Module.singleTest(createdAtStart: Boolean = false, noinline | ||||||
|  |     definition: Definition<T>): KoinDefinition<T> = single(named("test"), createdAtStart = | ||||||
|  |     createdAtStart, definition = definition) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will register [definition] with [org.koin.core.module.Module.factory] and key "test" | ||||||
|  |  */ | ||||||
|  | public inline fun <reified T : Any> Module.factoryTest(noinline definition: Definition<T>): | ||||||
|  |     KoinDefinition<T> = factory(named("test"), definition = definition) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @return Definition by key "testNullable" with [parameters] | ||||||
|  |  */ | ||||||
|  | public inline fun <reified T : Any> Scope.testNullable(noinline parameters: ParametersDefinition? = | ||||||
|  |     null): T? = getOrNull(named("testNullable"), parameters) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @return Definition by key "testNullable" with [parameters] | ||||||
|  |  */ | ||||||
|  | public inline fun <reified T : Any> Koin.testNullable(noinline parameters: ParametersDefinition? = | ||||||
|  |     null): T? = getOrNull(named("testNullable"), parameters) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will register [definition] with [org.koin.core.module.Module.single] and key "testNullable" | ||||||
|  |  */ | ||||||
|  | public inline fun <reified T : Any> Module.singleTestNullable(createdAtStart: Boolean = false, | ||||||
|  |     noinline definition: Definition<T>): KoinDefinition<T> = single(named("testNullable"), | ||||||
|  |     createdAtStart = createdAtStart, definition = definition) | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Will register [definition] with [org.koin.core.module.Module.factory] and key "testNullable" | ||||||
|  |  */ | ||||||
|  | public inline fun <reified T : Any> Module.factoryTestNullable(noinline definition: Definition<T>): | ||||||
|  |     KoinDefinition<T> = factory(named("testNullable"), definition = definition) | ||||||
							
								
								
									
										16
									
								
								koin/generator/test/src/commonMain/kotlin/Test.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								koin/generator/test/src/commonMain/kotlin/Test.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | @file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false) | ||||||
|  | @file:GenerateGenericKoinDefinition("test", nullable = false) | ||||||
|  | @file:GenerateGenericKoinDefinition("testNullable", nullable = true) | ||||||
|  | package dev.inmo.micro_utils.koin.generator.test | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.koin.annotations.GenerateGenericKoinDefinition | ||||||
|  | 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"/> | ||||||
							
								
								
									
										40
									
								
								koin/src/commonMain/kotlin/GetWithDefinition.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								koin/src/commonMain/kotlin/GetWithDefinition.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.Koin | ||||||
|  | import org.koin.core.definition.BeanDefinition | ||||||
|  | import org.koin.core.definition.KoinDefinition | ||||||
|  | import org.koin.core.instance.InstanceFactory | ||||||
|  | import org.koin.core.parameter.ParametersDefinition | ||||||
|  | import org.koin.core.scope.Scope | ||||||
|  |  | ||||||
|  | fun <T> Koin.get(definition: BeanDefinition<T>, parameters: ParametersDefinition? = null): T = get( | ||||||
|  |     definition.primaryType, | ||||||
|  |     definition.qualifier, | ||||||
|  |     parameters | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | fun <T> Koin.get(definition: InstanceFactory<T>, parameters: ParametersDefinition? = null): T = get( | ||||||
|  |     definition.beanDefinition, | ||||||
|  |     parameters | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | fun <T> Koin.get(definition: KoinDefinition<T>, parameters: ParametersDefinition? = null): T = get( | ||||||
|  |     definition.factory, | ||||||
|  |     parameters | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | fun <T> Scope.get(definition: BeanDefinition<T>, parameters: ParametersDefinition? = null): T = get( | ||||||
|  |     definition.primaryType, | ||||||
|  |     definition.qualifier, | ||||||
|  |     parameters | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | fun <T> Scope.get(definition: InstanceFactory<T>, parameters: ParametersDefinition? = null): T = get( | ||||||
|  |     definition.beanDefinition, | ||||||
|  |     parameters | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | fun <T> Scope.get(definition: KoinDefinition<T>, parameters: ParametersDefinition? = null): T = get( | ||||||
|  |     definition.factory, | ||||||
|  |     parameters | ||||||
|  | ) | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | 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 GenerateGenericKoinDefinition( | ||||||
|  |     val name: String, | ||||||
|  |     val nullable: Boolean = true, | ||||||
|  |     val generateSingle: Boolean = true, | ||||||
|  |     val generateFactory: Boolean = true | ||||||
|  | ) | ||||||
| @@ -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 | ||||||
|  | ) | ||||||
							
								
								
									
										27
									
								
								koin/src/jvmMain/kotlin/LazyInject.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								koin/src/jvmMain/kotlin/LazyInject.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | package dev.inmo.micro_utils.koin | ||||||
|  |  | ||||||
|  | import org.koin.core.parameter.ParametersDefinition | ||||||
|  | import org.koin.core.qualifier.Qualifier | ||||||
|  | import org.koin.java.KoinJavaComponent | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  |  | ||||||
|  | fun <T> lazyInject( | ||||||
|  |     kClassFactory: () -> KClass<*>, | ||||||
|  |     qualifier: Qualifier? = null, | ||||||
|  |     parameters: ParametersDefinition? = null | ||||||
|  | ): Lazy<T> { | ||||||
|  |     return lazy(LazyThreadSafetyMode.SYNCHRONIZED) { | ||||||
|  |         KoinJavaComponent.get(kClassFactory().java, qualifier, parameters) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun <T> lazyInject( | ||||||
|  |     kClass: KClass<*>, | ||||||
|  |     qualifier: Qualifier? = null, | ||||||
|  |     parameters: ParametersDefinition? = null | ||||||
|  | ): Lazy<T> = lazyInject({ kClass }, qualifier, parameters) | ||||||
|  |  | ||||||
|  | inline fun <reified T> lazyInject( | ||||||
|  |     qualifier: Qualifier? = null, | ||||||
|  |     noinline parameters: ParametersDefinition? = null | ||||||
|  | ): Lazy<T> = lazyInject(T::class, qualifier, parameters) | ||||||
| @@ -15,9 +15,20 @@ kotlin { | |||||||
|                 api libs.ktor.client |                 api libs.ktor.client | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         androidMain { |         androidMain { | ||||||
|             dependsOn jvmMain |             dependsOn jvmMain | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         linuxX64Main { | ||||||
|  |             dependencies { | ||||||
|  |                 api internalProject("micro_utils.mime_types") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         mingwX64Main { | ||||||
|  |             dependencies { | ||||||
|  |                 api internalProject("micro_utils.mime_types") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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() | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -7,5 +7,3 @@ import io.ktor.utils.io.streams.asInput | |||||||
| fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) { | fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) { | ||||||
|     inputStream().asInput() |     inputStream().asInput() | ||||||
| } | } | ||||||
|  |  | ||||||
| actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync() |  | ||||||
|   | |||||||
| @@ -44,7 +44,8 @@ actual suspend fun <T> HttpClient.uniUpload( | |||||||
|     val withBinary = data.values.any { it is File || it is UniUploadFileInfo } |     val withBinary = data.values.any { it is File || it is UniUploadFileInfo } | ||||||
|  |  | ||||||
|     val formData = formData { |     val formData = formData { | ||||||
|         data.forEach { (k, v) -> |         for (k in data.keys) { | ||||||
|  |             val v = data[k] ?: continue | ||||||
|             when (v) { |             when (v) { | ||||||
|                 is File -> append( |                 is File -> append( | ||||||
|                     k, |                     k, | ||||||
| @@ -89,7 +90,7 @@ actual suspend fun <T> HttpClient.uniUpload( | |||||||
|         submitForm( |         submitForm( | ||||||
|             url, |             url, | ||||||
|             Parameters.build { |             Parameters.build { | ||||||
|                 formData.forEach { |                 for (it in formData) { | ||||||
|                     val formItem = (it as PartData.FormItem) |                     val formItem = (it as PartData.FormItem) | ||||||
|                     append(it.name!!, it.value) |                     append(it.name!!, it.value) | ||||||
|                 } |                 } | ||||||
|   | |||||||
							
								
								
									
										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 | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.ktor.common | package dev.inmo.micro_utils.ktor.common | ||||||
|  |  | ||||||
| import com.soywiz.klock.DateTime | import korlibs.time.DateTime | ||||||
|  |  | ||||||
| typealias FromToDateTime = Pair<DateTime?, DateTime?> | typealias FromToDateTime = Pair<DateTime?, DateTime?> | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								ktor/common/src/linuxX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ktor/common/src/linuxX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.common | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.MPPFile | ||||||
|  | import dev.inmo.micro_utils.common.bytesAllocatorSync | ||||||
|  | import io.ktor.utils.io.core.ByteReadPacket | ||||||
|  | import io.ktor.utils.io.core.Input | ||||||
|  |  | ||||||
|  | actual fun MPPFile.input(): Input { | ||||||
|  |     return ByteReadPacket(bytesAllocatorSync()) | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								ktor/common/src/mingwX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								ktor/common/src/mingwX64Main/kotlin/ActualMPPFileInput.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | package dev.inmo.micro_utils.ktor.common | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.MPPFile | ||||||
|  | import dev.inmo.micro_utils.common.bytesAllocatorSync | ||||||
|  | import io.ktor.utils.io.core.ByteReadPacket | ||||||
|  | import io.ktor.utils.io.core.Input | ||||||
|  |  | ||||||
|  | actual fun MPPFile.input(): Input { | ||||||
|  |     return ByteReadPacket(bytesAllocatorSync()) | ||||||
|  | } | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| package dev.inmo.micro_utils.ktor.server | package dev.inmo.micro_utils.ktor.server | ||||||
|  |  | ||||||
| import com.soywiz.klock.DateTime | import korlibs.time.DateTime | ||||||
| import dev.inmo.micro_utils.ktor.common.FromToDateTime | import dev.inmo.micro_utils.ktor.common.FromToDateTime | ||||||
| import io.ktor.http.Parameters | import io.ktor.http.Parameters | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,10 +12,22 @@ suspend fun ApplicationCall.getParameterOrSendError( | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | suspend fun ApplicationCall.getParametersOrSendError( | ||||||
|  |     field: String | ||||||
|  | ) = parameters.getAll(field).also { | ||||||
|  |     if (it == null) { | ||||||
|  |         respond(HttpStatusCode.BadRequest, "Request must contains $field") | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| fun ApplicationCall.getQueryParameter( | fun ApplicationCall.getQueryParameter( | ||||||
|     field: String |     field: String | ||||||
| ) = request.queryParameters[field] | ) = request.queryParameters[field] | ||||||
|  |  | ||||||
|  | fun ApplicationCall.getQueryParameters( | ||||||
|  |     field: String | ||||||
|  | ) = request.queryParameters.getAll(field) | ||||||
|  |  | ||||||
| suspend fun ApplicationCall.getQueryParameterOrSendError( | suspend fun ApplicationCall.getQueryParameterOrSendError( | ||||||
|     field: String |     field: String | ||||||
| ) = getQueryParameter(field).also { | ) = getQueryParameter(field).also { | ||||||
| @@ -23,3 +35,11 @@ suspend fun ApplicationCall.getQueryParameterOrSendError( | |||||||
|         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") |         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | suspend fun ApplicationCall.getQueryParametersOrSendError( | ||||||
|  |     field: String | ||||||
|  | ) = getQueryParameters(field).also { | ||||||
|  |     if (it == null) { | ||||||
|  |         respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field") | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -5,3 +5,11 @@ plugins { | |||||||
| } | } | ||||||
|  |  | ||||||
| apply from: "$mppProjectWithSerializationPresetPath" | apply from: "$mppProjectWithSerializationPresetPath" | ||||||
|  |  | ||||||
|  | kotlin { | ||||||
|  |     sourceSets { | ||||||
|  |         androidMain { | ||||||
|  |             dependsOn jvmMain | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								language_codes/src/jvmMain/kotlin/Locale.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								language_codes/src/jvmMain/kotlin/Locale.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | package dev.inmo.micro_utils.language_codes | ||||||
|  |  | ||||||
|  | import java.util.Locale | ||||||
|  |  | ||||||
|  | fun IetfLanguageCode.toJavaLocale(): Locale = Locale.forLanguageTag(code) | ||||||
|  | fun IetfLanguageCode?.toJavaLocaleOrDefault(): Locale = this ?.toJavaLocale() ?: Locale.getDefault() | ||||||
|  |  | ||||||
|  | fun Locale.toIetfLanguageCode(): IetfLanguageCode = IetfLanguageCode(toLanguageTag()) | ||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import math | ||||||
|  |  | ||||||
| import requests | import requests | ||||||
| from bs4 import BeautifulSoup | from bs4 import BeautifulSoup | ||||||
| import pandas as pd | import pandas as pd | ||||||
| @@ -17,33 +19,45 @@ def fix_name(category, raw_name): | |||||||
|         result += out1 |         result += out1 | ||||||
|     return result |     return result | ||||||
|  |  | ||||||
|  | def remove_prefix(text, prefix): | ||||||
|  |     if text.startswith(prefix): | ||||||
|  |         return text[len(prefix):] | ||||||
|  |     return text  # or whatever | ||||||
|  |  | ||||||
|  | def extensionPreparationFun(extension): | ||||||
|  |     return "\"%s\"" % (remove_prefix(extension, ".")) | ||||||
|  |  | ||||||
| # https://www.freeformatter.com/mime-types-list.html | # https://www.freeformatter.com/mime-types-list.html | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|     df = pd.read_html(open('table.html', 'r')) |     df = pd.read_html(open('local.table.html', 'r')) | ||||||
|     mimes = [] |     mimes = [] | ||||||
|     for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows(): |     for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows(): | ||||||
|         mime = row[1][1] |         mime = row[1][1] | ||||||
|  |         extensions = list() | ||||||
|  |         if isinstance(row[1][2], str): | ||||||
|  |             extensions = list(map(extensionPreparationFun, row[1][2].split(", "))) | ||||||
|         mime_category = mime.split('/', 1)[0] |         mime_category = mime.split('/', 1)[0] | ||||||
|         mime_name = mime.split('/', 1)[1] |         mime_name = mime.split('/', 1)[1] | ||||||
|         mimes.append({ |         mimes.append([ | ||||||
|             'mime_category': mime_category, |             mime_category, | ||||||
|             'mime_name': mime_name, |             mime_name, | ||||||
|         }) |             extensions | ||||||
|  |         ]) | ||||||
|  |  | ||||||
|     # codegen |     # codegen | ||||||
|  |  | ||||||
|     mimes.sort(key=lambda x: x['mime_category']) |     mimes.sort(key=lambda x: x[0]) | ||||||
|     grouped = itertools.groupby(mimes, lambda x: x['mime_category']) |     grouped = itertools.groupby(mimes, lambda x: x[0]) | ||||||
|     code = '' |     code = '' | ||||||
|     code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n' |     code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n' | ||||||
|     code2 += '    KnownMimeTypes.Any,\n' |     code2 += '    KnownMimeTypes.Any,\n' | ||||||
|     for key, group in grouped: |     for key, group in grouped: | ||||||
|         group_name = fix_name(group, key) |         group_name = fix_name(group, key) | ||||||
|         code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name |         code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String, extensions: Array<String> = emptyArray()) : MimeType, KnownMimeTypes(raw, extensions) {\n' % group_name | ||||||
|         code += '    @Serializable(MimeTypeSerializer::class)\n    object Any: %s ("%s/*")\n' % (group_name, key) |         code += '    @Serializable(MimeTypeSerializer::class)\n    object Any: %s ("%s/*")\n' % (group_name, key) | ||||||
|         for mime in group: |         for mime in group: | ||||||
|             name = fix_name(mime['mime_category'], mime['mime_name']) |             name = fix_name(mime[0], mime[1]) | ||||||
|             code += '    @Serializable(MimeTypeSerializer::class)\n    object %s: %s ("%s/%s")\n' % (name, group_name, mime['mime_category'], mime['mime_name']) |             code += '    @Serializable(MimeTypeSerializer::class)\n    object %s: %s ("%s/%s", arrayOf(%s))\n' % (name, group_name, mime[0], mime[1], ", ".join(mime[2])) | ||||||
|             code2 += '    KnownMimeTypes.%s.%s,\n' % (group_name, name) |             code2 += '    KnownMimeTypes.%s.%s,\n' % (group_name, name) | ||||||
|         code += '}\n\n' |         code += '}\n\n' | ||||||
|     code2 += ')\n' |     code2 += ')\n' | ||||||
|   | |||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | package dev.inmo.micro_utils.mime_types | ||||||
|  |  | ||||||
|  | val mimeTypesByExtensions: Map<String, Array<MimeType>> by lazy { | ||||||
|  |     val extensionsMap = mutableMapOf<String, MutableList<MimeType>>() | ||||||
|  |  | ||||||
|  |     knownMimeTypes.forEach { mimeType -> | ||||||
|  |         mimeType.extensions.forEach { | ||||||
|  |             extensionsMap.getOrPut(it) { mutableListOf() }.add(mimeType) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     extensionsMap.mapValues { | ||||||
|  |         it.value.toTypedArray() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun getMimeType( | ||||||
|  |     stringWithExtension: String, | ||||||
|  |     selector: (Array<MimeType>) -> MimeType? = { it.firstOrNull() } | ||||||
|  | ) = mimeTypesByExtensions[stringWithExtension.takeLastWhile { it != '.' }] ?.takeIf { it.isNotEmpty() } ?.let(selector) | ||||||
|  | inline fun getMimeTypeOrAny( | ||||||
|  |     stringWithExtension: String, | ||||||
|  |     selector: (Array<MimeType>) -> MimeType? = { it.firstOrNull() } | ||||||
|  | ) = getMimeType(stringWithExtension, selector) ?: KnownMimeTypes.Any | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -5,4 +5,6 @@ import kotlinx.serialization.Serializable | |||||||
| @Serializable(MimeTypeSerializer::class) | @Serializable(MimeTypeSerializer::class) | ||||||
| interface MimeType { | interface MimeType { | ||||||
|     val raw: String |     val raw: String | ||||||
|  |     val extensions: Array<String> | ||||||
|  |         get() = emptyArray() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,6 +15,8 @@ kotlin { | |||||||
|         browser() |         browser() | ||||||
|         nodejs() |         nodejs() | ||||||
|     } |     } | ||||||
|  |     linuxX64() | ||||||
|  |     mingwX64() | ||||||
| 
 | 
 | ||||||
|     sourceSets { |     sourceSets { | ||||||
|         commonMain { |         commonMain { | ||||||
| @@ -18,6 +18,8 @@ kotlin { | |||||||
|     android { |     android { | ||||||
|         publishAllLibraryVariants() |         publishAllLibraryVariants() | ||||||
|     } |     } | ||||||
|  |     linuxX64() | ||||||
|  |     mingwX64() | ||||||
|  |  | ||||||
|     sourceSets { |     sourceSets { | ||||||
|         commonMain { |         commonMain { | ||||||
| @@ -50,6 +52,18 @@ kotlin { | |||||||
|                 implementation libs.android.espresso |                 implementation libs.android.espresso | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         mingwX64Test { | ||||||
|  |             dependencies { | ||||||
|  |                 implementation kotlin('test-junit') | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         linuxX64Test { | ||||||
|  |             dependencies { | ||||||
|  |                 implementation kotlin('test-junit') | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         androidMain.dependsOn jvmMain | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -61,6 +61,8 @@ kotlin { | |||||||
|                 implementation libs.android.espresso |                 implementation libs.android.espresso | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         androidMain.dependsOn jvmMain | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | package dev.inmo.micro_utils.pagination.utils | ||||||
|  |  | ||||||
|  | fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) { | ||||||
|  |     is List<T> -> optionallyReverse(reverse) | ||||||
|  |     is Set<T> -> optionallyReverse(reverse) | ||||||
|  |     else -> if (reverse) { | ||||||
|  |         reversed() | ||||||
|  |     } else { | ||||||
|  |         this | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) { | ||||||
|  |     reversed() | ||||||
|  | } else { | ||||||
|  |     this | ||||||
|  | } | ||||||
|  | fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) { | ||||||
|  |     reversed().toSet() | ||||||
|  | } else { | ||||||
|  |     this | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) { | ||||||
|  |     Array(size) { | ||||||
|  |         get(lastIndex - it) | ||||||
|  |     } | ||||||
|  | } else { | ||||||
|  |     this | ||||||
|  | } | ||||||
| @@ -32,6 +32,24 @@ fun <T> List<T>.paginate(with: Pagination): PaginationResult<T> { | |||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | fun <T> List<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> { | ||||||
|  |     val actualPagination = with.optionallyReverse( | ||||||
|  |         size, | ||||||
|  |         reversed | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     val firstIndex = maxOf(actualPagination.firstIndex, 0) | ||||||
|  |     val lastIndex = minOf(actualPagination.lastIndexExclusive, size) | ||||||
|  |     if (firstIndex > lastIndex) { | ||||||
|  |         return emptyPaginationResult() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return subList(firstIndex, lastIndex).optionallyReverse(reversed).createPaginationResult( | ||||||
|  |         with, | ||||||
|  |         size.toLong() | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> { | fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> { | ||||||
|     return this.drop(with.firstIndex).take(with.size).createPaginationResult( |     return this.drop(with.firstIndex).take(with.size).createPaginationResult( | ||||||
|         with, |         with, | ||||||
| @@ -39,30 +57,20 @@ fun <T> Set<T>.paginate(with: Pagination): PaginationResult<T> { | |||||||
|     ) |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fun <T> Iterable<T>.optionallyReverse(reverse: Boolean): Iterable<T> = when (this) { | fun <T> Set<T>.paginate(with: Pagination, reversed: Boolean): PaginationResult<T> { | ||||||
|     is List<T> -> optionallyReverse(reverse) |     val actualPagination = with.optionallyReverse( | ||||||
|     is Set<T> -> optionallyReverse(reverse) |         size, | ||||||
|     else -> if (reverse) { |         reversed | ||||||
|         reversed() |     ) | ||||||
|     } else { |  | ||||||
|         this |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| fun <T> List<T>.optionallyReverse(reverse: Boolean): List<T> = if (reverse) { |  | ||||||
|     reversed() |  | ||||||
| } else { |  | ||||||
|     this |  | ||||||
| } |  | ||||||
| fun <T> Set<T>.optionallyReverse(reverse: Boolean): Set<T> = if (reverse) { |  | ||||||
|     reversed().toSet() |  | ||||||
| } else { |  | ||||||
|     this |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| inline fun <reified T> Array<T>.optionallyReverse(reverse: Boolean) = if (reverse) { |     val firstIndex = maxOf(actualPagination.firstIndex, 0) | ||||||
|     Array(size) { |     val lastIndex = minOf(actualPagination.lastIndexExclusive, size) | ||||||
|         get(lastIndex - it) |     if (firstIndex > lastIndex) { | ||||||
|  |         return emptyPaginationResult() | ||||||
|     } |     } | ||||||
| } else { | 
 | ||||||
|     this |     return this.drop(firstIndex).take(lastIndex - firstIndex).optionallyReverse(reversed).createPaginationResult( | ||||||
|  |         with, | ||||||
|  |         size.toLong() | ||||||
|  |     ) | ||||||
| } | } | ||||||
							
								
								
									
										6
									
								
								renovate.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								renovate.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | { | ||||||
|  |   "$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||||||
|  |   "extends": [ | ||||||
|  |     "config:base" | ||||||
|  |   ] | ||||||
|  | } | ||||||
| @@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.cache | |||||||
|  |  | ||||||
| import dev.inmo.micro_utils.repos.* | import dev.inmo.micro_utils.repos.* | ||||||
| import dev.inmo.micro_utils.repos.cache.cache.KVCache | import dev.inmo.micro_utils.repos.cache.cache.KVCache | ||||||
|  | import dev.inmo.micro_utils.repos.cache.util.actualizeAll | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.flow.* | import kotlinx.coroutines.flow.* | ||||||
| @@ -15,6 +16,12 @@ open class ReadCRUDCacheRepo<ObjectType, IdType>( | |||||||
|         kvCache.set(id, it) |         kvCache.set(id, it) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<IdType, ObjectType> { | ||||||
|  |         return kvCache.getAll().takeIf { it.size.toLong() == count() } ?: parentRepo.getAll().also { | ||||||
|  |             kvCache.actualizeAll(true) { it } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id) |     override suspend fun contains(id: IdType): Boolean = kvCache.contains(id) || parentRepo.contains(id) | ||||||
|  |  | ||||||
|     override suspend fun invalidate() = kvCache.clear() |     override suspend fun invalidate() = kvCache.clear() | ||||||
|   | |||||||
| @@ -24,6 +24,12 @@ open class ReadKeyValueCacheRepo<Key,Value>( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<Key, Value> = kvCache.getAll().takeIf { | ||||||
|  |         it.size.toLong() == count() | ||||||
|  |     } ?: parentRepo.getAll().also { | ||||||
|  |         kvCache.set(it) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun invalidate() = kvCache.clear() |     override suspend fun invalidate() = kvCache.clear() | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -32,7 +38,7 @@ fun <Key, Value> ReadKeyValueRepo<Key, Value>.cached( | |||||||
| ) = ReadKeyValueCacheRepo(this, kvCache) | ) = ReadKeyValueCacheRepo(this, kvCache) | ||||||
|  |  | ||||||
| open class KeyValueCacheRepo<Key,Value>( | open class KeyValueCacheRepo<Key,Value>( | ||||||
|     parentRepo: KeyValueRepo<Key, Value>, |     override val parentRepo: KeyValueRepo<Key, Value>, | ||||||
|     kvCache: KVCache<Key, Value>, |     kvCache: KVCache<Key, Value>, | ||||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) | ||||||
| ) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CommonCacheRepo { | ) : ReadKeyValueCacheRepo<Key,Value>(parentRepo, kvCache), KeyValueRepo<Key,Value>, WriteKeyValueRepo<Key, Value> by parentRepo, CommonCacheRepo { | ||||||
| @@ -40,6 +46,11 @@ open class KeyValueCacheRepo<Key,Value>( | |||||||
|     protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope) |     protected val onRemoveJob = parentRepo.onValueRemoved.onEach { kvCache.unset(it) }.launchIn(scope) | ||||||
|  |  | ||||||
|     override suspend fun invalidate() = kvCache.clear() |     override suspend fun invalidate() = kvCache.clear() | ||||||
|  |  | ||||||
|  |     override suspend fun clear() { | ||||||
|  |         parentRepo.clear() | ||||||
|  |         kvCache.clear() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fun <Key, Value> KeyValueRepo<Key, Value>.cached( | fun <Key, Value> KeyValueRepo<Key, Value>.cached( | ||||||
|   | |||||||
| @@ -21,6 +21,12 @@ open class SimpleFullKVCache<K, V>( | |||||||
|             kvParent.unset(toUnset) |             kvParent.unset(toUnset) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun clear() { | ||||||
|  |         syncMutex.withLock { | ||||||
|  |             kvParent.clear() | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| inline fun <K, V> FullKVCache( | inline fun <K, V> FullKVCache( | ||||||
|   | |||||||
| @@ -37,6 +37,10 @@ open class SimpleKVCache<K, V>( | |||||||
|     override suspend fun unset(toUnset: List<K>) { |     override suspend fun unset(toUnset: List<K>) { | ||||||
|         syncMutex.withLock { makeUnset(toUnset) } |         syncMutex.withLock { makeUnset(toUnset) } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun clear() { | ||||||
|  |         syncMutex.withLock { makeUnset(cacheQueue) } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| inline fun <K, V> KVCache( | inline fun <K, V> KVCache( | ||||||
|   | |||||||
| @@ -52,6 +52,14 @@ open class AutoRecacheReadCRUDRepo<RegisteredObject, Id>( | |||||||
|         kvCache.contains(id) |         kvCache.contains(id) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap { | ||||||
|  |         originalRepo.getAll() | ||||||
|  |     }.onSuccess { | ||||||
|  |         kvCache.actualizeAll(clear = true) { it } | ||||||
|  |     }.getOrElse { | ||||||
|  |         kvCache.getAll() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun count(): Long = actionWrapper.wrap { |     override suspend fun count(): Long = actionWrapper.wrap { | ||||||
|         originalRepo.count() |         originalRepo.count() | ||||||
|     }.getOrElse { |     }.getOrElse { | ||||||
|   | |||||||
| @@ -21,9 +21,9 @@ open class AutoRecacheWriteCRUDRepo<RegisteredObject, Id, InputObject>( | |||||||
|     protected val idGetter: (RegisteredObject) -> Id |     protected val idGetter: (RegisteredObject) -> Id | ||||||
| ) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo { | ) : WriteCRUDRepo<RegisteredObject, Id, InputObject>, FallbackCacheRepo { | ||||||
|     override val deletedObjectsIdsFlow: Flow<Id> |     override val deletedObjectsIdsFlow: Flow<Id> | ||||||
|         get() = (originalRepo.deletedObjectsIdsFlow + kvCache.onValueRemoved).distinctUntilChanged() |         get() = (originalRepo.deletedObjectsIdsFlow).distinctUntilChanged() | ||||||
|     override val newObjectsFlow: Flow<RegisteredObject> |     override val newObjectsFlow: Flow<RegisteredObject> | ||||||
|         get() = (originalRepo.newObjectsFlow + kvCache.onNewValue.map { it.second }).distinctUntilChanged() |         get() = (originalRepo.newObjectsFlow).distinctUntilChanged() | ||||||
|     override val updatedObjectsFlow: Flow<RegisteredObject> |     override val updatedObjectsFlow: Flow<RegisteredObject> | ||||||
|         get() = originalRepo.updatedObjectsFlow |         get() = originalRepo.updatedObjectsFlow | ||||||
|  |  | ||||||
|   | |||||||
| @@ -39,4 +39,9 @@ open class AutoRecacheKeyValueRepo<Id, RegisteredObject>( | |||||||
|     ).also { |     ).also { | ||||||
|         kvCache.unsetWithValues(toUnset) |         kvCache.unsetWithValues(toUnset) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun clear() { | ||||||
|  |         originalRepo.clear() | ||||||
|  |         kvCache.clear() | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -52,6 +52,14 @@ open class AutoRecacheReadKeyValueRepo<Id, RegisteredObject>( | |||||||
|         kvCache.contains(key) |         kvCache.contains(key) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<Id, RegisteredObject> = actionWrapper.wrap { | ||||||
|  |         originalRepo.getAll() | ||||||
|  |     }.onSuccess { | ||||||
|  |         kvCache.actualizeAll(clear = true) { it } | ||||||
|  |     }.getOrElse { | ||||||
|  |         kvCache.getAll() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun count(): Long = actionWrapper.wrap { |     override suspend fun count(): Long = actionWrapper.wrap { | ||||||
|         originalRepo.count() |         originalRepo.count() | ||||||
|     }.getOrElse { |     }.getOrElse { | ||||||
|   | |||||||
| @@ -17,10 +17,10 @@ open class AutoRecacheWriteKeyValueRepo<Id, RegisteredObject>( | |||||||
|     protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache() |     protected val kvCache: FullKVCache<Id, RegisteredObject> = FullKVCache() | ||||||
| ) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo { | ) : WriteKeyValueRepo<Id, RegisteredObject>, FallbackCacheRepo { | ||||||
|     override val onValueRemoved: Flow<Id> |     override val onValueRemoved: Flow<Id> | ||||||
|         get() = (originalRepo.onValueRemoved + kvCache.onValueRemoved).distinctUntilChanged() |         get() = (originalRepo.onValueRemoved).distinctUntilChanged() | ||||||
|  |  | ||||||
|     override val onNewValue: Flow<Pair<Id, RegisteredObject>> |     override val onNewValue: Flow<Pair<Id, RegisteredObject>> | ||||||
|         get() = (originalRepo.onNewValue + kvCache.onNewValue).distinctUntilChanged() |         get() = (originalRepo.onNewValue).distinctUntilChanged() | ||||||
|  |  | ||||||
|     private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) { |     private val onRemovingUpdatesListeningJob = originalRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) { | ||||||
|         kvCache.unset(it) |         kvCache.unset(it) | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging | |||||||
| import dev.inmo.micro_utils.repos.WriteKeyValuesRepo | import dev.inmo.micro_utils.repos.WriteKeyValuesRepo | ||||||
| import dev.inmo.micro_utils.repos.cache.cache.FullKVCache | import dev.inmo.micro_utils.repos.cache.cache.FullKVCache | ||||||
| import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo | import dev.inmo.micro_utils.repos.cache.FallbackCacheRepo | ||||||
|  | import dev.inmo.micro_utils.repos.pagination.maxPagePagination | ||||||
| import dev.inmo.micro_utils.repos.set | import dev.inmo.micro_utils.repos.set | ||||||
| import dev.inmo.micro_utils.repos.unset | import dev.inmo.micro_utils.repos.unset | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| @@ -24,7 +25,7 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>( | |||||||
|     override val onNewValue: Flow<Pair<Id, RegisteredObject>> |     override val onNewValue: Flow<Pair<Id, RegisteredObject>> | ||||||
|         get() = originalRepo.onNewValue |         get() = originalRepo.onNewValue | ||||||
|     override val onDataCleared: Flow<Id> |     override val onDataCleared: Flow<Id> | ||||||
|         get() = (originalRepo.onDataCleared + kvCache.onValueRemoved).distinctUntilChanged() |         get() = (originalRepo.onDataCleared).distinctUntilChanged() | ||||||
|  |  | ||||||
|     private val onDataClearedListeningJob = originalRepo.onDataCleared.subscribeSafelyWithoutExceptions(scope) { |     private val onDataClearedListeningJob = originalRepo.onDataCleared.subscribeSafelyWithoutExceptions(scope) { | ||||||
|         kvCache.unset(it) |         kvCache.unset(it) | ||||||
| @@ -50,7 +51,7 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>( | |||||||
|  |  | ||||||
|     override suspend fun clearWithValue(v: RegisteredObject) { |     override suspend fun clearWithValue(v: RegisteredObject) { | ||||||
|         originalRepo.clearWithValue(v) |         originalRepo.clearWithValue(v) | ||||||
|         doForAllWithNextPaging(FirstPagePagination(kvCache.count().takeIf { it < Int.MAX_VALUE } ?.toInt() ?: Int.MAX_VALUE)) { |         doForAllWithNextPaging(kvCache.maxPagePagination()) { | ||||||
|             kvCache.keys(it).also { |             kvCache.keys(it).also { | ||||||
|                 it.results.forEach { id -> |                 it.results.forEach { id -> | ||||||
|                     kvCache.get(id) ?.takeIf { it.contains(v) } ?.let { |                     kvCache.get(id) ?.takeIf { it.contains(v) } ?.let { | ||||||
| @@ -73,6 +74,19 @@ open class AutoRecacheWriteKeyValuesRepo<Id, RegisteredObject>( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun removeWithValue(v: RegisteredObject) { | ||||||
|  |         originalRepo.removeWithValue(v) | ||||||
|  |         doForAllWithNextPaging(kvCache.maxPagePagination()) { | ||||||
|  |             kvCache.keys(it).also { | ||||||
|  |                 it.results.forEach { id -> | ||||||
|  |                     kvCache.get(id) ?.takeIf { it.contains(v) } ?.let { | ||||||
|  |                         kvCache.set(id, it - v) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) { |     override suspend fun add(toAdd: Map<Id, List<RegisteredObject>>) { | ||||||
|         originalRepo.add(toAdd) |         originalRepo.add(toAdd) | ||||||
|         toAdd.forEach { (k, v) -> |         toAdd.forEach { (k, v) -> | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.repos.cache.full | package dev.inmo.micro_utils.repos.cache.full | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.common.* | import dev.inmo.micro_utils.common.* | ||||||
|  | import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions | ||||||
| import dev.inmo.micro_utils.pagination.Pagination | import dev.inmo.micro_utils.pagination.Pagination | ||||||
| import dev.inmo.micro_utils.pagination.PaginationResult | import dev.inmo.micro_utils.pagination.PaginationResult | ||||||
| import dev.inmo.micro_utils.repos.* | import dev.inmo.micro_utils.repos.* | ||||||
| @@ -58,6 +59,12 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>( | |||||||
|         { if (it) parentRepo.getById(id) ?.let { set(id, it) } } |         { if (it) parentRepo.getById(id) ?.let { set(id, it) } } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<IdType, ObjectType> = doOrTakeAndActualize( | ||||||
|  |         { getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull }, | ||||||
|  |         { getAll() }, | ||||||
|  |         { kvCache.actualizeAll(clear = true) { it } } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize( |     override suspend fun getById(id: IdType): ObjectType? = doOrTakeAndActualize( | ||||||
|         { get(id) ?.optional ?: Optional.absent() }, |         { get(id) ?.optional ?: Optional.absent() }, | ||||||
|         { getById(id) }, |         { getById(id) }, | ||||||
| @@ -78,6 +85,7 @@ open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>( | |||||||
|     override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>, |     override val parentRepo: CRUDRepo<ObjectType, IdType, InputValueType>, | ||||||
|     kvCache: FullKVCache<IdType, ObjectType>, |     kvCache: FullKVCache<IdType, ObjectType>, | ||||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||||
|  |     skipStartInvalidate: Boolean = false, | ||||||
|     idGetter: (ObjectType) -> IdType |     idGetter: (ObjectType) -> IdType | ||||||
| ) : FullReadCRUDCacheRepo<ObjectType, IdType>( | ) : FullReadCRUDCacheRepo<ObjectType, IdType>( | ||||||
|     parentRepo, |     parentRepo, | ||||||
| @@ -91,13 +99,28 @@ open class FullCRUDCacheRepo<ObjectType, IdType, InputValueType>( | |||||||
|         idGetter |         idGetter | ||||||
|     ), |     ), | ||||||
|     CRUDRepo<ObjectType, IdType, InputValueType> { |     CRUDRepo<ObjectType, IdType, InputValueType> { | ||||||
|  |     init { | ||||||
|  |         if (!skipStartInvalidate) { | ||||||
|  |             scope.launchSafelyWithoutExceptions { invalidate() } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun invalidate() { |     override suspend fun invalidate() { | ||||||
|         actualizeAll() |         actualizeAll() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.fullyCached( | ||||||
|  |     kvCache: FullKVCache<IdType, ObjectType> = FullKVCache(), | ||||||
|  |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||||
|  |     skipStartInvalidate: Boolean = false, | ||||||
|  |     idGetter: (ObjectType) -> IdType | ||||||
|  | ) = FullCRUDCacheRepo(this, kvCache, scope, skipStartInvalidate, idGetter) | ||||||
|  |  | ||||||
|  | @Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope, idGetter)", "dev.inmo.micro_utils.repos.cache.full.fullyCached")) | ||||||
| fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached( | fun <ObjectType, IdType, InputType> CRUDRepo<ObjectType, IdType, InputType>.cached( | ||||||
|     kvCache: FullKVCache<IdType, ObjectType>, |     kvCache: FullKVCache<IdType, ObjectType>, | ||||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||||
|  |     skipStartInvalidate: Boolean = false, | ||||||
|     idGetter: (ObjectType) -> IdType |     idGetter: (ObjectType) -> IdType | ||||||
| ) = FullCRUDCacheRepo(this, kvCache, scope, idGetter) | ) = fullyCached(kvCache, scope, skipStartInvalidate, idGetter) | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.repos.cache.full | package dev.inmo.micro_utils.repos.cache.full | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.common.* | import dev.inmo.micro_utils.common.* | ||||||
|  | import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions | ||||||
| import dev.inmo.micro_utils.pagination.Pagination | import dev.inmo.micro_utils.pagination.Pagination | ||||||
| import dev.inmo.micro_utils.pagination.PaginationResult | import dev.inmo.micro_utils.pagination.PaginationResult | ||||||
| import dev.inmo.micro_utils.repos.* | import dev.inmo.micro_utils.repos.* | ||||||
| @@ -9,7 +10,6 @@ import dev.inmo.micro_utils.repos.cache.util.actualizeAll | |||||||
| import dev.inmo.micro_utils.repos.pagination.getAll | import dev.inmo.micro_utils.repos.pagination.getAll | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.Job |  | ||||||
| import kotlinx.coroutines.flow.* | import kotlinx.coroutines.flow.* | ||||||
|  |  | ||||||
| open class FullReadKeyValueCacheRepo<Key,Value>( | open class FullReadKeyValueCacheRepo<Key,Value>( | ||||||
| @@ -59,6 +59,12 @@ open class FullReadKeyValueCacheRepo<Key,Value>( | |||||||
|         { if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } } |         { if (it) parentRepo.get(key) ?.also { kvCache.set(key, it) } } | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<Key, Value> = doOrTakeAndActualize( | ||||||
|  |         { getAll().takeIf { it.isNotEmpty() }.optionalOrAbsentIfNull }, | ||||||
|  |         { getAll() }, | ||||||
|  |         { kvCache.actualizeAll(clear = true) { it } } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|     override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize( |     override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = doOrTakeAndActualize( | ||||||
|         { keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, |         { keys(pagination, reversed).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull }, | ||||||
|         { keys(pagination, reversed) }, |         { keys(pagination, reversed) }, | ||||||
| @@ -101,18 +107,39 @@ fun <Key, Value> WriteKeyValueRepo<Key, Value>.caching( | |||||||
| open class FullKeyValueCacheRepo<Key,Value>( | open class FullKeyValueCacheRepo<Key,Value>( | ||||||
|     protected open val parentRepo: KeyValueRepo<Key, Value>, |     protected open val parentRepo: KeyValueRepo<Key, Value>, | ||||||
|     kvCache: FullKVCache<Key, Value>, |     kvCache: FullKVCache<Key, Value>, | ||||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||||
|  |     skipStartInvalidate: Boolean = false | ||||||
| ) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope), | ) : FullWriteKeyValueCacheRepo<Key,Value>(parentRepo, kvCache, scope), | ||||||
|     KeyValueRepo<Key,Value>, |     KeyValueRepo<Key,Value>, | ||||||
|     ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo(parentRepo, kvCache) { |     ReadKeyValueRepo<Key, Value> by FullReadKeyValueCacheRepo( | ||||||
|  |         parentRepo, | ||||||
|  |         kvCache | ||||||
|  | ) { | ||||||
|  |     init { | ||||||
|  |         if (!skipStartInvalidate) { | ||||||
|  |             scope.launchSafelyWithoutExceptions { invalidate() } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset) |     override suspend fun unsetWithValues(toUnset: List<Value>) = parentRepo.unsetWithValues(toUnset) | ||||||
|  |  | ||||||
|     override suspend fun invalidate() { |     override suspend fun invalidate() { | ||||||
|         kvCache.actualizeAll(parentRepo) |         kvCache.actualizeAll(parentRepo) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun clear() { | ||||||
|  |         parentRepo.clear() | ||||||
|  |         kvCache.clear() | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fun <Key, Value> KeyValueRepo<Key, Value>.fullyCached( | ||||||
|  |     kvCache: FullKVCache<Key, Value> = FullKVCache(), | ||||||
|  |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) | ||||||
|  | ) = FullKeyValueCacheRepo(this, kvCache, scope) | ||||||
|  |  | ||||||
|  | @Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached")) | ||||||
| fun <Key, Value> KeyValueRepo<Key, Value>.cached( | fun <Key, Value> KeyValueRepo<Key, Value>.cached( | ||||||
|     kvCache: FullKVCache<Key, Value>, |     kvCache: FullKVCache<Key, Value>, | ||||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) | ||||||
| ) = FullKeyValueCacheRepo(this, kvCache, scope) | ) = fullyCached(kvCache, scope) | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.repos.cache.full | package dev.inmo.micro_utils.repos.cache.full | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.common.* | import dev.inmo.micro_utils.common.* | ||||||
|  | import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions | ||||||
| import dev.inmo.micro_utils.pagination.* | import dev.inmo.micro_utils.pagination.* | ||||||
| import dev.inmo.micro_utils.pagination.utils.* | import dev.inmo.micro_utils.pagination.utils.* | ||||||
| import dev.inmo.micro_utils.repos.* | import dev.inmo.micro_utils.repos.* | ||||||
| @@ -142,10 +143,17 @@ fun <Key, Value> WriteKeyValuesRepo<Key, Value>.caching( | |||||||
| open class FullKeyValuesCacheRepo<Key,Value>( | open class FullKeyValuesCacheRepo<Key,Value>( | ||||||
|     protected open val parentRepo: KeyValuesRepo<Key, Value>, |     protected open val parentRepo: KeyValuesRepo<Key, Value>, | ||||||
|     kvCache: FullKVCache<Key, List<Value>>, |     kvCache: FullKVCache<Key, List<Value>>, | ||||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default), | ||||||
|  |     skipStartInvalidate: Boolean = false | ||||||
| ) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope), | ) : FullWriteKeyValuesCacheRepo<Key, Value>(parentRepo, kvCache, scope), | ||||||
|     KeyValuesRepo<Key, Value>, |     KeyValuesRepo<Key, Value>, | ||||||
|     ReadKeyValuesRepo<Key, Value> by FullReadKeyValuesCacheRepo(parentRepo, kvCache) { |     ReadKeyValuesRepo<Key, Value> by FullReadKeyValuesCacheRepo(parentRepo, kvCache) { | ||||||
|  |     init { | ||||||
|  |         if (!skipStartInvalidate) { | ||||||
|  |             scope.launchSafelyWithoutExceptions { invalidate() } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun clearWithValue(v: Value) { |     override suspend fun clearWithValue(v: Value) { | ||||||
|         doAllWithCurrentPaging { |         doAllWithCurrentPaging { | ||||||
|             keys(v, it).also { |             keys(v, it).also { | ||||||
| @@ -157,8 +165,18 @@ open class FullKeyValuesCacheRepo<Key,Value>( | |||||||
|     override suspend fun invalidate() { |     override suspend fun invalidate() { | ||||||
|         kvCache.actualizeAll(parentRepo) |         kvCache.actualizeAll(parentRepo) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun removeWithValue(v: Value) { | ||||||
|  |         super<FullWriteKeyValuesCacheRepo>.removeWithValue(v) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fun <Key, Value> KeyValuesRepo<Key, Value>.fullyCached( | ||||||
|  |     kvCache: FullKVCache<Key, List<Value>> = FullKVCache(), | ||||||
|  |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) | ||||||
|  | ) = FullKeyValuesCacheRepo(this, kvCache, scope) | ||||||
|  |  | ||||||
|  | @Deprecated("Renamed", ReplaceWith("this.fullyCached(kvCache, scope)", "dev.inmo.micro_utils.repos.cache.full.fullyCached")) | ||||||
| fun <Key, Value> KeyValuesRepo<Key, Value>.caching( | fun <Key, Value> KeyValuesRepo<Key, Value>.caching( | ||||||
|     kvCache: FullKVCache<Key, List<Value>>, |     kvCache: FullKVCache<Key, List<Value>>, | ||||||
|     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) |     scope: CoroutineScope = CoroutineScope(Dispatchers.Default) | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ suspend inline fun <K, V> KVCache<K, V>.actualizeAll( | |||||||
|     clear: Boolean = true, |     clear: Boolean = true, | ||||||
| ) { | ) { | ||||||
|     actualizeAll(clear) { |     actualizeAll(clear) { | ||||||
|         repo.getAll { keys(it) }.toMap() |         repo.getAll() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -37,7 +37,7 @@ suspend inline fun <K, V> KVCache<K, List<V>>.actualizeAll( | |||||||
|     clear: Boolean = true, |     clear: Boolean = true, | ||||||
| ) { | ) { | ||||||
|     actualizeAll(clear) { |     actualizeAll(clear) { | ||||||
|         repo.getAll { keys(it) }.toMap() |         repo.getAll() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -46,8 +46,6 @@ suspend inline fun <K, V> KVCache<K, V>.actualizeAll( | |||||||
|     clear: Boolean = true, |     clear: Boolean = true, | ||||||
| ) { | ) { | ||||||
|     actualizeAll(clear) { |     actualizeAll(clear) { | ||||||
|         repo.getAllByWithNextPaging { |         repo.getAll() | ||||||
|             getIdsByPagination(it) |  | ||||||
|         }.mapNotNull { it to (repo.getById(it) ?: return@mapNotNull null) }.toMap() |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -32,5 +32,3 @@ kotlin { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| disableIncludingJvmCodeInAndroidPart() |  | ||||||
|   | |||||||
| @@ -2,6 +2,10 @@ package dev.inmo.micro_utils.repos | |||||||
|  |  | ||||||
| import dev.inmo.micro_utils.pagination.* | import dev.inmo.micro_utils.pagination.* | ||||||
| import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging | import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging | ||||||
|  | import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging | ||||||
|  | import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | ||||||
|  | import dev.inmo.micro_utils.pagination.utils.paginate | ||||||
|  | import dev.inmo.micro_utils.repos.pagination.maxPagePagination | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -17,30 +21,48 @@ interface ReadKeyValueRepo<Key, Value> : Repo { | |||||||
|     suspend fun get(k: Key): Value? |     suspend fun get(k: Key): Value? | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use |      * This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use | ||||||
|      * ascending sort for [Key]s |      * ascending sort for [Key]s | ||||||
|      */ |      */ | ||||||
|     suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value> |     suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value> | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use |      * This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use | ||||||
|      * ascending sort for [Key]s |      * ascending sort for [Key]s | ||||||
|      */ |      */ | ||||||
|     suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> |     suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use |      * This method should use sorted by [Key]s search and return the [PaginationResult]. By default, it should use | ||||||
|      * ascending sort for [Key]s |      * ascending sort for [Key]s. | ||||||
|  |      * | ||||||
|  |      * **DEFAULT REALIZATION IS NOT OPTIMAL AND HAS BEEN ADDED TO COVER CASES OF DIFFERENT COMMON MAPPINGS AND TRANSFORMATIONS** | ||||||
|      * |      * | ||||||
|      * @param v This value should be used to exclude from search the items with different [Value]s |      * @param v This value should be used to exclude from search the items with different [Value]s | ||||||
|      */ |      */ | ||||||
|     suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> |     suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key> { | ||||||
|  |         return getAllWithNextPaging { | ||||||
|  |             keys(it) | ||||||
|  |         }.filter { | ||||||
|  |             get(it) == v | ||||||
|  |         }.paginate(pagination, reversed) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @return true if [key] is presented in current collection or false otherwise |      * @return true if [key] is presented in current collection or false otherwise | ||||||
|      */ |      */ | ||||||
|     suspend fun contains(key: Key): Boolean |     suspend fun contains(key: Key): Boolean | ||||||
|  |  | ||||||
|  |     suspend fun getAll(): Map<Key, Value> = getAllByWithNextPaging(maxPagePagination()) { | ||||||
|  |         keys(it).let { | ||||||
|  |             it.changeResultsUnchecked( | ||||||
|  |                 it.results.mapNotNull { | ||||||
|  |                     it to (get(it) ?: return@mapNotNull null) | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     }.toMap() | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @return count of all collection objects |      * @return count of all collection objects | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package dev.inmo.micro_utils.repos | package dev.inmo.micro_utils.repos | ||||||
|  |  | ||||||
| import dev.inmo.micro_utils.pagination.* | import dev.inmo.micro_utils.pagination.* | ||||||
|  | import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging | ||||||
| import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
|  |  | ||||||
| @@ -44,9 +45,23 @@ interface WriteKeyValuesRepo<Key, Value> : Repo { | |||||||
|  |  | ||||||
|     suspend fun add(toAdd: Map<Key, List<Value>>) |     suspend fun add(toAdd: Map<Key, List<Value>>) | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Removes [Value]s by passed [Key]s without full clear of all data by [Key] | ||||||
|  |      */ | ||||||
|     suspend fun remove(toRemove: Map<Key, List<Value>>) |     suspend fun remove(toRemove: Map<Key, List<Value>>) | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Removes [v] without full clear of all data by [Key]s with [v] | ||||||
|  |      */ | ||||||
|  |     suspend fun removeWithValue(v: Value) | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Fully clear all data by [k] | ||||||
|  |      */ | ||||||
|     suspend fun clear(k: Key) |     suspend fun clear(k: Key) | ||||||
|  |     /** | ||||||
|  |      * Clear [v] **with** full clear of all data by [Key]s with [v] | ||||||
|  |      */ | ||||||
|     suspend fun clearWithValue(v: Value) |     suspend fun clearWithValue(v: Value) | ||||||
|  |  | ||||||
|     suspend fun set(toSet: Map<Key, List<Value>>) { |     suspend fun set(toSet: Map<Key, List<Value>>) { | ||||||
| @@ -100,6 +115,21 @@ interface KeyValuesRepo<Key, Value> : ReadKeyValuesRepo<Key, Value>, WriteKeyVal | |||||||
|             keysResult.currentPageIfNotEmpty() |             keysResult.currentPageIfNotEmpty() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     suspend override fun removeWithValue(v: Value) { | ||||||
|  |         val toRemove = mutableMapOf<Key, List<Value>>() | ||||||
|  |  | ||||||
|  |         doForAllWithNextPaging { | ||||||
|  |             keys(it).also { | ||||||
|  |                 it.results.forEach { | ||||||
|  |                     if (contains(it, v)) { | ||||||
|  |                         toRemove[it] = listOf(v) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         remove(toRemove) | ||||||
|  |     } | ||||||
| } | } | ||||||
| typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value> | typealias OneToManyKeyValueRepo<Key,Value> = KeyValuesRepo<Key, Value> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,9 @@ package dev.inmo.micro_utils.repos | |||||||
|  |  | ||||||
| import dev.inmo.micro_utils.pagination.Pagination | import dev.inmo.micro_utils.pagination.Pagination | ||||||
| import dev.inmo.micro_utils.pagination.PaginationResult | import dev.inmo.micro_utils.pagination.PaginationResult | ||||||
|  | import dev.inmo.micro_utils.pagination.changeResultsUnchecked | ||||||
|  | import dev.inmo.micro_utils.pagination.utils.getAllWithCurrentPaging | ||||||
|  | import dev.inmo.micro_utils.repos.pagination.maxPagePagination | ||||||
| import kotlinx.coroutines.flow.Flow | import kotlinx.coroutines.flow.Flow | ||||||
|  |  | ||||||
| interface ReadCRUDRepo<ObjectType, IdType> : Repo { | interface ReadCRUDRepo<ObjectType, IdType> : Repo { | ||||||
| @@ -9,6 +12,14 @@ interface ReadCRUDRepo<ObjectType, IdType> : Repo { | |||||||
|     suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> |     suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> | ||||||
|     suspend fun getById(id: IdType): ObjectType? |     suspend fun getById(id: IdType): ObjectType? | ||||||
|     suspend fun contains(id: IdType): Boolean |     suspend fun contains(id: IdType): Boolean | ||||||
|  |     suspend fun getAll(): Map<IdType, ObjectType> = getAllWithCurrentPaging(maxPagePagination()) { | ||||||
|  |         getIdsByPagination(it).let { | ||||||
|  |             it.changeResultsUnchecked( | ||||||
|  |                 it.results.mapNotNull { it to (getById(it) ?: return@mapNotNull null) } | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     }.toMap() | ||||||
|  |  | ||||||
|     suspend fun count(): Long |     suspend fun count(): Long | ||||||
| } | } | ||||||
| typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType> | typealias ReadStandardCRUDRepo<ObjectType, IdType> = ReadCRUDRepo<ObjectType, IdType> | ||||||
|   | |||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | package dev.inmo.micro_utils.repos.annotations | ||||||
|  |  | ||||||
|  | import kotlin.reflect.KClass | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Use this annotation and ksp generator (module `micro_utils.repos.generator`) to create the next hierarchy of models: | ||||||
|  |  * | ||||||
|  |  * * New model. For example: data class NewTest | ||||||
|  |  * * Registered model. For example: data class RegisteredTest | ||||||
|  |  * | ||||||
|  |  * @param registeredSupertypes These [KClass]es will be used as supertypes for registered model | ||||||
|  |  * @param serializable If true (default) will generate @[kotlinx.serialization.Serializable] for models. Affects [generateSerialName] | ||||||
|  |  * @param serializable If true (default) will generate @[kotlinx.serialization.SerialName] for models with their names as values | ||||||
|  |  * | ||||||
|  |  * @see GenerateCRUDModelExcludeOverride | ||||||
|  |  */ | ||||||
|  | @Retention(AnnotationRetention.BINARY) | ||||||
|  | @Target(AnnotationTarget.CLASS) | ||||||
|  | annotation class GenerateCRUDModel( | ||||||
|  |     vararg val registeredSupertypes: KClass<*>, | ||||||
|  |     val serializable: Boolean = true, | ||||||
|  |     val generateSerialName: Boolean = true | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Use this annotation on properties which should be excluded from overriding in models. | ||||||
|  |  * | ||||||
|  |  * @see GenerateCRUDModel | ||||||
|  |  */ | ||||||
|  | @Retention(AnnotationRetention.BINARY) | ||||||
|  | @Target(AnnotationTarget.PROPERTY) | ||||||
|  | annotation class GenerateCRUDModelExcludeOverride | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -32,6 +32,10 @@ open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>( | |||||||
|  |  | ||||||
|     override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey()) |     override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey()) | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<FromId, FromRegistered> = to.getAll().asSequence().associate { (k, v) -> | ||||||
|  |         k.toInnerKey() to v.toInnerValue() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override suspend fun getById(id: FromId): FromRegistered? = to.getById( |     override suspend fun getById(id: FromId): FromRegistered? = to.getById( | ||||||
|         id.toOutKey() |         id.toOutKey() | ||||||
|     ) ?.toInnerValue() |     ) ?.toInnerValue() | ||||||
|   | |||||||
| @@ -55,6 +55,10 @@ open class MapperReadKeyValueRepo<FromKey, FromValue, ToKey, ToValue>( | |||||||
|         key.toOutKey() |         key.toOutKey() | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<FromKey, FromValue> = to.getAll().map { (k, v) -> | ||||||
|  |         k.toInnerKey() to v.toInnerValue() | ||||||
|  |     }.toMap() | ||||||
|  |  | ||||||
|     override suspend fun count(): Long = to.count() |     override suspend fun count(): Long = to.count() | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -123,7 +127,11 @@ open class MapperKeyValueRepo<FromKey, FromValue, ToKey, ToValue>( | |||||||
| ) : KeyValueRepo<FromKey, FromValue>, | ) : KeyValueRepo<FromKey, FromValue>, | ||||||
|     MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper, |     MapperRepo<FromKey, FromValue, ToKey, ToValue> by mapper, | ||||||
|     ReadKeyValueRepo<FromKey, FromValue> by MapperReadKeyValueRepo(to, mapper), |     ReadKeyValueRepo<FromKey, FromValue> by MapperReadKeyValueRepo(to, mapper), | ||||||
|     WriteKeyValueRepo<FromKey, FromValue> by MapperWriteKeyValueRepo(to, mapper) |     WriteKeyValueRepo<FromKey, FromValue> by MapperWriteKeyValueRepo(to, mapper) { | ||||||
|  |     override suspend fun clear() { | ||||||
|  |         to.clear() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| @Suppress("NOTHING_TO_INLINE") | @Suppress("NOTHING_TO_INLINE") | ||||||
| inline fun <FromKey, FromValue, ToKey, ToValue> KeyValueRepo<ToKey, ToValue>.withMapper( | inline fun <FromKey, FromValue, ToKey, ToValue> KeyValueRepo<ToKey, ToValue>.withMapper( | ||||||
|   | |||||||
| @@ -97,6 +97,8 @@ open class MapperWriteKeyValuesRepo<FromKey, FromValue, ToKey, ToValue>( | |||||||
|         }.toMap() |         }.toMap() | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |     override suspend fun removeWithValue(v: FromValue) = to.removeWithValue(v.toOutValue()) | ||||||
|  |  | ||||||
|     override suspend fun set(toSet: Map<FromKey, List<FromValue>>) { |     override suspend fun set(toSet: Map<FromKey, List<FromValue>>) { | ||||||
|         to.set( |         to.set( | ||||||
|             toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap() |             toSet.map { (k, vs) -> k.toOutKey() to vs.map { v -> v.toOutValue() } }.toMap() | ||||||
|   | |||||||
| @@ -1,12 +1,19 @@ | |||||||
| package dev.inmo.micro_utils.repos.pagination | package dev.inmo.micro_utils.repos.pagination | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.toCoercedInt | ||||||
| import dev.inmo.micro_utils.pagination.* | import dev.inmo.micro_utils.pagination.* | ||||||
| import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | ||||||
| import dev.inmo.micro_utils.repos.ReadCRUDRepo | import dev.inmo.micro_utils.repos.ReadCRUDRepo | ||||||
|  |  | ||||||
| suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll( | suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll( | ||||||
|  |     pagination: Pagination, | ||||||
|     @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") |     @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") | ||||||
|     crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T> |     crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T> | ||||||
| ): List<T> = getAllWithNextPaging { | ): List<T> = getAllWithNextPaging(pagination) { | ||||||
|     methodCaller(this, it) |     methodCaller(this, it) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | suspend inline fun <T, ID, REPO : ReadCRUDRepo<T, ID>> REPO.getAll( | ||||||
|  |     @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") | ||||||
|  |     crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<T> | ||||||
|  | ): List<T> = getAll(maxPagePagination(), methodCaller) | ||||||
|   | |||||||
| @@ -1,15 +1,22 @@ | |||||||
| package dev.inmo.micro_utils.repos.pagination | package dev.inmo.micro_utils.repos.pagination | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.toCoercedInt | ||||||
| import dev.inmo.micro_utils.pagination.* | import dev.inmo.micro_utils.pagination.* | ||||||
| import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | ||||||
| import dev.inmo.micro_utils.repos.ReadKeyValueRepo | import dev.inmo.micro_utils.repos.ReadKeyValueRepo | ||||||
|  |  | ||||||
| suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll( | suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll( | ||||||
|  |     pagination: Pagination, | ||||||
|     @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") |     @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") | ||||||
|     crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> |     crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> | ||||||
| ): List<Pair<Key, Value>> = getAllWithNextPaging { | ): List<Pair<Key, Value>> = getAllWithNextPaging(pagination) { | ||||||
|     val result = methodCaller(it) |     val result = methodCaller(it) | ||||||
|     result.changeResultsUnchecked( |     result.changeResultsUnchecked( | ||||||
|         result.results.mapNotNull { it to (get(it) ?: return@mapNotNull null) } |         result.results.mapNotNull { it to (get(it) ?: return@mapNotNull null) } | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | suspend inline fun <Key, Value, REPO : ReadKeyValueRepo<Key, Value>> REPO.getAll( | ||||||
|  |     @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") | ||||||
|  |     crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> | ||||||
|  | ): List<Pair<Key, Value>> = getAll(maxPagePagination(), methodCaller) | ||||||
|   | |||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | package dev.inmo.micro_utils.repos.pagination | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.toCoercedInt | ||||||
|  | import dev.inmo.micro_utils.pagination.FirstPagePagination | ||||||
|  | import dev.inmo.micro_utils.repos.ReadCRUDRepo | ||||||
|  | import dev.inmo.micro_utils.repos.ReadKeyValueRepo | ||||||
|  | import dev.inmo.micro_utils.repos.ReadKeyValuesRepo | ||||||
|  |  | ||||||
|  | suspend inline fun ReadCRUDRepo<*, *>.maxPagePagination() = FirstPagePagination(count().toCoercedInt()) | ||||||
|  | suspend inline fun ReadKeyValueRepo<*, *>.maxPagePagination() = FirstPagePagination(count().toCoercedInt()) | ||||||
|  | suspend inline fun ReadKeyValuesRepo<*, *>.maxPagePagination() = FirstPagePagination(count().toCoercedInt()) | ||||||
| @@ -1,13 +1,15 @@ | |||||||
| package dev.inmo.micro_utils.repos.pagination | package dev.inmo.micro_utils.repos.pagination | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.common.toCoercedInt | ||||||
| import dev.inmo.micro_utils.pagination.* | import dev.inmo.micro_utils.pagination.* | ||||||
| import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | import dev.inmo.micro_utils.pagination.utils.getAllWithNextPaging | ||||||
| import dev.inmo.micro_utils.repos.ReadKeyValuesRepo | import dev.inmo.micro_utils.repos.ReadKeyValuesRepo | ||||||
|  |  | ||||||
| suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll( | suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll( | ||||||
|  |     pagination: Pagination, | ||||||
|     @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") |     @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") | ||||||
|     crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> |     crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> | ||||||
| ): List<Pair<Key, List<Value>>> = getAllWithNextPaging { | ): List<Pair<Key, List<Value>>> = getAllWithNextPaging(pagination) { | ||||||
|     val keysResult = methodCaller(it) |     val keysResult = methodCaller(it) | ||||||
|     keysResult.changeResultsUnchecked( |     keysResult.changeResultsUnchecked( | ||||||
|         keysResult.results.map { k -> |         keysResult.results.map { k -> | ||||||
| @@ -15,3 +17,8 @@ suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAl | |||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | suspend inline fun <Key, Value, REPO : ReadKeyValuesRepo<Key, Value>> REPO.getAll( | ||||||
|  |     @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") | ||||||
|  |     crossinline methodCaller: suspend REPO.(Pagination) -> PaginationResult<Key> | ||||||
|  | ): List<Pair<Key, List<Value>>> = getAll(maxPagePagination(), methodCaller) | ||||||
|   | |||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | package dev.inmo.micro_utils.repos.transforms.crud | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.repos.KeyValueRepo | ||||||
|  | import dev.inmo.micro_utils.repos.KeyValuesRepo | ||||||
|  | import dev.inmo.micro_utils.repos.ReadKeyValueRepo | ||||||
|  | import dev.inmo.micro_utils.repos.ReadKeyValuesRepo | ||||||
|  | import kotlin.js.JsName | ||||||
|  | import kotlin.jvm.JvmName | ||||||
|  |  | ||||||
|  | fun <K, V> ReadKeyValueRepo<K, V>.asReadCRUDRepo() = ReadCRUDFromKeyValueRepo(this) | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | package dev.inmo.micro_utils.repos.transforms.crud | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.pagination.Pagination | ||||||
|  | import dev.inmo.micro_utils.pagination.PaginationResult | ||||||
|  | import dev.inmo.micro_utils.repos.ReadCRUDRepo | ||||||
|  | import dev.inmo.micro_utils.repos.ReadKeyValueRepo | ||||||
|  |  | ||||||
|  | open class ReadCRUDFromKeyValueRepo<RegisteredType, IdType>( | ||||||
|  |     protected open val original: ReadKeyValueRepo<IdType, RegisteredType> | ||||||
|  | ) : ReadCRUDRepo<RegisteredType, IdType> { | ||||||
|  |     override suspend fun contains(id: IdType): Boolean = original.contains(id) | ||||||
|  |  | ||||||
|  |     override suspend fun count(): Long = original.count() | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<IdType, RegisteredType> = original.getAll() | ||||||
|  |  | ||||||
|  |     override suspend fun getByPagination(pagination: Pagination): PaginationResult<RegisteredType> = original.values(pagination) | ||||||
|  |  | ||||||
|  |     override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = original.keys(pagination) | ||||||
|  |  | ||||||
|  |     override suspend fun getById(id: IdType): RegisteredType? = original.get(id) | ||||||
|  | } | ||||||
| @@ -2,6 +2,7 @@ package dev.inmo.micro_utils.repos.transforms.kv | |||||||
|  |  | ||||||
| import dev.inmo.micro_utils.repos.KeyValueRepo | import dev.inmo.micro_utils.repos.KeyValueRepo | ||||||
| import dev.inmo.micro_utils.repos.KeyValuesRepo | import dev.inmo.micro_utils.repos.KeyValuesRepo | ||||||
|  | import dev.inmo.micro_utils.repos.ReadCRUDRepo | ||||||
| import dev.inmo.micro_utils.repos.ReadKeyValueRepo | import dev.inmo.micro_utils.repos.ReadKeyValueRepo | ||||||
| import dev.inmo.micro_utils.repos.ReadKeyValuesRepo | import dev.inmo.micro_utils.repos.ReadKeyValuesRepo | ||||||
| import kotlin.js.JsName | import kotlin.js.JsName | ||||||
| @@ -10,3 +11,5 @@ import kotlin.jvm.JvmName | |||||||
| fun <K, V> ReadKeyValuesRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromKeyValuesRepo(this) | fun <K, V> ReadKeyValuesRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromKeyValuesRepo(this) | ||||||
|  |  | ||||||
| fun <K, V> KeyValuesRepo<K, V>.asKeyValueRepo() = KeyValueFromKeyValuesRepo(this) | fun <K, V> KeyValuesRepo<K, V>.asKeyValueRepo() = KeyValueFromKeyValuesRepo(this) | ||||||
|  |  | ||||||
|  | fun <K, V> ReadCRUDRepo<K, V>.asReadKeyValueRepo() = ReadKeyValueFromCRUDRepo(this) | ||||||
|   | |||||||
| @@ -0,0 +1,49 @@ | |||||||
|  | package dev.inmo.micro_utils.repos.transforms.kv | ||||||
|  |  | ||||||
|  | import dev.inmo.micro_utils.pagination.FirstPagePagination | ||||||
|  | import dev.inmo.micro_utils.pagination.Pagination | ||||||
|  | import dev.inmo.micro_utils.pagination.PaginationResult | ||||||
|  | import dev.inmo.micro_utils.pagination.changeResults | ||||||
|  | import dev.inmo.micro_utils.pagination.changeResultsUnchecked | ||||||
|  | import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging | ||||||
|  | import dev.inmo.micro_utils.pagination.utils.optionallyReverse | ||||||
|  | import dev.inmo.micro_utils.pagination.utils.paginate | ||||||
|  | import dev.inmo.micro_utils.repos.ReadCRUDRepo | ||||||
|  | import dev.inmo.micro_utils.repos.ReadKeyValueRepo | ||||||
|  | import dev.inmo.micro_utils.repos.ReadKeyValuesRepo | ||||||
|  | import dev.inmo.micro_utils.repos.pagination.getAll | ||||||
|  | import dev.inmo.micro_utils.repos.transforms.kvs.ReadKeyValuesFromKeyValueRepo | ||||||
|  | import kotlin.jvm.JvmInline | ||||||
|  |  | ||||||
|  | @JvmInline | ||||||
|  | value class ReadKeyValueFromCRUDRepo<Key, Value>( | ||||||
|  |     private val original: ReadCRUDRepo<Value, Key> | ||||||
|  | ) : ReadKeyValueRepo<Key, Value> { | ||||||
|  |     override suspend fun get(k: Key): Value? = original.getById(k) | ||||||
|  |  | ||||||
|  |     override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = original.getByPagination( | ||||||
|  |         pagination.optionallyReverse(count(), reversed) | ||||||
|  |     ).let { | ||||||
|  |         if (reversed) { | ||||||
|  |             it.changeResultsUnchecked(it.results.reversed()) | ||||||
|  |         } else { | ||||||
|  |             it | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = original.getIdsByPagination( | ||||||
|  |         pagination.optionallyReverse(count(), reversed) | ||||||
|  |     ).let { | ||||||
|  |         if (reversed) { | ||||||
|  |             it.changeResultsUnchecked(it.results.reversed()) | ||||||
|  |         } else { | ||||||
|  |             it | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<Key, Value> = original.getAll() | ||||||
|  |  | ||||||
|  |     override suspend fun count(): Long = original.count() | ||||||
|  |  | ||||||
|  |     override suspend fun contains(key: Key): Boolean = original.contains(key) | ||||||
|  | } | ||||||
| @@ -41,6 +41,8 @@ open class ReadKeyValueFromKeyValuesRepo<Key, Value>( | |||||||
|         return original.contains(key) |         return original.contains(key) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override suspend fun getAll(): Map<Key, List<Value>> = original.getAll() | ||||||
|  |  | ||||||
|     override suspend fun keys(v: List<Value>, pagination: Pagination, reversed: Boolean): PaginationResult<Key> { |     override suspend fun keys(v: List<Value>, pagination: Pagination, reversed: Boolean): PaginationResult<Key> { | ||||||
|         val keys = mutableSetOf<Key>() |         val keys = mutableSetOf<Key>() | ||||||
|  |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user