Compare commits

..

7 Commits

Author SHA1 Message Date
216c03205c Revert "add Handlers subproject"
This reverts commit d1021d283a.
2022-04-26 13:23:46 +06:00
ab112aa7a4 Revert "HandlersRegistrar now is open"
This reverts commit 3ac56dcfd3.
2022-04-25 22:04:36 +06:00
d85b3d0da9 Revert "now HandlersRegisrar properties are open"
This reverts commit 67b9a03366.
2022-04-25 22:04:33 +06:00
67b9a03366 now HandlersRegisrar properties are open 2022-04-25 22:01:04 +06:00
3ac56dcfd3 HandlersRegistrar now is open 2022-04-25 21:55:38 +06:00
d1021d283a add Handlers subproject 2022-04-25 21:51:42 +06:00
97ed973cb5 start 0.9.25 2022-04-25 21:50:41 +06:00
227 changed files with 4847 additions and 9220 deletions

View File

@@ -11,8 +11,11 @@ jobs:
- uses: actions/setup-java@v1
with:
java-version: 11
- name: Fix android 32.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Build
run: ./gradlew build && ./gradlew dokkaHtml
run: ./gradlew dokkaHtml
- name: Publish KDocs
uses: peaceiris/actions-gh-pages@v3
with:

View File

@@ -1,14 +1,17 @@
name: Build
name: Publish package to GitHub Packages
on: [push]
jobs:
build:
publishing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- name: Fix android 32.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Rewrite version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
@@ -19,6 +22,7 @@ jobs:
run: ./gradlew build
- name: Publish
continue-on-error: true
run: ./gradlew publishAllPublicationsToGiteaRepository
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITHUBPACKAGES_USER: ${{ github.actor }}
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

8
.space.kts Normal file
View File

@@ -0,0 +1,8 @@
job("Build and run tests") {
container(displayName = "Run gradle build", image = "openjdk:11") {
kotlinScript { api ->
// here can be your complex logic
api.gradlew("build")
}
}
}

View File

@@ -1,424 +1,6 @@
# Changelog
## 0.15.1
* `Startup`:
* Inited :)
* `Plugin`:
* Inited :)
* `Launcher`:
* Inited :)
## 0.15.0
* `Repos`:
* `CRUD`:
* `Common`:
* New method `ReadCRUDRepo#getIdsByPagination`
* `Android`:
* `AbstractAndroidCRUDRepo` got new abstract method `toId`
* `Exposed`:
* `CommonExposedRepo` new abstract property `asId`
* `Ktor`:
* `Client`:
* `KtorReadCRUDRepoClient` now requires `paginationIdType`
* `LanguageCodes`:
* Updates and fixes in generation
* `MimeTypes`:
* Updates and fixes in generation
## 0.14.4
* `Common`:
* `JVM`:
* New extension `downloadToTempFile`
* `Ktor`:
* `Server`:
* Small fix in `handleUniUpload`
* `ApplicationCall#uniloadMultipartFile` now uses `uniloadMultipart`
* `Common`:
* New extension `downloadToTempFile`
* `Client`:
* New extensions on top of `uniUpload`
## 0.14.3
* `Common`:
* New type `Progress`
* `Ktor`:
* `Client`:
* New universal `uniUpload` extension for `HttpClient`
* `Server`:
* New universal `handleUniUpload` extension for `ApplicationCall`
* Add extensions `download` and `downloadToTemporalFile`
## 0.14.2
* `Versions`:
* `Exposed`: `0.40.1` -> `0.41.1`
## 0.14.1
* `Versions`:
* `Klock`: `3.3.1` -> `3.4.0`
* `UUID`: `0.5.0` -> `0.6.0`
## 0.14.0
**ALL DEPRECATIONS HAVE BEEN REMOVED**
* `Versions`:
* `Kotlin`: `1.7.10` -> `1.7.20`
* `Klock`: `3.3.0` -> `3.3.1`
* `Compose`: `1.2.0` -> `1.2.1`
* `Exposed`: `0.39.2` -> `0.40.1`
## 0.13.2
* `Versions`:
* `Klock`: `3.1.0` -> `3.3.0`
* `Ktor`: `2.1.2` -> `2.1.3`
## 0.13.1
* `Repos`:
* `Exposed`:
* `AbstractExposedWriteCRUDRepo#createAndInsertId` now is optional and returns nullable value
## 0.13.0
**ALL DEPRECATIONS HAVE BEEN REMOVED**
**A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED**
* `Repos`:
* `Exposed`:
* `AbstractExposedWriteCRUDRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `createAndInsertId`
* Old `update` method has been deprecated and not recommended to override anymore in realizations
* Old `insert` method now is `open` instead of `abstract` and can be omitted
* `AbstractExposedKeyValueRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `insertKey`
* Old `update` method has been deprecated and not recommended to override anymore
* Old `insert` method now is `open` instead of `abstract` and can be omitted in realizations
## 0.12.17
* `Versions`:
* `JB Compose`: `1.2.0-alpha01-dev774` -> `1.2.0-beta02`
* `Ktor`: `2.1.1` -> `2.1.2`
* `Koin`: `3.2.0` -> `3.2.2`
## 0.12.16
* `Coroutines`:
* `Android`:
* Add class `FlowOnHierarchyChangeListener`
* Add `ViewGroup#setOnHierarchyChangeListenerRecursively(OnHierarchyChangeListener)`
## 0.12.15
* `Common`:
* `applyDiff` will return `Diff` object since this release
* `Android`:
* New functions/extensions `findViewsByTag` and `findViewsByTagInActivity`
* `Coroutines`:
* Add `Flow` extensions `flatMap`, `flatMapNotNull` and `flatten`
* Add `Flow` extensions `takeNotNull` and `filterNotNull`
## 0.12.14
* `Versions`:
* `Android CoreKTX`: `1.8.0` -> `1.9.0`
* `Android AppCompat`: `1.4.2` -> `1.5.1`
* Android Compile SDK: 32 -> 33
* Android Build Tools: 32.0.0 -> 33.0.0
* `Common`:
* `Android`:
* Add `argumentOrNull`/`argumentOrThrow` delegates for fragments
* `Coroutines`:
* Rewrite `awaitFirstWithDeferred` onto `CompletableDeferred` instead of coroutines suspending
## 0.12.13
* `Coroutines`:
* Add opportunity to use markers in actors (solution of [#160](https://github.com/InsanusMokrassar/MicroUtils/issues/160))
* `Koin`:
* Module inited :)
* `Repos`:
* `Android`:
* Add typealias `KeyValueSPRepo` and opportunity to create shared preferences `KeyValue` repo with `KeyValueStore(...)` (fix of [#155](https://github.com/InsanusMokrassar/MicroUtils/issues/155))
## 0.12.12
* `Common`:
* `Compose`:
* `JS`:
* Add `SkeletonAnimation` stylesheet
## 0.12.11
* `Repos`:
* `Cache`:
* Override `KeyValue` cache method `values`
## 0.12.10
* `Repos`:
* `Cache`:
* Hotfix in key values `get`
## 0.12.9
* `Versions`:
* `Klock`: `3.0.0` -> `3.1.0`
* `Repos`:
* `Cache`:
* Fixes in key values cache
## 0.12.8
* `Versions`:
* `Ktor`: `2.1.0` -> `2.1.1`
* `Compose`: `1.2.0-alpha01-dev764` -> `1.2.0-alpha01-dev774`
* `Ktor`:
* `Client`:
* New extension `HttpClient#bodyOrNull` which returns `null` in case when server responded with `No Content` (204)
* `Server`:
* New extension `ApplicationCall#respondOrNoContent` which responds `No Content` (204) when passed data is null
## 0.12.7
* `Repos`:
* `Cache`:
* Force `WriteCRUDCacheRepo` to subscribe on new and updated objects of parent repo
* `Pagination`:
* New function `changeResultsUnchecked(Pagination)`
## 0.12.6
* `MimeeTypes>`:
* Fixed absence of `image/*` in known mime types
## 0.12.5
* `Repos`:
* `Exposed`:
* Fixes in `paginate` extensions
## 0.12.4
* `Versions`:
* `Kotlin`: `1.7.0` -> `1.7.10`
* `Compose`: `1.2.0-alpha01-dev755` -> `1.2.0-alpha01-dev764`
## 0.12.3
* `Repos`:
* `Exposed`:
* Add abstract exposed variants of `KeyValue` and `KeyValues` repos
* Add new extension `Query#selectPaginated`
## 0.12.2
* `Versions`:
* `Serialization`: `1.4.0-RC` -> `1.4.0`
* `Compose`: `1.2.0-alpha01-dev753` -> `1.2.0-alpha01-dev755`
## 0.12.1
* `Versions`:
* `Ktor`: `2.0.3` -> `2.1.0`
## 0.12.0
**OLD DEPRECATIONS HAVE BEEN REMOVED**
**MINIMAL ANDROID API HAS BEEN ENLARGED UP TO API 21 (Android 5.0)**
* `Versions`
* `Kotlin`: `1.6.21` -> `1.7.0`
* `Coroutines`: `1.6.3` -> `1.6.4`
* `Exposed`: `0.38.2` -> `0.39.2`
* `Compose`: `1.2.0-alpha01-dev729` -> `1.2.0-alpha01-dev753`
* `Klock`: `2.7.0` -> `3.0.0`
* `uuid`: `0.4.1` -> `0.5.0`
* `Android Core KTX`: `1.7.0` -> `1.8.0`
* `Android AppCompat`: `1.4.1` -> `1.4.2`
* `Ktor`:
* All previously standard functions related to work with binary data by default have been deprecated
## 0.11.14
* `Pagination`:
* `PaginationResult` got new field `objectsNumber` which by default is a times between `pagesNumber` and `size`
## 0.11.13
* `Versions`:
* `Coroutines`: `1.6.3` -> `1.6.4`
* `Compose`: `1.2.0-alpha01-dev629` -> `1.2.0-alpha01-dev731`
## 0.11.12
* `Repos`:
* `Common`:
* `JVM`:
* Fixes in `ReadFileKeyValueRepo` methods (`values`/`keys`)
## 0.11.11
* `Crypto`:
* `hmacSha256` has been deprecated
* `Ktor`:
* `Client`:
* `BodyPair` has been deprecated
* `Repos`:
* `Cache`:
* New interface `CacheRepo`
* New interface `FullCacheRepo`
* `actualize*` methods inside of full cache repos now open for overriding
## 0.11.10
* `Repos`:
* `Cache`:
* `KVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache`
* `SimpleKVCache` has been replaced to the package `dev.inmo.micro_utils.repos.cache`
* New `KVCache` subtype - `FullKVCache`
* Add `Full*` variants of standard repos
* Add `cached`/`caching` (for write repos) extensions for all standard types of repos
## 0.11.9
* `Versions`
* `Coroutines`: `1.6.1` -> `1.6.3`
* `Ktor`: `2.0.2` -> `2.0.3`
* `Compose`: `1.2.0-alpha01-dev686` -> `1.2.0-alpha01-dev729`
## 0.11.8
* `Repos`:
* `Common`:
* Fixes in `FileKeyValueRepo`
## 0.11.7
* `Common`:
* New abstractions `SimpleMapper` and `SimpleSuspendableMapper`
* `Repos`:
* `Common`:
* Add mappers for `CRUDRepo`
## 0.11.6
* `FSM`:
* `Common`
* Several fixes related to the jobs handling
## 0.11.5
* `Coroutines`:
* `Compose`:
* Add extension `StateFlow#asMutableComposeListState` and `StateFlow#asComposeList`
* Add extension `StateFlow#asMutableComposeState`/`StateFlow#asComposeState`
## 0.11.4
**THIS VERSION HAS BEEN BROKEN, DO NOT USE IT**
## 0.11.3
* `Ktor`:
* Support of `WebSockets` has been improved
* `Client`:
* New extensions: `HttpClient#openBaseWebSocketFlow`, `HttpClient#openWebSocketFlow`, `HttpClient#openSecureWebSocketFlow`
## 0.11.2
* `Ktor`:
* Support of `WebSockets` has been improved and added fixes inside of clients
## 0.11.1
* `Repos`
* `Ktor`
* In `configureReadKeyValueRepoRoutes` and `configureReadKeyValuesRepoRoutes` configurators fixed requiring of `reversed` property
## 0.11.0
* `Versions`
* `UUID`: `0.4.0` -> `0.4.1`
* `Ktor`
* `Client`:
* New extension fun `HttpResponse#throwOnUnsuccess`
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
* `Server`:
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
* `Repos`
* `Ktor`:
* Fully rewritten work with all declared repositories
* All old functions, classes and extensions has been rewritten with new ktor-way with types info and keeping `ContentNegotiation` in mind
## 0.10.8
* `Common`
* Add `Element.isOverflow*` extension properties
## 0.10.7
* `Pagination`:
* Now it is possible to use `doForAll*` and `getForAll` functions in non suspend places
## 0.10.6
* `Versions`
* `Ktor`: `2.0.1` -> `2.0.2`
* `Common`
* `JS`:
* Add `ResizeObserver` functionality
## 0.10.5
* `Versions`
* `Compose`: `1.2.0-alpha01-dev683` -> `1.2.0-alpha01-dev686`
* `Repos`
* `Android`:
* New function `SharedPreferencesKeyValueRepo`
* `FSM`
* Add `StateHandlingErrorHandler` and opportunity to handle states handling errors
## 0.10.4
* `Versions`:
* `Serialization`: `1.3.2` -> `1.3.3`
## 0.10.3
* `Versions`:
* `Compose`: `1.2.0-alpha01-dev682` -> `1.2.0-alpha01-dev683`
* `Coroutines`:
* Fixes in `AccumulatorFlow`
## 0.10.2
* `Versions`:
* `Compose`: `1.2.0-alpha01-dev675` -> `1.2.0-alpha01-dev682`
## 0.10.1
* `Versions`:
* `Ktor`: `2.0.0` -> `2.0.1`
* `Crypto`:
* Add `hmacSha256`
* Add `hex`
## 0.10.0
* `Versions`:
* `Kotlin`: `1.6.10` -> `1.6.21`
* `Compose`: `1.1.1` -> `1.2.0-alpha01-dev675`
* `Exposed`: `0.37.3` -> `0.38.2`
* `Ktor`: `1.6.8` -> `2.0.0`
* `Dokka`: `1.6.10` -> `1.6.21`
## 0.9.25
## 0.9.24

View File

@@ -21,7 +21,6 @@ allprojects {
mavenLocal()
mavenCentral()
google()
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
}
// temporal crutch until legacy tests will be stabled or legacy target will be removed

View File

@@ -16,7 +16,6 @@ kotlin {
androidMain {
dependencies {
api project(":micro_utils.coroutines")
api libs.android.fragment
}
}
}

View File

@@ -1,43 +0,0 @@
package dev.inmo.micro_utils.common.compose
import org.jetbrains.compose.web.css.*
object SkeletonAnimation : StyleSheet() {
val skeletonKeyFrames: CSSNamedKeyframes by keyframes {
to { backgroundPosition("-20% 0") }
}
fun CSSBuilder.includeSkeletonStyle(
duration: CSSSizeValue<out CSSUnitTime> = 2.s,
timingFunction: AnimationTimingFunction = AnimationTimingFunction.EaseInOut,
iterationCount: Int? = null,
direction: AnimationDirection = AnimationDirection.Normal,
keyFrames: CSSNamedKeyframes = skeletonKeyFrames,
hideChildren: Boolean = true,
hideText: Boolean = hideChildren
) {
backgroundImage("linear-gradient(110deg, rgb(236, 236, 236) 40%, rgb(245, 245, 245) 50%, rgb(236, 236, 236) 65%)")
backgroundSize("200% 100%")
backgroundPosition("180% 0")
animation(keyFrames) {
duration(duration)
timingFunction(timingFunction)
iterationCount(iterationCount)
direction(direction)
}
if (hideText) {
property("color", "${Color.transparent} !important")
}
if (hideChildren) {
child(self, universal) style {
property("visibility", "hidden")
}
}
}
val skeleton by style {
includeSkeletonStyle()
}
}

View File

@@ -1,5 +1,3 @@
@file:Suppress("OPT_IN_IS_NOT_ENABLED")
package dev.inmo.micro_utils.common
@RequiresOptIn(

View File

@@ -14,14 +14,6 @@ private inline fun <T> getObject(
/**
* Diff object which contains information about differences between two [Iterable]s
*
* See tests for more info
*
* @param removed The objects which has been presented in the old collection but absent in new one. Index here is the index in the old collection
* @param added The object which appear in new collection only. Indexes here show the index in the new collection
* @param replaced Pair of old-new changes. First object has been presented in the old collection on its
* [IndexedValue.index] place, the second one is the object in new collection. Both have indexes due to the fact that in
* case when some value has been replaced after adds or removes in original collection the object index will be changed
*
* @see calculateDiff
*/
data class Diff<T> internal constructor(
@@ -51,7 +43,6 @@ private inline fun <T> performChanges(
if (oldOneEqualToNewObject || newOneEqualToOldObject) {
changedList.addAll(
potentialChanges.take(i).mapNotNull {
@Suppress("UNCHECKED_CAST")
if (it.first != null && it.second != null) it as Pair<IndexedValue<T>, IndexedValue<T>> else null
}
)
@@ -130,10 +121,7 @@ fun <T> Iterable<T>.calculateDiff(
when {
oldObject === newObject || (oldObject == newObject && !strictComparison) -> {
changedObjects.addAll(potentiallyChangedObjects.map {
@Suppress("UNCHECKED_CAST")
it as Pair<IndexedValue<T>, IndexedValue<T>>
})
changedObjects.addAll(potentiallyChangedObjects.map { it as Pair<IndexedValue<T>, IndexedValue<T>> })
potentiallyChangedObjects.clear()
}
else -> {
@@ -173,7 +161,7 @@ inline fun <T> Iterable<T>.calculateStrictDiff(
fun <T> MutableList<T>.applyDiff(
source: Iterable<T>,
strictComparison: Boolean = false
): Diff<T> = calculateDiff(source, strictComparison).also {
) = calculateDiff(source, strictComparison).let {
for (i in it.removed.indices.sortedDescending()) {
removeAt(it.removed[i].index)
}

View File

@@ -1,5 +1,3 @@
@file:Suppress("unused", "NOTHING_TO_INLINE")
package dev.inmo.micro_utils.common
import kotlinx.serialization.*
@@ -23,18 +21,26 @@ import kotlinx.serialization.encoding.*
sealed interface Either<T1, T2> {
val optionalT1: Optional<T1>
val optionalT2: Optional<T2>
val t1OrNull: T1?
@Deprecated("Use optionalT1 instead", ReplaceWith("optionalT1"))
val t1: T1?
get() = optionalT1.dataOrNull()
val t2OrNull: T2?
@Deprecated("Use optionalT2 instead", ReplaceWith("optionalT2"))
val t2: T2?
get() = optionalT2.dataOrNull()
companion object {
fun <T1, T2> serializer(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
): KSerializer<Either<T1, T2>> = EitherSerializer(t1Serializer, t2Serializer)
}
}
class EitherSerializer<T1, T2>(
t1Serializer: KSerializer<T1>,
t2Serializer: KSerializer<T2>,
) : KSerializer<Either<T1, T2>> {
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
@OptIn(ExperimentalSerializationApi::class, InternalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor(
"TypedSerializer",
SerialKind.CONTEXTUAL
@@ -97,7 +103,7 @@ class EitherSerializer<T1, T2>(
*/
@Serializable
data class EitherFirst<T1, T2>(
val t1: T1
override val t1: T1
) : Either<T1, T2> {
override val optionalT1: Optional<T1> = t1.optional
override val optionalT2: Optional<T2> = Optional.absent()
@@ -108,7 +114,7 @@ data class EitherFirst<T1, T2>(
*/
@Serializable
data class EitherSecond<T1, T2>(
val t2: T2
override val t2: T2
) : Either<T1, T2> {
override val optionalT1: Optional<T1> = Optional.absent()
override val optionalT2: Optional<T2> = t2.optional

View File

@@ -1,53 +0,0 @@
package dev.inmo.micro_utils.common
import kotlin.jvm.JvmName
interface SimpleMapper<T1, T2> {
fun convertToT1(from: T2): T1
fun convertToT2(from: T1): T2
}
@JvmName("convertFromT2")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T2) = convertToT1(from)
@JvmName("convertFromT1")
fun <T1, T2> SimpleMapper<T1, T2>.convert(from: T1) = convertToT2(from)
class SimpleMapperImpl<T1, T2>(
private val t1: (T2) -> T1,
private val t2: (T1) -> T2,
) : SimpleMapper<T1, T2> {
override fun convertToT1(from: T2): T1 = t1.invoke(from)
override fun convertToT2(from: T1): T2 = t2.invoke(from)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleMapper(
noinline t1: (T2) -> T1,
noinline t2: (T1) -> T2,
) = SimpleMapperImpl(t1, t2)
interface SimpleSuspendableMapper<T1, T2> {
suspend fun convertToT1(from: T2): T1
suspend fun convertToT2(from: T1): T2
}
@JvmName("convertFromT2")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T2) = convertToT1(from)
@JvmName("convertFromT1")
suspend fun <T1, T2> SimpleSuspendableMapper<T1, T2>.convert(from: T1) = convertToT2(from)
class SimpleSuspendableMapperImpl<T1, T2>(
private val t1: suspend (T2) -> T1,
private val t2: suspend (T1) -> T2,
) : SimpleSuspendableMapper<T1, T2> {
override suspend fun convertToT1(from: T2): T1 = t1.invoke(from)
override suspend fun convertToT2(from: T1): T2 = t2.invoke(from)
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T1, T2> simpleSuspendableMapper(
noinline t1: suspend (T2) -> T1,
noinline t2: suspend (T1) -> T2,
) = SimpleSuspendableMapperImpl(t1, t2)

View File

@@ -41,18 +41,10 @@ data class Optional<T> internal constructor(
inline val <T> T.optional
get() = Optional.presented(this)
inline val <T : Any> T?.optionalOrAbsentIfNull
get() = if (this == null) {
Optional.absent<T>()
} else {
Optional.presented(this)
}
/**
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply {
@OptIn(Warning::class)
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) }
}
@@ -60,7 +52,6 @@ inline fun <T> Optional<T>.onPresented(block: (T) -> Unit): Optional<T> = apply
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
@OptIn(Warning::class)
if (dataPresented) { @Suppress("UNCHECKED_CAST") block(data as T) } else null
}
@@ -68,7 +59,6 @@ inline fun <T, R> Optional<T>.mapOnPresented(block: (T) -> R): R? = run {
* Will call [block] when data absent ([Optional.dataPresented] == false)
*/
inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
@OptIn(Warning::class)
if (!dataPresented) { block() }
}
@@ -76,22 +66,27 @@ inline fun <T> Optional<T>.onAbsent(block: () -> Unit): Optional<T> = apply {
* Will call [block] when data presented ([Optional.dataPresented] == true)
*/
inline fun <T, R> Optional<T>.mapOnAbsent(block: () -> R): R? = run {
@OptIn(Warning::class)
if (!dataPresented) { block() } else null
if (!dataPresented) { @Suppress("UNCHECKED_CAST") block() } else null
}
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or null otherwise
*/
fun <T> Optional<T>.dataOrNull() = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
fun <T> Optional<T>.dataOrNull() = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else null
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or throw [throwable] otherwise
*/
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
fun <T> Optional<T>.dataOrThrow(throwable: Throwable) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else throw throwable
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = @OptIn(Warning::class) if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
inline fun <T> Optional<T>.dataOrElse(block: () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()
/**
* Returns [Optional.data] if [Optional.dataPresented] of [this] is true, or call [block] and returns the result of it
*/
@Deprecated("dataOrElse now is inline", ReplaceWith("dataOrElse", "dev.inmo.micro_utils.common.dataOrElse"))
suspend fun <T> Optional<T>.dataOrElseSuspendable(block: suspend () -> T) = if (dataPresented) @Suppress("UNCHECKED_CAST") (data as T) else block()

View File

@@ -1,37 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
@Serializable
@JvmInline
value class Progress private constructor(
val of1: Double
) {
val of1Float
get() = of1.toFloat()
val of100
get() = of1 * 100
val of100Float
get() = of100.toFloat()
val of100Int
get() = of100.toInt()
init {
require(of1 in rangeOfValues) {
"Progress main value should be in $rangeOfValues, but incoming value is $of1"
}
}
companion object {
val rangeOfValues = 0.0 .. 1.0
val START = Progress(rangeOfValues.start)
val COMPLETED = Progress(rangeOfValues.endInclusive)
operator fun invoke(of1: Double) = Progress(of1.coerceIn(rangeOfValues))
operator fun invoke(part: Number, total: Number) = Progress(
part.toDouble() / total.toDouble()
)
}
}

View File

@@ -1,80 +0,0 @@
@file:Suppress(
"RemoveRedundantCallsOfConversionMethods",
"RedundantVisibilityModifier",
)
package dev.inmo.micro_utils.common
import kotlin.Byte
import kotlin.Double
import kotlin.Float
import kotlin.Int
import kotlin.Long
import kotlin.Short
import kotlin.Suppress
public operator fun Progress.plus(other: Progress): Progress = Progress(of1 + other.of1)
public operator fun Progress.minus(other: Progress): Progress = Progress(of1 - other.of1)
public operator fun Progress.plus(i: Byte): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Byte): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Byte): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Byte): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Byte): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Short): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Short): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Short): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Short): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Short): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Int): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Int): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Int): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Int): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Int): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Long): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Long): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Long): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Long): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Long): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Float): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Float): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Float): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Float): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Float): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.plus(i: Double): Progress = Progress((of1 + i).toDouble())
public operator fun Progress.minus(i: Double): Progress = Progress((of1 - i).toDouble())
public operator fun Progress.times(i: Double): Progress = Progress((of1 * i).toDouble())
public operator fun Progress.div(i: Double): Progress = Progress((of1 / i).toDouble())
public operator fun Progress.rem(i: Double): Progress = Progress((of1 % i).toDouble())
public operator fun Progress.compareTo(other: Progress): Int = (of1 - other.of1).toInt()

