A lot of minor fixes

This commit is contained in:
Alexander Nozik 2019-08-07 11:38:25 +03:00
parent a729d27d1c
commit 44737faa26
19 changed files with 288 additions and 54 deletions

View File

@ -12,20 +12,20 @@ kotlin {
dependencies { dependencies {
api(project(":dataforge-meta")) api(project(":dataforge-meta"))
api(kotlin("reflect")) api(kotlin("reflect"))
api("io.github.microutils:kotlin-logging-common:1.6.10") api("io.github.microutils:kotlin-logging-common:1.7.2")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion") api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion")
} }
} }
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
api("io.github.microutils:kotlin-logging:1.6.10") api("io.github.microutils:kotlin-logging:1.7.2")
api("ch.qos.logback:logback-classic:1.2.3") api("ch.qos.logback:logback-classic:1.2.3")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
} }
} }
val jsMain by getting { val jsMain by getting {
dependencies { dependencies {
api("io.github.microutils:kotlin-logging-js:1.6.10") api("io.github.microutils:kotlin-logging-js:1.7.2")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion") api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion")
} }
} }

View File

@ -3,6 +3,7 @@ package hep.dataforge.context
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName
abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin { abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin {
private var _context: Context? = null private var _context: Context? = null
@ -19,4 +20,8 @@ abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin {
} }
override fun provideTop(target: String): Map<Name, Any> = emptyMap() override fun provideTop(target: String): Map<Name, Any> = emptyMap()
companion object{
fun <T: Named> Collection<T>.toMap(): Map<Name, T> = associate { it.name.toName() to it }
}
} }

View File

