package org.hso.texturesyncclient.controller.net import com.google.gson.* import javafx.scene.image.Image import org.hso.texturesyncclient.model.Sha256 import org.hso.texturesyncclient.model.Texture import java.io.* import java.net.InetAddress import java.net.InetSocketAddress import java.net.Socket import java.util.* @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 withStreams(retry: Boolean = true, inner: (DataInputStream, DataOutputStream) -> R): R { val i: DataInputStream val o: DataOutputStream if (socket == null || socket!!.isClosed || !socket!!.isConnected) { val sock = Socket() sock.soTimeout = 10_000 /*ms*/ sock.keepAlive = true sock.connect(InetSocketAddress(address, port), 1_000 /*ms*/) i = DataInputStream(BufferedInputStream(sock.getInputStream())) o = DataOutputStream(BufferedOutputStream(sock.getOutputStream())) input = i output = o socket = sock } else { i = input!! o = output!! } return try { inner(i, o) } catch (e: IOException) { if (retry) { println("Got IOException; Retry ${e.message}") close() withStreams(false, inner) } else { throw e } } } @Throws(IOException::class, ConnectionException::class) fun ping() { return withStreams { i, o -> val obj = JsonObject() obj.add("ping", JsonObject()) JsonPackage(obj).write(o) when (val pkg = Package.read(i)) { is JsonPackage -> return@withStreams is BinaryPackage -> throw ConnectionUnexpectedPacketException() is ErrorPackage -> throw ConnectionErrorException(pkg) else -> throw RuntimeException("Unreachable") } } } @Throws(IOException::class, ConnectionException::class) fun query(query: Array): Array { return withStreams { i, o -> 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(o) when (val pkg = Package.read(i)) { is JsonPackage -> { try { return@withStreams Gson().fromJson>( pkg.content, Array::class.java ) .map { tex -> tex.toTexture() }.toTypedArray() } catch (e: JsonSyntaxException) { throw ConnectionInvalidJsonException() } } is BinaryPackage -> throw ConnectionUnexpectedPacketException() is ErrorPackage -> throw ConnectionErrorException(pkg) else -> throw RuntimeException("Unreachable") } } } @Throws(IOException::class, ConnectionException::class) fun getTextureById(id: UUID): Texture? { return withStreams { i, o -> val obj = JsonObject() obj.add("get_texture", { val inner = JsonObject() inner.addProperty("id", id.toString()) inner }()) JsonPackage(obj).write(o) when (val pkg = Package.read(i)) { is JsonPackage -> { return@withStreams if (pkg.content.isJsonNull) { null } else { try { Gson() .fromJson(pkg.content, InternalTexture::class.java) .toTexture() } catch (e: JsonSyntaxException) { throw ConnectionInvalidJsonException() } } } is BinaryPackage -> throw ConnectionUnexpectedPacketException() is ErrorPackage -> throw ConnectionErrorException(pkg) else -> throw RuntimeException("Unreachable") } } } @Throws(IOException::class, ConnectionException::class) fun getTextureByName(name: String): Texture? { return withStreams { i, o -> val obj = JsonObject() obj.add("get_texture", { val inner = JsonObject() inner.addProperty("name", name) inner }()) JsonPackage(obj).write(o) when (val pkg = Package.read(i)) { is JsonPackage -> { return@withStreams if (pkg.content.isJsonNull) { null } else { try { Gson() .fromJson(pkg.content, InternalTexture::class.java) .toTexture() } catch (e: JsonSyntaxException) { throw ConnectionInvalidJsonException() } } } is BinaryPackage -> throw ConnectionUnexpectedPacketException() is ErrorPackage -> throw ConnectionErrorException(pkg) else -> throw RuntimeException("Unreachable") } } } @Throws(IOException::class, ConnectionException::class) fun getTextureFile(hash: Sha256): ByteArray { return withStreams { i, o -> val obj = JsonObject() obj.add("get_texture_file", { val inner = JsonObject() inner.addProperty("texture_hash", hash.toString()) inner }()) JsonPackage(obj).write(o) when (val pkg = Package.read(i)) { is JsonPackage -> throw ConnectionUnexpectedPacketException() is BinaryPackage -> return@withStreams pkg.content is ErrorPackage -> throw ConnectionErrorException(pkg) else -> throw RuntimeException("Unreachable") } } } @Throws(IOException::class, ConnectionException::class) fun getTexturePreview(hash: Sha256): Image { return withStreams { i, o -> val obj = JsonObject() obj.add("get_texture_preview", { val inner = JsonObject() inner.addProperty("texture_hash", hash.toString()) inner.addProperty("desired_format", "jpeg") inner }()) JsonPackage(obj).write(o) when (val pkg = Package.read(i)) { is JsonPackage -> throw ConnectionUnexpectedPacketException() is BinaryPackage -> { return@withStreams Image(ByteArrayInputStream(pkg.content)) } is ErrorPackage -> throw ConnectionErrorException(pkg) else -> throw RuntimeException("Unreachable") } } } @Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class) private fun replaceTexture(old: Texture?, new: Texture?, image: ByteArray?) { return withStreams { i, o -> val obj = JsonObject() obj.add("replace_texture", { val inner = JsonObject() if (old != null) { inner.add("old", Gson().toJsonTree(InternalTexture(old), InternalTexture::class.java)) } else { inner.add("old", null) } if (new != null) { inner.add("new", Gson().toJsonTree(InternalTexture(new), InternalTexture::class.java)) } else { inner.add("new", null) } inner }()) JsonPackage(obj).write(o) when (val pkg = Package.read(i)) { is JsonPackage -> { when { pkg.content == JsonPrimitive(true) -> // everthing is fine! return@withStreams image != null -> { // should be { "get_texture_file": { texture_hash : }} // we don't check, since there is no good way to handle it. BinaryPackage(image).write(o) when (val ipkg = Package.read(i)) { is JsonPackage -> { if (ipkg.content != JsonPrimitive(true)) { // Protokoll Assertion failed throw ConnectionUnexpectedPacketException() } } is BinaryPackage -> throw ConnectionUnexpectedPacketException() is ErrorPackage -> throw ConnectionErrorException(ipkg) else -> throw RuntimeException("Unreachable") } } else -> { ErrorPackage(404, "Texture not found!").write(o) close() // gets re-opened on next request. throw IllegalArgumentException("Image Argument was needed.") } } } is BinaryPackage -> throw ConnectionUnexpectedPacketException() is ErrorPackage -> throw ConnectionErrorException(pkg) else -> throw RuntimeException("Unreachable") } } } @Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class) fun uploadTexture(texture: Texture, image: ByteArray) { if (texture.textureHash != Sha256(image)) { throw IllegalArgumentException("Sha256 of Image does not Match with Texture.") } replaceTexture(null, texture, image) } @Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class) fun updateTexture(old: Texture, new: Texture, image: ByteArray) { if (new.textureHash != Sha256(image)) { throw IllegalArgumentException("Sha256 of Image does not Match with Texture.") } replaceTexture(old, new, image) } @Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class) fun deleteTexture(texture: Texture) { replaceTexture(texture, null, null) } @Throws(IOException::class) override fun close() { try { if (output != null) { output!!.close() output = null } if (input != null) { input!!.close() input = null } if (socket != null) { socket!!.close() socket = null } } catch (e: IOException) { // Ignore } } }