View File

@@ -32,7 +32,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) {
for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
@@ -55,7 +55,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (step in oldList.indices) {
for ((i, _) in withIndex) {
for ((i, v) in withIndex) {
val mutable = oldList.toMutableList()
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step)
@@ -104,7 +104,7 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) {
for ((i, _) in withIndex) {
for ((i, v) in withIndex) {
if (i + count > oldList.lastIndex) {
continue
}
@@ -129,20 +129,15 @@ class DiffUtilsTests {
val withIndex = oldList.withIndex()
for (step in oldList.indices) {
for ((i, _) in withIndex) {
for ((i, v) in withIndex) {
val mutable = oldList.toMutableList()
val newList = if (step == 0) {
i until oldList.size
} else {
i until oldList.size step step
}
newList.forEach { index ->
val changes = (
if (step == 0) i until oldList.size else (i until oldList.size step step)
).map { index ->
IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also {
mutable[index] = it.value
}
}
val mutableOldList = oldList.toMutableList()
mutableOldList.applyDiff(mutable)
assertEquals(

View File

@@ -1,12 +0,0 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.Element
inline val Element.isOverflowWidth
get() = scrollWidth > clientWidth
inline val Element.isOverflowHeight
get() = scrollHeight > clientHeight
inline val Element.isOverflow
get() = isOverflowHeight || isOverflowWidth

View File

@@ -1,58 +0,0 @@
package dev.inmo.micro_utils.common
import org.w3c.dom.*
import kotlin.js.Json
import kotlin.js.json
external class ResizeObserver(
callback: (Array<ResizeObserverEntry>, ResizeObserver) -> Unit
) {
fun observe(target: Element, options: Json = definedExternally)
fun unobserve(target: Element)
fun disconnect()
}
external interface ResizeObserverSize {
val blockSize: Float
val inlineSize: Float
}
external interface ResizeObserverEntry {
val borderBoxSize: Array<ResizeObserverSize>
val contentBoxSize: Array<ResizeObserverSize>
val devicePixelContentBoxSize: Array<ResizeObserverSize>
val contentRect: DOMRectReadOnly
val target: Element
}
fun ResizeObserver.observe(target: Element, options: ResizeObserverObserveOptions) = observe(
target,
json(
"box" to options.box ?.name
)
)
class ResizeObserverObserveOptions(
val box: Box? = null
) {
sealed interface Box {
val name: String
object Content : Box {
override val name: String
get() = "content-box"
}
object Border : Box {
override val name: String
get() = "border-box"
}
object DevicePixelContent : Box {
override val name: String
get() = "device-pixel-content-box"
}
}
}

View File

@@ -1,20 +0,0 @@
package dev.inmo.micro_utils.common
import java.io.File
import java.io.InputStream
import java.util.UUID
fun InputStream.downloadToTempFile(
fileName: String = UUID.randomUUID().toString(),
fileExtension: String? = ".temp",
folder: File? = null
) = File.createTempFile(
fileName,
fileExtension,
folder
).apply {
outputStream().use {
copyTo(it)
}
deleteOnExit()
}

View File

@@ -1,76 +0,0 @@
package dev.inmo.micro_utils.common
import android.os.Bundle
import android.os.Parcelable
import androidx.fragment.app.Fragment
import java.io.Serializable
import kotlin.reflect.KProperty
object ArgumentPropertyNullableDelegate {
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T? {
val arguments = thisRef.arguments ?: return null
val key = property.name
return when (property.getter.returnType.classifier) {
// Scalars
String::class -> arguments.getString(key)
Boolean::class -> arguments.getBoolean(key)
Byte::class -> arguments.getByte(key)
Char::class -> arguments.getChar(key)
Double::class -> arguments.getDouble(key)
Float::class -> arguments.getFloat(key)
Int::class -> arguments.getInt(key)
Long::class -> arguments.getLong(key)
Short::class -> arguments.getShort(key)
// References
Bundle::class -> arguments.getBundle(key)
CharSequence::class -> arguments.getCharSequence(key)
Parcelable::class -> arguments.getParcelable(key)
// Scalar arrays
BooleanArray::class -> arguments.getBooleanArray(key)
ByteArray::class -> arguments.getByteArray(key)
CharArray::class -> arguments.getCharArray(key)
DoubleArray::class -> arguments.getDoubleArray(key)
FloatArray::class -> arguments.getFloatArray(key)
IntArray::class -> arguments.getIntArray(key)
LongArray::class -> arguments.getLongArray(key)
ShortArray::class -> arguments.getShortArray(key)
Array::class -> {
val componentType = property.returnType.classifier ?.javaClass ?.componentType!!
@Suppress("UNCHECKED_CAST") // Checked by reflection.
when {
Parcelable::class.java.isAssignableFrom(componentType) -> {
arguments.getParcelableArray(key)
}
String::class.java.isAssignableFrom(componentType) -> {
arguments.getStringArray(key)
}
CharSequence::class.java.isAssignableFrom(componentType) -> {
arguments.getCharSequenceArray(key)
}
Serializable::class.java.isAssignableFrom(componentType) -> {
arguments.getSerializable(key)
}
else -> {
val valueType = componentType.canonicalName
throw IllegalArgumentException(
"Illegal value array type $valueType for key \"$key\""
)
}
}
}
Serializable::class -> arguments.getSerializable(key)
else -> null
} as? T
}
}
object ArgumentPropertyNonNullableDelegate {
operator fun <T: Any> getValue(thisRef: Fragment, property: KProperty<*>): T {
return ArgumentPropertyNullableDelegate.getValue<T>(thisRef, property)!!
}
}
fun argumentOrNull() = ArgumentPropertyNullableDelegate
fun argumentOrThrow() = ArgumentPropertyNonNullableDelegate

View File

@@ -1,61 +0,0 @@
package dev.inmo.micro_utils.common
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.fragment.app.Fragment
fun findViewsByTag(viewGroup: ViewGroup, tag: Any?): List<View> {
return viewGroup.children.flatMap {
findViewsByTag(it, tag)
}.toList()
}
fun findViewsByTag(viewGroup: ViewGroup, key: Int, tag: Any?): List<View> {
return viewGroup.children.flatMap {
findViewsByTag(it, key, tag)
}.toList()
}
fun findViewsByTag(view: View, tag: Any?): List<View> {
val result = mutableListOf<View>()
if (view.tag == tag) {
result.add(view)
}
if (view is ViewGroup) {
result.addAll(findViewsByTag(view, tag))
}
return result.toList()
}
fun findViewsByTag(view: View, key: Int, tag: Any?): List<View> {
val result = mutableListOf<View>()
if (view.getTag(key) == tag) {
result.add(view)
}
if (view is ViewGroup) {
result.addAll(findViewsByTag(view, key, tag))
}
return result.toList()
}
fun Activity.findViewsByTag(tag: Any?) = rootView ?.let {
findViewsByTag(it, tag)
}
fun Activity.findViewsByTag(key: Int, tag: Any?) = rootView ?.let {
findViewsByTag(it, key, tag)
}
fun Fragment.findViewsByTag(tag: Any?) = view ?.let {
findViewsByTag(it, tag)
}
fun Fragment.findViewsByTag(key: Int, tag: Any?) = view ?.let {
findViewsByTag(it, key, tag)
}
fun Fragment.findViewsByTagInActivity(tag: Any?) = activity ?.findViewsByTag(tag)
fun Fragment.findViewsByTagInActivity(key: Int, tag: Any?) = activity ?.findViewsByTag(key, tag)

View File

@@ -1,7 +0,0 @@
package dev.inmo.micro_utils.common
import android.app.Activity
import android.view.View
val Activity.rootView: View?
get() = findViewById<View?>(android.R.id.content) ?.rootView ?: window.decorView.findViewById<View?>(android.R.id.content) ?.rootView

View File

@@ -1,26 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asMutableComposeListState(
scope: CoroutineScope
): SnapshotStateList<T> {
val state = mutableStateListOf<T>()
subscribeSafelyWithoutExceptions(scope) {
state.applyDiff(it)
}
return state
}
@Suppress("NOTHING_TO_INLINE")
inline fun <reified T> Flow<List<T>>.asComposeList(
scope: CoroutineScope
): List<T> = asMutableComposeListState(scope)

View File

@@ -1,35 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.*
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
fun <T> Flow<T>.asMutableComposeState(
initial: T,
scope: CoroutineScope
): MutableState<T> {
val state = mutableStateOf(initial)
subscribeSafelyWithoutExceptions(scope) { state.value = it }
return state
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asMutableComposeState(
scope: CoroutineScope
): MutableState<T> = asMutableComposeState(value, scope)
fun <T> Flow<T>.asComposeState(
initial: T,
scope: CoroutineScope
): State<T> {
val state = asMutableComposeState(initial, scope)
return derivedStateOf { state.value }
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.asComposeState(
scope: CoroutineScope
): State<T> = asComposeState(value, scope)

View File

@@ -16,7 +16,6 @@ fun <T> Flow<T>.toMutableState(
return state
}
@Suppress("NOTHING_TO_INLINE")
inline fun <T> StateFlow<T>.toMutableState(
scope: CoroutineScope
): MutableState<T> = toMutableState(value, scope)

View File

@@ -6,12 +6,11 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.cancellation.CancellationException
private sealed interface AccumulatorFlowStep<T>
private data class DataRetrievedAccumulatorFlowStep<T>(val data: T) : AccumulatorFlowStep<T>
private data class SubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
private data class UnsubscribeAccumulatorFlowStep<T>(val channel: Channel<T>) : AccumulatorFlowStep<T>
private sealed interface AccumulatorFlowStep
private data class DataRetrievedAccumulatorFlowStep(val data: Any) : AccumulatorFlowStep
private data class SubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
private data class UnsubscribeAccumulatorFlowStep(val channel: Channel<Any>) : AccumulatorFlowStep
/**
* This [Flow] will have behaviour very similar to [SharedFlow], but there are several differences:
@@ -27,12 +26,12 @@ class AccumulatorFlow<T>(
private val subscope = scope.LinkedSupervisorScope()
private val activeData = ArrayDeque<T>()
private val dataMutex = Mutex()
private val channelsForBroadcast = mutableListOf<Channel<T>>()
private val channelsForBroadcast = mutableListOf<Channel<Any>>()
private val channelsMutex = Mutex()
private val steps = subscope.actor<AccumulatorFlowStep<T>> { step ->
private val steps = subscope.actor<AccumulatorFlowStep> { step ->
when (step) {
is DataRetrievedAccumulatorFlowStep -> {
if (activeData.firstOrNull() === step.data) {
if (activeData.first() === step.data) {
dataMutex.withLock {
activeData.removeFirst()
}
@@ -43,7 +42,7 @@ class AccumulatorFlow<T>(
dataMutex.withLock {
val dataToSend = activeData.toList()
safelyWithoutExceptions {
dataToSend.forEach { step.channel.send(it) }
dataToSend.forEach { step.channel.send(it as Any) }
}
}
}
@@ -59,29 +58,24 @@ class AccumulatorFlow<T>(
channelsMutex.withLock {
channelsForBroadcast.forEach { channel ->
safelyWithResult {
channel.send(it)
channel.send(it as Any)
}
}
}
}
override suspend fun collectSafely(collector: FlowCollector<T>) {
val channel = Channel<T>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
val channel = Channel<Any>(Channel.UNLIMITED, BufferOverflow.SUSPEND)
steps.send(SubscribeAccumulatorFlowStep(channel))
val result = runCatchingSafely {
for (data in channel) {
val emitResult = runCatchingSafely {
collector.emit(data)
}
if (emitResult.isSuccess || emitResult.exceptionOrNull() is CancellationException) {
steps.send(DataRetrievedAccumulatorFlowStep(data))
}
emitResult.getOrThrow()
for (data in channel) {
try {
collector.emit(data as T)
steps.send(DataRetrievedAccumulatorFlowStep(data))
} finally {
channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel))
}
}
channel.cancel()
steps.send(UnsubscribeAccumulatorFlowStep(channel))
result.getOrThrow()
}
}

View File

@@ -1,15 +1,19 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
fun <T> CoroutineScope.actor(
channelCapacity: Int = Channel.UNLIMITED,
block: suspend (T) -> Unit
): Channel<T> {
val channel = Channel<T>(channelCapacity)
channel.consumeAsFlow().subscribe(this, block)
launch {
for (data in channel) {
block(data)
}
}
return channel
}

View File

@@ -1,30 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
fun <T> CoroutineScope.actorAsync(
channelCapacity: Int = Channel.UNLIMITED,
markerFactory: suspend (T) -> Any? = { null },
block: suspend (T) -> Unit
): Channel<T> {
val channel = Channel<T>(channelCapacity)
channel.consumeAsFlow().subscribeAsync(this, markerFactory, block)
return channel
}
inline fun <T> CoroutineScope.safeActorAsync(
channelCapacity: Int = Channel.UNLIMITED,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline markerFactory: suspend (T) -> Any? = { null },
crossinline block: suspend (T) -> Unit
): Channel<T> = actorAsync(
channelCapacity,
markerFactory
) {
safely(onException) {
block(it)
}
}

View File

@@ -6,19 +6,23 @@ import kotlin.coroutines.*
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
scope: CoroutineScope,
cancelOnResult: Boolean = true
): Pair<Deferred<T>, T> {
val resultDeferred = CompletableDeferred<Pair<Deferred<T>, T>>()
val scope = scope.LinkedSupervisorScope()
forEach {
scope.launch {
resultDeferred.complete(it to it.await())
scope.cancel()
): Pair<Deferred<T>, T> = suspendCoroutine<Pair<Deferred<T>, T>> { continuation ->
scope.launch(SupervisorJob()) {
val scope = this
forEach {
scope.launch {
continuation.resume(it to it.await())
scope.cancel()
}
}
}
return resultDeferred.await().also {
if (cancelOnResult) {
forEach {
runCatchingSafely { it.cancel() }
}.also {
if (cancelOnResult) {
forEach {
try {
it.cancel()
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
}

View File

@@ -1,39 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.*
import kotlin.js.JsName
import kotlin.jvm.JvmName
inline fun <T, R> Flow<Flow<T>>.flatMap(
crossinline mapper: suspend (T) -> R
) = flow {
collect {
it.collect {
emit(mapper(it))
}
}
}
@JsName("flatMapIterable")
@JvmName("flatMapIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMap(
crossinline mapper: suspend (T) -> R
) = map {
it.asFlow()
}.flatMap(mapper)
inline fun <T, R> Flow<Flow<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
@JsName("flatMapNotNullIterable")
@JvmName("flatMapNotNullIterable")
inline fun <T, R> Flow<Iterable<T>>.flatMapNotNull(
crossinline mapper: suspend (T) -> R
) = flatMap(mapper).takeNotNull()
fun <T> Flow<Flow<T>>.flatten() = flatMap { it }
@JsName("flattenIterable")
@JvmName("flattenIterable")
fun <T> Flow<Iterable<T>>.flatten() = flatMap { it }

View File

@@ -1,6 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.flow.*
fun <T> Flow<T>.takeNotNull() = mapNotNull { it }
fun <T> Flow<T>.filterNotNull() = takeNotNull()

View File

@@ -1,50 +0,0 @@
package dev.inmo.micro_utils.coroutines
import android.view.View
import android.view.ViewGroup
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
/**
* [kotlinx.coroutines.flow.Flow]-based [android.view.ViewGroup.OnHierarchyChangeListener]
*
* @param recursive If set, any call of [onChildViewAdded] will check if child [View] is [ViewGroup] and subscribe to this
* [ViewGroup] too
* @param [_onChildViewAdded] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewAdded] flow
* @param [_onChildViewRemoved] Internal [MutableSharedFlow] which will be used to pass data to [onChildViewRemoved] flow
*/
class FlowOnHierarchyChangeListener(
private val recursive: Boolean = false,
private val _onChildViewAdded: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE),
private val _onChildViewRemoved: MutableSharedFlow<Pair<View, View>> = MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
) : ViewGroup.OnHierarchyChangeListener {
val onChildViewAdded = _onChildViewAdded.asSharedFlow()
val onChildViewRemoved = _onChildViewRemoved.asSharedFlow()
/**
* Will emit data into [onChildViewAdded] flow. If [recursive] is true and [child] is [ViewGroup] will also
* subscribe to [child] hierarchy changes.
*
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewAdded]. That is why its default extraBufferCapacity is
* [Int.MAX_VALUE]
*/
override fun onChildViewAdded(parent: View, child: View) {
_onChildViewAdded.tryEmit(parent to child)
if (recursive && child is ViewGroup) {
child.setOnHierarchyChangeListener(this)
}
}
/**
* Just emit data into [onChildViewRemoved]
*
* Due to the fact that this method is not suspendable, [FlowOnHierarchyChangeListener] will use
* [MutableSharedFlow.tryEmit] to send data into [_onChildViewRemoved]. That is why its default extraBufferCapacity is
* [Int.MAX_VALUE]
*/
override fun onChildViewRemoved(parent: View, child: View) {
_onChildViewRemoved.tryEmit(parent to child)
}
}

View File

@@ -1,17 +0,0 @@
package dev.inmo.micro_utils.coroutines
import android.view.ViewGroup
import android.view.ViewGroup.OnHierarchyChangeListener
/**
* Use [ViewGroup.setOnHierarchyChangeListener] recursively for all available [ViewGroup]s starting with [this].
* This extension DO NOT guarantee that recursive subscription will happen after this method call
*/
fun ViewGroup.setOnHierarchyChangeListenerRecursively(
listener: OnHierarchyChangeListener
) {
setOnHierarchyChangeListener(listener)
(0 until childCount).forEach {
(getChildAt(it) as? ViewGroup) ?.setOnHierarchyChangeListenerRecursively(listener)
}
}

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.crypto
val HEX_ARRAY = "0123456789abcdef".toCharArray()
fun SourceBytes.hex(): String {
val hexChars = CharArray(size * 2)
for (j in indices) {
val v: Int = this[j].toInt() and 0xFF
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
}
return hexChars.concatToString()
}
fun SourceString.hex(): String = encodeToByteArray().hex()

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.crypto
import kotlin.test.*
class Hex {
@Test
fun testSimpleHmacSHA256Message() {
val text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
val resultHex = text.hex()
assertEquals(
"4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742c2073656420646f20656975736d6f642074656d706f7220696e6369646964756e74207574206c61626f726520657420646f6c6f7265206d61676e6120616c697175612e20557420656e696d206164206d696e696d2076656e69616d2c2071756973206e6f737472756420657865726369746174696f6e20756c6c616d636f206c61626f726973206e69736920757420616c697175697020657820656120636f6d6d6f646f20636f6e7365717561742e2044756973206175746520697275726520646f6c6f7220696e20726570726568656e646572697420696e20766f6c7570746174652076656c697420657373652063696c6c756d20646f6c6f726520657520667567696174206e756c6c612070617269617475722e204578636570746575722073696e74206f6363616563617420637570696461746174206e6f6e2070726f6964656e742c2073756e7420696e2063756c706120717569206f666669636961206465736572756e74206d6f6c6c697420616e696d20696420657374206c61626f72756d2e",
resultHex
)
}
}

View File

@@ -121,11 +121,11 @@ tasks.dokkaHtml {
// }
named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain"))
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
}
named("androidMain") {
sourceRoots.setFrom(findSourcesWithName("androidMain"))
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
}
}
}

View File

@@ -47,11 +47,24 @@ fun <I : O, O : State> CheckableHandlerHolder(
}
)
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
fun <I : O, O : State> StateHandlerHolder(
inputKlass: KClass<I>,
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(inputKlass, strict, delegateTo)
inline fun <reified I : O, O : State> CheckableHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(I::class, strict, delegateTo)
@Deprecated("Renamed", ReplaceWith("CheckableHandlerHolder"))
inline fun <reified I : O, O : State> StateHandlerHolder(
strict: Boolean = false,
delegateTo: StatesHandler<I, O>
) = CheckableHandlerHolder(strict, delegateTo)
inline fun <reified I : O, O: State> StatesHandler<I, O>.holder(
strict: Boolean = true
) = CheckableHandlerHolder<I, O>(

View File

@@ -3,10 +3,7 @@ package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -16,24 +13,13 @@ import kotlinx.coroutines.sync.withLock
* handling until [start] method will be called
*/
interface StatesMachine<T : State> : StatesHandler<T, T> {
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T>
): T? {
return runCatchingSafely {
handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}.getOrElse {
onStateHandlingErrorHandler(state, it)
}
}
suspend fun launchStateHandling(
state: T,
handlers: List<CheckableHandlerHolder<in T, T>>
): T? {
return launchStateHandling(state, handlers, defaultStateHandlingErrorHandler())
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}
/**
@@ -52,9 +38,8 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
*/
operator fun <T: State> invoke(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) = DefaultStatesMachine(statesManager, handlers, onStateHandlingErrorHandler)
handlers: List<CheckableHandlerHolder<in T, T>>
) = DefaultStatesMachine(statesManager, handlers)
}
}
@@ -67,17 +52,12 @@ interface StatesMachine<T : State> : StatesHandler<T, T> {
open class DefaultStatesMachine <T: State>(
protected val statesManager: StatesManager<T>,
protected val handlers: List<CheckableHandlerHolder<in T, T>>,
protected val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : StatesMachine<T> {
/**
* Will call [launchStateHandling] for state handling
*/
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(state, handlers)
override suspend fun launchStateHandling(state: T, handlers: List<CheckableHandlerHolder<in T, T>>): T? {
return launchStateHandling(state, handlers, onStateHandlingErrorHandler)
}
/**
* This
*/
@@ -86,10 +66,10 @@ open class DefaultStatesMachine <T: State>(
protected open suspend fun performUpdate(state: T) {
val newState = launchStateHandling(state, handlers)
if (newState == null) {
statesManager.endChain(state)
} else {
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
@@ -119,7 +99,7 @@ open class DefaultStatesMachine <T: State>(
* [StatesManager.endChain].
*/
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) {
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
@@ -135,6 +115,10 @@ open class DefaultStatesMachine <T: State>(
}
}
}
statesManager.getActiveStates().forEach {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
}
/**

View File

@@ -1,8 +1,6 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.withLock
@@ -22,11 +20,9 @@ interface UpdatableStatesMachine<T : State> : StatesMachine<T> {
open class DefaultUpdatableStatesMachine<T : State>(
statesManager: StatesManager<T>,
handlers: List<CheckableHandlerHolder<in T, T>>,
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
) : DefaultStatesMachine<T>(
statesManager,
handlers,
onStateHandlingErrorHandler
handlers
), UpdatableStatesMachine<T> {
protected val jobsStates = mutableMapOf<Job, T>()
@@ -38,7 +34,7 @@ open class DefaultUpdatableStatesMachine<T : State>(
*/
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
statesJobsMutex.withLock {
if (shouldReplaceJob(previousState, actualState)) {
if (compare(previousState, actualState)) {
statesJobs[actualState] ?.cancel()
}
val job = previousState.mapOnPresented {
@@ -52,7 +48,6 @@ open class DefaultUpdatableStatesMachine<T : State>(
statesJobs.remove(
jobsStates[job] ?: return@withLock
)
jobsStates.remove(job)
}
}
}
@@ -68,6 +63,9 @@ open class DefaultUpdatableStatesMachine<T : State>(
*/
protected open suspend fun shouldReplaceJob(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
@Deprecated("Overwrite shouldReplaceJob instead")
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = shouldReplaceJob(previous, new)
override suspend fun updateChain(currentState: T, newState: T) {
statesManager.update(currentState, newState)
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.micro_utils.fsm.common.managers
import dev.inmo.micro_utils.fsm.common.State
import kotlinx.coroutines.flow.*
/**
* Creates [DefaultStatesManager] with [InMemoryDefaultStatesManagerRepo]
*
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state
*/
@Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager"))
fun <T: State> InMemoryStatesManager(
onStartContextsConflictResolver: suspend (old: T, new: T) -> Boolean = { _, _ -> true },
onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
) = DefaultStatesManager(onStartContextsConflictResolver = onStartContextsConflictResolver, onUpdateContextsConflictResolver = onUpdateContextsConflictResolver)

View File

@@ -1,6 +0,0 @@
package dev.inmo.micro_utils.fsm.common.utils
typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T?
val DefaultStateHandlingErrorHandler: StateHandlingErrorHandler<*> = { _, _ -> null }
inline fun <T> defaultStateHandlingErrorHandler(): StateHandlingErrorHandler<T> = DefaultStateHandlingErrorHandler as StateHandlingErrorHandler<T>

View File

@@ -1,6 +1,7 @@
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.InMemoryStatesManager
import kotlinx.coroutines.*
sealed interface TrafficLightState : State {

View File

@@ -21,7 +21,7 @@ if (new File(projectDir, "secret.gradle").exists()) {
owner "InsanusMokrassar"
repo "MicroUtils"
tagName "v${project.version}"
tagName "${project.version}"
releaseName "${project.version}"
targetCommitish "${project.version}"

View File

@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.15.1
android_code_version=167
version=0.9.25
android_code_version=115

View File

@@ -1,72 +1,55 @@
[versions]
kt = "1.7.20"
kt-serialization = "1.4.1"
kt-coroutines = "1.6.4"
kt = "1.6.10"
kt-serialization = "1.3.2"
kt-coroutines = "1.6.1"
kslog = "0.5.4"
jb-compose = "1.1.1"
jb-exposed = "0.37.3"
jb-dokka = "1.6.10"
jb-compose = "1.2.1"
jb-exposed = "0.41.1"
jb-dokka = "1.7.20"
klock = "2.7.0"
uuid = "0.4.0"
klock = "3.4.0"
uuid = "0.6.0"
ktor = "1.6.8"
ktor = "2.1.3"
gh-release = "2.2.12"
gh-release = "2.4.1"
android-gradle = "7.0.4"
dexcount = "3.0.1"
koin = "3.2.2"
android-gradle = "7.2.2"
dexcount = "3.1.0"
android-coreKtx = "1.9.0"
android-coreKtx = "1.7.0"
android-recyclerView = "1.2.1"
android-appCompat = "1.5.1"
android-fragment = "1.5.3"
android-espresso = "3.4.0"
android-test = "1.1.3"
android-appCompat = "1.4.1"
android-espresso = "3.3.0"
android-test = "1.1.2"
android-props-minSdk = "21"
android-props-compileSdk = "33"
android-props-buildTools = "33.0.0"
android-props-minSdk = "19"
android-props-compileSdk = "32"
android-props-buildTools = "32.0.0"
[libraries]
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
kt-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kt" }
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kt-serialization" }
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
kt-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kt-coroutines" }
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" }
ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor" }
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
@@ -75,7 +58,6 @@ jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,28 +0,0 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api libs.koin
api libs.uuid
}
}
jvmMain {
dependencies {
api libs.kt.reflect
}
}
androidMain {
dependencies {
api libs.kt.reflect
}
}
}
}

View File

@@ -1,12 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
/**
* Will be useful in case you need to declare some singles with one type several types, but need to separate them and do
* not care about how :)
*/
inline fun <reified T : Any> Module.factoryWithRandomQualifier(
noinline definition: Definition<T>
) = factory(RandomQualifier(), definition)

View File

@@ -1,12 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
import org.koin.core.qualifier.StringQualifier
inline fun <reified T : Any> Module.factory(
qualifier: String,
noinline definition: Definition<T>
) = factory(StringQualifier(qualifier), definition)

View File

@@ -1,8 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.scope.Scope
inline fun <reified T : Any> Scope.getAllDistinct() = getAll<T>().distinct()
inline fun <reified T : Any> Koin.getAllDistinct() = getAll<T>().distinct()

View File

@@ -1,8 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.Koin
import org.koin.core.scope.Scope
inline fun <reified T : Any> Scope.getAny() = getAll<T>().first()
inline fun <reified T : Any> Koin.getAny() = getAll<T>().first()

View File

@@ -1,6 +0,0 @@
package dev.inmo.micro_utils.koin
import com.benasher44.uuid.uuid4
import org.koin.core.qualifier.StringQualifier
fun RandomQualifier(randomFun: () -> String = { uuid4().toString() }) = StringQualifier(randomFun())

View File

@@ -1,13 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
/**
* Will be useful in case you need to declare some singles with one type several types, but need to separate them and do
* not care about how :)
*/
inline fun <reified T : Any> Module.singleWithRandomQualifier(
createdAtStart: Boolean = false,
noinline definition: Definition<T>
) = single(RandomQualifier(), createdAtStart, definition)

View File

@@ -1,12 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.module.Module
import org.koin.core.qualifier.StringQualifier
inline fun <reified T : Any> Module.single(
qualifier: String,
createdAtStart: Boolean = false,
noinline definition: Definition<T>
) = single(StringQualifier(qualifier), createdAtStart, definition)

View File

@@ -1,27 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.binds
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: Qualifier? = null,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}
inline fun <reified T : Any> Module.factoryWithBinds(
qualifier: String,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return factory(qualifier, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}

View File

@@ -1,13 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import kotlin.reflect.KClass
inline fun <reified T : Any> Module.factoryWithRandomQualifierAndBinds(
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return factoryWithBinds(RandomQualifier(), bindFilter, definition)
}

View File

@@ -1,30 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.binds
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
inline fun <reified T : Any> Module.singleWithBinds(
qualifier: Qualifier? = null,
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}
inline fun <reified T : Any> Module.singleWithBinds(
qualifier: String,
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return single(qualifier, createdAtStart, definition) binds (T::class.allSuperclasses.filter(bindFilter).toTypedArray())
}

View File

@@ -1,14 +0,0 @@
package dev.inmo.micro_utils.koin
import org.koin.core.definition.Definition
import org.koin.core.instance.InstanceFactory
import org.koin.core.module.Module
import kotlin.reflect.KClass
inline fun <reified T : Any> Module.singleWithRandomQualifierAndBinds(
createdAtStart: Boolean = false,
bindFilter: (KClass<*>) -> Boolean = { true },
noinline definition: Definition<T>
): Pair<Module, InstanceFactory<*>> {
return singleWithBinds(RandomQualifier(), createdAtStart, bindFilter, definition)
}

View File

@@ -1 +0,0 @@
<manifest package="dev.inmo.micro_utils.koin"/>

View File

@@ -15,9 +15,5 @@ kotlin {
api libs.ktor.client
}
}
androidMain {
dependsOn jvmMain
}
}
}

View File

@@ -1,9 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import io.ktor.client.call.body
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpStatusCode
suspend inline fun <reified T : Any> HttpResponse.bodyOrNull() = takeIf {
status == HttpStatusCode.OK
} ?.body<T>()

View File

@@ -0,0 +1,78 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.features.websocket.ws
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readBytes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.serialization.DeserializationStrategy
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
): Flow<T> {
val correctedUrl = url.asCorrectWebSocketUrl
return channelFlow {
val producerScope = this@channelFlow
do {
val reconnect = try {
safely {
ws(correctedUrl, requestBuilder) {
for (received in incoming) {
when (received) {
is Frame.Binary -> producerScope.send(conversation(received.readBytes()))
else -> {
producerScope.close()
return@ws
}
}
}
}
}
checkReconnection(null)
} catch (e: Throwable) {
checkReconnection(e).also {
if (!it) {
producerScope.close(e)
}
}
}
} while (reconnect)
if (!producerScope.isClosedForSend) {
safely(
{ it.printStackTrace() }
) {
producerScope.close()
}
}
}
}
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <T> HttpClient.createStandardWebsocketFlow(
url: String,
crossinline checkReconnection: (Throwable?) -> Boolean = { true },
deserializer: DeserializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(
url,
checkReconnection,
requestBuilder
) {
serialFormat.decodeDefault(deserializer, it)
}

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.statement.HttpResponse
import io.ktor.http.isSuccess
inline fun HttpResponse.throwOnUnsuccess(
unsuccessMessage: () -> String
) {
if (status.isSuccess()) {
return
}
throw ClientRequestException(this, unsuccessMessage())
}

View File

@@ -1,103 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.Warning
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.plugins.pluginOrNull
import io.ktor.client.plugins.websocket.*
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.URLProtocol
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
@Warning("This feature is internal and should not be used directly. It is can be changed without any notification and warranty on compile-time or other guaranties")
inline fun <reified T : Any> openBaseWebSocketFlow(
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline webSocketSessionRequest: suspend SendChannel<T>.() -> Unit
): Flow<T> {
return channelFlow {
do {
val reconnect = runCatchingSafely {
webSocketSessionRequest()
checkReconnection(null)
}.getOrElse { e ->
checkReconnection(e).also {
if (!it) {
close(e)
}
}
}
} while (reconnect && isActive)
if (isActive) {
safely {
close()
}
}
}
}
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T : Any> HttpClient.openWebSocketFlow(
url: String,
useSecureConnection: Boolean,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> {
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
return openBaseWebSocketFlow<T>(checkReconnection) {
val block: suspend DefaultClientWebSocketSession.() -> Unit = {
while (isActive) {
send(receiveDeserialized<T>())
}
}
if (useSecureConnection) {
wss(url, requestBuilder, block)
} else {
ws(url, requestBuilder, block)
}
}
}
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T : Any> HttpClient.openWebSocketFlow(
url: String,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> = openWebSocketFlow(url, false, checkReconnection, requestBuilder)
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T : Any> HttpClient.openSecureWebSocketFlow(
url: String,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> = openWebSocketFlow(url, true, checkReconnection, requestBuilder)
/**
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
* connection. Must return true in case if must be reconnected. By default always reconnecting
*/
inline fun <reified T : Any> HttpClient.createStandardWebsocketFlow(
url: String,
noinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {}
): Flow<T> = openWebSocketFlow(url, checkReconnection, requestBuilder)

View File

@@ -1,3 +0,0 @@
package dev.inmo.micro_utils.ktor.client
typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit

View File

@@ -0,0 +1,251 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.client.HttpClient
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.serialization.*
typealias BodyPair<T> = Pair<SerializationStrategy<T>, T>
class UnifiedRequester(
val client: HttpClient = HttpClient(),
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) {
suspend fun <ResultType> uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>
): ResultType = client.uniget(url, resultDeserializer, serialFormat)
fun <T> encodeUrlQueryValue(
serializationStrategy: SerializationStrategy<T>,
value: T
) = serializationStrategy.encodeUrlQueryValue(
value,
serialFormat
)
suspend fun <BodyType, ResultType> unipost(
url: String,
bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
suspend fun <ResultType> unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
suspend fun <BodyType, ResultType> unimultipart(
url: String,
mppFile: MPPFile,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {}
): ResultType = client.unimultipart(
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
)
fun <T> createStandardWebsocketFlow(
url: String,
checkReconnection: (Throwable?) -> Boolean,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = client.createStandardWebsocketFlow(url, checkReconnection, deserializer, serialFormat, requestBuilder)
fun <T> createStandardWebsocketFlow(
url: String,
deserializer: DeserializationStrategy<T>,
requestBuilder: HttpRequestBuilder.() -> Unit = {},
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
}
val defaultRequester = UnifiedRequester()
suspend fun <ResultType> HttpClient.uniget(
url: String,
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = get<StandardKtorSerialInputData>(
url
).let {
serialFormat.decodeDefault(resultDeserializer, it)
}
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
value: T,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = serialFormat.encodeHex(
this,
value
)
suspend fun <BodyType, ResultType> HttpClient.unipost(
url: String,
bodyInfo: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
) = post<StandardKtorSerialInputData>(url) {
body = serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
}.let {
serialFormat.decodeDefault(resultDeserializer, it)
}
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
filename: String,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = submitFormWithBinaryData<StandardKtorSerialInputData>(
url,
formData = formData {
append(
"bytes",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, mimetype)
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
}
) {
requestBuilder()
}.let { serialFormat.decodeDefault(resultDeserializer, it) }
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
filename: String,
otherData: BodyPair<BodyType>,
inputProvider: InputProvider,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
filename,
inputProvider,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)
suspend fun <ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile.filename.string,
mppFile.inputProvider(),
resultDeserializer,
mimetype,
additionalParametersBuilder,
dataHeadersBuilder,
requestBuilder,
serialFormat
)
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
url: String,
mppFile: MPPFile,
otherData: BodyPair<BodyType>,
resultDeserializer: DeserializationStrategy<ResultType>,
mimetype: String = "*/*",
additionalParametersBuilder: FormBuilder.() -> Unit = {},
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
requestBuilder: HttpRequestBuilder.() -> Unit = {},
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
): ResultType = unimultipart(
url,
mppFile,
resultDeserializer,
mimetype,
additionalParametersBuilder = {
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
append(
"data",
InputProvider(serialized.size.toLong()) {
ByteReadPacket(serialized)
},
Headers.build {
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
dataHeadersBuilder()
}
)
additionalParametersBuilder()
},
dataHeadersBuilder,
requestBuilder,
serialFormat
)

View File

@@ -7,5 +7,13 @@ import io.ktor.client.HttpClient
expect suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: OnUploadCallback = { _, _ -> }
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
): TemporalFileId
suspend fun UnifiedRequester.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
): TemporalFileId = client.tempUpload(
fullTempUploadDraftPath, file, onUpload
)

View File

@@ -1,105 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.FileName
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.ktor.common.LambdaInputProvider
import io.ktor.client.HttpClient
import io.ktor.http.Headers
import io.ktor.utils.io.core.Input
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json
data class UniUploadFileInfo(
val fileName: FileName,
val mimeType: String,
val inputAllocator: LambdaInputProvider
)
/**
* Will execute submitting of multipart data request
*
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
* in case you wish to pass other source of multipart binary data than regular file
*
* @see dev.inmo.micro_utils.ktor.server.handleUniUpload
*/
expect suspend fun <T> HttpClient.uniUpload(
url: String,
data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>,
headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T?
/**
* Additional variant of [uniUpload] which will unify sending of some [MPPFile] with the server
*
* @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
*/
suspend fun <T> HttpClient.uniUpload(
url: String,
file: MPPFile,
resultDeserializer: DeserializationStrategy<T>,
additionalData: Map<String, Any> = emptyMap(),
headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T? = uniUpload(
url,
additionalData + ("bytes" to file),
resultDeserializer,
headers,
stringFormat,
onUpload
)
/**
* Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] with the server
*
* @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
*/
suspend fun <T> HttpClient.uniUpload(
url: String,
info: UniUploadFileInfo,
resultDeserializer: DeserializationStrategy<T>,
additionalData: Map<String, Any> = emptyMap(),
headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T? = uniUpload(
url,
additionalData + ("bytes" to info),
resultDeserializer,
headers,
stringFormat,
onUpload
)
/**
* Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] (built from [fileName],
* [mimeType] and [inputAllocator]) with the server
*
* @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
*/
suspend fun <T> HttpClient.uniUpload(
url: String,
fileName: FileName,
mimeType: String,
inputAllocator: LambdaInputProvider,
resultDeserializer: DeserializationStrategy<T>,
additionalData: Map<String, Any> = emptyMap(),
headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T? = uniUpload(
url,
UniUploadFileInfo(fileName, mimeType, inputAllocator),
resultDeserializer,
additionalData,
headers,
stringFormat,
onUpload
)

View File

@@ -1,24 +1,18 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import io.ktor.client.HttpClient
import kotlinx.coroutines.*
import org.w3c.dom.mediasource.ENDED
import org.w3c.dom.mediasource.ReadyState
import org.w3c.xhr.*
import org.w3c.xhr.XMLHttpRequest.Companion.DONE
suspend fun tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: OnUploadCallback
onUpload: (Long, Long) -> Unit
): TemporalFileId {
val formData = FormData()
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
val answer = CompletableDeferred<TemporalFileId>()
formData.append(
"data",
@@ -28,7 +22,7 @@ suspend fun tempUpload(
val request = XMLHttpRequest()
request.responseType = XMLHttpRequestResponseType.TEXT
request.upload.onprogress = {
subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
onUpload(it.loaded.toLong(), it.total.toLong())
}
request.onload = {
if (request.status == 200.toShort()) {
@@ -43,22 +37,22 @@ suspend fun tempUpload(
request.open("POST", fullTempUploadDraftPath, true)
request.send(formData)
answer.invokeOnCompletion {
val handle = currentCoroutineContext().job.invokeOnCompletion {
runCatching {
if (request.readyState != DONE) {
request.abort()
}
request.abort()
}
}
return answer.await().also {
subscope.cancel()
}
return runCatching {
answer.await()
}.also {
handle.dispose()
}.getOrThrow()
}
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: OnUploadCallback
onUpload: (uploaded: Long, count: Long) -> Unit
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)

View File

@@ -1,97 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.Progress
import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import io.ktor.client.HttpClient
import io.ktor.http.Headers
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.job
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import org.khronos.webgl.Int8Array
import org.w3c.files.Blob
import org.w3c.xhr.FormData
import org.w3c.xhr.TEXT
import org.w3c.xhr.XMLHttpRequest
import org.w3c.xhr.XMLHttpRequestResponseType
/**
* Will execute submitting of multipart data request
*
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
* in case you wish to pass other source of multipart binary data than regular file
* @suppress
*/
actual suspend fun <T> HttpClient.uniUpload(
url: String,
data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>,
headers: Headers,
stringFormat: StringFormat,
onUpload: OnUploadCallback
): T? {
val formData = FormData()
val answer = CompletableDeferred<T?>(currentCoroutineContext().job)
val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
data.forEach { (k, v) ->
when (v) {
is MPPFile -> formData.append(
k,
v
)
is UniUploadFileInfo -> formData.append(
k,
Blob(arrayOf(Int8Array(v.inputAllocator().readBytes().toTypedArray()))),
v.fileName.name
)
else -> formData.append(
k,
stringFormat.encodeToString(v)
)
}
}
val request = XMLHttpRequest()
headers.forEach { s, strings ->
request.setRequestHeader(s, strings.joinToString())
}
request.responseType = XMLHttpRequestResponseType.TEXT
request.upload.onprogress = {
subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
}
request.onload = {
if (request.status == 200.toShort()) {
answer.complete(
stringFormat.decodeFromString(resultDeserializer, request.responseText)
)
} else {
answer.completeExceptionally(Exception("Something went wrong: $it"))
}
}
request.onerror = {
answer.completeExceptionally(Exception("Something went wrong: $it"))
}
request.open("POST", url, true)
request.send(formData)
answer.invokeOnCompletion {
runCatching {
if (request.readyState != XMLHttpRequest.DONE) {
request.abort()
}
}
}
return answer.await().also {
subscope.cancel()
}
}

View File

@@ -4,8 +4,6 @@ import dev.inmo.micro_utils.common.MPPFile
import io.ktor.client.request.forms.InputProvider
import io.ktor.utils.io.streams.asInput
fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
inputStream().asInput()
}
actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync()

View File

@@ -4,10 +4,9 @@ import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import io.ktor.client.HttpClient
import io.ktor.client.plugins.onUpload
import io.ktor.client.features.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
import java.net.URLConnection
@@ -18,10 +17,10 @@ internal val MPPFile.mimeType: String
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: OnUploadCallback
onUpload: (Long, Long) -> Unit
): TemporalFileId {
val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData(
val fileId = submitFormWithBinaryData<String>(
fullTempUploadDraftPath,
formData = formData {
append(
@@ -35,6 +34,6 @@ actual suspend fun HttpClient.tempUpload(
}
) {
onUpload(onUpload)
}.bodyAsText()
}
return TemporalFileId(fileId)
}

View File

@@ -1,102 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.Progress
import io.ktor.client.HttpClient
import io.ktor.client.engine.mergeHeaders
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.forms.InputProvider
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.request.headers
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.http.content.PartData
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import java.io.File
/**
* Will execute submitting of multipart data request
*
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
* in case you wish to pass other source of multipart binary data than regular file
* @suppress
*/
actual suspend fun <T> HttpClient.uniUpload(
url: String,
data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>,
headers: Headers,
stringFormat: StringFormat,
onUpload: OnUploadCallback
): T? {
val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
val formData = formData {
data.forEach { (k, v) ->
when (v) {
is File -> append(
k,
v.inputProviderSync(),
Headers.build {
append(HttpHeaders.ContentType, v.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${v.name}\"")
}
)
is UniUploadFileInfo -> append(
k,
InputProvider(block = v.inputAllocator),
Headers.build {
append(HttpHeaders.ContentType, v.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${v.fileName.name}\"")
}
)
else -> append(
k,
stringFormat.encodeToString(v)
)
}
}
}
val requestBuilder: HttpRequestBuilder.() -> Unit = {
headers {
appendAll(headers)
}
onUpload { bytesSentTotal, contentLength ->
onUpload(bytesSentTotal, contentLength)
}
}
val response = if (withBinary) {
submitFormWithBinaryData(
url,
formData,
block = requestBuilder
)
} else {
submitForm(
url,
Parameters.build {
formData.forEach {
val formItem = (it as PartData.FormItem)
append(it.name!!, it.value)
}
},
block = requestBuilder
)
}
return if (response.status == HttpStatusCode.OK) {
stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
} else {
null
}
}

View File

@@ -1,5 +0,0 @@
package dev.inmo.micro_utils.ktor.common
import io.ktor.utils.io.core.Input
typealias LambdaInputProvider = () -> Input

View File

@@ -1,5 +1,3 @@
@file:Suppress("NOTHING_TO_INLINE")
package dev.inmo.micro_utils.ktor.common
import kotlinx.serialization.*

View File

@@ -1,23 +0,0 @@
package dev.inmo.micro_utils.ktor.common
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.streams.asOutput
import java.io.File
import java.io.InputStream
import java.util.UUID
fun Input.downloadToTempFile(
fileName: String = UUID.randomUUID().toString(),
fileExtension: String? = ".temp",
folder: File? = null
) = File.createTempFile(
fileName,
fileExtension,
folder
).apply {
outputStream().use {
copyTo(it.asOutput())
}
deleteOnExit()
}

View File

@@ -19,8 +19,7 @@ kotlin {
api libs.ktor.server
api libs.ktor.server.cio
api libs.ktor.server.host.common
api libs.ktor.server.websockets
api libs.ktor.server.statusPages
api libs.ktor.websockets
}
}
}

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.responseType
import io.ktor.util.InternalAPI
import io.ktor.util.reflect.TypeInfo
@InternalAPI
suspend fun <T : Any> ApplicationCall.respond(
message: T,
typeInfo: TypeInfo
) {
response.responseType = typeInfo
response.pipeline.execute(this, message as Any)
}

View File

@@ -1,56 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import com.benasher44.uuid.uuid4
import io.ktor.http.content.PartData
import io.ktor.utils.io.copyTo
import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.jvm.javaio.copyTo
import io.ktor.utils.io.streams.asOutput
import java.io.File
fun PartData.FileItem.download(target: File) {
provider().use { input ->
target.outputStream().use {
input.copyTo(it.asOutput())
}
}
}
fun PartData.FileItem.downloadToTemporalFile(): File {
val outputFile = File.createTempFile(uuid4().toString(), ".temp").apply {
deleteOnExit()
}
download(outputFile)
return outputFile
}
fun PartData.BinaryItem.download(target: File) {
provider().use { input ->
target.outputStream().use {
input.copyTo(it.asOutput())
}
}
}
fun PartData.BinaryItem.downloadToTemporalFile(): File {
val outputFile = File.createTempFile(uuid4().toString(), ".temp").apply {
deleteOnExit()
}
download(outputFile)
return outputFile
}
suspend fun PartData.BinaryChannelItem.download(target: File) {
val input = provider()
target.outputStream().use {
input.copyTo(it)
}
}
suspend fun PartData.BinaryChannelItem.downloadToTemporalFile(): File {
val outputFile = File.createTempFile(uuid4().toString(), ".temp").apply {
deleteOnExit()
}
download(outputFile)
return outputFile
}

View File

@@ -0,0 +1,59 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.featureOrNull
import io.ktor.application.install
import io.ktor.http.URLProtocol
import io.ktor.http.cio.websocket.*
import io.ktor.routing.Route
import io.ktor.routing.application
import io.ktor.websocket.*
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
protocol: URLProtocol = URLProtocol.WS,
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
) {
application.apply {
featureOrNull(io.ktor.websocket.WebSockets) ?: install(io.ktor.websocket.WebSockets)
}
webSocket(suburl, protocol.name) {
safely {
flow.collect {
converter(it) ?.let { data ->
send(data)
}
}
}
}
}
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>,
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(
suburl,
flow,
protocol,
converter = if (filter == null) {
{
serialFormat.encodeDefault(serializer, it)
}
} else {
{
if (filter(it)) {
serialFormat.encodeDefault(serializer, it)
} else {
null
}
}
}
)

View File

@@ -1,31 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.http.URLProtocol
import io.ktor.server.application.install
import io.ktor.server.application.pluginOrNull
import io.ktor.server.routing.Route
import io.ktor.server.routing.application
import io.ktor.server.websocket.*
import io.ktor.websocket.send
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.SerializationStrategy
inline fun <reified T : Any> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
protocol: URLProtocol? = null,
noinline dataMapper: suspend WebSocketServerSession.(T) -> T? = { it }
) {
application.apply {
pluginOrNull(WebSockets) ?: install(WebSockets)
}
webSocket(suburl, protocol ?.name) {
safely {
flow.collect {
sendSerialized(dataMapper(it) ?: return@collect)
}
}
}
}

View File

@@ -1,25 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond
suspend fun ApplicationCall.getParameterOrSendError(
field: String
) = parameters[field].also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun ApplicationCall.getQueryParameter(
field: String
) = request.queryParameters[field]
suspend fun ApplicationCall.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}

View File

@@ -1,15 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.response.respond
suspend inline fun <reified T : Any> ApplicationCall.respondOrNoContent(
data: T?
) {
if (data == null) {
respond(HttpStatusCode.NoContent)
} else {
respond(data)
}
}

View File

@@ -0,0 +1,293 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.*
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.*
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.request.receive
import io.ktor.request.receiveMultipart
import io.ktor.response.respond
import io.ktor.response.respondBytes
import io.ktor.routing.Route
import io.ktor.util.asStream
import io.ktor.util.cio.writeChannel
import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.*
import io.ktor.websocket.WebSocketServerSession
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.*
import java.io.File
import java.io.File.createTempFile
class UnifiedRouter(
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
) {
fun <T> Route.includeWebsocketHandling(
suburl: String,
flow: Flow<T>,
serializer: SerializationStrategy<T>,
protocol: URLProtocol = URLProtocol.WS,
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T
) {
call.respondBytes (
serialFormat.encodeDefault(answerSerializer, answer),
serialFormatContentType
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
deserializer: DeserializationStrategy<T>
) = safely {
serialFormat.decodeDefault(
deserializer,
call.receive()
)
}
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
field: String
) = call.parameters[field].also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
field: String
) = call.request.queryParameters[field]
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
field: String,
deserializer: DeserializationStrategy<T>
) = getQueryParameter(field) ?.let {
serialFormat.decodeHex(
deserializer,
it
)
}
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
field: String,
deserializer: DeserializationStrategy<T>
) = decodeUrlQueryValue(field, deserializer).also {
if (it == null) {
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
companion object {
val default
get() = defaultUnifiedRouter
}
}
val defaultUnifiedRouter = UnifiedRouter()
suspend fun <T> ApplicationCall.unianswer(
answerSerializer: SerializationStrategy<T>,
answer: T
) {
respondBytes (
standardKtorSerialFormat.encodeDefault(answerSerializer, answer),
standardKtorSerialFormatContentType
)
}
suspend fun <T> ApplicationCall.uniload(
deserializer: DeserializationStrategy<T>
) = safely {
standardKtorSerialFormat.decodeDefault(
deserializer,
receive()
)
}
suspend fun ApplicationCall.uniloadMultipart(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
) = safely {
val multipartData = receiveMultipart()
var resultInput: Input? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
when (it.name) {
"bytes" -> resultInput = it.provider()
else -> onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
}
}
resultInput ?: error("Bytes has not been received")
}
suspend fun <T> ApplicationCall.uniloadMultipart(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
): Pair<Input, T> {
var data: Optional<T>? = null
val resultInput = uniloadMultipart(
onFormItem,
{
if (it.name == "data") {
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
} else {
onCustomFileItem(it)
}
},
onBinaryContent
)
val completeData = data ?: error("Data has not been received")
return resultInput to (completeData.dataOrNull().let { it as T })
}
suspend fun <T> ApplicationCall.uniloadMultipartFile(
deserializer: DeserializationStrategy<T>,
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
var resultInput: MPPFile? = null
var data: Optional<T>? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
when (it.name) {
"bytes" -> {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile(
name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.provider().asStream().copyTo(fileStream)
}
}
}
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
else -> onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
}
}
val completeData = data ?: error("Data has not been received")
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
}
suspend fun ApplicationCall.uniloadMultipartFile(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
) = safely {
val multipartData = receiveMultipart()
var resultInput: MPPFile? = null
multipartData.forEachPart {
when (it) {
is PartData.FormItem -> onFormItem(it)
is PartData.FileItem -> {
if (it.name == "bytes") {
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
resultInput = MPPFile.createTempFile(
name.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${name.extension}"
).apply {
outputStream().use { fileStream ->
it.provider().asStream().copyTo(fileStream)
}
}
} else {
onCustomFileItem(it)
}
}
is PartData.BinaryItem -> onBinaryContent(it)
}
}
resultInput ?: error("Bytes has not been received")
}
suspend fun ApplicationCall.getParameterOrSendError(
field: String
) = parameters[field].also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request must contains $field")
}
}
fun ApplicationCall.getQueryParameter(
field: String
) = request.queryParameters[field]
suspend fun ApplicationCall.getQueryParameterOrSendError(
field: String
) = getQueryParameter(field).also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}
fun <T> ApplicationCall.decodeUrlQueryValue(
field: String,
deserializer: DeserializationStrategy<T>
) = getQueryParameter(field) ?.let {
standardKtorSerialFormat.decodeHex(
deserializer,
it
)
}
suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError(
field: String,
deserializer: DeserializationStrategy<T>
) = decodeUrlQueryValue(field, deserializer).also {
if (it == null) {
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
}
}

View File

@@ -1,7 +1,7 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.ktor.server.configurators.KtorApplicationConfigurator
import io.ktor.server.application.Application
import io.ktor.application.Application
import io.ktor.server.cio.CIO
import io.ktor.server.cio.CIOApplicationEngine
import io.ktor.server.engine.*

View File

@@ -7,15 +7,14 @@ import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.common.DefaultTemporalFilesSubPath
import dev.inmo.micro_utils.ktor.common.TemporalFileId
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.streamProvider
import io.ktor.server.application.call
import io.ktor.server.request.receiveMultipart
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import io.ktor.request.receiveMultipart
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.routing.post
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
@@ -26,6 +25,7 @@ import java.nio.file.attribute.FileTime
class TemporalFilesRoutingConfigurator(
private val subpath: String = DefaultTemporalFilesSubPath,
private val unifiedRouter: UnifiedRouter = UnifiedRouter.default,
private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer
) : ApplicationRoutingConfigurator.Element {
interface TemporalFilesUtilizer {
@@ -79,40 +79,42 @@ class TemporalFilesRoutingConfigurator(
override fun Route.invoke() {
post(subpath) {
val multipart = call.receiveMultipart()
unifiedRouter.apply {
val multipart = call.receiveMultipart()
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
var part = multipart.readPart()
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
var part = multipart.readPart()
while (part != null) {
if (part is PartData.FileItem) {
break
while (part != null) {
if (part is PartData.FileItem) {
break
}
part = multipart.readPart()
}
part = multipart.readPart()
}
part ?.let {
if (it is PartData.FileItem) {
val fileId = TemporalFileId(uuid4().toString())
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
outputStream().use { outputStream ->
it.streamProvider().use {
it.copyTo(outputStream)
part ?.let {
if (it is PartData.FileItem) {
val fileId = TemporalFileId(uuid4().toString())
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
outputStream().use { outputStream ->
it.streamProvider().use {
it.copyTo(outputStream)
}
}
deleteOnExit()
}
deleteOnExit()
}
}
}
fileInfo ?.also { (fileId, file) ->
temporalFilesMutex.withLock {
temporalFilesMap[fileId] = file
}
call.respondText(fileId.string)
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
} ?: call.respond(HttpStatusCode.BadRequest)
fileInfo ?.also { (fileId, file) ->
temporalFilesMutex.withLock {
temporalFilesMap[fileId] = file
}
call.respond(fileId.string)
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
} ?: call.respond(HttpStatusCode.BadRequest)
}
}
}

View File

@@ -1,70 +0,0 @@
package dev.inmo.micro_utils.ktor.server
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.micro_utils.ktor.common.downloadToTempFile
import io.ktor.http.content.*
import io.ktor.server.application.ApplicationCall
import io.ktor.server.request.receiveMultipart
import io.ktor.utils.io.core.*
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
/**
* Server-side part which receives [dev.inmo.micro_utils.ktor.client.uniUpload] request
*/
suspend inline fun ApplicationCall.handleUniUpload(
onFormItem: (PartData.FormItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onFileItem: (PartData.FileItem) -> Unit = {}
) {
val multipartData = receiveMultipart()
while (currentCoroutineContext().isActive) {
val partData = multipartData.readPart() ?: break
when (partData) {
is PartData.FormItem -> onFormItem(partData)
is PartData.FileItem -> onFileItem(partData)
is PartData.BinaryItem -> onBinaryContent(partData)
is PartData.BinaryChannelItem -> onBinaryChannelItem(partData)
}
partData.dispose()
}
}
suspend fun ApplicationCall.uniloadMultipart(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
): Input = safely {
var resultInput: Input? = null
handleUniUpload(
onFormItem,
onBinaryContent,
onBinaryChannelItem
) {
when (it.name) {
"bytes" -> resultInput = it.provider()
else -> onCustomFileItem(it)
}
}
resultInput ?: error("Bytes has not been received")
}
suspend fun ApplicationCall.uniloadMultipartFile(
onFormItem: (PartData.FormItem) -> Unit = {},
onCustomFileItem: (PartData.FileItem) -> Unit = {},
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
): MPPFile = safely {
uniloadMultipart(
onFormItem,
onCustomFileItem,
onBinaryChannelItem,
onBinaryContent
).downloadToTempFile()
}

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.cachingheaders.CachingHeaders
import io.ktor.server.plugins.cachingheaders.CachingHeadersConfig
import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.features.CachingHeaders
import kotlinx.serialization.Contextual
data class ApplicationCachingHeadersConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun CachingHeadersConfig.invoke() }
fun interface Element { operator fun CachingHeaders.Configuration.invoke() }
override fun Application.configure() {
install(CachingHeaders) {

View File

@@ -1,9 +1,8 @@
package dev.inmo.micro_utils.ktor.server.configurators
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator.Element
import io.ktor.server.application.*
import io.ktor.server.routing.Route
import io.ktor.server.routing.Routing
import io.ktor.application.*
import io.ktor.routing.Route
import io.ktor.routing.Routing
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
@@ -19,7 +18,7 @@ class ApplicationRoutingConfigurator(
}
override fun Application.configure() {
pluginOrNull(Routing) ?.apply {
featureOrNull(Routing) ?.apply {
rootInstaller.apply { invoke() }
} ?: install(Routing) {
rootInstaller.apply { invoke() }

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.sessions.Sessions
import io.ktor.server.sessions.SessionsConfig
import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.sessions.Sessions
import kotlinx.serialization.Contextual
class ApplicationSessionsConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun SessionsConfig.invoke() }
fun interface Element { operator fun Sessions.Configuration.invoke() }
override fun Application.configure() {
install(Sessions) {

View File

@@ -1,6 +1,6 @@
package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application
import io.ktor.application.Application
interface KtorApplicationConfigurator {
fun Application.configure()

View File

@@ -1,15 +1,14 @@
package dev.inmo.micro_utils.ktor.server.configurators
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.plugins.statuspages.StatusPagesConfig
import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.features.StatusPages
import kotlinx.serialization.Contextual
class StatusPagesConfigurator(
private val elements: List<@Contextual Element>
) : KtorApplicationConfigurator {
fun interface Element { operator fun StatusPagesConfig.invoke() }
fun interface Element { operator fun StatusPages.Configuration.invoke() }
override fun Application.configure() {
install(StatusPages) {

View File

@@ -1,6 +1,5 @@
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer
@@ -78,14 +77,12 @@ private fun printLanguageCodeAndTags(
indents: String = " "
): String = if (tag.subtags.isEmpty()) {
"""${indents}${baseClassSerializerAnnotationName}
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"; override val withoutDialect: String get() = ${parent ?.title ?.let { "$it.code" } ?: "code"} }"""
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}" }"""
} else {
"""
${indents}${baseClassSerializerAnnotationName}
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
${indents} override val code: String = "${tag.tag}"
${indents} override val withoutDialect: String
${indents} get() = code
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
@@ -106,14 +103,11 @@ import kotlinx.serialization.Serializable
${baseClassSerializerAnnotationName}
sealed class $baseClassName {
abstract val code: String
abstract val withoutDialect: String
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
$baseClassSerializerAnnotationName
data class $unknownBaseClassName (override val code: String) : $baseClassName() {
override val withoutDialect: String = code.takeWhile { it != '-' }
}
data class $unknownBaseClassName (override val code: String) : $baseClassName()
override fun toString() = code
}
@@ -170,7 +164,7 @@ suspend fun main(vararg args: String) {
val ietfLanguageCodes = json.decodeFromString(
ListSerializer(LanguageCode.serializer()),
client.get(ietfLanguageCodesLink).bodyAsText()
client.get(ietfLanguageCodesLink)
).map {
it.copy(
title = it.title
@@ -181,7 +175,7 @@ suspend fun main(vararg args: String) {
}
val ietfLanguageCodesWithTagsMap = json.decodeFromString(
ListSerializer(LanguageCodeWithTag.serializer()),
client.get(ietfLanguageCodesAdditionalTagsLink).bodyAsText()
client.get(ietfLanguageCodesAdditionalTagsLink)
).filter { it.withSubtag != it.tag }.groupBy { it.tag }
val tags = ietfLanguageCodes.map {

View File

@@ -17,11 +17,10 @@ def fix_name(category, raw_name):
result += out1
return result
# https://www.freeformatter.com/mime-types-list.html
if __name__ == '__main__':
df = pd.read_html(open('table.html', 'r'))
mimes = []
for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows():
for row in df[0].iterrows():
mime = row[1][1]
mime_category = mime.split('/', 1)[0]
mime_name = mime.split('/', 1)[1]
@@ -38,7 +37,7 @@ if __name__ == '__main__':
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
code2 += ' KnownMimeTypes.Any,\n'
for key, group in grouped:
group_name = fix_name(group, key)
group_name = key.capitalize()
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
for mime in group:

View File

@@ -1,3 +0,0 @@
package dev.inmo.micro_utils.mime_types
data class CustomMimeType(override val raw: String) : MimeType

View File

@@ -1,705 +0,0 @@
package dev.inmo.micro_utils.mime_types
internal val knownMimeTypes: Set<MimeType> = setOf(
KnownMimeTypes.Any,
KnownMimeTypes.Application.VndHzn3dCrossword,
KnownMimeTypes.Application.VndMseq,
KnownMimeTypes.Application.Vnd3mPostItNotes,
KnownMimeTypes.Application.Vnd3gppPicBwLarge,
KnownMimeTypes.Application.Vnd3gppPicBwSmall,
KnownMimeTypes.Application.Vnd3gppPicBwVar,
KnownMimeTypes.Application.Vnd3gpp2Tcap,
KnownMimeTypes.Application.X7zCompressed,
KnownMimeTypes.Application.XAbiword,
KnownMimeTypes.Application.XAceCompressed,
KnownMimeTypes.Application.VndAmericandynamicsAcc,
KnownMimeTypes.Application.VndAcucobol,
KnownMimeTypes.Application.VndAcucorp,
KnownMimeTypes.Application.XAuthorwareBin,
KnownMimeTypes.Application.XAuthorwareMap,
KnownMimeTypes.Application.XAuthorwareSeg,
KnownMimeTypes.Application.VndAdobeAirApplicationInstallerPackageZip,
KnownMimeTypes.Application.XShockwaveFlash,
KnownMimeTypes.Application.VndAdobeFxp,
KnownMimeTypes.Application.Pdf,
KnownMimeTypes.Application.VndCupsPpd,
KnownMimeTypes.Application.XDirector,
KnownMimeTypes.Application.VndAdobeXdpXml,
KnownMimeTypes.Application.VndAdobeXfdf,
KnownMimeTypes.Application.VndAheadSpace,
KnownMimeTypes.Application.VndAirzipFilesecureAzf,
KnownMimeTypes.Application.VndAirzipFilesecureAzs,
KnownMimeTypes.Application.VndAmazonEbook,
KnownMimeTypes.Application.VndAmigaAmi,
KnownMimeTypes.Application.AndrewInset,
KnownMimeTypes.Application.VndAndroidPackageArchive,
KnownMimeTypes.Application.VndAnserWebCertificateIssueInitiation,
KnownMimeTypes.Application.VndAnserWebFundsTransferInitiation,
KnownMimeTypes.Application.VndAntixGameComponent,
KnownMimeTypes.Application.XAppleDiskimage,
KnownMimeTypes.Application.VndAppleInstallerXml,
KnownMimeTypes.Application.Applixware,
KnownMimeTypes.Application.VndHheLessonPlayer,
KnownMimeTypes.Application.XFreearc,
KnownMimeTypes.Application.VndAristanetworksSwi,
KnownMimeTypes.Application.AtomcatXml,
KnownMimeTypes.Application.AtomsvcXml,
KnownMimeTypes.Application.AtomXml,
KnownMimeTypes.Application.PkixAttrCert,
KnownMimeTypes.Application.VndAudiograph,
KnownMimeTypes.Application.XBcpio,
KnownMimeTypes.Application.OctetStream,
KnownMimeTypes.Application.XBittorrent,
KnownMimeTypes.Application.VndRimCod,
KnownMimeTypes.Application.VndBlueiceMultipass,
KnownMimeTypes.Application.VndBmi,
KnownMimeTypes.Application.XSh,
KnownMimeTypes.Application.VndBusinessobjects,
KnownMimeTypes.Application.XBzip,
KnownMimeTypes.Application.XBzip2,
KnownMimeTypes.Application.XCsh,
KnownMimeTypes.Application.VndChemdrawXml,
KnownMimeTypes.Application.XCdf,
KnownMimeTypes.Application.VndContactCmsg,
KnownMimeTypes.Application.VndClaymore,
KnownMimeTypes.Application.VndClonkC4group,
KnownMimeTypes.Application.CdmiCapability,
KnownMimeTypes.Application.CdmiContainer,
KnownMimeTypes.Application.CdmiDomain,
KnownMimeTypes.Application.CdmiObject,
KnownMimeTypes.Application.CdmiQueue,
KnownMimeTypes.Application.VndCluetrustCartomobileConfig,
KnownMimeTypes.Application.VndCluetrustCartomobileConfigPkg,
KnownMimeTypes.Application.MacCompactpro,
KnownMimeTypes.Application.VndWapWmlc,
KnownMimeTypes.Application.VndXara,
KnownMimeTypes.Application.VndCosmocaller,
KnownMimeTypes.Application.XCpio,
KnownMimeTypes.Application.VndCrickClicker,
KnownMimeTypes.Application.VndCrickClickerKeyboard,
KnownMimeTypes.Application.VndCrickClickerPalette,
KnownMimeTypes.Application.VndCrickClickerTemplate,
KnownMimeTypes.Application.VndCrickClickerWordbank,
KnownMimeTypes.Application.VndCriticaltoolsWbsXml,
KnownMimeTypes.Application.VndRigCryptonote,
KnownMimeTypes.Application.CuSeeme,
KnownMimeTypes.Application.PrsCww,
KnownMimeTypes.Application.VndCurlCar,
KnownMimeTypes.Application.VndCurlPcurl,
KnownMimeTypes.Application.VndYellowriverCustomMenu,
KnownMimeTypes.Application.DsscDer,
KnownMimeTypes.Application.DsscXml,
KnownMimeTypes.Application.XDebianPackage,
KnownMimeTypes.Application.XDvi,
KnownMimeTypes.Application.VndFdsnSeed,
KnownMimeTypes.Application.XDtbookXml,
KnownMimeTypes.Application.XDtbresourceXml,
KnownMimeTypes.Application.VndDvbAit,
KnownMimeTypes.Application.VndDvbService,
KnownMimeTypes.Application.XmlDtd,
KnownMimeTypes.Application.VndDolbyMlp,
KnownMimeTypes.Application.XDoom,
KnownMimeTypes.Application.VndDpgraph,
KnownMimeTypes.Application.VndDreamfactory,
KnownMimeTypes.Application.VndDynageo,
KnownMimeTypes.Application.Ecmascript,
KnownMimeTypes.Application.VndEcowinChart,
KnownMimeTypes.Application.Exi,
KnownMimeTypes.Application.VndProteusMagazine,
KnownMimeTypes.Application.EpubZip,
KnownMimeTypes.Application.VndEnliven,
KnownMimeTypes.Application.VndIsXpr,
KnownMimeTypes.Application.VndXfdl,
KnownMimeTypes.Application.EmmaXml,
KnownMimeTypes.Application.VndEzpixAlbum,
KnownMimeTypes.Application.VndEzpixPackage,
KnownMimeTypes.Application.VndDenovoFcselayoutLink,
KnownMimeTypes.Application.VndFluxtimeClip,
KnownMimeTypes.Application.VndFdf,
KnownMimeTypes.Application.VndMif,
KnownMimeTypes.Application.VndFramemaker,
KnownMimeTypes.Application.VndFscWeblaunch,
KnownMimeTypes.Application.VndFrogansFnc,
KnownMimeTypes.Application.VndFrogansLtf,
KnownMimeTypes.Application.VndFujixeroxDdd,
KnownMimeTypes.Application.VndFujixeroxDocuworks,
KnownMimeTypes.Application.VndFujixeroxDocuworksBinder,
KnownMimeTypes.Application.VndFujitsuOasys,
KnownMimeTypes.Application.VndFujitsuOasys2,
KnownMimeTypes.Application.VndFujitsuOasys3,
KnownMimeTypes.Application.VndFujitsuOasysgp,
KnownMimeTypes.Application.VndFujitsuOasysprs,
KnownMimeTypes.Application.XFuturesplash,
KnownMimeTypes.Application.VndFuzzysheet,
KnownMimeTypes.Application.VndGmx,
KnownMimeTypes.Application.VndGenomatixTuxedo,
KnownMimeTypes.Application.VndGeogebraFile,
KnownMimeTypes.Application.VndGeogebraTool,
KnownMimeTypes.Application.VndGeometryExplorer,
KnownMimeTypes.Application.VndGeonext,
KnownMimeTypes.Application.VndGeoplan,
KnownMimeTypes.Application.VndGeospace,
KnownMimeTypes.Application.XFontGhostscript,
KnownMimeTypes.Application.XFontBdf,
KnownMimeTypes.Application.XGtar,
KnownMimeTypes.Application.XTexinfo,
KnownMimeTypes.Application.XGnumeric,
KnownMimeTypes.Application.VndGoogleEarthKmlXml,
KnownMimeTypes.Application.VndGoogleEarthKmz,
KnownMimeTypes.Application.GpxXml,
KnownMimeTypes.Application.VndGrafeq,
KnownMimeTypes.Application.VndGrooveAccount,
KnownMimeTypes.Application.VndGrooveHelp,
KnownMimeTypes.Application.VndGrooveIdentityMessage,
KnownMimeTypes.Application.VndGrooveInjector,
KnownMimeTypes.Application.VndGrooveToolMessage,
KnownMimeTypes.Application.VndGrooveToolTemplate,
KnownMimeTypes.Application.VndGrooveVcard,
KnownMimeTypes.Application.Gzip,
KnownMimeTypes.Application.VndHpHpid,
KnownMimeTypes.Application.VndHpHps,
KnownMimeTypes.Application.XHdf,
KnownMimeTypes.Application.VndHbci,
KnownMimeTypes.Application.VndHpJlyt,
KnownMimeTypes.Application.VndHpPcl,
KnownMimeTypes.Application.VndHpHpgl,
KnownMimeTypes.Application.VndYamahaHvScript,
KnownMimeTypes.Application.VndYamahaHvDic,
KnownMimeTypes.Application.VndYamahaHvVoice,
KnownMimeTypes.Application.VndHydrostatixSofData,
KnownMimeTypes.Application.Hyperstudio,
KnownMimeTypes.Application.VndHalXml,
KnownMimeTypes.Application.VndIbmRightsManagement,
KnownMimeTypes.Application.VndIbmSecureContainer,
KnownMimeTypes.Application.VndIccprofile,
KnownMimeTypes.Application.VndIgloader,
KnownMimeTypes.Application.VndImmervisionIvp,
KnownMimeTypes.Application.VndImmervisionIvu,
KnownMimeTypes.Application.ReginfoXml,
KnownMimeTypes.Application.VndIntergeo,
KnownMimeTypes.Application.VndCinderella,
KnownMimeTypes.Application.VndInterconFormnet,
KnownMimeTypes.Application.VndIsacFcs,
KnownMimeTypes.Application.Ipfix,
KnownMimeTypes.Application.PkixCert,
KnownMimeTypes.Application.Pkixcmp,
KnownMimeTypes.Application.PkixCrl,
KnownMimeTypes.Application.PkixPkipath,
KnownMimeTypes.Application.VndInsorsIgm,
KnownMimeTypes.Application.VndIpunpluggedRcprofile,
KnownMimeTypes.Application.VndIrepositoryPackageXml,
KnownMimeTypes.Application.JavaArchive,
KnownMimeTypes.Application.JavaVm,
KnownMimeTypes.Application.XJavaJnlpFile,
KnownMimeTypes.Application.JavaSerializedObject,
KnownMimeTypes.Application.Javascript,
KnownMimeTypes.Application.Json,
KnownMimeTypes.Application.VndJoostJodaArchive,
KnownMimeTypes.Application.LdJson,
KnownMimeTypes.Application.VndKahootz,
KnownMimeTypes.Application.VndChipnutsKaraokeMmd,
KnownMimeTypes.Application.VndKdeKarbon,
KnownMimeTypes.Application.VndKdeKchart,
KnownMimeTypes.Application.VndKdeKformula,
KnownMimeTypes.Application.VndKdeKivio,
KnownMimeTypes.Application.VndKdeKontour,
KnownMimeTypes.Application.VndKdeKpresenter,
KnownMimeTypes.Application.VndKdeKspread,
KnownMimeTypes.Application.VndKdeKword,
KnownMimeTypes.Application.VndKenameaapp,
KnownMimeTypes.Application.VndKidspiration,
KnownMimeTypes.Application.VndKinar,
KnownMimeTypes.Application.VndKodakDescriptor,
KnownMimeTypes.Application.VndLasLasXml,
KnownMimeTypes.Application.XLatex,
KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceDesktop,
KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceExchangeXml,
KnownMimeTypes.Application.VndJam,
KnownMimeTypes.Application.VndLotus123,
KnownMimeTypes.Application.VndLotusApproach,
KnownMimeTypes.Application.VndLotusFreelance,
KnownMimeTypes.Application.VndLotusNotes,
KnownMimeTypes.Application.VndLotusOrganizer,
KnownMimeTypes.Application.VndLotusScreencam,
KnownMimeTypes.Application.VndLotusWordpro,
KnownMimeTypes.Application.MacBinhex40,
KnownMimeTypes.Application.VndMacportsPortpkg,
KnownMimeTypes.Application.VndOsgeoMapguidePackage,
KnownMimeTypes.Application.Marc,
KnownMimeTypes.Application.MarcxmlXml,
KnownMimeTypes.Application.Mxf,
KnownMimeTypes.Application.VndWolframPlayer,
KnownMimeTypes.Application.Mathematica,
KnownMimeTypes.Application.MathmlXml,
KnownMimeTypes.Application.Mbox,
KnownMimeTypes.Application.VndMedcalcdata,
KnownMimeTypes.Application.MediaservercontrolXml,
KnownMimeTypes.Application.VndMediastationCdkey,
KnownMimeTypes.Application.VndMfer,
KnownMimeTypes.Application.VndMfmp,
KnownMimeTypes.Application.MadsXml,
KnownMimeTypes.Application.MetsXml,
KnownMimeTypes.Application.ModsXml,
KnownMimeTypes.Application.Metalink4Xml,
KnownMimeTypes.Application.VndMcd,
KnownMimeTypes.Application.VndMicrografxFlo,
KnownMimeTypes.Application.VndMicrografxIgx,
KnownMimeTypes.Application.VndEszigno3Xml,
KnownMimeTypes.Application.XMsaccess,
KnownMimeTypes.Application.XMsdownload,
KnownMimeTypes.Application.VndMsArtgalry,
KnownMimeTypes.Application.VndMsCabCompressed,
KnownMimeTypes.Application.VndMsIms,
KnownMimeTypes.Application.XMsApplication,
KnownMimeTypes.Application.XMsclip,
KnownMimeTypes.Application.VndMsFontobject,
KnownMimeTypes.Application.VndMsExcel,
KnownMimeTypes.Application.VndMsExcelAddinMacroenabled12,
KnownMimeTypes.Application.VndMsExcelSheetBinaryMacroenabled12,
KnownMimeTypes.Application.VndMsExcelTemplateMacroenabled12,
KnownMimeTypes.Application.VndMsExcelSheetMacroenabled12,
KnownMimeTypes.Application.VndMsHtmlhelp,
KnownMimeTypes.Application.XMscardfile,
KnownMimeTypes.Application.VndMsLrm,
KnownMimeTypes.Application.XMsmediaview,
KnownMimeTypes.Application.XMsmoney,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlPresentation,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlide,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlideshow,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlTemplate,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlSheet,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlTemplate,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlDocument,
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlTemplate,
KnownMimeTypes.Application.XMsbinder,
KnownMimeTypes.Application.VndMsOfficetheme,
KnownMimeTypes.Application.Onenote,
KnownMimeTypes.Application.VndMsPowerpoint,
KnownMimeTypes.Application.VndMsPowerpointAddinMacroenabled12,
KnownMimeTypes.Application.VndMsPowerpointSlideMacroenabled12,
KnownMimeTypes.Application.VndMsPowerpointPresentationMacroenabled12,
KnownMimeTypes.Application.VndMsPowerpointSlideshowMacroenabled12,
KnownMimeTypes.Application.VndMsPowerpointTemplateMacroenabled12,
KnownMimeTypes.Application.VndMsProject,
KnownMimeTypes.Application.XMspublisher,
KnownMimeTypes.Application.XMsschedule,
KnownMimeTypes.Application.XSilverlightApp,
KnownMimeTypes.Application.VndMsPkiStl,
KnownMimeTypes.Application.VndMsPkiSeccat,
KnownMimeTypes.Application.VndVisio,
KnownMimeTypes.Application.VndVisio2013,
KnownMimeTypes.Application.XMsWmd,
KnownMimeTypes.Application.VndMsWpl,
KnownMimeTypes.Application.XMsWmz,
KnownMimeTypes.Application.XMsmetafile,
KnownMimeTypes.Application.XMsterminal,
KnownMimeTypes.Application.Msword,
KnownMimeTypes.Application.VndMsWordDocumentMacroenabled12,
KnownMimeTypes.Application.VndMsWordTemplateMacroenabled12,
KnownMimeTypes.Application.XMswrite,
KnownMimeTypes.Application.VndMsWorks,
KnownMimeTypes.Application.XMsXbap,
KnownMimeTypes.Application.VndMsXpsdocument,
KnownMimeTypes.Application.VndIbmMinipay,
KnownMimeTypes.Application.VndIbmModcap,
KnownMimeTypes.Application.VndJcpJavameMidletRms,
KnownMimeTypes.Application.VndTmobileLivetv,
KnownMimeTypes.Application.XMobipocketEbook,
KnownMimeTypes.Application.VndMobiusMbk,
KnownMimeTypes.Application.VndMobiusDis,
KnownMimeTypes.Application.VndMobiusPlc,
KnownMimeTypes.Application.VndMobiusMqy,
KnownMimeTypes.Application.VndMobiusMsl,
KnownMimeTypes.Application.VndMobiusTxf,
KnownMimeTypes.Application.VndMobiusDaf,
KnownMimeTypes.Application.VndMophunCertificate,
KnownMimeTypes.Application.VndMophunApplication,
KnownMimeTypes.Application.Mp21,
KnownMimeTypes.Application.Mp4,
KnownMimeTypes.Application.VndAppleMpegurl,
KnownMimeTypes.Application.VndMusician,
KnownMimeTypes.Application.VndMuveeStyle,
KnownMimeTypes.Application.XvXml,
KnownMimeTypes.Application.VndNokiaNGageData,
KnownMimeTypes.Application.VndNokiaNGageSymbianInstall,
KnownMimeTypes.Application.XDtbncxXml,
KnownMimeTypes.Application.XNetcdf,
KnownMimeTypes.Application.VndNeurolanguageNlu,
KnownMimeTypes.Application.VndDna,
KnownMimeTypes.Application.VndNoblenetDirectory,
KnownMimeTypes.Application.VndNoblenetSealer,
KnownMimeTypes.Application.VndNoblenetWeb,
KnownMimeTypes.Application.VndNokiaRadioPreset,
KnownMimeTypes.Application.VndNokiaRadioPresets,
KnownMimeTypes.Application.VndNovadigmEdm,
KnownMimeTypes.Application.VndNovadigmEdx,
KnownMimeTypes.Application.VndNovadigmExt,
KnownMimeTypes.Application.VndFlographit,
KnownMimeTypes.Application.Oda,
KnownMimeTypes.Application.Ogg,
KnownMimeTypes.Application.VndOmaDd2Xml,
KnownMimeTypes.Application.VndOasisOpendocumentTextWeb,
KnownMimeTypes.Application.OebpsPackageXml,
KnownMimeTypes.Application.VndIntuQbo,
KnownMimeTypes.Application.VndOpenofficeorgExtension,
KnownMimeTypes.Application.VndYamahaOpenscoreformat,
KnownMimeTypes.Application.VndOasisOpendocumentChart,
KnownMimeTypes.Application.VndOasisOpendocumentChartTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentDatabase,
KnownMimeTypes.Application.VndOasisOpendocumentFormula,
KnownMimeTypes.Application.VndOasisOpendocumentFormulaTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentGraphics,
KnownMimeTypes.Application.VndOasisOpendocumentGraphicsTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentImage,
KnownMimeTypes.Application.VndOasisOpendocumentImageTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentPresentation,
KnownMimeTypes.Application.VndOasisOpendocumentPresentationTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheet,
KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheetTemplate,
KnownMimeTypes.Application.VndOasisOpendocumentText,
KnownMimeTypes.Application.VndOasisOpendocumentTextMaster,
KnownMimeTypes.Application.VndOasisOpendocumentTextTemplate,
KnownMimeTypes.Application.VndSunXmlCalc,
KnownMimeTypes.Application.VndSunXmlCalcTemplate,
KnownMimeTypes.Application.VndSunXmlDraw,
KnownMimeTypes.Application.VndSunXmlDrawTemplate,
KnownMimeTypes.Application.VndSunXmlImpress,
KnownMimeTypes.Application.VndSunXmlImpressTemplate,
KnownMimeTypes.Application.VndSunXmlMath,
KnownMimeTypes.Application.VndSunXmlWriter,
KnownMimeTypes.Application.VndSunXmlWriterGlobal,
KnownMimeTypes.Application.VndSunXmlWriterTemplate,
KnownMimeTypes.Application.XFontOtf,
KnownMimeTypes.Application.VndYamahaOpenscoreformatOsfpvgXml,
KnownMimeTypes.Application.VndOsgiDp,
KnownMimeTypes.Application.VndPalm,
KnownMimeTypes.Application.VndPawaafile,
KnownMimeTypes.Application.VndHpPclxl,
KnownMimeTypes.Application.VndPicsel,
KnownMimeTypes.Application.PicsRules,
KnownMimeTypes.Application.XChat,
KnownMimeTypes.Application.Pkcs10,
KnownMimeTypes.Application.XPkcs12,
KnownMimeTypes.Application.Pkcs7Mime,
KnownMimeTypes.Application.Pkcs7Signature,
KnownMimeTypes.Application.XPkcs7Certreqresp,
KnownMimeTypes.Application.XPkcs7Certificates,
KnownMimeTypes.Application.Pkcs8,
KnownMimeTypes.Application.VndPocketlearn,
KnownMimeTypes.Application.XFontPcf,
KnownMimeTypes.Application.FontTdpfr,
KnownMimeTypes.Application.XChessPgn,
KnownMimeTypes.Application.PskcXml,
KnownMimeTypes.Application.VndCtcPosml,
KnownMimeTypes.Application.Postscript,
KnownMimeTypes.Application.XFontType1,
KnownMimeTypes.Application.VndPowerbuilder6,
KnownMimeTypes.Application.PgpEncrypted,
KnownMimeTypes.Application.PgpSignature,
KnownMimeTypes.Application.VndPreviewsystemsBox,
KnownMimeTypes.Application.VndPviPtid1,
KnownMimeTypes.Application.PlsXml,
KnownMimeTypes.Application.VndPgFormat,
KnownMimeTypes.Application.VndPgOsasli,
KnownMimeTypes.Application.XFontLinuxPsf,
KnownMimeTypes.Application.VndPublishareDeltaTree,
KnownMimeTypes.Application.VndPmiWidget,
KnownMimeTypes.Application.VndQuarkQuarkxpress,
KnownMimeTypes.Application.VndEpsonEsf,
KnownMimeTypes.Application.VndEpsonMsf,
KnownMimeTypes.Application.VndEpsonSsf,
KnownMimeTypes.Application.VndEpsonQuickanime,
KnownMimeTypes.Application.VndIntuQfx,
KnownMimeTypes.Application.XRarCompressed,
KnownMimeTypes.Application.RsdXml,
KnownMimeTypes.Application.VndRnRealmedia,
KnownMimeTypes.Application.VndRealvncBed,
KnownMimeTypes.Application.VndRecordareMusicxml,
KnownMimeTypes.Application.VndRecordareMusicxmlXml,
KnownMimeTypes.Application.RelaxNgCompactSyntax,
KnownMimeTypes.Application.VndDataVisionRdz,
KnownMimeTypes.Application.RdfXml,
KnownMimeTypes.Application.VndCloantoRp9,
KnownMimeTypes.Application.VndJisp,
KnownMimeTypes.Application.Rtf,
KnownMimeTypes.Application.VndRoute66Link66Xml,
KnownMimeTypes.Application.RssXml,
KnownMimeTypes.Application.ShfXml,
KnownMimeTypes.Application.VndSailingtrackerTrack,
KnownMimeTypes.Application.VndSusCalendar,
KnownMimeTypes.Application.SruXml,
KnownMimeTypes.Application.SetPaymentInitiation,
KnownMimeTypes.Application.SetRegistrationInitiation,
KnownMimeTypes.Application.VndSema,
KnownMimeTypes.Application.VndSemd,
KnownMimeTypes.Application.VndSemf,
KnownMimeTypes.Application.VndSeemail,
KnownMimeTypes.Application.XFontSnf,
KnownMimeTypes.Application.ScvpVpRequest,
KnownMimeTypes.Application.ScvpVpResponse,
KnownMimeTypes.Application.ScvpCvRequest,
KnownMimeTypes.Application.ScvpCvResponse,
KnownMimeTypes.Application.Sdp,
KnownMimeTypes.Application.VndShanaInformedFormdata,
KnownMimeTypes.Application.VndShanaInformedFormtemplate,
KnownMimeTypes.Application.VndShanaInformedInterchange,
KnownMimeTypes.Application.VndShanaInformedPackage,
KnownMimeTypes.Application.ThraudXml,
KnownMimeTypes.Application.XShar,
KnownMimeTypes.Application.VndEpsonSalt,
KnownMimeTypes.Application.VndAccpacSimplyAso,
KnownMimeTypes.Application.VndAccpacSimplyImp,
KnownMimeTypes.Application.VndSimtechMindmapper,
KnownMimeTypes.Application.VndCommonspace,
KnownMimeTypes.Application.VndYamahaSmafAudio,
KnownMimeTypes.Application.VndSmaf,
KnownMimeTypes.Application.VndYamahaSmafPhrase,
KnownMimeTypes.Application.VndSmartTeacher,
KnownMimeTypes.Application.VndSvd,
KnownMimeTypes.Application.SparqlQuery,
KnownMimeTypes.Application.SparqlResultsXml,
KnownMimeTypes.Application.Srgs,
KnownMimeTypes.Application.SrgsXml,
KnownMimeTypes.Application.SsmlXml,
KnownMimeTypes.Application.VndKoan,
KnownMimeTypes.Application.VndStardivisionCalc,
KnownMimeTypes.Application.VndStardivisionDraw,
KnownMimeTypes.Application.VndStardivisionImpress,
KnownMimeTypes.Application.VndStardivisionMath,
KnownMimeTypes.Application.VndStardivisionWriter,
KnownMimeTypes.Application.VndStardivisionWriterGlobal,
KnownMimeTypes.Application.VndStepmaniaStepchart,
KnownMimeTypes.Application.XStuffit,
KnownMimeTypes.Application.XStuffitx,
KnownMimeTypes.Application.VndSolentSdkmXml,
KnownMimeTypes.Application.VndOlpcSugar,
KnownMimeTypes.Application.VndWqd,
KnownMimeTypes.Application.VndSymbianInstall,
KnownMimeTypes.Application.SmilXml,
KnownMimeTypes.Application.VndSyncmlXml,
KnownMimeTypes.Application.VndSyncmlDmWbxml,
KnownMimeTypes.Application.VndSyncmlDmXml,
KnownMimeTypes.Application.XSv4cpio,
KnownMimeTypes.Application.XSv4crc,
KnownMimeTypes.Application.SbmlXml,
KnownMimeTypes.Application.VndTaoIntentModuleArchive,
KnownMimeTypes.Application.XTar,
KnownMimeTypes.Application.XTcl,
KnownMimeTypes.Application.XTex,
KnownMimeTypes.Application.XTexTfm,
KnownMimeTypes.Application.TeiXml,
KnownMimeTypes.Application.VndSpotfireDxp,
KnownMimeTypes.Application.VndSpotfireSfs,
KnownMimeTypes.Application.TimestampedData,
KnownMimeTypes.Application.VndTridTpt,
KnownMimeTypes.Application.VndTriscapeMxs,
KnownMimeTypes.Application.VndTrueapp,
KnownMimeTypes.Application.XFontTtf,
KnownMimeTypes.Application.VndUmajin,
KnownMimeTypes.Application.VndUomlXml,
KnownMimeTypes.Application.VndUnity,
KnownMimeTypes.Application.VndUfdl,
KnownMimeTypes.Application.VndUiqTheme,
KnownMimeTypes.Application.XUstar,
KnownMimeTypes.Application.XCdlink,
KnownMimeTypes.Application.VndVsf,
KnownMimeTypes.Application.VndVcx,
KnownMimeTypes.Application.VndVisionary,
KnownMimeTypes.Application.CcxmlXml,
KnownMimeTypes.Application.VoicexmlXml,
KnownMimeTypes.Application.XWaisSource,
KnownMimeTypes.Application.VndWapWbxml,
KnownMimeTypes.Application.DavmountXml,
KnownMimeTypes.Application.XFontWoff,
KnownMimeTypes.Application.WspolicyXml,
KnownMimeTypes.Application.VndWebturbo,
KnownMimeTypes.Application.Widget,
KnownMimeTypes.Application.Winhlp,
KnownMimeTypes.Application.VndWapWmlscriptc,
KnownMimeTypes.Application.VndWordperfect,
KnownMimeTypes.Application.VndWtStf,
KnownMimeTypes.Application.WsdlXml,
KnownMimeTypes.Application.XX509CaCert,
KnownMimeTypes.Application.XXfig,
KnownMimeTypes.Application.XhtmlXml,
KnownMimeTypes.Application.Xml,
KnownMimeTypes.Application.XcapDiffXml,
KnownMimeTypes.Application.XencXml,
KnownMimeTypes.Application.PatchOpsErrorXml,
KnownMimeTypes.Application.ResourceListsXml,
KnownMimeTypes.Application.RlsServicesXml,
KnownMimeTypes.Application.ResourceListsDiffXml,
KnownMimeTypes.Application.XsltXml,
KnownMimeTypes.Application.XopXml,
KnownMimeTypes.Application.XXpinstall,
KnownMimeTypes.Application.XspfXml,
KnownMimeTypes.Application.VndMozillaXulXml,
KnownMimeTypes.Application.Yang,
KnownMimeTypes.Application.YinXml,
KnownMimeTypes.Application.VndZul,
KnownMimeTypes.Application.Zip,
KnownMimeTypes.Application.VndHandheldEntertainmentXml,
KnownMimeTypes.Application.VndZzazzDeckXml,
KnownMimeTypes.Audio.Adpcm,
KnownMimeTypes.Audio.XAac,
KnownMimeTypes.Audio.XAiff,
KnownMimeTypes.Audio.VndDeceAudio,
KnownMimeTypes.Audio.VndDigitalWinds,
KnownMimeTypes.Audio.VndDra,
KnownMimeTypes.Audio.VndDts,
KnownMimeTypes.Audio.VndDtsHd,
KnownMimeTypes.Audio.VndRip,
KnownMimeTypes.Audio.VndLucentVoice,
KnownMimeTypes.Audio.XMpegurl,
KnownMimeTypes.Audio.VndMsPlayreadyMediaPya,
KnownMimeTypes.Audio.XMsWma,
KnownMimeTypes.Audio.XMsWax,
KnownMimeTypes.Audio.Midi,
KnownMimeTypes.Audio.Mpeg,
KnownMimeTypes.Audio.Mp4,
KnownMimeTypes.Audio.VndNueraEcelp4800,
KnownMimeTypes.Audio.VndNueraEcelp7470,
KnownMimeTypes.Audio.VndNueraEcelp9600,
KnownMimeTypes.Audio.Ogg,
KnownMimeTypes.Audio.Webm,
KnownMimeTypes.Audio.Opus,
KnownMimeTypes.Audio.XPnRealaudio,
KnownMimeTypes.Audio.XPnRealaudioPlugin,
KnownMimeTypes.Audio.Basic,
KnownMimeTypes.Audio.XWav,
KnownMimeTypes.Chemical.XCdx,
KnownMimeTypes.Chemical.XCml,
KnownMimeTypes.Chemical.XCsml,
KnownMimeTypes.Chemical.XCif,
KnownMimeTypes.Chemical.XCmdf,
KnownMimeTypes.Chemical.XXyz,
KnownMimeTypes.Image.VndDxf,
KnownMimeTypes.Image.Avif,
KnownMimeTypes.Image.Bmp,
KnownMimeTypes.Image.PrsBtif,
KnownMimeTypes.Image.VndDvbSubtitle,
KnownMimeTypes.Image.XCmuRaster,
KnownMimeTypes.Image.Cgm,
KnownMimeTypes.Image.XCmx,
KnownMimeTypes.Image.VndDeceGraphic,
KnownMimeTypes.Image.VndDjvu,
KnownMimeTypes.Image.VndDwg,
KnownMimeTypes.Image.VndFujixeroxEdmicsMmr,
KnownMimeTypes.Image.VndFujixeroxEdmicsRlc,
KnownMimeTypes.Image.VndXiff,
KnownMimeTypes.Image.VndFst,
KnownMimeTypes.Image.VndFastbidsheet,
KnownMimeTypes.Image.VndFpx,
KnownMimeTypes.Image.VndNetFpx,
KnownMimeTypes.Image.XFreehand,
KnownMimeTypes.Image.G3fax,
KnownMimeTypes.Image.Gif,
KnownMimeTypes.Image.XIcon,
KnownMimeTypes.Image.Ief,
KnownMimeTypes.Image.Jpeg,
KnownMimeTypes.Image.XCitrixJpeg,
KnownMimeTypes.Image.Pjpeg,
KnownMimeTypes.Image.VndMsModi,
KnownMimeTypes.Image.Ktx,
KnownMimeTypes.Image.XPcx,
KnownMimeTypes.Image.VndAdobePhotoshop,
KnownMimeTypes.Image.XPict,
KnownMimeTypes.Image.XPortableAnymap,
KnownMimeTypes.Image.XPortableBitmap,
KnownMimeTypes.Image.XPortableGraymap,
KnownMimeTypes.Image.Png,
KnownMimeTypes.Image.XCitrixPng,
KnownMimeTypes.Image.XPng,
KnownMimeTypes.Image.XPortablePixmap,
KnownMimeTypes.Image.SvgXml,
KnownMimeTypes.Image.XRgb,
KnownMimeTypes.Image.Tiff,
KnownMimeTypes.Image.VndWapWbmp,
KnownMimeTypes.Image.Webp,
KnownMimeTypes.Image.XXbitmap,
KnownMimeTypes.Image.XXpixmap,
KnownMimeTypes.Image.XXwindowdump,
KnownMimeTypes.Message.Rfc822,
KnownMimeTypes.Model.VndDwf,
KnownMimeTypes.Model.VndColladaXml,
KnownMimeTypes.Model.VndGtw,
KnownMimeTypes.Model.VndGdl,
KnownMimeTypes.Model.Iges,
KnownMimeTypes.Model.Mesh,
KnownMimeTypes.Model.Vrml,
KnownMimeTypes.Model.VndMts,
KnownMimeTypes.Model.VndVtu,
KnownMimeTypes.Text.XAsm,
KnownMimeTypes.Text.PlainBas,
KnownMimeTypes.Text.XC,
KnownMimeTypes.Text.Css,
KnownMimeTypes.Text.Csv,
KnownMimeTypes.Text.VndCurl,
KnownMimeTypes.Text.VndCurlDcurl,
KnownMimeTypes.Text.VndCurlMcurl,
KnownMimeTypes.Text.VndCurlScurl,
KnownMimeTypes.Text.VndFmiFlexstor,
KnownMimeTypes.Text.XFortran,
KnownMimeTypes.Text.VndGraphviz,
KnownMimeTypes.Text.Html,
KnownMimeTypes.Text.Calendar,
KnownMimeTypes.Text.VndIn3d3dml,
KnownMimeTypes.Text.VndIn3dSpot,
KnownMimeTypes.Text.VndSunJ2meAppDescriptor,
KnownMimeTypes.Text.XJavaSourceJava,
KnownMimeTypes.Text.Javascript,
KnownMimeTypes.Text.VndFly,
KnownMimeTypes.Text.N3,
KnownMimeTypes.Text.XPascal,
KnownMimeTypes.Text.PrsLinesTag,
KnownMimeTypes.Text.Richtext,
KnownMimeTypes.Text.XSetext,
KnownMimeTypes.Text.Sgml,
KnownMimeTypes.Text.TabSeparatedValues,
KnownMimeTypes.Text.Plain,
KnownMimeTypes.Text.Troff,
KnownMimeTypes.Text.Turtle,
KnownMimeTypes.Text.UriList,
KnownMimeTypes.Text.XUuencode,
KnownMimeTypes.Text.XVcalendar,
KnownMimeTypes.Text.XVcard,
KnownMimeTypes.Text.VndWapWml,
KnownMimeTypes.Text.VndWapWmlscript,
KnownMimeTypes.Text.Yaml,
KnownMimeTypes.Video.V3gpp,
KnownMimeTypes.Video.V3gpp2,
KnownMimeTypes.Video.XMsvideo,
KnownMimeTypes.Video.VndDeceHd,
KnownMimeTypes.Video.VndDeceMobile,
KnownMimeTypes.Video.VndUvvuMp4,
KnownMimeTypes.Video.VndDecePd,
KnownMimeTypes.Video.VndDeceSd,
KnownMimeTypes.Video.VndDeceVideo,
KnownMimeTypes.Video.VndFvt,
KnownMimeTypes.Video.XF4v,
KnownMimeTypes.Video.XFlv,
KnownMimeTypes.Video.XFli,
KnownMimeTypes.Video.H261,
KnownMimeTypes.Video.H263,
KnownMimeTypes.Video.H264,
KnownMimeTypes.Video.Jpm,
KnownMimeTypes.Video.Jpeg,
KnownMimeTypes.Video.XM4v,
KnownMimeTypes.Video.XMsAsf,
KnownMimeTypes.Video.VndMsPlayreadyMediaPyv,
KnownMimeTypes.Video.XMsWm,
KnownMimeTypes.Video.XMsWmx,
KnownMimeTypes.Video.XMsWmv,
KnownMimeTypes.Video.XMsWvx,
KnownMimeTypes.Video.Mj2,
KnownMimeTypes.Video.Mp2t,
KnownMimeTypes.Video.VndMpegurl,
KnownMimeTypes.Video.Mpeg,
KnownMimeTypes.Video.Mp4,
KnownMimeTypes.Video.Ogg,
KnownMimeTypes.Video.Webm,
KnownMimeTypes.Video.Quicktime,
KnownMimeTypes.Video.XSgiMovie,
KnownMimeTypes.Video.VndVivo,
KnownMimeTypes.XConference.XCooltalk,
)

View File

@@ -1,8 +0,0 @@
package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.Serializable
@Serializable(MimeTypeSerializer::class)
interface MimeType {
val raw: String
}

View File

@@ -1,24 +0,0 @@
package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@Suppress("OPT_IN_USAGE")
@Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MimeType {
val mimeType = decoder.decodeString()
return mimeType(mimeType)
}
override fun serialize(encoder: Encoder, value: MimeType) {
encoder.encodeString(value.raw)
}
}

View File

@@ -1,5 +1,11 @@
package dev.inmo.micro_utils.mime_types
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
private val mimesCache = mutableMapOf<String, MimeType>().also {
knownMimeTypes.forEach { mimeType -> it[mimeType.raw] = mimeType }
}
@@ -10,3 +16,16 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) {
internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw)
@Serializer(MimeType::class)
object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MimeType {
val mimeType = decoder.decodeString()
return mimeType(mimeType)
}
override fun serialize(encoder: Encoder, value: MimeType) {
encoder.encodeString(value.raw)
}
}

Some files were not shown because too many files have changed in this diff Show More