diff --git a/BackgroundServices/SelectiveAutoUserDownload.cs b/BackgroundServices/SelectiveAutoUserDownload.cs
index 06de92a..0b2fe53 100644
--- a/BackgroundServices/SelectiveAutoUserDownload.cs
+++ b/BackgroundServices/SelectiveAutoUserDownload.cs
@@ -1,86 +1,61 @@
using BirthdayBot.Data;
+using Discord;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-namespace BirthdayBot.BackgroundServices
-{
- ///
- /// A type of workaround to the issue of user information not being cached for guilds that
- /// have user information existing in the bot's database. This service runs frequently and
- /// determines guilds in which user data must be downloaded, and proceeds to request it.
- ///
- class SelectiveAutoUserDownload : BackgroundService
- {
- private static readonly SemaphoreSlim _updateLock = new(2);
+namespace BirthdayBot.BackgroundServices;
- private readonly HashSet _fetchRequests = new();
+///
+/// Rather than use up unnecessary resources by auto-downloading the user list in -every-
+/// server we're in, this service checks if fetching the user list is warranted for each
+/// guild before proceeding to request it.
+///
+class SelectiveAutoUserDownload : BackgroundService {
+ private readonly HashSet _fetchRequests = new();
- public SelectiveAutoUserDownload(ShardInstance instance) : base(instance) { }
+ public SelectiveAutoUserDownload(ShardInstance instance) : base(instance) { }
- public override async Task OnTick(CancellationToken token)
- {
- IEnumerable requests;
- lock (_fetchRequests)
- {
- requests = _fetchRequests.ToArray();
- _fetchRequests.Clear();
- }
-
- foreach (var guild in ShardInstance.DiscordClient.Guilds)
- {
- if (ShardInstance.DiscordClient.ConnectionState != Discord.ConnectionState.Connected)
- {
- Log("Client no longer connected. Stopping early.");
- return;
- }
-
- // Determine if there is action to be taken...
- if (guild.HasAllMembers) continue;
- if (requests.Contains(guild.Id) || await GuildUserAnyAsync(guild.Id, token).ConfigureAwait(false))
- {
- await guild.DownloadUsersAsync().ConfigureAwait(false);
- await Task.Delay(500, CancellationToken.None).ConfigureAwait(false);
- }
- }
+ public override async Task OnTick(CancellationToken token) {
+ IEnumerable requests;
+ lock (_fetchRequests) {
+ requests = _fetchRequests.ToArray();
+ _fetchRequests.Clear();
}
- ///
- /// Determines if the user database contains any entries corresponding to this guild.
- ///
- /// True if any entries exist.
- private static async Task GuildUserAnyAsync(ulong guildId, CancellationToken token)
- {
- try
- {
- await _updateLock.WaitAsync(token).ConfigureAwait(false);
+ foreach (var guild in ShardInstance.DiscordClient.Guilds) {
+ if (ShardInstance.DiscordClient.ConnectionState != ConnectionState.Connected) {
+ Log("Client no longer connected. Stopping early.");
+ return;
}
- catch (Exception ex) when (ex is OperationCanceledException or ObjectDisposedException)
- {
- // Calling thread does not expect the exception that SemaphoreSlim throws...
- throw new TaskCanceledException();
- }
- try
- {
- using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
- using var c = db.CreateCommand();
- c.CommandText = $"select count(*) from {GuildUserConfiguration.BackingTable} where guild_id = @Gid";
- c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = (long)guildId;
- await c.PrepareAsync(CancellationToken.None).ConfigureAwait(false);
- var r = (long)await c.ExecuteScalarAsync(token).ConfigureAwait(false);
- return r != 0;
- }
- finally
- {
- _updateLock.Release();
- }
- }
- public void RequestDownload(ulong guildId)
- {
- lock (_fetchRequests) _fetchRequests.Add(guildId);
+ // Determine if there is action to be taken...
+ if (guild.HasAllMembers) continue;
+ if (requests.Contains(guild.Id) || await GuildUserAnyAsync(guild.Id).ConfigureAwait(false)) {
+ await guild.DownloadUsersAsync().ConfigureAwait(false);
+ // Must delay after a download request. Seems to hang indefinitely otherwise.
+ await Task.Delay(300, CancellationToken.None).ConfigureAwait(false);
+ }
}
}
+
+ ///
+ /// Determines if the user database contains any entries corresponding to this guild.
+ ///
+ /// True if any entries exist.
+ private static async Task GuildUserAnyAsync(ulong guildId) {
+ using var db = await Database.OpenConnectionAsync().ConfigureAwait(false);
+ using var c = db.CreateCommand();
+ c.CommandText = $"select true from {GuildUserConfiguration.BackingTable} where guild_id = @Gid limit 1";
+ c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = (long)guildId;
+ await c.PrepareAsync(CancellationToken.None).ConfigureAwait(false);
+ using var r = await c.ExecuteReaderAsync(CancellationToken.None).ConfigureAwait(false);
+ return r.Read();
+ }
+
+ public void RequestDownload(ulong guildId) {
+ lock (_fetchRequests) _fetchRequests.Add(guildId);
+ }
}
diff --git a/UserInterface/CommandsCommon.cs b/UserInterface/CommandsCommon.cs
index 9bca783..6b7800d 100644
--- a/UserInterface/CommandsCommon.cs
+++ b/UserInterface/CommandsCommon.cs
@@ -22,7 +22,7 @@ namespace BirthdayBot.UserInterface
public const string ParameterError = ":x: Invalid usage. Refer to how to use the command and try again.";
public const string NoParameterError = ":x: This command does not accept any parameters.";
public const string InternalError = ":x: An internal bot error occurred. The bot maintainer has been notified of the issue.";
- public const string UsersNotDownloadedError = ":x: Currently unavailable. Please try again in a few minutes.";
+ public const string UsersNotDownloadedError = ":eight_spoked_asterisk: Still catching up... Please try the command again in a few minutes.";
public delegate Task CommandHandler(ShardInstance instance, GuildConfiguration gconf,
string[] param, SocketTextChannel reqChannel, SocketGuildUser reqUser);