using System.Numerics; using ChatTwo.Resources; using ChatTwo.Util; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Windowing; using ImGuiNET; namespace ChatTwo.Ui; internal class LegacyMesasgeImporterWindow : Window { private readonly MessageStore _store; private LegacyMessageImporterEligibility Eligibility { get; set; } private LegacyMessageImporter? Importer { get; set; } internal LegacyMesasgeImporterWindow(MessageStore store) : base("Chat 2 Legacy Importer###chat2-legacy-importer") { _store = store; Eligibility = LegacyMessageImporterEligibility.CheckEligibility(); LogAndNotify(); } public void Dispose() { Importer?.Dispose(); } private void LogAndNotify() { Plugin.Log.Info( $"[Migration] Checked migration eligibility: {Eligibility.Status} - '{Eligibility.AdditionalIneligibilityInfo}'"); switch (Eligibility.Status) { case LegacyMessageImporterEligibilityStatus.Eligible: { var notification = Plugin.Notification.AddNotification(new Notification { Type = NotificationType.Info, // The user needs to dismiss this for it to go away. InitialDuration = TimeSpan.FromHours(6), Title = "Chat 2 Migration", Content = "Import messages from old database into new database? Click for more information.", }); // TODO: clicking does not dismiss notification.Click += _ => IsOpen = true; notification.Dismiss += _ => WriteChatMessage(); break; } case LegacyMessageImporterEligibilityStatus.IneligibleLiteDbFailed: { var notification = Plugin.Notification.AddNotification(new Notification { Type = NotificationType.Warning, InitialDuration = TimeSpan.FromMinutes(1), Title = "Chat Two Migration", Content = "Migration is not possible because the old database could not be opened. Click for more information." }); notification.Click += _ => IsOpen = true; notification.Dismiss += _ => WriteChatMessage(); break; } } } private void WriteChatMessage() { // TODO: write a message to chat saying how to open the window again // TODO: add a way of opening the window again, maybe a command or in // database settings } public override void Draw() { if (Importer != null) { DrawImportStatus(); return; } if (Eligibility.Status == LegacyMessageImporterEligibilityStatus.Eligible) DrawEligible(); else DrawIneligible(); } private void DrawEligible() { // TODO: pretty ImGui.Text("Import database messages from legacy LiteDB database to Sqlite database?"); ImGui.Text($"Message count: {Eligibility.MessageCount}"); ImGui.Text($"Database size: {Eligibility.DatabaseSizeBytes}"); if (ImGui.Button("Yes, import messages")) { // Next draw call will run DrawImportStatus(). Importer = Eligibility.StartImport(_store); return; } ImGui.SameLine(); if (ImGuiUtil.CtrlShiftButton("No, do not import messages", "Ctrl+Shift: renames old database to avoid prompting again")) { Eligibility.RenameOldDatabase(); IsOpen = false; } } private void DrawIneligible() { // TODO: pretty ImGui.Text("Your legacy LiteDB database is not eligible for import:"); switch (Eligibility.Status) { case LegacyMessageImporterEligibilityStatus.IneligibleOriginalDbNotExists: ImGui.Text("The old database could not be found."); break; case LegacyMessageImporterEligibilityStatus.IneligibleMigrationDbExists: ImGui.Text("The migration process was already started."); break; case LegacyMessageImporterEligibilityStatus.IneligibleLiteDbFailed: ImGui.Text("The old database could not be opened."); break; case LegacyMessageImporterEligibilityStatus.IneligibleNoMessages: ImGui.Text("The old database contains no messages."); break; case LegacyMessageImporterEligibilityStatus.Eligible: default: throw new ArgumentOutOfRangeException(); } if (!string.IsNullOrWhiteSpace(Eligibility.AdditionalIneligibilityInfo)) ImGui.Text(Eligibility.AdditionalIneligibilityInfo); // LiteDB failures notify the user, so give them a chance to rename the // database to avoid prompting again. if (Eligibility.Status == LegacyMessageImporterEligibilityStatus.IneligibleLiteDbFailed) { if (ImGuiUtil.CtrlShiftButton("Rename old database", "Ctrl+Shift: rename old database to avoid import prompt in the future")) { Eligibility.RenameOldDatabase(); // TODO: notify success as this changes the status } } } private void DrawImportStatus() { // TODO: pretty if (Importer == null) return; var importStart = Importer.ImportStart; var importEnd = Importer.ImportComplete; var total = Importer.ImportCount; var successful = Importer!.SuccessfulMessages; var failed = Importer.FailedMessages; var remaining = Importer.RemainingMessages; if (importEnd != null) { ImGui.Text($"Completed migration in {Duration(importStart, importEnd.Value)}"); ImGui.Text($"Successfully imported: {successful} messages"); ImGui.Text($"Failed to import: {failed} messages"); ImGui.Text($"Unaccounted for: {remaining}"); ImGui.Text("See logs for more details: /xllog"); return; } // TODO: implement Importer.MaxMessageRate slider in UI, values 0 (infinity) => 10000 ImGui.Text($"Importing messages... {Importer.Progress:P}%"); ImGui.Text($"Duration: {Duration(importStart, Environment.TickCount64)}"); ImGui.Text($"Successfully imported: {successful} messages"); ImGui.Text($"Failed to import: {failed} messages"); ImGui.Text($"Progress: {Importer.ProcessedMessages}/{total} messages"); ImGui.Text($"Remaining: {remaining} messages"); ImGui.Text($"Messages per second: {Importer.CurrentMessageRate}"); ImGui.Text($"Estimated time remaining: {Importer.EstimatedTimeRemaining}"); ImGui.Text("See logs for more details: /xllog"); // TODO: this doesn't render properly ImGui.ProgressBar(Importer.Progress, new Vector2(0.0f, 0.0f), $"{Importer.Progress:P}%"); if (ImGuiUtil.CtrlShiftButton("Cancel import", "Ctrl+Shift: cancel import and close window")) { // TODO: This currently crashes the whole game because we don't ask // the importer thread to stop and wait for it to stop before // disposing it. // See LegacyMessageImporter.Dispose() for more details. /* Importer.Dispose(); Importer = null; Eligibility = LegacyMessageImporterEligibility.CheckEligibility(); */ } } private static TimeSpan Duration(long startTicks, long endTicks) { return endTicks < startTicks ? TimeSpan.Zero : TimeSpan.FromTicks(endTicks - startTicks); } }