complete universal uniupload

This commit is contained in:
InsanusMokrassar 2022-11-22 12:57:43 +06:00
parent a3090cca9d
commit 844a129094
10 changed files with 229 additions and 12 deletions

View File

@ -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()
)
}
}

View File

@ -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()

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import dev.inmo.micro_utils.common.FileName
import dev.inmo.micro_utils.ktor.common.LambdaInputProvider import dev.inmo.micro_utils.ktor.common.LambdaInputProvider
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.http.Headers import io.ktor.http.Headers
import io.ktor.utils.io.core.Input
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -27,5 +26,6 @@ expect suspend fun <T> HttpClient.uniupload(
data: Map<String, Any>, data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>, resultDeserializer: DeserializationStrategy<T>,
headers: Headers = Headers.Empty, headers: Headers = Headers.Empty,
stringFormat: StringFormat = Json.Default stringFormat: StringFormat = Json,
onUpload: OnUploadCallback = { _, _ -> }
): T? ): T?

View File

@ -55,5 +55,5 @@ suspend fun tempUpload(
actual suspend fun HttpClient.tempUpload( actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String, fullTempUploadDraftPath: String,
file: MPPFile, file: MPPFile,
onUpload: (uploaded: Long, count: Long) -> Unit onUpload: OnUploadCallback
): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload) ): TemporalFileId = dev.inmo.micro_utils.ktor.client.tempUpload(fullTempUploadDraftPath, file, onUpload)

View File

@ -0,0 +1,86 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.Progress
import io.ktor.client.HttpClient
import io.ktor.http.Headers
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.CompletableDeferred
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)
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()
request.responseType = XMLHttpRequestResponseType.TEXT
request.upload.onprogress = {
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()
}

View File

@ -18,7 +18,7 @@ internal val MPPFile.mimeType: String
actual suspend fun HttpClient.tempUpload( actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String, fullTempUploadDraftPath: String,
file: MPPFile, file: MPPFile,
onUpload: (Long, Long) -> Unit onUpload: OnUploadCallback
): TemporalFileId { ): TemporalFileId {
val inputProvider = file.inputProvider() val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData( val fileId = submitFormWithBinaryData(

View File

@ -1,7 +1,8 @@
package dev.inmo.micro_utils.ktor.client package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.ktor.common.input import dev.inmo.micro_utils.common.Progress
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.forms.InputProvider import io.ktor.client.request.forms.InputProvider
import io.ktor.client.request.forms.formData import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitForm import io.ktor.client.request.forms.submitForm
@ -13,10 +14,8 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters import io.ktor.http.Parameters
import io.ktor.http.content.PartData import io.ktor.http.content.PartData
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.serializer
import java.io.File import java.io.File
/** /**
@ -26,13 +25,13 @@ import java.io.File
* [dev.inmo.micro_utils.common.MPPFile] (File from JS or JVM platform). Also you may pass [UniUploadFileInfo] as value * [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 * in case you wish to pass other source of multipart binary data than regular file
*/ */
@OptIn(InternalSerializationApi::class)
actual suspend fun <T> HttpClient.uniupload( actual suspend fun <T> HttpClient.uniupload(
url: String, url: String,
data: Map<String, Any>, data: Map<String, Any>,
resultDeserializer: DeserializationStrategy<T>, resultDeserializer: DeserializationStrategy<T>,
headers: Headers, headers: Headers,
stringFormat: StringFormat stringFormat: StringFormat,
onUpload: OnUploadCallback
): T? { ): T? {
val withBinary = data.values.any { it is File || it is UniUploadFileInfo } val withBinary = data.values.any { it is File || it is UniUploadFileInfo }
@ -67,7 +66,11 @@ actual suspend fun <T> HttpClient.uniupload(
submitFormWithBinaryData( submitFormWithBinaryData(
url, url,
formData formData
) ) {
onUpload { bytesSentTotal, contentLength ->
onUpload(bytesSentTotal, contentLength)
}
}
} else { } else {
submitForm( submitForm(
url, url,
@ -77,7 +80,11 @@ actual suspend fun <T> HttpClient.uniupload(
append(it.name!!, it.value) append(it.name!!, it.value)
} }
} }
) ) {
onUpload { bytesSentTotal, contentLength ->
onUpload(bytesSentTotal, contentLength)
}
}
} }
return if (response.status == HttpStatusCode.OK) { return if (response.status == HttpStatusCode.OK) {