core/features/content/binary/server/src/jvmMain/kotlin/dev/inmo/postssystem/features/content/binary/server/BinaryServerContentStorage.kt

148 lines
5.7 KiB
Kotlin

package dev.inmo.postssystem.features.content.binary.server
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.coroutines.plus
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.mime_types.KnownMimeTypes
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.*
import dev.inmo.postssystem.features.common.common.FileBasedInputProvider
import dev.inmo.postssystem.features.content.common.*
import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage
import dev.inmo.postssystem.features.files.common.*
import dev.inmo.postssystem.features.files.common.storage.FilesStorage
import io.ktor.util.asStream
import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.streams.asOutput
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.io.File
class BinaryServerContentStorage(
private val filesStorage: FilesStorage,
private val previewFilesStorage: FilesStorage,
private val cropper: ImagesCropper,
private val scope: CoroutineScope
) : ServerContentStorage<BinaryContent> {
private val FileId.asContentId
get() = ContentId(string)
private val ContentId.asFileId
get() = FileId(string)
private val FullFileInfoStorageWrapper.asRegisteredContent
get() = RegisteredContent(
id.asContentId,
BinaryContent(
fileInfo.name,
fileInfo.mimeType,
fileInfo.inputProvider
)
)
private val BinaryContent.asFullFileInfo
get() = FullFileInfo(
filename,
mimeType,
inputProvider
)
override val deletedObjectsIdsFlow: Flow<ContentId> = filesStorage.deletedObjectsIdsFlow.map { it.asContentId }
override val newObjectsFlow: Flow<RegisteredContent> = filesStorage.newObjectsFlow.map { it.asRegisteredContent }
override val updatedObjectsFlow: Flow<RegisteredContent> = filesStorage.updatedObjectsFlow.map { it.asRegisteredContent }
private val fullsRemovedJob = deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(scope) {
previewFilesStorage.deleteById(it.asFileId)
}
private val previewCroppingJob = (newObjectsFlow + updatedObjectsFlow).subscribeSafelyWithoutExceptions(scope) {
val content = it.content
val fileId = it.id.asFileId
if (content !is BinaryContent) {
return@subscribeSafelyWithoutExceptions
}
val fullFileInfo = filesStorage.getFullFileInfo(fileId) ?: return@subscribeSafelyWithoutExceptions
val fileInfo = fullFileInfo.fileInfo
if (fileInfo.mimeType is KnownMimeTypes.Image) {
cropper.crop(fileInfo.inputProvider).subscribeSafelyWithoutExceptions(scope) {
val tempFile = File.createTempFile(uuid4().toString(), ".${fileInfo.name.extension}").apply {
deleteOnExit()
createNewFile()
}
runCatching {
tempFile.outputStream().use { output ->
it.asStream().use { input ->
input.copyTo(output)
}
}
val newFullFileInfo = FullFileInfo(
fileInfo.name,
fileInfo.mimeType,
FileBasedInputProvider(tempFile)
)
if (previewFilesStorage.contains(fileId)) {
previewFilesStorage.update(
fullFileInfo.id,
newFullFileInfo
)
} else {
previewFilesStorage.create(newFullFileInfo).firstOrNull()
}
}
tempFile.delete()
}
}
}
override suspend fun create(values: List<BinaryContent>): List<RegisteredContent> {
return filesStorage.create(
values.map { it.asFullFileInfo }
).map { it.asRegisteredContent }
}
override suspend fun deleteById(ids: List<ContentId>) {
filesStorage.deleteById(ids.map { it.asFileId })
}
override suspend fun update(id: ContentId, value: BinaryContent): RegisteredContent? {
return filesStorage.update(
id.asFileId,
value.asFullFileInfo
) ?.asRegisteredContent
}
override suspend fun update(values: List<UpdatedValuePair<ContentId, BinaryContent>>): List<RegisteredContent> {
return filesStorage.update(
values.map { (id, content) ->
id.asFileId to content.asFullFileInfo
}
).map {
it.asRegisteredContent
}
}
override suspend fun contains(id: ContentId): Boolean {
return filesStorage.contains(id.asFileId)
}
override suspend fun count(): Long = filesStorage.count()
override suspend fun getById(id: ContentId): RegisteredContent? {
return filesStorage.getFullFileInfo(id.asFileId) ?.asRegisteredContent
}
override suspend fun getByPagination(pagination: Pagination): PaginationResult<RegisteredContent> {
return filesStorage.getByPagination(pagination).let {
it.changeResultsUnchecked(
it.results.mapNotNull {
filesStorage.getFullFileInfo(it.id) ?.asRegisteredContent
}
)
}
}
override suspend fun getContentPreview(id: ContentId): RegisteredContent? {
val fileId = id.asFileId
val fileInfo = previewFilesStorage.getFullFileInfo(fileId) ?: return null
return fileInfo.asRegisteredContent
}
}