package net.taehui.twilight.etc import com.fasterxml.jackson.databind.ObjectMapper import io.netty.buffer.ByteBufUtil import io.netty.channel.ChannelFutureListener import io.netty.channel.ChannelHandlerContext import io.netty.channel.SimpleChannelInboundHandler import io.netty.handler.codec.http.* import net.taehui.twilight.Logger import net.taehui.twilight.Utility import net.taehui.twilight.system.AvatarHandler import net.taehui.twilight.system.Configure import org.apache.commons.compress.archivers.zip.ZipFile import org.slf4j.LoggerFactory import java.net.InetSocketAddress import java.net.URLDecoder import java.nio.charset.StandardCharsets import java.nio.file.Files import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.createParentDirectories import kotlin.io.path.deleteRecursively class EtcAvatar : SimpleChannelInboundHandler<FullHttpRequest>(), Logger { class TaehuiQwilightDate { var date = "" var hashAMD64 = "" var hashARM64 = "" var titleAMD64 = "Qwilight.AMD64.zip" var titleARM64 = "Qwilight.ARM64.zip" var hashesAMD64 = mutableMapOf<String, String>() var hashesARM64 = mutableMapOf<String, String>() } private var avatarIP = "" private fun send204(handler: ChannelHandlerContext) { handler.writeAndFlush(DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NO_CONTENT)) .addListener(ChannelFutureListener.CLOSE) } override fun channelActive(ctx: ChannelHandlerContext) { avatarIP = (ctx.channel().remoteAddress() as InetSocketAddress).address.hostAddress } @OptIn(ExperimentalPathApi::class) public override fun channelRead0(ctx: ChannelHandlerContext, msg: FullHttpRequest) { val path = URLDecoder.decode(msg.uri(), StandardCharsets.UTF_8) val mode = msg.method() logInfo("$mode $path") when (mode) { HttpMethod.GET -> when (path) { "/health" -> { send204(ctx) } } HttpMethod.PATCH -> when (path) { "/date/AMD64" -> { val data = ByteBufUtil.getBytes(msg.content()) logFuture { val jm = ObjectMapper() val filePath = Configure.path.wwwPath.resolve("qwilight.json") val text = jm.readValue(Files.readString(filePath), TaehuiQwilightDate::class.java) Files.write(Configure.path.wwwPath.resolve("Qwilight.AMD64.zip"), data) val zipAMD64EntryPath = Configure.path.wwwPath.resolve("zip").resolve("AMD64") zipAMD64EntryPath.deleteRecursively() ZipFile.builder().setByteArray(data).get().use { for (entry in it.entries) { if (!entry.isDirectory) { val targetPath = zipAMD64EntryPath.resolve(entry.name) targetPath.createParentDirectories() it.getInputStream(entry).use { zis -> val rawData = zis.readAllBytes() Files.write(targetPath, rawData) text.hashesAMD64[entry.name] = Utility.getID128(rawData) } } } } Files.newBufferedWriter(filePath).use { jm.writerWithDefaultPrettyPrinter().writeValue(it, text) } send204(ctx) } } "/date/ARM64" -> { val data = ByteBufUtil.getBytes(msg.content()) logFuture { val jm = ObjectMapper() val filePath = Configure.path.wwwPath.resolve("qwilight.json") val text = jm.readValue(Files.readString(filePath), TaehuiQwilightDate::class.java) Files.write(Configure.path.wwwPath.resolve("Qwilight.ARM64.zip"), data) val zipARM64EntryPath = Configure.path.wwwPath.resolve("zip").resolve("ARM64") zipARM64EntryPath.deleteRecursively() ZipFile.builder().setByteArray(data).get().use { for (entry in it.entries) { if (!entry.isDirectory) { val targetPath = zipARM64EntryPath.resolve(entry.name) targetPath.createParentDirectories() it.getInputStream(entry).use { zis -> val rawData = zis.readAllBytes() Files.write(targetPath, rawData) text.hashesARM64[entry.name] = Utility.getID128(rawData) } } } } Files.newBufferedWriter(filePath).use { jm.writerWithDefaultPrettyPrinter().writeValue(it, text) } send204(ctx) } } "/date" -> { val data = msg.content().toString(StandardCharsets.UTF_8) logFuture { val jm = ObjectMapper() val filePath = Configure.path.wwwPath.resolve("qwilight.json") val text = jm.readValue(Files.readString(filePath), TaehuiQwilightDate::class.java) text.date = data Files.newBufferedWriter(filePath).use { jm.writerWithDefaultPrettyPrinter().writeValue(it, text) } Configure.loadDate() send204(ctx) } } "/drawing" -> { AvatarHandler.sendInvalidateAvatarDrawing(msg.content().toString(StandardCharsets.UTF_8)) send204(ctx) } "/totem" -> { AvatarHandler.handleNotLogIn(msg.content().toString(StandardCharsets.UTF_8)) send204(ctx) } else -> ctx.writeAndFlush( DefaultFullHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND ) ).addListener( ChannelFutureListener.CLOSE ) } else -> ctx.writeAndFlush( DefaultFullHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.METHOD_NOT_ALLOWED ) ).addListener( ChannelFutureListener.CLOSE ) } } override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { logFault(cause) } override fun logInfo(toNotify: String) { LoggerFactory.getLogger(javaClass).info("[{}] {}", avatarIP, toNotify) } override fun logFault(e: Throwable) { LoggerFactory.getLogger(javaClass).error("[{}] {}", avatarIP, Utility.getFaultText(e)) } }