From 8a34cb51862b8368c96af5452a1e6a7e18d136b8 Mon Sep 17 00:00:00 2001 From: Infi Date: Sat, 13 Apr 2024 18:01:00 +0200 Subject: [PATCH] Fix double messages on startup --- ChatTwo/ChatTwo.csproj | 4 +- ChatTwo/Configuration.cs | 114 +++++++++++++++--------- ChatTwo/Store.cs | 185 +++++++++++++++++++++------------------ 3 files changed, 175 insertions(+), 128 deletions(-) diff --git a/ChatTwo/ChatTwo.csproj b/ChatTwo/ChatTwo.csproj index f1c3804..5b42fa5 100755 --- a/ChatTwo/ChatTwo.csproj +++ b/ChatTwo/ChatTwo.csproj @@ -1,7 +1,6 @@ - - 1.20.4 + 1.20.5 net8.0-windows enable enable @@ -87,5 +86,4 @@ - diff --git a/ChatTwo/Configuration.cs b/ChatTwo/Configuration.cs index 5fad066..4cb5ed7 100755 --- a/ChatTwo/Configuration.cs +++ b/ChatTwo/Configuration.cs @@ -7,7 +7,8 @@ using ImGuiNET; namespace ChatTwo; [Serializable] -internal class Configuration : IPluginConfiguration { +internal class Configuration : IPluginConfiguration +{ private const int LatestVersion = 5; internal const int LatestDbVersion = 1; @@ -56,7 +57,8 @@ internal class Configuration : IPluginConfiguration { public uint DatabaseMigration = LatestDbVersion; - internal void UpdateFrom(Configuration other) { + internal void UpdateFrom(Configuration other) + { HideChat = other.HideChat; HideDuringCutscenes = other.HideDuringCutscenes; HideWhenNotLoggedIn = other.HideWhenNotLoggedIn; @@ -97,14 +99,17 @@ internal class Configuration : IPluginConfiguration { ChosenStyle = other.ChosenStyle; } - public void Migrate() { + public void Migrate() + { var loop = true; - while (loop && Version < LatestVersion) { + while (loop && Version < LatestVersion) + { switch (Version) { case 1: { Version = 2; - foreach (var tab in Tabs) { + foreach (var tab in Tabs) + { #pragma warning disable CS0618 tab.UnreadMode = tab.DisplayUnread ? UnreadMode.Unseen : UnreadMode.None; #pragma warning restore CS0618 @@ -126,10 +131,8 @@ internal class Configuration : IPluginConfiguration { case 4: Version = 5; - foreach (var tab in Tabs) { + foreach (var tab in Tabs) tab.ExtraChatAll = true; - } - break; default: Plugin.Log.Warning($"Couldn't migrate config version {Version}"); @@ -141,21 +144,25 @@ internal class Configuration : IPluginConfiguration { } [Serializable] -internal enum UnreadMode { +internal enum UnreadMode +{ All, Unseen, None, } -internal static class UnreadModeExt { - internal static string Name(this UnreadMode mode) => mode switch { +internal static class UnreadModeExt +{ + internal static string Name(this UnreadMode mode) => mode switch + { UnreadMode.All => Language.UnreadMode_All, UnreadMode.Unseen => Language.UnreadMode_Unseen, UnreadMode.None => Language.UnreadMode_None, _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null), }; - internal static string? Tooltip(this UnreadMode mode) => mode switch { + internal static string? Tooltip(this UnreadMode mode) => mode switch + { UnreadMode.All => Language.UnreadMode_All_Tooltip, UnreadMode.Unseen => Language.UnreadMode_Unseen_Tooltip, UnreadMode.None => Language.UnreadMode_None_Tooltip, @@ -164,7 +171,8 @@ internal static class UnreadModeExt { } [Serializable] -internal class Tab { +internal class Tab +{ public string Name = Language.Tab_DefaultName; public Dictionary ChatCodes = new(); public bool ExtraChatAll; @@ -189,41 +197,48 @@ internal class Tab { [NonSerialized] public List Messages = new(); - ~Tab() { - MessagesMutex.Dispose(); + ~Tab() { MessagesMutex.Dispose(); } + + internal bool Contains(Message message) + { + return Messages.Any(m => m.Hash == message.Hash); } - internal bool Matches(Message message) { - if (message.ExtraChatChannel != Guid.Empty) { + internal bool Matches(Message message) + { + if (message.ExtraChatChannel != Guid.Empty) return ExtraChatAll || ExtraChatChannels.Contains(message.ExtraChatChannel); - } return message.Code.Type.IsGm() - || ChatCodes.TryGetValue(message.Code.Type, out var sources) && (message.Code.Source is 0 or (ChatSource) 1 || sources.HasFlag(message.Code.Source)); + || ChatCodes.TryGetValue(message.Code.Type, out var sources) + && (message.Code.Source is 0 or (ChatSource) 1 + || sources.HasFlag(message.Code.Source)); } - internal void AddMessage(Message message, bool unread = true) { + internal void AddMessage(Message message, bool unread = true) + { MessagesMutex.Wait(); Messages.Add(message); - while (Messages.Count > Store.MessagesLimit) { + while (Messages.Count > Store.MessagesLimit) Messages.RemoveAt(0); - } MessagesMutex.Release(); - if (unread) { + if (unread) Unread += 1; - } } - internal void Clear() { + internal void Clear() + { MessagesMutex.Wait(); Messages.Clear(); MessagesMutex.Release(); } - internal Tab Clone() { - return new Tab { + internal Tab Clone() + { + return new Tab + { Name = Name, ChatCodes = ChatCodes.ToDictionary(entry => entry.Key, entry => entry.Value), ExtraChatAll = ExtraChatAll, @@ -242,14 +257,17 @@ internal class Tab { } [Serializable] -internal enum CommandHelpSide { +internal enum CommandHelpSide +{ None, Left, Right, } -internal static class CommandHelpSideExt { - internal static string Name(this CommandHelpSide side) => side switch { +internal static class CommandHelpSideExt +{ + internal static string Name(this CommandHelpSide side) => side switch + { CommandHelpSide.None => Language.CommandHelpSide_None, CommandHelpSide.Left => Language.CommandHelpSide_Left, CommandHelpSide.Right => Language.CommandHelpSide_Right, @@ -258,19 +276,23 @@ internal static class CommandHelpSideExt { } [Serializable] -internal enum KeybindMode { +internal enum KeybindMode +{ Flexible, Strict, } -internal static class KeybindModeExt { - internal static string Name(this KeybindMode mode) => mode switch { +internal static class KeybindModeExt +{ + internal static string Name(this KeybindMode mode) => mode switch + { KeybindMode.Flexible => Language.KeybindMode_Flexible_Name, KeybindMode.Strict => Language.KeybindMode_Strict_Name, _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null), }; - internal static string? Tooltip(this KeybindMode mode) => mode switch { + internal static string? Tooltip(this KeybindMode mode) => mode switch + { KeybindMode.Flexible => Language.KeybindMode_Flexible_Tooltip, KeybindMode.Strict => Language.KeybindMode_Strict_Tooltip, _ => null, @@ -278,7 +300,8 @@ internal static class KeybindModeExt { } [Serializable] -internal enum LanguageOverride { +internal enum LanguageOverride +{ None, ChineseSimplified, ChineseTraditional, @@ -300,8 +323,10 @@ internal enum LanguageOverride { Swedish, } -internal static class LanguageOverrideExt { - internal static string Name(this LanguageOverride mode) => mode switch { +internal static class LanguageOverrideExt +{ + internal static string Name(this LanguageOverride mode) => mode switch + { LanguageOverride.None => Language.LanguageOverride_None, LanguageOverride.ChineseSimplified => "简体中文", LanguageOverride.ChineseTraditional => "繁體中文", @@ -322,7 +347,8 @@ internal static class LanguageOverrideExt { _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null), }; - internal static string Code(this LanguageOverride mode) => mode switch { + internal static string Code(this LanguageOverride mode) => mode switch + { LanguageOverride.None => "", LanguageOverride.ChineseSimplified => "zh-hans", LanguageOverride.ChineseTraditional => "zh-hant", @@ -346,7 +372,8 @@ internal static class LanguageOverrideExt { [Serializable] [Flags] -internal enum ExtraGlyphRanges { +internal enum ExtraGlyphRanges +{ ChineseFull = 1 << 0, ChineseSimplifiedCommon = 1 << 1, Cyrillic = 1 << 2, @@ -356,8 +383,10 @@ internal enum ExtraGlyphRanges { Vietnamese = 1 << 6, } -internal static class ExtraGlyphRangesExt { - internal static string Name(this ExtraGlyphRanges ranges) => ranges switch { +internal static class ExtraGlyphRangesExt +{ + internal static string Name(this ExtraGlyphRanges ranges) => ranges switch + { ExtraGlyphRanges.ChineseFull => Language.ExtraGlyphRanges_ChineseFull_Name, ExtraGlyphRanges.ChineseSimplifiedCommon => Language.ExtraGlyphRanges_ChineseSimplifiedCommon_Name, ExtraGlyphRanges.Cyrillic => Language.ExtraGlyphRanges_Cyrillic_Name, @@ -368,7 +397,8 @@ internal static class ExtraGlyphRangesExt { _ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null), }; - internal static IntPtr Range(this ExtraGlyphRanges ranges) => ranges switch { + internal static IntPtr Range(this ExtraGlyphRanges ranges) => ranges switch + { ExtraGlyphRanges.ChineseFull => ImGui.GetIO().Fonts.GetGlyphRangesChineseFull(), ExtraGlyphRanges.ChineseSimplifiedCommon => ImGui.GetIO().Fonts.GetGlyphRangesChineseSimplifiedCommon(), ExtraGlyphRanges.Cyrillic => ImGui.GetIO().Fonts.GetGlyphRangesCyrillic(), diff --git a/ChatTwo/Store.cs b/ChatTwo/Store.cs index d2695d9..49e3529 100755 --- a/ChatTwo/Store.cs +++ b/ChatTwo/Store.cs @@ -10,7 +10,8 @@ using Lumina.Excel.GeneratedSheets; namespace ChatTwo; -internal class Store : IDisposable { +internal class Store : IDisposable +{ internal const int MessagesLimit = 10_000; private Plugin Plugin { get; } @@ -23,24 +24,29 @@ internal class Store : IDisposable { private Dictionary Formats { get; } = new(); private ulong LastContentId { get; set; } - private ulong CurrentContentId { - get { + private ulong CurrentContentId + { + get + { var contentId = Plugin.ClientState.LocalContentId; return contentId == 0 ? LastContentId : contentId; } } - internal Store(Plugin plugin) { + internal Store(Plugin plugin) + { Plugin = plugin; CheckpointTimer.Start(); - BsonMapper.Global = new BsonMapper { + BsonMapper.Global = new BsonMapper + { IncludeNonPublic = true, TrimWhitespace = false, // EnumAsInteger = true, }; - if (Plugin.Config.DatabaseMigration == 0) { + if (Plugin.Config.DatabaseMigration == 0) + { BsonMapper.Global.Entity() .Id(msg => msg.Id) .Ctor(doc => new Message( @@ -55,7 +61,9 @@ internal class Store : IDisposable { doc["ContentSource"], doc["SortCode"].AsDocument )); - } else { + } + else + { BsonMapper.Global.Entity() .Id(msg => msg.Id) .Ctor(doc => new Message( @@ -74,8 +82,10 @@ internal class Store : IDisposable { } BsonMapper.Global.RegisterType( - payload => { - switch (payload) { + payload => + { + switch (payload) + { case AchievementPayload achievement: return new BsonDocument(new Dictionary { ["Type"] = new("Achievement"), @@ -95,13 +105,15 @@ internal class Store : IDisposable { return payload?.Encode(); }, - bson => { - if (bson.IsNull) { + bson => + { + if (bson.IsNull) return null; - } - if (bson.IsDocument) { - return bson["Type"].AsString switch { + if (bson.IsDocument) + { + return bson["Type"].AsString switch + { "Achievement" => new AchievementPayload((uint) bson["Id"].AsInt64), "PartyFinder" => new PartyFinderPayload((uint) bson["Id"].AsInt64), "URI" => new URIPayload(new Uri(bson["Uri"].AsString)), @@ -115,10 +127,10 @@ internal class Store : IDisposable { seString => seString == null ? null : new BsonArray(seString.Payloads.Select(payload => new BsonValue(payload.Encode()))), - bson => { - if (bson.IsNull) { + bson => + { + if (bson.IsNull) return null; - } var array = bson.IsArray ? bson.AsArray : bson["Payloads"].AsArray; var payloads = array @@ -167,7 +179,8 @@ internal class Store : IDisposable { var dbPath = DatabasePath(); var connection = Plugin.Config.SharedMode ? "shared" : "direct"; var connString = $"Filename='{dbPath}';Connection={connection}"; - var conn = new LiteDatabase(connString, BsonMapper.Global) { + var conn = new LiteDatabase(connString, BsonMapper.Global) + { CheckpointSize = 1_000, Timeout = TimeSpan.FromSeconds(1), }; @@ -178,7 +191,8 @@ internal class Store : IDisposable { return conn; } - internal void Reconnect() { + internal void Reconnect() + { Database.Dispose(); Database = Connect(); } @@ -203,69 +217,71 @@ internal class Store : IDisposable { internal int MessageCount() => Messages.Count(); - private void Logout() { + private void Logout() + { LastContentId = 0; } - private void UpdateReceiver(IFramework framework) { + private void UpdateReceiver(IFramework framework) + { var contentId = Plugin.ClientState.LocalContentId; - if (contentId != 0) { + if (contentId != 0) LastContentId = contentId; - } } - private void GetMessageInfo(IFramework framework) { - if (CheckpointTimer.Elapsed > TimeSpan.FromMinutes(5)) { + private void GetMessageInfo(IFramework framework) + { + if (CheckpointTimer.Elapsed > TimeSpan.FromMinutes(5)) + { CheckpointTimer.Restart(); new Thread(() => Database.Checkpoint()).Start(); } - if (!Pending.TryDequeue(out var entry)) { + if (!Pending.TryDequeue(out var entry)) return; - } var contentId = Plugin.Functions.Chat.GetContentIdForEntry(entry.Item1); entry.Item2.ContentId = contentId ?? 0; - if (Plugin.Config.DatabaseBattleMessages || !entry.Item2.Code.IsBattle()) { + if (Plugin.Config.DatabaseBattleMessages || !entry.Item2.Code.IsBattle()) Messages.Update(entry.Item2); - } } - internal void AddMessage(Message message, Tab? currentTab) { - if (Plugin.Config.DatabaseBattleMessages || !message.Code.IsBattle()) { + internal void AddMessage(Message message, Tab? currentTab) + { + if (Plugin.Config.DatabaseBattleMessages || !message.Code.IsBattle()) Messages.Insert(message); - } var currentMatches = currentTab?.Matches(message) ?? false; - foreach (var tab in Plugin.Config.Tabs) { + foreach (var tab in Plugin.Config.Tabs) + { var unread = !(tab.UnreadMode == UnreadMode.Unseen && currentTab != tab && currentMatches); - if (tab.Matches(message)) { + if (tab.Matches(message)) tab.AddMessage(message, unread); - } } } - internal void FilterAllTabs(bool unread = true) { - foreach (var tab in Plugin.Config.Tabs) { + internal void FilterAllTabs(bool unread = true) + { + foreach (var tab in Plugin.Config.Tabs) FilterTab(tab, unread); - } } - internal void FilterTab(Tab tab, bool unread) { + internal void FilterTab(Tab tab, bool unread) + { var sortCodes = new List(); - foreach (var (type, sources) in tab.ChatCodes) { + foreach (var (type, sources) in tab.ChatCodes) + { sortCodes.Add(new SortCode(type, 0)); sortCodes.Add(new SortCode(type, (ChatSource) 1)); - if (type.HasSource()) { - foreach (var source in Enum.GetValues()) { - if (sources.HasFlag(source)) { - sortCodes.Add(new SortCode(type, source)); - } - } - } + if (!type.HasSource()) + continue; + + foreach (var source in Enum.GetValues()) + if (sources.HasFlag(source)) + sortCodes.Add(new SortCode(type, source)); } var query = Messages @@ -273,98 +289,101 @@ internal class Store : IDisposable { .OrderByDescending(msg => msg.Date) .Where(msg => sortCodes.Contains(msg.SortCode) || msg.ExtraChatChannel != Guid.Empty) .Where(msg => msg.Receiver == CurrentContentId); - if (!Plugin.Config.FilterIncludePreviousSessions) { - query = query.Where(msg => msg.Date >= Plugin.GameStarted); - } - var messages = query - .Limit(MessagesLimit) - .ToEnumerable() - .Reverse(); - foreach (var message in messages) { + if (!Plugin.Config.FilterIncludePreviousSessions) + query = query.Where(msg => msg.Date >= Plugin.GameStarted); + + var messages = query.Limit(MessagesLimit).ToEnumerable().Reverse(); + + foreach (var message in messages) + { + // check primarily for startup double posting messages + if (tab.Contains(message)) + continue; + // redundant matches check for extrachat - if (tab.Matches(message)) { + if (tab.Matches(message)) tab.AddMessage(message, unread); - } } } public (SeString? Sender, SeString? Message) LastMessage = (null, null); - private void ChatMessage(XivChatType type, uint senderId, SeString sender, SeString message) { + private void ChatMessage(XivChatType type, uint senderId, SeString sender, SeString message) + { var chatCode = new ChatCode((ushort) type); NameFormatting? formatting = null; - if (sender.Payloads.Count > 0) { + if (sender.Payloads.Count > 0) formatting = FormatFor(chatCode.Type); - } LastMessage = (sender, message); var senderChunks = new List(); - if (formatting is { IsPresent: true }) { - senderChunks.Add(new TextChunk(ChunkSource.None, null, formatting.Before) { + if (formatting is { IsPresent: true }) + { + senderChunks.Add(new TextChunk(ChunkSource.None, null, formatting.Before) + { FallbackColour = chatCode.Type, }); senderChunks.AddRange(ChunkUtil.ToChunks(sender, ChunkSource.Sender, chatCode.Type)); - senderChunks.Add(new TextChunk(ChunkSource.None, null, formatting.After) { + senderChunks.Add(new TextChunk(ChunkSource.None, null, formatting.After) + { FallbackColour = chatCode.Type, }); } var messageChunks = ChunkUtil.ToChunks(message, ChunkSource.Content, chatCode.Type).ToList(); + Plugin.Log.Information($"Adding Message with code {chatCode} timestamp {senderId} content {message.TextValue}"); var msg = new Message(CurrentContentId, chatCode, senderChunks, messageChunks, sender, message); AddMessage(msg, Plugin.ChatLogWindow.CurrentTab ?? null); var idx = Plugin.Functions.GetCurrentChatLogEntryIndex(); - if (idx != null) { + if (idx != null) Pending.Enqueue((idx.Value - 1, msg)); - } } - internal class NameFormatting { + internal class NameFormatting + { internal string Before { get; private set; } = string.Empty; internal string After { get; private set; } = string.Empty; internal bool IsPresent { get; private set; } = true; - internal static NameFormatting Empty() { - return new() { - IsPresent = false, - }; + internal static NameFormatting Empty() + { + return new NameFormatting { IsPresent = false, }; } - internal static NameFormatting Of(string before, string after) { - return new() { + internal static NameFormatting Of(string before, string after) + { + return new NameFormatting + { Before = before, After = after, }; } } - private NameFormatting? FormatFor(ChatType type) { - if (Formats.TryGetValue(type, out var cached)) { + private NameFormatting? FormatFor(ChatType type) + { + if (Formats.TryGetValue(type, out var cached)) return cached; - } var logKind = Plugin.DataManager.GetExcelSheet()!.GetRow((ushort) type); - - if (logKind == null) { + if (logKind == null) return null; - } var format = (SeString) logKind.Format; - - static bool IsStringParam(Payload payload, byte num) { + static bool IsStringParam(Payload payload, byte num) + { var data = payload.Encode(); - return data.Length >= 5 && data[1] == 0x29 && data[4] == num + 1; } var firstStringParam = format.Payloads.FindIndex(payload => IsStringParam(payload, 1)); var secondStringParam = format.Payloads.FindIndex(payload => IsStringParam(payload, 2)); - if (firstStringParam == -1 || secondStringParam == -1) { + if (firstStringParam == -1 || secondStringParam == -1) return NameFormatting.Empty(); - } var before = format.Payloads .GetRange(0, firstStringParam)