refactor(di): migrate services layer to ILogger<T> (DI-4 Slice A)
MessageStore, MessageEnumerator, MessageManager, AutoTellTabsService
move from Plugin.LogProxy / IPluginLogProxy onto
Microsoft.Extensions.Logging.ILogger<T> via constructor injection.
MessageStore additionally takes ILoggerFactory so it can build a
per-instance ILogger<MessageEnumerator> at each of the five reader-
spawning sites; the enumerator is not a container singleton.
PluginHostFactory's MessageManager and AutoTellTabsService factory
lambdas grow to resolve the new logger args; everything else stays in
place.
Site-level migration in the four files:
- MessageStore: 12 calls, _logger field IPluginLogProxy -> ILogger<MessageStore>
- MessageManager: 7 Plugin.LogProxy.* sites, new _logger field
- AutoTellTabsService: 9 Plugin.LogProxy.* sites, new _logger field
Plus a pre-existing template bug surfaced by CA2017: a LogDebug call
in AutoTellTabsService used "{tab.Name}" with no `$` prefix, which
landed in xllog as literal text under Plugin.LogProxy; ILogger now
reads that as a structured placeholder, so the call was promoted to
proper structured logging with tab.Name passed as a parameter.
This commit is contained in:
@@ -9,6 +9,7 @@ using HellionChat.Code;
|
|||||||
using HellionChat.GameFunctions.Types;
|
using HellionChat.GameFunctions.Types;
|
||||||
using HellionChat.Resources;
|
using HellionChat.Resources;
|
||||||
using HellionChat.Util;
|
using HellionChat.Util;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace HellionChat;
|
namespace HellionChat;
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
private readonly Plugin _plugin;
|
private readonly Plugin _plugin;
|
||||||
private readonly MessageManager _messageManager;
|
private readonly MessageManager _messageManager;
|
||||||
private readonly MessageStore _store;
|
private readonly MessageStore _store;
|
||||||
|
private readonly ILogger<AutoTellTabsService> _logger;
|
||||||
private readonly object _tempTabsLock = new();
|
private readonly object _tempTabsLock = new();
|
||||||
|
|
||||||
// Hard cap on pinned TempTabs so the sidebar doesn't inflate over years
|
// Hard cap on pinned TempTabs so the sidebar doesn't inflate over years
|
||||||
@@ -29,11 +31,17 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
|
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
|
|
||||||
internal AutoTellTabsService(Plugin plugin, MessageManager messageManager, MessageStore store)
|
internal AutoTellTabsService(
|
||||||
|
Plugin plugin,
|
||||||
|
MessageManager messageManager,
|
||||||
|
MessageStore store,
|
||||||
|
ILogger<AutoTellTabsService> logger
|
||||||
|
)
|
||||||
{
|
{
|
||||||
_plugin = plugin;
|
_plugin = plugin;
|
||||||
_messageManager = messageManager;
|
_messageManager = messageManager;
|
||||||
_store = store;
|
_store = store;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derived from the tab list on read. Pin/Unpin/Promote/Logout simply
|
// Derived from the tab list on read. Pin/Unpin/Promote/Logout simply
|
||||||
@@ -67,7 +75,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
private void RehydratePinnedTabs()
|
private void RehydratePinnedTabs()
|
||||||
{
|
{
|
||||||
var pinned = Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInPinnedPool);
|
var pinned = Plugin.Config.Tabs.Count(TabLifecycleHelpers.IsInPinnedPool);
|
||||||
Plugin.LogProxy.Debug($"[Pin] Rehydrate scan: {pinned} pinned tab(s) found");
|
_logger.LogDebug($"[Pin] Rehydrate scan: {pinned} pinned tab(s) found");
|
||||||
|
|
||||||
foreach (var tab in Plugin.Config.Tabs)
|
foreach (var tab in Plugin.Config.Tabs)
|
||||||
{
|
{
|
||||||
@@ -76,7 +84,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
|
|
||||||
if (tab.TellTarget is null || !tab.TellTarget.IsSet())
|
if (tab.TellTarget is null || !tab.TellTarget.IsSet())
|
||||||
{
|
{
|
||||||
Plugin.LogProxy.Warning(
|
_logger.LogWarning(
|
||||||
$"[Pin] Pinned tab '{tab.Name}' has no usable TellTarget "
|
$"[Pin] Pinned tab '{tab.Name}' has no usable TellTarget "
|
||||||
+ $"(Name={tab.TellTarget?.Name ?? "<null>"} World={tab.TellTarget?.World ?? 0}). "
|
+ $"(Name={tab.TellTarget?.Name ?? "<null>"} World={tab.TellTarget?.World ?? 0}). "
|
||||||
+ "Chat input on this tab will be empty until the partner sends a tell or you /tell manually."
|
+ "Chat input on this tab will be empty until the partner sends a tell or you /tell manually."
|
||||||
@@ -93,7 +101,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
// sees the recent conversation, not a blank tab.
|
// sees the recent conversation, not a blank tab.
|
||||||
PreloadHistory(tab, tab.TellTarget.Name, tab.TellTarget.World, Guid.Empty);
|
PreloadHistory(tab, tab.TellTarget.Name, tab.TellTarget.World, Guid.Empty);
|
||||||
|
|
||||||
Plugin.LogProxy.Debug(
|
_logger.LogDebug(
|
||||||
$"[Pin] Rehydrated '{tab.Name}' -> Tell target {tab.TellTarget.Name}@{tab.TellTarget.World}"
|
$"[Pin] Rehydrated '{tab.Name}' -> Tell target {tab.TellTarget.Name}@{tab.TellTarget.World}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -130,7 +138,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
if (partner == null)
|
if (partner == null)
|
||||||
{
|
{
|
||||||
// Diagnostics: helps detect regressions (FFXIV payload changes, new edge cases)
|
// Diagnostics: helps detect regressions (FFXIV payload changes, new edge cases)
|
||||||
Plugin.LogProxy.Warning(
|
_logger.LogWarning(
|
||||||
$"[AutoTellTabs] Could not extract tell partner. type={message.Code.Type}, "
|
$"[AutoTellTabs] Could not extract tell partner. type={message.Code.Type}, "
|
||||||
+ $"senderChunks={message.Sender.Count}, contentChunks={message.Content.Count}, "
|
+ $"senderChunks={message.Sender.Count}, contentChunks={message.Content.Count}, "
|
||||||
+ $"senderSourcePayloads={message.SenderSource?.Payloads?.Count ?? 0}, "
|
+ $"senderSourcePayloads={message.SenderSource?.Payloads?.Count ?? 0}, "
|
||||||
@@ -361,7 +369,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// Non-fatal: tab still spawns with visible error notice instead of silent history loss
|
// Non-fatal: tab still spawns with visible error notice instead of silent history loss
|
||||||
Plugin.LogProxy.Error(ex, "[AutoTellTabs] History preload failed");
|
_logger.LogError(ex, "[AutoTellTabs] History preload failed");
|
||||||
tab.Messages.AddPrune(
|
tab.Messages.AddPrune(
|
||||||
MakeSystemMarker(HellionStrings.AutoTellTabs_HistoryLoadError),
|
MakeSystemMarker(HellionStrings.AutoTellTabs_HistoryLoadError),
|
||||||
MessageManager.MessageDisplayLimit
|
MessageManager.MessageDisplayLimit
|
||||||
@@ -456,7 +464,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
{
|
{
|
||||||
if (!tab.IsTempTab || tab.IsPinned)
|
if (!tab.IsTempTab || tab.IsPinned)
|
||||||
{
|
{
|
||||||
Plugin.LogProxy.Debug(
|
_logger.LogDebug(
|
||||||
$"[Pin] TryPin skipped: IsTempTab={tab.IsTempTab} IsPinned={tab.IsPinned}"
|
$"[Pin] TryPin skipped: IsTempTab={tab.IsTempTab} IsPinned={tab.IsPinned}"
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
@@ -472,7 +480,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
tab.IsPinned = true;
|
tab.IsPinned = true;
|
||||||
Plugin.LogProxy.Debug(
|
_logger.LogDebug(
|
||||||
$"[Pin] Pinned tab '{tab.Name}' target={tab.TellTarget?.Name}@{tab.TellTarget?.World}"
|
$"[Pin] Pinned tab '{tab.Name}' target={tab.TellTarget?.Name}@{tab.TellTarget?.World}"
|
||||||
);
|
);
|
||||||
_plugin.SaveConfig();
|
_plugin.SaveConfig();
|
||||||
@@ -495,7 +503,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
tab.IsPinned = false;
|
tab.IsPinned = false;
|
||||||
Plugin.LogProxy.Debug("[Pin] Unpinned tab '{tab.Name}'");
|
_logger.LogDebug("[Pin] Unpinned tab '{TabName}'", tab.Name);
|
||||||
_plugin.SaveConfig();
|
_plugin.SaveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,9 +517,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
|||||||
tab.IsTempTab = false;
|
tab.IsTempTab = false;
|
||||||
tab.IsPinned = false;
|
tab.IsPinned = false;
|
||||||
tab.TellTarget = TellTarget.Empty();
|
tab.TellTarget = TellTarget.Empty();
|
||||||
Plugin.LogProxy.Debug(
|
_logger.LogDebug($"[Pin] Promoted tab '{tab.Name}' to permanent (tell-binding dropped)");
|
||||||
$"[Pin] Promoted tab '{tab.Name}' to permanent (tell-binding dropped)"
|
|
||||||
);
|
|
||||||
_plugin.SaveConfig();
|
_plugin.SaveConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using HellionChat.Util;
|
|||||||
using Lumina.Text.Expressions;
|
using Lumina.Text.Expressions;
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace HellionChat;
|
namespace HellionChat;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
internal const int MessageDisplayLimit = 10_000;
|
internal const int MessageDisplayLimit = 10_000;
|
||||||
|
|
||||||
private Plugin Plugin { get; }
|
private Plugin Plugin { get; }
|
||||||
|
private readonly ILogger<MessageManager> _logger;
|
||||||
internal MessageStore Store { get; }
|
internal MessageStore Store { get; }
|
||||||
|
|
||||||
private Dictionary<ChatType, NameFormatting> Formats { get; } = [];
|
private Dictionary<ChatType, NameFormatting> Formats { get; } = [];
|
||||||
@@ -48,11 +50,21 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
// AutoTellTabsService to spawn or refresh temp tabs without coupling.
|
// AutoTellTabsService to spawn or refresh temp tabs without coupling.
|
||||||
public event Action<Message>? MessageProcessed;
|
public event Action<Message>? MessageProcessed;
|
||||||
|
|
||||||
internal unsafe MessageManager(Plugin plugin)
|
internal unsafe MessageManager(
|
||||||
|
Plugin plugin,
|
||||||
|
ILogger<MessageManager> logger,
|
||||||
|
ILoggerFactory loggerFactory
|
||||||
|
)
|
||||||
{
|
{
|
||||||
Plugin = plugin;
|
Plugin = plugin;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
Store = new MessageStore(DatabasePath(), Plugin.PlatformUtil, Plugin.LogProxy);
|
Store = new MessageStore(
|
||||||
|
DatabasePath(),
|
||||||
|
Plugin.PlatformUtil,
|
||||||
|
loggerFactory.CreateLogger<MessageStore>(),
|
||||||
|
loggerFactory
|
||||||
|
);
|
||||||
|
|
||||||
PendingMessageThread = new Thread(() =>
|
PendingMessageThread = new Thread(() =>
|
||||||
ProcessPendingMessages(PendingThreadCancellationToken.Token)
|
ProcessPendingMessages(PendingThreadCancellationToken.Token)
|
||||||
@@ -91,7 +103,7 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
|
|
||||||
if (PendingMessageThread.IsAlive)
|
if (PendingMessageThread.IsAlive)
|
||||||
Plugin.LogProxy.Warning(
|
_logger.LogWarning(
|
||||||
"PendingMessageThread did not observe cancellation within 10s. "
|
"PendingMessageThread did not observe cancellation within 10s. "
|
||||||
+ "Worker remains on background thread; next plugin reload releases it."
|
+ "Worker remains on background thread; next plugin reload releases it."
|
||||||
);
|
);
|
||||||
@@ -137,7 +149,7 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Plugin.LogProxy.Error(ex, "Error processing pending message");
|
_logger.LogError(ex, "Error processing pending message");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -182,12 +194,12 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
|
|
||||||
// Mark failed messages as deleted to prevent retry attempts
|
// Mark failed messages as deleted to prevent retry attempts
|
||||||
var failedIds = messages.FailedMessageIds();
|
var failedIds = messages.FailedMessageIds();
|
||||||
Plugin.LogProxy.Info(
|
_logger.LogInformation(
|
||||||
$"Marking {failedIds.Count} messages as deleted due to parse failures"
|
$"Marking {failedIds.Count} messages as deleted due to parse failures"
|
||||||
);
|
);
|
||||||
foreach (var msgId in messages.FailedMessageIds())
|
foreach (var msgId in messages.FailedMessageIds())
|
||||||
{
|
{
|
||||||
Plugin.LogProxy.Debug($"Marking message '{msgId}' as deleted due to parse failure");
|
_logger.LogDebug($"Marking message '{msgId}' as deleted due to parse failure");
|
||||||
Store.DeleteMessage(msgId);
|
Store.DeleteMessage(msgId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,13 +215,13 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Plugin.LogProxy.Error(ex, "Error in FilterAllTabs");
|
_logger.LogError(ex, "Error in FilterAllTabs");
|
||||||
}
|
}
|
||||||
|
|
||||||
// v1.4.9 R3 profiling: Information so the xllog tail surfaces this
|
// v1.4.9 R3 profiling: Information so the xllog tail surfaces this
|
||||||
// without a Debug filter. Belt-and-suspenders for future plugin-load
|
// without a Debug filter. Belt-and-suspenders for future plugin-load
|
||||||
// regressions; remains in place after Sub-Task 3.4 Befund.
|
// regressions; remains in place after Sub-Task 3.4 Befund.
|
||||||
Plugin.LogProxy.Information($"FilterAllTabs took {stopwatch.ElapsedMilliseconds}ms");
|
_logger.LogInformation($"FilterAllTabs took {stopwatch.ElapsedMilliseconds}ms");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +276,7 @@ internal class MessageManager : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Plugin.LogProxy.Error(ex, "Error in ContentIdResolver");
|
_logger.LogError(ex, "Error in ContentIdResolver");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
-21
@@ -9,6 +9,7 @@ using MessagePack;
|
|||||||
using MessagePack.Formatters;
|
using MessagePack.Formatters;
|
||||||
using MessagePack.Resolvers;
|
using MessagePack.Resolvers;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Encoding = System.Text.Encoding;
|
using Encoding = System.Text.Encoding;
|
||||||
|
|
||||||
namespace HellionChat;
|
namespace HellionChat;
|
||||||
@@ -179,7 +180,8 @@ internal class MessageStore : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly IPlatformUtil _platformUtil;
|
private readonly IPlatformUtil _platformUtil;
|
||||||
private readonly IPluginLogProxy _logger;
|
private readonly ILogger<MessageStore> _logger;
|
||||||
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
// Readiness gate for the FTS5 full-text index. Volatile so the DbViewer's
|
// Readiness gate for the FTS5 full-text index. Volatile so the DbViewer's
|
||||||
// per-frame IsFtsIndexBuilt read sees the flip the moment the bulk-insert
|
// per-frame IsFtsIndexBuilt read sees the flip the moment the bulk-insert
|
||||||
@@ -197,11 +199,17 @@ internal class MessageStore : IDisposable
|
|||||||
// own SqliteConnection via OpenSecondaryConnection.
|
// own SqliteConnection via OpenSecondaryConnection.
|
||||||
private readonly object _readLock = new();
|
private readonly object _readLock = new();
|
||||||
|
|
||||||
internal MessageStore(string dbPath, IPlatformUtil platformUtil, IPluginLogProxy logger)
|
internal MessageStore(
|
||||||
|
string dbPath,
|
||||||
|
IPlatformUtil platformUtil,
|
||||||
|
ILogger<MessageStore> logger,
|
||||||
|
ILoggerFactory loggerFactory
|
||||||
|
)
|
||||||
{
|
{
|
||||||
DbPath = dbPath;
|
DbPath = dbPath;
|
||||||
_platformUtil = platformUtil;
|
_platformUtil = platformUtil;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_loggerFactory = loggerFactory;
|
||||||
Connection = Connect();
|
Connection = Connect();
|
||||||
Migrate();
|
Migrate();
|
||||||
InitFtsReadyCache();
|
InitFtsReadyCache();
|
||||||
@@ -246,7 +254,7 @@ internal class MessageStore : IDisposable
|
|||||||
conn.Open();
|
conn.Open();
|
||||||
ApplyPragmas(conn);
|
ApplyPragmas(conn);
|
||||||
connectSw.Stop();
|
connectSw.Stop();
|
||||||
_logger.Information($"MessageStore.Connect took {connectSw.ElapsedMilliseconds}ms");
|
_logger.LogInformation($"MessageStore.Connect took {connectSw.ElapsedMilliseconds}ms");
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,12 +298,12 @@ internal class MessageStore : IDisposable
|
|||||||
migration();
|
migration();
|
||||||
|
|
||||||
migrateSw.Stop();
|
migrateSw.Stop();
|
||||||
_logger.Information($"MessageStore.Migrate took {migrateSw.ElapsedMilliseconds}ms");
|
_logger.LogInformation($"MessageStore.Migrate took {migrateSw.ElapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Migrate0()
|
private void Migrate0()
|
||||||
{
|
{
|
||||||
_logger.Information("Running migration 0: Creating tables");
|
_logger.LogInformation("Running migration 0: Creating tables");
|
||||||
Connection.Execute(
|
Connection.Execute(
|
||||||
@"
|
@"
|
||||||
CREATE TABLE IF NOT EXISTS messages (
|
CREATE TABLE IF NOT EXISTS messages (
|
||||||
@@ -322,7 +330,7 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
private void Migrate1()
|
private void Migrate1()
|
||||||
{
|
{
|
||||||
_logger.Information("Running migration 1: Adding Deleted column");
|
_logger.LogInformation("Running migration 1: Adding Deleted column");
|
||||||
Connection.Execute(
|
Connection.Execute(
|
||||||
@"
|
@"
|
||||||
ALTER TABLE messages ADD COLUMN Deleted BOOLEAN NOT NULL DEFAULT false;
|
ALTER TABLE messages ADD COLUMN Deleted BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -334,7 +342,7 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
private void Migrate2()
|
private void Migrate2()
|
||||||
{
|
{
|
||||||
_logger.Information("Running migration 2: Adding Channel generated column");
|
_logger.LogInformation("Running migration 2: Adding Channel generated column");
|
||||||
Connection.Execute(
|
Connection.Execute(
|
||||||
@"
|
@"
|
||||||
ALTER TABLE messages ADD COLUMN Channel INTEGER GENERATED ALWAYS AS (Code & 0x7f) VIRTUAL;
|
ALTER TABLE messages ADD COLUMN Channel INTEGER GENERATED ALWAYS AS (Code & 0x7f) VIRTUAL;
|
||||||
@@ -362,13 +370,15 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
private void Migrate3()
|
private void Migrate3()
|
||||||
{
|
{
|
||||||
_logger.Information("Running migration 3: Fix log kinds to fit the new format");
|
_logger.LogInformation("Running migration 3: Fix log kinds to fit the new format");
|
||||||
|
|
||||||
// Recovery for partially-applied Migrate3: schema already in target
|
// Recovery for partially-applied Migrate3: schema already in target
|
||||||
// shape but user_version was never bumped -- just record and exit.
|
// shape but user_version was never bumped -- just record and exit.
|
||||||
if (ColumnExists("messages", "ChatType") && !ColumnExists("messages", "Code"))
|
if (ColumnExists("messages", "ChatType") && !ColumnExists("messages", "Code"))
|
||||||
{
|
{
|
||||||
_logger.Information("Migration 3: schema already migrated, only bumping user_version");
|
_logger.LogInformation(
|
||||||
|
"Migration 3: schema already migrated, only bumping user_version"
|
||||||
|
);
|
||||||
SetMigrationVersion(3);
|
SetMigrationVersion(3);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -398,7 +408,7 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
private void Migrate4()
|
private void Migrate4()
|
||||||
{
|
{
|
||||||
_logger.Information("Running migration 4: Add FTS5 virtual table for full-text search");
|
_logger.LogInformation("Running migration 4: Add FTS5 virtual table for full-text search");
|
||||||
|
|
||||||
// Standalone FTS5 table (no content='messages' linking, no content_rowid).
|
// Standalone FTS5 table (no content='messages' linking, no content_rowid).
|
||||||
// messages.Id is BLOB-PK (Guid), which is incompatible with FTS5's
|
// messages.Id is BLOB-PK (Guid), which is incompatible with FTS5's
|
||||||
@@ -422,7 +432,7 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
private void SetMigrationVersion(int version)
|
private void SetMigrationVersion(int version)
|
||||||
{
|
{
|
||||||
_logger.Information($"Setting version {version}");
|
_logger.LogInformation($"Setting version {version}");
|
||||||
using var cmd = Connection.CreateCommand();
|
using var cmd = Connection.CreateCommand();
|
||||||
// PRAGMA does not accept SQLite parameter bindings; version is a
|
// PRAGMA does not accept SQLite parameter bindings; version is a
|
||||||
// compile-time int from the migration sequence, never user input.
|
// compile-time int from the migration sequence, never user input.
|
||||||
@@ -837,7 +847,7 @@ internal class MessageStore : IDisposable
|
|||||||
// Privacy filter -- drop disallowed ChatTypes before they reach storage.
|
// Privacy filter -- drop disallowed ChatTypes before they reach storage.
|
||||||
if (!Plugin.Config.IsAllowedForStorage(message.Code.Type))
|
if (!Plugin.Config.IsAllowedForStorage(message.Code.Type))
|
||||||
{
|
{
|
||||||
_logger.Verbose($"Privacy filter dropped message: ChatType={message.Code.Type}");
|
_logger.LogTrace($"Privacy filter dropped message: ChatType={message.Code.Type}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -941,7 +951,10 @@ internal class MessageStore : IDisposable
|
|||||||
if (to is not null)
|
if (to is not null)
|
||||||
cmd.Parameters.AddWithValue("$To", to.Value.ToUnixTimeMilliseconds());
|
cmd.Parameters.AddWithValue("$To", to.Value.ToUnixTimeMilliseconds());
|
||||||
|
|
||||||
return new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
return new MessageEnumerator(
|
||||||
|
cmd.ExecuteReader(),
|
||||||
|
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -993,7 +1006,10 @@ internal class MessageStore : IDisposable
|
|||||||
|
|
||||||
cmd.Parameters.AddWithValue("$Count", count);
|
cmd.Parameters.AddWithValue("$Count", count);
|
||||||
|
|
||||||
return new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
return new MessageEnumerator(
|
||||||
|
cmd.ExecuteReader(),
|
||||||
|
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1033,7 +1049,10 @@ internal class MessageStore : IDisposable
|
|||||||
cmd.Parameters.AddWithValue("$TellOutgoing", (int)ChatType.TellOutgoing);
|
cmd.Parameters.AddWithValue("$TellOutgoing", (int)ChatType.TellOutgoing);
|
||||||
|
|
||||||
var collected = new List<Message>();
|
var collected = new List<Message>();
|
||||||
using var enumerator = new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
using var enumerator = new MessageEnumerator(
|
||||||
|
cmd.ExecuteReader(),
|
||||||
|
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||||
|
);
|
||||||
foreach (var message in enumerator)
|
foreach (var message in enumerator)
|
||||||
{
|
{
|
||||||
if (!ChunkUtil.MatchesSender(message, senderName, senderWorld))
|
if (!ChunkUtil.MatchesSender(message, senderName, senderWorld))
|
||||||
@@ -1145,7 +1164,10 @@ internal class MessageStore : IDisposable
|
|||||||
((DateTimeOffset)before).ToUnixTimeMilliseconds()
|
((DateTimeOffset)before).ToUnixTimeMilliseconds()
|
||||||
);
|
);
|
||||||
|
|
||||||
return new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
return new MessageEnumerator(
|
||||||
|
cmd.ExecuteReader(),
|
||||||
|
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1198,7 +1220,10 @@ internal class MessageStore : IDisposable
|
|||||||
cmd.Parameters.AddWithValue("$Offset", DbViewer.RowPerPage * page);
|
cmd.Parameters.AddWithValue("$Offset", DbViewer.RowPerPage * page);
|
||||||
cmd.Parameters.AddWithValue("$OffsetCount", DbViewer.RowPerPage);
|
cmd.Parameters.AddWithValue("$OffsetCount", DbViewer.RowPerPage);
|
||||||
|
|
||||||
return new MessageEnumerator(cmd.ExecuteReader(), _logger);
|
return new MessageEnumerator(
|
||||||
|
cmd.ExecuteReader(),
|
||||||
|
_loggerFactory.CreateLogger<MessageEnumerator>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1219,14 +1244,14 @@ internal class MessageStore : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MessageEnumerator(DbDataReader reader, IPluginLogProxy logger)
|
internal class MessageEnumerator(DbDataReader reader, ILogger<MessageEnumerator> logger)
|
||||||
: IEnumerable<Message>,
|
: IEnumerable<Message>,
|
||||||
IDisposable,
|
IDisposable,
|
||||||
IAsyncDisposable
|
IAsyncDisposable
|
||||||
{
|
{
|
||||||
private const int MaxErrorLogs = 10;
|
private const int MaxErrorLogs = 10;
|
||||||
|
|
||||||
private readonly IPluginLogProxy _logger = logger;
|
private readonly ILogger<MessageEnumerator> _logger = logger;
|
||||||
private readonly List<Guid> FailedIds = [];
|
private readonly List<Guid> FailedIds = [];
|
||||||
private int FailedCount;
|
private int FailedCount;
|
||||||
public bool DidError => FailedCount > 0;
|
public bool DidError => FailedCount > 0;
|
||||||
@@ -1247,10 +1272,10 @@ internal class MessageEnumerator(DbDataReader reader, IPluginLogProxy logger)
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (FailedCount < MaxErrorLogs)
|
if (FailedCount < MaxErrorLogs)
|
||||||
_logger.Error($"Exception while reading message '{id}' from database: {e}");
|
_logger.LogError($"Exception while reading message '{id}' from database: {e}");
|
||||||
FailedCount++;
|
FailedCount++;
|
||||||
if (FailedCount == MaxErrorLogs)
|
if (FailedCount == MaxErrorLogs)
|
||||||
_logger.Error("Further parsing errors will not be logged");
|
_logger.LogError("Further parsing errors will not be logged");
|
||||||
if (id != Guid.Empty)
|
if (id != Guid.Empty)
|
||||||
FailedIds.Add(id);
|
FailedIds.Add(id);
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,11 @@ internal static class PluginHostFactory
|
|||||||
sp.GetRequiredService<IFramework>()
|
sp.GetRequiredService<IFramework>()
|
||||||
));
|
));
|
||||||
|
|
||||||
services.AddSingleton(sp => new MessageManager(sp.GetRequiredService<Plugin>()));
|
services.AddSingleton(sp => new MessageManager(
|
||||||
|
sp.GetRequiredService<Plugin>(),
|
||||||
|
sp.GetRequiredService<ILogger<MessageManager>>(),
|
||||||
|
sp.GetRequiredService<ILoggerFactory>()
|
||||||
|
));
|
||||||
|
|
||||||
// AutoTellTabsService pulls MessageStore through MessageManager.Store
|
// AutoTellTabsService pulls MessageStore through MessageManager.Store
|
||||||
// because MessageStore is still allocated inside MessageManager.ctor
|
// because MessageStore is still allocated inside MessageManager.ctor
|
||||||
@@ -131,7 +135,12 @@ internal static class PluginHostFactory
|
|||||||
{
|
{
|
||||||
var pluginRef = sp.GetRequiredService<Plugin>();
|
var pluginRef = sp.GetRequiredService<Plugin>();
|
||||||
var manager = sp.GetRequiredService<MessageManager>();
|
var manager = sp.GetRequiredService<MessageManager>();
|
||||||
return new AutoTellTabsService(pluginRef, manager, manager.Store);
|
return new AutoTellTabsService(
|
||||||
|
pluginRef,
|
||||||
|
manager,
|
||||||
|
manager.Store,
|
||||||
|
sp.GetRequiredService<ILogger<AutoTellTabsService>>()
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user