full reborn

This commit is contained in:
2021-11-24 13:52:27 +06:00
parent 0ac6b0a4df
commit 6a6a197041
246 changed files with 4327 additions and 6952 deletions

View File

@@ -0,0 +1,18 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":postssystem.features.files.common")
api project(":postssystem.features.common.client")
}
}
}
}

View File

@@ -0,0 +1,48 @@
package dev.inmo.postssystem.features.files.client
import dev.inmo.postssystem.features.files.common.*
import dev.inmo.postssystem.features.files.common.storage.FilesStorage
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
import dev.inmo.micro_utils.ktor.common.buildStandardUrl
import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.micro_utils.repos.ktor.client.crud.KtorReadStandardCrudRepo
import io.ktor.client.HttpClient
import io.ktor.client.request.post
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.readBytes
import kotlinx.serialization.BinaryFormat
import kotlinx.serialization.builtins.nullable
class ClientFilesStorage(
baseUrl: String,
private val client: HttpClient,
private val serialFormat: BinaryFormat
) : FilesStorage, ReadCRUDRepo<MetaFileInfoStorageWrapper, FileId> by KtorReadStandardCrudRepo(
buildStandardUrl(baseUrl, filesRootPathPart),
UnifiedRequester(client, serialFormat),
MetaFileInfoStorageWrapper.serializer(),
MetaFileInfoStorageWrapper.serializer().nullable,
FileId.serializer()
) {
private val unifiedRequester = UnifiedRequester(client, serialFormat)
private val fullFilesPath = buildStandardUrl(baseUrl, filesRootPathPart)
private val fullFilesGetBytesPath = buildStandardUrl(
fullFilesPath,
filesGetFilesPathPart
)
override suspend fun getBytes(id: FileId): ByteArray = client.post<HttpResponse>(fullFilesGetBytesPath) {
body = serialFormat.encodeToByteArray(FileId.serializer(), id)
}.readBytes()
override suspend fun getFullFileInfo(
id: FileId
): FullFileInfoStorageWrapper? = unifiedRequester.uniget(
buildStandardUrl(
fullFilesPath,
filesGetFullFileInfoPathPart,
filesFileIdParameter to unifiedRequester.encodeUrlQueryValue(FileId.serializer(), id)
),
FullFileInfoStorageWrapper.serializer().nullable
)
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.postssystem.features.files.client"/>

View File

@@ -0,0 +1,19 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":postssystem.features.common.common")
api "dev.inmo:micro_utils.mime_types:$microutils_version"
api "dev.inmo:micro_utils.repos.common:$microutils_version"
}
}
}
}

View File

@@ -0,0 +1,6 @@
package dev.inmo.postssystem.features.files.common
const val filesRootPathPart = "files"
const val filesGetFilesPathPart = "getFiles"
const val filesGetFullFileInfoPathPart = "getFullFileInfo"
const val filesFileIdParameter = "fileId"

View File

@@ -0,0 +1,35 @@
package dev.inmo.postssystem.features.files.common
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.mime_types.MimeType
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
@Serializable(FileInfoSerializer::class)
sealed interface FileInfo {
val name: FileName
val mimeType: MimeType
companion object {
fun serializer(): KSerializer<FileInfo> = FileInfoSerializer
}
}
object FileInfoSerializer : KSerializer<FileInfo> by TypedSerializer(
"meta" to MetaFileInfo.serializer(),
"full" to FullFileInfo.serializer(),
)
@Serializable
data class MetaFileInfo(override val name: FileName, override val mimeType: MimeType) : FileInfo
@Serializable
data class FullFileInfo(
override val name: FileName,
override val mimeType: MimeType,
@Serializable(ByteArrayAllocatorSerializer::class)
val byteArrayAllocator: ByteArrayAllocator
) : FileInfo
fun FullFileInfo.toMetaFileInfo() = MetaFileInfo(name, mimeType)

View File

@@ -0,0 +1,21 @@
package dev.inmo.postssystem.features.files.common
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
@Serializable
@JvmInline
value class FileId(val string: String) {
override fun toString(): String = string.toString()
}
@Serializable
sealed class FileInfoStorageWrapper {
abstract val id: FileId
abstract val fileInfo: FileInfo
}
@Serializable
data class MetaFileInfoStorageWrapper(override val id: FileId, override val fileInfo: MetaFileInfo) : FileInfoStorageWrapper()
@Serializable
data class FullFileInfoStorageWrapper(override val id: FileId, override val fileInfo: FullFileInfo) : FileInfoStorageWrapper()

