experimentally add linuxx64 and mingwx64 as target platforms

This commit is contained in:
InsanusMokrassar 2023-04-03 22:35:41 +06:00
parent d23e005985
commit 617dfb54e0
27 changed files with 1248 additions and 1087 deletions

View File

@ -20,5 +20,16 @@ kotlin {
} }
dependsOn jvmMain dependsOn jvmMain
} }
linuxX64Main {
dependencies {
api libs.okio
}
}
mingwX64Main {
dependencies {
api libs.okio
}
}
} }
} }

View File

@ -1,146 +1,36 @@
package dev.inmo.micro_utils.common package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar import okio.FileSystem
import kotlinx.cinterop.CPointer import okio.Path
import kotlinx.cinterop.alloc import okio.use
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.readBytes
import platform.linux.statvfs
import platform.posix.ACCESSPERMS
import platform.posix.FILE
import platform.posix.F_OK
import platform.posix.SEEK_END
import platform.posix.S_IFDIR
import platform.posix.S_IFMT
import platform.posix.access
import platform.posix.fgets
import platform.posix.fopen
import platform.posix.fseek
import platform.posix.ftell
import platform.posix.remove
import platform.posix.rename
import platform.posix.rmdir
import platform.posix.stat
actual data class MPPFile(internal val filename: FileName) { actual typealias MPPFile = Path
val path = filename.string
val isFile: Boolean
get() = memScoped {
val stat = alloc<stat>()
if (stat(path, stat.ptr) != 0)
return@memScoped false
(S_IFDIR != (stat.st_mode and S_IFMT.convert()).convert<Int>())
}
val isDirectory: Boolean
get() = memScoped {
val stat = alloc<stat>()
if (stat(path, stat.ptr) != 0)
return@memScoped false
S_IFDIR == (stat.st_mode and S_IFMT.convert()).convert<Int>()
}
val size: Long
get() = memScoped {
val stat = alloc<stat>()
if (stat(path, stat.ptr) != 0)
return@memScoped 0
return stat.st_size.convert()
}
val lastModified: Long
get() = memScoped {
val stat = alloc<stat>()
if (stat(path, stat.ptr) != 0)
return@memScoped 0
return stat.st_ctim.tv_nsec / 1000L
}
val freeSpace: Long
get() =
memScoped {
val stat = alloc<statvfs>()
statvfs(path, stat.ptr)
(stat.f_bfree.toULong() * stat.f_bsize.toULong()).toLong()
}
val availableSpace: Long
get() =
memScoped {
val stat = alloc<statvfs>()
statvfs(path, stat.ptr)
(stat.f_bavail.toULong() * stat.f_bsize.toULong()).toLong()
}
val totalSpace: Long
get() = memScoped {
val stat = alloc<statvfs>()
statvfs(path, stat.ptr)
(stat.f_blocks.toULong() * stat.f_frsize.toULong()).toLong()
}
constructor(vararg path: String) : this(FileName(path.map { it.removeSuffix(SEPARATOR_STRING) }.joinToString(SEPARATOR_STRING)))
constructor(parent: MPPFile, subpath: String) : this("${parent.filename.withoutSlashAtTheEnd}$SEPARATOR${subpath}")
fun createPointer(mode: String = "r") = fopen(filename.name, mode)
fun delete(): Boolean {
return when {
isDirectory -> rmdir(path) == 0
isFile -> remove(path) == 0
else -> false
}
}
fun mkdir(): Boolean = platform.posix.mkdir(path, ACCESSPERMS) == 0
override fun toString(): String = path
override fun equals(other: Any?): Boolean {
if (other !is MPPFile) return false
return path == other.path
}
override fun hashCode(): Int = 31 + path.hashCode()
fun renameTo(newPath: MPPFile): Boolean = rename(path, newPath.path) == 0
fun list(): List<MPPFile> {
val out = ArrayList<MPPFile>()
FileIterator(this).forEach { file ->
out += file
}
return out
}
fun fileChannel(mode: String = "r") = FileChannel(this, mode)
companion object {
val SEPARATOR: Char = '/'
val SEPARATOR_STRING: String = SEPARATOR.toString()
val temporalDirectory: MPPFile?
get() = MPPFile("/tmp").takeIf {
it.isDirectory
}
}
}
/**
* @suppress
*/
actual val MPPFile.filename: FileName actual val MPPFile.filename: FileName
get() = this.filename get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long actual val MPPFile.filesize: Long
get() = memScoped { get() = FileSystem.SYSTEM.openReadOnly(this).use {
val pointer = createPointer() it.size()
fseek(pointer, 0L, SEEK_END)
ftell(pointer)
} }
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = { get() = {
memScoped { FileSystem.SYSTEM.read(this) {
fileChannel().readFully() readByteArray()
} }
} }
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = { get() = {
bytesAllocatorSync() bytesAllocatorSync()
} }