@ -26,8 +26,10 @@ import kotlin.jvm.JvmName
* Since plugins could contain mutable state, context has two states: active and inactive. No changes are allowed to active context. * Since plugins could contain mutable state, context has two states: active and inactive. No changes are allowed to active context.
* @author Alexander Nozik * @author Alexander Nozik
*/ */
open class Context(final override val name: String, val parent: Context? = Global) : Named, MetaRepr, Provider, open class Context(
CoroutineScope { final override val name: String,
val parent: Context? = Global
) : Named, MetaRepr, Provider, CoroutineScope {
private val config = Config() private val config = Config()
@ -60,10 +62,10 @@ open class Context(final override val name: String, val parent: Context? = Globa
override val defaultTarget: String get() = Plugin.PLUGIN_TARGET override val defaultTarget: String get() = Plugin.PLUGIN_TARGET
override fun provideTop(target: String): Map<Name, Any> { override fun provideTop(target: String): Map<Name, Any> {
return when(target){ return when (target) {
Value.TYPE -> properties.sequence().toMap() Value.TYPE -> properties.sequence().toMap()
Plugin.PLUGIN_TARGET -> plugins.sequence(true).associateBy { it.name.toName() } Plugin.PLUGIN_TARGET -> plugins.sequence(true).associateBy { it.name.toName() }
else-> emptyMap() else -> emptyMap()
} }
} }
@ -111,7 +113,7 @@ open class Context(final override val name: String, val parent: Context? = Globa
fun Context.content(target: String): Map<Name, Any> = content<Any>(target) fun Context.content(target: String): Map<Name, Any> = content<Any>(target)
/** /**
* A sequences of all objects provided by plugins with given target and type * A map of all objects provided by plugins with given target and type
*/ */
@JvmName("typedContent") @JvmName("typedContent")
inline fun <reified T : Any> Context.content(target: String): Map<Name, T> = inline fun <reified T : Any> Context.content(target: String): Map<Name, T> =

View File

@ -77,8 +77,8 @@ private class StaticGoalImpl<T>(override val scope: CoroutineScope, deferred: Co
* *
* **Important:** Unlike regular deferred, the [Goal] is started lazily, so the actual calculation is called only when result is requested. * **Important:** Unlike regular deferred, the [Goal] is started lazily, so the actual calculation is called only when result is requested.
*/ */
fun <R> CoroutineScope.createGoal( fun <R> CoroutineScope.goal(
dependencies: Collection<Goal<*>>, dependencies: Collection<Goal<*>> = emptyList(),
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> R block: suspend CoroutineScope.() -> R
): Goal<R> { ): Goal<R> {
@ -102,7 +102,7 @@ fun <R> CoroutineScope.createGoal(
fun <T, R> Goal<T>.pipe( fun <T, R> Goal<T>.pipe(
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.(T) -> R block: suspend CoroutineScope.(T) -> R
): Goal<R> = createGoal(listOf(this), context) { block(await()) } ): Goal<R> = goal(listOf(this), context) { block(await()) }
/** /**
* Create a joining goal. * Create a joining goal.
@ -112,7 +112,7 @@ fun <T, R> Collection<Goal<T>>.join(
scope: CoroutineScope = first(), scope: CoroutineScope = first(),
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.(Collection<T>) -> R block: suspend CoroutineScope.(Collection<T>) -> R
): Goal<R> = scope.createGoal(this, context) { ): Goal<R> = scope.goal(this, context) {
block(map { it.await() }) block(map { it.await() })
} }
@ -126,6 +126,6 @@ fun <K, T, R> Map<K, Goal<T>>.join(
scope: CoroutineScope = values.first(), scope: CoroutineScope = values.first(),
context: CoroutineContext = EmptyCoroutineContext, context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.(Map<K, T>) -> R block: suspend CoroutineScope.(Map<K, T>) -> R
): Goal<R> = scope.createGoal(this.values, context) { ): Goal<R> = scope.goal(this.values, context) {
block(mapValues { it.value.await() }) block(mapValues { it.value.await() })
} }

View File

@ -14,7 +14,7 @@ kotlin {
sourceSets { sourceSets {
val commonMain by getting{ val commonMain by getting{
dependencies { dependencies {
api(project(":dataforge-meta")) api(project(":dataforge-context"))
} }
} }
val jsMain by getting{ val jsMain by getting{

View File

@ -1,9 +1,12 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.io.EnvelopeFormat.Companion.ENVELOPE_FORMAT_TYPE
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.string import hep.dataforge.meta.string
import hep.dataforge.provider.Type
import kotlinx.io.core.Input import kotlinx.io.core.Input
import kotlinx.io.core.Output
interface Envelope { interface Envelope {
val meta: Meta val meta: Meta
@ -46,4 +49,17 @@ val Envelope.dataType: String? get() = meta[Envelope.ENVELOPE_DATA_TYPE_KEY].str
*/ */
val Envelope.description: String? get() = meta[Envelope.ENVELOPE_DESCRIPTION_KEY].string val Envelope.description: String? get() = meta[Envelope.ENVELOPE_DESCRIPTION_KEY].string
typealias EnvelopeFormat = IOFormat<Envelope> data class PartialEnvelope(val meta: Meta, val dataOffset: UInt, val dataSize: ULong?)
@Type(ENVELOPE_FORMAT_TYPE)
interface EnvelopeFormat : IOFormat<Envelope>{
fun readPartial(input: Input): PartialEnvelope
fun Output.writeEnvelope(envelope: Envelope, format: MetaFormat)
override fun Output.writeObject(obj: Envelope) = writeEnvelope(obj, JsonMetaFormat)
companion object {
const val ENVELOPE_FORMAT_TYPE = "envelopeFormat"
}
}

View File

@ -0,0 +1,39 @@
package hep.dataforge.io
import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.context.content
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import kotlin.reflect.KClass
class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
override val tag: PluginTag get() = Companion.tag
val metaFormats by lazy {
context.content<MetaFormat>(MetaFormat.META_FORMAT_TYPE).values
}
fun metaFormat(key: Short): MetaFormat? = metaFormats.find { it.key == key }
fun metaFormat(name: String): MetaFormat? = metaFormats.find { it.name == name }
override fun provideTop(target: String): Map<Name, Any> {
return when (target) {
MetaFormat.META_FORMAT_TYPE -> internalMetaFormats
EnvelopeFormat.ENVELOPE_FORMAT_TYPE -> mapOf(
TaggedEnvelopeFormat.VERSION.asName() to TaggedEnvelopeFormat(metaFormats)
)
else -> super.provideTop(target)
}
}
companion object : PluginFactory<IOPlugin> {
private val internalMetaFormats = listOf(JsonMetaFormat, BinaryMetaFormat).toMap()
override val tag: PluginTag = PluginTag("io", group = PluginTag.DATAFORGE_GROUP)
override val type: KClass<out IOPlugin> = IOPlugin::class
override fun invoke(meta: Meta): IOPlugin = IOPlugin(meta)
}
}

View File

@ -1,14 +1,18 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.context.Named
import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.io.MetaFormat.Companion.META_FORMAT_TYPE
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.provider.Type
import kotlinx.io.core.* import kotlinx.io.core.*
/** /**
* A format for meta serialization * A format for meta serialization
*/ */
interface MetaFormat : IOFormat<Meta> { @Type(META_FORMAT_TYPE)
val name: String interface MetaFormat : IOFormat<Meta>, Named {
override val name: String
val key: Short val key: Short
override fun Output.writeObject(obj: Meta) { override fun Output.writeObject(obj: Meta) {
@ -17,8 +21,12 @@ interface MetaFormat : IOFormat<Meta> {
override fun Input.readObject(): Meta = readMeta(null) override fun Input.readObject(): Meta = readMeta(null)
fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor? = null)
fun Input.readMeta(descriptor: NodeDescriptor?): Meta fun Input.readMeta(descriptor: NodeDescriptor? = null): Meta
companion object{
const val META_FORMAT_TYPE = "metaFormat"
}
} }
fun Meta.toString(format: MetaFormat = JsonMetaFormat): String = buildPacket { fun Meta.toString(format: MetaFormat = JsonMetaFormat): String = buildPacket {

View File

@ -3,15 +3,29 @@ package hep.dataforge.io
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.toConfig import hep.dataforge.meta.toConfig
import kotlinx.serialization.Decoder import hep.dataforge.names.Name
import kotlinx.serialization.Encoder import hep.dataforge.names.toName
import kotlinx.serialization.KSerializer import kotlinx.serialization.*
import kotlinx.serialization.SerialDescriptor import kotlinx.serialization.internal.StringDescriptor
import kotlinx.serialization.json.JsonObjectSerializer import kotlinx.serialization.json.JsonObjectSerializer
@Serializer(Name::class)
object NameSerializer : KSerializer<Name> {
override val descriptor: SerialDescriptor = StringDescriptor
override fun deserialize(decoder: Decoder): Name {
return decoder.decodeString().toName()
}
override fun serialize(encoder: Encoder, obj: Name) {
encoder.encodeString(obj.toString())
}
}
/** /**
* Serialized for meta * Serialized for meta
*/ */
@Serializer(Meta::class)
object MetaSerializer : KSerializer<Meta> { object MetaSerializer : KSerializer<Meta> {
override val descriptor: SerialDescriptor = JsonObjectSerializer.descriptor override val descriptor: SerialDescriptor = JsonObjectSerializer.descriptor
@ -25,7 +39,8 @@ object MetaSerializer : KSerializer<Meta> {
} }
} }
object ConfigSerializer: KSerializer<Config>{ @Serializer(Config::class)
object ConfigSerializer : KSerializer<Config> {
override val descriptor: SerialDescriptor = JsonObjectSerializer.descriptor override val descriptor: SerialDescriptor = JsonObjectSerializer.descriptor
override fun deserialize(decoder: Decoder): Config { override fun deserialize(decoder: Decoder): Config {

View File

@ -2,14 +2,10 @@ package hep.dataforge.io
import kotlinx.io.core.* import kotlinx.io.core.*
@ExperimentalUnsignedTypes class TaggedEnvelopeFormat(val metaFormats: Collection<MetaFormat>) : EnvelopeFormat {
class TaggedEnvelopeFormat(
val metaFormats: Collection<MetaFormat>,
val outputMetaFormat: MetaFormat = metaFormats.first()
) : EnvelopeFormat {
override fun Output.writeObject(obj: Envelope) { override fun Output.writeEnvelope(envelope: Envelope, format: MetaFormat) {
write(obj, this, outputMetaFormat) write(this, envelope, format)
} }
/** /**
@ -20,6 +16,7 @@ class TaggedEnvelopeFormat(
*/ */
override fun Input.readObject(): Envelope = read(this, metaFormats) override fun Input.readObject(): Envelope = read(this, metaFormats)
override fun readPartial(input: Input): PartialEnvelope = Companion.readPartial(input, metaFormats)
private data class Tag( private data class Tag(
val metaFormatKey: Short, val metaFormatKey: Short,
@ -28,9 +25,10 @@ class TaggedEnvelopeFormat(
) )
companion object { companion object {
private const val VERSION = "DF03" const val VERSION = "DF03"
private const val START_SEQUENCE = "#~" private const val START_SEQUENCE = "#~"
private const val END_SEQUENCE = "~#\r\n" private const val END_SEQUENCE = "~#\r\n"
private const val TAG_SIZE = 26u
private fun Tag.toBytes(): ByteReadPacket = buildPacket(24) { private fun Tag.toBytes(): ByteReadPacket = buildPacket(24) {
writeText(START_SEQUENCE) writeText(START_SEQUENCE)
@ -66,12 +64,24 @@ class TaggedEnvelopeFormat(
return SimpleEnvelope(meta, ArrayBinary(dataBytes)) return SimpleEnvelope(meta, ArrayBinary(dataBytes))
} }
fun write(obj: Envelope, out: Output, metaFormat: MetaFormat) { fun readPartial(input: Input, metaFormats: Collection<MetaFormat>): PartialEnvelope {
val metaBytes = metaFormat.writeBytes(obj.meta) val tag = input.readTag()
val tag = Tag(metaFormat.key, metaBytes.size.toUInt(), obj.data?.size ?: 0.toULong())
val metaFormat = metaFormats.find { it.key == tag.metaFormatKey }
?: error("Meta format with key ${tag.metaFormatKey} not found")
val metaPacket = ByteReadPacket(input.readBytes(tag.metaSize.toInt()))
val meta = metaFormat.run { metaPacket.readObject() }
return PartialEnvelope(meta, TAG_SIZE + tag.metaSize, tag.dataSize)
}
fun write(out: Output, envelope: Envelope, metaFormat: MetaFormat) {
val metaBytes = metaFormat.writeBytes(envelope.meta)
val tag = Tag(metaFormat.key, metaBytes.size.toUInt(), envelope.data?.size ?: 0.toULong())
out.writePacket(tag.toBytes()) out.writePacket(tag.toBytes())
out.writeFully(metaBytes) out.writeFully(metaBytes)
obj.data?.read { copyTo(out) } envelope.data?.read { copyTo(out) }
} }
} }
} }

View File

@ -7,15 +7,13 @@ import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardOpenOption import java.nio.file.StandardOpenOption
class FileBinary(val path: Path, private val offset: Int = 0) : RandomAccessBinary { class FileBinary(val path: Path, private val offset: UInt = 0u, size: ULong? = null) : RandomAccessBinary {
override val size: ULong
get() = (Files.size(path) - offset).toULong()
override val size: ULong = size ?: (Files.size(path).toULong() - offset).toULong()
override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R { override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
FileChannel.open(path, StandardOpenOption.READ).use { FileChannel.open(path, StandardOpenOption.READ).use {
val buffer = it.map(FileChannel.MapMode.READ_ONLY, (from.toLong() + offset), size.toLong()) val buffer = it.map(FileChannel.MapMode.READ_ONLY, (from + offset).toLong(), size.toLong())
return ByteReadPacket(buffer).block() return ByteReadPacket(buffer).block()
} }
} }

View File

@ -0,0 +1,24 @@
package hep.dataforge.io
import hep.dataforge.meta.Meta
import kotlinx.io.nio.asInput
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
class FileEnvelope internal constructor(val path: Path, val format: EnvelopeFormat) : Envelope {
//TODO do not like this constructor. Hope to replace it later
private val partialEnvelope: PartialEnvelope
init {
val input = Files.newByteChannel(path, StandardOpenOption.READ).asInput()
partialEnvelope = format.readPartial(input)
}
override val meta: Meta get() = partialEnvelope.meta
override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset, partialEnvelope.dataSize)
}
fun Path.readEnvelope(format: EnvelopeFormat) = FileEnvelope(this,format)

View File

@ -17,7 +17,7 @@ class Laminate(layers: List<Meta>) : Meta {
} }
} }
constructor(vararg layers: Meta) : this(layers.asList()) constructor(vararg layers: Meta?) : this(layers.filterNotNull())
override val items: Map<NameToken, MetaItem<Meta>> override val items: Map<NameToken, MetaItem<Meta>>
get() = layers.map { it.items.keys }.flatten().associateWith { key -> get() = layers.map { it.items.keys }.flatten().associateWith { key ->

View File

@ -8,7 +8,7 @@ import hep.dataforge.values.Value
* DSL builder for meta. Is not intended to store mutable state * DSL builder for meta. Is not intended to store mutable state
*/ */
class MetaBuilder : AbstractMutableMeta<MetaBuilder>() { class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
override fun wrapNode(meta: Meta): MetaBuilder = meta.builder() override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.builder()
override fun empty(): MetaBuilder = MetaBuilder() override fun empty(): MetaBuilder = MetaBuilder()
infix fun String.to(value: Any) { infix fun String.to(value: Any) {

View File

@ -51,32 +51,55 @@ data class NameToken(val body: String, val index: String = "") {
if (body.isEmpty()) error("Syntax error: Name token body is empty") if (body.isEmpty()) error("Syntax error: Name token body is empty")
} }
private fun String.escape() =
replace("\\", "\\\\")
.replace(".", "\\.")
.replace("[", "\\[")
.replace("]", "\\]")
override fun toString(): String = if (hasIndex()) { override fun toString(): String = if (hasIndex()) {
"$body[$index]" "${body.escape()}[$index]"
} else { } else {
body body.escape()
} }
fun hasIndex() = index.isNotEmpty() fun hasIndex() = index.isNotEmpty()
} }
/**
* Convert a [String] to name parsing it and extracting name tokens and index syntax.
* This operation is rather heavy so it should be used with care in high performance code.
*/
fun String.toName(): Name { fun String.toName(): Name {
if (isBlank()) return EmptyName if (isBlank()) return EmptyName
val tokens = sequence { val tokens = sequence {
var bodyBuilder = StringBuilder() var bodyBuilder = StringBuilder()
var queryBuilder = StringBuilder() var queryBuilder = StringBuilder()
var bracketCount: Int = 0 var bracketCount: Int = 0
var escape: Boolean = false
fun queryOn() = bracketCount > 0 fun queryOn() = bracketCount > 0
asSequence().forEach { for (it in this@toName) {
if (queryOn()) { when {
when (it) { escape -> {
'[' -> bracketCount++ if (queryOn()) {
']' -> bracketCount-- queryBuilder.append(it)
} else {
bodyBuilder.append(it)
}
escape = false
} }
if (queryOn()) queryBuilder.append(it) it == '\\' -> {
} else { escape = true
when (it) { }
queryOn() -> {
when (it) {
'[' -> bracketCount++
']' -> bracketCount--
}
if (queryOn()) queryBuilder.append(it)
}
else -> when (it) {
'.' -> { '.' -> {
yield(NameToken(bodyBuilder.toString(), queryBuilder.toString())) yield(NameToken(bodyBuilder.toString(), queryBuilder.toString()))
bodyBuilder = StringBuilder() bodyBuilder = StringBuilder()
@ -96,6 +119,14 @@ fun String.toName(): Name {
return Name(tokens.toList()) return Name(tokens.toList())
} }
/**
* Convert the [String] to a [Name] by simply wrapping it in a single name token without parsing.
* The input string could contain dots and braces, but they are just escaped, not parsed.
*/
fun String.asName(): Name {
return NameToken(this).asName()
}
operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens) operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens)
operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens) operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)

View File

@ -1,6 +1,9 @@
package hep.dataforge.names package hep.dataforge.names
import kotlin.test.* import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NameTest { class NameTest {
@Test @Test
@ -25,4 +28,14 @@ class NameTest {
assertTrue { name1.endsWith(name3) } assertTrue { name1.endsWith(name3) }
assertFalse { name1.startsWith(name3) } assertFalse { name1.startsWith(name3) }
} }
@Test
fun escapeTest(){
val escapedName = "token\\.one.token2".toName()
val unescapedName = "token\\.one.token2".asName()
assertEquals(2, escapedName.length)
assertEquals(1, unescapedName.length)
assertEquals(escapedName, escapedName.toString().toName())
}
} }

View File

@ -10,6 +10,7 @@ kotlin {
dependencies { dependencies {
api(project(":dataforge-context")) api(project(":dataforge-context"))
api(project(":dataforge-data")) api(project(":dataforge-data"))
api(project(":dataforge-output"))
} }
} }
} }

View File

@ -0,0 +1,72 @@
package hep.dataforge.workspace
import hep.dataforge.context.Context
import hep.dataforge.data.Data
import hep.dataforge.data.goal
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.io.IOFormat
import hep.dataforge.io.JsonMetaFormat
import hep.dataforge.io.MetaFormat
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext
import kotlinx.io.nio.asInput
import kotlinx.io.nio.asOutput
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import kotlin.reflect.KClass
/**
* Read meta from file in a given [format]
*/
suspend fun Path.readMeta(format: MetaFormat, descriptor: NodeDescriptor? = null): Meta {
return withContext(Dispatchers.IO) {
format.run {
Files.newByteChannel(this@readMeta, StandardOpenOption.READ)
.asInput()
.readMeta(descriptor)
}
}
}
/**
* Write meta to file in a given [format]
*/
suspend fun Meta.write(path: Path, format: MetaFormat, descriptor: NodeDescriptor? = null) {
withContext(Dispatchers.IO) {
format.run {
Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)
.asOutput()
.writeMeta(this@write, descriptor)
}
}
}
suspend fun <T : Any> Context.readData(
type: KClass<out T>,
path: Path,
format: IOFormat<T>,
metaFile: Path = path.resolveSibling("${path.fileName}.meta"),
metaFileFormat: MetaFormat = JsonMetaFormat
): Data<T> {
return coroutineScope {
val externalMeta = if (Files.exists(metaFile)) {
metaFile.readMeta(metaFileFormat)
} else {
null
}
val goal = goal {
withContext(Dispatchers.IO) {
format.run {
Files.newByteChannel(path, StandardOpenOption.READ)
.asInput()
.readObject()
}
}
}
Data.of(type, goal, externalMeta ?: EmptyMeta)
}
}