View File

@@ -0,0 +1,9 @@
package dev.inmo.postssystem.features.files.common.storage
import dev.inmo.postssystem.features.files.common.*
import dev.inmo.micro_utils.repos.ReadCRUDRepo
interface FilesStorage : ReadCRUDRepo<MetaFileInfoStorageWrapper, FileId> {
suspend fun getBytes(id: FileId): ByteArray
suspend fun getFullFileInfo(id: FileId): FullFileInfoStorageWrapper?
}

View File

@@ -0,0 +1,8 @@
package dev.inmo.postssystem.features.files.common.storage
interface FullFilesStorage : FilesStorage, WriteFilesStorage
class DefaultFullFilesStorage(
filesStorage: FilesStorage,
writeFilesStorage: WriteFilesStorage
) : FullFilesStorage, FilesStorage by filesStorage, WriteFilesStorage by writeFilesStorage

View File

@@ -0,0 +1,6 @@
package dev.inmo.postssystem.features.files.common.storage
import dev.inmo.postssystem.features.files.common.*
import dev.inmo.micro_utils.repos.WriteCRUDRepo
interface WriteFilesStorage : WriteCRUDRepo<FullFileInfoStorageWrapper, FileId, FullFileInfo>

View File

@@ -0,0 +1,63 @@
package dev.inmo.postssystem.features.files.common
import dev.inmo.postssystem.features.files.common.storage.FilesStorage
import dev.inmo.micro_utils.pagination.*
import dev.inmo.micro_utils.repos.ReadKeyValueRepo
import java.io.File
class DiskFilesStorage(
private val filesFolder: File,
private val metasKeyValueRepo: ReadKeyValueRepo<FileId, MetaFileInfo>
) : FilesStorage {
private val FileId.file
get() = File(filesFolder, string)
init {
if (!filesFolder.exists()) {
filesFolder.mkdirs()
} else {
require(filesFolder.isDirectory) { "$filesFolder must be a directory" }
}
}
private suspend fun FileId.meta(): MetaFileInfoStorageWrapper? {
return MetaFileInfoStorageWrapper(
this,
metasKeyValueRepo.get(this) ?: return null
)
}
override suspend fun getBytes(id: FileId): ByteArray = id.file.readBytes()
override suspend fun getFullFileInfo(id: FileId): FullFileInfoStorageWrapper? = getById(
id
) ?.let {
FullFileInfoStorageWrapper(
id,
FullFileInfo(
it.fileInfo.name,
it.fileInfo.mimeType
) {
id.file.readBytes()
}
)
}
override suspend fun contains(id: FileId): Boolean = metasKeyValueRepo.contains(id)
override suspend fun count(): Long = metasKeyValueRepo.count()
override suspend fun getById(id: FileId): MetaFileInfoStorageWrapper? = id.meta()
override suspend fun getByPagination(pagination: Pagination): PaginationResult<MetaFileInfoStorageWrapper> {
val keys = metasKeyValueRepo.keys(pagination)
return keys.changeResults(
keys.results.mapNotNull {
MetaFileInfoStorageWrapper(
it,
metasKeyValueRepo.get(it) ?: return@mapNotNull null
)
}
)
}
}

View File

@@ -0,0 +1,15 @@
package dev.inmo.postssystem.features.files.common
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.mappers.withMapper
import kotlinx.serialization.StringFormat
fun MetasKeyValueRepo(
serialFormat: StringFormat,
originalRepo: KeyValueRepo<String, String>
) = originalRepo.withMapper<FileId, MetaFileInfo, String, String>(
{ string },
{ serialFormat.encodeToString(MetaFileInfo.serializer(), this) },
{ FileId(this) },
{ serialFormat.decodeFromString(MetaFileInfo.serializer(), this) }
)

View File

