Move development of 'feature-client-net' to 'master'
This commit is contained in:
commit
badd50de38
|
@ -28,6 +28,7 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "no.tornado:tornadofx:$tornadofx_version"
|
||||
implementation "com.jfoenix:jfoenix:8.0.8"
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
import com.google.gson.*
|
||||
import org.hso.texturesyncclient.model.Texture
|
||||
import java.io.*
|
||||
import java.lang.RuntimeException
|
||||
import java.net.*
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
|
||||
var socket: Socket? = null
|
||||
var output: DataOutputStream? = null
|
||||
var input: DataInputStream? = null
|
||||
|
||||
@Throws(IOException::class)
|
||||
@Synchronized
|
||||
private fun getStreams(): Pair<DataInputStream, DataOutputStream> {
|
||||
val i: DataInputStream
|
||||
val o: DataOutputStream
|
||||
|
||||
if (socket == null || !socket!!.isConnected) {
|
||||
val sock = Socket(address, port)
|
||||
i = DataInputStream(BufferedInputStream(sock.getInputStream()))
|
||||
o = DataOutputStream(BufferedOutputStream(sock.getOutputStream()))
|
||||
|
||||
input = i
|
||||
output = o
|
||||
socket = sock
|
||||
} else {
|
||||
i = input!!
|
||||
o = output!!
|
||||
}
|
||||
|
||||
return Pair(i, o)
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
@Synchronized
|
||||
fun ping() {
|
||||
val io = getStreams()
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("ping", JsonObject())
|
||||
|
||||
JsonPackage(obj).write(io.second)
|
||||
|
||||
when (val pkg = Package.read(io.first)) {
|
||||
is JsonPackage -> return
|
||||
is BinaryPackage -> throw ConnectionUnexpecedPacketException()
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
@Synchronized
|
||||
fun query(query : Array<String>) : Array<Texture> {
|
||||
val io = getStreams()
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("query", {
|
||||
val inner = JsonObject()
|
||||
inner.add("query", {
|
||||
val array = JsonArray()
|
||||
for(queryString in query) {
|
||||
array.add(queryString)
|
||||
}
|
||||
array
|
||||
}())
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(io.second)
|
||||
|
||||
when (val pkg = Package.read(io.first)) {
|
||||
is JsonPackage -> {
|
||||
try {
|
||||
return Gson().fromJson<Array<InternalTexture>>(pkg.content, Array<InternalTexture>::class.java).map {
|
||||
tex -> tex.toTexture()
|
||||
}.toTypedArray()
|
||||
} catch (e : JsonSyntaxException ){
|
||||
throw ConnectionInvalidJsonException()
|
||||
}
|
||||
}
|
||||
is BinaryPackage -> throw ConnectionUnexpecedPacketException()
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@Synchronized
|
||||
override fun close() {
|
||||
if (output != null) {
|
||||
output!!.close()
|
||||
}
|
||||
if (input != null) {
|
||||
input!!.close()
|
||||
}
|
||||
if (socket != null) {
|
||||
socket!!.close()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
// These types will be converted to the model.DataModel
|
||||
|
||||
import org.hso.texturesyncclient.model.Texture
|
||||
import org.hso.texturesyncclient.model.TextureFormat
|
||||
import java.lang.Exception
|
||||
import java.util.*
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
internal data class InternalTexture(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val tags: Array<String>,
|
||||
val format: String,
|
||||
val resolution: Array<Int>,
|
||||
val added_on: Array<Int>,
|
||||
val texture_hash: String
|
||||
) {
|
||||
|
||||
constructor(tex: Texture) : this(
|
||||
id = tex.id.toString(),
|
||||
name = tex.name,
|
||||
tags = tex.tags.clone(),
|
||||
format = when (tex.format) {
|
||||
TextureFormat.PNG -> "png"
|
||||
TextureFormat.JPEG -> "jpeg"
|
||||
},
|
||||
resolution = arrayOf(tex.resolution.first, tex.resolution.second),
|
||||
added_on = arrayOf(
|
||||
tex.addedOn.get(Calendar.YEAR), //
|
||||
tex.addedOn.get(Calendar.MONTH), //
|
||||
tex.addedOn.get(Calendar.DAY_OF_MONTH)
|
||||
),
|
||||
texture_hash = bytes2hexString(tex.textureHash)
|
||||
)
|
||||
|
||||
@Throws(ConnectionException::class)
|
||||
fun toTexture(): Texture {
|
||||
try {
|
||||
return Texture(
|
||||
id = UUID.fromString(id),
|
||||
name = name,
|
||||
tags = tags.clone(),
|
||||
format = when (format.toLowerCase()) {
|
||||
"jpeg" -> TextureFormat.JPEG
|
||||
"jpg" -> TextureFormat.JPEG
|
||||
"png" -> TextureFormat.PNG
|
||||
else -> throw ConnectionInvalidJsonException()
|
||||
},
|
||||
resolution = Pair(resolution[0], resolution[1]),
|
||||
addedOn = GregorianCalendar(added_on[0], added_on[1], added_on[2]),
|
||||
textureHash = hexString2Bytes(texture_hash)
|
||||
)
|
||||
} catch (e: Exception) { // i Know, but no time :[]
|
||||
throw ConnectionInvalidJsonException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bytes2hexString(bytes: ByteArray): String {
|
||||
val s = StringBuilder(64)
|
||||
for (byte in bytes) {
|
||||
s.append(String.format("%02X"), byte)
|
||||
}
|
||||
return s.toString()
|
||||
}
|
||||
|
||||
private fun hexString2Bytes(bytes: String): ByteArray {
|
||||
return ByteArray(bytes.length / 2) { i ->
|
||||
bytes.substring(i * 2, i * 2 + 2).toInt(16).toByte()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.JsonParser
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
internal abstract class Package {
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun write(out: DataOutputStream)
|
||||
|
||||
companion object {
|
||||
|
||||
const val TYPE_ERROR: Byte = 0
|
||||
protected const val TYPE_ERROR_MAX_PAYLOAD: Int = 1024
|
||||
|
||||
const val TYPE_JSON: Byte = 1
|
||||
protected const val TYPE_JSON_MAX_PAYLOAD: Int = 16 * 1024 * 1024
|
||||
|
||||
const val TYPE_BINARY: Byte = 2
|
||||
protected const val TYPE_BINARY_MAX_PAYLOAD: Int = 512 * 1024 * 1024
|
||||
|
||||
@Throws(PacketException::class, IOException::class)
|
||||
fun read(input: DataInputStream): Package {
|
||||
// Type Byte
|
||||
val type = input.readByte()
|
||||
|
||||
// 3 Reserved Bytes
|
||||
input.readByte()
|
||||
input.readByte()
|
||||
input.readByte()
|
||||
|
||||
// 4 Len Bytes
|
||||
val length = input.readInt()
|
||||
|
||||
when (type) {
|
||||
TYPE_ERROR -> if (length > TYPE_ERROR_MAX_PAYLOAD) {
|
||||
throw PacketTooLongException()
|
||||
}
|
||||
|
||||
TYPE_JSON -> if (length > TYPE_JSON_MAX_PAYLOAD) {
|
||||
throw PacketTooLongException()
|
||||
}
|
||||
|
||||
TYPE_BINARY -> if (length > TYPE_BINARY_MAX_PAYLOAD) {
|
||||
throw PacketTooLongException()
|
||||
}
|
||||
|
||||
else -> throw PacketInvalidType()
|
||||
}
|
||||
|
||||
val payload = ByteArray(length)
|
||||
input.readFully(payload)
|
||||
|
||||
when (type) {
|
||||
TYPE_ERROR -> {
|
||||
val msg = String(payload)
|
||||
val results = msg.split(' ', ignoreCase = false, limit = 2)
|
||||
|
||||
return if (results.size == 2) {
|
||||
val code = results[0].toIntOrNull()
|
||||
|
||||
if (code == null) {
|
||||
ErrorPackage(0, msg)
|
||||
} else {
|
||||
ErrorPackage(code, results[1])
|
||||
}
|
||||
} else {
|
||||
ErrorPackage(0, msg)
|
||||
}
|
||||
}
|
||||
|
||||
TYPE_JSON -> {
|
||||
try {
|
||||
val obj = JsonParser().parse(String(payload))
|
||||
|
||||
return JsonPackage(obj)
|
||||
} catch (e: JsonParseException) {
|
||||
throw PacketInvalidData()
|
||||
}
|
||||
}
|
||||
|
||||
TYPE_BINARY -> {
|
||||
return BinaryPackage(payload)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Unreachable
|
||||
throw PacketInvalidType()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal data class JsonPackage(val content: JsonElement) : Package() {
|
||||
override fun write(out: DataOutputStream) {
|
||||
val payload = content.toString().toByteArray()
|
||||
// Tag Byte
|
||||
out.writeByte(TYPE_JSON.toInt())
|
||||
|
||||
// 3 Reserved Bytes
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
|
||||
// Length of Payload
|
||||
out.writeInt(payload.size)
|
||||
|
||||
// Payload
|
||||
out.write(payload)
|
||||
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
internal data class BinaryPackage(val content: ByteArray) : Package() {
|
||||
override fun write(out: DataOutputStream) {
|
||||
// Tag Byte
|
||||
out.writeByte(TYPE_BINARY.toInt())
|
||||
|
||||
// 3 Reserved Bytes
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
|
||||
// Length of Payload
|
||||
out.writeInt(content.size)
|
||||
|
||||
// Payload
|
||||
out.write(content)
|
||||
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ErrorPackage(val code: Int, val message: String) : Package() {
|
||||
override fun write(out: DataOutputStream) {
|
||||
val payload = "$code $message".toByteArray()
|
||||
// Tag Byte
|
||||
out.writeByte(TYPE_ERROR.toInt())
|
||||
|
||||
// 3 Reserved Bytes
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
|
||||
// Length of Payload
|
||||
out.writeInt(payload.size)
|
||||
|
||||
// Payload
|
||||
out.write(payload)
|
||||
|
||||
out.flush()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
import java.lang.Exception
|
||||
|
||||
sealed class ConnectionException(override val message : String) : Exception(message)
|
||||
|
||||
class ConnectionErrorException(val errorCode : Int, val errorMessage : String) : ConnectionException("${errorCode} ${errorMessage}") {
|
||||
internal constructor(err : ErrorPackage) : this(err.code, err.message)
|
||||
}
|
||||
class ConnectionUnexpecedPacketException : ConnectionException("Got Unexpected Type of Packet")
|
||||
class ConnectionInvalidJsonException : ConnectionException("The Format of the Json Received is Unexpected.")
|
||||
|
||||
|
||||
sealed class PacketException(msg: String) : ConnectionException(msg)
|
||||
class PacketTooLongException : PacketException("The Package is too long.")
|
||||
class PacketInvalidType : PacketException("The Package has an Invalid Type.")
|
||||
class PacketInvalidData : PacketException("The Package has an Invalid Data. (e.g. Invalid Json.)")
|
|
@ -0,0 +1,22 @@
|
|||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
import java.net.*
|
||||
|
||||
fun main() {
|
||||
// Just some test code.
|
||||
|
||||
val con = Connection(InetAddress.getByName("::1"))
|
||||
|
||||
con.ping()
|
||||
|
||||
|
||||
println("Query:")
|
||||
for (tex in con.query(
|
||||
arrayOf("Red", "Food")
|
||||
)) {
|
||||
println(tex.toString())
|
||||
}
|
||||
println()
|
||||
|
||||
con.close()
|
||||
}
|
|
@ -2,8 +2,7 @@ package org.hso.texturesyncclient.model
|
|||
|
||||
import java.util.*
|
||||
|
||||
class DataModel {
|
||||
}
|
||||
class DataModel
|
||||
|
||||
enum class TextureFormat {
|
||||
PNG, JPEG,
|
||||
|
@ -16,6 +15,6 @@ data class Texture(
|
|||
val tags : Array<String>,
|
||||
val format : TextureFormat,
|
||||
val resolution : Pair<Int, Int>,
|
||||
val addedOn : Date,
|
||||
val addedOn : Calendar,
|
||||
val textureHash : ByteArray
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue