diff --git a/BirthdayBot/BackgroundServiceRunner.vb b/BirthdayBot/BackgroundServiceRunner.vb index f132c07..42240ae 100644 --- a/BirthdayBot/BackgroundServiceRunner.vb +++ b/BirthdayBot/BackgroundServiceRunner.vb @@ -4,24 +4,24 @@ ''' Handles the execution of periodic background tasks. ''' Class BackgroundServiceRunner - Const Interval = 45 ' Tick interval in seconds. Adjust as needed. + Const Interval = 8 * 60 ' Tick interval in seconds. Adjust as needed. Private ReadOnly Property Workers As List(Of BackgroundService) Private ReadOnly Property WorkerCancel As New CancellationTokenSource + Friend ReadOnly Property BirthdayUpdater As BirthdayRoleUpdate Private _workerTask As Task - Private _tickCount As Integer Sub New(instance As BirthdayBot) + BirthdayUpdater = New BirthdayRoleUpdate(instance) Workers = New List(Of BackgroundService) From { {New GuildStatistics(instance)}, {New Heartbeat(instance)}, - {New BirthdayRoleUpdate(instance)} + {BirthdayUpdater} } End Sub Public Sub Start() - _tickCount = 0 _workerTask = Task.Factory.StartNew(AddressOf WorkerLoop, WorkerCancel.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default) End Sub @@ -43,7 +43,7 @@ Class BackgroundServiceRunner ' Execute background tasks. Dim tasks As New List(Of Task) For Each service In Workers - tasks.Add(service.OnTick(_tickCount)) + tasks.Add(service.OnTick()) Next Await Task.WhenAll(tasks) Catch ex As TaskCanceledException @@ -52,7 +52,6 @@ Class BackgroundServiceRunner Log("Background task", "Unhandled exception in background task thread:") Log("Background task", ex.ToString()) End Try - _tickCount += 1 End While End Function End Class diff --git a/BirthdayBot/BackgroundServices/BackgroundService.vb b/BirthdayBot/BackgroundServices/BackgroundService.vb index 86ab79f..9c44048 100644 --- a/BirthdayBot/BackgroundServices/BackgroundService.vb +++ b/BirthdayBot/BackgroundServices/BackgroundService.vb @@ -13,5 +13,5 @@ MustInherit Class BackgroundService Program.Log(Me.GetType().Name, message) End Sub - MustOverride Function OnTick(tick As Integer) As Task + MustOverride Function OnTick() As Task End Class diff --git a/BirthdayBot/BackgroundServices/BirthdayRoleUpdate.vb b/BirthdayBot/BackgroundServices/BirthdayRoleUpdate.vb index 0d0acc1..8d718c4 100644 --- a/BirthdayBot/BackgroundServices/BirthdayRoleUpdate.vb +++ b/BirthdayBot/BackgroundServices/BirthdayRoleUpdate.vb @@ -4,22 +4,20 @@ Imports Discord.WebSocket Imports NodaTime ''' -''' Periodically scans all known guilds and adjusts birthday role membership as necessary. -''' Also handles birthday announcements. +''' Core automatic functionality of the bot. Manages role memberships based on birthday information, +''' and optionally sends the announcement message to appropriate guilds. ''' Class BirthdayRoleUpdate Inherits BackgroundService - Private ReadOnly Property Clock As IClock Public Sub New(instance As BirthdayBot) MyBase.New(instance) - Clock = SystemClock.Instance ' can be replaced with FakeClock during testing End Sub ''' - ''' Initial processing: Sets up a task per guild and waits on all. + ''' Does processing on all available guilds at once. ''' - Public Overrides Async Function OnTick(tick As Integer) As Task + Public Overrides Async Function OnTick() As Task Dim tasks As New List(Of Task(Of Integer)) For Each guild In BotInstance.DiscordClient.Guilds Dim t = ProcessGuildAsync(guild) @@ -38,19 +36,24 @@ Class BirthdayRoleUpdate Next End Try - ' Usage report: Show how many announcements were done - Dim announces = 0 - Dim guilds = 0 - For Each task In tasks - If task.Result > 0 Then - announces += task.Result - guilds += 1 - End If - Next - If announces > 0 Then Log($"Announcing {announces} birthday(s) in {guilds} guild(s).") + ' TODO metrics for role sets, unsets, announcements - and how to do that for singles too? End Function - Async Function ProcessGuildAsync(guild As SocketGuild) As Task(Of Integer) + ''' + ''' Does role and announcement processing for a single specified guild. + ''' + Public Async Function SingleUpdateFor(guild As SocketGuild) As Task + Try + Await ProcessGuildAsync(guild) + Catch ex As Exception + Log("Encountered an error during guild processing:") + Log(ex.ToString()) + End Try + + ' TODO metrics for role sets, unsets, announcements - and I mentioned this above too + End Function + + Private Async Function ProcessGuildAsync(guild As SocketGuild) As Task(Of Integer) ' Gather required information Dim tz As String Dim users As IEnumerable(Of GuildUserSettings) @@ -156,7 +159,7 @@ Class BirthdayRoleUpdate Dim targetMonth = item.BirthMonth Dim targetDay = item.BirthDay - Dim checkNow = Clock.GetCurrentInstant().InZone(tz) + Dim checkNow = SystemClock.Instance.GetCurrentInstant().InZone(tz) ' Special case: If birthday is February 29 and it's not a leap year, recognize it on March 1st If targetMonth = 2 And targetDay = 29 And Not Date.IsLeapYear(checkNow.Year) Then targetMonth = 3 diff --git a/BirthdayBot/BackgroundServices/GuildStatistics.vb b/BirthdayBot/BackgroundServices/GuildStatistics.vb index 82233b8..6679176 100644 --- a/BirthdayBot/BackgroundServices/GuildStatistics.vb +++ b/BirthdayBot/BackgroundServices/GuildStatistics.vb @@ -10,10 +10,7 @@ Class GuildStatistics DBotsToken = instance.Config.DBotsToken End Sub - Public Overrides Async Function OnTick(tick As Integer) As Task - ' Activate roughly every 2 hours (interval: 45) - If tick Mod 160 <> 2 Then Return - + Public Overrides Async Function OnTick() As Task Dim count = BotInstance.DiscordClient.Guilds.Count Log($"Currently in {count} guild(s).") diff --git a/BirthdayBot/BackgroundServices/Heartbeat.vb b/BirthdayBot/BackgroundServices/Heartbeat.vb index 15eba4b..db06770 100644 --- a/BirthdayBot/BackgroundServices/Heartbeat.vb +++ b/BirthdayBot/BackgroundServices/Heartbeat.vb @@ -1,5 +1,5 @@ ''' -''' Basic heartbeat function - indicates that the background task is still functioning. +''' Basic heartbeat function - hints that the background task is still alive. ''' Class Heartbeat Inherits BackgroundService @@ -8,19 +8,16 @@ Class Heartbeat MyBase.New(instance) End Sub - Public Overrides Function OnTick(tick As Integer) As Task - ' Print a message roughly every 15 minutes (assuming 45s per tick). - If tick Mod 20 = 0 Then - Dim uptime = DateTimeOffset.UtcNow - Program.BotStartTime - Log($"Tick {tick:00000} - Bot uptime: {BotUptime()}") - End If + Public Overrides Function OnTick() As Task + Dim uptime = DateTimeOffset.UtcNow - Program.BotStartTime + Log($"Bot uptime: {BotUptime()}") ' Disconnection warn For Each shard In BotInstance.DiscordClient.Shards If shard.ConnectionState = Discord.ConnectionState.Disconnected Then Log($"Shard {shard.ShardId} is disconnected! Restart the app if this persists.") ' The library alone cannot be restarted as it is in an unknown state. It was not designed to be restarted. - ' This is the part where we'd signal something to restart us if we were fancy. + ' TODO This is the part where we'd signal something to restart us if we were fancy. End If Next diff --git a/BirthdayBot/BirthdayBot.vb b/BirthdayBot/BirthdayBot.vb index 5d9f7ed..330ce79 100644 --- a/BirthdayBot/BirthdayBot.vb +++ b/BirthdayBot/BirthdayBot.vb @@ -118,6 +118,7 @@ Class BirthdayBot End If End If + ' Execute the command Try Log("Command", $"{channel.Guild.Name}/{author.Username}#{author.Discriminator}: {msg.Content}") Await command(csplit, channel, author) @@ -130,6 +131,9 @@ Class BirthdayBot ' Fail silently. End Try End Try + + ' Immediately check for role updates in the invoking guild + Await _worker.BirthdayUpdater.SingleUpdateFor(channel.Guild) End If End If End Function diff --git a/BirthdayBot/BirthdayBot.vbproj b/BirthdayBot/BirthdayBot.vbproj index 410e126..93b9408 100644 --- a/BirthdayBot/BirthdayBot.vbproj +++ b/BirthdayBot/BirthdayBot.vbproj @@ -4,7 +4,7 @@ Exe BirthdayBot netcoreapp2.0 - 1.3.3 + 1.3.4 Noi Discord bot for birthday reminders.