@@ -0,0 +1,69 @@
package dev.inmo.postssystem.features.files.common
import com.benasher44.uuid.uuid4
import dev.inmo.postssystem.features.files.common.storage.WriteFilesStorage
import dev.inmo.micro_utils.repos.*
import kotlinx.coroutines.flow.*
import java.io.File
class WriteDistFilesStorage(
private val filesFolder: File,
private val metasKeyValueRepo: WriteKeyValueRepo<FileId, MetaFileInfo>
) : WriteFilesStorage {
private val FileId.file
get() = File(filesFolder, string)
private val _deletedObjectsIdsFlow = MutableSharedFlow<FileId>()
private val _newObjectsFlow = MutableSharedFlow<FullFileInfoStorageWrapper>()
private val _updatedObjectsFlow = MutableSharedFlow<FullFileInfoStorageWrapper>()
override val deletedObjectsIdsFlow: Flow<FileId> = _deletedObjectsIdsFlow.asSharedFlow()
override val newObjectsFlow: Flow<FullFileInfoStorageWrapper> = _newObjectsFlow.asSharedFlow()
override val updatedObjectsFlow: Flow<FullFileInfoStorageWrapper> = _updatedObjectsFlow.asSharedFlow()
init {
if (!filesFolder.exists()) {
filesFolder.mkdirs()
} else {
require(filesFolder.isDirectory) { "$filesFolder must be a directory" }
}
}
override suspend fun create(values: List<FullFileInfo>): List<FullFileInfoStorageWrapper> = values.map {
var newId: FileId
var file: File
do {
newId = FileId(uuid4().toString())
file = newId.file
} while (file.exists())
metasKeyValueRepo.set(newId, it.toMetaFileInfo())
file.writeBytes(it.byteArrayAllocator())
FullFileInfoStorageWrapper(newId, it)
}
override suspend fun deleteById(ids: List<FileId>) {
ids.forEach {
if (it.file.delete()) {
metasKeyValueRepo.unset(it)
_deletedObjectsIdsFlow.emit(it)
}
}
}
override suspend fun update(
id: FileId,
value: FullFileInfo
): FullFileInfoStorageWrapper? = id.file.takeIf { it.exists() } ?.writeBytes(value.byteArrayAllocator()) ?.let {
val result = FullFileInfoStorageWrapper(id, value.copy())
metasKeyValueRepo.set(id, value.toMetaFileInfo())
_updatedObjectsFlow.emit(result)
result
}
override suspend fun update(
values: List<UpdatedValuePair<FileId, FullFileInfo>>
): List<FullFileInfoStorageWrapper> = values.mapNotNull { (id, file) ->
update(id, file)
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.postssystem.features.files.common"/>

View File

@@ -0,0 +1,17 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
}
apply from: "$mppJavaProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":postssystem.features.files.common")
api project(":postssystem.features.common.server")
}
}
}
}

View File

@@ -0,0 +1,57 @@
package dev.inmo.postssystem.features.files.server
import dev.inmo.postssystem.features.files.common.*
import dev.inmo.postssystem.features.files.common.storage.*
import dev.inmo.micro_utils.ktor.server.*
import dev.inmo.micro_utils.ktor.server.configurators.ApplicationRoutingConfigurator
import dev.inmo.micro_utils.repos.ktor.server.crud.configureReadStandardCrudRepoRoutes
import dev.inmo.micro_utils.repos.ktor.server.crud.configureWriteStandardCrudRepoRoutes
import io.ktor.application.call
import io.ktor.auth.authenticate
import io.ktor.response.respondBytes
import io.ktor.routing.*
import kotlinx.serialization.builtins.nullable
class FilesRoutingConfigurator(
private val filesStorage: FilesStorage,
private val writeFilesStorage: WriteFilesStorage?
) : ApplicationRoutingConfigurator.Element {
constructor(fullFilesStorage: FullFilesStorage) : this(fullFilesStorage, fullFilesStorage)
override fun Route.invoke() {
authenticate {
route(filesRootPathPart) {
configureReadStandardCrudRepoRoutes(
filesStorage,
MetaFileInfoStorageWrapper.serializer(),
MetaFileInfoStorageWrapper.serializer().nullable,
FileId.serializer()
)
writeFilesStorage ?.let {
configureWriteStandardCrudRepoRoutes(
writeFilesStorage,
FullFileInfoStorageWrapper.serializer(),
FullFileInfoStorageWrapper.serializer().nullable,
FullFileInfo.serializer(),
FileId.serializer()
)
}
post(filesGetFilesPathPart) {
call.respondBytes(
filesStorage.getBytes(
call.uniload(FileId.serializer())
)
)
}
get(filesGetFullFileInfoPathPart) {
call.unianswer(
FullFileInfoStorageWrapper.serializer().nullable,
filesStorage.getFullFileInfo(
call.decodeUrlQueryValueOrSendError(filesFileIdParameter, FileId.serializer()) ?: return@get
)
)
}
}
}
}
}