Added channel cache to EntityCache

This commit is contained in:
Noikoio 2018-04-04 11:00:58 -07:00
parent 7e55acb5e6
commit e5758e9aac
5 changed files with 186 additions and 10 deletions

154
EntityCache/CacheChannel.cs Normal file
View file

@ -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<CacheChannel> 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<CacheChannel> 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<IEnumerable<CacheChannel>> 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<CacheChannel> 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<CacheChannel>();
foreach (var item in qresult)
{
result.Add(new CacheChannel(item));
}
return result;
}
private static async Task<IEnumerable<CacheChannel>> DbQueryAsync(NpgsqlConnection db, ulong guild, string search)
{
var result = new List<CacheChannel>();
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
}
}

View file

@ -102,7 +102,7 @@ namespace Noikoio.RegexBot.EntityCache
// Database cache search // Database cache search
var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync(); var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync();
if (db == null) return null; // Database not available for query. 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) private static CacheUser LocalQueryAsync(DiscordSocketClient c, ulong guild, ulong user)
@ -175,8 +175,8 @@ namespace Noikoio.RegexBot.EntityCache
// Database cache search // Database cache search
var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync(); var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync();
if (db == null) return null; // Database not available for query. if (db == null) return new CacheUser[0]; // Database not available for query.
using (db) return await DbQueryAsync(db, guild, name, disc); return await DbQueryAsync(db, guild, name, disc);
} }
private static IEnumerable<CacheUser> LocalQueryAsync(DiscordSocketClient c, ulong guild, string name, string disc) private static IEnumerable<CacheUser> LocalQueryAsync(DiscordSocketClient c, ulong guild, string name, string disc)
@ -210,7 +210,7 @@ namespace Noikoio.RegexBot.EntityCache
{ {
var result = new List<CacheUser>(); var result = new List<CacheUser>();
using (db = await RegexBot.Config.GetOpenDatabaseConnectionAsync()) using (db)
{ {
using (var c = db.CreateCommand()) using (var c = db.CreateCommand())
{ {

View file

@ -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. /// 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. /// This module should be initialized BEFORE any other modules that make use of the entity cache.
/// </summary> /// </summary>
class Module : BotModule class ECModule : BotModule
{ {
private readonly DatabaseConfig _db; private readonly DatabaseConfig _db;
public Module(DiscordSocketClient client) : base(client) public ECModule(DiscordSocketClient client) : base(client)
{ {
if (RegexBot.Config.DatabaseAvailable) if (RegexBot.Config.DatabaseAvailable)
{ {

View file

@ -24,15 +24,37 @@ namespace Noikoio.RegexBot.EntityCache
/// Does not handle exceptions that may occur. /// Does not handle exceptions that may occur.
/// </summary> /// </summary>
/// <returns>Null on no result.</returns> /// <returns>Null on no result.</returns>
internal static Task<CacheUser> QueryAsync(ulong guild, ulong user) internal static Task<CacheUser> QueryUserAsync(ulong guild, ulong user)
=> CacheUser.QueryAsync(_client, guild, user); => CacheUser.QueryAsync(_client, guild, user);
/// <summary> /// <summary>
/// Attempts to look up the user given a search string. /// Attempts to look up the user given a search string.
/// This string looks up case-insensitive, exact matches of nicknames and usernames. /// This string looks up case-insensitive, exact matches of nicknames and usernames.
/// </summary> /// </summary>
/// <returns>An <see cref="IEnumerable{T}"/> containing zero or more query results, sorted by cache date.</returns> /// <returns>
internal static Task<IEnumerable<CacheUser>> QueryAsync(ulong guild, string search) /// An <see cref="IEnumerable{T}"/> containing zero or more query results,
/// sorted by cache date from most to least recent.
/// </returns>
internal static Task<IEnumerable<CacheUser>> QueryUserAsync(ulong guild, string search)
=> CacheUser.QueryAsync(_client, guild, search); => CacheUser.QueryAsync(_client, guild, search);
/// <summary>
/// Attempts to query for an exact result with the given parameters.
/// Does not handle exceptions that may occur.
/// </summary>
/// <returns>Null on no result.</returns>
internal static Task<CacheChannel> QueryChannelAsync(ulong guild, ulong channel)
=> CacheChannel.QueryAsync(_client, guild, channel);
/// <summary>
/// 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.
/// </summary>
/// <returns>
/// An <see cref="IEnumerable{T}"/> containing zero or more query results,
/// sorted by cache date from most to least recent.
/// </returns>
internal static Task<IEnumerable<CacheChannel>> QueryChannelAsync(ulong guild, string search)
=> CacheChannel.QueryAsync(_client, guild, search);
} }
} }

View file

@ -61,7 +61,7 @@ namespace Noikoio.RegexBot
new Module.EntryAutoRole.EntryAutoRole(_client), new Module.EntryAutoRole.EntryAutoRole(_client),
// EntityCache loads before anything using it // EntityCache loads before anything using it
new EntityCache.Module(_client), new EntityCache.ECModule(_client),
new Module.ModLogs.ModLogs(_client) new Module.ModLogs.ModLogs(_client)
}; };