mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2024-12-22 08:37:14 +00:00
commit
11116d8cab
.github/workflows
CHANGELOG.mdcommon/src/commonMain/kotlin/dev/inmo/micro_utils/common
gradle.propertiesktor
client
build.gradle
src
commonMain/kotlin/dev/inmo/micro_utils/ktor/client
jsMain/kotlin/dev/inmo/micro_utils/ktor/client
jvmMain/kotlin/dev/inmo/micro_utils/ktor/client
common/src/commonMain/kotlin/dev/inmo/micro_utils/ktor/common
server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server
repos/common
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 }}
|
||||
|
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,5 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
## 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`:
|
||||
|
@ -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()
|
@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.14.2
|
||||
android_code_version=163
|
||||
version=0.14.3
|
||||
android_code_version=164
|
||||
|
@ -15,5 +15,9 @@ kotlin {
|
||||
api libs.ktor.client
|
||||
}
|
||||
}
|
||||
|
||||
androidMain {
|
||||
dependsOn jvmMain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
typealias OnUploadCallback = suspend (uploaded: Long, count: Long) -> Unit
|
@ -7,5 +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
|
||||
|
@ -0,0 +1,31 @@
|
||||
package dev.inmo.micro_utils.ktor.client
|
||||
|
||||
import dev.inmo.micro_utils.common.FileName
|
||||
import dev.inmo.micro_utils.ktor.common.LambdaInputProvider
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.http.Headers
|
||||
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
|
||||
*/
|
||||
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?
|
@ -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,96 @@
|
||||
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
|
||||
*/
|
||||
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,101 @@
|
||||
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
|
||||
*/
|
||||
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,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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -2,22 +2,34 @@ 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ApplicationCall.uniloadMultipart(
|
||||
onFormItem: (PartData.FormItem) -> Unit = {},
|
||||
@ -25,23 +37,18 @@ suspend fun ApplicationCall.uniloadMultipart(
|
||||
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 -> {
|
||||
handleUniUpload(
|
||||
onFormItem,
|
||||
onBinaryContent,
|
||||
onBinaryChannelItem
|
||||
) {
|
||||
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")
|
||||
}
|
||||
@ -89,23 +96,3 @@ suspend fun ApplicationCall.uniloadMultipartFile(
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ kotlin {
|
||||
api internalProject("micro_utils.common")
|
||||
api internalProject("micro_utils.coroutines")
|
||||
}
|
||||
dependsOn jvmMain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user