Moving EntityCache files to separate directory

This commit is contained in:
Noikoio 2017-12-22 17:53:46 -08:00
parent 74c80c50e9
commit 8bb274bd69
4 changed files with 108 additions and 77 deletions

View file

@ -7,37 +7,26 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.EntityCache namespace Noikoio.RegexBot.EntityCache
{ {
/// <summary> /// <summary>
/// Caches information regarding all known guilds, channels, and users. /// Bot module portion of the entity cache. Caches information regarding all known guilds, channels, and users.
/// 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 guild and user cache. /// This module should be initialized BEFORE any other modules that make use of guild and user cache.
/// </summary> /// </summary>
class EntityCache : BotModule class Module : BotModule
{ {
/*
* Future plans:
* Have this, or something connected to this class, be accessible throughout the bot.
*
* There should be a system that holds a small in-memory cache of users (as EntityCache objects)
* for quick lookups by other parts of the bot.
* Without this system, we'll be having future bot features constantly querying the database
* on their own to look up entity cache records, which (among other things) could result in
* a race conditions where an event becomes aware of a user before it has been recorded.
*/
private readonly DatabaseConfig _db; private readonly DatabaseConfig _db;
public override string Name => nameof(EntityCache); public override string Name => nameof(EntityCache);
public EntityCache(DiscordSocketClient client) : base(client) public Module(DiscordSocketClient client) : base(client)
{ {
_db = RegexBot.Config.Database; _db = RegexBot.Config.Database;
if (_db.Available) if (_db.Available)
{ {
Sql.CreateCacheTables(); SqlHelper.CreateCacheTables();
client.GuildAvailable += Client_GuildAvailable; client.GuildAvailable += Client_GuildAvailable;
client.GuildUpdated += Client_GuildUpdated; client.GuildUpdated += Client_GuildUpdated;
@ -49,12 +38,12 @@ namespace Noikoio.RegexBot.Module.EntityCache
Log("No database storage available.").Wait(); Log("No database storage available.").Wait();
} }
} }
public override Task<object> ProcessConfiguration(JToken configSection) => Task.FromResult<object>(null); public override Task<object> ProcessConfiguration(JToken configSection) => Task.FromResult<object>(null);
#region Event handling #region Event handling
// Guild and guild member information has become available. // Guild and guild member information has become available.
// This is a very expensive operation, when joining larger guilds for the first time. // This is a very expensive operation, especially when joining larger guilds.
private async Task Client_GuildAvailable(SocketGuild arg) private async Task Client_GuildAvailable(SocketGuild arg)
{ {
await Task.Run(async () => await Task.Run(async () =>
@ -124,7 +113,7 @@ namespace Noikoio.RegexBot.Module.EntityCache
+ "cache_date = EXCLUDED.cache_date, username = EXCLUDED.username, " + "cache_date = EXCLUDED.cache_date, username = EXCLUDED.username, "
+ "discriminator = EXCLUDED.discriminator, " // I've seen someone's discriminator change this one time... + "discriminator = EXCLUDED.discriminator, " // I've seen someone's discriminator change this one time...
+ "nickname = EXCLUDED.nickname, avatar_url = EXCLUDED.avatar_url"; + "nickname = EXCLUDED.nickname, avatar_url = EXCLUDED.avatar_url";
var uid = c.Parameters.Add("@Uid", NpgsqlDbType.Bigint); var uid = c.Parameters.Add("@Uid", NpgsqlDbType.Bigint);
var gid = c.Parameters.Add("@Gid", NpgsqlDbType.Bigint); var gid = c.Parameters.Add("@Gid", NpgsqlDbType.Bigint);
c.Parameters.Add("@Date", NpgsqlDbType.TimestampTZ).Value = DateTime.Now; c.Parameters.Add("@Date", NpgsqlDbType.TimestampTZ).Value = DateTime.Now;

98
EntityCache/SqlHelper.cs Normal file
View file

@ -0,0 +1,98 @@
using Npgsql;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Noikoio.RegexBot.EntityCache
{
/// <summary>
/// Helper methods for database operations.
/// </summary>
static class SqlHelper
{
public const string TableGuild = "cache_guild";
public const string TableTextChannel = "cache_textchannel";
public const string TableUser = "cache_users";
private static async Task<NpgsqlConnection> OpenDB()
{
if (!RegexBot.Config.Database.Available) return null;
return await RegexBot.Config.Database.GetOpenConnectionAsync();
}
public static async Task CreateCacheTables()
{
var db = await OpenDB();
if (db == null) return;
using (db)
{
// Guild cache
using (var c = db.CreateCommand())
{
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableGuild + " ("
+ "guild_id bigint primary key, "
+ "cache_date timestamptz not null, "
+ "current_name text not null, "
+ "display_name text null"
+ ")";
await c.ExecuteNonQueryAsync();
}
// May not require other indexes. Add here if they become necessary.
// Text channel cache
using (var c = db.CreateCommand())
{
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableTextChannel + " ("
+ "channel_id bigint not null primary key, "
+ $"guild_id bigint not null references {TableGuild}, "
+ "cache_date timestamptz not null, "
+ "name text not null";
await c.ExecuteNonQueryAsync();
}
// As of the time of this commit, Discord doesn't allow any uppercase characters
// in channel names. No lowercase name index needed.
// User cache
using (var c = db.CreateCommand())
{
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableUser + " ("
+ "user_id bigint not null, "
+ $"guild_id bigint not null references {TableGuild}, "
+ "cache_date timestamptz not null, "
+ "username text not null, "
+ "discriminator text not null, "
+ "nickname text null, "
+ "avatar_url text null"
+ ")";
await c.ExecuteNonQueryAsync();
}
using (var c = db.CreateCommand())
{
// guild_id is a foreign key, and also one half of the primary key here
c.CommandText = "CREATE UNIQUE INDEX IF NOT EXISTS "
+ $"{TableUser}_ck_idx on {TableUser} (user_id, guild_id)";
await c.ExecuteNonQueryAsync();
}
using (var c = db.CreateCommand())
{
c.CommandText = "CREATE INDEX IF NOT EXISTS "
+ $"{TableUser}_usersearch_idx on {TableUser} LOWER(username)";
await c.ExecuteNonQueryAsync();
}
}
}
#region Insertions and updates
static async Task UpdateGuild()
{
var db = await OpenDB();
if (db == null) return;
using (db)
{
}
}
#endregion
}
}

View file

@ -1,56 +0,0 @@
using Npgsql;
using System;
using System.Collections.Generic;
using System.Text;
namespace Noikoio.RegexBot.Module.EntityCache
{
/// <summary>
/// Contains common constants and static methods for cache access.
/// </summary>
class Sql
{
public const string TableGuild = "cache_guild";
public const string TableUser = "cache_users";
private static NpgsqlConnection OpenDB() =>
RegexBot.Config.Database.GetOpenConnectionAsync().GetAwaiter().GetResult();
public static void CreateCacheTables()
{
using (var db = OpenDB())
{
using (var c = db.CreateCommand())
{
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableGuild + "("
+ "guild_id bigint primary key, "
+ "current_name text not null, "
+ "display_name text null"
+ ")";
c.ExecuteNonQuery();
}
using (var c = db.CreateCommand())
{
c.CommandText = "CREATE TABLE IF NOT EXISTS " + TableUser + "("
+ "user_id bigint not null, "
+ $"guild_id bigint not null references {TableGuild}, "
+ "cache_date timestamptz not null, "
+ "username text not null, "
+ "discriminator text not null, "
+ "nickname text null, "
+ "avatar_url text null"
+ ")";
c.ExecuteNonQuery();
}
using (var c = db.CreateCommand())
{
c.CommandText = "CREATE UNIQUE INDEX IF NOT EXISTS "
+ $"{TableUser}_idx on {TableUser} (user_id, guild_id)";
c.ExecuteNonQuery();
}
// TODO create indexes for string-based queries
}
}
}
}

View file

@ -48,7 +48,7 @@ namespace Noikoio.RegexBot
new Module.AutoMod.AutoMod(_client), new Module.AutoMod.AutoMod(_client),
new Module.ModTools.ModTools(_client), new Module.ModTools.ModTools(_client),
new Module.AutoRespond.AutoRespond(_client), new Module.AutoRespond.AutoRespond(_client),
new Module.EntityCache.EntityCache(_client) // EntityCache goes before anything else that uses its data new EntityCache.Module(_client) // EntityCache goes before anything else that uses its data
}; };
var dlog = Logger.GetLogger("Discord.Net"); var dlog = Logger.GetLogger("Discord.Net");
_client.Log += async (arg) => _client.Log += async (arg) =>