temp progress on binary correct including
This commit is contained in:
features/common/common
build.gradle
src
commonMain
kotlin
dev
inmo
postssystem
features
common
common
jsMain
kotlin
dev
inmo
postssystem
features
common
common
jvmMain
kotlin
dev
inmo
postssystem
features
common
common
services/posts
client
src
commonMain
kotlin
dev
inmo
postssystem
jsMain
kotlin
dev
inmo
postssystem
services
posts
client
jvmMain
kotlin
dev
inmo
postssystem
services
posts
client
common
server
src
jvmMain
kotlin
dev
inmo
postssystem
services
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
15
services/posts/server/src/jvmMain/kotlin/dev/inmo/postssystem/services/posts/server/TempFileIdentifierInputProvider.kt
Normal file
15
services/posts/server/src/jvmMain/kotlin/dev/inmo/postssystem/services/posts/server/TempFileIdentifierInputProvider.kt
Normal file
@ -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")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user