From e5758e9aac329e5ed0dd7056cbece5e49c624c76 Mon Sep 17 00:00:00 2001 From: Noikoio Date: Wed, 4 Apr 2018 11:00:58 -0700 Subject: [PATCH] Added channel cache to EntityCache --- EntityCache/CacheChannel.cs | 154 +++++++++++++++++++++++++ EntityCache/CacheUser.cs | 8 +- EntityCache/{Module.cs => ECModule.cs} | 4 +- EntityCache/EntityCache.cs | 28 ++++- RegexBot.cs | 2 +- 5 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 EntityCache/CacheChannel.cs rename EntityCache/{Module.cs => ECModule.cs} (97%) diff --git a/EntityCache/CacheChannel.cs b/EntityCache/CacheChannel.cs new file mode 100644 index 0000000..731f85f --- /dev/null +++ b/EntityCache/CacheChannel.cs @@ -0,0 +1,154 @@ +using Discord.WebSocket; +using Npgsql; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; + +namespace Noikoio.RegexBot.EntityCache +{ + class CacheChannel + { + readonly ulong _channelId; + readonly ulong _guildId; + readonly DateTimeOffset _cacheDate; + readonly string _channelName; + + private CacheChannel(SocketGuildChannel c) + { + _channelId = c.Id; + _guildId = c.Guild.Id; + _cacheDate = DateTimeOffset.UtcNow; + _channelName = c.Name; + } + + // Double-check SqlHelper if making changes to this constant + const string QueryColumns = "channel_id, guild_id, cache_date, channel_name"; + private CacheChannel(DbDataReader r) + { + // Double-check ordinals if making changes to QueryColumns + unchecked + { + _channelId = (ulong)r.GetInt64(0); + _guildId = (ulong)r.GetInt64(1); + } + _cacheDate = r.GetDateTime(2).ToUniversalTime(); + _channelName = r.GetString(3); + } + + #region Queries + // Accessible by EntityCache. Documentation is there. + internal static async Task QueryAsync(DiscordSocketClient c, ulong guild, ulong channel) + { + // Local cache search + var lresult = LocalQueryAsync(c, guild, channel); + if (lresult != null) return lresult; + + // Database cache search + var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync(); + if (db == null) return null; // Database not available for query. + return await DbQueryAsync(db, guild, channel); + } + + private static CacheChannel LocalQueryAsync(DiscordSocketClient c, ulong guild, ulong channel) + { + var ch = c.GetGuild(guild)?.GetChannel(channel); + if (ch == null) return null; + return new CacheChannel(ch); + } + private static async Task DbQueryAsync(NpgsqlConnection db, ulong guild, ulong channel) + { + using (db) + { + using (var c = db.CreateCommand()) + { + c.CommandText = $"SELECT {QueryColumns} from {SqlHelper.TableTextChannel} WHERE " + + "channel_id = @Cid AND guild_id = @Gid"; + c.Parameters.Add("@Cid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = channel; + c.Parameters.Add("@Gid", NpgsqlTypes.NpgsqlDbType.Bigint).Value = guild; + c.Prepare(); + using (var r = await c.ExecuteReaderAsync()) + { + if (await r.ReadAsync()) + { + return new CacheChannel(r); + } + else + { + return null; + } + } + } + } + } + + // ----- + + // Accessible by EntityCache. Documentation is there. + internal static async Task> QueryAsync(DiscordSocketClient c, ulong guild, string search) + { + // Is search just a number? Assume ID, pass it on to the correct place. + if (ulong.TryParse(search, out var presult)) + { + var r = await QueryAsync(c, guild, presult); + if (r == null) return new CacheChannel[0]; + else return new CacheChannel[] { r }; + } + + // Split leading # from name, if exists + if (search.Length > 0 && search[0] == '#') search = search.Substring(1); + + // Local cache search + var lresult = LocalQueryAsync(c, guild, search); + if (lresult.Count() != 0) return lresult; + + // Database cache search + var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync(); + if (db == null) return new CacheChannel[0]; + return await DbQueryAsync(db, guild, search); + } + + private static IEnumerable LocalQueryAsync(DiscordSocketClient c, ulong guild, string search) + { + var g = c.GetGuild(guild); + if (g == null) return new CacheChannel[0]; + + var qresult = g.Channels + .Where(i => string.Equals(i.Name, search, StringComparison.InvariantCultureIgnoreCase)); + var result = new List(); + foreach (var item in qresult) + { + result.Add(new CacheChannel(item)); + } + return result; + } + + private static async Task> DbQueryAsync(NpgsqlConnection db, ulong guild, string search) + { + var result = new List(); + + using (db) + { + using (var c = db.CreateCommand()) + { + c.CommandText = $"SELECT {QueryColumns} FROM {SqlHelper.TableTextChannel} WHERE" + + " name = lower(@NameSearch)" // all channel names presumed to be lowercase already + + " ORDER BY cache_date desc, name"; + c.Parameters.Add("@NameSearch", NpgsqlTypes.NpgsqlDbType.Text).Value = search; + c.Prepare(); + + using (var r = await c.ExecuteReaderAsync()) + { + while (await r.ReadAsync()) + { + result.Add(new CacheChannel(r)); + } + } + } + } + return result; + } + #endregion + } +} diff --git a/EntityCache/CacheUser.cs b/EntityCache/CacheUser.cs index 75a74df..5277ab7 100644 --- a/EntityCache/CacheUser.cs +++ b/EntityCache/CacheUser.cs @@ -102,7 +102,7 @@ namespace Noikoio.RegexBot.EntityCache // Database cache search var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync(); if (db == null) return null; // Database not available for query. - using (db) return await DbQueryAsync(db, guild, user); + return await DbQueryAsync(db, guild, user); } private static CacheUser LocalQueryAsync(DiscordSocketClient c, ulong guild, ulong user) @@ -175,8 +175,8 @@ namespace Noikoio.RegexBot.EntityCache // Database cache search var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync(); - if (db == null) return null; // Database not available for query. - using (db) return await DbQueryAsync(db, guild, name, disc); + if (db == null) return new CacheUser[0]; // Database not available for query. + return await DbQueryAsync(db, guild, name, disc); } private static IEnumerable LocalQueryAsync(DiscordSocketClient c, ulong guild, string name, string disc) @@ -210,7 +210,7 @@ namespace Noikoio.RegexBot.EntityCache { var result = new List(); - using (db = await RegexBot.Config.GetOpenDatabaseConnectionAsync()) + using (db) { using (var c = db.CreateCommand()) { diff --git a/EntityCache/Module.cs b/EntityCache/ECModule.cs similarity index 97% rename from EntityCache/Module.cs rename to EntityCache/ECModule.cs index 845dd87..7223139 100644 --- a/EntityCache/Module.cs +++ b/EntityCache/ECModule.cs @@ -11,11 +11,11 @@ namespace Noikoio.RegexBot.EntityCache /// The function of this module should be transparent to the user, and thus no configuration is needed. /// This module should be initialized BEFORE any other modules that make use of the entity cache. /// - class Module : BotModule + class ECModule : BotModule { private readonly DatabaseConfig _db; - public Module(DiscordSocketClient client) : base(client) + public ECModule(DiscordSocketClient client) : base(client) { if (RegexBot.Config.DatabaseAvailable) { diff --git a/EntityCache/EntityCache.cs b/EntityCache/EntityCache.cs index 5937f40..f523086 100644 --- a/EntityCache/EntityCache.cs +++ b/EntityCache/EntityCache.cs @@ -24,15 +24,37 @@ namespace Noikoio.RegexBot.EntityCache /// Does not handle exceptions that may occur. /// /// Null on no result. - internal static Task QueryAsync(ulong guild, ulong user) + internal static Task QueryUserAsync(ulong guild, ulong user) => CacheUser.QueryAsync(_client, guild, user); /// /// Attempts to look up the user given a search string. /// This string looks up case-insensitive, exact matches of nicknames and usernames. /// - /// An containing zero or more query results, sorted by cache date. - internal static Task> QueryAsync(ulong guild, string search) + /// + /// An containing zero or more query results, + /// sorted by cache date from most to least recent. + /// + internal static Task> QueryUserAsync(ulong guild, string search) => CacheUser.QueryAsync(_client, guild, search); + + /// + /// Attempts to query for an exact result with the given parameters. + /// Does not handle exceptions that may occur. + /// + /// Null on no result. + internal static Task QueryChannelAsync(ulong guild, ulong channel) + => CacheChannel.QueryAsync(_client, guild, channel); + + /// + /// Attempts to look up the channel given a search string. + /// This string looks up exact matches of the given name, regardless of if the channel has been deleted. + /// + /// + /// An containing zero or more query results, + /// sorted by cache date from most to least recent. + /// + internal static Task> QueryChannelAsync(ulong guild, string search) + => CacheChannel.QueryAsync(_client, guild, search); } } diff --git a/RegexBot.cs b/RegexBot.cs index abd4620..d99964f 100644 --- a/RegexBot.cs +++ b/RegexBot.cs @@ -61,7 +61,7 @@ namespace Noikoio.RegexBot new Module.EntryAutoRole.EntryAutoRole(_client), // EntityCache loads before anything using it - new EntityCache.Module(_client), + new EntityCache.ECModule(_client), new Module.ModLogs.ModLogs(_client) };