View File

@ -1,107 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toCValues
import kotlinx.cinterop.toKString
import platform.posix.SEEK_END
import platform.posix.SEEK_SET
import platform.posix.fclose
import platform.posix.feof
import platform.posix.fread
import platform.posix.fseek
import platform.posix.ftell
import platform.posix.fwrite
import platform.posix.size_t
class FileChannel (file: MPPFile, mode: String) : RandomAccess {
internal val handler = file.createPointer(mode)
private var position: ULong
get() = ftell(handler).convert()
set(value) {
fseek(handler, value.convert(), SEEK_SET)
}
private val size: ULong
get() {
val pos = position
gotoEnd()
val result = position
position = pos
return result
}
fun skip(length: Long): Long {
checkClosed()
memScoped { }
if (length == 0L)
return 0L
if (feof(handler) != 0)
return 0L
val endOfFile = size
val position = minOf(endOfFile, this.position + length.toULong())
this.position = position
return (endOfFile - position).toLong()
}
fun read(dest: ByteArray): Int {
checkClosed()
if (feof(handler) != 0)
return 0
memScoped {
val tmp = allocArray<ByteVar>(dest.size);
fread(tmp, Byte.SIZE_BYTES.convert<size_t>(), dest.size.convert(), handler).convert<Int>()
tmp.toKString()
}
return fread(dest.toCValues(), Byte.SIZE_BYTES.convert<size_t>(), dest.size.convert(), handler).convert<Int>()
}
private var closed = false
private fun checkClosed() {
if (closed) {
error("File channel has been closed already")
}
}
fun close() {
checkClosed()
fclose(handler)
closed = true
}
fun write(data: ByteArray): Int {
checkClosed()
if (feof(handler) != 0)
return 0
return fwrite(data.toCValues(), 1.convert<size_t>(), data.size.convert<size_t>(), handler).convert<Int>()
}
fun flush() {
checkClosed()
}
private fun gotoEnd() {
fseek(handler, 0, SEEK_END)
}
fun readFully(): ByteArray {
var result = ByteArray(0)
memScoped {
val tmp = ByteArray(64 * 1024)
do {
val read = read(tmp)
result += tmp.take(read)
} while (read > 0)
}
val tmp = ByteArray(64 * 1024)
return result
}
}

View File

@ -1,56 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.pointed
import kotlinx.cinterop.toKString
import platform.posix.closedir
import platform.posix.dirent
import platform.posix.opendir
import platform.posix.readdir
import kotlin.native.internal.createCleaner
class FileIterator internal constructor(private val file: MPPFile) : Iterator<MPPFile> {
init {
if (!file.isDirectory)
error("\"${file.path}\" is not direction")
}
private val handler = opendir(file.path)
private var next: dirent? = null
private var end = false
override fun hasNext(): Boolean {
while (true) {
if (end)
return false
if (next == null) {
next = readdir(handler)?.pointed
if (next == null) {
end = true
return false
}
val name = next!!.d_name.toKString()
if (name == "." || name == "..") {
next = null
continue
}
return true
}
return true
}
}
override fun next(): MPPFile {
if (!hasNext())
throw NoSuchElementException()
val result = MPPFile(file, next!!.d_name.toKString())
next = null
return result
}
@OptIn(ExperimentalStdlibApi::class)
private val cleaner = createCleaner(handler) {
closedir(it)
}
}

View File

@ -1,36 +0,0 @@
package dev.inmo.micro_utils.common
import dev.inmo.micro_utils.common.MPPFile
import kotlin.test.Test
import kotlin.test.assertEquals
fun createTestFile(content: String): MPPFile = MPPFile(
MPPFile.temporalDirectory!!,
"tmp.file.txt"
).apply {
val channel = fileChannel("wc")
runCatching {
channel.write(content.encodeToByteArray())
}
channel.close()
}
fun MPPFile.removeTestFile() {
delete()
}
class FileTests {
@Test
fun testReadFromFile() {
val testContent = "Test"
val file = createTestFile(testContent)
try {
val content = runCatching {
file.fileChannel().readFully().contentToString()
}.getOrThrow()
assertEquals(testContent, content)
} finally {
file.removeTestFile()
}
}
}

