mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-09-17 14:29:24 +00:00
Compare commits
118 Commits
Author | SHA1 | Date | |
---|---|---|---|
23590be5de | |||
94acc3c93b | |||
5616326a3b | |||
7601860c5c | |||
8b43d785cc | |||
b62d3a0b7d | |||
fad73c7213 | |||
2403c7c2b0 | |||
fa090bf920 | |||
a83ee86340 | |||
204955bcce | |||
ee56e9543a | |||
96fdff6ffd | |||
58b007cbb3 | |||
4f0c139889 | |||
c584c24fce | |||
85e5cee154 | |||
5af91981f1 | |||
2fe4f08059 | |||
83796f345a | |||
c1e21364a6 | |||
067d9d0d3b | |||
03f527d83e | |||
ced05a4586 | |||
43fe06206a | |||
023657558e | |||
9b0b726c80 | |||
4ee67321c4 | |||
59f1f2e59b | |||
0766d48b7c | |||
e18903b9e9 | |||
d0eecdead2 | |||
cc4a83a033 | |||
1cf911bbde | |||
36d73d5023 | |||
c395242e3e | |||
cd9cd7cc5d | |||
acbb8a0c07 | |||
b9d8528599 | |||
4971326eca | |||
09d1047260 | |||
02dbd493c2 | |||
b17931e7bd | |||
2a4570eafc | |||
c9514d3a6d | |||
072805efc7 | |||
369ff26627 | |||
c5abbbbd2d | |||
d974639f1e | |||
26efde316b | |||
fafe50f80a | |||
41504469db | |||
03b3ddd98b | |||
89d919f2be | |||
b53cfd5504 | |||
31022733ac | |||
f9a8c39879 | |||
a812c2dd2f | |||
217e977f0d | |||
04c301d1ac | |||
7f0c425389 | |||
1ede1c423b | |||
7cb064896a | |||
0c5e2862ca | |||
30d4507f54 | |||
b6c6d89455 | |||
9d218ee534 | |||
11116d8cab | |||
58d754bbde | |||
8f25c123dd | |||
76e214fc08 | |||
2b5380f8d6 | |||
844a129094 | |||
a3090cca9d | |||
b7b5159e9c | |||
0f8bc2c950 | |||
69f5c49f45 | |||
9b308e6fb8 | |||
3e3f91128b | |||
0d1aae0ef7 | |||
4d022f0480 | |||
153e20d00e | |||
a9a8171dd6 | |||
bf5c3b59b2 | |||
607c432bdb | |||
ae5c010770 | |||
d5e432437f | |||
9f99ebce01 | |||
64ee899b84 | |||
e0e0c1658b | |||
2c586f667c | |||
64164ef6c1 | |||
22343c0731 | |||
f4ec1a4c60 | |||
c1c33cceb1 | |||
a3e975b2ba | |||
06e705a687 | |||
d56eb6c867 | |||
9cbca864e3 | |||
abb4378694 | |||
0eb698d9a4 | |||
15ea9f2093 | |||
d47aca0923 | |||
1ac50e9959 | |||
6adfbe3a96 | |||
59f36e62e9 | |||
54af116009 | |||
38fbec8e3b | |||
babbfc55e4 | |||
2511e18d69 | |||
29658c70a0 | |||
96311ee43d | |||
bd33b09052 | |||
8055003b47 | |||
1257492f85 | |||
1107b7f4ef | |||
a1a1171240 | |||
46c02e5df1 |
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@@ -9,9 +9,6 @@ jobs:
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Fix android 32.0.0 dx
|
||||
continue-on-error: true
|
||||
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
|
||||
- name: Rewrite version
|
||||
run: |
|
||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||
@@ -20,9 +17,8 @@ jobs:
|
||||
mv gradle.properties.tmp gradle.properties
|
||||
- name: Build
|
||||
run: ./gradlew build
|
||||
# - name: Publish
|
||||
# continue-on-error: true
|
||||
# run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
|
||||
# env:
|
||||
# GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||
# GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Publish
|
||||
continue-on-error: true
|
||||
run: ./gradlew publishAllPublicationsToGiteaRepository
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
|
3
.github/workflows/dokka_push.yml
vendored
3
.github/workflows/dokka_push.yml
vendored
@@ -11,9 +11,6 @@ jobs:
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Fix android 32.0.0 dx
|
||||
continue-on-error: true
|
||||
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
|
||||
- name: Build
|
||||
run: ./gradlew build && ./gradlew dokkaHtml
|
||||
- name: Publish KDocs
|
||||
|
@@ -1,8 +0,0 @@
|
||||
job("Build and run tests") {
|
||||
container(displayName = "Run gradle build", image = "openjdk:11") {
|
||||
kotlinScript { api ->
|
||||
// here can be your complex logic
|
||||
api.gradlew("build")
|
||||
}
|
||||
}
|
||||
}
|
136
CHANGELOG.md
136
CHANGELOG.md
@@ -1,5 +1,141 @@
|
||||
# Changelog
|
||||
|
||||
## 0.16.5
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `2.2.1` -> `2.2.2`
|
||||
|
||||
## 0.16.4
|
||||
|
||||
* `Coroutines`:
|
||||
* Create `launchInCurrentThread`
|
||||
|
||||
## 0.16.3
|
||||
|
||||
* `Startup`:
|
||||
* `Launcher`:
|
||||
* All starting API have been moved into `StartLauncherPlugin` and do not require serialize/deserialize cycle for now
|
||||
|
||||
## 0.16.2
|
||||
|
||||
* `Versions`:
|
||||
* `Compose`: `1.2.1` -> `1.2.2`
|
||||
* `Startup`:
|
||||
* Module become available on `JS` target
|
||||
|
||||
## 0.16.1
|
||||
|
||||
* `Coroutines`:
|
||||
* New `runCatchingSafely`/`safelyWithResult` with receivers
|
||||
* `SafeWrapper`:
|
||||
* Module inited
|
||||
|
||||
## 0.16.0
|
||||
|
||||
* `Versions`:
|
||||
* `Ktor`: `2.1.3` -> `2.2.1`
|
||||
* `Android Fragment`: `1.5.3` -> `1.5.5`
|
||||
|
||||
## 0.15.1
|
||||
|
||||
* `Startup`:
|
||||
* Inited :)
|
||||
* `Plugin`:
|
||||
* Inited :)
|
||||
* `Launcher`:
|
||||
* Inited :)
|
||||
|
||||
## 0.15.0
|
||||
|
||||
* `Repos`:
|
||||
* `CRUD`:
|
||||
* `Common`:
|
||||
* New method `ReadCRUDRepo#getIdsByPagination`
|
||||
* `Android`:
|
||||
* `AbstractAndroidCRUDRepo` got new abstract method `toId`
|
||||
* `Exposed`:
|
||||
* `CommonExposedRepo` new abstract property `asId`
|
||||
* `Ktor`:
|
||||
* `Client`:
|
||||
* `KtorReadCRUDRepoClient` now requires `paginationIdType`
|
||||
* `LanguageCodes`:
|
||||
* Updates and fixes in generation
|
||||
* `MimeTypes`:
|
||||
* Updates and fixes in generation
|
||||
|
||||
## 0.14.4
|
||||
|
||||
* `Common`:
|
||||
* `JVM`:
|
||||
* New extension `downloadToTempFile`
|
||||
* `Ktor`:
|
||||
* `Server`:
|
||||
* Small fix in `handleUniUpload`
|
||||
* `ApplicationCall#uniloadMultipartFile` now uses `uniloadMultipart`
|
||||
* `Common`:
|
||||
* New extension `downloadToTempFile`
|
||||
* `Client`:
|
||||
* New extensions on top of `uniUpload`
|
||||
|
||||
## 0.14.3
|
||||
|
||||
* `Common`:
|
||||
* New type `Progress`
|
||||
* `Ktor`:
|
||||
* `Client`:
|
||||
* New universal `uniUpload` extension for `HttpClient`
|
||||
* `Server`:
|
||||
* New universal `handleUniUpload` extension for `ApplicationCall`
|
||||
* Add extensions `download` and `downloadToTemporalFile`
|
||||
|
||||
## 0.14.2
|
||||
|
||||
* `Versions`:
|
||||
* `Exposed`: `0.40.1` -> `0.41.1`
|
||||
|
||||
|
||||
## 0.14.1
|
||||
|
||||
* `Versions`:
|
||||
* `Klock`: `3.3.1` -> `3.4.0`
|
||||
* `UUID`: `0.5.0` -> `0.6.0`
|
||||
|
||||
## 0.14.0
|
||||
|
||||
**ALL DEPRECATIONS HAVE BEEN REMOVED**
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.7.10` -> `1.7.20`
|
||||
* `Klock`: `3.3.0` -> `3.3.1`
|
||||
* `Compose`: `1.2.0` -> `1.2.1`
|
||||
* `Exposed`: `0.39.2` -> `0.40.1`
|
||||
|
||||
## 0.13.2
|
||||
|
||||
* `Versions`:
|
||||
* `Klock`: `3.1.0` -> `3.3.0`
|
||||
* `Ktor`: `2.1.2` -> `2.1.3`
|
||||
|
||||
## 0.13.1
|
||||
|
||||
* `Repos`:
|
||||
* `Exposed`:
|
||||
* `AbstractExposedWriteCRUDRepo#createAndInsertId` now is optional and returns nullable value
|
||||
|
||||
## 0.13.0
|
||||
|
||||
**ALL DEPRECATIONS HAVE BEEN REMOVED**
|
||||
**A LOT OF KTOR METHODS RELATED TO UnifierRouter/UnifiedRequester HAVE BEEN REMOVED**
|
||||
|
||||
* `Repos`:
|
||||
* `Exposed`:
|
||||
* `AbstractExposedWriteCRUDRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `createAndInsertId`
|
||||
* Old `update` method has been deprecated and not recommended to override anymore in realizations
|
||||
* Old `insert` method now is `open` instead of `abstract` and can be omitted
|
||||
* `AbstractExposedKeyValueRepo` got two new methods: `update` with `it` as `UpdateBuilder<Int>` and `insertKey`
|
||||
* Old `update` method has been deprecated and not recommended to override anymore
|
||||
* Old `insert` method now is `open` instead of `abstract` and can be omitted in realizations
|
||||
|
||||
## 0.12.17
|
||||
|
||||
* `Versions`:
|
||||
|
@@ -0,0 +1,37 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class Progress private constructor(
|
||||
val of1: Double
|
||||
) {
|
||||
val of1Float
|
||||
get() = of1.toFloat()
|
||||
val of100
|
||||
get() = of1 * 100
|
||||
val of100Float
|
||||
get() = of100.toFloat()
|
||||
val of100Int
|
||||
get() = of100.toInt()
|
||||
|
||||
init {
|
||||
require(of1 in rangeOfValues) {
|
||||
"Progress main value should be in $rangeOfValues, but incoming value is $of1"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val rangeOfValues = 0.0 .. 1.0
|
||||
|
||||
val START = Progress(rangeOfValues.start)
|
||||
val COMPLETED = Progress(rangeOfValues.endInclusive)
|
||||
|
||||
operator fun invoke(of1: Double) = Progress(of1.coerceIn(rangeOfValues))
|
||||
operator fun invoke(part: Number, total: Number) = Progress(
|
||||
part.toDouble() / total.toDouble()
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
@file:Suppress(
|
||||
"RemoveRedundantCallsOfConversionMethods",
|
||||
"RedundantVisibilityModifier",
|
||||
)
|
||||
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlin.Byte
|
||||
import kotlin.Double
|
||||
import kotlin.Float
|
||||
import kotlin.Int
|
||||
import kotlin.Long
|
||||
import kotlin.Short
|
||||
import kotlin.Suppress
|
||||
|
||||
public operator fun Progress.plus(other: Progress): Progress = Progress(of1 + other.of1)
|
||||
|
||||
public operator fun Progress.minus(other: Progress): Progress = Progress(of1 - other.of1)
|
||||
|
||||
public operator fun Progress.plus(i: Byte): Progress = Progress((of1 + i).toDouble())
|
||||
|
||||
public operator fun Progress.minus(i: Byte): Progress = Progress((of1 - i).toDouble())
|
||||
|
||||
public operator fun Progress.times(i: Byte): Progress = Progress((of1 * i).toDouble())
|
||||
|
||||
public operator fun Progress.div(i: Byte): Progress = Progress((of1 / i).toDouble())
|
||||
|
||||
public operator fun Progress.rem(i: Byte): Progress = Progress((of1 % i).toDouble())
|
||||
|
||||
public operator fun Progress.plus(i: Short): Progress = Progress((of1 + i).toDouble())
|
||||
|
||||
public operator fun Progress.minus(i: Short): Progress = Progress((of1 - i).toDouble())
|
||||
|
||||
public operator fun Progress.times(i: Short): Progress = Progress((of1 * i).toDouble())
|
||||
|
||||
public operator fun Progress.div(i: Short): Progress = Progress((of1 / i).toDouble())
|
||||
|
||||
public operator fun Progress.rem(i: Short): Progress = Progress((of1 % i).toDouble())
|
||||
|
||||
public operator fun Progress.plus(i: Int): Progress = Progress((of1 + i).toDouble())
|
||||
|
||||
public operator fun Progress.minus(i: Int): Progress = Progress((of1 - i).toDouble())
|
||||
|
||||
public operator fun Progress.times(i: Int): Progress = Progress((of1 * i).toDouble())
|
||||
|
||||
public operator fun Progress.div(i: Int): Progress = Progress((of1 / i).toDouble())
|
||||
|
||||
public operator fun Progress.rem(i: Int): Progress = Progress((of1 % i).toDouble())
|
||||
|
||||
public operator fun Progress.plus(i: Long): Progress = Progress((of1 + i).toDouble())
|
||||
|
||||
public operator fun Progress.minus(i: Long): Progress = Progress((of1 - i).toDouble())
|
||||
|
||||
public operator fun Progress.times(i: Long): Progress = Progress((of1 * i).toDouble())
|
||||
|
||||
public operator fun Progress.div(i: Long): Progress = Progress((of1 / i).toDouble())
|
||||
|
||||
public operator fun Progress.rem(i: Long): Progress = Progress((of1 % i).toDouble())
|
||||
|
||||
public operator fun Progress.plus(i: Float): Progress = Progress((of1 + i).toDouble())
|
||||
|
||||
public operator fun Progress.minus(i: Float): Progress = Progress((of1 - i).toDouble())
|
||||
|
||||
public operator fun Progress.times(i: Float): Progress = Progress((of1 * i).toDouble())
|
||||
|
||||
public operator fun Progress.div(i: Float): Progress = Progress((of1 / i).toDouble())
|
||||
|
||||
public operator fun Progress.rem(i: Float): Progress = Progress((of1 % i).toDouble())
|
||||
|
||||
public operator fun Progress.plus(i: Double): Progress = Progress((of1 + i).toDouble())
|
||||
|
||||
public operator fun Progress.minus(i: Double): Progress = Progress((of1 - i).toDouble())
|
||||
|
||||
public operator fun Progress.times(i: Double): Progress = Progress((of1 * i).toDouble())
|
||||
|
||||
public operator fun Progress.div(i: Double): Progress = Progress((of1 / i).toDouble())
|
||||
|
||||
public operator fun Progress.rem(i: Double): Progress = Progress((of1 % i).toDouble())
|
||||
|
||||
public operator fun Progress.compareTo(other: Progress): Int = (of1 - other.of1).toInt()
|
@@ -0,0 +1,20 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.UUID
|
||||
|
||||
fun InputStream.downloadToTempFile(
|
||||
fileName: String = UUID.randomUUID().toString(),
|
||||
fileExtension: String? = ".temp",
|
||||
folder: File? = null
|
||||
) = File.createTempFile(
|
||||
fileName,
|
||||
fileExtension,
|
||||
folder
|
||||
).apply {
|
||||
outputStream().use {
|
||||
copyTo(it)
|
||||
}
|
||||
deleteOnExit()
|
||||
}
|
@@ -22,6 +22,7 @@ kotlin {
|
||||
dependencies {
|
||||
api libs.kt.coroutines.android
|
||||
}
|
||||
dependsOn(jvmMain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -115,10 +115,21 @@ suspend inline fun <T> runCatchingSafely(
|
||||
safely(onException, block)
|
||||
}
|
||||
|
||||
suspend inline fun <T, R> T.runCatchingSafely(
|
||||
noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
|
||||
noinline block: suspend T.() -> R
|
||||
): Result<R> = runCatching {
|
||||
safely(onException) { block() }
|
||||
}
|
||||
|
||||
suspend inline fun <T> safelyWithResult(
|
||||
noinline block: suspend CoroutineScope.() -> T
|
||||
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
||||
|
||||
suspend inline fun <T, R> T.safelyWithResult(
|
||||
noinline block: suspend T.() -> R
|
||||
): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block)
|
||||
|
||||
/**
|
||||
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
|
||||
* returning null at one time
|
||||
|
@@ -0,0 +1,9 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
fun <T> launchInCurrentThread(block: suspend CoroutineScope.() -> T): T {
|
||||
val scope = CoroutineScope(Dispatchers.Unconfined)
|
||||
return scope.launchSynchronously(block)
|
||||
}
|
@@ -6,7 +6,7 @@ fun <T> CoroutineScope.launchSynchronously(block: suspend CoroutineScope.() -> T
|
||||
var result: Result<T>? = null
|
||||
val objectToSynchronize = Object()
|
||||
synchronized(objectToSynchronize) {
|
||||
launch {
|
||||
launch(start = CoroutineStart.UNDISPATCHED) {
|
||||
result = safelyWithResult(block)
|
||||
}.invokeOnCompletion {
|
||||
synchronized(objectToSynchronize) {
|
||||
|
@@ -0,0 +1,47 @@
|
||||
package dev.inmo.micro_utils.coroutines
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class LaunchInCurrentThreadTests {
|
||||
@Test
|
||||
fun simpleTestThatLaunchInCurrentThreadWorks() {
|
||||
val expectedResult = 10
|
||||
val result = launchInCurrentThread {
|
||||
expectedResult
|
||||
}
|
||||
assertEquals(expectedResult, result)
|
||||
}
|
||||
@Test
|
||||
fun simpleTestThatSeveralLaunchInCurrentThreadWorks() {
|
||||
val testData = 0 until 100
|
||||
|
||||
testData.forEach {
|
||||
val result = launchInCurrentThread {
|
||||
it
|
||||
}
|
||||
assertEquals(it, result)
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun simpleTestThatLaunchInCurrentThreadWillCorrectlyHandleSuspensionsWorks() {
|
||||
val testData = 0 until 100
|
||||
|
||||
suspend fun test(data: Any): Any {
|
||||
return withContext(Dispatchers.Default) {
|
||||
delay(1)
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
testData.forEach {
|
||||
val result = launchInCurrentThread {
|
||||
test(it)
|
||||
}
|
||||
assertEquals(it, result)
|
||||
}
|
||||
}
|
||||
}
|
@@ -121,11 +121,11 @@ tasks.dokkaHtml {
|
||||
// }
|
||||
|
||||
named("jvmMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
|
||||
sourceRoots.setFrom(findSourcesWithName("jvmMain"))
|
||||
}
|
||||
|
||||
named("androidMain") {
|
||||
sourceRoots.setFrom(findSourcesWithName("androidMain", "commonMain"))
|
||||
sourceRoots.setFrom(findSourcesWithName("androidMain"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ allprojects {
|
||||
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
|
||||
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
|
||||
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
|
||||
mppJsAndJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsAndJavaProject.gradle"
|
||||
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
|
||||
|
||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.12.17
|
||||
android_code_version=156
|
||||
version=0.16.5
|
||||
android_code_version=173
|
||||
|
@@ -1,29 +1,31 @@
|
||||
[versions]
|
||||
|
||||
kt = "1.7.10"
|
||||
kt-serialization = "1.4.0"
|
||||
kt = "1.7.20"
|
||||
kt-serialization = "1.4.1"
|
||||
kt-coroutines = "1.6.4"
|
||||
|
||||
jb-compose = "1.2.0-beta02"
|
||||
jb-exposed = "0.39.2"
|
||||
jb-dokka = "1.7.10"
|
||||
kslog = "0.5.4"
|
||||
|
||||
klock = "3.1.0"
|
||||
uuid = "0.5.0"
|
||||
jb-compose = "1.2.2"
|
||||
jb-exposed = "0.41.1"
|
||||
jb-dokka = "1.7.20"
|
||||
|
||||
ktor = "2.1.2"
|
||||
klock = "3.4.0"
|
||||
uuid = "0.6.0"
|
||||
|
||||
ktor = "2.2.2"
|
||||
|
||||
gh-release = "2.4.1"
|
||||
|
||||
koin = "3.2.2"
|
||||
|
||||
android-gradle = "7.2.2"
|
||||
android-gradle = "7.3.0"
|
||||
dexcount = "3.1.0"
|
||||
|
||||
android-coreKtx = "1.9.0"
|
||||
android-recyclerView = "1.2.1"
|
||||
android-appCompat = "1.5.1"
|
||||
android-fragment = "1.5.3"
|
||||
android-fragment = "1.5.5"
|
||||
android-espresso = "3.4.0"
|
||||
android-test = "1.1.3"
|
||||
|
||||
@@ -60,6 +62,7 @@ ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.re
|
||||
ktor-server-statusPages = { module = "io.ktor:ktor-server-status-pages", version.ref = "ktor" }
|
||||
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
|
||||
|
||||
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
|
||||
|
||||
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
|
||||
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
|
||||
|
@@ -15,5 +15,9 @@ kotlin {
|
||||
api libs.ktor.client
|
||||
}
|
||||
}
|
||||
|
||||
androidMain {
|
||||
dependsOn jvmMain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,82 +0,0 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.pluginOrNull
|
||||
import io.ktor.client.plugins.websocket.WebSockets
|
||||
import io.ktor.client.plugins.websocket.ws
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.websocket.Frame
|
||||
import io.ktor.websocket.readBytes
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
|
||||
/**
|
||||
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||
*/
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
url: String,
|
||||
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
crossinline conversation: suspend (StandardKtorSerialInputData) -> T
|
||||
): Flow<T> {
|
||||
pluginOrNull(WebSockets) ?: error("Plugin $WebSockets must be installed for using createStandardWebsocketFlow")
|
||||
|
||||
val correctedUrl = url.asCorrectWebSocketUrl
|
||||
|
||||
return channelFlow {
|
||||
do {
|
||||
val reconnect = runCatchingSafely {
|
||||
ws(correctedUrl, requestBuilder) {
|
||||
for (received in incoming) {
|
||||
when (received) {
|
||||
is Frame.Binary -> send(conversation(received.data))
|
||||
else -> {
|
||||
close()
|
||||
return@ws
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkReconnection(null)
|
||||
}.getOrElse { e ->
|
||||
checkReconnection(e).also {
|
||||
if (!it) {
|
||||
close(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (reconnect && isActive)
|
||||
|
||||
if (isActive) {
|
||||
safely {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checkReconnection This lambda will be called when it is required to reconnect to websocket to establish
|
||||
* connection. Must return true in case if must be reconnected. By default always reconnecting
|
||||
*/
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
inline fun <T> HttpClient.createStandardWebsocketFlow(
|
||||
url: String,
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
crossinline checkReconnection: suspend (Throwable?) -> Boolean = { true },
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
noinline requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
) = createStandardWebsocketFlow(
|
||||
url,
|
||||
checkReconnection,
|
||||
requestBuilder
|
||||
) {
|
||||
serialFormat.decodeDefault(deserializer, it)
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit
|
@@ -1,260 +0,0 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.filename
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.client.statement.readBytes
|
||||
import io.ktor.http.*
|
||||
import io.ktor.utils.io.core.ByteReadPacket
|
||||
import kotlinx.serialization.*
|
||||
|
||||
@Deprecated("This class will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
class UnifiedRequester(
|
||||
val client: HttpClient = HttpClient(),
|
||||
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) {
|
||||
suspend fun <ResultType> uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
): ResultType = client.uniget(url, resultDeserializer, serialFormat)
|
||||
|
||||
fun <T> encodeUrlQueryValue(
|
||||
serializationStrategy: SerializationStrategy<T>,
|
||||
value: T
|
||||
) = serializationStrategy.encodeUrlQueryValue(
|
||||
value,
|
||||
serialFormat
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> unipost(
|
||||
url: String,
|
||||
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>
|
||||
) = client.unipost(url, bodyInfo, resultDeserializer, serialFormat)
|
||||
|
||||
suspend fun <ResultType> unimultipart(
|
||||
url: String,
|
||||
filename: String,
|
||||
inputProvider: InputProvider,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
): ResultType = client.unimultipart(url, filename, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
||||
|
||||
suspend fun <BodyType, ResultType> unimultipart(
|
||||
url: String,
|
||||
filename: String,
|
||||
inputProvider: InputProvider,
|
||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
): ResultType = client.unimultipart(url, filename, otherData, inputProvider, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat)
|
||||
|
||||
suspend fun <ResultType> unimultipart(
|
||||
url: String,
|
||||
mppFile: MPPFile,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||
): ResultType = client.unimultipart(
|
||||
url, mppFile, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
||||
)
|
||||
|
||||
suspend fun <BodyType, ResultType> unimultipart(
|
||||
url: String,
|
||||
mppFile: MPPFile,
|
||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {}
|
||||
): ResultType = client.unimultipart(
|
||||
url, mppFile, otherData, resultDeserializer, mimetype, additionalParametersBuilder, dataHeadersBuilder, requestBuilder, serialFormat
|
||||
)
|
||||
|
||||
fun <T> createStandardWebsocketFlow(
|
||||
url: String,
|
||||
checkReconnection: suspend (Throwable?) -> Boolean,
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
) = client.createStandardWebsocketFlow(url, deserializer, checkReconnection, serialFormat, requestBuilder)
|
||||
|
||||
fun <T> createStandardWebsocketFlow(
|
||||
url: String,
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
) = createStandardWebsocketFlow(url, { true }, deserializer, requestBuilder)
|
||||
}
|
||||
|
||||
@Deprecated("This property will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
val defaultRequester = UnifiedRequester()
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <ResultType> HttpClient.uniget(
|
||||
url: String,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = get(url).let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
fun <T> SerializationStrategy<T>.encodeUrlQueryValue(
|
||||
value: T,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = serialFormat.encodeHex(
|
||||
this,
|
||||
value
|
||||
)
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <BodyType, ResultType> HttpClient.unipost(
|
||||
url: String,
|
||||
bodyInfo: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
) = post(url) {
|
||||
setBody(
|
||||
serialFormat.encodeDefault(bodyInfo.first, bodyInfo.second)
|
||||
)
|
||||
}.let {
|
||||
serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>())
|
||||
}
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <ResultType> HttpClient.unimultipart(
|
||||
url: String,
|
||||
filename: String,
|
||||
inputProvider: InputProvider,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
): ResultType = submitFormWithBinaryData(
|
||||
url,
|
||||
formData = formData {
|
||||
append(
|
||||
"bytes",
|
||||
inputProvider,
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, mimetype)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"$filename\"")
|
||||
dataHeadersBuilder()
|
||||
}
|
||||
)
|
||||
additionalParametersBuilder()
|
||||
}
|
||||
) {
|
||||
requestBuilder()
|
||||
}.let { serialFormat.decodeDefault(resultDeserializer, it.body<StandardKtorSerialInputData>()) }
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||
url: String,
|
||||
filename: String,
|
||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
inputProvider: InputProvider,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
): ResultType = unimultipart(
|
||||
url,
|
||||
filename,
|
||||
inputProvider,
|
||||
resultDeserializer,
|
||||
mimetype,
|
||||
additionalParametersBuilder = {
|
||||
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
||||
append(
|
||||
"data",
|
||||
InputProvider(serialized.size.toLong()) {
|
||||
ByteReadPacket(serialized)
|
||||
},
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
||||
dataHeadersBuilder()
|
||||
}
|
||||
)
|
||||
additionalParametersBuilder()
|
||||
},
|
||||
dataHeadersBuilder,
|
||||
requestBuilder,
|
||||
serialFormat
|
||||
)
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <ResultType> HttpClient.unimultipart(
|
||||
url: String,
|
||||
mppFile: MPPFile,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
): ResultType = unimultipart(
|
||||
url,
|
||||
mppFile.filename.string,
|
||||
mppFile.inputProvider(),
|
||||
resultDeserializer,
|
||||
mimetype,
|
||||
additionalParametersBuilder,
|
||||
dataHeadersBuilder,
|
||||
requestBuilder,
|
||||
serialFormat
|
||||
)
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <BodyType, ResultType> HttpClient.unimultipart(
|
||||
url: String,
|
||||
mppFile: MPPFile,
|
||||
otherData: Pair<SerializationStrategy<BodyType>, BodyType>,
|
||||
resultDeserializer: DeserializationStrategy<ResultType>,
|
||||
mimetype: String = "*/*",
|
||||
additionalParametersBuilder: FormBuilder.() -> Unit = {},
|
||||
dataHeadersBuilder: HeadersBuilder.() -> Unit = {},
|
||||
requestBuilder: HttpRequestBuilder.() -> Unit = {},
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat
|
||||
): ResultType = unimultipart(
|
||||
url,
|
||||
mppFile,
|
||||
resultDeserializer,
|
||||
mimetype,
|
||||
additionalParametersBuilder = {
|
||||
val serialized = serialFormat.encodeDefault(otherData.first, otherData.second)
|
||||
append(
|
||||
"data",
|
||||
InputProvider(serialized.size.toLong()) {
|
||||
ByteReadPacket(serialized)
|
||||
},
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, ContentType.Application.Cbor.contentType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=data.bytes")
|
||||
dataHeadersBuilder()
|
||||
}
|
||||
)
|
||||
additionalParametersBuilder()
|
||||
},
|
||||
dataHeadersBuilder,
|
||||
requestBuilder,
|
||||
serialFormat
|
||||
)
|
@@ -7,13 +7,5 @@ import io.ktor.client.HttpClient
|
||||
expect suspend fun HttpClient.tempUpload(
|
||||
fullTempUploadDraftPath: String,
|
||||
file: MPPFile,
|
||||
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
|
||||
onUpload: OnUploadCallback = { _, _ -> }
|
||||
): TemporalFileId
|
||||
|
||||
suspend fun UnifiedRequester.tempUpload(
|
||||
fullTempUploadDraftPath: String,
|
||||
file: MPPFile,
|
||||
onUpload: (uploaded: Long, count: Long) -> Unit = { _, _ -> }
|
||||
): TemporalFileId = client.tempUpload(
|
||||
fullTempUploadDraftPath, file, onUpload
|
||||
)
|
||||
|
@@ -0,0 +1,105 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.FileName
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.ktor.common.LambdaInputProvider
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.utils.io.core.Input
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.StringFormat
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
data class UniUploadFileInfo(
|
||||
val fileName: FileName,
|
||||
val mimeType: String,
|
||||
val inputAllocator: LambdaInputProvider
|
||||
)
|
||||
|
||||
/**
|
||||
* Will execute submitting of multipart data request
|
||||
*
|
||||
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
|
||||
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
|
||||
* in case you wish to pass other source of multipart binary data than regular file
|
||||
*
|
||||
* @see dev.inmo.micro_utils.ktor.server.handleUniUpload
|
||||
*/
|
||||
expect suspend fun <T> HttpClient.uniUpload(
|
||||
url: String,
|
||||
data: Map<String, Any>,
|
||||
resultDeserializer: DeserializationStrategy<T>,
|
||||
headers: Headers = Headers.Empty,
|
||||
stringFormat: StringFormat = Json,
|
||||
onUpload: OnUploadCallback = { _, _ -> }
|
||||
): T?
|
||||
|
||||
/**
|
||||
* Additional variant of [uniUpload] which will unify sending of some [MPPFile] with the server
|
||||
*
|
||||
* @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
|
||||
*/
|
||||
suspend fun <T> HttpClient.uniUpload(
|
||||
url: String,
|
||||
file: MPPFile,
|
||||
resultDeserializer: DeserializationStrategy<T>,
|
||||
additionalData: Map<String, Any> = emptyMap(),
|
||||
headers: Headers = Headers.Empty,
|
||||
stringFormat: StringFormat = Json,
|
||||
onUpload: OnUploadCallback = { _, _ -> }
|
||||
): T? = uniUpload(
|
||||
url,
|
||||
additionalData + ("bytes" to file),
|
||||
resultDeserializer,
|
||||
headers,
|
||||
stringFormat,
|
||||
onUpload
|
||||
)
|
||||
|
||||
/**
|
||||
* Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] with the server
|
||||
*
|
||||
* @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
|
||||
*/
|
||||
suspend fun <T> HttpClient.uniUpload(
|
||||
url: String,
|
||||
info: UniUploadFileInfo,
|
||||
resultDeserializer: DeserializationStrategy<T>,
|
||||
additionalData: Map<String, Any> = emptyMap(),
|
||||
headers: Headers = Headers.Empty,
|
||||
stringFormat: StringFormat = Json,
|
||||
onUpload: OnUploadCallback = { _, _ -> }
|
||||
): T? = uniUpload(
|
||||
url,
|
||||
additionalData + ("bytes" to info),
|
||||
resultDeserializer,
|
||||
headers,
|
||||
stringFormat,
|
||||
onUpload
|
||||
)
|
||||
|
||||
/**
|
||||
* Additional variant of [uniUpload] which will unify sending of some [UniUploadFileInfo] (built from [fileName],
|
||||
* [mimeType] and [inputAllocator]) with the server
|
||||
*
|
||||
* @see dev.inmo.micro_utils.ktor.server.uniloadMultipartFile
|
||||
*/
|
||||
suspend fun <T> HttpClient.uniUpload(
|
||||
url: String,
|
||||
fileName: FileName,
|
||||
mimeType: String,
|
||||
inputAllocator: LambdaInputProvider,
|
||||
resultDeserializer: DeserializationStrategy<T>,
|
||||
additionalData: Map<String, Any> = emptyMap(),
|
||||
headers: Headers = Headers.Empty,
|
||||
stringFormat: StringFormat = Json,
|
||||
onUpload: OnUploadCallback = { _, _ -> }
|
||||
): T? = uniUpload(
|
||||
url,
|
||||
UniUploadFileInfo(fileName, mimeType, inputAllocator),
|
||||
resultDeserializer,
|
||||
additionalData,
|
||||
headers,
|
||||
stringFormat,
|
||||
onUpload
|
||||
)
|
@@ -1,6 +1,8 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
|
||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.ktor.common.TemporalFileId
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.coroutines.*
|
||||
@@ -12,10 +14,11 @@ import org.w3c.xhr.XMLHttpRequest.Companion.DONE
|
||||
suspend fun tempUpload(
|
||||
fullTempUploadDraftPath: String,
|
||||
file: MPPFile,
|
||||
onUpload: (Long, Long) -> Unit
|
||||
onUpload: OnUploadCallback
|
||||
): TemporalFileId {
|
||||
val formData = FormData()
|
||||
val answer = CompletableDeferred<TemporalFileId>(currentCoroutineContext().job)
|
||||
val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
|
||||
|
||||
formData.append(
|
||||
"data",
|
||||
@@ -25,7 +28,7 @@ suspend fun tempUpload(
|
||||
val request = XMLHttpRequest()
|
||||
request.responseType = XMLHttpRequestResponseType.TEXT
|
||||
request.upload.onprogress = {
|
||||
onUpload(it.loaded.toLong(), it.total.toLong())
|
||||
subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
|
||||
}
|
||||
request.onload = {
|
||||
if (request.status == 200.toShort()) {
|
||||
@@ -48,12 +51,14 @@ suspend fun tempUpload(
|
||||
}
|
||||
}
|
||||
|
||||
return answer.await()
|
||||
return answer.await().also {
|
||||
subscope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
actual suspend fun HttpClient.tempUpload(
|
||||
fullTempUploadDraftPath: String,
|
||||
file: MPPFile,
|
||||
onUpload: (uploaded: Long, count: Long) -> Unit
|
||||
onUpload: OnUploadCallback
|
||||
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)
|
||||
|
@@ -0,0 +1,97 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.Progress
|
||||
import dev.inmo.micro_utils.coroutines.LinkedSupervisorJob
|
||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.utils.io.core.readBytes
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.StringFormat
|
||||
import kotlinx.serialization.encodeToString
|
||||
import org.khronos.webgl.Int8Array
|
||||
import org.w3c.files.Blob
|
||||
import org.w3c.xhr.FormData
|
||||
import org.w3c.xhr.TEXT
|
||||
import org.w3c.xhr.XMLHttpRequest
|
||||
import org.w3c.xhr.XMLHttpRequestResponseType
|
||||
|
||||
/**
|
||||
* Will execute submitting of multipart data request
|
||||
*
|
||||
* @param data [Map] where keys will be used as names for multipart parts and values as values. If you will pass
|
||||
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value
|
||||
* in case you wish to pass other source of multipart binary data than regular file
|
||||
* @suppress
|
||||
*/
|
||||
actual suspend fun <T> HttpClient.uniUpload(
|
||||
url: String,
|
||||
data: Map<String, Any>,
|
||||
resultDeserializer: DeserializationStrategy<T>,
|
||||
headers: Headers,
|
||||
stringFormat: StringFormat,
|
||||
onUpload: OnUploadCallback
|
||||
): T? {
|
||||
val formData = FormData()
|
||||
val answer = CompletableDeferred<T?>(currentCoroutineContext().job)
|
||||
val subscope = CoroutineScope(currentCoroutineContext().LinkedSupervisorJob())
|
||||
|
||||
data.forEach { (k, v) ->
|
||||
when (v) {
|
||||
is MPPFile -> formData.append(
|
||||
k,
|
||||
v
|
||||
)
|
||||
is UniUploadFileInfo -> formData.append(
|
||||
k,
|
||||
Blob(arrayOf(Int8Array(v.inputAllocator().readBytes().toTypedArray()))),
|
||||
v.fileName.name
|
||||
)
|
||||
else -> formData.append(
|
||||
k,
|
||||
stringFormat.encodeToString(v)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val request = XMLHttpRequest()
|
||||
headers.forEach { s, strings ->
|
||||
request.setRequestHeader(s, strings.joinToString())
|
||||
}
|
||||
request.responseType = XMLHttpRequestResponseType.TEXT
|
||||
request.upload.onprogress = {
|
||||
subscope.launchSafelyWithoutExceptions { onUpload(it.loaded.toLong(), it.total.toLong()) }
|
||||
}
|
||||
request.onload = {
|
||||
if (request.status == 200.toShort()) {
|
||||
answer.complete(
|
||||
stringFormat.decodeFromString(resultDeserializer, request.responseText)
|
||||
)
|
||||
} else {
|
||||
answer.completeExceptionally(Exception("Something went wrong: $it"))
|
||||
}
|
||||
}
|
||||
request.onerror = {
|
||||
answer.completeExceptionally(Exception("Something went wrong: $it"))
|
||||
}
|
||||
request.open("POST", url, true)
|
||||
request.send(formData)
|
||||
|
||||
answer.invokeOnCompletion {
|
||||
runCatching {
|
||||
if (request.readyState != XMLHttpRequest.DONE) {
|
||||
request.abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return answer.await().also {
|
||||
subscope.cancel()
|
||||
}
|
||||
}
|
@@ -4,6 +4,8 @@ import dev.inmo.micro_utils.common.MPPFile
|
||||
import io.ktor.client.request.forms.InputProvider
|
||||
import io.ktor.utils.io.streams.asInput
|
||||
|
||||
actual suspend fun MPPFile.inputProvider(): InputProvider = InputProvider(length()) {
|
||||
fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
|
||||
inputStream().asInput()
|
||||
}
|
||||
|
||||
actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync()
|
||||
|
@@ -18,7 +18,7 @@ internal val MPPFile.mimeType: String
|
||||
actual suspend fun HttpClient.tempUpload(
|
||||
fullTempUploadDraftPath: String,
|
||||
file: MPPFile,
|
||||
onUpload: (Long, Long) -> Unit
|
||||
onUpload: OnUploadCallback
|
||||
): TemporalFileId {
|
||||
val inputProvider = file.inputProvider()
|
||||
val fileId = submitFormWithBinaryData(
|
||||
|
@@ -0,0 +1,102 @@
|
||||
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
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.micro_utils.ktor.common
|
||||
|
||||
import io.ktor.utils.io.core.Input
|
||||
|
||||
typealias LambdaInputProvider = () -> Input
|
@@ -0,0 +1,23 @@
|
||||
package dev.inmo.micro_utils.ktor.common
|
||||
|
||||
import io.ktor.utils.io.core.Input
|
||||
import io.ktor.utils.io.core.copyTo
|
||||
import io.ktor.utils.io.streams.asOutput
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.UUID
|
||||
|
||||
fun Input.downloadToTempFile(
|
||||
fileName: String = UUID.randomUUID().toString(),
|
||||
fileExtension: String? = ".temp",
|
||||
folder: File? = null
|
||||
) = File.createTempFile(
|
||||
fileName,
|
||||
fileExtension,
|
||||
folder
|
||||
).apply {
|
||||
outputStream().use {
|
||||
copyTo(it.asOutput())
|
||||
}
|
||||
deleteOnExit()
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
import com.benasher44.uuid.uuid4
|
||||
import io.ktor.http.content.PartData
|
||||
import io.ktor.utils.io.copyTo
|
||||
import io.ktor.utils.io.core.copyTo
|
||||
import io.ktor.utils.io.jvm.javaio.copyTo
|
||||
import io.ktor.utils.io.streams.asOutput
|
||||
import java.io.File
|
||||
|
||||
fun PartData.FileItem.download(target: File) {
|
||||
provider().use { input ->
|
||||
target.outputStream().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
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.http.URLProtocol
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.application.pluginOrNull
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.application
|
||||
import io.ktor.server.websocket.*
|
||||
import io.ktor.websocket.send
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
@Deprecated("This method will be removed soon")
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
protocol: URLProtocol? = null,
|
||||
converter: suspend WebSocketServerSession.(T) -> StandardKtorSerialInputData?
|
||||
) {
|
||||
application.apply {
|
||||
pluginOrNull(WebSockets) ?: install(WebSockets)
|
||||
}
|
||||
webSocket(suburl, protocol ?.name) {
|
||||
safely {
|
||||
flow.collect {
|
||||
converter(it) ?.let { data ->
|
||||
send(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This method will be removed soon")
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
protocol: URLProtocol? = null,
|
||||
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
||||
) = includeWebsocketHandling(
|
||||
suburl,
|
||||
flow,
|
||||
protocol,
|
||||
converter = if (filter == null) {
|
||||
{
|
||||
serialFormat.encodeDefault(serializer, it)
|
||||
}
|
||||
} else {
|
||||
{
|
||||
if (filter(it)) {
|
||||
serialFormat.encodeDefault(serializer, it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
@@ -0,0 +1,25 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.response.respond
|
||||
|
||||
suspend fun ApplicationCall.getParameterOrSendError(
|
||||
field: String
|
||||
) = parameters[field].also {
|
||||
if (it == null) {
|
||||
respond(HttpStatusCode.BadRequest, "Request must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
fun ApplicationCall.getQueryParameter(
|
||||
field: String
|
||||
) = request.queryParameters[field]
|
||||
|
||||
suspend fun ApplicationCall.getQueryParameterOrSendError(
|
||||
field: String
|
||||
) = getQueryParameter(field).also {
|
||||
if (it == null) {
|
||||
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
@@ -1,308 +0,0 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
import dev.inmo.micro_utils.common.*
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.content.*
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.request.receiveMultipart
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.response.respondBytes
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.websocket.WebSocketServerSession
|
||||
import io.ktor.util.pipeline.PipelineContext
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
@Deprecated("This class method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
class UnifiedRouter(
|
||||
val serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
val serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) {
|
||||
fun <T> Route.includeWebsocketHandling(
|
||||
suburl: String,
|
||||
flow: Flow<T>,
|
||||
serializer: SerializationStrategy<T>,
|
||||
protocol: URLProtocol? = null,
|
||||
filter: (suspend WebSocketServerSession.(T) -> Boolean)? = null
|
||||
) = includeWebsocketHandling(suburl, flow, serializer, serialFormat, protocol, filter)
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.unianswer(
|
||||
answerSerializer: SerializationStrategy<T>,
|
||||
answer: T
|
||||
) {
|
||||
call.respondBytes (
|
||||
serialFormat.encodeDefault(answerSerializer, answer),
|
||||
serialFormatContentType
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.uniload(
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = safely {
|
||||
serialFormat.decodeDefault(
|
||||
deserializer,
|
||||
call.receive()
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun PipelineContext<*, ApplicationCall>.getParameterOrSendError(
|
||||
field: String
|
||||
) = call.parameters[field].also {
|
||||
if (it == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
fun PipelineContext<*, ApplicationCall>.getQueryParameter(
|
||||
field: String
|
||||
) = call.request.queryParameters[field]
|
||||
|
||||
suspend fun PipelineContext<*, ApplicationCall>.getQueryParameterOrSendError(
|
||||
field: String
|
||||
) = getQueryParameter(field).also {
|
||||
if (it == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValue(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = getQueryParameter(field) ?.let {
|
||||
serialFormat.decodeHex(
|
||||
deserializer,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <T> PipelineContext<*, ApplicationCall>.decodeUrlQueryValueOrSendError(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = decodeUrlQueryValue(field, deserializer).also {
|
||||
if (it == null) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val default
|
||||
get() = defaultUnifiedRouter
|
||||
}
|
||||
}
|
||||
|
||||
val defaultUnifiedRouter = UnifiedRouter()
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <T> ApplicationCall.unianswer(
|
||||
answerSerializer: SerializationStrategy<T>,
|
||||
answer: T
|
||||
) {
|
||||
respondBytes (
|
||||
standardKtorSerialFormat.encodeDefault(answerSerializer, answer),
|
||||
standardKtorSerialFormatContentType
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <T> ApplicationCall.uniload(
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = safely {
|
||||
standardKtorSerialFormat.decodeDefault(
|
||||
deserializer,
|
||||
receive()
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun ApplicationCall.uniloadMultipart(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||
) = safely {
|
||||
val multipartData = receiveMultipart()
|
||||
|
||||
var resultInput: Input? = null
|
||||
|
||||
multipartData.forEachPart {
|
||||
when (it) {
|
||||
is PartData.FormItem -> onFormItem(it)
|
||||
is PartData.FileItem -> {
|
||||
when (it.name) {
|
||||
"bytes" -> resultInput = it.provider()
|
||||
else -> onCustomFileItem(it)
|
||||
}
|
||||
}
|
||||
is PartData.BinaryItem -> onBinaryContent(it)
|
||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
resultInput ?: error("Bytes has not been received")
|
||||
}
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <T> ApplicationCall.uniloadMultipart(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||
): Pair<Input, T> {
|
||||
var data: Optional<T>? = null
|
||||
val resultInput = uniloadMultipart(
|
||||
onFormItem,
|
||||
{
|
||||
if (it.name == "data") {
|
||||
data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
||||
} else {
|
||||
onCustomFileItem(it)
|
||||
}
|
||||
},
|
||||
onBinaryChannelItem,
|
||||
onBinaryContent
|
||||
)
|
||||
|
||||
val completeData = data ?: error("Data has not been received")
|
||||
return resultInput to (completeData.dataOrNull().let { it as T })
|
||||
}
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <T> ApplicationCall.uniloadMultipartFile(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||
) = safely {
|
||||
val multipartData = receiveMultipart()
|
||||
|
||||
var resultInput: MPPFile? = null
|
||||
var data: Optional<T>? = null
|
||||
|
||||
multipartData.forEachPart {
|
||||
when (it) {
|
||||
is PartData.FormItem -> onFormItem(it)
|
||||
is PartData.FileItem -> {
|
||||
when (it.name) {
|
||||
"bytes" -> {
|
||||
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
|
||||
resultInput = MPPFile.createTempFile(
|
||||
name.nameWithoutExtension.let {
|
||||
var resultName = it
|
||||
while (resultName.length < 3) {
|
||||
resultName += "_"
|
||||
}
|
||||
resultName
|
||||
},
|
||||
".${name.extension}"
|
||||
).apply {
|
||||
outputStream().use { fileStream ->
|
||||
it.streamProvider().use {
|
||||
it.copyTo(fileStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"data" -> data = standardKtorSerialFormat.decodeDefault(deserializer, it.provider().readBytes()).optional
|
||||
else -> onCustomFileItem(it)
|
||||
}
|
||||
}
|
||||
is PartData.BinaryItem -> onBinaryContent(it)
|
||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
val completeData = data ?: error("Data has not been received")
|
||||
(resultInput ?: error("Bytes has not been received")) to (completeData.dataOrNull().let { it as T })
|
||||
}
|
||||
|
||||
suspend fun ApplicationCall.uniloadMultipartFile(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||
) = safely {
|
||||
val multipartData = receiveMultipart()
|
||||
|
||||
var resultInput: MPPFile? = null
|
||||
|
||||
multipartData.forEachPart {
|
||||
when (it) {
|
||||
is PartData.FormItem -> onFormItem(it)
|
||||
is PartData.FileItem -> {
|
||||
if (it.name == "bytes") {
|
||||
val name = FileName(it.originalFileName ?: error("File name is unknown for default part"))
|
||||
resultInput = MPPFile.createTempFile(
|
||||
name.nameWithoutExtension.let {
|
||||
var resultName = it
|
||||
while (resultName.length < 3) {
|
||||
resultName += "_"
|
||||
}
|
||||
resultName
|
||||
},
|
||||
".${name.extension}"
|
||||
).apply {
|
||||
outputStream().use { fileStream ->
|
||||
it.streamProvider().use {
|
||||
it.copyTo(fileStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onCustomFileItem(it)
|
||||
}
|
||||
}
|
||||
is PartData.BinaryItem -> onBinaryContent(it)
|
||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
resultInput ?: error("Bytes has not been received")
|
||||
}
|
||||
|
||||
suspend fun ApplicationCall.getParameterOrSendError(
|
||||
field: String
|
||||
) = parameters[field].also {
|
||||
if (it == null) {
|
||||
respond(HttpStatusCode.BadRequest, "Request must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
fun ApplicationCall.getQueryParameter(
|
||||
field: String
|
||||
) = request.queryParameters[field]
|
||||
|
||||
suspend fun ApplicationCall.getQueryParameterOrSendError(
|
||||
field: String
|
||||
) = getQueryParameter(field).also {
|
||||
if (it == null) {
|
||||
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
fun <T> ApplicationCall.decodeUrlQueryValue(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = getQueryParameter(field) ?.let {
|
||||
standardKtorSerialFormat.decodeHex(
|
||||
deserializer,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("This method will be removed soon. It is now recommended to use built-in ktor features instead")
|
||||
suspend fun <T> ApplicationCall.decodeUrlQueryValueOrSendError(
|
||||
field: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
) = decodeUrlQueryValue(field, deserializer).also {
|
||||
if (it == null) {
|
||||
respond(HttpStatusCode.BadRequest, "Request query parameters must contains $field")
|
||||
}
|
||||
}
|
@@ -26,7 +26,6 @@ import java.nio.file.attribute.FileTime
|
||||
|
||||
class TemporalFilesRoutingConfigurator(
|
||||
private val subpath: String = DefaultTemporalFilesSubPath,
|
||||
private val unifiedRouter: UnifiedRouter = UnifiedRouter.default,
|
||||
private val temporalFilesUtilizer: TemporalFilesUtilizer = TemporalFilesUtilizer
|
||||
) : ApplicationRoutingConfigurator.Element {
|
||||
interface TemporalFilesUtilizer {
|
||||
@@ -80,42 +79,40 @@ class TemporalFilesRoutingConfigurator(
|
||||
|
||||
override fun Route.invoke() {
|
||||
post(subpath) {
|
||||
unifiedRouter.apply {
|
||||
val multipart = call.receiveMultipart()
|
||||
val multipart = call.receiveMultipart()
|
||||
|
||||
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
|
||||
var part = multipart.readPart()
|
||||
var fileInfo: Pair<TemporalFileId, MPPFile>? = null
|
||||
var part = multipart.readPart()
|
||||
|
||||
while (part != null) {
|
||||
if (part is PartData.FileItem) {
|
||||
break
|
||||
}
|
||||
part = multipart.readPart()
|
||||
while (part != null) {
|
||||
if (part is PartData.FileItem) {
|
||||
break
|
||||
}
|
||||
|
||||
part ?.let {
|
||||
if (it is PartData.FileItem) {
|
||||
val fileId = TemporalFileId(uuid4().toString())
|
||||
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
|
||||
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
|
||||
outputStream().use { outputStream ->
|
||||
it.streamProvider().use {
|
||||
it.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
deleteOnExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo ?.also { (fileId, file) ->
|
||||
temporalFilesMutex.withLock {
|
||||
temporalFilesMap[fileId] = file
|
||||
}
|
||||
call.respondText(fileId.string)
|
||||
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
|
||||
} ?: call.respond(HttpStatusCode.BadRequest)
|
||||
part = multipart.readPart()
|
||||
}
|
||||
|
||||
part ?.let {
|
||||
if (it is PartData.FileItem) {
|
||||
val fileId = TemporalFileId(uuid4().toString())
|
||||
val fileName = it.originalFileName ?.let { FileName(it) } ?: return@let
|
||||
fileInfo = fileId to File.createTempFile(fileId.string, ".${fileName.extension}").apply {
|
||||
outputStream().use { outputStream ->
|
||||
it.streamProvider().use {
|
||||
it.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
deleteOnExit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo ?.also { (fileId, file) ->
|
||||
temporalFilesMutex.withLock {
|
||||
temporalFilesMap[fileId] = file
|
||||
}
|
||||
call.respondText(fileId.string)
|
||||
launchSafelyWithoutExceptions { filesFlow.emit(fileId) }
|
||||
} ?: call.respond(HttpStatusCode.BadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,70 @@
|
||||
package dev.inmo.micro_utils.ktor.server
|
||||
|
||||
import dev.inmo.micro_utils.common.*
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.micro_utils.ktor.common.downloadToTempFile
|
||||
import io.ktor.http.content.*
|
||||
import io.ktor.server.application.ApplicationCall
|
||||
import io.ktor.server.request.receiveMultipart
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.isActive
|
||||
|
||||
/**
|
||||
* Server-side part which receives [dev.inmo.micro_utils.ktor.client.uniUpload] request
|
||||
*/
|
||||
suspend inline fun ApplicationCall.handleUniUpload(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onFileItem: (PartData.FileItem) -> Unit = {}
|
||||
) {
|
||||
val multipartData = receiveMultipart()
|
||||
|
||||
while (currentCoroutineContext().isActive) {
|
||||
val partData = multipartData.readPart() ?: break
|
||||
when (partData) {
|
||||
is PartData.FormItem -> onFormItem(partData)
|
||||
is PartData.FileItem -> onFileItem(partData)
|
||||
is PartData.BinaryItem -> onBinaryContent(partData)
|
||||
is PartData.BinaryChannelItem -> onBinaryChannelItem(partData)
|
||||
}
|
||||
partData.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ApplicationCall.uniloadMultipart(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {}
|
||||
): Input = safely {
|
||||
var resultInput: Input? = null
|
||||
|
||||
handleUniUpload(
|
||||
onFormItem,
|
||||
onBinaryContent,
|
||||
onBinaryChannelItem
|
||||
) {
|
||||
when (it.name) {
|
||||
"bytes" -> resultInput = it.provider()
|
||||
else -> onCustomFileItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
resultInput ?: error("Bytes has not been received")
|
||||
}
|
||||
|
||||
suspend fun ApplicationCall.uniloadMultipartFile(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
onCustomFileItem: (PartData.FileItem) -> Unit = {},
|
||||
onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {},
|
||||
onBinaryContent: (PartData.BinaryItem) -> Unit = {},
|
||||
): MPPFile = safely {
|
||||
uniloadMultipart(
|
||||
onFormItem,
|
||||
onCustomFileItem,
|
||||
onBinaryChannelItem,
|
||||
onBinaryContent
|
||||
).downloadToTempFile()
|
||||
}
|
@@ -78,12 +78,14 @@ private fun printLanguageCodeAndTags(
|
||||
indents: String = " "
|
||||
): String = if (tag.subtags.isEmpty()) {
|
||||
"""${indents}${baseClassSerializerAnnotationName}
|
||||
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}" }"""
|
||||
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"; override val withoutDialect: String get() = ${parent ?.title ?.let { "$it.code" } ?: "code"} }"""
|
||||
} else {
|
||||
"""
|
||||
${indents}${baseClassSerializerAnnotationName}
|
||||
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
|
||||
${indents} override val code: String = "${tag.tag}"
|
||||
${indents} override val withoutDialect: String
|
||||
${indents} get() = code
|
||||
|
||||
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
|
||||
|
||||
@@ -104,11 +106,14 @@ import kotlinx.serialization.Serializable
|
||||
${baseClassSerializerAnnotationName}
|
||||
sealed class $baseClassName {
|
||||
abstract val code: String
|
||||
abstract val withoutDialect: String
|
||||
|
||||
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
|
||||
|
||||
$baseClassSerializerAnnotationName
|
||||
data class $unknownBaseClassName (override val code: String) : $baseClassName()
|
||||
data class $unknownBaseClassName (override val code: String) : $baseClassName() {
|
||||
override val withoutDialect: String = code.takeWhile { it != '-' }
|
||||
}
|
||||
|
||||
override fun toString() = code
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -17,10 +17,11 @@ def fix_name(category, raw_name):
|
||||
result += out1
|
||||
return result
|
||||
|
||||
# https://www.freeformatter.com/mime-types-list.html
|
||||
if __name__ == '__main__':
|
||||
df = pd.read_html(open('table.html', 'r'))
|
||||
mimes = []
|
||||
for row in df[0].iterrows():
|
||||
for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows():
|
||||
mime = row[1][1]
|
||||
mime_category = mime.split('/', 1)[0]
|
||||
mime_name = mime.split('/', 1)[1]
|
||||
@@ -37,7 +38,7 @@ if __name__ == '__main__':
|
||||
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
|
||||
code2 += ' KnownMimeTypes.Any,\n'
|
||||
for key, group in grouped:
|
||||
group_name = key.capitalize()
|
||||
group_name = fix_name(group, key)
|
||||
code += '@Serializable(MimeTypeSerializer::class)\nsealed class %s(raw: String) : MimeType, KnownMimeTypes(raw) {\n' % group_name
|
||||
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
|
||||
for mime in group:
|
||||
|
@@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.mime_types
|
||||
|
||||
data class CustomMimeType(override val raw: String) : MimeType
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,705 @@
|
||||
package dev.inmo.micro_utils.mime_types
|
||||
|
||||
internal val knownMimeTypes: Set<MimeType> = setOf(
|
||||
KnownMimeTypes.Any,
|
||||
KnownMimeTypes.Application.VndHzn3dCrossword,
|
||||
KnownMimeTypes.Application.VndMseq,
|
||||
KnownMimeTypes.Application.Vnd3mPostItNotes,
|
||||
KnownMimeTypes.Application.Vnd3gppPicBwLarge,
|
||||
KnownMimeTypes.Application.Vnd3gppPicBwSmall,
|
||||
KnownMimeTypes.Application.Vnd3gppPicBwVar,
|
||||
KnownMimeTypes.Application.Vnd3gpp2Tcap,
|
||||
KnownMimeTypes.Application.X7zCompressed,
|
||||
KnownMimeTypes.Application.XAbiword,
|
||||
KnownMimeTypes.Application.XAceCompressed,
|
||||
KnownMimeTypes.Application.VndAmericandynamicsAcc,
|
||||
KnownMimeTypes.Application.VndAcucobol,
|
||||
KnownMimeTypes.Application.VndAcucorp,
|
||||
KnownMimeTypes.Application.XAuthorwareBin,
|
||||
KnownMimeTypes.Application.XAuthorwareMap,
|
||||
KnownMimeTypes.Application.XAuthorwareSeg,
|
||||
KnownMimeTypes.Application.VndAdobeAirApplicationInstallerPackageZip,
|
||||
KnownMimeTypes.Application.XShockwaveFlash,
|
||||
KnownMimeTypes.Application.VndAdobeFxp,
|
||||
KnownMimeTypes.Application.Pdf,
|
||||
KnownMimeTypes.Application.VndCupsPpd,
|
||||
KnownMimeTypes.Application.XDirector,
|
||||
KnownMimeTypes.Application.VndAdobeXdpXml,
|
||||
KnownMimeTypes.Application.VndAdobeXfdf,
|
||||
KnownMimeTypes.Application.VndAheadSpace,
|
||||
KnownMimeTypes.Application.VndAirzipFilesecureAzf,
|
||||
KnownMimeTypes.Application.VndAirzipFilesecureAzs,
|
||||
KnownMimeTypes.Application.VndAmazonEbook,
|
||||
KnownMimeTypes.Application.VndAmigaAmi,
|
||||
KnownMimeTypes.Application.AndrewInset,
|
||||
KnownMimeTypes.Application.VndAndroidPackageArchive,
|
||||
KnownMimeTypes.Application.VndAnserWebCertificateIssueInitiation,
|
||||
KnownMimeTypes.Application.VndAnserWebFundsTransferInitiation,
|
||||
KnownMimeTypes.Application.VndAntixGameComponent,
|
||||
KnownMimeTypes.Application.XAppleDiskimage,
|
||||
KnownMimeTypes.Application.VndAppleInstallerXml,
|
||||
KnownMimeTypes.Application.Applixware,
|
||||
KnownMimeTypes.Application.VndHheLessonPlayer,
|
||||
KnownMimeTypes.Application.XFreearc,
|
||||
KnownMimeTypes.Application.VndAristanetworksSwi,
|
||||
KnownMimeTypes.Application.AtomcatXml,
|
||||
KnownMimeTypes.Application.AtomsvcXml,
|
||||
KnownMimeTypes.Application.AtomXml,
|
||||
KnownMimeTypes.Application.PkixAttrCert,
|
||||
KnownMimeTypes.Application.VndAudiograph,
|
||||
KnownMimeTypes.Application.XBcpio,
|
||||
KnownMimeTypes.Application.OctetStream,
|
||||
KnownMimeTypes.Application.XBittorrent,
|
||||
KnownMimeTypes.Application.VndRimCod,
|
||||
KnownMimeTypes.Application.VndBlueiceMultipass,
|
||||
KnownMimeTypes.Application.VndBmi,
|
||||
KnownMimeTypes.Application.XSh,
|
||||
KnownMimeTypes.Application.VndBusinessobjects,
|
||||
KnownMimeTypes.Application.XBzip,
|
||||
KnownMimeTypes.Application.XBzip2,
|
||||
KnownMimeTypes.Application.XCsh,
|
||||
KnownMimeTypes.Application.VndChemdrawXml,
|
||||
KnownMimeTypes.Application.XCdf,
|
||||
KnownMimeTypes.Application.VndContactCmsg,
|
||||
KnownMimeTypes.Application.VndClaymore,
|
||||
KnownMimeTypes.Application.VndClonkC4group,
|
||||
KnownMimeTypes.Application.CdmiCapability,
|
||||
KnownMimeTypes.Application.CdmiContainer,
|
||||
KnownMimeTypes.Application.CdmiDomain,
|
||||
KnownMimeTypes.Application.CdmiObject,
|
||||
KnownMimeTypes.Application.CdmiQueue,
|
||||
KnownMimeTypes.Application.VndCluetrustCartomobileConfig,
|
||||
KnownMimeTypes.Application.VndCluetrustCartomobileConfigPkg,
|
||||
KnownMimeTypes.Application.MacCompactpro,
|
||||
KnownMimeTypes.Application.VndWapWmlc,
|
||||
KnownMimeTypes.Application.VndXara,
|
||||
KnownMimeTypes.Application.VndCosmocaller,
|
||||
KnownMimeTypes.Application.XCpio,
|
||||
KnownMimeTypes.Application.VndCrickClicker,
|
||||
KnownMimeTypes.Application.VndCrickClickerKeyboard,
|
||||
KnownMimeTypes.Application.VndCrickClickerPalette,
|
||||
KnownMimeTypes.Application.VndCrickClickerTemplate,
|
||||
KnownMimeTypes.Application.VndCrickClickerWordbank,
|
||||
KnownMimeTypes.Application.VndCriticaltoolsWbsXml,
|
||||
KnownMimeTypes.Application.VndRigCryptonote,
|
||||
KnownMimeTypes.Application.CuSeeme,
|
||||
KnownMimeTypes.Application.PrsCww,
|
||||
KnownMimeTypes.Application.VndCurlCar,
|
||||
KnownMimeTypes.Application.VndCurlPcurl,
|
||||
KnownMimeTypes.Application.VndYellowriverCustomMenu,
|
||||
KnownMimeTypes.Application.DsscDer,
|
||||
KnownMimeTypes.Application.DsscXml,
|
||||
KnownMimeTypes.Application.XDebianPackage,
|
||||
KnownMimeTypes.Application.XDvi,
|
||||
KnownMimeTypes.Application.VndFdsnSeed,
|
||||
KnownMimeTypes.Application.XDtbookXml,
|
||||
KnownMimeTypes.Application.XDtbresourceXml,
|
||||
KnownMimeTypes.Application.VndDvbAit,
|
||||
KnownMimeTypes.Application.VndDvbService,
|
||||
KnownMimeTypes.Application.XmlDtd,
|
||||
KnownMimeTypes.Application.VndDolbyMlp,
|
||||
KnownMimeTypes.Application.XDoom,
|
||||
KnownMimeTypes.Application.VndDpgraph,
|
||||
KnownMimeTypes.Application.VndDreamfactory,
|
||||
KnownMimeTypes.Application.VndDynageo,
|
||||
KnownMimeTypes.Application.Ecmascript,
|
||||
KnownMimeTypes.Application.VndEcowinChart,
|
||||
KnownMimeTypes.Application.Exi,
|
||||
KnownMimeTypes.Application.VndProteusMagazine,
|
||||
KnownMimeTypes.Application.EpubZip,
|
||||
KnownMimeTypes.Application.VndEnliven,
|
||||
KnownMimeTypes.Application.VndIsXpr,
|
||||
KnownMimeTypes.Application.VndXfdl,
|
||||
KnownMimeTypes.Application.EmmaXml,
|
||||
KnownMimeTypes.Application.VndEzpixAlbum,
|
||||
KnownMimeTypes.Application.VndEzpixPackage,
|
||||
KnownMimeTypes.Application.VndDenovoFcselayoutLink,
|
||||
KnownMimeTypes.Application.VndFluxtimeClip,
|
||||
KnownMimeTypes.Application.VndFdf,
|
||||
KnownMimeTypes.Application.VndMif,
|
||||
KnownMimeTypes.Application.VndFramemaker,
|
||||
KnownMimeTypes.Application.VndFscWeblaunch,
|
||||
KnownMimeTypes.Application.VndFrogansFnc,
|
||||
KnownMimeTypes.Application.VndFrogansLtf,
|
||||
KnownMimeTypes.Application.VndFujixeroxDdd,
|
||||
KnownMimeTypes.Application.VndFujixeroxDocuworks,
|
||||
KnownMimeTypes.Application.VndFujixeroxDocuworksBinder,
|
||||
KnownMimeTypes.Application.VndFujitsuOasys,
|
||||
KnownMimeTypes.Application.VndFujitsuOasys2,
|
||||
KnownMimeTypes.Application.VndFujitsuOasys3,
|
||||
KnownMimeTypes.Application.VndFujitsuOasysgp,
|
||||
KnownMimeTypes.Application.VndFujitsuOasysprs,
|
||||
KnownMimeTypes.Application.XFuturesplash,
|
||||
KnownMimeTypes.Application.VndFuzzysheet,
|
||||
KnownMimeTypes.Application.VndGmx,
|
||||
KnownMimeTypes.Application.VndGenomatixTuxedo,
|
||||
KnownMimeTypes.Application.VndGeogebraFile,
|
||||
KnownMimeTypes.Application.VndGeogebraTool,
|
||||
KnownMimeTypes.Application.VndGeometryExplorer,
|
||||
KnownMimeTypes.Application.VndGeonext,
|
||||
KnownMimeTypes.Application.VndGeoplan,
|
||||
KnownMimeTypes.Application.VndGeospace,
|
||||
KnownMimeTypes.Application.XFontGhostscript,
|
||||
KnownMimeTypes.Application.XFontBdf,
|
||||
KnownMimeTypes.Application.XGtar,
|
||||
KnownMimeTypes.Application.XTexinfo,
|
||||
KnownMimeTypes.Application.XGnumeric,
|
||||
KnownMimeTypes.Application.VndGoogleEarthKmlXml,
|
||||
KnownMimeTypes.Application.VndGoogleEarthKmz,
|
||||
KnownMimeTypes.Application.GpxXml,
|
||||
KnownMimeTypes.Application.VndGrafeq,
|
||||
KnownMimeTypes.Application.VndGrooveAccount,
|
||||
KnownMimeTypes.Application.VndGrooveHelp,
|
||||
KnownMimeTypes.Application.VndGrooveIdentityMessage,
|
||||
KnownMimeTypes.Application.VndGrooveInjector,
|
||||
KnownMimeTypes.Application.VndGrooveToolMessage,
|
||||
KnownMimeTypes.Application.VndGrooveToolTemplate,
|
||||
KnownMimeTypes.Application.VndGrooveVcard,
|
||||
KnownMimeTypes.Application.Gzip,
|
||||
KnownMimeTypes.Application.VndHpHpid,
|
||||
KnownMimeTypes.Application.VndHpHps,
|
||||
KnownMimeTypes.Application.XHdf,
|
||||
KnownMimeTypes.Application.VndHbci,
|
||||
KnownMimeTypes.Application.VndHpJlyt,
|
||||
KnownMimeTypes.Application.VndHpPcl,
|
||||
KnownMimeTypes.Application.VndHpHpgl,
|
||||
KnownMimeTypes.Application.VndYamahaHvScript,
|
||||
KnownMimeTypes.Application.VndYamahaHvDic,
|
||||
KnownMimeTypes.Application.VndYamahaHvVoice,
|
||||
KnownMimeTypes.Application.VndHydrostatixSofData,
|
||||
KnownMimeTypes.Application.Hyperstudio,
|
||||
KnownMimeTypes.Application.VndHalXml,
|
||||
KnownMimeTypes.Application.VndIbmRightsManagement,
|
||||
KnownMimeTypes.Application.VndIbmSecureContainer,
|
||||
KnownMimeTypes.Application.VndIccprofile,
|
||||
KnownMimeTypes.Application.VndIgloader,
|
||||
KnownMimeTypes.Application.VndImmervisionIvp,
|
||||
KnownMimeTypes.Application.VndImmervisionIvu,
|
||||
KnownMimeTypes.Application.ReginfoXml,
|
||||
KnownMimeTypes.Application.VndIntergeo,
|
||||
KnownMimeTypes.Application.VndCinderella,
|
||||
KnownMimeTypes.Application.VndInterconFormnet,
|
||||
KnownMimeTypes.Application.VndIsacFcs,
|
||||
KnownMimeTypes.Application.Ipfix,
|
||||
KnownMimeTypes.Application.PkixCert,
|
||||
KnownMimeTypes.Application.Pkixcmp,
|
||||
KnownMimeTypes.Application.PkixCrl,
|
||||
KnownMimeTypes.Application.PkixPkipath,
|
||||
KnownMimeTypes.Application.VndInsorsIgm,
|
||||
KnownMimeTypes.Application.VndIpunpluggedRcprofile,
|
||||
KnownMimeTypes.Application.VndIrepositoryPackageXml,
|
||||
KnownMimeTypes.Application.JavaArchive,
|
||||
KnownMimeTypes.Application.JavaVm,
|
||||
KnownMimeTypes.Application.XJavaJnlpFile,
|
||||
KnownMimeTypes.Application.JavaSerializedObject,
|
||||
KnownMimeTypes.Application.Javascript,
|
||||
KnownMimeTypes.Application.Json,
|
||||
KnownMimeTypes.Application.VndJoostJodaArchive,
|
||||
KnownMimeTypes.Application.LdJson,
|
||||
KnownMimeTypes.Application.VndKahootz,
|
||||
KnownMimeTypes.Application.VndChipnutsKaraokeMmd,
|
||||
KnownMimeTypes.Application.VndKdeKarbon,
|
||||
KnownMimeTypes.Application.VndKdeKchart,
|
||||
KnownMimeTypes.Application.VndKdeKformula,
|
||||
KnownMimeTypes.Application.VndKdeKivio,
|
||||
KnownMimeTypes.Application.VndKdeKontour,
|
||||
KnownMimeTypes.Application.VndKdeKpresenter,
|
||||
KnownMimeTypes.Application.VndKdeKspread,
|
||||
KnownMimeTypes.Application.VndKdeKword,
|
||||
KnownMimeTypes.Application.VndKenameaapp,
|
||||
KnownMimeTypes.Application.VndKidspiration,
|
||||
KnownMimeTypes.Application.VndKinar,
|
||||
KnownMimeTypes.Application.VndKodakDescriptor,
|
||||
KnownMimeTypes.Application.VndLasLasXml,
|
||||
KnownMimeTypes.Application.XLatex,
|
||||
KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceDesktop,
|
||||
KnownMimeTypes.Application.VndLlamagraphicsLifeBalanceExchangeXml,
|
||||
KnownMimeTypes.Application.VndJam,
|
||||
KnownMimeTypes.Application.VndLotus123,
|
||||
KnownMimeTypes.Application.VndLotusApproach,
|
||||
KnownMimeTypes.Application.VndLotusFreelance,
|
||||
KnownMimeTypes.Application.VndLotusNotes,
|
||||
KnownMimeTypes.Application.VndLotusOrganizer,
|
||||
KnownMimeTypes.Application.VndLotusScreencam,
|
||||
KnownMimeTypes.Application.VndLotusWordpro,
|
||||
KnownMimeTypes.Application.MacBinhex40,
|
||||
KnownMimeTypes.Application.VndMacportsPortpkg,
|
||||
KnownMimeTypes.Application.VndOsgeoMapguidePackage,
|
||||
KnownMimeTypes.Application.Marc,
|
||||
KnownMimeTypes.Application.MarcxmlXml,
|
||||
KnownMimeTypes.Application.Mxf,
|
||||
KnownMimeTypes.Application.VndWolframPlayer,
|
||||
KnownMimeTypes.Application.Mathematica,
|
||||
KnownMimeTypes.Application.MathmlXml,
|
||||
KnownMimeTypes.Application.Mbox,
|
||||
KnownMimeTypes.Application.VndMedcalcdata,
|
||||
KnownMimeTypes.Application.MediaservercontrolXml,
|
||||
KnownMimeTypes.Application.VndMediastationCdkey,
|
||||
KnownMimeTypes.Application.VndMfer,
|
||||
KnownMimeTypes.Application.VndMfmp,
|
||||
KnownMimeTypes.Application.MadsXml,
|
||||
KnownMimeTypes.Application.MetsXml,
|
||||
KnownMimeTypes.Application.ModsXml,
|
||||
KnownMimeTypes.Application.Metalink4Xml,
|
||||
KnownMimeTypes.Application.VndMcd,
|
||||
KnownMimeTypes.Application.VndMicrografxFlo,
|
||||
KnownMimeTypes.Application.VndMicrografxIgx,
|
||||
KnownMimeTypes.Application.VndEszigno3Xml,
|
||||
KnownMimeTypes.Application.XMsaccess,
|
||||
KnownMimeTypes.Application.XMsdownload,
|
||||
KnownMimeTypes.Application.VndMsArtgalry,
|
||||
KnownMimeTypes.Application.VndMsCabCompressed,
|
||||
KnownMimeTypes.Application.VndMsIms,
|
||||
KnownMimeTypes.Application.XMsApplication,
|
||||
KnownMimeTypes.Application.XMsclip,
|
||||
KnownMimeTypes.Application.VndMsFontobject,
|
||||
KnownMimeTypes.Application.VndMsExcel,
|
||||
KnownMimeTypes.Application.VndMsExcelAddinMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsExcelSheetBinaryMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsExcelTemplateMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsExcelSheetMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsHtmlhelp,
|
||||
KnownMimeTypes.Application.XMscardfile,
|
||||
KnownMimeTypes.Application.VndMsLrm,
|
||||
KnownMimeTypes.Application.XMsmediaview,
|
||||
KnownMimeTypes.Application.XMsmoney,
|
||||
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlPresentation,
|
||||
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlide,
|
||||
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlSlideshow,
|
||||
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentPresentationmlTemplate,
|
||||
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlSheet,
|
||||
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentSpreadsheetmlTemplate,
|
||||
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlDocument,
|
||||
KnownMimeTypes.Application.VndOpenxmlformatsOfficedocumentWordprocessingmlTemplate,
|
||||
KnownMimeTypes.Application.XMsbinder,
|
||||
KnownMimeTypes.Application.VndMsOfficetheme,
|
||||
KnownMimeTypes.Application.Onenote,
|
||||
KnownMimeTypes.Application.VndMsPowerpoint,
|
||||
KnownMimeTypes.Application.VndMsPowerpointAddinMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsPowerpointSlideMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsPowerpointPresentationMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsPowerpointSlideshowMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsPowerpointTemplateMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsProject,
|
||||
KnownMimeTypes.Application.XMspublisher,
|
||||
KnownMimeTypes.Application.XMsschedule,
|
||||
KnownMimeTypes.Application.XSilverlightApp,
|
||||
KnownMimeTypes.Application.VndMsPkiStl,
|
||||
KnownMimeTypes.Application.VndMsPkiSeccat,
|
||||
KnownMimeTypes.Application.VndVisio,
|
||||
KnownMimeTypes.Application.VndVisio2013,
|
||||
KnownMimeTypes.Application.XMsWmd,
|
||||
KnownMimeTypes.Application.VndMsWpl,
|
||||
KnownMimeTypes.Application.XMsWmz,
|
||||
KnownMimeTypes.Application.XMsmetafile,
|
||||
KnownMimeTypes.Application.XMsterminal,
|
||||
KnownMimeTypes.Application.Msword,
|
||||
KnownMimeTypes.Application.VndMsWordDocumentMacroenabled12,
|
||||
KnownMimeTypes.Application.VndMsWordTemplateMacroenabled12,
|
||||
KnownMimeTypes.Application.XMswrite,
|
||||
KnownMimeTypes.Application.VndMsWorks,
|
||||
KnownMimeTypes.Application.XMsXbap,
|
||||
KnownMimeTypes.Application.VndMsXpsdocument,
|
||||
KnownMimeTypes.Application.VndIbmMinipay,
|
||||
KnownMimeTypes.Application.VndIbmModcap,
|
||||
KnownMimeTypes.Application.VndJcpJavameMidletRms,
|
||||
KnownMimeTypes.Application.VndTmobileLivetv,
|
||||
KnownMimeTypes.Application.XMobipocketEbook,
|
||||
KnownMimeTypes.Application.VndMobiusMbk,
|
||||
KnownMimeTypes.Application.VndMobiusDis,
|
||||
KnownMimeTypes.Application.VndMobiusPlc,
|
||||
KnownMimeTypes.Application.VndMobiusMqy,
|
||||
KnownMimeTypes.Application.VndMobiusMsl,
|
||||
KnownMimeTypes.Application.VndMobiusTxf,
|
||||
KnownMimeTypes.Application.VndMobiusDaf,
|
||||
KnownMimeTypes.Application.VndMophunCertificate,
|
||||
KnownMimeTypes.Application.VndMophunApplication,
|
||||
KnownMimeTypes.Application.Mp21,
|
||||
KnownMimeTypes.Application.Mp4,
|
||||
KnownMimeTypes.Application.VndAppleMpegurl,
|
||||
KnownMimeTypes.Application.VndMusician,
|
||||
KnownMimeTypes.Application.VndMuveeStyle,
|
||||
KnownMimeTypes.Application.XvXml,
|
||||
KnownMimeTypes.Application.VndNokiaNGageData,
|
||||
KnownMimeTypes.Application.VndNokiaNGageSymbianInstall,
|
||||
KnownMimeTypes.Application.XDtbncxXml,
|
||||
KnownMimeTypes.Application.XNetcdf,
|
||||
KnownMimeTypes.Application.VndNeurolanguageNlu,
|
||||
KnownMimeTypes.Application.VndDna,
|
||||
KnownMimeTypes.Application.VndNoblenetDirectory,
|
||||
KnownMimeTypes.Application.VndNoblenetSealer,
|
||||
KnownMimeTypes.Application.VndNoblenetWeb,
|
||||
KnownMimeTypes.Application.VndNokiaRadioPreset,
|
||||
KnownMimeTypes.Application.VndNokiaRadioPresets,
|
||||
KnownMimeTypes.Application.VndNovadigmEdm,
|
||||
KnownMimeTypes.Application.VndNovadigmEdx,
|
||||
KnownMimeTypes.Application.VndNovadigmExt,
|
||||
KnownMimeTypes.Application.VndFlographit,
|
||||
KnownMimeTypes.Application.Oda,
|
||||
KnownMimeTypes.Application.Ogg,
|
||||
KnownMimeTypes.Application.VndOmaDd2Xml,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentTextWeb,
|
||||
KnownMimeTypes.Application.OebpsPackageXml,
|
||||
KnownMimeTypes.Application.VndIntuQbo,
|
||||
KnownMimeTypes.Application.VndOpenofficeorgExtension,
|
||||
KnownMimeTypes.Application.VndYamahaOpenscoreformat,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentChart,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentChartTemplate,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentDatabase,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentFormula,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentFormulaTemplate,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentGraphics,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentGraphicsTemplate,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentImage,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentImageTemplate,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentPresentation,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentPresentationTemplate,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheet,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentSpreadsheetTemplate,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentText,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentTextMaster,
|
||||
KnownMimeTypes.Application.VndOasisOpendocumentTextTemplate,
|
||||
KnownMimeTypes.Application.VndSunXmlCalc,
|
||||
KnownMimeTypes.Application.VndSunXmlCalcTemplate,
|
||||
KnownMimeTypes.Application.VndSunXmlDraw,
|
||||
KnownMimeTypes.Application.VndSunXmlDrawTemplate,
|
||||
KnownMimeTypes.Application.VndSunXmlImpress,
|
||||
KnownMimeTypes.Application.VndSunXmlImpressTemplate,
|
||||
KnownMimeTypes.Application.VndSunXmlMath,
|
||||
KnownMimeTypes.Application.VndSunXmlWriter,
|
||||
KnownMimeTypes.Application.VndSunXmlWriterGlobal,
|
||||
KnownMimeTypes.Application.VndSunXmlWriterTemplate,
|
||||
KnownMimeTypes.Application.XFontOtf,
|
||||
KnownMimeTypes.Application.VndYamahaOpenscoreformatOsfpvgXml,
|
||||
KnownMimeTypes.Application.VndOsgiDp,
|
||||
KnownMimeTypes.Application.VndPalm,
|
||||
KnownMimeTypes.Application.VndPawaafile,
|
||||
KnownMimeTypes.Application.VndHpPclxl,
|
||||
KnownMimeTypes.Application.VndPicsel,
|
||||
KnownMimeTypes.Application.PicsRules,
|
||||
KnownMimeTypes.Application.XChat,
|
||||
KnownMimeTypes.Application.Pkcs10,
|
||||
KnownMimeTypes.Application.XPkcs12,
|
||||
KnownMimeTypes.Application.Pkcs7Mime,
|
||||
KnownMimeTypes.Application.Pkcs7Signature,
|
||||
KnownMimeTypes.Application.XPkcs7Certreqresp,
|
||||
KnownMimeTypes.Application.XPkcs7Certificates,
|
||||
KnownMimeTypes.Application.Pkcs8,
|
||||
KnownMimeTypes.Application.VndPocketlearn,
|
||||
KnownMimeTypes.Application.XFontPcf,
|
||||
KnownMimeTypes.Application.FontTdpfr,
|
||||
KnownMimeTypes.Application.XChessPgn,
|
||||
KnownMimeTypes.Application.PskcXml,
|
||||
KnownMimeTypes.Application.VndCtcPosml,
|
||||
KnownMimeTypes.Application.Postscript,
|
||||
KnownMimeTypes.Application.XFontType1,
|
||||
KnownMimeTypes.Application.VndPowerbuilder6,
|
||||
KnownMimeTypes.Application.PgpEncrypted,
|
||||
KnownMimeTypes.Application.PgpSignature,
|
||||
KnownMimeTypes.Application.VndPreviewsystemsBox,
|
||||
KnownMimeTypes.Application.VndPviPtid1,
|
||||
KnownMimeTypes.Application.PlsXml,
|
||||
KnownMimeTypes.Application.VndPgFormat,
|
||||
KnownMimeTypes.Application.VndPgOsasli,
|
||||
KnownMimeTypes.Application.XFontLinuxPsf,
|
||||
KnownMimeTypes.Application.VndPublishareDeltaTree,
|
||||
KnownMimeTypes.Application.VndPmiWidget,
|
||||
KnownMimeTypes.Application.VndQuarkQuarkxpress,
|
||||
KnownMimeTypes.Application.VndEpsonEsf,
|
||||
KnownMimeTypes.Application.VndEpsonMsf,
|
||||
KnownMimeTypes.Application.VndEpsonSsf,
|
||||
KnownMimeTypes.Application.VndEpsonQuickanime,
|
||||
KnownMimeTypes.Application.VndIntuQfx,
|
||||
KnownMimeTypes.Application.XRarCompressed,
|
||||
KnownMimeTypes.Application.RsdXml,
|
||||
KnownMimeTypes.Application.VndRnRealmedia,
|
||||
KnownMimeTypes.Application.VndRealvncBed,
|
||||
KnownMimeTypes.Application.VndRecordareMusicxml,
|
||||
KnownMimeTypes.Application.VndRecordareMusicxmlXml,
|
||||
KnownMimeTypes.Application.RelaxNgCompactSyntax,
|
||||
KnownMimeTypes.Application.VndDataVisionRdz,
|
||||
KnownMimeTypes.Application.RdfXml,
|
||||
KnownMimeTypes.Application.VndCloantoRp9,
|
||||
KnownMimeTypes.Application.VndJisp,
|
||||
KnownMimeTypes.Application.Rtf,
|
||||
KnownMimeTypes.Application.VndRoute66Link66Xml,
|
||||
KnownMimeTypes.Application.RssXml,
|
||||
KnownMimeTypes.Application.ShfXml,
|
||||
KnownMimeTypes.Application.VndSailingtrackerTrack,
|
||||
KnownMimeTypes.Application.VndSusCalendar,
|
||||
KnownMimeTypes.Application.SruXml,
|
||||
KnownMimeTypes.Application.SetPaymentInitiation,
|
||||
KnownMimeTypes.Application.SetRegistrationInitiation,
|
||||
KnownMimeTypes.Application.VndSema,
|
||||
KnownMimeTypes.Application.VndSemd,
|
||||
KnownMimeTypes.Application.VndSemf,
|
||||
KnownMimeTypes.Application.VndSeemail,
|
||||
KnownMimeTypes.Application.XFontSnf,
|
||||
KnownMimeTypes.Application.ScvpVpRequest,
|
||||
KnownMimeTypes.Application.ScvpVpResponse,
|
||||
KnownMimeTypes.Application.ScvpCvRequest,
|
||||
KnownMimeTypes.Application.ScvpCvResponse,
|
||||
KnownMimeTypes.Application.Sdp,
|
||||
KnownMimeTypes.Application.VndShanaInformedFormdata,
|
||||
KnownMimeTypes.Application.VndShanaInformedFormtemplate,
|
||||
KnownMimeTypes.Application.VndShanaInformedInterchange,
|
||||
KnownMimeTypes.Application.VndShanaInformedPackage,
|
||||
KnownMimeTypes.Application.ThraudXml,
|
||||
KnownMimeTypes.Application.XShar,
|
||||
KnownMimeTypes.Application.VndEpsonSalt,
|
||||
KnownMimeTypes.Application.VndAccpacSimplyAso,
|
||||
KnownMimeTypes.Application.VndAccpacSimplyImp,
|
||||
KnownMimeTypes.Application.VndSimtechMindmapper,
|
||||
KnownMimeTypes.Application.VndCommonspace,
|
||||
KnownMimeTypes.Application.VndYamahaSmafAudio,
|
||||
KnownMimeTypes.Application.VndSmaf,
|
||||
KnownMimeTypes.Application.VndYamahaSmafPhrase,
|
||||
KnownMimeTypes.Application.VndSmartTeacher,
|
||||
KnownMimeTypes.Application.VndSvd,
|
||||
KnownMimeTypes.Application.SparqlQuery,
|
||||
KnownMimeTypes.Application.SparqlResultsXml,
|
||||
KnownMimeTypes.Application.Srgs,
|
||||
KnownMimeTypes.Application.SrgsXml,
|
||||
KnownMimeTypes.Application.SsmlXml,
|
||||
KnownMimeTypes.Application.VndKoan,
|
||||
KnownMimeTypes.Application.VndStardivisionCalc,
|
||||
KnownMimeTypes.Application.VndStardivisionDraw,
|
||||
KnownMimeTypes.Application.VndStardivisionImpress,
|
||||
KnownMimeTypes.Application.VndStardivisionMath,
|
||||
KnownMimeTypes.Application.VndStardivisionWriter,
|
||||
KnownMimeTypes.Application.VndStardivisionWriterGlobal,
|
||||
KnownMimeTypes.Application.VndStepmaniaStepchart,
|
||||
KnownMimeTypes.Application.XStuffit,
|
||||
KnownMimeTypes.Application.XStuffitx,
|
||||
KnownMimeTypes.Application.VndSolentSdkmXml,
|
||||
KnownMimeTypes.Application.VndOlpcSugar,
|
||||
KnownMimeTypes.Application.VndWqd,
|
||||
KnownMimeTypes.Application.VndSymbianInstall,
|
||||
KnownMimeTypes.Application.SmilXml,
|
||||
KnownMimeTypes.Application.VndSyncmlXml,
|
||||
KnownMimeTypes.Application.VndSyncmlDmWbxml,
|
||||
KnownMimeTypes.Application.VndSyncmlDmXml,
|
||||
KnownMimeTypes.Application.XSv4cpio,
|
||||
KnownMimeTypes.Application.XSv4crc,
|
||||
KnownMimeTypes.Application.SbmlXml,
|
||||
KnownMimeTypes.Application.VndTaoIntentModuleArchive,
|
||||
KnownMimeTypes.Application.XTar,
|
||||
KnownMimeTypes.Application.XTcl,
|
||||
KnownMimeTypes.Application.XTex,
|
||||
KnownMimeTypes.Application.XTexTfm,
|
||||
KnownMimeTypes.Application.TeiXml,
|
||||
KnownMimeTypes.Application.VndSpotfireDxp,
|
||||
KnownMimeTypes.Application.VndSpotfireSfs,
|
||||
KnownMimeTypes.Application.TimestampedData,
|
||||
KnownMimeTypes.Application.VndTridTpt,
|
||||
KnownMimeTypes.Application.VndTriscapeMxs,
|
||||
KnownMimeTypes.Application.VndTrueapp,
|
||||
KnownMimeTypes.Application.XFontTtf,
|
||||
KnownMimeTypes.Application.VndUmajin,
|
||||
KnownMimeTypes.Application.VndUomlXml,
|
||||
KnownMimeTypes.Application.VndUnity,
|
||||
KnownMimeTypes.Application.VndUfdl,
|
||||
KnownMimeTypes.Application.VndUiqTheme,
|
||||
KnownMimeTypes.Application.XUstar,
|
||||
KnownMimeTypes.Application.XCdlink,
|
||||
KnownMimeTypes.Application.VndVsf,
|
||||
KnownMimeTypes.Application.VndVcx,
|
||||
KnownMimeTypes.Application.VndVisionary,
|
||||
KnownMimeTypes.Application.CcxmlXml,
|
||||
KnownMimeTypes.Application.VoicexmlXml,
|
||||
KnownMimeTypes.Application.XWaisSource,
|
||||
KnownMimeTypes.Application.VndWapWbxml,
|
||||
KnownMimeTypes.Application.DavmountXml,
|
||||
KnownMimeTypes.Application.XFontWoff,
|
||||
KnownMimeTypes.Application.WspolicyXml,
|
||||
KnownMimeTypes.Application.VndWebturbo,
|
||||
KnownMimeTypes.Application.Widget,
|
||||
KnownMimeTypes.Application.Winhlp,
|
||||
KnownMimeTypes.Application.VndWapWmlscriptc,
|
||||
KnownMimeTypes.Application.VndWordperfect,
|
||||
KnownMimeTypes.Application.VndWtStf,
|
||||
KnownMimeTypes.Application.WsdlXml,
|
||||
KnownMimeTypes.Application.XX509CaCert,
|
||||
KnownMimeTypes.Application.XXfig,
|
||||
KnownMimeTypes.Application.XhtmlXml,
|
||||
KnownMimeTypes.Application.Xml,
|
||||
KnownMimeTypes.Application.XcapDiffXml,
|
||||
KnownMimeTypes.Application.XencXml,
|
||||
KnownMimeTypes.Application.PatchOpsErrorXml,
|
||||
KnownMimeTypes.Application.ResourceListsXml,
|
||||
KnownMimeTypes.Application.RlsServicesXml,
|
||||
KnownMimeTypes.Application.ResourceListsDiffXml,
|
||||
KnownMimeTypes.Application.XsltXml,
|
||||
KnownMimeTypes.Application.XopXml,
|
||||
KnownMimeTypes.Application.XXpinstall,
|
||||
KnownMimeTypes.Application.XspfXml,
|
||||
KnownMimeTypes.Application.VndMozillaXulXml,
|
||||
KnownMimeTypes.Application.Yang,
|
||||
KnownMimeTypes.Application.YinXml,
|
||||
KnownMimeTypes.Application.VndZul,
|
||||
KnownMimeTypes.Application.Zip,
|
||||
KnownMimeTypes.Application.VndHandheldEntertainmentXml,
|
||||
KnownMimeTypes.Application.VndZzazzDeckXml,
|
||||
KnownMimeTypes.Audio.Adpcm,
|
||||
KnownMimeTypes.Audio.XAac,
|
||||
KnownMimeTypes.Audio.XAiff,
|
||||
KnownMimeTypes.Audio.VndDeceAudio,
|
||||
KnownMimeTypes.Audio.VndDigitalWinds,
|
||||
KnownMimeTypes.Audio.VndDra,
|
||||
KnownMimeTypes.Audio.VndDts,
|
||||
KnownMimeTypes.Audio.VndDtsHd,
|
||||
KnownMimeTypes.Audio.VndRip,
|
||||
KnownMimeTypes.Audio.VndLucentVoice,
|
||||
KnownMimeTypes.Audio.XMpegurl,
|
||||
KnownMimeTypes.Audio.VndMsPlayreadyMediaPya,
|
||||
KnownMimeTypes.Audio.XMsWma,
|
||||
KnownMimeTypes.Audio.XMsWax,
|
||||
KnownMimeTypes.Audio.Midi,
|
||||
KnownMimeTypes.Audio.Mpeg,
|
||||
KnownMimeTypes.Audio.Mp4,
|
||||
KnownMimeTypes.Audio.VndNueraEcelp4800,
|
||||
KnownMimeTypes.Audio.VndNueraEcelp7470,
|
||||
KnownMimeTypes.Audio.VndNueraEcelp9600,
|
||||
KnownMimeTypes.Audio.Ogg,
|
||||
KnownMimeTypes.Audio.Webm,
|
||||
KnownMimeTypes.Audio.Opus,
|
||||
KnownMimeTypes.Audio.XPnRealaudio,
|
||||
KnownMimeTypes.Audio.XPnRealaudioPlugin,
|
||||
KnownMimeTypes.Audio.Basic,
|
||||
KnownMimeTypes.Audio.XWav,
|
||||
KnownMimeTypes.Chemical.XCdx,
|
||||
KnownMimeTypes.Chemical.XCml,
|
||||
KnownMimeTypes.Chemical.XCsml,
|
||||
KnownMimeTypes.Chemical.XCif,
|
||||
KnownMimeTypes.Chemical.XCmdf,
|
||||
KnownMimeTypes.Chemical.XXyz,
|
||||
KnownMimeTypes.Image.VndDxf,
|
||||
KnownMimeTypes.Image.Avif,
|
||||
KnownMimeTypes.Image.Bmp,
|
||||
KnownMimeTypes.Image.PrsBtif,
|
||||
KnownMimeTypes.Image.VndDvbSubtitle,
|
||||
KnownMimeTypes.Image.XCmuRaster,
|
||||
KnownMimeTypes.Image.Cgm,
|
||||
KnownMimeTypes.Image.XCmx,
|
||||
KnownMimeTypes.Image.VndDeceGraphic,
|
||||
KnownMimeTypes.Image.VndDjvu,
|
||||
KnownMimeTypes.Image.VndDwg,
|
||||
KnownMimeTypes.Image.VndFujixeroxEdmicsMmr,
|
||||
KnownMimeTypes.Image.VndFujixeroxEdmicsRlc,
|
||||
KnownMimeTypes.Image.VndXiff,
|
||||
KnownMimeTypes.Image.VndFst,
|
||||
KnownMimeTypes.Image.VndFastbidsheet,
|
||||
KnownMimeTypes.Image.VndFpx,
|
||||
KnownMimeTypes.Image.VndNetFpx,
|
||||
KnownMimeTypes.Image.XFreehand,
|
||||
KnownMimeTypes.Image.G3fax,
|
||||
KnownMimeTypes.Image.Gif,
|
||||
KnownMimeTypes.Image.XIcon,
|
||||
KnownMimeTypes.Image.Ief,
|
||||
KnownMimeTypes.Image.Jpeg,
|
||||
KnownMimeTypes.Image.XCitrixJpeg,
|
||||
KnownMimeTypes.Image.Pjpeg,
|
||||
KnownMimeTypes.Image.VndMsModi,
|
||||
KnownMimeTypes.Image.Ktx,
|
||||
KnownMimeTypes.Image.XPcx,
|
||||
KnownMimeTypes.Image.VndAdobePhotoshop,
|
||||
KnownMimeTypes.Image.XPict,
|
||||
KnownMimeTypes.Image.XPortableAnymap,
|
||||
KnownMimeTypes.Image.XPortableBitmap,
|
||||
KnownMimeTypes.Image.XPortableGraymap,
|
||||
KnownMimeTypes.Image.Png,
|
||||
KnownMimeTypes.Image.XCitrixPng,
|
||||
KnownMimeTypes.Image.XPng,
|
||||
KnownMimeTypes.Image.XPortablePixmap,
|
||||
KnownMimeTypes.Image.SvgXml,
|
||||
KnownMimeTypes.Image.XRgb,
|
||||
KnownMimeTypes.Image.Tiff,
|
||||
KnownMimeTypes.Image.VndWapWbmp,
|
||||
KnownMimeTypes.Image.Webp,
|
||||
KnownMimeTypes.Image.XXbitmap,
|
||||
KnownMimeTypes.Image.XXpixmap,
|
||||
KnownMimeTypes.Image.XXwindowdump,
|
||||
KnownMimeTypes.Message.Rfc822,
|
||||
KnownMimeTypes.Model.VndDwf,
|
||||
KnownMimeTypes.Model.VndColladaXml,
|
||||
KnownMimeTypes.Model.VndGtw,
|
||||
KnownMimeTypes.Model.VndGdl,
|
||||
KnownMimeTypes.Model.Iges,
|
||||
KnownMimeTypes.Model.Mesh,
|
||||
KnownMimeTypes.Model.Vrml,
|
||||
KnownMimeTypes.Model.VndMts,
|
||||
KnownMimeTypes.Model.VndVtu,
|
||||
KnownMimeTypes.Text.XAsm,
|
||||
KnownMimeTypes.Text.PlainBas,
|
||||
KnownMimeTypes.Text.XC,
|
||||
KnownMimeTypes.Text.Css,
|
||||
KnownMimeTypes.Text.Csv,
|
||||
KnownMimeTypes.Text.VndCurl,
|
||||
KnownMimeTypes.Text.VndCurlDcurl,
|
||||
KnownMimeTypes.Text.VndCurlMcurl,
|
||||
KnownMimeTypes.Text.VndCurlScurl,
|
||||
KnownMimeTypes.Text.VndFmiFlexstor,
|
||||
KnownMimeTypes.Text.XFortran,
|
||||
KnownMimeTypes.Text.VndGraphviz,
|
||||
KnownMimeTypes.Text.Html,
|
||||
KnownMimeTypes.Text.Calendar,
|
||||
KnownMimeTypes.Text.VndIn3d3dml,
|
||||
KnownMimeTypes.Text.VndIn3dSpot,
|
||||
KnownMimeTypes.Text.VndSunJ2meAppDescriptor,
|
||||
KnownMimeTypes.Text.XJavaSourceJava,
|
||||
KnownMimeTypes.Text.Javascript,
|
||||
KnownMimeTypes.Text.VndFly,
|
||||
KnownMimeTypes.Text.N3,
|
||||
KnownMimeTypes.Text.XPascal,
|
||||
KnownMimeTypes.Text.PrsLinesTag,
|
||||
KnownMimeTypes.Text.Richtext,
|
||||
KnownMimeTypes.Text.XSetext,
|
||||
KnownMimeTypes.Text.Sgml,
|
||||
KnownMimeTypes.Text.TabSeparatedValues,
|
||||
KnownMimeTypes.Text.Plain,
|
||||
KnownMimeTypes.Text.Troff,
|
||||
KnownMimeTypes.Text.Turtle,
|
||||
KnownMimeTypes.Text.UriList,
|
||||
KnownMimeTypes.Text.XUuencode,
|
||||
KnownMimeTypes.Text.XVcalendar,
|
||||
KnownMimeTypes.Text.XVcard,
|
||||
KnownMimeTypes.Text.VndWapWml,
|
||||
KnownMimeTypes.Text.VndWapWmlscript,
|
||||
KnownMimeTypes.Text.Yaml,
|
||||
KnownMimeTypes.Video.V3gpp,
|
||||
KnownMimeTypes.Video.V3gpp2,
|
||||
KnownMimeTypes.Video.XMsvideo,
|
||||
KnownMimeTypes.Video.VndDeceHd,
|
||||
KnownMimeTypes.Video.VndDeceMobile,
|
||||
KnownMimeTypes.Video.VndUvvuMp4,
|
||||
KnownMimeTypes.Video.VndDecePd,
|
||||
KnownMimeTypes.Video.VndDeceSd,
|
||||
KnownMimeTypes.Video.VndDeceVideo,
|
||||
KnownMimeTypes.Video.VndFvt,
|
||||
KnownMimeTypes.Video.XF4v,
|
||||
KnownMimeTypes.Video.XFlv,
|
||||
KnownMimeTypes.Video.XFli,
|
||||
KnownMimeTypes.Video.H261,
|
||||
KnownMimeTypes.Video.H263,
|
||||
KnownMimeTypes.Video.H264,
|
||||
KnownMimeTypes.Video.Jpm,
|
||||
KnownMimeTypes.Video.Jpeg,
|
||||
KnownMimeTypes.Video.XM4v,
|
||||
KnownMimeTypes.Video.XMsAsf,
|
||||
KnownMimeTypes.Video.VndMsPlayreadyMediaPyv,
|
||||
KnownMimeTypes.Video.XMsWm,
|
||||
KnownMimeTypes.Video.XMsWmx,
|
||||
KnownMimeTypes.Video.XMsWmv,
|
||||
KnownMimeTypes.Video.XMsWvx,
|
||||
KnownMimeTypes.Video.Mj2,
|
||||
KnownMimeTypes.Video.Mp2t,
|
||||
KnownMimeTypes.Video.VndMpegurl,
|
||||
KnownMimeTypes.Video.Mpeg,
|
||||
KnownMimeTypes.Video.Mp4,
|
||||
KnownMimeTypes.Video.Ogg,
|
||||
KnownMimeTypes.Video.Webm,
|
||||
KnownMimeTypes.Video.Quicktime,
|
||||
KnownMimeTypes.Video.XSgiMovie,
|
||||
KnownMimeTypes.Video.VndVivo,
|
||||
KnownMimeTypes.XConference.XCooltalk,
|
||||
)
|
@@ -0,0 +1,8 @@
|
||||
package dev.inmo.micro_utils.mime_types
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable(MimeTypeSerializer::class)
|
||||
interface MimeType {
|
||||
val raw: String
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package dev.inmo.micro_utils.mime_types
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
@Suppress("OPT_IN_USAGE")
|
||||
@Serializer(MimeType::class)
|
||||
object MimeTypeSerializer : KSerializer<MimeType> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): MimeType {
|
||||
val mimeType = decoder.decodeString()
|
||||
return mimeType(mimeType)
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: MimeType) {
|
||||
encoder.encodeString(value.raw)
|
||||
}
|
||||
}
|
@@ -1,10 +1,5 @@
|
||||
package dev.inmo.micro_utils.mime_types
|
||||
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.descriptors.*
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
private val mimesCache = mutableMapOf<String, MimeType>().also {
|
||||
knownMimeTypes.forEach { mimeType -> it[mimeType.raw] = mimeType }
|
||||
}
|
||||
@@ -15,17 +10,3 @@ fun mimeType(raw: String) = mimesCache.getOrPut(raw) {
|
||||
|
||||
internal fun parseMimeType(raw: String): MimeType = CustomMimeType(raw)
|
||||
|
||||
@Suppress("OPT_IN_USAGE")
|
||||
@Serializer(MimeType::class)
|
||||
object MimeTypeSerializer : KSerializer<MimeType> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("mimeType", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): MimeType {
|
||||
val mimeType = decoder.decodeString()
|
||||
return mimeType(mimeType)
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: MimeType) {
|
||||
encoder.encodeString(value.raw)
|
||||
}
|
||||
}
|
||||
|
49
mppJsAndJavaProject.gradle
Normal file
49
mppJsAndJavaProject.gradle
Normal file
@@ -0,0 +1,49 @@
|
||||
project.version = "$version"
|
||||
project.group = "$group"
|
||||
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.main {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
js (IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-common')
|
||||
implementation kotlin('test-annotations-common')
|
||||
}
|
||||
}
|
||||
|
||||
jvmTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-junit')
|
||||
}
|
||||
}
|
||||
jsTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-js')
|
||||
implementation kotlin('test-junit')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
@@ -23,7 +23,7 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
implementation libs.kt.serialization
|
||||
api libs.kt.serialization
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
|
@@ -23,7 +23,7 @@ kotlin {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
implementation libs.kt.serialization
|
||||
api libs.kt.serialization
|
||||
implementation compose.runtime
|
||||
}
|
||||
}
|
||||
|
@@ -33,18 +33,6 @@ data class PaginationResult<T>(
|
||||
results,
|
||||
(pagesNumber * size).toLong()
|
||||
)
|
||||
@Deprecated("Replace with The other order of incoming parameters or objectsCount parameter")
|
||||
constructor(
|
||||
page: Int,
|
||||
pagesNumber: Int,
|
||||
results: List<T>,
|
||||
size: Int
|
||||
) : this(
|
||||
page,
|
||||
results,
|
||||
pagesNumber,
|
||||
size
|
||||
)
|
||||
}
|
||||
|
||||
fun <T> emptyPaginationResult() = PaginationResult<T>(0, 0, emptyList(), 0L)
|
||||
|
@@ -19,29 +19,29 @@ publishing {
|
||||
}
|
||||
|
||||
developers {
|
||||
|
||||
|
||||
developer {
|
||||
id = "InsanusMokrassar"
|
||||
name = "Aleksei Ovsiannikov"
|
||||
email = "ovsyannikov.alexey95@gmail.com"
|
||||
}
|
||||
|
||||
|
||||
|
||||
developer {
|
||||
id = "000Sanya"
|
||||
name = "Syrov Aleksandr"
|
||||
email = "000sanya.000sanya@gmail.com"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
licenses {
|
||||
|
||||
|
||||
license {
|
||||
name = "Apache Software License 2.0"
|
||||
url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
@@ -49,35 +49,54 @@ publishing {
|
||||
maven {
|
||||
name = "GithubPackages"
|
||||
url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils")
|
||||
|
||||
credentials {
|
||||
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
|
||||
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
|
||||
maven {
|
||||
name = "Gitea"
|
||||
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
|
||||
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = "Authorization"
|
||||
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
|
||||
}
|
||||
|
||||
authentication {
|
||||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
|
||||
credentials {
|
||||
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (project.hasProperty("signing.gnupg.keyName")) {
|
||||
apply plugin: 'signing'
|
||||
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
|
||||
|
||||
sign publishing.publications
|
||||
}
|
||||
|
||||
|
||||
task signAll {
|
||||
tasks.withType(Sign).forEach {
|
||||
dependsOn(it)
|
||||
|
@@ -1 +1 @@
|
||||
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
|
||||
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
|
@@ -46,6 +46,12 @@ open class FullReadCRUDCacheRepo<ObjectType, IdType>(
|
||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = doOrTakeAndActualize(
|
||||
{ keys(pagination).takeIf { it.results.isNotEmpty() }.optionalOrAbsentIfNull },
|
||||
{ getIdsByPagination(pagination) },
|
||||
{ if (it.results.isNotEmpty()) actualizeAll() }
|
||||
)
|
||||
|
||||
override suspend fun count(): Long = doOrTakeAndActualize(
|
||||
{ count().takeIf { it != 0L }.optionalOrAbsentIfNull },
|
||||
{ count() },
|
||||
|
@@ -28,6 +28,7 @@ kotlin {
|
||||
api internalProject("micro_utils.common")
|
||||
api internalProject("micro_utils.coroutines")
|
||||
}
|
||||
dependsOn jvmMain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,138 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Read part of [KeyValueRepo]
|
||||
*
|
||||
* @param Key This type will be used as key in all operations related to searches of data
|
||||
* @param Value This type will be used as returning data in most "get" operations
|
||||
*/
|
||||
interface ReadKeyValueRepo<Key, Value> : Repo {
|
||||
/**
|
||||
* @return Result [Value] in case when it is presented in repo by its [k] or null otherwise
|
||||
*/
|
||||
suspend fun get(k: Key): Value?
|
||||
|
||||
/**
|
||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
||||
* ascending sort for [Key]s
|
||||
*/
|
||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
|
||||
/**
|
||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
||||
* ascending sort for [Key]s
|
||||
*/
|
||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
|
||||
/**
|
||||
* This method should use sorted by [Key]s search and take the [PaginationResult]. By default, it should use
|
||||
* ascending sort for [Key]s
|
||||
*
|
||||
* @param v This value should be used to exclude from search the items with different [Value]s
|
||||
*/
|
||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
|
||||
/**
|
||||
* @return true if [key] is presented in current collection or false otherwise
|
||||
*/
|
||||
suspend fun contains(key: Key): Boolean
|
||||
|
||||
/**
|
||||
* @return count of all collection objects
|
||||
*/
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value>
|
||||
|
||||
/**
|
||||
* Write part of [KeyValueRepo]
|
||||
*
|
||||
* @param Key This type will be used as key in all operations related to changes of data
|
||||
* @param Value This type will be used as incoming data in most operations
|
||||
*/
|
||||
interface WriteKeyValueRepo<Key, Value> : Repo {
|
||||
/**
|
||||
* This flow must emit data each time when data by [Key] has been changed with [set] method or in any other way
|
||||
* excluding cases of data removing
|
||||
*
|
||||
* @see onValueRemoved
|
||||
*/
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
|
||||
/**
|
||||
* This flow must emit data each time when data by [Key] has been removed with [unset]/[unsetWithValues] methods or
|
||||
* in any other way
|
||||
*
|
||||
* @see onNewValue
|
||||
*/
|
||||
val onValueRemoved: Flow<Key>
|
||||
|
||||
/**
|
||||
* Will set as batch [toSet] data in current repo. Must pass the data which were successfully updated in repo to
|
||||
* [onNewValue]
|
||||
*/
|
||||
suspend fun set(toSet: Map<Key, Value>)
|
||||
/**
|
||||
* Will unset as batch data with keys from [toUnset]. Must pass the [Key]s which were successfully removed in repo to
|
||||
* [onValueRemoved]
|
||||
*/
|
||||
suspend fun unset(toUnset: List<Key>)
|
||||
/**
|
||||
* Will unset as batch data with values from [toUnset]. Must pass the [Key]s which were successfully removed in repo
|
||||
* to [onValueRemoved]
|
||||
*/
|
||||
suspend fun unsetWithValues(toUnset: List<Value>)
|
||||
}
|
||||
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
vararg toSet: Pair<Key, Value>
|
||||
) = set(toSet.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
k: Key, v: Value
|
||||
) = set(k to v)
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
|
||||
vararg k: Key
|
||||
) = unset(k.toList())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
|
||||
vararg v: Value
|
||||
) = unsetWithValues(v.toList())
|
||||
|
||||
/**
|
||||
* Full version of standard key-value repository with all set/unset/clear/get methods
|
||||
*/
|
||||
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> {
|
||||
/**
|
||||
* By default, will walk throw all the [keys] with [Value]s from [toUnset] and run [doAllWithCurrentPaging] with
|
||||
* [unset] of found data [Key]s
|
||||
*/
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
||||
doAllWithCurrentPaging {
|
||||
keys(v, it).also {
|
||||
unset(it.results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* By default, will remove all the data of current repo using [doAllWithCurrentPaging], [keys] and [unset]
|
||||
*/
|
||||
suspend fun clear() {
|
||||
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
|
||||
}
|
||||
}
|
||||
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
|
||||
|
||||
class DelegateBasedKeyValueRepo<Key, Value>(
|
||||
readDelegate: ReadKeyValueRepo<Key, Value>,
|
||||
writeDelegate: WriteKeyValueRepo<Key, Value>
|
||||
) : KeyValueRepo<Key, Value>,
|
||||
ReadKeyValueRepo<Key, Value> by readDelegate,
|
||||
WriteKeyValueRepo<Key, Value> by writeDelegate
|
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadCRUDRepo<ObjectType, IdType> : Repo {
|
||||
suspend fun getByPagination(pagination: Pagination): PaginationResult<ObjectType>
|
||||
suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType>
|
||||
suspend fun getById(id: IdType): ObjectType?
|
||||
suspend fun contains(id: IdType): Boolean
|
||||
suspend fun count(): Long
|
||||
|
@@ -1,63 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.pagination.utils.doAllWithCurrentPaging
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface ReadKeyValueRepo<Key, Value> : Repo {
|
||||
suspend fun get(k: Key): Value?
|
||||
suspend fun values(pagination: Pagination, reversed: Boolean = false): PaginationResult<Value>
|
||||
suspend fun keys(pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean = false): PaginationResult<Key>
|
||||
suspend fun contains(key: Key): Boolean
|
||||
suspend fun count(): Long
|
||||
}
|
||||
typealias ReadStandardKeyValueRepo<Key,Value> = ReadKeyValueRepo<Key, Value>
|
||||
|
||||
interface WriteKeyValueRepo<Key, Value> : Repo {
|
||||
val onNewValue: Flow<Pair<Key, Value>>
|
||||
val onValueRemoved: Flow<Key>
|
||||
|
||||
suspend fun set(toSet: Map<Key, Value>)
|
||||
suspend fun unset(toUnset: List<Key>)
|
||||
suspend fun unsetWithValues(toUnset: List<Value>)
|
||||
}
|
||||
typealias WriteStandardKeyValueRepo<Key,Value> = WriteKeyValueRepo<Key, Value>
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
vararg toSet: Pair<Key, Value>
|
||||
) = set(toSet.toMap())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.set(
|
||||
k: Key, v: Value
|
||||
) = set(k to v)
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unset(
|
||||
vararg k: Key
|
||||
) = unset(k.toList())
|
||||
|
||||
suspend inline fun <Key, Value> WriteKeyValueRepo<Key, Value>.unsetWithValues(
|
||||
vararg v: Value
|
||||
) = unsetWithValues(v.toList())
|
||||
|
||||
interface KeyValueRepo<Key, Value> : ReadKeyValueRepo<Key, Value>, WriteKeyValueRepo<Key, Value> {
|
||||
override suspend fun unsetWithValues(toUnset: List<Value>) = toUnset.forEach { v ->
|
||||
doAllWithCurrentPaging {
|
||||
keys(v, it).also {
|
||||
unset(it.results)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clear() {
|
||||
doAllWithCurrentPaging { keys(it).also { unset(it.results) } }
|
||||
}
|
||||
}
|
||||
typealias StandardKeyValueRepo<Key,Value> = KeyValueRepo<Key, Value>
|
||||
|
||||
class DelegateBasedKeyValueRepo<Key, Value>(
|
||||
readDelegate: ReadKeyValueRepo<Key, Value>,
|
||||
writeDelegate: WriteKeyValueRepo<Key, Value>
|
||||
) : KeyValueRepo<Key, Value>,
|
||||
ReadKeyValueRepo<Key, Value> by readDelegate,
|
||||
WriteKeyValueRepo<Key, Value> by writeDelegate
|
@@ -20,6 +20,14 @@ open class MapperReadCRUDRepo<FromId, FromRegistered, ToId, ToRegistered>(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<FromId> = to.getIdsByPagination(
|
||||
pagination
|
||||
).let {
|
||||
it.changeResultsUnchecked(
|
||||
it.results.map { it.toInnerKey() }
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun count(): Long = to.count()
|
||||
|
||||
override suspend fun contains(id: FromId): Boolean = to.contains(id.toOutKey())
|
||||
|
@@ -16,6 +16,7 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
|
||||
protected abstract val tableName: String
|
||||
protected abstract val idColumnName: String
|
||||
protected abstract suspend fun Cursor.toObject(): ObjectType
|
||||
protected abstract suspend fun Cursor.toId(): IdType
|
||||
protected fun SQLiteDatabase.count(): Long = select(tableName).use {
|
||||
it.count
|
||||
}.toLong()
|
||||
@@ -64,4 +65,23 @@ abstract class AbstractAndroidCRUDRepo<ObjectType, IdType>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> {
|
||||
return helper.readableTransaction {
|
||||
select(
|
||||
tableName,
|
||||
limit = pagination.limitClause()
|
||||
).use {
|
||||
if (it.moveToFirst()) {
|
||||
val resultList = mutableListOf(it.toId())
|
||||
while (it.moveToNext()) {
|
||||
resultList.add(it.toId())
|
||||
}
|
||||
resultList.createPaginationResult(pagination, count())
|
||||
} else {
|
||||
emptyList<IdType>().createPaginationResult(pagination, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -22,6 +22,17 @@ abstract class AbstractExposedReadCRUDRepo<ObjectType, IdType>(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> {
|
||||
return transaction(db = database) {
|
||||
selectAll().paginate(pagination).map {
|
||||
it.asId
|
||||
}.createPaginationResult(
|
||||
pagination,
|
||||
selectAll().count()
|
||||
)
|
||||
}
|
||||
}
|
||||
override suspend fun getById(id: IdType): ObjectType? {
|
||||
return transaction(db = database) {
|
||||
select {
|
||||
|
@@ -4,9 +4,9 @@ import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
||||
import org.jetbrains.exposed.sql.statements.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.util.Objects
|
||||
|
||||
abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
flowsChannelsSize: Int = 0,
|
||||
@@ -27,10 +27,39 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
|
||||
protected abstract fun InsertStatement<Number>.asObject(value: InputValueType): ObjectType
|
||||
|
||||
protected abstract fun insert(value: InputValueType, it: InsertStatement<Number>)
|
||||
protected abstract fun update(id: IdType, value: InputValueType, it: UpdateStatement)
|
||||
/**
|
||||
* @param id Can be null only if [createAndInsertId] have returned null (it can be useful when you have
|
||||
* autoincrement identifier)
|
||||
* @param it Will be [UpdateStatement] when it is called from [update] method or [InsertStatement] from the [create]
|
||||
* one. Anyway, it is main method where you should put the logic of table filling by [value] data
|
||||
*
|
||||
* @see createAndInsertId
|
||||
*/
|
||||
protected abstract fun update(id: IdType?, value: InputValueType, it: UpdateBuilder<Int>)
|
||||
|
||||
/**
|
||||
* Override this method to interact with [it] ([InsertStatement]) and put there new id with [IdType].
|
||||
*
|
||||
* By default, have null value due to the fact that in the most cases users have [autoIncrement]ing id columns
|
||||
*
|
||||
* @return In case when id for the model has been created new [IdType] should be returned
|
||||
*/
|
||||
protected open fun createAndInsertId(value: InputValueType, it: InsertStatement<Number>): IdType? = null
|
||||
|
||||
protected open fun insert(value: InputValueType, it: InsertStatement<Number>) {
|
||||
val id = createAndInsertId(value, it)
|
||||
update(id, value, it as UpdateBuilder<Int>)
|
||||
}
|
||||
|
||||
protected open suspend fun onBeforeCreate(value: List<InputValueType>) {}
|
||||
|
||||
/**
|
||||
* Use this method to do the something with [values]. You may change and output values in that list and return
|
||||
* changed list of pairs
|
||||
*/
|
||||
protected open suspend fun onAfterCreate(
|
||||
values: List<Pair<InputValueType, ObjectType>>
|
||||
): List<ObjectType> = values.map { it.second }
|
||||
private fun createWithoutNotification(value: InputValueType): ObjectType {
|
||||
return transaction(database) {
|
||||
insert { insert(value, it) }.asObject(value)
|
||||
@@ -40,13 +69,18 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
override suspend fun create(values: List<InputValueType>): List<ObjectType> {
|
||||
onBeforeCreate(values)
|
||||
return transaction(db = database) {
|
||||
values.map { value -> createWithoutNotification(value) }
|
||||
values.map { value -> value to createWithoutNotification(value) }
|
||||
}.let {
|
||||
onAfterCreate(it)
|
||||
}.onEach {
|
||||
_newObjectsFlow.emit(it)
|
||||
}
|
||||
}
|
||||
|
||||
protected open suspend fun onBeforeUpdate(value: List<UpdatedValuePair<IdType, InputValueType>>) {}
|
||||
protected open suspend fun onAfterUpdate(
|
||||
value: List<UpdatedValuePair<InputValueType, ObjectType>>
|
||||
): List<ObjectType> = value.map { it.second }
|
||||
private fun updateWithoutNotification(id: IdType, value: InputValueType): ObjectType? {
|
||||
return transaction(db = database) {
|
||||
update(
|
||||
@@ -54,7 +88,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
selectById(this, id)
|
||||
}
|
||||
) {
|
||||
update(id, value, it)
|
||||
update(id, value, it as UpdateBuilder<Int>)
|
||||
}
|
||||
}.let {
|
||||
if (it > 0) {
|
||||
@@ -71,7 +105,9 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
|
||||
override suspend fun update(id: IdType, value: InputValueType): ObjectType? {
|
||||
onBeforeUpdate(listOf(id to value))
|
||||
return updateWithoutNotification(id, value).also {
|
||||
return updateWithoutNotification(id, value).let {
|
||||
onAfterUpdate(listOf(value to (it ?: return@let emptyList())))
|
||||
}.firstOrNull().also {
|
||||
if (it != null) {
|
||||
_updatedObjectsFlow.emit(it)
|
||||
}
|
||||
@@ -81,9 +117,11 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
onBeforeUpdate(values)
|
||||
return (
|
||||
transaction(db = database) {
|
||||
values.map { (id, value) -> updateWithoutNotification(id, value) }
|
||||
}.filterNotNull()
|
||||
).onEach {
|
||||
values.mapNotNull { (id, value) -> value to (updateWithoutNotification(id, value) ?: return@mapNotNull null) }
|
||||
}
|
||||
).let {
|
||||
onAfterUpdate(it)
|
||||
}.onEach {
|
||||
_updatedObjectsFlow.emit(it)
|
||||
}
|
||||
}
|
||||
@@ -91,9 +129,7 @@ abstract class AbstractExposedWriteCRUDRepo<ObjectType, IdType, InputValueType>(
|
||||
override suspend fun deleteById(ids: List<IdType>) {
|
||||
onBeforeDelete(ids)
|
||||
transaction(db = database) {
|
||||
val deleted = deleteWhere(null, null) {
|
||||
selectByIds(ids)
|
||||
}
|
||||
val deleted = deleteWhere(null, null) { selectByIds(it, ids) }
|
||||
if (deleted == ids.size) {
|
||||
ids
|
||||
} else {
|
||||
|
@@ -4,8 +4,9 @@ import org.jetbrains.exposed.sql.*
|
||||
|
||||
interface CommonExposedRepo<IdType, ObjectType> : ExposedRepo {
|
||||
val ResultRow.asObject: ObjectType
|
||||
val selectById: SqlExpressionBuilder.(IdType) -> Op<Boolean>
|
||||
val selectByIds: SqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
||||
val ResultRow.asId: IdType
|
||||
val selectById: ISqlExpressionBuilder.(IdType) -> Op<Boolean>
|
||||
val selectByIds: ISqlExpressionBuilder.(List<IdType>) -> Op<Boolean>
|
||||
get() = { list ->
|
||||
if (list.isEmpty()) {
|
||||
Op.FALSE
|
||||
|
@@ -3,8 +3,7 @@ package dev.inmo.micro_utils.repos.exposed.keyvalue
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
||||
import org.jetbrains.exposed.sql.statements.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
abstract class AbstractExposedKeyValueRepo<Key, Value>(
|
||||
@@ -20,13 +19,18 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
|
||||
override val onNewValue: Flow<Pair<Key, Value>> = _onNewValue.asSharedFlow()
|
||||
override val onValueRemoved: Flow<Key> = _onValueRemoved.asSharedFlow()
|
||||
|
||||
protected abstract fun update(k: Key, v: Value, it: UpdateStatement)
|
||||
protected abstract fun insert(k: Key, v: Value, it: InsertStatement<Number>)
|
||||
protected abstract fun update(k: Key, v: Value, it: UpdateBuilder<Int>)
|
||||
protected abstract fun insertKey(k: Key, v: Value, it: InsertStatement<Number>)
|
||||
|
||||
protected open fun insert(k: Key, v: Value, it: InsertStatement<Number>) {
|
||||
insertKey(k, v, it)
|
||||
update(k, v, it as UpdateBuilder<Int>)
|
||||
}
|
||||
|
||||
override suspend fun set(toSet: Map<Key, Value>) {
|
||||
transaction(database) {
|
||||
toSet.mapNotNull { (k, v) ->
|
||||
if (update({ selectById(k) }) { update(k, v, it) } > 0) {
|
||||
if (update({ selectById(k) }) { update(k, v, it as UpdateBuilder<Int>) } > 0) {
|
||||
k to v
|
||||
} else {
|
||||
val inserted = insert {
|
||||
@@ -46,9 +50,9 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
|
||||
|
||||
override suspend fun unset(toUnset: List<Key>) {
|
||||
transaction(database) {
|
||||
toUnset.mapNotNull {
|
||||
if (deleteWhere { selectById(it) } > 0) {
|
||||
it
|
||||
toUnset.mapNotNull { item ->
|
||||
if (deleteWhere { selectById(it, item) } > 0) {
|
||||
item
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -62,7 +66,7 @@ abstract class AbstractExposedKeyValueRepo<Key, Value>(
|
||||
transaction(database) {
|
||||
toUnset.flatMap {
|
||||
val keys = select { selectByValue(it) }.mapNotNull { it.asKey }
|
||||
deleteWhere { selectByIds(keys) }
|
||||
deleteWhere { selectByIds(it, keys) }
|
||||
keys
|
||||
}
|
||||
}.distinct().forEach {
|
||||
|
@@ -15,8 +15,14 @@ abstract class AbstractExposedReadKeyValueRepo<Key, Value>(
|
||||
CommonExposedRepo<Key, Value>,
|
||||
Table(tableName ?: "") {
|
||||
abstract val keyColumn: Column<*>
|
||||
|
||||
/**
|
||||
* Same as [asId] in context of KeyValue repo
|
||||
*/
|
||||
abstract val ResultRow.asKey: Key
|
||||
abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean>
|
||||
override val ResultRow.asId: Key
|
||||
get() = asKey
|
||||
abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean>
|
||||
|
||||
override suspend fun get(k: Key): Value? = transaction(database) {
|
||||
select { selectById(k) }.limit(1).firstOrNull() ?.asObject
|
||||
|
@@ -4,6 +4,8 @@ import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
open class ExposedKeyValueRepo<Key, Value>(
|
||||
@@ -47,9 +49,9 @@ open class ExposedKeyValueRepo<Key, Value>(
|
||||
|
||||
override suspend fun unset(toUnset: List<Key>) {
|
||||
transaction(database) {
|
||||
toUnset.mapNotNull {
|
||||
if (deleteWhere { keyColumn.eq(it) } > 0) {
|
||||
it
|
||||
toUnset.mapNotNull { item ->
|
||||
if (deleteWhere { keyColumn.eq(item) } > 0) {
|
||||
item
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@@ -5,57 +5,27 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.*
|
||||
import dev.inmo.micro_utils.repos.exposed.utils.selectPaginated
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import org.jetbrains.exposed.sql.statements.UpdateBuilder
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
open class ExposedReadKeyValueRepo<Key, Value>(
|
||||
override val database: Database,
|
||||
database: Database,
|
||||
keyColumnAllocator: ColumnAllocator<Key>,
|
||||
valueColumnAllocator: ColumnAllocator<Value>,
|
||||
tableName: String? = null
|
||||
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
) : ReadKeyValueRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValueRepo<Key, Value>(database, tableName) {
|
||||
|
||||
override val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||
override val primaryKey: PrimaryKey = PrimaryKey(keyColumn, valueColumn)
|
||||
override val ResultRow.asKey: Key
|
||||
get() = get(keyColumn)
|
||||
override val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
|
||||
override val ResultRow.asObject: Value
|
||||
get() = get(valueColumn)
|
||||
override val selectById: ISqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
|
||||
override val primaryKey: Table.PrimaryKey
|
||||
get() = PrimaryKey(keyColumn, valueColumn)
|
||||
|
||||
init { initTable() }
|
||||
|
||||
override suspend fun get(k: Key): Value? = transaction(database) {
|
||||
select { keyColumn.eq(k) }.limit(1).firstOrNull() ?.getOrNull(valueColumn)
|
||||
}
|
||||
|
||||
override suspend fun contains(key: Key): Boolean = transaction(database) {
|
||||
select { keyColumn.eq(key) }.limit(1).any()
|
||||
}
|
||||
|
||||
override suspend fun count(): Long = transaction(database) { selectAll().count() }
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||
selectAll().selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it[keyColumn]
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun keys(v: Value, pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||
select { valueColumn.eq(v) }.selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it[keyColumn]
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun values(pagination: Pagination, reversed: Boolean): PaginationResult<Value> = transaction(database) {
|
||||
selectAll().selectPaginated(
|
||||
pagination,
|
||||
keyColumn,
|
||||
reversed
|
||||
) {
|
||||
it[valueColumn]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -49,7 +49,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>(
|
||||
transaction(database) {
|
||||
toRemove.keys.flatMap { k ->
|
||||
toRemove[k] ?.mapNotNull { v ->
|
||||
if (deleteWhere { selectById(k).and(selectByValue(v)) } > 0 ) {
|
||||
if (deleteWhere { selectById(it, k).and(SqlExpressionBuilder.selectByValue(v)) } > 0 ) {
|
||||
k to v
|
||||
} else {
|
||||
null
|
||||
@@ -63,7 +63,7 @@ abstract class AbstractExposedKeyValuesRepo<Key, Value>(
|
||||
|
||||
override suspend fun clear(k: Key) {
|
||||
transaction(database) {
|
||||
deleteWhere { selectById(k) }
|
||||
deleteWhere { selectById(it, k) }
|
||||
}.also { _onDataCleared.emit(k) }
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,9 @@ abstract class AbstractExposedReadKeyValuesRepo<Key, Value>(
|
||||
Table(tableName ?: "") {
|
||||
abstract val keyColumn: Column<*>
|
||||
abstract val ResultRow.asKey: Key
|
||||
abstract val selectByValue: SqlExpressionBuilder.(Value) -> Op<Boolean>
|
||||
override val ResultRow.asId: Key
|
||||
get() = asKey
|
||||
abstract val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean>
|
||||
|
||||
override suspend fun count(k: Key): Long = transaction(database) { select { selectById(k) }.count() }
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.ColumnAllocator
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
typealias ExposedOneToManyKeyValueRepo1<Key, Value> = ExposedKeyValuesRepo<Key, Value>
|
||||
|
@@ -1,10 +1,8 @@
|
||||
package dev.inmo.micro_utils.repos.exposed.onetomany
|
||||
|
||||
import dev.inmo.micro_utils.pagination.*
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.exposed.*
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
typealias ExposedReadOneToManyKeyValueRepo<Key, Value> = ExposedReadKeyValuesRepo<Key, Value>
|
||||
|
||||
@@ -13,54 +11,15 @@ open class ExposedReadKeyValuesRepo<Key, Value>(
|
||||
keyColumnAllocator: ColumnAllocator<Key>,
|
||||
valueColumnAllocator: ColumnAllocator<Value>,
|
||||
tableName: String? = null
|
||||
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, Table(tableName ?: "") {
|
||||
val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
) : ReadKeyValuesRepo<Key, Value>, ExposedRepo, AbstractExposedReadKeyValuesRepo<Key, Value>(database, tableName) {
|
||||
override val keyColumn: Column<Key> = keyColumnAllocator()
|
||||
override val ResultRow.asKey: Key
|
||||
get() = get(keyColumn)
|
||||
override val selectByValue: ISqlExpressionBuilder.(Value) -> Op<Boolean> = { valueColumn.eq(it) }
|
||||
override val ResultRow.asObject: Value
|
||||
get() = get(valueColumn)
|
||||
override val selectById: ISqlExpressionBuilder.(Key) -> Op<Boolean> = { keyColumn.eq(it) }
|
||||
val valueColumn: Column<Value> = valueColumnAllocator()
|
||||
|
||||
init { initTable() }
|
||||
|
||||
override suspend fun count(k: Key): Long = transaction(database) { select { keyColumn.eq(k) }.count() }
|
||||
|
||||
override suspend fun count(): Long = transaction(database) { selectAll().count() }
|
||||
|
||||
override suspend fun get(
|
||||
k: Key,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Value> = transaction(database) {
|
||||
select { keyColumn.eq(k) }.paginate(pagination, keyColumn, reversed).map { it[valueColumn] }
|
||||
}.createPaginationResult(
|
||||
pagination,
|
||||
count(k)
|
||||
)
|
||||
|
||||
override suspend fun keys(pagination: Pagination, reversed: Boolean): PaginationResult<Key> = transaction(database) {
|
||||
selectAll().paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
|
||||
}.createPaginationResult(
|
||||
pagination,
|
||||
count()
|
||||
)
|
||||
|
||||
override suspend fun keys(
|
||||
v: Value,
|
||||
pagination: Pagination,
|
||||
reversed: Boolean
|
||||
): PaginationResult<Key> = transaction(database) {
|
||||
select { valueColumn.eq(v) }.let {
|
||||
it.count() to it.paginate(pagination, keyColumn, reversed).map { it[keyColumn] }
|
||||
}
|
||||
}.let { (count, list) ->
|
||||
list.createPaginationResult(
|
||||
pagination,
|
||||
count
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key): Boolean = transaction(database) {
|
||||
select { keyColumn.eq(k) }.limit(1).any()
|
||||
}
|
||||
|
||||
override suspend fun contains(k: Key, v: Value): Boolean = transaction(database) {
|
||||
select { keyColumn.eq(k).and(valueColumn.eq(v)) }.limit(1).any()
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,13 @@ class ReadMapCRUDRepo<ObjectType, IdType>(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> {
|
||||
return map.keys.drop(pagination.firstIndex).take(pagination.size).createPaginationResult(
|
||||
pagination,
|
||||
count()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getById(id: IdType): ObjectType? = map[id]
|
||||
|
||||
override suspend fun contains(id: IdType): Boolean = map.containsKey(id)
|
||||
|
@@ -28,6 +28,7 @@ class KtorCRUDRepoClient<ObjectType, IdType, InputValue> (
|
||||
httpClient,
|
||||
typeInfo<ObjectType>(),
|
||||
typeInfo<PaginationResult<ObjectType>>(),
|
||||
typeInfo<PaginationResult<IdType>>(),
|
||||
contentType,
|
||||
idSerializer
|
||||
),
|
||||
|
@@ -19,6 +19,7 @@ class KtorReadCRUDRepoClient<ObjectType, IdType> (
|
||||
private val httpClient: HttpClient,
|
||||
private val objectType: TypeInfo,
|
||||
private val paginationObjectType: TypeInfo,
|
||||
private val paginationIdType: TypeInfo,
|
||||
private val contentType: ContentType,
|
||||
private val idSerializer: suspend (IdType) -> String
|
||||
) : ReadCRUDRepo<ObjectType, IdType> {
|
||||
@@ -27,6 +28,11 @@ class KtorReadCRUDRepoClient<ObjectType, IdType> (
|
||||
) {
|
||||
contentType(contentType)
|
||||
}.body(paginationObjectType)
|
||||
override suspend fun getIdsByPagination(pagination: Pagination): PaginationResult<IdType> = httpClient.get(
|
||||
buildStandardUrl(baseUrl, getIdsByPaginationRouting, pagination.asUrlQueryParts)
|
||||
) {
|
||||
contentType(contentType)
|
||||
}.body(paginationIdType)
|
||||
|
||||
override suspend fun getById(id: IdType): ObjectType? = httpClient.get(
|
||||
buildStandardUrl(
|
||||
@@ -72,6 +78,7 @@ inline fun <reified ObjectType, IdType> KtorReadCRUDRepoClient(
|
||||
httpClient,
|
||||
typeInfo<ObjectType>(),
|
||||
typeInfo<PaginationResult<ObjectType>>(),
|
||||
typeInfo<PaginationResult<IdType>>(),
|
||||
contentType,
|
||||
idSerializer
|
||||
)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.common.crud
|
||||
|
||||
const val getByPaginationRouting = "getByPagination"
|
||||
const val getIdsByPaginationRouting = "getIdsByPagination"
|
||||
const val getByIdRouting = "getById"
|
||||
const val containsRouting = "contains"
|
||||
|
@@ -23,6 +23,11 @@ inline fun <reified ObjectType, reified IdType> Route.configureReadCRUDRepoRoute
|
||||
|
||||
call.respond(originalRepo.getByPagination(pagination))
|
||||
}
|
||||
get(getIdsByPaginationRouting) {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
|
||||
call.respond(originalRepo.getIdsByPagination(pagination))
|
||||
}
|
||||
|
||||
get(getByIdRouting) {
|
||||
val id = idDeserializer(
|
||||
|
@@ -1,83 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.extractPagination
|
||||
import dev.inmo.micro_utils.repos.ReadCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.countRouting
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.get
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes(
|
||||
originalRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val paginationResultSerializer = PaginationResult.serializer(objectsSerializer)
|
||||
|
||||
get(getByPaginationRouting) {
|
||||
unifiedRouter.apply {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
|
||||
unianswer(
|
||||
paginationResultSerializer,
|
||||
originalRepo.getByPagination(pagination)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(getByIdRouting) {
|
||||
unifiedRouter.apply {
|
||||
val id = decodeUrlQueryValueOrSendError(
|
||||
"id",
|
||||
idsSerializer
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
objectsNullableSerializer,
|
||||
originalRepo.getById(id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(containsRouting) {
|
||||
unifiedRouter.apply {
|
||||
val id = decodeUrlQueryValueOrSendError(
|
||||
"id",
|
||||
idsSerializer
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(id)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(countRouting) {
|
||||
unifiedRouter.apply {
|
||||
unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <ObjectType, IdType> Route.configureReadCRUDRepoRoutes(
|
||||
originalRepo: ReadCRUDRepo<ObjectType, IdType>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
@@ -1,39 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
||||
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
|
||||
import dev.inmo.micro_utils.repos.CRUDRepo
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.route
|
||||
import kotlinx.serialization.KSerializer
|
||||
|
||||
fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: CRUDRepo<ObjectType, IdType, InputValue>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
route(baseSubpart) {
|
||||
configureReadCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, idsSerializer, unifiedRouter)
|
||||
configureWriteCRUDRepoRoutes(originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, unifiedRouter)
|
||||
}
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType, InputValue> Route.configureCRUDRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: CRUDRepo<ObjectType, IdType, InputValue>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureCRUDRepoRoutes(
|
||||
baseSubpart, originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
||||
)
|
@@ -1,107 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.crud
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.repos.WriteCRUDRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.crud.*
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.post
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.*
|
||||
|
||||
fun <ObjectType, IdType, InputValue> Route.configureWriteCRUDRepoRoutes(
|
||||
originalRepo: WriteCRUDRepo<ObjectType, IdType, InputValue>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val listObjectsSerializer = ListSerializer(objectsSerializer)
|
||||
val listInputSerializer = ListSerializer(inputsSerializer)
|
||||
val listIdsSerializer = ListSerializer(idsSerializer)
|
||||
val inputUpdateSerializer = PairSerializer(
|
||||
idsSerializer,
|
||||
inputsSerializer
|
||||
)
|
||||
val listInputUpdateSerializer = ListSerializer(inputUpdateSerializer)
|
||||
|
||||
unifiedRouter.apply {
|
||||
includeWebsocketHandling(
|
||||
newObjectsFlowRouting,
|
||||
originalRepo.newObjectsFlow,
|
||||
objectsSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
updatedObjectsFlowRouting,
|
||||
originalRepo.updatedObjectsFlow,
|
||||
objectsSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
deletedObjectsIdsFlowRouting,
|
||||
originalRepo.deletedObjectsIdsFlow,
|
||||
idsSerializer
|
||||
)
|
||||
}
|
||||
|
||||
post(createRouting) {
|
||||
unifiedRouter.apply {
|
||||
unianswer(
|
||||
listObjectsSerializer,
|
||||
originalRepo.create(
|
||||
uniload(listInputSerializer)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(updateRouting) {
|
||||
unifiedRouter.apply {
|
||||
val (id, input) = uniload(inputUpdateSerializer)
|
||||
unianswer(
|
||||
objectsNullableSerializer,
|
||||
originalRepo.update(
|
||||
id, input
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(updateManyRouting) {
|
||||
unifiedRouter.apply {
|
||||
val updates = uniload(listInputUpdateSerializer)
|
||||
unianswer(
|
||||
listObjectsSerializer,
|
||||
originalRepo.update(
|
||||
updates
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(deleteByIdRouting) {
|
||||
unifiedRouter.apply {
|
||||
val ids = uniload(listIdsSerializer)
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.deleteById(
|
||||
ids
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <ObjectType, IdType, InputValue> Route.configureWriteCRUDRepoRoutes(
|
||||
originalRepo: WriteCRUDRepo<ObjectType, IdType, InputValue>,
|
||||
objectsSerializer: KSerializer<ObjectType>,
|
||||
objectsNullableSerializer: KSerializer<ObjectType?>,
|
||||
inputsSerializer: KSerializer<InputValue>,
|
||||
idsSerializer: KSerializer<IdType>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureWriteCRUDRepoRoutes(
|
||||
originalRepo, objectsSerializer, objectsNullableSerializer, inputsSerializer, idsSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
||||
)
|
@@ -1,46 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
||||
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.route
|
||||
import kotlinx.serialization.KSerializer
|
||||
|
||||
fun <K, V> Route.configureKeyValueRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: KeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
route(baseSubpart) {
|
||||
configureReadStandartKeyValueRepoRoutes(
|
||||
originalRepo,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
valueNullableSerializer,
|
||||
unifiedRouter
|
||||
)
|
||||
configureWriteKeyValueRepoRoutes(
|
||||
originalRepo,
|
||||
keySerializer,
|
||||
valueSerializer,
|
||||
unifiedRouter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun <K, V> Route.configureStandartKeyValueRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: KeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureKeyValueRepoRoutes(baseSubpart, originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
@@ -1,107 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.extractPagination
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.containsRoute
|
||||
import dev.inmo.micro_utils.repos.ktor.common.countRoute
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.keyParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.reversedParameterName
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.get
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
fun <K, V> Route.configureReadStandartKeyValueRepoRoutes (
|
||||
originalRepo: ReadKeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
get(getRoute) {
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
valueNullableSerializer,
|
||||
originalRepo.get(key)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(valuesRoute) {
|
||||
unifiedRouter.apply {
|
||||
val parination = call.request.queryParameters.extractPagination;
|
||||
val reversed = decodeUrlQueryValueOrSendError(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
PaginationResult.serializer(valueSerializer),
|
||||
originalRepo.values(parination, reversed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(keysRoute) {
|
||||
unifiedRouter.apply {
|
||||
val parination = call.request.queryParameters.extractPagination;
|
||||
val reversed = decodeUrlQueryValueOrSendError(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: return@get
|
||||
val value = decodeUrlQueryValue(valueParameterName, valueSerializer)
|
||||
|
||||
unianswer(
|
||||
PaginationResult.serializer(keySerializer),
|
||||
value?.let { originalRepo.keys(value, parination, reversed) } ?: originalRepo.keys(parination, reversed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(containsRoute) {
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(key)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(countRoute) {
|
||||
unifiedRouter.apply {
|
||||
unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <K, V> Route.configureReadStandartKeyValueRepoRoutes (
|
||||
originalRepo: ReadKeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
valueNullableSerializer: KSerializer<V?>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureReadStandartKeyValueRepoRoutes(
|
||||
originalRepo, keySerializer, valueSerializer, valueNullableSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
||||
)
|
@@ -1,70 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.key_value
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.repos.WriteKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.key_value.*
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.post
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.*
|
||||
|
||||
fun <K, V> Route.configureWriteKeyValueRepoRoutes (
|
||||
originalRepo: WriteKeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val keyValueMapSerializer = MapSerializer(keySerializer, valueSerializer)
|
||||
val keysListSerializer = ListSerializer(keySerializer)
|
||||
val valuesListSerializer = ListSerializer(valueSerializer)
|
||||
unifiedRouter.apply {
|
||||
includeWebsocketHandling(
|
||||
onNewValueRoute,
|
||||
originalRepo.onNewValue,
|
||||
PairSerializer(keySerializer, valueSerializer)
|
||||
)
|
||||
|
||||
includeWebsocketHandling(
|
||||
onValueRemovedRoute,
|
||||
originalRepo.onValueRemoved,
|
||||
keySerializer
|
||||
)
|
||||
}
|
||||
|
||||
post(setRoute) {
|
||||
unifiedRouter.apply {
|
||||
val toSet = uniload(
|
||||
keyValueMapSerializer
|
||||
)
|
||||
|
||||
unianswer(Unit.serializer(), originalRepo.set(toSet))
|
||||
}
|
||||
}
|
||||
|
||||
post(unsetRoute) {
|
||||
unifiedRouter.apply {
|
||||
val toUnset = uniload(keysListSerializer)
|
||||
|
||||
unianswer(Unit.serializer(), originalRepo.unset(toUnset))
|
||||
}
|
||||
}
|
||||
|
||||
post(unsetWithValuesRoute) {
|
||||
unifiedRouter.apply {
|
||||
val toUnset = uniload(valuesListSerializer)
|
||||
|
||||
unianswer(Unit.serializer(), originalRepo.unsetWithValues(toUnset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <K, V> Route.configureWriteStandartKeyValueRepoRoutes (
|
||||
originalRepo: WriteKeyValueRepo<K, V>,
|
||||
keySerializer: KSerializer<K>,
|
||||
valueSerializer: KSerializer<V>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
@@ -1,35 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.UnifiedRouter
|
||||
import dev.inmo.micro_utils.ktor.server.standardKtorSerialFormatContentType
|
||||
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.route
|
||||
import kotlinx.serialization.KSerializer
|
||||
|
||||
fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: KeyValuesRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
route(baseSubpart) {
|
||||
configureOneToManyReadKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, unifiedRouter)
|
||||
configureOneToManyWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, unifiedRouter)
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> Route.configureOneToManyKeyValueRepoRoutes(
|
||||
baseSubpart: String,
|
||||
originalRepo: KeyValuesRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureOneToManyKeyValueRepoRoutes(
|
||||
baseSubpart, originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType)
|
||||
)
|
@@ -1,129 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.pagination.PaginationResult
|
||||
import dev.inmo.micro_utils.pagination.extractPagination
|
||||
import dev.inmo.micro_utils.repos.ReadKeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.keyParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||
import dev.inmo.micro_utils.repos.ktor.common.valueParameterName
|
||||
import dev.inmo.micro_utils.repos.ktor.common.reversedParameterName
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.get
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
|
||||
fun <Key, Value> Route.configureOneToManyReadKeyValueRepoRoutes(
|
||||
originalRepo: ReadKeyValuesRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val paginationKeyResult = PaginationResult.serializer(keySerializer)
|
||||
val paginationValueResult = PaginationResult.serializer(valueSerializer)
|
||||
|
||||
get(getRoute) {
|
||||
unifiedRouter.apply {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
val reversed = decodeUrlQueryValue(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: false
|
||||
|
||||
unianswer(
|
||||
paginationValueResult,
|
||||
originalRepo.get(key, pagination, reversed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(keysRoute) {
|
||||
unifiedRouter.apply {
|
||||
val pagination = call.request.queryParameters.extractPagination
|
||||
val reversed = decodeUrlQueryValue(
|
||||
reversedParameterName,
|
||||
Boolean.serializer()
|
||||
) ?: false
|
||||
val value: Value? = decodeUrlQueryValue(
|
||||
valueParameterName,
|
||||
valueSerializer
|
||||
)
|
||||
|
||||
unianswer(
|
||||
paginationKeyResult,
|
||||
value?.let { originalRepo.keys(value, pagination, reversed) } ?: originalRepo.keys(pagination, reversed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(containsByKeyRoute) {
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(key)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(containsByKeyValueRoute) {
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
val value = decodeUrlQueryValueOrSendError(
|
||||
valueParameterName,
|
||||
valueSerializer
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
Boolean.serializer(),
|
||||
originalRepo.contains(key, value)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(countByKeyRoute) {
|
||||
unifiedRouter.apply {
|
||||
val key = decodeUrlQueryValueOrSendError(
|
||||
keyParameterName,
|
||||
keySerializer
|
||||
) ?: return@get
|
||||
|
||||
unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count(key)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get(countRoute) {
|
||||
unifiedRouter.apply {
|
||||
unianswer(
|
||||
Long.serializer(),
|
||||
originalRepo.count()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <Key, Value> Route.configureOneToManyReadKeyValueRepoRoutes(
|
||||
originalRepo: ReadKeyValuesRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureOneToManyReadKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
@@ -1,103 +0,0 @@
|
||||
package dev.inmo.micro_utils.repos.ktor.server.one_to_many
|
||||
|
||||
import dev.inmo.micro_utils.ktor.common.StandardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.common.standardKtorSerialFormat
|
||||
import dev.inmo.micro_utils.ktor.server.*
|
||||
import dev.inmo.micro_utils.repos.WriteKeyValuesRepo
|
||||
import dev.inmo.micro_utils.repos.ktor.common.one_to_many.*
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.post
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.*
|
||||
|
||||
fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
|
||||
originalRepo: WriteKeyValuesRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
unifiedRouter: UnifiedRouter
|
||||
) {
|
||||
val keyValueSerializer = PairSerializer(keySerializer, valueSerializer)
|
||||
val keyValueMapSerializer = MapSerializer(keySerializer, ListSerializer(valueSerializer))
|
||||
|
||||
unifiedRouter.apply {
|
||||
includeWebsocketHandling(
|
||||
onNewValueRoute,
|
||||
originalRepo.onNewValue,
|
||||
keyValueSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
onValueRemovedRoute,
|
||||
originalRepo.onValueRemoved,
|
||||
keyValueSerializer
|
||||
)
|
||||
includeWebsocketHandling(
|
||||
onDataClearedRoute,
|
||||
originalRepo.onDataCleared,
|
||||
keySerializer
|
||||
)
|
||||
}
|
||||
|
||||
post(addRoute) {
|
||||
unifiedRouter.apply {
|
||||
val obj = uniload(keyValueMapSerializer)
|
||||
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.add(obj)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(removeRoute) {
|
||||
unifiedRouter.apply {
|
||||
val obj = uniload(keyValueMapSerializer)
|
||||
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.remove(obj),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(clearRoute) {
|
||||
unifiedRouter.apply {
|
||||
val key = uniload(keySerializer)
|
||||
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.clear(key),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(clearWithValueRoute) {
|
||||
unifiedRouter.apply {
|
||||
val v = uniload(valueSerializer)
|
||||
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.clearWithValue(v),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
post(setRoute) {
|
||||
unifiedRouter.apply {
|
||||
val obj = uniload(keyValueMapSerializer)
|
||||
|
||||
unianswer(
|
||||
Unit.serializer(),
|
||||
originalRepo.set(obj)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <Key, Value> Route.configureOneToManyWriteKeyValueRepoRoutes(
|
||||
originalRepo: WriteKeyValuesRepo<Key, Value>,
|
||||
keySerializer: KSerializer<Key>,
|
||||
valueSerializer: KSerializer<Value>,
|
||||
serialFormat: StandardKtorSerialFormat = standardKtorSerialFormat,
|
||||
serialFormatContentType: ContentType = standardKtorSerialFormatContentType
|
||||
) = configureOneToManyWriteKeyValueRepoRoutes(originalRepo, keySerializer, valueSerializer, UnifiedRouter(serialFormat, serialFormatContentType))
|
17
safe_wrapper/build.gradle
Normal file
17
safe_wrapper/build.gradle
Normal file
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "com.android.library"
|
||||
}
|
||||
|
||||
apply from: "$mppProjectWithSerializationPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api project(":micro_utils.coroutines")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
safe_wrapper/src/commonMain/kotlin/SafeWrapper.kt
Normal file
17
safe_wrapper/src/commonMain/kotlin/SafeWrapper.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.safe_wrapper
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
|
||||
interface SafeWrapper<T> {
|
||||
fun <R> safe(block: T.() -> R): Result<R> = unsafeTarget().runCatching(block)
|
||||
fun <R> unsafe(block: T.() -> R): R = unsafeTarget().block()
|
||||
suspend fun <R> safeS(block: suspend T.() -> R): Result<R> = unsafeTarget().runCatchingSafely(block = block)
|
||||
suspend fun <R> unsafeS(block: suspend T.() -> R): R = unsafeTarget().block()
|
||||
fun unsafeTarget(): T
|
||||
|
||||
class Default<T>(private val t: T) : SafeWrapper<T> { override fun unsafeTarget(): T = t }
|
||||
|
||||
companion object {
|
||||
operator fun <T> invoke(t: T) = Default(t)
|
||||
}
|
||||
}
|
1
safe_wrapper/src/main/AndroidManifest.xml
Normal file
1
safe_wrapper/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.micro_utils.safe_wrapper"/>
|
@@ -4,6 +4,7 @@ String[] includes = [
|
||||
":common",
|
||||
":common:compose",
|
||||
":matrix",
|
||||
":safe_wrapper",
|
||||
":crypto",
|
||||
":koin",
|
||||
":selector:common",
|
||||
@@ -32,6 +33,8 @@ String[] includes = [
|
||||
":serialization:base64",
|
||||
":serialization:encapsulator",
|
||||
":serialization:typed_serializer",
|
||||
":startup:plugin",
|
||||
":startup:launcher",
|
||||
|
||||
":fsm:common",
|
||||
":fsm:repos:common",
|
||||
|
92
startup/launcher/README.md
Normal file
92
startup/launcher/README.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Startup Plugin Launcher
|
||||
|
||||
This module contains tools to start your plugin system.
|
||||
|
||||
## Config
|
||||
|
||||
Base config is pretty simple:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": [
|
||||
"dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
So, `"dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin"` is the fully qualified name of plugin you wish to be
|
||||
included in the server.
|
||||
|
||||
> JS note: In JS there are no opportunity to determine object type by its full name. Because of it, in JS developers
|
||||
> should prefer to use `Config` in their kotlin code directly instead of json config passing. More info see in [JS](#js)
|
||||
> section
|
||||
|
||||
## JVM
|
||||
|
||||
For JVM target you may use main class by path: `dev.inmo.micro_utils.startup.launcher.MainKt`
|
||||
|
||||
It is expected, that you will pass the main ONE argument with path to the config json. Sample of launching:
|
||||
|
||||
```bash
|
||||
./gradlew run --args="sample.config.json"
|
||||
```
|
||||
|
||||
Content of `sample.config.json` described in [Config](#config) section.
|
||||
|
||||
You may build runnable app using:
|
||||
|
||||
```bash
|
||||
./gradlew assembleDist
|
||||
```
|
||||
|
||||
In that case in `build/distributions` folder you will be able to find zip and tar files with all required
|
||||
tools for application running (via their `bin/app_name` binary). In that case yoy will not need to pass
|
||||
`--args=...` and launch will look like `./bin/app_name sample.config.json`
|
||||
|
||||
## JS
|
||||
|
||||
In JS for starting of your plugins app, you should use `PluginsStarter` in your code:
|
||||
|
||||
```kotlin
|
||||
PluginsStarter.startPlugins(
|
||||
Config(HelloWorldPlugin)
|
||||
)
|
||||
```
|
||||
|
||||
`Config` here is deserialized variant from [Config](#config) section. As was said in [Config](#config) section, in JS
|
||||
there is no way to find classes/objects by their full qualifiers. Because of it you should use some way to register your
|
||||
plugins in `StartPluginSerializer` or use the code like in the snippet above: there plugins will be registered
|
||||
automatically.
|
||||
|
||||
In case you wish to register your plugins manually and run server from config, you should use one of the ways to register
|
||||
plugin on start.
|
||||
|
||||
Sample with `EagerInitialization`: [Kotlin JS doc about lazy initialization](https://kotlinlang.org/docs/js-ir-compiler.html#incremental-compilation-for-development-binaries),
|
||||
[@EagerInitialization docs](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.js/-eager-initialization/):
|
||||
|
||||
```kotlin
|
||||
@ExperimentalStdlibApi
|
||||
@EagerInitialization
|
||||
val plugin = createStartupPluginAndRegister("PluginNameToUseInConfig") {
|
||||
// Your plugin creation. For example:
|
||||
HelloWorldPlugin
|
||||
}
|
||||
```
|
||||
|
||||
So, in that case you will be able to load plugins list as `JsonObject` from anywhere and start plugins app with it:
|
||||
|
||||
```kotlin
|
||||
PluginsStarter.startPlugins(
|
||||
jsonObject
|
||||
)
|
||||
```
|
||||
|
||||
It will load `HelloWorldPlugin` if `jsonObject` have next content:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": [
|
||||
"PluginNameToUseInConfig"
|
||||
]
|
||||
}
|
||||
```
|
31
startup/launcher/build.gradle
Normal file
31
startup/launcher/build.gradle
Normal file
@@ -0,0 +1,31 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
id "application"
|
||||
}
|
||||
|
||||
apply from: "$mppJsAndJavaProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api internalProject("micro_utils.startup.plugin")
|
||||
}
|
||||
}
|
||||
commonTest {
|
||||
dependencies {
|
||||
implementation libs.kt.coroutines.test
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = "dev.inmo.micro_utils.startup.launcher.MainKt"
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
22
startup/launcher/src/commonMain/kotlin/Config.kt
Normal file
22
startup/launcher/src/commonMain/kotlin/Config.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package dev.inmo.micro_utils.startup.launcher
|
||||
|
||||
import dev.inmo.micro_utils.startup.plugin.StartPlugin
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Contains just [List] of [StartPlugin]s. In json this config should look like:
|
||||
*
|
||||
* ```json
|
||||
* {
|
||||
* "plugins": [
|
||||
* "dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin"
|
||||
* ]
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* In the sample above [HelloWorldPlugin] will be loaded during startup of application
|
||||
*/
|
||||
@Serializable
|
||||
data class Config(
|
||||
val plugins: List<StartPlugin>
|
||||
)
|
7
startup/launcher/src/commonMain/kotlin/DefaultJson.kt
Normal file
7
startup/launcher/src/commonMain/kotlin/DefaultJson.kt
Normal file
@@ -0,0 +1,7 @@
|
||||
package dev.inmo.micro_utils.startup.launcher
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
val defaultJson = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
13
startup/launcher/src/commonMain/kotlin/HelloWorldPlugin.kt
Normal file
13
startup/launcher/src/commonMain/kotlin/HelloWorldPlugin.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package dev.inmo.micro_utils.startup.launcher
|
||||
|
||||
import dev.inmo.kslog.common.i
|
||||
import dev.inmo.kslog.common.logger
|
||||
import dev.inmo.micro_utils.startup.plugin.StartPlugin
|
||||
import org.koin.core.Koin
|
||||
|
||||
object HelloWorldPlugin : StartPlugin {
|
||||
override suspend fun startPlugin(koin: Koin) {
|
||||
super.startPlugin(koin)
|
||||
logger.i("Hello world")
|
||||
}
|
||||
}
|
17
startup/launcher/src/commonMain/kotlin/Start.kt
Normal file
17
startup/launcher/src/commonMain/kotlin/Start.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
package dev.inmo.micro_utils.startup.launcher
|
||||
|
||||
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.setupDI
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import org.koin.core.KoinApplication
|
||||
|
||||
/**
|
||||
* Will create [KoinApplication], init, load modules using [StartLauncherPlugin] and start plugins using the same base
|
||||
* plugin
|
||||
*
|
||||
* @param rawConfig It is expected that this [JsonObject] will contain serialized [Config] ([StartLauncherPlugin] will
|
||||
* deserialize it in its [StartLauncherPlugin.setupDI]
|
||||
*/
|
||||
@Deprecated("Fully replaced with StartLauncherPlugin#start", ReplaceWith("StartLauncherPlugin.start(rawConfig)", "dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin"))
|
||||
suspend fun start(rawConfig: JsonObject) {
|
||||
StartLauncherPlugin.start(rawConfig)
|
||||
}
|
141
startup/launcher/src/commonMain/kotlin/StartLauncherPlugin.kt
Normal file
141
startup/launcher/src/commonMain/kotlin/StartLauncherPlugin.kt
Normal file
@@ -0,0 +1,141 @@
|
||||
package dev.inmo.micro_utils.startup.launcher
|
||||
|
||||
import dev.inmo.kslog.common.i
|
||||
import dev.inmo.kslog.common.taggedLogger
|
||||
import dev.inmo.kslog.common.w
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.setupDI
|
||||
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin.startPlugin
|
||||
import dev.inmo.micro_utils.startup.plugin.StartPlugin
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.SerialFormat
|
||||
import kotlinx.serialization.StringFormat
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.KoinApplication
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.binds
|
||||
import org.koin.dsl.module
|
||||
|
||||
/**
|
||||
* Default startup plugin. See [setupDI] and [startPlugin] for more info
|
||||
*/
|
||||
object StartLauncherPlugin : StartPlugin {
|
||||
internal val logger = taggedLogger(this)
|
||||
|
||||
fun Module.setupDI(config: Config, rawJsonObject: JsonObject? = null) {
|
||||
val rawJsonObject = rawJsonObject ?: defaultJson.encodeToJsonElement(Config.serializer(), config).jsonObject
|
||||
|
||||
single { rawJsonObject }
|
||||
single { config }
|
||||
single { CoroutineScope(Dispatchers.Default) }
|
||||
single { defaultJson } binds arrayOf(StringFormat::class, SerialFormat::class)
|
||||
|
||||
includes(
|
||||
config.plugins.mapNotNull {
|
||||
runCatching {
|
||||
module {
|
||||
with(it) {
|
||||
setupDI(rawJsonObject)
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
logger.w("Unable to load DI part of $it", e)
|
||||
}.getOrNull()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will deserialize [Config] from [config], register it in receiver [Module] (as well as [CoroutineScope] and
|
||||
* [kotlinx.serialization.json.Json])
|
||||
*
|
||||
* Besides, in this method will be called [StartPlugin.setupDI] on each plugin from [Config.plugins]. In case when
|
||||
* some plugin will not be loaded correctly it will be reported throw the [logger]
|
||||
*/
|
||||
override fun Module.setupDI(config: JsonObject) {
|
||||
logger.i("Koin for current module has started setup")
|
||||
setupDI(
|
||||
defaultJson.decodeFromJsonElement(Config.serializer(), config),
|
||||
config
|
||||
)
|
||||
logger.i("Koin for current module has been setup")
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes [CoroutineScope] and [Config] from the [koin], and call starting of each plugin from [Config.plugins]
|
||||
* ASYNCHRONOUSLY. Just like in [setupDI], in case of fail in some plugin it will be reported using [logger]
|
||||
*/
|
||||
override suspend fun startPlugin(koin: Koin) {
|
||||
logger.i("Start starting of subplugins")
|
||||
val scope = koin.get<CoroutineScope>()
|
||||
koin.get<Config>().plugins.map { plugin ->
|
||||
scope.launch {
|
||||
runCatchingSafely {
|
||||
logger.i("Start loading of $plugin")
|
||||
with(plugin) {
|
||||
startPlugin(koin)
|
||||
}
|
||||
}.onFailure { e ->
|
||||
logger.w("Unable to start plugin $plugin", e)
|
||||
}.onSuccess {
|
||||
logger.i("Complete loading of $plugin")
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
logger.i("Complete subplugins start")
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create [KoinApplication], init, load modules using [StartLauncherPlugin] and start plugins using the same base
|
||||
* plugin
|
||||
*
|
||||
* @param rawConfig It is expected that this [JsonObject] will contain serialized [Config] ([StartLauncherPlugin] will
|
||||
* deserialize it in its [StartLauncherPlugin.setupDI]
|
||||
*/
|
||||
suspend fun start(rawConfig: JsonObject) {
|
||||
|
||||
logger.i("Start initialization")
|
||||
val koinApp = KoinApplication.init()
|
||||
koinApp.modules(
|
||||
module {
|
||||
setupDI(rawConfig)
|
||||
}
|
||||
)
|
||||
logger.i("Modules loaded")
|
||||
startKoin(koinApp)
|
||||
logger.i("Koin started")
|
||||
startPlugin(koinApp.koin)
|
||||
logger.i("App has been setup")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Will create [KoinApplication], init, load modules using [StartLauncherPlugin] and start plugins using the same base
|
||||
* plugin
|
||||
*
|
||||
* @param config In difference with other [start] method here config is already deserialized and this config will
|
||||
* be converted to [JsonObject] as raw config. That means that all plugins from [config] will receive
|
||||
* serialized version of [config] in [StartPlugin.setupDI] method
|
||||
*/
|
||||
suspend fun start(config: Config) {
|
||||
|
||||
logger.i("Start initialization")
|
||||
val koinApp = KoinApplication.init()
|
||||
logger.i("Koin app created")
|
||||
koinApp.modules(
|
||||
module {
|
||||
setupDI(config)
|
||||
}
|
||||
)
|
||||
startKoin(koinApp)
|
||||
logger.i("Koin started")
|
||||
startPlugin(koinApp.koin)
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
import dev.inmo.micro_utils.startup.launcher.Config
|
||||
import dev.inmo.micro_utils.startup.launcher.HelloWorldPlugin
|
||||
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin
|
||||
import dev.inmo.micro_utils.startup.launcher.defaultJson
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import org.koin.core.context.stopKoin
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
|
||||
class StartupLaunchingTests {
|
||||
@BeforeTest
|
||||
fun resetGlobalKoinContext() {
|
||||
runCatching { stopKoin() }
|
||||
}
|
||||
@Test
|
||||
fun CheckThatEmptyPluginsListLeadsToEndOfMain() {
|
||||
val emptyJson = defaultJson.encodeToJsonElement(
|
||||
Config.serializer(),
|
||||
Config(emptyList())
|
||||
).jsonObject
|
||||
|
||||
runTest {
|
||||
val job = launch {
|
||||
StartLauncherPlugin.start(emptyJson)
|
||||
}
|
||||
job.join()
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun CheckThatHelloWorldPluginsListLeadsToEndOfMain() {
|
||||
val emptyJson = defaultJson.encodeToJsonElement(
|
||||
Config.serializer(),
|
||||
Config(listOf(HelloWorldPlugin))
|
||||
).jsonObject
|
||||
|
||||
runTest {
|
||||
val job = launch {
|
||||
StartLauncherPlugin.start(emptyJson)
|
||||
}
|
||||
job.join()
|
||||
}
|
||||
}
|
||||
}
|
25
startup/launcher/src/jsMain/kotlin/PluginsStarter.kt
Normal file
25
startup/launcher/src/jsMain/kotlin/PluginsStarter.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package dev.inmo.micro_utils.startup.launcher
|
||||
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.i
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
|
||||
@Deprecated("Useless due to including of the same functionality in StrtLauncherPlugin")
|
||||
object PluginsStarter {
|
||||
init {
|
||||
KSLog.default = KSLog("Launcher")
|
||||
}
|
||||
|
||||
/**
|
||||
* It is expected that you have registered all the [dev.inmo.micro_utils.startup.plugin.StartPlugin]s of your JS
|
||||
* app inside of [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer] using its
|
||||
* [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer.registerPlugin] method
|
||||
*/
|
||||
suspend fun startPlugins(json: JsonObject) = StartLauncherPlugin.start(json)
|
||||
/**
|
||||
* Will convert [config] to [JsonObject] with auto registration of [dev.inmo.micro_utils.startup.plugin.StartPlugin]s
|
||||
* in [dev.inmo.micro_utils.startup.plugin.StartPluginSerializer]
|
||||
*/
|
||||
suspend fun startPlugins(config: Config) = StartLauncherPlugin.start(config)
|
||||
}
|
37
startup/launcher/src/jvmMain/kotlin/Main.kt
Normal file
37
startup/launcher/src/jvmMain/kotlin/Main.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package dev.inmo.micro_utils.startup.launcher
|
||||
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.i
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* It is expected, that [args] will contain ONE argument with path to the config json. Sample of launching:
|
||||
*
|
||||
* ```bash
|
||||
* ./gradlew run --args="sample.config.json"
|
||||
* ```
|
||||
*
|
||||
* Content of `sample.config.json` described in [Config] KDocs.
|
||||
*
|
||||
* You may build runnable app using:
|
||||
*
|
||||
* ```bash
|
||||
* ./gradlew assembleDist
|
||||
* ```
|
||||
*
|
||||
* In that case in `build/distributions` folder you will be able to find zip and tar files with all required
|
||||
* tools for application running (via their `bin/app_name` binary). In that case yoy will not need to pass
|
||||
* `--args=...` and launch will look like `./bin/app_name sample.config.json`
|
||||
*/
|
||||
suspend fun main(args: Array<String>) {
|
||||
|
||||
KSLog.default = KSLog("Launcher")
|
||||
val (configPath) = args
|
||||
val file = File(configPath)
|
||||
KSLog.i("Start read config from ${file.absolutePath}")
|
||||
val json = defaultJson.parseToJsonElement(file.readText()).jsonObject
|
||||
KSLog.i("Config has been read")
|
||||
|
||||
StartLauncherPlugin.start(json)
|
||||
}
|
25
startup/plugin/build.gradle
Normal file
25
startup/plugin/build.gradle
Normal file
@@ -0,0 +1,25 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
}
|
||||
|
||||
apply from: "$mppJsAndJavaProjectPresetPath"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
api libs.koin
|
||||
api libs.kt.serialization
|
||||
api libs.kslog
|
||||
api libs.kt.reflect
|
||||
api project(":micro_utils.coroutines")
|
||||
}
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
api libs.uuid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
startup/plugin/src/commonMain/kotlin/StartPlugin.kt
Normal file
31
startup/plugin/src/commonMain/kotlin/StartPlugin.kt
Normal file
@@ -0,0 +1,31 @@
|
||||
package dev.inmo.micro_utils.startup.plugin
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.module.Module
|
||||
|
||||
/**
|
||||
* Default plugin for start of your app
|
||||
*/
|
||||
@Serializable(StartPluginSerializer::class)
|
||||
interface StartPlugin {
|
||||
/**
|
||||
* This method will be called first to configure [Koin] [Module] related to this plugin. You may use
|
||||
* [org.koin.core.scope.Scope.get] in your koin definitions like [Module.single] to retrieve
|
||||
* [kotlinx.coroutines.CoroutineScope], [kotlinx.serialization.json.Json] or [dev.inmo.micro_utils.startup.launcher.Config]
|
||||
*/
|
||||
fun Module.setupDI(config: JsonObject) {}
|
||||
|
||||
/**
|
||||
* This method will be called after all other [StartPlugin] will [setupDI]
|
||||
*
|
||||
* It is allowed to lock end of this method in case you require to prevent application to end its run (for example,
|
||||
* you are starting some web server)
|
||||
*
|
||||
* @param koin Will contains everything you will register in [setupDI] (as well as other [StartPlugin]s) and
|
||||
* [kotlinx.coroutines.CoroutineScope], [kotlinx.serialization.json.Json] and [dev.inmo.micro_utils.startup.launcher.Config]
|
||||
* by their types
|
||||
*/
|
||||
suspend fun startPlugin(koin: Koin) {}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package dev.inmo.micro_utils.startup.plugin
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
|
||||
expect object StartPluginSerializer : KSerializer<StartPlugin>
|
@@ -0,0 +1,38 @@
|
||||
package dev.inmo.micro_utils.startup.plugin
|
||||
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Creates [T] using [block], register it in [StartPluginSerializer] using its [StartPluginSerializer.registerPlugin]
|
||||
* and returns created by [block] plugin
|
||||
*
|
||||
* @param name Will be used as a key for registration in [StartPluginSerializer] and will be passed to the [block] as
|
||||
* parameter
|
||||
*/
|
||||
inline fun <T : StartPlugin> createStartupPluginAndRegister(
|
||||
name: String = uuid4().toString(),
|
||||
block: (String) -> T
|
||||
): T {
|
||||
val plugin = block(name)
|
||||
StartPluginSerializer.registerPlugin(name, plugin)
|
||||
return plugin
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates [T] using [block], register it in [StartPluginSerializer] using its [StartPluginSerializer.registerPlugin]
|
||||
* and returns created by [block] plugin
|
||||
*/
|
||||
inline fun <T : StartPlugin> createStartupPluginAndRegister(
|
||||
kClass: KClass<T>,
|
||||
name: String = uuid4().toString(),
|
||||
block: (String) -> T
|
||||
): T = createStartupPluginAndRegister("${kClass.simpleName}_$name", block)
|
||||
|
||||
/**
|
||||
* Creates [T] using [block], register it in [StartPluginSerializer] using its [StartPluginSerializer.registerPlugin]
|
||||
* and returns created by [block] plugin
|
||||
*/
|
||||
inline fun <reified T : StartPlugin> createStartupPluginAndRegister(
|
||||
block: (String) -> T
|
||||
): T = createStartupPluginAndRegister(T::class, uuid4().toString(), block)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user