Implement SharedEventService; remove cache update event
This commit is contained in:
parent
4f896e8311
commit
b4db1fcff8
10 changed files with 84 additions and 57 deletions
|
@ -12,7 +12,7 @@ internal partial class ModLogs : RegexbotModule {
|
||||||
public ModLogs(RegexbotClient bot) : base(bot) {
|
public ModLogs(RegexbotClient bot) : base(bot) {
|
||||||
// TODO missing logging features: joins, leaves, bans, kicks, user edits (nick/username/discr)
|
// TODO missing logging features: joins, leaves, bans, kicks, user edits (nick/username/discr)
|
||||||
DiscordClient.MessageDeleted += HandleDelete;
|
DiscordClient.MessageDeleted += HandleDelete;
|
||||||
bot.EcOnMessageUpdate += HandleUpdate;
|
bot.SharedEventReceived += FilterIncomingEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<object?> CreateGuildStateAsync(ulong guildID, JToken config) {
|
public override Task<object?> CreateGuildStateAsync(ulong guildID, JToken config) {
|
||||||
|
|
|
@ -73,6 +73,12 @@ internal partial class ModLogs {
|
||||||
await reportChannel.SendMessageAsync(embed: reportEmbed.Build());
|
await reportChannel.SendMessageAsync(embed: reportEmbed.Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task FilterIncomingEvents(ISharedEvent ev) {
|
||||||
|
if (ev is MessageCacheUpdateEvent upd) {
|
||||||
|
await HandleUpdate(upd.OldMessage, upd.NewMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task HandleUpdate(CachedGuildMessage? oldMsg, SocketMessage newMsg) {
|
private async Task HandleUpdate(CachedGuildMessage? oldMsg, SocketMessage newMsg) {
|
||||||
const int MaxPreviewLength = 500;
|
const int MaxPreviewLength = 500;
|
||||||
var channel = (SocketTextChannel)newMsg.Channel;
|
var channel = (SocketTextChannel)newMsg.Channel;
|
||||||
|
|
|
@ -26,6 +26,7 @@ public partial class RegexbotClient {
|
||||||
|
|
||||||
// Get all services started up
|
// Get all services started up
|
||||||
_svcLogging = new Services.Logging.LoggingService(this);
|
_svcLogging = new Services.Logging.LoggingService(this);
|
||||||
|
_svcSharedEvents = new Services.SharedEventService.SharedEventService(this);
|
||||||
_svcGuildState = new Services.ModuleState.ModuleStateService(this);
|
_svcGuildState = new Services.ModuleState.ModuleStateService(this);
|
||||||
_svcCommonFunctions = new Services.CommonFunctions.CommonFunctionsService(this);
|
_svcCommonFunctions = new Services.CommonFunctions.CommonFunctionsService(this);
|
||||||
_svcEntityCache = new Services.EntityCache.EntityCacheService(this);
|
_svcEntityCache = new Services.EntityCache.EntityCacheService(this);
|
||||||
|
|
|
@ -6,12 +6,13 @@ namespace RegexBot.Services.EntityCache;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class EntityCacheService : Service {
|
class EntityCacheService : Service {
|
||||||
private readonly UserCachingSubservice _uc;
|
private readonly UserCachingSubservice _uc;
|
||||||
|
#pragma warning disable IDE0052
|
||||||
private readonly MessageCachingSubservice _mc;
|
private readonly MessageCachingSubservice _mc;
|
||||||
|
#pragma warning restore IDE0052
|
||||||
|
|
||||||
internal EntityCacheService(RegexbotClient bot) : base(bot) {
|
internal EntityCacheService(RegexbotClient bot) : base(bot) {
|
||||||
// Currently we only have UserCache. May add Channel and Server caches later.
|
|
||||||
_uc = new UserCachingSubservice(bot, Log);
|
_uc = new UserCachingSubservice(bot, Log);
|
||||||
_mc = new MessageCachingSubservice(bot, Log);
|
_mc = new MessageCachingSubservice(bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hooked
|
// Hooked
|
||||||
|
@ -21,10 +22,4 @@ class EntityCacheService : Service {
|
||||||
// Hooked
|
// Hooked
|
||||||
internal CachedGuildUser? QueryGuildUserCache(ulong guildId, string search)
|
internal CachedGuildUser? QueryGuildUserCache(ulong guildId, string search)
|
||||||
=> _uc.DoGuildUserQuery(guildId, search);
|
=> _uc.DoGuildUserQuery(guildId, search);
|
||||||
|
|
||||||
// Hooked
|
|
||||||
internal event RegexbotClient.EcMessageUpdateHandler? OnCachePreUpdate {
|
|
||||||
add { lock (_mc) _mc.OnCachePreUpdate += value; }
|
|
||||||
remove { lock (_mc) _mc.OnCachePreUpdate -= value; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,25 +23,4 @@ partial class RegexbotClient {
|
||||||
/// <param name="search">Search string. May be a name with discriminator, a name, or an ID.</param>
|
/// <param name="search">Search string. May be a name with discriminator, a name, or an ID.</param>
|
||||||
/// <returns>A <see cref="CachedGuildUser"/> instance containing cached information, or null if no result.</returns>
|
/// <returns>A <see cref="CachedGuildUser"/> instance containing cached information, or null if no result.</returns>
|
||||||
public CachedGuildUser? EcQueryGuildUser(ulong guildId, string search) => _svcEntityCache.QueryGuildUserCache(guildId, search);
|
public CachedGuildUser? EcQueryGuildUser(ulong guildId, string search) => _svcEntityCache.QueryGuildUserCache(guildId, search);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired after a message edit, when the message cache is about to be updated with the edited message.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This event serves as an alternative to <seealso cref="BaseSocketClient.MessageUpdated"/>,
|
|
||||||
/// pulling the previous state of the message from the entity cache instead of the library's cache.
|
|
||||||
/// </remarks>
|
|
||||||
public event EcMessageUpdateHandler? EcOnMessageUpdate {
|
|
||||||
add { _svcEntityCache.OnCachePreUpdate += value; }
|
|
||||||
remove { _svcEntityCache.OnCachePreUpdate -= value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delegate used for the <seealso cref="EcOnMessageUpdate"/> event.
|
|
||||||
/// </summary>
|
|
||||||
/// <params>
|
|
||||||
/// <param name="oldMsg">The previous state of the message prior to being updated, as known by the entity cache.</param>
|
|
||||||
/// <param name="newMsg">The new, updated incoming message.</param>
|
|
||||||
/// </params>
|
|
||||||
public delegate Task EcMessageUpdateHandler(CachedGuildMessage? oldMsg, SocketMessage newMsg);
|
|
||||||
}
|
}
|
||||||
|
|
26
Services/EntityCache/MessageCacheUpdateEvent.cs
Normal file
26
Services/EntityCache/MessageCacheUpdateEvent.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using RegexBot.Data;
|
||||||
|
|
||||||
|
namespace RegexBot;
|
||||||
|
/// <summary>
|
||||||
|
/// Fired after a message edit, when the message cache is about to be updated with the edited message.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Processing this serves as an alternative to <seealso cref="BaseSocketClient.MessageUpdated"/>,
|
||||||
|
/// pulling the previous state of the message from the entity cache instead of the library's cache.
|
||||||
|
/// </remarks>
|
||||||
|
public class MessageCacheUpdateEvent : ISharedEvent {
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the previous state of the message prior to being updated, as known by the entity cache.
|
||||||
|
/// </summary>
|
||||||
|
public CachedGuildMessage? OldMessage { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the new, updated incoming message.
|
||||||
|
/// </summary>
|
||||||
|
public SocketMessage NewMessage { get; }
|
||||||
|
|
||||||
|
internal MessageCacheUpdateEvent(CachedGuildMessage? old, SocketMessage @new) {
|
||||||
|
OldMessage = old;
|
||||||
|
NewMessage = @new;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,12 @@
|
||||||
using Discord;
|
using Discord;
|
||||||
using RegexBot.Data;
|
using RegexBot.Data;
|
||||||
using static RegexBot.RegexbotClient;
|
|
||||||
|
|
||||||
namespace RegexBot.Services.EntityCache;
|
namespace RegexBot.Services.EntityCache;
|
||||||
class MessageCachingSubservice {
|
class MessageCachingSubservice {
|
||||||
// Hooked
|
private readonly RegexbotClient _bot;
|
||||||
public event EcMessageUpdateHandler? OnCachePreUpdate;
|
|
||||||
|
|
||||||
private readonly Action<string> _log;
|
internal MessageCachingSubservice(RegexbotClient bot) {
|
||||||
|
_bot = bot;
|
||||||
internal MessageCachingSubservice(RegexbotClient bot, Action<string> logMethod) {
|
|
||||||
_log = logMethod;
|
|
||||||
bot.DiscordClient.MessageReceived += DiscordClient_MessageReceived;
|
bot.DiscordClient.MessageReceived += DiscordClient_MessageReceived;
|
||||||
bot.DiscordClient.MessageUpdated += DiscordClient_MessageUpdated;
|
bot.DiscordClient.MessageUpdated += DiscordClient_MessageUpdated;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +30,8 @@ class MessageCachingSubservice {
|
||||||
// Alternative for Discord.Net's MessageUpdated handler:
|
// Alternative for Discord.Net's MessageUpdated handler:
|
||||||
// Notify subscribers of message update using EC entry for the previous message state
|
// Notify subscribers of message update using EC entry for the previous message state
|
||||||
var oldMsg = CachedGuildMessage.Clone(cachedMsg);
|
var oldMsg = CachedGuildMessage.Clone(cachedMsg);
|
||||||
await Task.Factory.StartNew(async () => await RunPreUpdateHandlersAsync(oldMsg, arg));
|
var updEvent = new MessageCacheUpdateEvent(oldMsg, arg);
|
||||||
|
await _bot.PushSharedEventAsync(updEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cachedMsg == null) {
|
if (cachedMsg == null) {
|
||||||
|
@ -55,21 +52,4 @@ class MessageCachingSubservice {
|
||||||
}
|
}
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunPreUpdateHandlersAsync(CachedGuildMessage? oldMsg, SocketMessage newMsg) {
|
|
||||||
Delegate[]? subscribers;
|
|
||||||
lock (this) {
|
|
||||||
subscribers = OnCachePreUpdate?.GetInvocationList();
|
|
||||||
if (subscribers == null || subscribers.Length == 0) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var handler in subscribers) {
|
|
||||||
try {
|
|
||||||
await (Task)handler.DynamicInvoke(oldMsg, newMsg)!;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
_log($"Unhandled exception in {nameof(RegexbotClient.EcOnMessageUpdate)} handler '{handler.Method.Name}':\n"
|
|
||||||
+ ex.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
#pragma warning disable CA1822 // "Mark members as static" - will not make static to encourage better structure
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using RegexBot.Common;
|
using RegexBot.Common;
|
||||||
using RegexBot.Data;
|
using RegexBot.Data;
|
||||||
|
|
||||||
|
@ -8,7 +9,6 @@ namespace RegexBot.Services.EntityCache;
|
||||||
/// It is meant to work as a supplement to Discord.Net's own user caching capabilities. Its purpose is to
|
/// It is meant to work as a supplement to Discord.Net's own user caching capabilities. Its purpose is to
|
||||||
/// provide information on users which the library may not be aware about, such as users no longer in a guild.
|
/// provide information on users which the library may not be aware about, such as users no longer in a guild.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")]
|
|
||||||
class UserCachingSubservice {
|
class UserCachingSubservice {
|
||||||
private readonly Action<string> _log;
|
private readonly Action<string> _log;
|
||||||
|
|
||||||
|
|
34
Services/SharedEventService/Hooks.cs
Normal file
34
Services/SharedEventService/Hooks.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using RegexBot.Services.SharedEventService;
|
||||||
|
|
||||||
|
namespace RegexBot;
|
||||||
|
partial class RegexbotClient {
|
||||||
|
private readonly SharedEventService _svcSharedEvents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate used for the <seealso cref="SharedEventReceived"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ev">The incoming event instance.</param>
|
||||||
|
public delegate Task IncomingSharedEventHandler(ISharedEvent ev);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an object instance implementing <seealso cref="ISharedEvent"/> to all modules and services
|
||||||
|
/// subscribed to the <seealso cref="SharedEventReceived"/> event.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is non-blocking. Event handlers are executed in their own thread.
|
||||||
|
/// </remarks>
|
||||||
|
public Task PushSharedEventAsync(ISharedEvent ev) => _svcSharedEvents.PushSharedEventAsync(ev);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event is fired after a module or internal service calls <see cref="PushSharedEventAsync"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Subscribers to this event are handled on a "fire and forget" basis and may execute on a thread
|
||||||
|
/// separate from the main one handling Discord events. Ensure that the code executed by the handler
|
||||||
|
/// executes quickly, is thread-safe, and throws no exceptions.
|
||||||
|
/// </remarks>
|
||||||
|
public event IncomingSharedEventHandler? SharedEventReceived {
|
||||||
|
add { lock (_svcSharedEvents) _svcSharedEvents.Subscribers += value; }
|
||||||
|
remove { lock (_svcSharedEvents) _svcSharedEvents.Subscribers -= value; }
|
||||||
|
}
|
||||||
|
}
|
6
Services/SharedEventService/SharedEvent.cs
Normal file
6
Services/SharedEventService/SharedEvent.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace RegexBot; // Note: Within RegexBot namespace, for ease of use by modules
|
||||||
|
/// <summary>
|
||||||
|
/// An empty interface which denotes that the implementing object instance may be passed through
|
||||||
|
/// the shared event service.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISharedEvent { }
|
Loading…
Reference in a new issue