refactor(messagestore): inject IPluginLogProxy for test isolation

MessageStore's Migrate0 (and the Migrate1/2/3 siblings) called
Plugin.Log.Information directly, which prevented an isolated xUnit
construction test from running — Dalamud.dll cannot load in the test
AppDomain. With IPluginLogProxy threaded through the ctor and the inner
MessageEnumerator, the whole MessageStore.cs file is now Dalamud-static
free and the Build-Suite covers it (Floor 688 -> 690).

This is the second half of F12.2; the remaining ~82 Plugin.Log call
sites in the rest of the plugin will be routed through the static
Plugin.LogProxy wrapper in a follow-up commit.
This commit is contained in:
2026-05-13 08:15:20 +02:00
parent 8edc3c70d3
commit dca5de4085
2 changed files with 20 additions and 19 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ internal class MessageManager : IAsyncDisposable
{ {
Plugin = plugin; Plugin = plugin;
Store = new MessageStore(DatabasePath(), Plugin.PlatformUtil); Store = new MessageStore(DatabasePath(), Plugin.PlatformUtil, Plugin.LogProxy);
PendingMessageThread = new Thread(() => PendingMessageThread = new Thread(() =>
ProcessPendingMessages(PendingThreadCancellationToken.Token) ProcessPendingMessages(PendingThreadCancellationToken.Token)
+19 -18
View File
@@ -137,11 +137,13 @@ internal class MessageStore : IDisposable
); );
private readonly IPlatformUtil _platformUtil; private readonly IPlatformUtil _platformUtil;
private readonly IPluginLogProxy _logger;
internal MessageStore(string dbPath, IPlatformUtil platformUtil) internal MessageStore(string dbPath, IPlatformUtil platformUtil, IPluginLogProxy logger)
{ {
DbPath = dbPath; DbPath = dbPath;
_platformUtil = platformUtil; _platformUtil = platformUtil;
_logger = logger;
Connection = Connect(); Connection = Connect();
Migrate(); Migrate();
} }
@@ -204,7 +206,7 @@ internal class MessageStore : IDisposable
private void Migrate0() private void Migrate0()
{ {
Plugin.Log.Information("Running migration 0: Creating tables"); _logger.Information("Running migration 0: Creating tables");
Connection.Execute( Connection.Execute(
@" @"
CREATE TABLE IF NOT EXISTS messages ( CREATE TABLE IF NOT EXISTS messages (
@@ -231,7 +233,7 @@ internal class MessageStore : IDisposable
private void Migrate1() private void Migrate1()
{ {
Plugin.Log.Information("Running migration 1: Adding Deleted column"); _logger.Information("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;
@@ -243,7 +245,7 @@ internal class MessageStore : IDisposable
private void Migrate2() private void Migrate2()
{ {
Plugin.Log.Information("Running migration 2: Adding Channel generated column"); _logger.Information("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;
@@ -271,15 +273,13 @@ internal class MessageStore : IDisposable
private void Migrate3() private void Migrate3()
{ {
Plugin.Log.Information("Running migration 3: Fix log kinds to fit the new format"); _logger.Information("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"))
{ {
Plugin.Log.Information( _logger.Information("Migration 3: schema already migrated, only bumping user_version");
"Migration 3: schema already migrated, only bumping user_version"
);
SetMigrationVersion(3); SetMigrationVersion(3);
return; return;
} }
@@ -309,7 +309,7 @@ internal class MessageStore : IDisposable
private void SetMigrationVersion(int version) private void SetMigrationVersion(int version)
{ {
Plugin.Log.Information($"Setting version {version}"); _logger.Information($"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.
@@ -461,7 +461,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))
{ {
Plugin.Log.Verbose($"Privacy filter dropped message: ChatType={message.Code.Type}"); _logger.Verbose($"Privacy filter dropped message: ChatType={message.Code.Type}");
return; return;
} }
@@ -554,7 +554,7 @@ 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()); return new MessageEnumerator(cmd.ExecuteReader(), _logger);
} }
// Returns the most recent messages, oldest-first. // Returns the most recent messages, oldest-first.
@@ -602,7 +602,7 @@ internal class MessageStore : IDisposable
cmd.Parameters.AddWithValue("$Count", count); cmd.Parameters.AddWithValue("$Count", count);
return new MessageEnumerator(cmd.ExecuteReader()); return new MessageEnumerator(cmd.ExecuteReader(), _logger);
} }
// Returns up to limit tells exchanged with the named player, oldest-first. // Returns up to limit tells exchanged with the named player, oldest-first.
@@ -640,7 +640,7 @@ internal class MessageStore : IDisposable
cmd.Parameters.AddWithValue("$ScanLimit", sqlScanLimit); cmd.Parameters.AddWithValue("$ScanLimit", sqlScanLimit);
var collected = new List<Message>(); var collected = new List<Message>();
using var enumerator = new MessageEnumerator(cmd.ExecuteReader()); using var enumerator = new MessageEnumerator(cmd.ExecuteReader(), _logger);
foreach (var message in enumerator) foreach (var message in enumerator)
{ {
if (!ChunkUtil.MatchesSender(message, senderName, senderWorld)) if (!ChunkUtil.MatchesSender(message, senderName, senderWorld))
@@ -732,7 +732,7 @@ internal class MessageStore : IDisposable
cmd.Parameters.AddWithValue("$After", ((DateTimeOffset)after).ToUnixTimeMilliseconds()); cmd.Parameters.AddWithValue("$After", ((DateTimeOffset)after).ToUnixTimeMilliseconds());
cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset)before).ToUnixTimeMilliseconds()); cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset)before).ToUnixTimeMilliseconds());
return new MessageEnumerator(cmd.ExecuteReader()); return new MessageEnumerator(cmd.ExecuteReader(), _logger);
} }
internal MessageEnumerator GetPagedDateRange( internal MessageEnumerator GetPagedDateRange(
@@ -776,7 +776,7 @@ 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()); return new MessageEnumerator(cmd.ExecuteReader(), _logger);
} }
// Builds a "$prefix0,$prefix1,..." placeholder list and binds values to the command. // Builds a "$prefix0,$prefix1,..." placeholder list and binds values to the command.
@@ -796,13 +796,14 @@ internal class MessageStore : IDisposable
} }
} }
internal class MessageEnumerator(DbDataReader reader) internal class MessageEnumerator(DbDataReader reader, IPluginLogProxy 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 List<Guid> FailedIds = []; private readonly List<Guid> FailedIds = [];
private int FailedCount; private int FailedCount;
public bool DidError => FailedCount > 0; public bool DidError => FailedCount > 0;
@@ -848,10 +849,10 @@ internal class MessageEnumerator(DbDataReader reader)
catch (Exception e) catch (Exception e)
{ {
if (FailedCount < MaxErrorLogs) if (FailedCount < MaxErrorLogs)
Plugin.Log.Error($"Exception while reading message '{id}' from database: {e}"); _logger.Error($"Exception while reading message '{id}' from database: {e}");
FailedCount++; FailedCount++;
if (FailedCount == MaxErrorLogs) if (FailedCount == MaxErrorLogs)
Plugin.Log.Error("Further parsing errors will not be logged"); _logger.Error("Further parsing errors will not be logged");
if (id != Guid.Empty) if (id != Guid.Empty)
FailedIds.Add(id); FailedIds.Add(id);