diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ff8f9da13..e978cee9496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 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`: diff --git a/common/src/jvmMain/kotlin/dev/inmo/micro_utils/common/CreateTempFileFromInput.kt b/common/src/jvmMain/kotlin/dev/inmo/micro_utils/common/CreateTempFileFromInput.kt new file mode 100644 index 00000000000..0cc88afc862 --- /dev/null +++ b/common/src/jvmMain/kotlin/dev/inmo/micro_utils/common/CreateTempFileFromInput.kt @@ -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() +} diff --git a/gradle.properties b/gradle.properties index 48cf61f1249..283fbfee177 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,5 +14,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.14.3 -android_code_version=164 +version=0.14.4 +android_code_version=165 diff --git a/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/ktor/client/UniUpload.kt b/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/ktor/client/UniUpload.kt index 6bef13f2f33..38f6cc8aea0 100644 --- a/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/ktor/client/UniUpload.kt +++ b/ktor/client/src/commonMain/kotlin/dev/inmo/micro_utils/ktor/client/UniUpload.kt @@ -1,9 +1,11 @@ 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 @@ -20,6 +22,8 @@ data class UniUploadFileInfo( * @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 HttpClient.uniUpload( url: String, @@ -29,3 +33,73 @@ expect suspend fun HttpClient.uniUpload( 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 HttpClient.uniUpload( + url: String, + file: MPPFile, + resultDeserializer: DeserializationStrategy, + additionalData: Map = 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 HttpClient.uniUpload( + url: String, + info: UniUploadFileInfo, + resultDeserializer: DeserializationStrategy, + additionalData: Map = 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 HttpClient.uniUpload( + url: String, + fileName: FileName, + mimeType: String, + inputAllocator: LambdaInputProvider, + resultDeserializer: DeserializationStrategy, + additionalData: Map = emptyMap(), + headers: Headers = Headers.Empty, + stringFormat: StringFormat = Json, + onUpload: OnUploadCallback = { _, _ -> } +): T? = uniUpload( + url, + UniUploadFileInfo(fileName, mimeType, inputAllocator), + resultDeserializer, + additionalData, + headers, + stringFormat, + onUpload +) diff --git a/ktor/common/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/common/CreateTempFileFromInput.kt b/ktor/common/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/common/CreateTempFileFromInput.kt new file mode 100644 index 00000000000..5c7b97f888f --- /dev/null +++ b/ktor/common/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/common/CreateTempFileFromInput.kt @@ -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() +} diff --git a/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/UniloadMultipart.kt b/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/UniloadMultipart.kt index 27071b701b6..7898092e54d 100644 --- a/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/UniloadMultipart.kt +++ b/ktor/server/src/jvmMain/kotlin/dev/inmo/micro_utils/ktor/server/UniloadMultipart.kt @@ -2,6 +2,7 @@ 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 @@ -28,6 +29,7 @@ suspend inline fun ApplicationCall.handleUniUpload( is PartData.BinaryItem -> onBinaryContent(partData) is PartData.BinaryChannelItem -> onBinaryChannelItem(partData) } + partData.dispose() } } @@ -36,7 +38,7 @@ suspend fun ApplicationCall.uniloadMultipart( onCustomFileItem: (PartData.FileItem) -> Unit = {}, onBinaryChannelItem: (PartData.BinaryChannelItem) -> Unit = {}, onBinaryContent: (PartData.BinaryItem) -> Unit = {} -) = safely { +): Input = safely { var resultInput: Input? = null handleUniUpload( @@ -58,41 +60,11 @@ suspend fun ApplicationCall.uniloadMultipartFile( 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") +): MPPFile = safely { + uniloadMultipart( + onFormItem, + onCustomFileItem, + onBinaryChannelItem, + onBinaryContent + ).downloadToTempFile() }