Newer
Older
Twilight / src / main / kotlin / net / taehui / twilight / etc / EtcAvatar.kt
@taehui taehui on 21 Aug 7 KB v1.0-SNAPSHOT
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))
    }
}