package net.taehui.twilight.qwilight import CommentOuterClass import EventOuterClass import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.ByteString import io.netty.channel.ChannelFuture import io.netty.channel.ChannelHandlerContext import io.netty.channel.SimpleChannelInboundHandler import io.netty.handler.codec.haproxy.HAProxyMessage import net.taehui.twilight.* import net.taehui.twilight.BundleIO.BundleVariety import net.taehui.twilight.BundleIO.BundleVariety.Companion.getBundleVariety import net.taehui.twilight.system.* import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream import org.apache.commons.io.FilenameUtils import org.apache.commons.io.IOUtils import org.apache.commons.lang3.RandomStringUtils import org.slf4j.LoggerFactory import java.net.InetAddress import java.nio.channels.AsynchronousFileChannel import java.nio.channels.CompletionHandler import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption import java.sql.Timestamp import java.time.LocalDateTime import java.util.* import java.util.concurrent.ConcurrentHashMap import kotlin.io.path.nameWithoutExtension class QwilightAvatar : SimpleChannelInboundHandler<EventOuterClass.Event>(), Avatar { override val availableCSX = Any() override val siteYellMillis = LinkedList<Long>() override var lastLogInDate: LocalDateTime = LocalDateTime.MIN override lateinit var handler: ChannelHandlerContext override var isAwilight = false override var isAvailable = true private val defaultNoteNames = LinkedList<String>() private val defaultUINames = LinkedList<String>() override lateinit var avatarIP: InetAddress override var avatarEstimatedID = "" private val savingBundleIOs = ConcurrentHashMap<String, BundleIO>() override var isEstablished = false override var isLoggedIn = false override var isValve = false override var qwilightHash = "" override var qwilightDate = "" override var language = "ko-KR" override var qwilightID = "" override var qwilightName = "" override var avatarID = "" override var avatarName = "" override var avatarCompetence = 1 override var situationValue = NOTE_FILE_MODE override var situationText = "" override val isEnterQuitAware = true override val avatarNameTitle = "" override fun send(eventID: EventOuterClass.Event.EventID, text: Any?, vararg data: ByteString?): ChannelFuture { return handler.writeAndFlush(EventOuterClass.Event.newBuilder().apply { this.eventID = eventID if (text != null) { this.text = if (text is String) text else ObjectMapper().writeValueAsString(text) } this.addAllData(data.toList()) }.build()) } override fun send(event: EventOuterClass.Event) { handler.writeAndFlush(event) } private fun hasAvailableBundleIOs(): Boolean { return savingBundleIOs.size < BUNDLE_AVAILABLE_COUNT } private fun doCallBundle(targetAvatarID: String, isSilent: Boolean) { DB.getBundles( Utility.getDefaultAvatarID(targetAvatarID) ).thenAccept { bundles -> if (bundles != null) { send(EventOuterClass.Event.EventID.CALL_BUNDLE, object { val targetAvatar = targetAvatarID val targetValue = bundles.second val bundleLength = TwilightComponent.BUNDLE_LENGTH @JvmField val isSilent = isSilent val data = bundles.first.map { object { val date = it[0] as Timestamp val bundleName = it[1] as String val bundleLength = it[2] as Long val bundleCompetence = it[3] as Int val bundleVariety = it[4] as Int } } }) } } } private fun translateTitleItem(target: TitleSystem.TitleItem): String { return when (language) { "ko-KR" -> target.title1042 else -> target.title } } override fun toString(): String { return if (isEstablished) { when (situationValue) { NOTE_FILE_MODE -> "[$loggerID] Note File Mode ($situationText)" DEFAULT_COMPUTING -> "[$loggerID] Default Computing ($situationText)" COMMENT_COMPUTING -> "[$loggerID] Comment Computing ($situationText)" AUTO_COMPUTING -> "[$loggerID] Auto Computing ($situationText)" QUIT_MODE -> "[$loggerID] Quit Computing ($situationText)" NET_COMPUTING -> "[$loggerID] Net Computing ($situationText)" IO_COMPUTING -> "[$loggerID] IO Computing ($situationText)" else -> "[$loggerID] Idle" } } else "[$loggerID] Not Established" } override fun channelActive(ctx: ChannelHandlerContext) { handler = ctx } override fun channelInactive(ctx: ChannelHandlerContext) { AvatarHandler.quitAvatar(avatarID) } override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { super.channelRead(ctx, msg) if (msg is HAProxyMessage) { avatarIP = InetAddress.getByName(msg.sourceAddress()) isAwilight = avatarIP.isLoopbackAddress avatarEstimatedID = AvatarIPSystem.getAvatarID(remote) } } public override fun channelRead0(ctx: ChannelHandlerContext, msg: EventOuterClass.Event) { doIfValid(msg) { doIfAvailable { val jm = ObjectMapper() val eventText = msg.text val eventData = msg.dataList fun saveDefaultNote(eventText: String) { val text = if (eventText.isEmpty()) null else jm.readValue(eventText, Array<String>::class.java) if (text != null) { if (defaultNoteNames.isEmpty()) { Files.list(TwilightComponent.DEFAULT_NOTE_ENTRY).use { defaultNoteFilePaths -> defaultNoteNames.addAll(defaultNoteFilePaths.map { defaultNoteFilePath -> defaultNoteFilePath.nameWithoutExtension }.toList().subtract(text.toSet())) } } else { send(EventOuterClass.Event.EventID.ALREADY_LOADING_BUNDLE, null) return } } defaultNoteNames.poll().let { if (it != null) { val saveBundleID = UUID.randomUUID().toString() val bundleFileName = "$it.zip" val bundleFilePath = TwilightComponent.DEFAULT_NOTE_ENTRY.resolve(bundleFileName) val fileChannel = AsynchronousFileChannel.open(bundleFilePath) savingBundleIOs[saveBundleID] = BundleIO( bundleFilePath, fileChannel, it, BundleVariety.DEFAULT_NOTE_FILE, "", bundleFileName, false ) send(EventOuterClass.Event.EventID.SAVE_BUNDLE, object { val bundleID = saveBundleID val bundleVariety = BundleVariety.DEFAULT_NOTE_FILE val bundleLength = fileChannel.size() }) } } } fun saveDefaultUI(eventText: String) { if (eventText.toBoolean()) { if (defaultUINames.isEmpty()) { Files.list(TwilightComponent.DEFAULT_UI_ENTRY).use { defaultUIFilePaths -> defaultUINames.addAll(defaultUIFilePaths.map { it.nameWithoutExtension }.toList()) } } else { send(EventOuterClass.Event.EventID.ALREADY_LOADING_BUNDLE, null) return } } defaultUINames.poll().let { if (it != null) { val saveBundleID = UUID.randomUUID().toString() val bundleFileName = "$it.zip" val bundleFilePath = TwilightComponent.DEFAULT_UI_ENTRY.resolve(bundleFileName) val fileChannel = AsynchronousFileChannel.open(bundleFilePath) savingBundleIOs[saveBundleID] = BundleIO( bundleFilePath, fileChannel, it, BundleVariety.DEFAULT_UI, "", bundleFileName, false ) send(EventOuterClass.Event.EventID.SAVE_BUNDLE, object { val bundleID = saveBundleID val bundleVariety = BundleVariety.DEFAULT_UI val bundleLength = fileChannel.size() }) } } } if (!isAwilight) { logEvent(msg) } when (msg.eventID) { EventOuterClass.Event.EventID.ESTABLISH -> wantNotEstablished { val text = jm.readValue(eventText, JSON.QwilightEstablish::class.java) val isValve = eventData.size > 0 language = text.language qwilightHash = text.hash qwilightDate = text.date val isAllowedIP = avatarIP.isSiteLocalAddress || avatarIP.isLoopbackAddress val isAllowedClient = Configure.hash.contains(qwilightHash) if (Configure.mode.pause) { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("pause") val isUrgent = false val v = 2 }) val avatarID = "*" + RandomStringUtils.randomAlphanumeric(19) if (isValve) { ValveSystem.putDrawing(avatarID, eventData[0]) } AvatarHandler.establish(this, avatarID, text.qwilightName, isValve, true) } else if (BannedIPSystem.isBanned(remote)) { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("bannedIP") val isUrgent = false val v = 2 }) } else if (isAllowedIP || isAllowedClient) { val avatarID = "*" + RandomStringUtils.randomAlphanumeric(19) if (isValve) { ValveSystem.putDrawing(avatarID, eventData[0]) } AvatarHandler.establish(this, avatarID, text.qwilightName, isValve, false) if (!isAllowedClient) { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = qwilightHash val isUrgent = false val v = 2 }) } } else { if (isValve) { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("unavailableValveDate") val isUrgent = false val v = 2 }) } else { send( EventOuterClass.Event.EventID.UNAVAILABLE_DATE, translateLanguage("unavailableDate") ) } } } EventOuterClass.Event.EventID.LOG_IN -> wantNotLoggedIn { if (allowLogIn()) { tryLogInDate() val text = jm.readValue(eventText, JSON.QwilightLogIn::class.java) handleLogIn(text.avatarID, text.avatarCipher).thenAccept { if (it != null) { val avatarCompetence = it[3] as Int if (avatarCompetence >= 1) { val totem = it[0] as String val avatarID = it[1] as String val avatarName = it[2] as String AvatarHandler.handleLogIn( this, totem, avatarID, avatarName, avatarCompetence ) AvatarIPSystem.putAvatarIP(this, avatarID) send(EventOuterClass.Event.EventID.NOTIFY_PASS, DB.getPasses(avatarID)) DB.getNotifyUbuntu(avatarID).forEach { ubuntuID -> AvatarHandler.getAvatar(ubuntuID)?.let { ubuntu -> if (DB.isCrossUbuntu(avatarID, ubuntuID)) { ubuntu.send(EventOuterClass.Event.EventID.NOTIFY, object { val text = String.format( ubuntu.translateLanguage("ubuntuNotify"), avatarName ) val isUrgent = false val v = 3 }) } } } } else { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("unavailableAvatar") val isUrgent = true val v = 3 }) } } } } } EventOuterClass.Event.EventID.NOT_LOG_IN -> wantLoggedIn { AvatarHandler.handleNotLogIn( it ) } EventOuterClass.Event.EventID.CALL_UBUNTU -> wantLoggedIn { avatarID -> DB.getUbuntuIDs(avatarID).thenAccept { ubuntuIDs -> send( EventOuterClass.Event.EventID.CALL_UBUNTU, ubuntuIDs.stream().map { val ubuntuID = it[0] val situationValue: Int val situationText: String if (DB.isCrossUbuntu(avatarID, ubuntuID)) { val avatarUbuntu = AvatarHandler.getAvatar(ubuntuID) situationValue = avatarUbuntu?.situationValue ?: NOT_SIGNED_IN situationText = avatarUbuntu?.situationText ?: DB.getLastDate(ubuntuID).toString() } else { situationValue = NOT_UBUNTU situationText = "" } object { val ubuntuID = ubuntuID val ubuntuName = it[1] val situationValue = situationValue val situationText = situationText } }.toArray() ) } } EventOuterClass.Event.EventID.ENTER_SITE -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightEnterSite::class.java) SiteHandler.enterSite(UUID.fromString(text.siteID), this, text.siteCipher) } EventOuterClass.Event.EventID.SITE_YELL -> wantEstablished { SiteHandler.putSiteYell( this, jm.readValue(eventText, JSON.QwilightSiteYell::class.java), true ) } EventOuterClass.Event.EventID.GET_SITE_YELLS -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightGetSiteYells::class.java) SiteHandler.getSiteYells(UUID.fromString(text.siteID), this, text) } EventOuterClass.Event.EventID.SAVE_AS_BUNDLE -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightSaveAsBundle::class.java) val bundleVariety = text.bundleVariety val bundleName = text.bundleName val etc = text.etc fun saveAsBundle(bundleEntryPath: Path, saveAsBundleName: String, saveAsBundleID: String) { if (hasAvailableBundleIOs()) { if (!Files.isDirectory(bundleEntryPath)) { Files.createDirectories(bundleEntryPath) } val bundleFilePath = bundleEntryPath.resolve("$saveAsBundleName.tmp") savingBundleIOs[saveAsBundleID] = BundleIO( bundleFilePath, AsynchronousFileChannel.open( bundleFilePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE ), saveAsBundleName, getBundleVariety(bundleVariety), etc, FilenameUtils.getName(text.bundleEntryPath) + getBundleVariety( bundleVariety ).fileVariety, true ) send(EventOuterClass.Event.EventID.SAVE_AS_BUNDLE, object { val bundleID = saveAsBundleID val bundleVariety = bundleVariety val bundleName = bundleName val bundleEntryPath = text.bundleEntryPath }) } else { send(EventOuterClass.Event.EventID.ALREADY_LOADING_BUNDLE, null) } } if (getBundleVariety(bundleVariety) == BundleVariety.NET) { if (SiteHandler.isNetSiteHand(this, UUID.fromString(etc))) { saveAsBundle(TwilightComponent.BUNDLE_ENTRY_PATH, etc, etc) } } else { val saveAsBundleName = bundleName.ifEmpty { UUID.randomUUID().toString() } DB.hasBundleBefore( avatarID, saveAsBundleName ).thenAccept { if (it) { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("alreadyBundle") val isUrgent = true val v = 2 }) } else { saveAsBundle( TwilightComponent.BUNDLE_ENTRY_PATH.resolve(avatarID), saveAsBundleName, UUID.randomUUID().toString() ) } } } } EventOuterClass.Event.EventID.SAVING_AS_BUNDLE -> wantEstablished { savingBundleIOs[eventText]?.let { val savingAsBundleData = eventData[0].asReadOnlyByteBuffer() it.fileChannel.write(savingAsBundleData, it.filePosition.toLong(), it, object : CompletionHandler<Int, BundleIO> { override fun completed(result: Int, attachment: BundleIO) = Unit override fun failed(exc: Throwable, attachment: BundleIO) { savingBundleIOs.remove(eventText)?.close() logFault(exc) } }) if (it.saveFilePosition(savingAsBundleData.capacity())) { savingBundleIOs.remove(eventText)?.close() send(EventOuterClass.Event.EventID.STOP_SAVING_AS_BUNDLE, eventText) } } } EventOuterClass.Event.EventID.STOP_SAVING_AS_BUNDLE -> wantEstablished { savingBundleIOs.remove(eventText)?.close() } EventOuterClass.Event.EventID.SAVED_AS_BUNDLE -> wantEstablished { avatarID -> savingBundleIOs.remove(eventText)?.let { savingBundleIO -> savingBundleIO.fileChannel.write(eventData[0].asReadOnlyByteBuffer(), savingBundleIO.filePosition.toLong(), savingBundleIO, object : CompletionHandler<Int, BundleIO> { override fun completed(result: Int, attachment: BundleIO) { attachment.use { val etc = it.etc val bundleName = it.bundleName val bundleVariety = it.bundleVariety if (bundleVariety == BundleVariety.NET) { it.setFileVariety() val bundleFileName = it.bundleFileName SiteHandler.putSiteYell( this@QwilightAvatar, JSON.QwilightSiteYell().apply { siteID = etc siteYell = String.format( translateLanguage("tryNetBundle"), bundleFileName ) }, false ) SiteHandler.setNetBundle( UUID.fromString(etc), bundleFileName ) } else { val bundleLength = it.getBundleLength() if (DB.isBundleLengthAvailable(avatarID, bundleLength)) { it.setFileVariety() DB.saveBundle( avatarID, bundleName, bundleLength, it.bundleVariety, etc ) doCallBundle(avatarID, true) } else { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("hasNotLength") val isUrgent = true val v = 2 }) } } } } override fun failed(exc: Throwable, attachment: BundleIO) { attachment.use { logFault(exc) } } }) } } EventOuterClass.Event.EventID.SAVE_BUNDLE -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightSaveBundle::class.java) val etc = text.etc if (hasAvailableBundleIOs()) { if (Utility.isEtcNetBundle(etc)) { val bundleFileName = etc + BundleVariety.NET.fileVariety val bundleFilePath = TwilightComponent.BUNDLE_ENTRY_PATH.resolve(bundleFileName) val fileChannel = AsynchronousFileChannel.open(bundleFilePath) savingBundleIOs[etc] = BundleIO( bundleFilePath, fileChannel, text.bundleName, BundleVariety.NET, etc, bundleFileName, false ) send(EventOuterClass.Event.EventID.SAVE_BUNDLE, object { val bundleID = etc val bundleVariety = BundleVariety.NET val bundleLength = fileChannel.size() }) } else { val targetAvatarID = Utility.getDefaultAvatarID(text.avatarID) DB.getBundle( targetAvatarID, text.bundleName ).thenAccept { bundle -> if (bundle != null) { val bundleCompetence = bundle[0] as Int val bundleVariety = bundle[1] as Int val bundleEtc = bundle[2] as String val bundleName = bundle[3] as String if (isSU || Utility.getDefaultAvatarID(avatarID) == targetAvatarID || bundleCompetence == BUNDLE_CALLABLE || bundleCompetence == BUNDLE_AVATAR && isLoggedIn || bundleCompetence == BUNDLE_UBUNTU && DB.isCrossUbuntu( avatarID, targetAvatarID ) ) { val saveBundleID = UUID.randomUUID().toString() val bundleFileName = bundleName + getBundleVariety(bundleVariety).fileVariety val bundleFilePath = TwilightComponent.BUNDLE_ENTRY_PATH.resolve(targetAvatarID) .resolve(bundleFileName) val fileChannel = AsynchronousFileChannel.open(bundleFilePath) savingBundleIOs[saveBundleID] = BundleIO( bundleFilePath, fileChannel, bundleName, getBundleVariety(bundleVariety), bundleEtc, bundleFileName, false ) send(EventOuterClass.Event.EventID.SAVE_BUNDLE, object { val bundleID = saveBundleID val bundleVariety = bundleVariety val bundleLength = fileChannel.size() }) val toNotifySaveBundle = DB.getNotifySaveBundle(targetAvatarID) if (toNotifySaveBundle == NOTIFY_SAVE_BUNDLE_CALLABLE || (toNotifySaveBundle == NOTIFY_SAVE_BUNDLE_AVATAR && isLoggedIn) || (toNotifySaveBundle == NOTIFY_SAVE_BUNDLE_UBUNTU && DB.isCrossUbuntu( avatarID, targetAvatarID )) ) { AvatarHandler.getAvatar( targetAvatarID )?.let { if (it !== this) { it.send(EventOuterClass.Event.EventID.NOTIFY, object { val text = String.format( it.translateLanguage("bundleNotify"), avatarName, bundleName ) val isUrgent = true val v = 3 }) } } } } else { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("lowerCompetenceAsSave") val isUrgent = true val v = 2 }) } } } } } else { send(EventOuterClass.Event.EventID.ALREADY_LOADING_BUNDLE, null) } } EventOuterClass.Event.EventID.SAVE_DEFAULT_NOTE -> wantEstablished { saveDefaultNote(eventText) } EventOuterClass.Event.EventID.SAVE_DEFAULT_UI -> wantEstablished { saveDefaultUI(eventText) } EventOuterClass.Event.EventID.SAVING_BUNDLE -> wantEstablished { savingBundleIOs[eventText]?.let { savingBundleIO -> val savingBundleData = savingBundleIO.data savingBundleIO.fileChannel.read(savingBundleData, savingBundleIO.filePosition.toLong(), savingBundleIO, object : CompletionHandler<Int, BundleIO> { override fun completed(result: Int, attachment: BundleIO) { attachment.saveFilePosition(result) if (result == savingBundleData.capacity()) { savingBundleIOs[eventText]?.let { send( EventOuterClass.Event.EventID.SAVING_BUNDLE, eventText, ByteString.copyFrom(savingBundleData.flip()) ).addListener { future -> if (future.isSuccess) { savingBundleData.flip() it.fileChannel.read( savingBundleData, it.filePosition.toLong(), attachment, this ) } else { failed(future.cause(), attachment) } } } } else { savingBundleIO.close() send( EventOuterClass.Event.EventID.SAVED_BUNDLE, object { val bundleID = eventText val bundleVariety = savingBundleIO.bundleVariety val bundleName = savingBundleIO.bundleName val etc = savingBundleIO.etc @JvmField val isLastDefault = when (savingBundleIO.bundleVariety) { BundleIO.BundleVariety.DEFAULT_NOTE_FILE -> defaultNoteNames.isEmpty() BundleIO.BundleVariety.DEFAULT_UI -> defaultUINames.isEmpty() else -> false } }, ByteString.copyFrom(savingBundleData.flip()) ).addListener { future -> if (future.isSuccess) { savingBundleIOs.remove(eventText)?.let { savingBundleIO -> when (savingBundleIO.bundleVariety) { BundleIO.BundleVariety.DEFAULT_NOTE_FILE -> saveDefaultNote( "" ) BundleIO.BundleVariety.DEFAULT_UI -> saveDefaultUI(false.toString()) else -> Unit } } } else { failed(future.cause(), attachment) } } } } override fun failed(exc: Throwable, attachment: BundleIO) { savingBundleIOs.remove(eventText)?.close() logFault(exc) } }) } } EventOuterClass.Event.EventID.STOP_SAVING_BUNDLE -> wantEstablished { savingBundleIOs.remove(eventText)?.use { when (it.bundleVariety) { BundleVariety.DEFAULT_NOTE_FILE -> defaultNoteNames.clear() BundleVariety.DEFAULT_UI -> defaultUINames.clear() else -> Unit } } } EventOuterClass.Event.EventID.NEW_UBUNTU -> wantLoggedIn { val avatarID = Utility.getDefaultAvatarID(it) val ubuntuID = Utility.getDefaultAvatarID(eventText) if (avatarID == ubuntuID) { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("ubuntuIsYou") val isUrgent = true val v = 2 }) } else { DB.saveUbuntu(avatarID, ubuntuID).thenAccept { isOK -> if (!isOK) { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("alreadyUbuntu") val isUrgent = true val v = 2 }) } } } } EventOuterClass.Event.EventID.CALL_BUNDLE -> wantEstablished { doCallBundle( Utility.getDefaultAvatarID(eventText), false ) } EventOuterClass.Event.EventID.CALL_CONFIGURE -> wantLoggedIn { DB.getConfigure(it).thenAccept { twilightConfigure -> send(EventOuterClass.Event.EventID.CALL_CONFIGURE, object { val silentSiteCompetence = twilightConfigure[0] val toNotifyUbuntu = twilightConfigure[1] val defaultBundleCompetence = twilightConfigure[2] val ioCompetence = twilightConfigure[3] val toNotifySaveBundle = twilightConfigure[4] val toNotifyPass = twilightConfigure[5] }) } } EventOuterClass.Event.EventID.WIPE_BUNDLE -> wantLoggedIn { DB.wipeBundle(it, eventText) } EventOuterClass.Event.EventID.WIPE_UBUNTU -> wantLoggedIn { DB.wipeUbuntu(it, eventText) } EventOuterClass.Event.EventID.SET_BUNDLE -> wantLoggedIn { DB.setBundle( jm.readValue(eventText, JSON.QwilightSetBundle::class.java), it ) } EventOuterClass.Event.EventID.SET_CONFIGURE -> wantLoggedIn { DB.setConfigure( jm.readValue(eventText, JSON.QwilightSetConfigure::class.java), it ) } EventOuterClass.Event.EventID.NEW_SITE -> wantEstablished { putSite(jm.readValue(eventText, JSON.QwilightNewSite::class.java)) } EventOuterClass.Event.EventID.NEW_SILENT_SITE -> wantEstablished { putSilentSite( eventText ) } EventOuterClass.Event.EventID.COMMENT -> wantLoggedIn { val text = jm.readValue(eventText, JSON.QwilightComment::class.java) val dataID = text.dataID val multiplier = text.multiplier val autoMode = text.autoMode val noteSaltMode = text.noteSaltMode val audioMultiplier = text.audioMultiplier val faintNoteMode = text.faintNoteMode val judgmentMode = text.judgmentMode val hitPointsMode = text.hitPointsMode val noteMobilityMode = text.noteMobilityMode val longNoteMode = text.longNoteMode val inputFavorMode = text.inputFavorMode val noteModifyMode = text.noteModifyMode val lowestJudgmentConditionMode = text.lowestJudgmentConditionMode val stand = text.stand val band = text.band val point = text.point val salt = text.salt val isPaused = text.isPaused val inputFlags = text.inputFlags val commentFileContents = eventData[1] if (longNoteMode != Component.INPUT_LONG_NOTE_MODE && judgmentMode != Component.FAVOR_JUDGMENT_MODE && hitPointsMode != Component.FAVOR_HIT_POINTS_MODE && hitPointsMode != Component.TEST_HIT_POINTS_MODE && noteModifyMode != Component.LONG_NOTE_NOTE_MODIFY_MODE) { val noteFileData = eventData[0].toByteArray() val targetComputing = BaseCompiler.handleCompile(noteFileData, dataID).single() val noteID512 = Utility.getID512(noteFileData) if (!targetComputing.isBanned && !BannedNoteSystem.isBanned(targetComputing.noteID)) { logFuture { Files.write(TwilightComponent.NOTE_ENTRY_PATH.resolve(noteID512), noteFileData) DB.setNote( targetComputing.noteID, Utility.getID128(noteFileData), Utility.getID256(noteFileData), targetComputing ) val lastTitles = DB.getTitleItems(it) val inputMode = targetComputing.inputMode val lastAvatarAbility = when (inputMode) { Component.InputMode.INPUT_MODE_6 -> DB.getAvatarAbility6K(avatarID) Component.InputMode.INPUT_MODE_5_1 -> DB.getAvatarAbility5K(avatarID) Component.InputMode.INPUT_MODE_7_1 -> DB.getAvatarAbility7K(avatarID) Component.InputMode.INPUT_MODE_9 -> DB.getAvatarAbility9K(avatarID) else -> 0.0 } val lastAvatarLevel = DB.getAvatarLevels(it)[0] val abilityClassVariety = when (inputMode) { Component.InputMode.INPUT_MODE_6 -> AbilityClassSystem.AbilityClassVariety.INPUT_MODE_6K Component.InputMode.INPUT_MODE_5_1 -> AbilityClassSystem.AbilityClassVariety.INPUT_MODE_5K Component.InputMode.INPUT_MODE_7_1 -> AbilityClassSystem.AbilityClassVariety.INPUT_MODE_7K Component.InputMode.INPUT_MODE_9 -> AbilityClassSystem.AbilityClassVariety.INPUT_MODE_9K else -> null } val lastAbilityClassText = if (abilityClassVariety != null) AbilityClassSystem.getText( abilityClassVariety, lastAvatarAbility ) else null val comment = CommentOuterClass.Comment.parseFrom(commentFileContents) val isBand1 = comment.lowestJudgment == 0 val lowestAudioMultiplier = comment.audioMultipliersList.map { it.audioMultiplier }.plus(audioMultiplier) .min() DB.saveHandled( avatarID, targetComputing.noteID, isBand1, point == 1.0, autoMode, judgmentMode, hitPointsMode, longNoteMode, inputFavorMode, noteModifyMode, lowestAudioMultiplier ) val commentFileData = commentFileContents.toByteArray() val commentIDNew = Utility.getID512(commentFileData) val (commentID, standBefore) = DB.saveComment( multiplier, autoMode, noteSaltMode, audioMultiplier, faintNoteMode, judgmentMode, hitPointsMode, noteMobilityMode, longNoteMode, inputFavorMode, noteModifyMode, lowestJudgmentConditionMode, stand, band, isBand1, point, salt, commentIDNew, it, targetComputing.noteID, isPaused, inputFlags, lowestAudioMultiplier, comment.audioMultipliersList.map { it.audioMultiplier }.plus(audioMultiplier) .max(), ) if (SiteHandler.hasAvatar(this, SiteHandler.commentSiteID)) { SiteHandler.putSiteYell( SiteHandler.commentSiteID, JSON.TwilightCommentSiteYell( it, avatarName, targetComputing.artist, targetComputing.title, targetComputing.genre, targetComputing.levelText, targetComputing.level, stand, hitPointsMode ) ) } if (commentID != null) { val commentEntryPath = TwilightComponent.COMMENT_ENTRY_PATH.resolve(noteID512) Files.createDirectories(commentEntryPath) if (commentID.isNotEmpty()) { Files.deleteIfExists(commentEntryPath.resolve("$commentID.xz")) } commentFileData.inputStream().use { XZCompressorOutputStream(Files.newOutputStream(commentEntryPath.resolve("$commentIDNew.xz"))).use { os -> IOUtils.copy(it, os) } } val avatarAbility = when (inputMode) { Component.InputMode.INPUT_MODE_6 -> DB.getAvatarAbility6K(avatarID) Component.InputMode.INPUT_MODE_5_1 -> DB.getAvatarAbility5K(avatarID) Component.InputMode.INPUT_MODE_7_1 -> DB.getAvatarAbility7K(avatarID) Component.InputMode.INPUT_MODE_9 -> DB.getAvatarAbility9K(avatarID) else -> 0.0 } val distanceAvatarAbility = avatarAbility - lastAvatarAbility if (0 < distanceAvatarAbility) { send(EventOuterClass.Event.EventID.ABILITY_UP, object { val inputMode = inputMode val ability = distanceAvatarAbility }) if (SiteHandler.hasAvatar(this, SiteHandler.commentSiteID)) { SiteHandler.putSiteYell( SiteHandler.commentSiteID, JSON.TwilightAbilitySiteYell( it, avatarName, inputMode, distanceAvatarAbility ) ) } } val abilityClassText = if (abilityClassVariety != null) AbilityClassSystem.getText( abilityClassVariety, avatarAbility ) else null if (lastAbilityClassText != abilityClassText) { send(EventOuterClass.Event.EventID.ABILITY_CLASS_UP, abilityClassText) PlatformSystem.setAbilityName(avatarID) } DB.getBetweenAvatarIDs(targetComputing.noteID, avatarID, standBefore, stand) .forEach { val toNotifyPass = DB.getNotifyPass(it) if (toNotifyPass == NOTIFY_PASS_AVATAR || (toNotifyPass == NOTIFY_PASS_UBUNTU && DB.isCrossUbuntu( avatarID, it )) ) { val avatar = AvatarHandler.getAvatar(it) if (avatar != null) { avatar.send( EventOuterClass.Event.EventID.NOTIFY_PASS, arrayOf(object { val title = targetComputing.title val artist = targetComputing.artist val genre = targetComputing.genre val levelText = targetComputing.levelText val avatarID = this@QwilightAvatar.avatarID val avatarName = this@QwilightAvatar.avatarName val noteID = targetComputing.noteID }) ) } else { DB.setPass(it, targetComputing, avatarID) } } } } val titles = DB.getTitleItems(it) val distanceTitles = titles.filter { !lastTitles.contains(it) }.toList() if (distanceTitles.isNotEmpty()) { distanceTitles.forEach { send( EventOuterClass.Event.EventID.NEW_TITLE, translateTitleItem(it) ) } } val avatarLevel = DB.getAvatarLevels(it)[0] if (lastAvatarLevel < avatarLevel) { send(EventOuterClass.Event.EventID.LEVEL_UP, object { val from = lastAvatarLevel val to = avatarLevel }) } } } } } EventOuterClass.Event.EventID.VALVE_COMMENT -> wantNotLoggedIn { val text = jm.readValue(eventText, JSON.QwilightValveComment::class.java) val targetComputing = BaseCompiler.handleCompile(eventData[0].toByteArray(), text.dataID).single() if (!targetComputing.isBanned && !BannedNoteSystem.isBanned(targetComputing.noteID)) { logFuture { if (SiteHandler.hasAvatar(this, SiteHandler.commentSiteID)) { SiteHandler.putSiteYell( SiteHandler.commentSiteID, JSON.TwilightCommentSiteYell( avatarID, avatarName, targetComputing.artist, targetComputing.title, targetComputing.genre, targetComputing.levelText, targetComputing.level, text.stand, text.hitPointsMode ) ) } } } } EventOuterClass.Event.EventID.QUIT_SITE -> wantEstablished { SiteHandler.quitAvatar( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.SET_SITUATION -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetSituation::class.java) this.situationValue = text.situationValue this.situationText = text.situationText } EventOuterClass.Event.EventID.EXILE_AVATAR -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightExileAvatar::class.java) AvatarHandler.getAvatar(text.avatarID)?.let { SiteHandler.exileAvatar( UUID.fromString( text.siteID ), this, it ) } } EventOuterClass.Event.EventID.LEVY_NET -> wantEstablished { SiteHandler.setLevying( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.COMPILED -> wantEstablished { SiteHandler.setCompiled( this, jm.readValue(eventText, JSON.QwilightCompiled::class.java) ) } EventOuterClass.Event.EventID.CALL_NET -> wantEstablished { SiteHandler.setNetData( this, msg.qwilightCallNet, if (eventData.isEmpty()) null else CommentOuterClass.Comment.parseFrom( eventData[0] ) ) } EventOuterClass.Event.EventID.SET_MODE_COMPONENT -> wantEstablished { SiteHandler.setModeComponent( this, jm.readValue(eventText, JSON.QwilightSetModeComponent::class.java) ) } EventOuterClass.Event.EventID.SET_FAVOR_NOTE_FILE -> wantEstablished { SiteHandler.setFavorNoteFile( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.SET_FAVOR_MODE_COMPONENT -> wantEstablished { SiteHandler.setFavorModeComponent( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.SET_FAVOR_AUDIO_MULTIPLIER -> wantEstablished { SiteHandler.setFavorAudioMultiplier( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.SET_AVATAR_GROUP -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetAvatarGroup::class.java) SiteHandler.setAvatarGroup( this, UUID.fromString(text.siteID), text ) } EventOuterClass.Event.EventID.SET_AUTO_SITE_HAND -> wantEstablished { SiteHandler.setAutoSiteHand( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.SET_VALID_HUNTER_MODE -> wantEstablished { SiteHandler.setValidHunterMode( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.SET_VALID_NET_MODE -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetValidNetMode::class.java) SiteHandler.setValidNetMode( this, UUID.fromString(text.siteID), text ) } EventOuterClass.Event.EventID.SET_ALLOWED_POSTABLE_ITEMS -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetAllowedPostableItems::class.java) SiteHandler.setAllowedPostableItems( this, UUID.fromString(text.siteID), text ) } EventOuterClass.Event.EventID.SET_POSTABLE_ITEM_BAND -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetPostableItemBand::class.java) SiteHandler.setPostableItemBand( this, UUID.fromString(text.siteID), text ) } EventOuterClass.Event.EventID.CALL_NET_SITE_COMMENTS -> wantEstablished { SiteHandler.doCallNetSiteComments( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.SET_NOTE_FILE -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetNoteFile::class.java) SiteHandler.setNoteFileContents(this, UUID.fromString(text.siteID), text) } EventOuterClass.Event.EventID.SET_SITE_HAND -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetSiteHand::class.java) AvatarHandler.getAvatar(text.avatarID)?.let { SiteHandler.setSiteHand( this, UUID.fromString(text.siteID), it ) } } EventOuterClass.Event.EventID.SET_SITE_NAME -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetSiteName::class.java) setSiteName(text.siteID, text.siteName) } EventOuterClass.Event.EventID.POST_FILE -> wantEstablished { logFuture { val tmpPath = Configure.path.tmpPath.resolve( RandomStringUtils.randomAlphanumeric(8) + "." + eventText ) Files.write(Configure.path.wwwPath.resolve(tmpPath), eventData[0].toByteArray()) send( EventOuterClass.Event.EventID.POST_FILE, "${Configure.www.remote}/${ tmpPath.toString().replace(" ".toRegex(), "%20") }" ) } } EventOuterClass.Event.EventID.WWW_LEVEL -> wantLoggedIn { avatarID -> val text = jm.readValue(eventText, JSON.QwilightWwwLevel::class.java) DB.getClearedLevelIDs(avatarID).thenAccept { val levelItems = LevelSystem.getSatisfiedLevelItems(text) levelItems.filter { levelItem -> !it.contains( levelItem.levelID ) }.forEach { levelItem -> val lastTitles = DB.getTitleItems(avatarID) DB.setWwwLevel(avatarID, levelItem.levelID) val titles = DB.getTitleItems(avatarID) val distanceTitles = titles.filter { !lastTitles.contains(it) }.toList() if (distanceTitles.isNotEmpty()) { distanceTitles.forEach { send( EventOuterClass.Event.EventID.NEW_TITLE, translateTitleItem(it) ) } } send(EventOuterClass.Event.EventID.WWW_LEVEL, levelItem.title) if (SiteHandler.hasAvatar(this, SiteHandler.commentSiteID)) { SiteHandler.putSiteYell( SiteHandler.commentSiteID, JSON.TwilightLevelSiteYell(avatarID, avatarName, levelItem.title) ) } } } } EventOuterClass.Event.EventID.CALL_IO -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightCallIO::class.java) val targetAvatarID = Utility.getDefaultAvatarID(text.avatarID) if (Utility.getDefaultAvatarID(avatarID) == targetAvatarID) { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("ioAvatarIsYou") val isUrgent = true val v = 2 }) } else { AvatarHandler.getAvatar(text.avatarID)?.let { ioAvatar -> DB.getIOCompetence(targetAvatarID).thenAccept { if (it == IO_CALLABLE || it == IO_AVATAR && isLoggedIn || it == IO_UBUNTU && DB.isCrossUbuntu( avatarID, targetAvatarID ) ) { ioAvatar.send(EventOuterClass.Event.EventID.CALL_IO, object { val handlerID = text.handlerID val avatarID = avatarID val ioMillis = text.ioMillis }) } else { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("lowerCompetenceAsIO") val isUrgent = true val v = 2 }) } } } } } EventOuterClass.Event.EventID.IO_NOT -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightIONot::class.java) AvatarHandler.getAvatar(text.avatarID)?.send( EventOuterClass.Event.EventID.IO_NOT, text.handlerID ) } EventOuterClass.Event.EventID.CALL_IO_COMPONENT -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightCallIOComponent::class.java) AvatarHandler.getAvatar(text.avatarID) ?.send(EventOuterClass.Event.EventID.CALL_IO_COMPONENT, object { val avatarID = avatarID val avatarName = this@QwilightAvatar.avatarName val noteID = text.noteID val handlerID = text.handlerID @JvmField val isFailMode = text.isFailMode val data = text.data val ioHandlerID = text.ioHandlerID val ioMillis = text.ioMillis val targetIOMillis = text.targetIOMillis }) } EventOuterClass.Event.EventID.COMPILED_IO -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightCompiledIO::class.java) AvatarHandler.getAvatar(text.avatarID)?.send(EventOuterClass.Event.EventID.COMPILED_IO, object { val handlerID = text.handlerID @JvmField val isCompiled = text.isCompiled val avatarID = avatarID val avatarName = this@QwilightAvatar.avatarName }) } EventOuterClass.Event.EventID.LEVY_IO -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightLevyIO::class.java) text.avatarIDs.forEach { AvatarHandler.getAvatar(it)?.send( EventOuterClass.Event.EventID.LEVY_IO, object { val handlerID = text.handlerID val levyingWait = text.levyingWait val lastStand = text.lastStand @JvmField val isF = text.isF val multiplier = text.multiplier val audioMultiplier = text.audioMultiplier val ioMillis = text.ioMillis }, eventData[0] ) } } EventOuterClass.Event.EventID.IO_INPUT -> wantEstablished { val qwilightIOInput = msg.qwilightIOInput qwilightIOInput.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_INPUT twilightIOInput = EventOuterClass.Event.TwilightIOInput.newBuilder().apply { handlerID = qwilightIOInput.handlerID input = qwilightIOInput.input this.power = qwilightIOInput.power }.build() }.build() ) } } EventOuterClass.Event.EventID.IO_JUDGE -> wantEstablished { val qwilightIOJudge = msg.qwilightIOJudge qwilightIOJudge.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_JUDGE twilightIOJudge = EventOuterClass.Event.TwilightIOJudge.newBuilder().apply { handlerID = qwilightIOJudge.handlerID noteID = qwilightIOJudge.noteID this.judged = qwilightIOJudge.judged }.build() }.build() ) } } EventOuterClass.Event.EventID.IO_NOTE_VISIBILITY -> wantEstablished { val qwilightIONoteVisibility = msg.qwilightIONoteVisibility qwilightIONoteVisibility.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_NOTE_VISIBILITY twilightIONoteVisibility = EventOuterClass.Event.TwilightIONoteVisibility.newBuilder().apply { handlerID = qwilightIONoteVisibility.handlerID noteID = qwilightIONoteVisibility.noteID setValidJudgedNotes = qwilightIONoteVisibility.setValidJudgedNotes setNoteFailed = qwilightIONoteVisibility.setNoteFailed }.build() }.build() ) } } EventOuterClass.Event.EventID.IO_JUDGMENT_METER -> wantEstablished { val qwilightIOJudgmentMeter = msg.qwilightIOJudgmentMeter qwilightIOJudgmentMeter.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_JUDGMENT_METER twilightIOJudgmentMeter = EventOuterClass.Event.TwilightIOJudgmentMeter.newBuilder().apply { handlerID = qwilightIOJudgmentMeter.handlerID input = qwilightIOJudgmentMeter.input judgmentMeter = qwilightIOJudgmentMeter.judgmentMeter assist = qwilightIOJudgmentMeter.assist }.build() }.build() ) } } EventOuterClass.Event.EventID.IO_MULTIPLIER -> wantEstablished { val qwilightIOMultiplier = msg.qwilightIOMultiplier qwilightIOMultiplier.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_MULTIPLIER twilightIOMultiplier = EventOuterClass.Event.TwilightIOMultiplier.newBuilder().apply { handlerID = qwilightIOMultiplier.handlerID multiplier = qwilightIOMultiplier.multiplier }.build() }.build() ) } } EventOuterClass.Event.EventID.IO_AUDIO_MULTIPLIER -> wantEstablished { val qwilightIOAudioMultiplier = msg.qwilightIOAudioMultiplier qwilightIOAudioMultiplier.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_AUDIO_MULTIPLIER twilightIOAudioMultiplier = EventOuterClass.Event.TwilightIOAudioMultiplier.newBuilder().apply { handlerID = qwilightIOAudioMultiplier.handlerID audioMultiplier = qwilightIOAudioMultiplier.audioMultiplier }.build() }.build() ) } } EventOuterClass.Event.EventID.IO_PAUSE -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightIOPause::class.java) text.avatarIDs.forEach { AvatarHandler.getAvatar(it)?.send(EventOuterClass.Event.EventID.IO_PAUSE, object { val handlerID = text.handlerID @JvmField val isPaused = text.isPaused }) } } EventOuterClass.Event.EventID.IO_QUIT -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightIOQuit::class.java) text.avatarIDs.forEach { AvatarHandler.getAvatar(it)?.send(EventOuterClass.Event.EventID.IO_QUIT, object { val avatarID = (if (text.isBanned) "" else avatarID) val handlerID = text.handlerID }) } } EventOuterClass.Event.EventID.AUDIO_INPUT -> wantEstablished { SiteHandler.audioInput( UUID.fromString(eventText), this, eventData[0] ) } EventOuterClass.Event.EventID.QUIT_NET -> wantEstablished { SiteHandler.stopSiteNet( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.COMMENTARY -> wantLoggedIn { val text = jm.readValue(eventText, JSON.QwilightCommentary::class.java) DB.setCommentary(text.noteID, it, text.commentary).thenAccept { isOK -> if (!isOK) { send(EventOuterClass.Event.EventID.NOTIFY, object { val text = translateLanguage("hasNotComment") val isUrgent = true val v = 2 }) } } } EventOuterClass.Event.EventID.AVATAR_TITLE -> wantLoggedIn { DB.setAvatarTitle(it, eventText).thenRun { AvatarHandler.sendInvalidateAvatarTitle( it ) } } EventOuterClass.Event.EventID.AVATAR_EDGE -> wantLoggedIn { DB.setAvatarEdge(it, eventText).thenRun { AvatarHandler.sendInvalidateAvatarEdge( it ) } } EventOuterClass.Event.EventID.SET_LANGUAGE -> wantEstablished { language = eventText } EventOuterClass.Event.EventID.SET_FAVOR -> wantLoggedIn { DB.setFavor(jm.readValue(eventText, JSON.QwilightSetFavor::class.java), avatarID) } EventOuterClass.Event.EventID.POST_ITEM -> wantEstablished { val qwilightPostItem = msg.qwilightPostItem SiteHandler.postItem( UUID.fromString(qwilightPostItem.siteID), this, qwilightPostItem ) } else -> Unit } } } } override val loggerID: String get() = if (isEstablished) { if (isLoggedIn) { "{ID: $avatarID, Name: $avatarName, IP: $remote, Date: v$qwilightDate (${ qwilightHash.substring( 0, 8 ) })}" } else { if (isAwilight) { "{ID: $avatarID, Name: $avatarName}" } else if (avatarEstimatedID.isNotEmpty()) { "{ID: $avatarID ($avatarEstimatedID?), Name: $avatarName, IP: $remote, Date: v$qwilightDate (${ qwilightHash.substring( 0, 8 ) })}" } else { "{ID: $avatarID, Name: $avatarName, IP: $remote, Date: v$qwilightDate (${ qwilightHash.substring( 0, 8 ) })}" } } } else { if (qwilightDate.isEmpty() || qwilightHash.isEmpty()) { "{IP: $remote}}" } else { "{IP: $remote}, Date: v$qwilightDate (${qwilightHash.substring(0, 8)})}" } } override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { logFault(cause) } override fun logInfo(toNotify: String) { LoggerFactory.getLogger(javaClass).info("[{}] {}", loggerID, toNotify) } override fun logFault(e: Throwable) { if (Utility.isValidFault(e)) { LoggerFactory.getLogger(javaClass).error("[{}] {}", loggerID, Utility.getFaultText(e)) } } companion object { const val NOT_UBUNTU = 0 const val NOT_SIGNED_IN = 1 const val NOTIFY_UBUNTU_UBUNTU = 0 const val NOTIFY_SAVE_BUNDLE_CALLABLE = 0 const val NOTIFY_SAVE_BUNDLE_AVATAR = 1 const val NOTIFY_SAVE_BUNDLE_UBUNTU = 2 const val NOTIFY_PASS_AVATAR = 0 const val NOTIFY_PASS_UBUNTU = 1 const val NOTIFY_PASS_VOID = 2 const val IO_CALLABLE = 0 const val IO_UBUNTU = 1 const val IO_AVATAR = 3 const val BUNDLE_CALLABLE = 0 const val BUNDLE_UBUNTU = 1 const val BUNDLE_VOID = 2 const val BUNDLE_AVATAR = 3 const val SILENT_SITE_CALLABLE = 0 const val SILENT_SITE_UBUNTU = 1 const val NOTE_FILE_MODE = 2 const val SILENT_SITE_AVATAR = 3 const val DEFAULT_COMPUTING = 3 const val COMMENT_COMPUTING = 4 const val AUTO_COMPUTING = 5 const val QUIT_MODE = 6 const val NET_COMPUTING = 7 const val IO_COMPUTING = 8 const val BUNDLE_AVAILABLE_COUNT = 1 } }