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) {
|
||||
// TODO missing logging features: joins, leaves, bans, kicks, user edits (nick/username/discr)
|
||||
DiscordClient.MessageDeleted += HandleDelete;
|
||||
bot.EcOnMessageUpdate += HandleUpdate;
|
||||
bot.SharedEventReceived += FilterIncomingEvents;
|
||||
}
|
||||
|
||||
public override Task<object?> CreateGuildStateAsync(ulong guildID, JToken config) {
|
||||
|
|
|
@ -73,6 +73,12 @@ internal partial class ModLogs {
|
|||
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) {
|
||||
const int MaxPreviewLength = 500;
|
||||
var channel = (SocketTextChannel)newMsg.Channel;
|
||||
|
|
|
@ -26,6 +26,7 @@ public partial class RegexbotClient {
|
|||
|
||||
// Get all services started up
|
||||
_svcLogging = new Services.Logging.LoggingService(this);
|
||||
_svcSharedEvents = new Services.SharedEventService.SharedEventService(this);
|
||||
_svcGuildState = new Services.ModuleState.ModuleStateService(this);
|
||||
_svcCommonFunctions = new Services.CommonFunctions.CommonFunctionsService(this);
|
||||
_svcEntityCache = new Services.EntityCache.EntityCacheService(this);
|
||||
|
|
|
@ -6,12 +6,13 @@ namespace RegexBot.Services.EntityCache;
|
|||
/// </summary>
|
||||
class EntityCacheService : Service {
|
||||
private readonly UserCachingSubservice _uc;
|
||||
#pragma warning disable IDE0052
|
||||
private readonly MessageCachingSubservice _mc;
|
||||
#pragma warning restore IDE0052
|
||||
|
||||
internal EntityCacheService(RegexbotClient bot) : base(bot) {
|
||||
// Currently we only have UserCache. May add Channel and Server caches later.
|
||||
_uc = new UserCachingSubservice(bot, Log);
|
||||
_mc = new MessageCachingSubservice(bot, Log);
|
||||
_mc = new MessageCachingSubservice(bot);
|
||||
}
|
||||
|
||||
// Hooked
|
||||
|
@ -21,10 +22,4 @@ class EntityCacheService : Service {
|
|||
// Hooked
|
||||
internal CachedGuildUser? QueryGuildUserCache(ulong guildId, string 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>
|
||||
/// <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);
|
||||
|
||||
/// <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 RegexBot.Data;
|
||||
using static RegexBot.RegexbotClient;
|
||||
|
||||
namespace RegexBot.Services.EntityCache;
|
||||
class MessageCachingSubservice {
|
||||
// Hooked
|
||||
public event EcMessageUpdateHandler? OnCachePreUpdate;
|
||||
private readonly RegexbotClient _bot;
|
||||
|
||||
private readonly Action<string> _log;
|
||||
|
||||
internal MessageCachingSubservice(RegexbotClient bot, Action<string> logMethod) {
|
||||
_log = logMethod;
|
||||
internal MessageCachingSubservice(RegexbotClient bot) {
|
||||
_bot = bot;
|
||||
bot.DiscordClient.MessageReceived += DiscordClient_MessageReceived;
|
||||
bot.DiscordClient.MessageUpdated += DiscordClient_MessageUpdated;
|
||||
}
|
||||
|
@ -34,7 +30,8 @@ class MessageCachingSubservice {
|
|||
// Alternative for Discord.Net's MessageUpdated handler:
|
||||
// Notify subscribers of message update using EC entry for the previous message state
|
||||
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) {
|
||||
|
@ -55,21 +52,4 @@ class MessageCachingSubservice {
|
|||
}
|
||||
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.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
|
||||
/// provide information on users which the library may not be aware about, such as users no longer in a guild.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static")]
|
||||
class UserCachingSubservice {
|
||||
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