View File

@ -0,0 +1,36 @@
package dev.inmo.micro_utils.common
import okio.FileSystem
import okio.Path
import okio.use
actual typealias MPPFile = Path
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = FileSystem.SYSTEM.openReadOnly(this).use {
it.size()
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
FileSystem.SYSTEM.read(this) {
readByteArray()
}
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@ -0,0 +1,26 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf
import platform.posix.sprintf
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@ -11,6 +11,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
api project(":micro_utils.common") api project(":micro_utils.common")
api libs.krypto
} }
} }
jsMain { jsMain {

View File

@ -1,6 +1,8 @@
package dev.inmo.micro_utils.crypto package dev.inmo.micro_utils.crypto
import com.soywiz.krypto.md5
typealias MD5 = String typealias MD5 = String
expect fun SourceBytes.md5(): MD5 fun SourceBytes.md5(): MD5 = md5().hexLower
fun SourceString.md5(): MD5 = encodeToByteArray().md5() fun SourceString.md5(): MD5 = encodeToByteArray().md5().hexLower

View File

@ -1,6 +0,0 @@
package dev.inmo.micro_utils.crypto
/**
* @suppress
*/
actual fun SourceBytes.md5(): MD5 = CryptoJS.MD5(decodeToString())

View File

@ -1,12 +0,0 @@
package dev.inmo.micro_utils.crypto
import java.math.BigInteger
import java.security.MessageDigest
/**
* @suppress
*/
actual fun SourceBytes.md5(): MD5 = BigInteger(
1,
MessageDigest.getInstance("MD5").digest(this)
).toString(16)

View File

@ -10,7 +10,7 @@ jb-compose = "1.3.1"
jb-exposed = "0.41.1" jb-exposed = "0.41.1"
jb-dokka = "1.8.10" jb-dokka = "1.8.10"
klock = "3.4.0" korlibs = "3.4.0"
uuid = "0.7.0" uuid = "0.7.0"
ktor = "2.2.4" ktor = "2.2.4"
@ -19,6 +19,8 @@ gh-release = "2.4.1"
koin = "3.3.2" koin = "3.3.2"
okio = "3.3.0"
ksp = "1.8.10-1.0.9" ksp = "1.8.10-1.0.9"
kotlin-poet = "1.12.0" kotlin-poet = "1.12.0"
@ -67,7 +69,8 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" } klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" }
krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
@ -91,6 +94,8 @@ kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref
kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" } kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" }
ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
# Buildscript # Buildscript
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" } buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }

View File

@ -18,5 +18,17 @@ kotlin {
androidMain { androidMain {
dependsOn jvmMain dependsOn jvmMain
} }
linuxX64Main {
dependencies {
api internalProject("micro_utils.mime_types")
}
}
mingwX64Main {
dependencies {
api internalProject("micro_utils.mime_types")
}
}
} }
} }

View File

@ -1,6 +1,10 @@
package dev.inmo.micro_utils.ktor.client package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.MPPFile import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filesize
import dev.inmo.micro_utils.ktor.common.input
import io.ktor.client.request.forms.InputProvider import io.ktor.client.request.forms.InputProvider
expect suspend fun MPPFile.inputProvider(): InputProvider fun MPPFile.inputProvider(): InputProvider = InputProvider(filesize) {
input()
}

View File

@ -1,11 +0,0 @@
package dev.inmo.micro_utils.ktor.client
import dev.inmo.micro_utils.common.*
import io.ktor.client.request.forms.InputProvider
import io.ktor.utils.io.core.ByteReadPacket
actual suspend fun MPPFile.inputProvider(): InputProvider = bytes().let {
InputProvider(it.size.toLong()) {
ByteReadPacket(it)
}
}

View File

@ -7,5 +7,3 @@ import io.ktor.utils.io.streams.asInput
fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) { fun MPPFile.inputProviderSync(): InputProvider = InputProvider(length()) {
inputStream().asInput() inputStream().asInput()
} }
actual suspend fun MPPFile.inputProvider(): InputProvider = inputProviderSync()

View File

@ -0,0 +1,40 @@
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.TemporalFileId
import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
import io.ktor.client.HttpClient
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
internal val MPPFile.mimeType: String
get() = getMimeTypeOrAny(filename.extension).raw
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: OnUploadCallback
): TemporalFileId {
val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData(
fullTempUploadDraftPath,
formData = formData {
append(
"data",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, file.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
}
)
}
) {
onUpload(onUpload)
}.bodyAsText()
return TemporalFileId(fileId)
}

