From b29c0bbe192c97c75eba304dfdc90e29ee411c15 Mon Sep 17 00:00:00 2001 From: Sri Ramanujam Date: Thu, 4 Nov 2021 01:24:49 -0400 Subject: [PATCH 1/4] Automatically leave voice channel after 4 minutes spent idle --- .../dev/arbjerg/ukulele/UkuleleApplication.kt | 2 + .../dev/arbjerg/ukulele/config/BotProps.kt | 5 +- .../arbjerg/ukulele/features/LeaveOnIdle.kt | 78 +++++++++++++++++++ .../dev/arbjerg/ukulele/jda/EventHandler.kt | 13 +++- 4 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdle.kt diff --git a/src/main/kotlin/dev/arbjerg/ukulele/UkuleleApplication.kt b/src/main/kotlin/dev/arbjerg/ukulele/UkuleleApplication.kt index eed3494..60a206b 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/UkuleleApplication.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/UkuleleApplication.kt @@ -3,9 +3,11 @@ package dev.arbjerg.ukulele import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.boot.runApplication +import org.springframework.scheduling.annotation.EnableScheduling @SpringBootApplication @ConfigurationPropertiesScan +@EnableScheduling class UkuleleApplication fun main(args: Array) { diff --git a/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt b/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt index 61a66ed..8f9bc22 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt @@ -10,5 +10,6 @@ class BotProps( var database: String = "./database", var game: String = "", var trackDurationLimit: Int = 0, - var announceTracks: Boolean = false -) \ No newline at end of file + var announceTracks: Boolean = false, + var idleTimeMinutes: Int = 10 +) diff --git a/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdle.kt b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdle.kt new file mode 100644 index 0000000..cac0d4f --- /dev/null +++ b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdle.kt @@ -0,0 +1,78 @@ +package dev.arbjerg.ukulele.features + +import dev.arbjerg.ukulele.audio.Player +import org.springframework.stereotype.Service +import dev.arbjerg.ukulele.audio.PlayerRegistry +import dev.arbjerg.ukulele.config.BotProps +import dev.arbjerg.ukulele.data.GuildPropertiesService +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import net.dv8tion.jda.api.entities.Guild +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.entities.VoiceChannel +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.scheduling.TaskScheduler +import java.time.Duration +import java.util.concurrent.ScheduledFuture + +@Service +class LeaveOnIdle( + private val players: PlayerRegistry, + private val guildPropertiesService: GuildPropertiesService, + private val scheduler: TaskScheduler, + private val botProps: BotProps +) { + private val log: Logger = LoggerFactory.getLogger(LeaveOnIdle::class.java) + private val timers: MutableMap> = HashMap() + + fun onVoiceJoin(guild: Guild, channel: VoiceChannel, member: Member) { + if (member == guild.selfMember) { + log.info("Joined voice channel {} in guild {}", channel.name, guild.name) + createTimer(guild) + } + } + + fun onVoiceLeave(guild: Guild, channel: VoiceChannel, member: Member) { + if (member == guild.selfMember) { + log.info("Left voice channel {} in guild {}", channel.name, guild.name) + removeTimer(guild) + } + } + + private fun createTimer(guild: Guild) { + GlobalScope.launch { + val taskPeriod = Duration.ofMinutes(1) + val player = players.get(guild, guildPropertiesService.getAwait(guild.idLong)) + val timerTaskFuture = scheduler.scheduleAtFixedRate(IdleTask(guild, player, botProps.idleTimeMinutes), taskPeriod) + + timers[guild] = timerTaskFuture + } + } + + private fun removeTimer(guild: Guild) { + timers[guild]?.let { + it.cancel(true) + timers.remove(guild) + } + } +} + +class IdleTask(val guild: Guild, val player: Player, val idleTimeMinutes: Int) : Runnable { + private var numberOfMinutesIdleElapsed = 0 + private val log: Logger = LoggerFactory.getLogger(IdleTask::class.java) + + override fun run() { + if (player.remainingDuration <= 0) { + // the player is idle, increment our minutes elapsed + numberOfMinutesIdleElapsed++ + } else { + // if the player has something in its queue, reset to 0 + numberOfMinutesIdleElapsed = 0 + } + + if (numberOfMinutesIdleElapsed >= idleTimeMinutes) { + guild.audioManager.closeAudioConnection() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt b/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt index fd9705c..3a57df3 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt @@ -1,7 +1,9 @@ package dev.arbjerg.ukulele.jda -import net.dv8tion.jda.api.events.ReadyEvent +import dev.arbjerg.ukulele.features.LeaveOnIdle import net.dv8tion.jda.api.events.StatusChangeEvent +import net.dv8tion.jda.api.events.guild.voice.GuildVoiceJoinEvent +import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent import net.dv8tion.jda.api.hooks.ListenerAdapter import org.slf4j.Logger @@ -9,7 +11,7 @@ import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @Service -class EventHandler(private val commandManager: CommandManager) : ListenerAdapter() { +class EventHandler(private val commandManager: CommandManager, private val leaveOnIdle: LeaveOnIdle) : ListenerAdapter() { private val log: Logger = LoggerFactory.getLogger(EventHandler::class.java) @@ -22,4 +24,11 @@ class EventHandler(private val commandManager: CommandManager) : ListenerAdapter log.info("{}: {} -> {}", event.entity.shardInfo, event.oldStatus, event.newStatus) } + override fun onGuildVoiceJoin(event: GuildVoiceJoinEvent) { + leaveOnIdle.onVoiceJoin(event.guild, event.channelJoined, event.member) + } + + override fun onGuildVoiceLeave(event: GuildVoiceLeaveEvent) { + leaveOnIdle.onVoiceLeave(event.guild, event.channelLeft, event.member) + } } \ No newline at end of file From 0246a16039c8865a86e0b91084cde44ecb4f76e1 Mon Sep 17 00:00:00 2001 From: Sri Ramanujam Date: Sat, 6 Nov 2021 17:22:30 -0400 Subject: [PATCH 2/4] Re-implemented based on a non-racy design. Unfortunately, trying to model the task as a duration-based timeout saw race conditions where the callback to determine whether to start a timer was called before the callback to add a new track to the player had run. This caused an issue where the code believed that no track was playing when in fact a track would be queued mere milliseconds later. When the video in question was under a minute long, this led to a state where the bot would leave the channel a minute early. I've re-implemented the functionality such that it is invoked as part of the onTrackStart and onTrackEnd callbacks of Player. This guarantees that the idle timer kickoff logic runs _after_ all other Player state updates have completed, which neatly avoids race/ordering bugs. --- .../dev/arbjerg/ukulele/audio/Player.kt | 25 +++++- .../arbjerg/ukulele/audio/PlayerRegistry.kt | 5 +- .../dev/arbjerg/ukulele/config/BotProps.kt | 2 +- .../arbjerg/ukulele/features/LeaveOnIdle.kt | 78 ------------------- .../ukulele/features/LeaveOnIdleService.kt | 67 ++++++++++++++++ .../dev/arbjerg/ukulele/jda/EventHandler.kt | 11 ++- 6 files changed, 100 insertions(+), 88 deletions(-) delete mode 100644 src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdle.kt create mode 100644 src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt diff --git a/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt b/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt index 9a48545..4b8122c 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt @@ -11,8 +11,10 @@ import dev.arbjerg.ukulele.command.NowPlayingCommand import dev.arbjerg.ukulele.config.BotProps import dev.arbjerg.ukulele.data.GuildProperties import dev.arbjerg.ukulele.data.GuildPropertiesService +import dev.arbjerg.ukulele.features.LeaveOnIdleService import net.dv8tion.jda.api.audio.AudioSendHandler import net.dv8tion.jda.api.entities.TextChannel +import net.dv8tion.jda.api.entities.Guild import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @@ -20,7 +22,12 @@ import java.nio.Buffer import java.nio.ByteBuffer -class Player(val beans: Beans, guildProperties: GuildProperties) : AudioEventAdapter(), AudioSendHandler { +class Player( + private val beans: Beans, + guildProperties: GuildProperties, + private val leaveOnIdleService: LeaveOnIdleService, + private val guild: Guild +) : AudioEventAdapter(), AudioSendHandler { @Component class Beans( val apm: AudioPlayerManager, @@ -127,14 +134,26 @@ class Player(val beans: Beans, guildProperties: GuildProperties) : AudioEventAda if (beans.botProps.announceTracks) { lastChannel?.sendMessage(beans.nowPlayingCommand.buildEmbed(track))?.queue() } + + log.debug("onTrackStart called for player in guild {}", guild.idLong) + // handle idle timer cleanup + leaveOnIdleService.maybeDestroyTimer(guild) } override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) { + log.debug("onTrackEnd called for player in guild {}", guild.idLong) if (isRepeating && endReason.mayStartNext) { queue.add(track.makeClone()) } - val new = queue.take() ?: return - player.playTrack(new) + val new = queue.take() + if (new != null) { + player.playTrack(new) + } + + if (remainingDuration <= 0) { + // if remainingDuration is 0, there is nothing playing. handle idle timer creation + leaveOnIdleService.maybeCreateTimer(guild) + } } override fun onTrackException(player: AudioPlayer, track: AudioTrack, exception: FriendlyException) { diff --git a/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt b/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt index e89b061..f13f11f 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt @@ -1,14 +1,15 @@ package dev.arbjerg.ukulele.audio import dev.arbjerg.ukulele.data.GuildProperties +import dev.arbjerg.ukulele.features.LeaveOnIdleService import net.dv8tion.jda.api.entities.Guild import org.springframework.stereotype.Service @Service -class PlayerRegistry(val playerBeans: Player.Beans) { +class PlayerRegistry(val playerBeans: Player.Beans, val leaveOnIdleService: LeaveOnIdleService) { private val players = mutableMapOf() - fun get(guild: Guild, guildProperties: GuildProperties) = players.computeIfAbsent(guild.idLong) { Player(playerBeans, guildProperties) } + fun get(guild: Guild, guildProperties: GuildProperties) = players.computeIfAbsent(guild.idLong) { Player(playerBeans, guildProperties, leaveOnIdleService, guild) } } \ No newline at end of file diff --git a/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt b/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt index 8f9bc22..b9d629a 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/config/BotProps.kt @@ -11,5 +11,5 @@ class BotProps( var game: String = "", var trackDurationLimit: Int = 0, var announceTracks: Boolean = false, - var idleTimeMinutes: Int = 10 + var idleTimeMinutes: Int = 0 ) diff --git a/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdle.kt b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdle.kt deleted file mode 100644 index cac0d4f..0000000 --- a/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdle.kt +++ /dev/null @@ -1,78 +0,0 @@ -package dev.arbjerg.ukulele.features - -import dev.arbjerg.ukulele.audio.Player -import org.springframework.stereotype.Service -import dev.arbjerg.ukulele.audio.PlayerRegistry -import dev.arbjerg.ukulele.config.BotProps -import dev.arbjerg.ukulele.data.GuildPropertiesService -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import net.dv8tion.jda.api.entities.Guild -import net.dv8tion.jda.api.entities.Member -import net.dv8tion.jda.api.entities.VoiceChannel -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.scheduling.TaskScheduler -import java.time.Duration -import java.util.concurrent.ScheduledFuture - -@Service -class LeaveOnIdle( - private val players: PlayerRegistry, - private val guildPropertiesService: GuildPropertiesService, - private val scheduler: TaskScheduler, - private val botProps: BotProps -) { - private val log: Logger = LoggerFactory.getLogger(LeaveOnIdle::class.java) - private val timers: MutableMap> = HashMap() - - fun onVoiceJoin(guild: Guild, channel: VoiceChannel, member: Member) { - if (member == guild.selfMember) { - log.info("Joined voice channel {} in guild {}", channel.name, guild.name) - createTimer(guild) - } - } - - fun onVoiceLeave(guild: Guild, channel: VoiceChannel, member: Member) { - if (member == guild.selfMember) { - log.info("Left voice channel {} in guild {}", channel.name, guild.name) - removeTimer(guild) - } - } - - private fun createTimer(guild: Guild) { - GlobalScope.launch { - val taskPeriod = Duration.ofMinutes(1) - val player = players.get(guild, guildPropertiesService.getAwait(guild.idLong)) - val timerTaskFuture = scheduler.scheduleAtFixedRate(IdleTask(guild, player, botProps.idleTimeMinutes), taskPeriod) - - timers[guild] = timerTaskFuture - } - } - - private fun removeTimer(guild: Guild) { - timers[guild]?.let { - it.cancel(true) - timers.remove(guild) - } - } -} - -class IdleTask(val guild: Guild, val player: Player, val idleTimeMinutes: Int) : Runnable { - private var numberOfMinutesIdleElapsed = 0 - private val log: Logger = LoggerFactory.getLogger(IdleTask::class.java) - - override fun run() { - if (player.remainingDuration <= 0) { - // the player is idle, increment our minutes elapsed - numberOfMinutesIdleElapsed++ - } else { - // if the player has something in its queue, reset to 0 - numberOfMinutesIdleElapsed = 0 - } - - if (numberOfMinutesIdleElapsed >= idleTimeMinutes) { - guild.audioManager.closeAudioConnection() - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt new file mode 100644 index 0000000..d5e9b3f --- /dev/null +++ b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt @@ -0,0 +1,67 @@ +package dev.arbjerg.ukulele.features + +import org.springframework.stereotype.Service +import dev.arbjerg.ukulele.config.BotProps +import net.dv8tion.jda.api.entities.Guild +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.scheduling.TaskScheduler +import java.time.Duration +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ScheduledFuture + +@Service +class LeaveOnIdleService(private val scheduler: TaskScheduler, private val botProps: BotProps) { + private val log: Logger = LoggerFactory.getLogger(LeaveOnIdleService::class.java) + private val timers: ConcurrentHashMap> = ConcurrentHashMap() + + /** + * Runnable that gets passed to the Spring TaskScheduler. Used to keep track of how many minutes + * a player in a guild has spent idling. + */ + class IdleTask(val guild: Guild, private val idleTimeMinutes: Int) : Runnable { + private var numberOfMinutesIdleElapsed = -1 + + override fun run() { + numberOfMinutesIdleElapsed++ + + if (numberOfMinutesIdleElapsed >= idleTimeMinutes) { + guild.audioManager.closeAudioConnection() + } + } + } + + /** + * Create a new timer which will start counting from the current moment and cancel any existing timers + * associated with this guild. + */ + fun maybeCreateTimer(guild: Guild) { + // don't do anything if idleTimeMinutes is not set + if (botProps.idleTimeMinutes <= 0) return + + log.info("Player for guild {} is idle, starting timeout of {} minutes", guild.idLong, botProps.idleTimeMinutes) + val interval = Duration.ofMinutes(1) + val task = IdleTask(guild, botProps.idleTimeMinutes) + val fut = scheduler.scheduleAtFixedRate(task, interval) + + if (timers.containsKey(guild.idLong)) { + timers.replace(guild.idLong, fut)?.cancel(true) + } else { + timers[guild.idLong] = fut + } + } + + /** + * If a timer exists for this guild, cancel it. + */ + fun maybeDestroyTimer(guild: Guild) { + // don't do anything if idleTimeMinutes is not set + if (botProps.idleTimeMinutes <= 0) return + + timers[guild.idLong]?.let { + log.debug("Cleaning up idle timer for guild {}", guild.idLong) + it.cancel(true) + timers.remove(guild.idLong) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt b/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt index 3a57df3..14df442 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt @@ -1,6 +1,6 @@ package dev.arbjerg.ukulele.jda -import dev.arbjerg.ukulele.features.LeaveOnIdle +import dev.arbjerg.ukulele.features.LeaveOnIdleService import net.dv8tion.jda.api.events.StatusChangeEvent import net.dv8tion.jda.api.events.guild.voice.GuildVoiceJoinEvent import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent @@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory import org.springframework.stereotype.Service @Service -class EventHandler(private val commandManager: CommandManager, private val leaveOnIdle: LeaveOnIdle) : ListenerAdapter() { +class EventHandler(private val commandManager: CommandManager, private val leaveOnIdleService: LeaveOnIdleService) : ListenerAdapter() { private val log: Logger = LoggerFactory.getLogger(EventHandler::class.java) @@ -25,10 +25,13 @@ class EventHandler(private val commandManager: CommandManager, private val leave } override fun onGuildVoiceJoin(event: GuildVoiceJoinEvent) { - leaveOnIdle.onVoiceJoin(event.guild, event.channelJoined, event.member) + log.info("Joining voice channel {} in guild {}", event.channelJoined, event.guild) } override fun onGuildVoiceLeave(event: GuildVoiceLeaveEvent) { - leaveOnIdle.onVoiceLeave(event.guild, event.channelLeft, event.member) + log.info("Leaving voice channel {} in guild {}", event.channelLeft, event.guild) + + // clean up idle timer, if it exists + leaveOnIdleService.maybeDestroyTimer(event.guild) } } \ No newline at end of file From 6ab6f87622cd2ca4d77aff7bd3c56c758b5e531b Mon Sep 17 00:00:00 2001 From: Sri Ramanujam Date: Mon, 8 Nov 2021 21:42:00 -0500 Subject: [PATCH 3/4] Changes from code review * put leaveOnIdleService in Player.Beans * better names for leaveOnIdleService's callbacks * Directly call timers.remove() * Schedule VC leave task directly, rather than periodically calling timer * Clean up redundant comments --- .../dev/arbjerg/ukulele/audio/Player.kt | 10 +++--- .../arbjerg/ukulele/audio/PlayerRegistry.kt | 4 +-- .../ukulele/features/LeaveOnIdleService.kt | 36 ++++++------------- .../dev/arbjerg/ukulele/jda/EventHandler.kt | 4 +-- 4 files changed, 17 insertions(+), 37 deletions(-) diff --git a/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt b/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt index 4b8122c..a188b30 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/audio/Player.kt @@ -25,7 +25,6 @@ import java.nio.ByteBuffer class Player( private val beans: Beans, guildProperties: GuildProperties, - private val leaveOnIdleService: LeaveOnIdleService, private val guild: Guild ) : AudioEventAdapter(), AudioSendHandler { @Component @@ -33,7 +32,8 @@ class Player( val apm: AudioPlayerManager, val guildProperties: GuildPropertiesService, val nowPlayingCommand: NowPlayingCommand, - val botProps: BotProps + val botProps: BotProps, + val leaveOnIdleService: LeaveOnIdleService ) private val guildId = guildProperties.guildId @@ -136,8 +136,7 @@ class Player( } log.debug("onTrackStart called for player in guild {}", guild.idLong) - // handle idle timer cleanup - leaveOnIdleService.maybeDestroyTimer(guild) + beans.leaveOnIdleService.destroyTimer(guild) } override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) { @@ -151,8 +150,7 @@ class Player( } if (remainingDuration <= 0) { - // if remainingDuration is 0, there is nothing playing. handle idle timer creation - leaveOnIdleService.maybeCreateTimer(guild) + beans.leaveOnIdleService.onQueueEmpty(guild) } } diff --git a/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt b/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt index f13f11f..5dfcd66 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/audio/PlayerRegistry.kt @@ -6,10 +6,10 @@ import net.dv8tion.jda.api.entities.Guild import org.springframework.stereotype.Service @Service -class PlayerRegistry(val playerBeans: Player.Beans, val leaveOnIdleService: LeaveOnIdleService) { +class PlayerRegistry(val playerBeans: Player.Beans) { private val players = mutableMapOf() - fun get(guild: Guild, guildProperties: GuildProperties) = players.computeIfAbsent(guild.idLong) { Player(playerBeans, guildProperties, leaveOnIdleService, guild) } + fun get(guild: Guild, guildProperties: GuildProperties) = players.computeIfAbsent(guild.idLong) { Player(playerBeans, guildProperties, guild) } } \ No newline at end of file diff --git a/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt index d5e9b3f..3033a4e 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt @@ -6,7 +6,7 @@ import net.dv8tion.jda.api.entities.Guild import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.scheduling.TaskScheduler -import java.time.Duration +import java.time.Instant import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ScheduledFuture @@ -15,19 +15,9 @@ class LeaveOnIdleService(private val scheduler: TaskScheduler, private val botPr private val log: Logger = LoggerFactory.getLogger(LeaveOnIdleService::class.java) private val timers: ConcurrentHashMap> = ConcurrentHashMap() - /** - * Runnable that gets passed to the Spring TaskScheduler. Used to keep track of how many minutes - * a player in a guild has spent idling. - */ - class IdleTask(val guild: Guild, private val idleTimeMinutes: Int) : Runnable { - private var numberOfMinutesIdleElapsed = -1 - + inner class IdleTask(val guild: Guild) : Runnable { override fun run() { - numberOfMinutesIdleElapsed++ - - if (numberOfMinutesIdleElapsed >= idleTimeMinutes) { - guild.audioManager.closeAudioConnection() - } + guild.audioManager.closeAudioConnection() } } @@ -35,14 +25,13 @@ class LeaveOnIdleService(private val scheduler: TaskScheduler, private val botPr * Create a new timer which will start counting from the current moment and cancel any existing timers * associated with this guild. */ - fun maybeCreateTimer(guild: Guild) { - // don't do anything if idleTimeMinutes is not set + fun onQueueEmpty(guild: Guild) { if (botProps.idleTimeMinutes <= 0) return log.info("Player for guild {} is idle, starting timeout of {} minutes", guild.idLong, botProps.idleTimeMinutes) - val interval = Duration.ofMinutes(1) - val task = IdleTask(guild, botProps.idleTimeMinutes) - val fut = scheduler.scheduleAtFixedRate(task, interval) + val task = IdleTask(guild) + val instant = Instant.now().plusSeconds(60 * botProps.idleTimeMinutes.toLong()) + val fut = scheduler.schedule(task, instant) if (timers.containsKey(guild.idLong)) { timers.replace(guild.idLong, fut)?.cancel(true) @@ -51,17 +40,12 @@ class LeaveOnIdleService(private val scheduler: TaskScheduler, private val botPr } } - /** - * If a timer exists for this guild, cancel it. - */ - fun maybeDestroyTimer(guild: Guild) { - // don't do anything if idleTimeMinutes is not set + fun destroyTimer(guild: Guild) { if (botProps.idleTimeMinutes <= 0) return - timers[guild.idLong]?.let { - log.debug("Cleaning up idle timer for guild {}", guild.idLong) + timers.remove(guild.idLong)?.let { + log.debug("Cleaned up idle timer for guild {}", guild.idLong) it.cancel(true) - timers.remove(guild.idLong) } } } \ No newline at end of file diff --git a/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt b/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt index 14df442..4556969 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/jda/EventHandler.kt @@ -30,8 +30,6 @@ class EventHandler(private val commandManager: CommandManager, private val leave override fun onGuildVoiceLeave(event: GuildVoiceLeaveEvent) { log.info("Leaving voice channel {} in guild {}", event.channelLeft, event.guild) - - // clean up idle timer, if it exists - leaveOnIdleService.maybeDestroyTimer(event.guild) + leaveOnIdleService.destroyTimer(event.guild) } } \ No newline at end of file From b623ad202b089beb6c31195ab7d4444c51d863a5 Mon Sep 17 00:00:00 2001 From: Sri Ramanujam Date: Sat, 19 Feb 2022 11:40:15 -0500 Subject: [PATCH 4/4] Use inline lambda for one-liner runnable --- .../arbjerg/ukulele/features/LeaveOnIdleService.kt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt index 3033a4e..3443709 100644 --- a/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt +++ b/src/main/kotlin/dev/arbjerg/ukulele/features/LeaveOnIdleService.kt @@ -15,12 +15,6 @@ class LeaveOnIdleService(private val scheduler: TaskScheduler, private val botPr private val log: Logger = LoggerFactory.getLogger(LeaveOnIdleService::class.java) private val timers: ConcurrentHashMap> = ConcurrentHashMap() - inner class IdleTask(val guild: Guild) : Runnable { - override fun run() { - guild.audioManager.closeAudioConnection() - } - } - /** * Create a new timer which will start counting from the current moment and cancel any existing timers * associated with this guild. @@ -29,9 +23,10 @@ class LeaveOnIdleService(private val scheduler: TaskScheduler, private val botPr if (botProps.idleTimeMinutes <= 0) return log.info("Player for guild {} is idle, starting timeout of {} minutes", guild.idLong, botProps.idleTimeMinutes) - val task = IdleTask(guild) val instant = Instant.now().plusSeconds(60 * botProps.idleTimeMinutes.toLong()) - val fut = scheduler.schedule(task, instant) + val fut = scheduler.schedule({ + guild.audioManager.closeAudioConnection() + }, instant) if (timers.containsKey(guild.idLong)) { timers.replace(guild.idLong, fut)?.cancel(true) @@ -48,4 +43,4 @@ class LeaveOnIdleService(private val scheduler: TaskScheduler, private val botPr it.cancel(true) } } -} \ No newline at end of file +}