temp progress on binary correct including

This commit is contained in:
2022-03-09 13:49:00 +06:00
parent 186bfd7ac0
commit 99b953635e
15 changed files with 342 additions and 180 deletions
features/common/common
build.gradle
src
commonMain
kotlin
dev
inmo
postssystem
features
common
jsMain
kotlin
dev
inmo
postssystem
features
jvmMain
kotlin
dev
inmo
postssystem
features
services/posts
client
src
commonMain
jsMain
kotlin
dev
inmo
postssystem
services
jvmMain
kotlin
dev
inmo
postssystem
services
common
build.gradle
src
commonMain
kotlin
dev
inmo
postssystem
services
posts
server
src
jvmMain

@ -1,6 +1,8 @@
package dev.inmo.postssystem.services.posts.server
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.common.decodeHex
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
@ -11,154 +13,102 @@ import dev.inmo.micro_utils.repos.ktor.common.one_to_many.removeRoute
import dev.inmo.micro_utils.repos.ktor.server.crud.configureReadStandardCrudRepoRoutes
import dev.inmo.postssystem.features.common.common.FileBasedInputProvider
import dev.inmo.postssystem.features.content.common.*
import dev.inmo.postssystem.features.files.common.FileId
import dev.inmo.postssystem.features.posts.common.*
import dev.inmo.postssystem.services.posts.common.*
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.auth.authenticate
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.streamProvider
import io.ktor.request.isMultipart
import io.ktor.request.receiveMultipart
import io.ktor.response.respond
import io.ktor.routing.*
import io.ktor.util.asStream
import io.ktor.util.pipeline.PipelineContext
import io.ktor.utils.io.core.use
import io.ktor.utils.io.streams.asInput
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.builtins.*
import java.io.File
import java.nio.file.Files
import java.nio.file.attribute.FileTime
import java.util.concurrent.TimeUnit
class ServerPostsServiceRoutingConfigurator(
private val readPostsService: ReadPostsService,
private val writePostsService: WritePostsService? = readPostsService as? WritePostsService,
private val scope: CoroutineScope,
private val unifiedRouter: UnifiedRouter
) : ApplicationRoutingConfigurator.Element {
private val contentEitherSerializer = EitherSerializer(ContentId.serializer(), ContentSerializer)
private val contentsEitherSerializer = ListSerializer(contentEitherSerializer)
private val contentsSerializer = ListSerializer(ContentSerializer)
private val temporalFilesMap = mutableMapOf<FileId, MPPFile>()
private val temporalFilesMutex = Mutex()
private val removingJob = scope.launchSafelyWithoutExceptions {
while (isActive) {
val filesWithCreationInfo = temporalFilesMap.mapNotNull { (fileId, file) ->
fileId to ((Files.getAttribute(file.toPath(), "creationTime") as? FileTime) ?.toMillis() ?: return@mapNotNull null)
}
if (filesWithCreationInfo.isEmpty()) {
delay(TimeUnit.HOURS.toMillis(1L))
continue
}
var min = filesWithCreationInfo.first()
for (fileWithCreationInfo in filesWithCreationInfo) {
if (fileWithCreationInfo.second < min.second) {
min = fileWithCreationInfo
}
}
delay(System.currentTimeMillis() - (min.second + TimeUnit.HOURS.toMillis(1)))
temporalFilesMutex.withLock {
temporalFilesMap.remove(min.first)
} ?.delete()
}
}
private suspend fun mapBinary(content: BinaryContent): BinaryContent? {
val provider = content.inputProvider
if (provider is TempFileIdentifierInputProvider) {
val newProvider = temporalFilesMutex.withLock {
temporalFilesMap.remove(provider.tempFile)
} ?.let(::FileBasedInputProvider)
if (newProvider != null) {
return content.copy(
inputProvider = newProvider
)
}
}
return null
}
private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContents(): List<Content> {
return unifiedRouter.run {
if (call.request.isMultipart()) {
val multipart = call.receiveMultipart()
val list = mutableListOf<Pair<String, Content>>()
var part = multipart.readPart()
while (part != null) {
val name = part.name
val capturedPart = part
when {
name == null -> {}
capturedPart is PartData.FormItem -> {
list.add(
name to unifiedRouter.serialFormat.decodeHex(
ContentSerializer,
capturedPart.value
)
)
}
capturedPart is PartData.FileItem -> {
val filename = capturedPart.originalFileName ?.let(::FileName) ?: error("File name is unknown for default part")
val mimeType = capturedPart.contentType ?.let {
findBuiltinMimeType("${it.contentType}/${it.contentSubtype}")
} ?: error("File type is unknown for default part")
val resultInput = MPPFile.createTempFile(
filename.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${filename.extension}"
).apply {
outputStream().use { fileStream ->
capturedPart.provider().asStream().copyTo(fileStream)
}
}
list.add(
name to BinaryContent(
filename,
mimeType,
FileBasedInputProvider(resultInput)
)
)
}
else -> {}
}
part = multipart.readPart()
}
list.sortedBy { it.first }.map { it.second }
} else {
uniload(contentsSerializer)
uniload(contentsSerializer).mapNotNull {
mapBinary(it as? BinaryContent ?: return@mapNotNull it)
}
}
}
private suspend fun PipelineContext<Unit, ApplicationCall>.receiveContentsEithers(): List<Either<ContentId, Content>> {
return unifiedRouter.run {
if (call.request.isMultipart()) {
val multipart = call.receiveMultipart()
val list = mutableListOf<Pair<String, Either<ContentId, Content>>>()
var part = multipart.readPart()
while (part != null) {
val name = part.name
val capturedPart = part
when {
name == null -> {}
capturedPart is PartData.FormItem -> {
list.add(
name to unifiedRouter.serialFormat.decodeHex(
contentEitherSerializer,
capturedPart.value
)
)
}
capturedPart is PartData.FileItem -> {
val filename = capturedPart.originalFileName ?.let(::FileName) ?: error("File name is unknown for default part")
val mimeType = capturedPart.contentType ?.let {
findBuiltinMimeType("${it.contentType}/${it.contentSubtype}")
} ?: error("File type is unknown for default part")
val resultInput = MPPFile.createTempFile(
filename.nameWithoutExtension.let {
var resultName = it
while (resultName.length < 3) {
resultName += "_"
}
resultName
},
".${filename.extension}"
).apply {
outputStream().use { fileStream ->
capturedPart.provider().asStream().copyTo(fileStream)
}
}
list.add(
name to BinaryContent(
filename,
mimeType,
FileBasedInputProvider(resultInput)
).either()
)
}
else -> {}
}
part = multipart.readPart()
}
list.sortedBy { it.first }.map { it.second }
} else {
uniload(contentsEitherSerializer)
uniload(contentsEitherSerializer).mapNotNull {
it.mapOnSecond {
mapBinary(it as? BinaryContent ?: return@mapOnSecond null) ?.either()
} ?: it
}
}
}
override fun Route.invoke() {
authenticate {
route(postsRootPath) {
@ -205,6 +155,41 @@ class ServerPostsServiceRoutingConfigurator(
)
}
post(postsCreateTempPathPart) {
val multipart = call.receiveMultipart()
var fileInfo: Pair<FileId, MPPFile>? = null
var part = multipart.readPart()
while (part != null) {
if (part is PartData.FileItem) {
break
}
part = multipart.readPart()
}
part ?.let {
if (it is PartData.FileItem) {
val fileId = FileId(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.respond(fileId.string)
} ?: call.respond(HttpStatusCode.BadRequest)
}
}
}

@ -0,0 +1,15 @@
package dev.inmo.postssystem.services.posts.server
import dev.inmo.postssystem.features.common.common.SimpleInputProvider
import dev.inmo.postssystem.features.files.common.FileId
import io.ktor.utils.io.core.Input
import kotlinx.serialization.Serializable
@Serializable
internal data class TempFileIdentifierInputProvider(
val tempFile: FileId
) : SimpleInputProvider {
override fun invoke(): Input {
TODO("Not yet implemented")
}
}