Skip to content

Commit

Permalink
Improve shutdown behavior
Browse files Browse the repository at this point in the history
Erdragh committed Dec 2, 2023
1 parent 56db912 commit f51e32f
Showing 6 changed files with 112 additions and 68 deletions.
25 changes: 22 additions & 3 deletions common/src/main/kotlin/dev/erdragh/astralbot/Bot.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package dev.erdragh.astralbot

import dev.erdragh.astralbot.commands.CommandHandlingListener
import dev.erdragh.astralbot.handlers.FAQHandler
import dev.erdragh.astralbot.handlers.MinecraftHandler
import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.JDABuilder
import net.dv8tion.jda.api.requests.GatewayIntent
import net.minecraft.server.MinecraftServer
@@ -10,20 +12,37 @@ import org.slf4j.LoggerFactory

val LOGGER: Logger = LoggerFactory.getLogger("AstralBot")
var minecraftHandler: MinecraftHandler? = null
fun setupAstralbot(server: MinecraftServer) {
var jda: JDA? = null

fun startAstralbot(server: MinecraftServer) {
minecraftHandler = MinecraftHandler(server)
FAQHandler.start()
val env = System.getenv()
if (!env.containsKey("DISCORD_TOKEN")) {
LOGGER.info("Not starting AstralBot because of missing DISCORD_TOKEN environment variable.")
return
}
JDABuilder
jda = JDABuilder
.createLight(env["DISCORD_TOKEN"],
GatewayIntent.MESSAGE_CONTENT,
GatewayIntent.GUILD_MESSAGES,
GatewayIntent.GUILD_MEMBERS)
.addEventListeners(CommandHandlingListener)
.build().awaitReady()
.build()

LOGGER.info("AstralBot fully started")

Runtime.getRuntime().addShutdownHook(object : Thread() {
override fun run() {
stopAstralbot()
}
})
}

fun stopAstralbot() {
LOGGER.info("Shutting down AstralBot")
FAQHandler.stop()
jda?.shutdown()
jda?.awaitShutdown()
LOGGER.info("Shut down AstralBot")
}
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEve
import net.dv8tion.jda.api.hooks.ListenerAdapter

object CommandHandlingListener : ListenerAdapter() {

override fun onGuildJoin(event: GuildJoinEvent) {
event.guild.updateCommands().addCommands(
commands.map { it.command }
63 changes: 20 additions & 43 deletions common/src/main/kotlin/dev/erdragh/astralbot/handlers/FAQHandler.kt
Original file line number Diff line number Diff line change
@@ -2,20 +2,16 @@ package dev.erdragh.astralbot.handlers

import dev.erdragh.astralbot.LOGGER
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
import java.nio.file.WatchKey
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
import kotlin.io.path.nameWithoutExtension

object FAQHandler {
private val faqDirectory = File("faq")
private val availableFAQIDs = ArrayList<String>()
private var watcherThread: Thread? = null
private val availableFAQIDs = HashSet<String>()
private lateinit var watcher: FileWatcher

init {
fun start() {
LOGGER.info("FAQHandler loading")
if (!faqDirectory.exists() && !faqDirectory.mkdir()) {
LOGGER.error("Couldn't create FAQ directory")
@@ -26,43 +22,23 @@ object FAQHandler {
val findMarkdownRegex = Regex(".+\\.md$")
val faqFiles = faqDirectory.listFiles { it -> !it.isDirectory }?.filter { it.name.matches(findMarkdownRegex) }?.map { it.nameWithoutExtension }
faqFiles?.forEach(availableFAQIDs::add)
watcherThread = (Thread {
val watcher = FileSystems.getDefault().newWatchService()

val faqDirectoryPath = faqDirectory.toPath()
faqDirectoryPath.register(
watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE
)

try {
var key: WatchKey = watcher.take()
while (!Thread.currentThread().isInterrupted) {
for (event in key.pollEvents()) {
val ctx = event.context()
val path = event.context() as Path
LOGGER.debug("Handling File Event: {} for {}", event.kind(), ctx)
if (!path.isDirectory() && path.extension == "md") {
when (event.kind()) {
StandardWatchEventKinds.ENTRY_CREATE -> {
availableFAQIDs.add(path.nameWithoutExtension)
}

StandardWatchEventKinds.ENTRY_DELETE -> {
availableFAQIDs.remove(path.nameWithoutExtension)
}
}
}
}
key.reset()
key = watcher.take()
watcher = FileWatcher(faqDirectory.toPath()) {
LOGGER.info("Event Handling: {}", it.kind())
val fileName = it.context().nameWithoutExtension
val extension = it.context().extension
if (extension == "md") when (it.kind()) {
StandardWatchEventKinds.ENTRY_CREATE -> {
availableFAQIDs.add(fileName)
}
StandardWatchEventKinds.ENTRY_DELETE -> {
availableFAQIDs.remove(fileName)
}
} catch (_: InterruptedException) {
// Do Nothing. If this thread is interrupted it means it should just stop
}
})
watcherThread?.start()
}
watcher.startWatching()

LOGGER.info("FAQHandler loaded")
}
}

@@ -80,8 +56,9 @@ object FAQHandler {
return availableFAQIDs.filter { it.startsWith(slug, true) }
}

fun shutdownWatcher() {
watcherThread?.interrupt()
fun stop() {
LOGGER.info("Shutting down FileSystem Watcher")
watcher.stopWatching()
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dev.erdragh.astralbot.handlers

import dev.erdragh.astralbot.LOGGER
import kotlinx.coroutines.*
import java.nio.file.*

class FileWatcher(private val directoryPath: Path, private val handler: (event: WatchEvent<Path>) -> Unit) {
private var job: Job? = null
private var watchService: WatchService? = null

fun startWatching() {
job = GlobalScope.launch(Dispatchers.IO) {
watchService = FileSystems.getDefault().newWatchService()
directoryPath.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE
)

try {
while (isActive) {
LOGGER.info("Waiting for watchService WatchKey")
val key = watchService?.take() ?: break
LOGGER.info("Got watchService WatchKey")

for (event in key.pollEvents()) {
LOGGER.info("Event: {}", event.kind())
// Send the event to the channel
handler(event as WatchEvent<Path>)
}

key.reset()
}
} catch (_: ClosedWatchServiceException) {
// Do nothing, this exception means we should just stop
}

LOGGER.info("WatchService ending")
}
}

fun stopWatching() {
watchService?.close()
job?.cancel()
}
}
18 changes: 10 additions & 8 deletions fabric/src/main/kotlin/dev/erdragh/astralbot/fabric/BotMod.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package dev.erdragh.astralbot.fabric

import dev.erdragh.astralbot.LOGGER
import dev.erdragh.astralbot.setupAstralbot
import dev.erdragh.astralbot.startAstralbot
import dev.erdragh.astralbot.stopAstralbot
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents
import java.util.concurrent.atomic.AtomicBoolean

object BotMod : ModInitializer {
private val LOADED = AtomicBoolean(false)
override fun onInitialize() {
ServerWorldEvents.LOAD.register { server, _ ->
if (!LOADED.getAndSet(true)) {
LOGGER.info("Starting AstralBot on Fabric")
setupAstralbot(server)
}
ServerLifecycleEvents.SERVER_STARTED.register {
LOGGER.info("Starting AstralBot on Fabric")
startAstralbot(it)
}

ServerLifecycleEvents.SERVER_STOPPING.register {
stopAstralbot()
}
}
}
26 changes: 13 additions & 13 deletions forge/src/main/kotlin/dev/erdragh/astralbot/forge/BotMod.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
package dev.erdragh.astralbot.forge

import dev.erdragh.astralbot.LOGGER
import dev.erdragh.astralbot.setupAstralbot
import dev.erdragh.astralbot.startAstralbot
import dev.erdragh.astralbot.stopAstralbot
import net.minecraftforge.event.level.LevelEvent
import net.minecraftforge.event.server.ServerStartedEvent
import net.minecraftforge.event.server.ServerStoppingEvent
import net.minecraftforge.fml.common.Mod
import thedarkcolour.kotlinforforge.forge.FORGE_BUS
import java.util.concurrent.atomic.AtomicBoolean

@Mod("astralbot")
object BotMod {
private val LOADED = AtomicBoolean(false)
init {
FORGE_BUS.addListener(::onWorldLoad)
FORGE_BUS.addListener(::onServerStart)
FORGE_BUS.addListener(::onServerStop)
}

private fun onWorldLoad(event: LevelEvent.Load) {
if (!LOADED.getAndSet(true)) {
LOGGER.info("AstralBot starting on Forge")
val server = event.level.server
if (server == null) {
throw IllegalStateException("No server accessible onWorldLoad")
} else {
setupAstralbot(server)
}
}
private fun onServerStart(event: ServerStartedEvent) {
LOGGER.info("AstralBot starting on Forge")
startAstralbot(event.server)
}

private fun onServerStop(event: ServerStoppingEvent) {
stopAstralbot()
}
}

0 comments on commit f51e32f

Please sign in to comment.