diff --git a/ChatTwo/Resources/Language.Designer.cs b/ChatTwo/Resources/Language.Designer.cs index 01c8b87..63cc8e5 100755 --- a/ChatTwo/Resources/Language.Designer.cs +++ b/ChatTwo/Resources/Language.Designer.cs @@ -1634,6 +1634,33 @@ namespace ChatTwo.Resources { } } + /// + /// Looks up a localized string similar to Clear the message history database. + /// + internal static string Options_ClearDatabase_Button { + get { + return ResourceManager.GetString("Options_ClearDatabase_Button", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Successfully cleared the chat database. + /// + internal static string Options_ClearDatabase_Success { + get { + return ResourceManager.GetString("Options_ClearDatabase_Success", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes all message history. Cannot be restored. Hold Ctrl+Shift to click.. + /// + internal static string Options_ClearDatabase_Tooltip { + get { + return ResourceManager.GetString("Options_ClearDatabase_Tooltip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Replace consecutive duplicate messages with a counter appended to the first instance of the message.. /// @@ -1688,6 +1715,69 @@ namespace ChatTwo.Resources { } } + /// + /// Looks up a localized string similar to Click to copy database directory path. + /// + internal static string Options_Database_Metadata_CopyConfigPath { + get { + return ResourceManager.GetString("Options_Database_Metadata_CopyConfigPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copied database directory path to clipboard. + /// + internal static string Options_Database_Metadata_CopyConfigPathNotification { + get { + return ResourceManager.GetString("Options_Database_Metadata_CopyConfigPathNotification", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Database details:. + /// + internal static string Options_Database_Metadata_Heading { + get { + return ResourceManager.GetString("Options_Database_Metadata_Heading", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Log size: {0}. + /// + internal static string Options_Database_Metadata_LogSize { + get { + return ResourceManager.GetString("Options_Database_Metadata_LogSize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stored messages: {0:N0}/{1:N0}. + /// + internal static string Options_Database_Metadata_MessageCount { + get { + return ResourceManager.GetString("Options_Database_Metadata_MessageCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Path: {0}. + /// + internal static string Options_Database_Metadata_Path { + get { + return ResourceManager.GetString("Options_Database_Metadata_Path", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size: {0}. + /// + internal static string Options_Database_Metadata_Size { + get { + return ResourceManager.GetString("Options_Database_Metadata_Size", resourceCulture); + } + } + /// /// Looks up a localized string similar to Database. /// diff --git a/ChatTwo/Resources/Language.resx b/ChatTwo/Resources/Language.resx index da1ea88..f29ecc8 100644 --- a/ChatTwo/Resources/Language.resx +++ b/ChatTwo/Resources/Language.resx @@ -952,4 +952,34 @@ Hide {0} during loading screens. + + Clear the message history database + + + Successfully cleared the chat database + + + Removes all message history. Cannot be restored. Hold Ctrl+Shift to click. + + + Click to copy database directory path + + + Copied database directory path to clipboard + + + Database details: + + + Log size: {0} + + + Stored messages: {0:N0}/{1:N0} + + + Path: {0} + + + Size: {0} + diff --git a/ChatTwo/Store.cs b/ChatTwo/Store.cs index 60a6209..d2695d9 100755 --- a/ChatTwo/Store.cs +++ b/ChatTwo/Store.cs @@ -140,9 +140,6 @@ internal class Store : IDisposable { bson => DateTime.UnixEpoch.AddMilliseconds(bson.AsInt64) ); Database = Connect(); - Messages.EnsureIndex(msg => msg.Date); - Messages.EnsureIndex(msg => msg.SortCode); - Messages.EnsureIndex(msg => msg.ExtraChatChannel); Plugin.ChatGui.ChatMessageUnhandled += ChatMessage; Plugin.Framework.Update += GetMessageInfo; @@ -159,17 +156,26 @@ internal class Store : IDisposable { Database.Dispose(); } - private ILiteDatabase Connect() { + internal static string DatabasePath() + { var dir = Plugin.Interface.ConfigDirectory; dir.Create(); + return Path.Join(dir.FullName, "chat.db"); + } - var dbPath = Path.Join(dir.FullName, "chat.db"); + private LiteDatabase Connect() { + var dbPath = DatabasePath(); var connection = Plugin.Config.SharedMode ? "shared" : "direct"; var connString = $"Filename='{dbPath}';Connection={connection}"; - return new LiteDatabase(connString, BsonMapper.Global) { + var conn = new LiteDatabase(connString, BsonMapper.Global) { CheckpointSize = 1_000, Timeout = TimeSpan.FromSeconds(1), }; + var messages = conn.GetCollection("messages"); + messages.EnsureIndex(msg => msg.Date); + messages.EnsureIndex(msg => msg.SortCode); + messages.EnsureIndex(msg => msg.ExtraChatChannel); + return conn; } internal void Reconnect() { @@ -177,6 +183,26 @@ internal class Store : IDisposable { Database = Connect(); } + internal void ClearDatabase() + { + Messages.DeleteAll(); + Database.Rebuild(); + } + + internal static long DatabaseSize() + { + var dbPath = DatabasePath(); + return !File.Exists(dbPath) ? 0 : new FileInfo(dbPath).Length; + } + + internal static long DatabaseLogSize() + { + var dbLogPath = Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat-log.db"); + return !File.Exists(dbLogPath) ? 0 : new FileInfo(dbLogPath).Length; + } + + internal int MessageCount() => Messages.Count(); + private void Logout() { LastContentId = 0; } diff --git a/ChatTwo/Ui/Settings.cs b/ChatTwo/Ui/Settings.cs index d4ae5d4..436a631 100755 --- a/ChatTwo/Ui/Settings.cs +++ b/ChatTwo/Ui/Settings.cs @@ -35,7 +35,7 @@ public sealed class SettingsWindow : Window, IUiComponent new Ui.SettingsTabs.Fonts(Mutable), new ChatColours(Mutable, Plugin), new Tabs(Plugin, Mutable), - new Database(Mutable, Plugin.Store), + new Database(Mutable, Plugin), new Miscellaneous(Mutable), new About(), }; diff --git a/ChatTwo/Ui/SettingsTabs/Database.cs b/ChatTwo/Ui/SettingsTabs/Database.cs index f0814f5..95d4f66 100755 --- a/ChatTwo/Ui/SettingsTabs/Database.cs +++ b/ChatTwo/Ui/SettingsTabs/Database.cs @@ -1,22 +1,28 @@ using ChatTwo.Resources; using ChatTwo.Util; +using Dalamud.Interface.Internal.Notifications; using ImGuiNET; namespace ChatTwo.Ui.SettingsTabs; internal sealed class Database : ISettingsTab { private Configuration Mutable { get; } - private Store Store { get; } + private Plugin Plugin { get; } public string Name => Language.Options_Database_Tab + "###tabs-database"; - internal Database(Configuration mutable, Store store) { - Store = store; + internal Database(Configuration mutable, Plugin plugin) { + Plugin = plugin; Mutable = mutable; } private bool _showAdvanced; + private long _databaseLastRefreshTicks; + private long _databaseSize; + private long _databaseLogSize; + private int _databaseMessageCount; + public void Draw(bool changed) { if (changed) { _showAdvanced = ImGui.GetIO().KeyShift; @@ -46,18 +52,88 @@ internal sealed class Database : ISettingsTab { ); ImGuiUtil.WarningText(string.Format(Language.Options_SharedMode_Warning, Plugin.PluginName)); + ImGui.Spacing(); + ImGui.Separator(); ImGui.Spacing(); - if (_showAdvanced && ImGui.TreeNodeEx(Language.Options_Database_Advanced)) { + ImGui.Text(Language.Options_Database_Metadata_Heading); + var style = ImGui.GetStyle(); + ImGui.Indent(style.IndentSpacing); + + // Refresh the database size and message count every 5 seconds to avoid + // constant stat calls and spamming the database. + if (_databaseLastRefreshTicks + 5 * 1000 < Environment.TickCount64) + { + _databaseSize = Store.DatabaseSize(); + _databaseLogSize = Store.DatabaseLogSize(); + _databaseMessageCount = Plugin.Store.MessageCount(); + _databaseLastRefreshTicks = Environment.TickCount64; + } + + ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_Path, Store.DatabasePath())); + if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) + { + // Copy the directory path instead of the file path so people can + // paste it into their file explorer. + var path = Path.GetDirectoryName(Store.DatabasePath()); + ImGui.SetClipboardText(path); + WrapperUtil.AddNotification(Language.Options_Database_Metadata_CopyConfigPathNotification, NotificationType.Info); + } + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + ImGui.BeginTooltip(); + ImGui.Text(Language.Options_Database_Metadata_CopyConfigPath); + ImGui.EndTooltip(); + } + + ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_Size, StringUtil.BytesToString(_databaseSize))); + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.Text(_databaseSize.ToString("N0") + "B"); + ImGui.EndTooltip(); + } + + ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_LogSize, StringUtil.BytesToString(_databaseLogSize))); + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.Text(_databaseLogSize.ToString("N0") + "B"); + ImGui.EndTooltip(); + } + + ImGuiUtil.HelpText(string.Format(Language.Options_Database_Metadata_MessageCount, _databaseMessageCount, Store.MessagesLimit)); + + if (ImGuiUtil.CtrlShiftButton(Language.Options_ClearDatabase_Button, Language.Options_ClearDatabase_Tooltip)) + { + Plugin.Log.Warning("Clearing database"); + Plugin.Store.ClearDatabase(); + foreach (var tab in Plugin.Config.Tabs) + { + tab.Clear(); + } + // Refresh on next draw + _databaseLastRefreshTicks = 0; + WrapperUtil.AddNotification(Language.Options_ClearDatabase_Success, NotificationType.Info); + } + + ImGui.Unindent(style.IndentSpacing); + ImGui.Spacing(); + + if (_showAdvanced && ImGui.TreeNodeEx(Language.Options_Database_Advanced)) + { ImGui.PushTextWrapPos(); ImGuiUtil.WarningText(Language.Options_Database_Advanced_Warning); - if (ImGui.Button("Checkpoint")) { - Store.Database.Checkpoint(); + if (ImGuiUtil.CtrlShiftButton("Checkpoint", "Ctrl+Shift: Database.Checkpoint()")) + { + Plugin.Store.Database.Checkpoint(); } - if (ImGui.Button("Rebuild")) { - Store.Database.Rebuild(); + if (ImGuiUtil.CtrlShiftButton("Rebuild", "Ctrl+Shift: Database.Rebuild()")) + { + Plugin.Store.Database.Rebuild(); } ImGui.PopTextWrapPos(); diff --git a/ChatTwo/Util/ImGuiUtil.cs b/ChatTwo/Util/ImGuiUtil.cs index 31c4efc..ffb1534 100755 --- a/ChatTwo/Util/ImGuiUtil.cs +++ b/ChatTwo/Util/ImGuiUtil.cs @@ -234,6 +234,22 @@ internal static class ImGuiUtil { return r; } + internal static bool CtrlShiftButton(string label, string tooltip = "") + { + var io = ImGui.GetIO(); + var ctrlShiftHeld = io.KeyCtrl && io.KeyShift; + if (!ctrlShiftHeld) ImGui.BeginDisabled(); + var ret = ImGui.Button(label) && ctrlShiftHeld; + if (!ctrlShiftHeld) ImGui.EndDisabled(); + if (!string.IsNullOrEmpty(tooltip) && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { + ImGui.BeginTooltip(); + ImGui.TextUnformatted(tooltip); + ImGui.EndTooltip(); + } + + return ret; + } + internal static bool TryToImGui(this VirtualKey key, out ImGuiKey result) { result = key switch { VirtualKey.NO_KEY => ImGuiKey.None, diff --git a/ChatTwo/Util/StringUtil.cs b/ChatTwo/Util/StringUtil.cs index b92fb62..d54456e 100755 --- a/ChatTwo/Util/StringUtil.cs +++ b/ChatTwo/Util/StringUtil.cs @@ -10,4 +10,16 @@ internal static class StringUtil { bytes[^1] = 0; return bytes; } + + // Taken from https://stackoverflow.com/a/4975942 + internal static String BytesToString(long byteCount) { + string[] suf = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]; // Longs run out around EB + if (byteCount == 0) + return "0" + suf[0]; + + var bytes = Math.Abs(byteCount); + var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + var num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(byteCount) * num).ToString("N0") + suf[place]; + } }