View File

@ -0,0 +1,107 @@
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.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.InternalSerializationApi
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import kotlinx.serialization.serializer
/**
* 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
*/
@OptIn(InternalSerializationApi::class)
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 MPPFile || it is UniUploadFileInfo }
val formData = formData {
for (k in data.keys) {
val v = data[k] ?: continue
when (v) {
is MPPFile -> append(
k,
v.inputProvider(),
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::class.serializer() as SerializationStrategy<in Any>, 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 {
for (it in formData) {
val formItem = (it as PartData.FormItem)
append(it.name!!, it.value)
}
},
block = requestBuilder
)
}
return if (response.status == HttpStatusCode.OK) {
stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
} else {
null
}
}

View File

@ -0,0 +1,40 @@
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.TemporalFileId
import dev.inmo.micro_utils.mime_types.getMimeTypeOrAny
import io.ktor.client.HttpClient
import io.ktor.client.plugins.onUpload
import io.ktor.client.request.forms.formData
import io.ktor.client.request.forms.submitFormWithBinaryData
import io.ktor.client.statement.bodyAsText
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
internal val MPPFile.mimeType: String
get() = getMimeTypeOrAny(filename.extension).raw
actual suspend fun HttpClient.tempUpload(
fullTempUploadDraftPath: String,
file: MPPFile,
onUpload: OnUploadCallback
): TemporalFileId {
val inputProvider = file.inputProvider()
val fileId = submitFormWithBinaryData(
fullTempUploadDraftPath,
formData = formData {
append(
"data",
inputProvider,
Headers.build {
append(HttpHeaders.ContentType, file.mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"${file.filename.string}\"")
}
)
}
) {
onUpload(onUpload)
}.bodyAsText()
return TemporalFileId(fileId)
}

View File

@ -0,0 +1,107 @@
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.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.InternalSerializationApi
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.StringFormat
import kotlinx.serialization.encodeToString
import kotlinx.serialization.serializer
/**
* 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
*/
@OptIn(InternalSerializationApi::class)
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 MPPFile || it is UniUploadFileInfo }
val formData = formData {
for (k in data.keys) {
val v = data[k] ?: continue
when (v) {
is MPPFile -> append(
k,
v.inputProvider(),
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::class.serializer() as SerializationStrategy<in Any>, 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 {
for (it in formData) {
val formItem = (it as PartData.FormItem)
append(it.name!!, it.value)
}
},
block = requestBuilder
)
}
return if (response.status == HttpStatusCode.OK) {
stringFormat.decodeFromString(resultDeserializer, response.bodyAsText())
} else {
null
}
}

View File

@ -0,0 +1,33 @@
package dev.inmo.micro_utils.ktor.common
import dev.inmo.micro_utils.common.MPPFile
import io.ktor.utils.io.bits.Memory
import io.ktor.utils.io.bits.loadByteArray
import io.ktor.utils.io.core.Input
import okio.FileSystem
import okio.Path
private class FileInput(
private val path: Path
) : Input() {
private val openedFile = FileSystem.SYSTEM.openReadOnly(path)
override fun closeSource() {
openedFile.close()
}
override fun fill(destination: Memory, offset: Int, length: Int): Int {
val byteArray = ByteArray(length)
val read = openedFile.read(offset.toLong(), byteArray, 0, length)
destination.loadByteArray(
offset,
byteArray,
count = length
)
return read
}
}
actual fun MPPFile.input(): Input {
return FileInput(this)
}

View File

@ -0,0 +1,33 @@
package dev.inmo.micro_utils.ktor.common
import dev.inmo.micro_utils.common.MPPFile
import io.ktor.utils.io.bits.Memory
import io.ktor.utils.io.bits.loadByteArray
import io.ktor.utils.io.core.Input
import okio.FileSystem
import okio.Path
private class FileInput(
private val path: Path
) : Input() {
private val openedFile = FileSystem.SYSTEM.openReadOnly(path)
override fun closeSource() {
openedFile.close()
}
override fun fill(destination: Memory, offset: Int, length: Int): Int {
val byteArray = ByteArray(length)
val read = openedFile.read(offset.toLong(), byteArray, 0, length)
destination.loadByteArray(
offset,
byteArray,
count = length
)
return read
}
}
actual fun MPPFile.input(): Input {
return FileInput(this)
}

View File

@ -1,3 +1,5 @@
import math
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import pandas as pd import pandas as pd
@ -17,33 +19,45 @@ def fix_name(category, raw_name):
result += out1 result += out1
return result return result
def remove_prefix(text, prefix):
if text.startswith(prefix):
return text[len(prefix):]
return text # or whatever
def extensionPreparationFun(extension):
return "\"%s\"" % (remove_prefix(extension, "."))
# https://www.freeformatter.com/mime-types-list.html # https://www.freeformatter.com/mime-types-list.html
if __name__ == '__main__': if __name__ == '__main__':
df = pd.read_html(open('table.html', 'r')) df = pd.read_html(open('local.table.html', 'r'))
mimes = [] mimes = []
for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows(): for row in df[0].drop_duplicates(subset=['MIME Type / Internet Media Type'], keep='first').iterrows():
mime = row[1][1] mime = row[1][1]
extensions = list()
if isinstance(row[1][2], str):
extensions = list(map(extensionPreparationFun, row[1][2].split(", ")))
mime_category = mime.split('/', 1)[0] mime_category = mime.split('/', 1)[0]
mime_name = mime.split('/', 1)[1] mime_name = mime.split('/', 1)[1]
mimes.append({ mimes.append([
'mime_category': mime_category, mime_category,
'mime_name': mime_name, mime_name,
}) extensions
])
# codegen # codegen
mimes.sort(key=lambda x: x['mime_category']) mimes.sort(key=lambda x: x[0])
grouped = itertools.groupby(mimes, lambda x: x['mime_category']) grouped = itertools.groupby(mimes, lambda x: x[0])
code = '' code = ''
code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n' code2 = 'internal val knownMimeTypes: Set<MimeType> = setOf(\n'
code2 += ' KnownMimeTypes.Any,\n' code2 += ' KnownMimeTypes.Any,\n'
for key, group in grouped: for key, group in grouped:
group_name = fix_name(group, key) 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)\nsealed class %s(raw: String, extensions: Array<String> = emptyArray()) : MimeType, KnownMimeTypes(raw, extensions) {\n' % group_name
code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key) code += ' @Serializable(MimeTypeSerializer::class)\n object Any: %s ("%s/*")\n' % (group_name, key)
for mime in group: for mime in group:
name = fix_name(mime['mime_category'], mime['mime_name']) name = fix_name(mime[0], mime[1])
code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s")\n' % (name, group_name, mime['mime_category'], mime['mime_name']) code += ' @Serializable(MimeTypeSerializer::class)\n object %s: %s ("%s/%s", arrayOf(%s))\n' % (name, group_name, mime[0], mime[1], ", ".join(mime[2]))
code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name) code2 += ' KnownMimeTypes.%s.%s,\n' % (group_name, name)
code += '}\n\n' code += '}\n\n'
code2 += ')\n' code2 += ')\n'

