forked from kscience/visionforge
WIP ROOT json serialization
This commit is contained in:
parent
a748282d63
commit
5d2c853cbe
@ -37,8 +37,7 @@ apiValidation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
afterEvaluate {
|
//workaround for https://youtrack.jetbrains.com/issue/KT-48273
|
||||||
extensions.configure<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension> {
|
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) {
|
||||||
versions.webpackDevServer.version = "4.0.0"
|
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>().versions.webpackDevServer.version = "4.0.0"
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,25 +0,0 @@
|
|||||||
package ru.mipt.npm.root
|
|
||||||
|
|
||||||
import kotlin.properties.PropertyDelegateProvider
|
|
||||||
import kotlin.reflect.KType
|
|
||||||
import kotlin.reflect.typeOf
|
|
||||||
|
|
||||||
public interface RootValueProvider {
|
|
||||||
/**
|
|
||||||
* Provide a member cast or reinterpreted to given type.
|
|
||||||
* Returns null if member with given name/type could not be resolved.
|
|
||||||
*/
|
|
||||||
public fun <T : Any> provideOrNull(name: String, type: KType): T?
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface RootModel {
|
|
||||||
public val provider: RootValueProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
public inline fun <reified T : Any> RootValueProvider.provide(name: String): T =
|
|
||||||
provideOrNull(name, typeOf<T>()) ?: error("A member with type ${T::class} and name $name could not be resolved")
|
|
||||||
|
|
||||||
public inline fun <reified T : Any> RootModel.member(name: String? = null): PropertyDelegateProvider<Any?, Lazy<T>> =
|
|
||||||
PropertyDelegateProvider { _, property ->
|
|
||||||
lazy { provider.provide(name ?: property.name) }
|
|
||||||
}
|
|
@ -1,15 +1,19 @@
|
|||||||
package ru.mipt.npm.root
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoMatrix")
|
||||||
public sealed class TGeoMatrix : TNamed()
|
public sealed class TGeoMatrix : TNamed()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoIdentity")
|
||||||
public class TGeoIdentity : TGeoMatrix()
|
public class TGeoIdentity : TGeoMatrix()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoHMatrix")
|
||||||
public class TGeoHMatrix(
|
public class TGeoHMatrix(
|
||||||
public val fTranslation: DoubleArray,
|
public val fTranslation: DoubleArray,
|
||||||
public val fRotationMatrix: DoubleArray,
|
public val fRotationMatrix: DoubleArray,
|
||||||
@ -17,16 +21,19 @@ public class TGeoHMatrix(
|
|||||||
) : TGeoMatrix()
|
) : TGeoMatrix()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoTranslation")
|
||||||
public class TGeoTranslation(
|
public class TGeoTranslation(
|
||||||
public val fTranslation: DoubleArray
|
public val fTranslation: DoubleArray
|
||||||
) : TGeoMatrix()
|
) : TGeoMatrix()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoRotation")
|
||||||
public class TGeoRotation(
|
public class TGeoRotation(
|
||||||
public val fRotationMatrix: DoubleArray
|
public val fRotationMatrix: DoubleArray
|
||||||
): TGeoMatrix()
|
): TGeoMatrix()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoCombiTrans")
|
||||||
public class TGeoCombiTrans(
|
public class TGeoCombiTrans(
|
||||||
public val fTranslation: DoubleArray,
|
public val fTranslation: DoubleArray,
|
||||||
public val fRotation: TGeoRotation? = null,
|
public val fRotation: TGeoRotation? = null,
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
package ru.mipt.npm.root
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonDecoder
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.modules.SerializersModule
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import kotlinx.serialization.modules.polymorphic
|
||||||
|
import kotlinx.serialization.modules.subclass
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoManager")
|
||||||
public class TGeoManager : TNamed() {
|
public class TGeoManager : TNamed() {
|
||||||
|
|
||||||
public val fMatrices: TObjArray = TObjArray.empty
|
public val fMatrices: TObjArray = TObjArray.empty
|
||||||
@ -20,19 +27,31 @@ public class TGeoManager : TNamed() {
|
|||||||
* Load Json encoded TGeoManager
|
* Load Json encoded TGeoManager
|
||||||
*/
|
*/
|
||||||
public fun decodeFromJson(jsonObject: JsonObject): TGeoManager = TODO()
|
public fun decodeFromJson(jsonObject: JsonObject): TGeoManager = TODO()
|
||||||
|
|
||||||
public fun decodeFromString(string: String): TGeoManager =
|
public fun decodeFromString(string: String): TGeoManager =
|
||||||
RootJsonSerialFormat().decodeFromString(serializer(), string)
|
Root().decodeFromString(serializer(), string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
private class RootJsonSerialFormat : StringFormat {
|
private class RootJsonSerializer<T>(private val tSerializer: KSerializer<T>) : KSerializer<T> {
|
||||||
|
|
||||||
override val serializersModule: SerializersModule get() = json.serializersModule
|
|
||||||
|
|
||||||
private val refCache: HashMap<UInt, TObject> = HashMap()
|
private val refCache: HashMap<UInt, TObject> = HashMap()
|
||||||
|
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor get() = tSerializer.descriptor
|
||||||
|
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): T {
|
||||||
|
val input = decoder as JsonDecoder
|
||||||
|
val element = input.decodeJsonElement()
|
||||||
|
return input.json.decodeFromJsonElement(tSerializer, transformDeserialize(element))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: T) {
|
||||||
|
tSerializer.serialize(encoder, value)
|
||||||
|
}
|
||||||
|
|
||||||
override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T {
|
override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T {
|
||||||
val match = refRegex.matchEntire(string)
|
val match = refRegex.matchEntire(string)
|
||||||
return if (match != null) {
|
return if (match != null) {
|
||||||
@ -41,7 +60,7 @@ private class RootJsonSerialFormat : StringFormat {
|
|||||||
val refValue = refCache[ref] ?: error("Reference $ref unresolved")
|
val refValue = refCache[ref] ?: error("Reference $ref unresolved")
|
||||||
refValue as T //TODO research means to make it safe
|
refValue as T //TODO research means to make it safe
|
||||||
} else {
|
} else {
|
||||||
val res = json.decodeFromString(deserializer, string)
|
val res = rootJson.decodeFromString(deserializer, string)
|
||||||
val uid = (res as? TObject)?.fUniqueID
|
val uid = (res as? TObject)?.fUniqueID
|
||||||
if (uid != null && refCache[uid] == null) {
|
if (uid != null && refCache[uid] == null) {
|
||||||
refCache[uid] = res
|
refCache[uid] = res
|
||||||
@ -50,17 +69,70 @@ private class RootJsonSerialFormat : StringFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String =
|
override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String =
|
||||||
json.encodeToString(serializer, value)
|
rootJson.encodeToString(serializer, value)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val refRegex = """\{\s*"${"\\$"}ref"\s*:\s*(\d*)}""".toRegex()
|
val refRegex = """\{\s*"${"\\$"}ref"\s*:\s*(\d*)}""".toRegex()
|
||||||
|
|
||||||
val json: Json = Json {
|
val rootSerializersModule = SerializersModule {
|
||||||
|
polymorphic(TGeoShape::class) {
|
||||||
|
subclass(TGeoBBox.serializer())
|
||||||
|
subclass(TGeoCompositeShape.serializer())
|
||||||
|
subclass(TGeoXtru.serializer())
|
||||||
|
subclass(TGeoTube.serializer())
|
||||||
|
subclass(TGeoTubeSeg.serializer())
|
||||||
|
subclass(TGeoShapeAssembly.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
polymorphic(TGeoMatrix::class) {
|
||||||
|
subclass(TGeoIdentity.serializer())
|
||||||
|
subclass(TGeoHMatrix.serializer())
|
||||||
|
subclass(TGeoTranslation.serializer())
|
||||||
|
subclass(TGeoRotation.serializer())
|
||||||
|
subclass(TGeoCombiTrans.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
polymorphic(TObject::class) {
|
||||||
|
subclass(TGeoBBox.serializer())
|
||||||
|
subclass(TGeoCompositeShape.serializer())
|
||||||
|
subclass(TGeoXtru.serializer())
|
||||||
|
subclass(TGeoTube.serializer())
|
||||||
|
subclass(TGeoTubeSeg.serializer())
|
||||||
|
subclass(TGeoShapeAssembly.serializer())
|
||||||
|
|
||||||
|
subclass(TGeoIdentity.serializer())
|
||||||
|
subclass(TGeoHMatrix.serializer())
|
||||||
|
subclass(TGeoTranslation.serializer())
|
||||||
|
subclass(TGeoRotation.serializer())
|
||||||
|
subclass(TGeoCombiTrans.serializer())
|
||||||
|
|
||||||
|
subclass(TGeoMaterial.serializer())
|
||||||
|
subclass(TGeoMixture.serializer())
|
||||||
|
|
||||||
|
subclass(TGeoMedium.serializer())
|
||||||
|
|
||||||
|
subclass(TGeoNode.serializer())
|
||||||
|
subclass(TGeoNodeMatrix.serializer())
|
||||||
|
subclass(TGeoVolume.serializer())
|
||||||
|
subclass(TGeoVolumeAssembly.serializer())
|
||||||
|
}
|
||||||
|
polymorphic(TGeoNode::class, TGeoNode.serializer()) {
|
||||||
|
subclass(TGeoNodeMatrix.serializer())
|
||||||
|
}
|
||||||
|
polymorphic(TGeoVolume::class, TGeoVolume.serializer()) {
|
||||||
|
subclass(TGeoVolumeAssembly.serializer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val rootJson: Json = Json {
|
||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
classDiscriminator = "_typename"
|
classDiscriminator = "_typename"
|
||||||
|
serializersModule = rootSerializersModule
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package ru.mipt.npm.root
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoMaterial")
|
||||||
public open class TGeoMaterial: TNamed()
|
public open class TGeoMaterial: TNamed()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoMixture")
|
||||||
public class TGeoMixture: TGeoMaterial()
|
public class TGeoMixture: TGeoMaterial()
|
@ -1,8 +1,10 @@
|
|||||||
package ru.mipt.npm.root
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoMedium")
|
||||||
public class TGeoMedium(
|
public class TGeoMedium(
|
||||||
public val fId : Int,
|
public val fId : Int,
|
||||||
public val fMaterial: TGeoMaterial,
|
public val fMaterial: TGeoMaterial,
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package ru.mipt.npm.root
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public class TGeoNode : TNamed() {
|
@SerialName("TGeoNode")
|
||||||
|
public open class TGeoNode : TNamed() {
|
||||||
//val fGeoAtt: UInt
|
//val fGeoAtt: UInt
|
||||||
public val fVolume: TGeoVolume? = null
|
public val fVolume: TGeoVolume? = null
|
||||||
public val fMother: TGeoVolume? = null
|
public val fMother: TGeoVolume? = null
|
||||||
@ -12,6 +14,8 @@ public class TGeoNode : TNamed() {
|
|||||||
public val fOverlaps: IntArray = intArrayOf()
|
public val fOverlaps: IntArray = intArrayOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TGeoNodeMatrix : TGeoMatrix() {
|
@Serializable
|
||||||
|
@SerialName("TGeoNodeMatrix")
|
||||||
|
public class TGeoNodeMatrix : TGeoNode() {
|
||||||
public val fMatrix: TGeoMatrix? = null
|
public val fMatrix: TGeoMatrix? = null
|
||||||
}
|
}
|
@ -1,14 +1,17 @@
|
|||||||
package ru.mipt.npm.root
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public abstract class TGeoShape : TNamed() {
|
@SerialName("TGeoShape")
|
||||||
|
public sealed class TGeoShape : TNamed() {
|
||||||
public val fShapeBits: UInt = 0u
|
public val fShapeBits: UInt = 0u
|
||||||
public val fShapeId: Int = 0
|
public val fShapeId: Int = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoBBox")
|
||||||
public open class TGeoBBox : TGeoShape() {
|
public open class TGeoBBox : TGeoShape() {
|
||||||
public val fDX: Double = 0.0
|
public val fDX: Double = 0.0
|
||||||
public val fDY: Double = 0.0
|
public val fDY: Double = 0.0
|
||||||
@ -17,6 +20,7 @@ public open class TGeoBBox : TGeoShape() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoBoolNode")
|
||||||
public sealed class TGeoBoolNode : TObject() {
|
public sealed class TGeoBoolNode : TObject() {
|
||||||
public abstract val fLeft: TGeoShape
|
public abstract val fLeft: TGeoShape
|
||||||
public abstract val fLeftMat: TGeoMatrix
|
public abstract val fLeftMat: TGeoMatrix
|
||||||
@ -25,6 +29,7 @@ public sealed class TGeoBoolNode : TObject() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoUnion")
|
||||||
public class TGeoUnion(
|
public class TGeoUnion(
|
||||||
override val fLeft: TGeoShape,
|
override val fLeft: TGeoShape,
|
||||||
override val fLeftMat: TGeoMatrix,
|
override val fLeftMat: TGeoMatrix,
|
||||||
@ -33,6 +38,7 @@ public class TGeoUnion(
|
|||||||
) : TGeoBoolNode()
|
) : TGeoBoolNode()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoSubtraction")
|
||||||
public class TGeoSubtraction(
|
public class TGeoSubtraction(
|
||||||
override val fLeft: TGeoShape,
|
override val fLeft: TGeoShape,
|
||||||
override val fLeftMat: TGeoMatrix,
|
override val fLeftMat: TGeoMatrix,
|
||||||
@ -41,6 +47,7 @@ public class TGeoSubtraction(
|
|||||||
) : TGeoBoolNode()
|
) : TGeoBoolNode()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoIntersection")
|
||||||
public class TGeoIntersection(
|
public class TGeoIntersection(
|
||||||
override val fLeft: TGeoShape,
|
override val fLeft: TGeoShape,
|
||||||
override val fLeftMat: TGeoMatrix,
|
override val fLeftMat: TGeoMatrix,
|
||||||
@ -50,9 +57,11 @@ public class TGeoIntersection(
|
|||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoCompositeShape")
|
||||||
public class TGeoCompositeShape(public val fNode: TGeoBoolNode) : TGeoBBox()
|
public class TGeoCompositeShape(public val fNode: TGeoBoolNode) : TGeoBBox()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoXtru")
|
||||||
public class TGeoXtru(
|
public class TGeoXtru(
|
||||||
public val fNvert: Int,
|
public val fNvert: Int,
|
||||||
public val fNz: Int,
|
public val fNz: Int,
|
||||||
@ -67,14 +76,33 @@ public class TGeoXtru(
|
|||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public class TGeoTube(
|
@SerialName("TGeoTube")
|
||||||
|
public open class TGeoTube(
|
||||||
public val fRmin: Double,
|
public val fRmin: Double,
|
||||||
public val fRmax: Double,
|
public val fRmax: Double,
|
||||||
public val fDz: Double,
|
public val fDz: Double,
|
||||||
) : TGeoBBox()
|
) : TGeoBBox()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoTubeSeg")
|
||||||
|
public class TGeoTubeSeg(
|
||||||
|
public val fRmin: Double,
|
||||||
|
public val fRmax: Double,
|
||||||
|
public val fDz: Double,
|
||||||
|
public val fPhi1: Double,
|
||||||
|
public val fPhi2: Double,
|
||||||
|
public val fS1: Double,
|
||||||
|
public val fC1: Double,
|
||||||
|
public val fS2: Double,
|
||||||
|
public val fC2: Double,
|
||||||
|
public val fSm: Double,
|
||||||
|
public val fCm: Double,
|
||||||
|
public val fCdfi: Double,
|
||||||
|
) : TGeoBBox()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("TGeoShapeAssembly")
|
||||||
public class TGeoShapeAssembly(
|
public class TGeoShapeAssembly(
|
||||||
public val fVolume: TGeoVolumeAssembly,
|
public val fVolume: TGeoVolumeAssembly,
|
||||||
public val fBBoxOK: Boolean = true
|
public val fBBoxOK: Boolean = true
|
||||||
): TGeoBBox()
|
) : TGeoBBox()
|
@ -1,8 +1,10 @@
|
|||||||
package ru.mipt.npm.root
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoVolume")
|
||||||
public open class TGeoVolume : TNamed(){
|
public open class TGeoVolume : TNamed(){
|
||||||
// "fGeoAtt" : 3084,
|
// "fGeoAtt" : 3084,
|
||||||
// "fLineColor" : 3,
|
// "fLineColor" : 3,
|
||||||
@ -19,4 +21,5 @@ public open class TGeoVolume : TNamed(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TGeoVolumeAssembly")
|
||||||
public class TGeoVolumeAssembly : TGeoVolume()
|
public class TGeoVolumeAssembly : TGeoVolume()
|
@ -1,5 +1,6 @@
|
|||||||
package ru.mipt.npm.root
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -15,6 +16,7 @@ public abstract class TNamed : TObject() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@SerialName("TObjArray")
|
||||||
public class TObjArray(public val arr: List<TObject>){
|
public class TObjArray(public val arr: List<TObject>){
|
||||||
public companion object{
|
public companion object{
|
||||||
public val empty = TObjArray(emptyList())
|
public val empty = TObjArray(emptyList())
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package ru.mipt.npm.root
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val string = TGeoManager::class.java.getResourceAsStream("/BM@N.root.json")!!
|
||||||
|
.readAllBytes().decodeToString()
|
||||||
|
val geo = TGeoManager.decodeFromString(string)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user