View File

@ -0,0 +1,24 @@
package dev.inmo.micro_utils.mime_types
val mimeTypesByExtensions: Map<String, Array<MimeType>> by lazy {
val extensionsMap = mutableMapOf<String, MutableList<MimeType>>()
knownMimeTypes.forEach { mimeType ->
mimeType.extensions.forEach {
extensionsMap.getOrPut(it) { mutableListOf() }.add(mimeType)
}
}
extensionsMap.mapValues {
it.value.toTypedArray()
}
}
inline fun getMimeType(
stringWithExtension: String,
selector: (Array<MimeType>) -> MimeType? = { it.firstOrNull() }
) = mimeTypesByExtensions[stringWithExtension.takeLastWhile { it != '.' }] ?.takeIf { it.isNotEmpty() } ?.let(selector)
inline fun getMimeTypeOrAny(
stringWithExtension: String,
selector: (Array<MimeType>) -> MimeType? = { it.firstOrNull() }
) = getMimeType(stringWithExtension, selector) ?: KnownMimeTypes.Any

View File

@ -5,4 +5,6 @@ import kotlinx.serialization.Serializable
@Serializable(MimeTypeSerializer::class) @Serializable(MimeTypeSerializer::class)
interface MimeType { interface MimeType {
val raw: String val raw: String
val extensions: Array<String>
get() = emptyArray()
} }

View File

@ -54,7 +54,11 @@ kotlin {
} }
mingwX64Test { mingwX64Test {
dependencies { dependencies {
implementation kotlin('test-js') implementation kotlin('test-junit')
}
}
linuxX64Test {
dependencies {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }