diff --git a/Pal.Client/Configuration/ConfigurationManager.cs b/Pal.Client/Configuration/ConfigurationManager.cs index 2e8e284..2fae187 100644 --- a/Pal.Client/Configuration/ConfigurationManager.cs +++ b/Pal.Client/Configuration/ConfigurationManager.cs @@ -7,6 +7,8 @@ using System.Text.Json; using Dalamud.Logging; using Dalamud.Plugin; using ImGuiNET; +using Microsoft.Extensions.DependencyInjection; +using Pal.Client.Database; using NJson = Newtonsoft.Json; namespace Pal.Client.Configuration @@ -14,12 +16,14 @@ namespace Pal.Client.Configuration internal sealed class ConfigurationManager { private readonly DalamudPluginInterface _pluginInterface; + private readonly IServiceProvider _serviceProvider; public event EventHandler? Saved; - public ConfigurationManager(DalamudPluginInterface pluginInterface) + public ConfigurationManager(DalamudPluginInterface pluginInterface, IServiceProvider serviceProvider) { _pluginInterface = pluginInterface; + _serviceProvider = serviceProvider; Migrate(); } @@ -61,6 +65,26 @@ namespace Pal.Client.Configuration var v7 = MigrateToV7(configurationV1); Save(v7, queue: false); + using (var scope = _serviceProvider.CreateScope()) + { + using var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Imports.RemoveRange(dbContext.Imports); + + foreach (var importHistory in configurationV1.ImportHistory) + { + PluginLog.Information($"Migrating import {importHistory.Id}"); + dbContext.Imports.Add(new ImportHistory + { + Id = importHistory.Id, + RemoteUrl = importHistory.RemoteUrl?.Replace(".μ.tv", ".liza.sh"), + ExportedAt = importHistory.ExportedAt, + ImportedAt = importHistory.ImportedAt + }); + } + + dbContext.SaveChanges(); + } + File.Move(_pluginInterface.ConfigFile.FullName, _pluginInterface.ConfigFile.FullName + ".old", true); } } diff --git a/Pal.Client/Configuration/IPalacePalConfiguration.cs b/Pal.Client/Configuration/IPalacePalConfiguration.cs index 2752fce..c8a1ee8 100644 --- a/Pal.Client/Configuration/IPalacePalConfiguration.cs +++ b/Pal.Client/Configuration/IPalacePalConfiguration.cs @@ -23,9 +23,6 @@ namespace Pal.Client.Configuration DeepDungeonConfiguration DeepDungeons { get; set; } RendererConfiguration Renderer { get; set; } - [Obsolete] - List ImportHistory { get; } - IAccountConfiguration CreateAccount(string server, Guid accountId); IAccountConfiguration? FindAccount(string server); void RemoveAccount(string server); diff --git a/Pal.Client/Database/ImportHistory.cs b/Pal.Client/Database/ImportHistory.cs new file mode 100644 index 0000000..33e74f4 --- /dev/null +++ b/Pal.Client/Database/ImportHistory.cs @@ -0,0 +1,12 @@ +using System; + +namespace Pal.Client.Database +{ + internal sealed class ImportHistory + { + public Guid Id { get; set; } + public string? RemoteUrl { get; set; } + public DateTime ExportedAt { get; set; } + public DateTime ImportedAt { get; set; } + } +} diff --git a/Pal.Client/Database/Migrations/20230216154417_AddImportHistory.Designer.cs b/Pal.Client/Database/Migrations/20230216154417_AddImportHistory.Designer.cs new file mode 100644 index 0000000..c65bf7e --- /dev/null +++ b/Pal.Client/Database/Migrations/20230216154417_AddImportHistory.Designer.cs @@ -0,0 +1,45 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Pal.Client.Database; + +#nullable disable + +namespace Pal.Client.Database.Migrations +{ + [DbContext(typeof(PalClientContext))] + [Migration("20230216154417_AddImportHistory")] + partial class AddImportHistory + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.3"); + + modelBuilder.Entity("Pal.Client.Database.ImportHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ExportedAt") + .HasColumnType("TEXT"); + + b.Property("ImportedAt") + .HasColumnType("TEXT"); + + b.Property("RemoteUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Imports"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Pal.Client/Database/Migrations/20230216154417_AddImportHistory.cs b/Pal.Client/Database/Migrations/20230216154417_AddImportHistory.cs new file mode 100644 index 0000000..065048a --- /dev/null +++ b/Pal.Client/Database/Migrations/20230216154417_AddImportHistory.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Pal.Client.Database.Migrations +{ + /// + public partial class AddImportHistory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Imports", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + RemoteUrl = table.Column(type: "TEXT", nullable: true), + ExportedAt = table.Column(type: "TEXT", nullable: false), + ImportedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Imports", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Imports"); + } + } +} diff --git a/Pal.Client/Database/Migrations/PalClientContextModelSnapshot.cs b/Pal.Client/Database/Migrations/PalClientContextModelSnapshot.cs new file mode 100644 index 0000000..d6be610 --- /dev/null +++ b/Pal.Client/Database/Migrations/PalClientContextModelSnapshot.cs @@ -0,0 +1,42 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Pal.Client.Database; + +#nullable disable + +namespace Pal.Client.Database.Migrations +{ + [DbContext(typeof(PalClientContext))] + partial class PalClientContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.3"); + + modelBuilder.Entity("Pal.Client.Database.ImportHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ExportedAt") + .HasColumnType("TEXT"); + + b.Property("ImportedAt") + .HasColumnType("TEXT"); + + b.Property("RemoteUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Imports"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Pal.Client/Database/PalClientContext.cs b/Pal.Client/Database/PalClientContext.cs new file mode 100644 index 0000000..b1c65e3 --- /dev/null +++ b/Pal.Client/Database/PalClientContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; + +namespace Pal.Client.Database +{ + internal class PalClientContext : DbContext + { + public DbSet Imports { get; set; } = null!; + + public PalClientContext(DbContextOptions options) + : base(options) + { + } + } +} diff --git a/Pal.Client/Database/PalClientContextFactory.cs b/Pal.Client/Database/PalClientContextFactory.cs new file mode 100644 index 0000000..30124b9 --- /dev/null +++ b/Pal.Client/Database/PalClientContextFactory.cs @@ -0,0 +1,20 @@ +#if EF +using System; +using System.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Pal.Client.Database +{ + internal sealed class PalClientContextFactory : IDesignTimeDbContextFactory + { + public PalClientContext CreateDbContext(string[] args) + { + var optionsBuilder = + new DbContextOptionsBuilder().UseSqlite( + $"Data Source={Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "pluginConfigs", "Palace Pal", "palace-pal.data.sqlite3")}"); + return new PalClientContext(optionsBuilder.Options); + } + } +} +#endif diff --git a/Pal.Client/DependencyInjection/ImportService.cs b/Pal.Client/DependencyInjection/ImportService.cs new file mode 100644 index 0000000..a3c17ef --- /dev/null +++ b/Pal.Client/DependencyInjection/ImportService.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Pal.Client.Database; + +namespace Pal.Client.DependencyInjection +{ + internal sealed class ImportService + { + private readonly IServiceProvider _serviceProvider; + + public ImportService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public void Add(ImportHistory history) + { + using var scope = _serviceProvider.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); + + dbContext.Imports.Add(history); + dbContext.SaveChanges(); + } + + public ImportHistory? FindLast() + { + using var scope = _serviceProvider.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); + + return dbContext.Imports.OrderByDescending(x => x.ImportedAt).ThenBy(x => x.Id).FirstOrDefault(); + } + + public List FindForServer(string server) + { + if (string.IsNullOrEmpty(server)) + return new(); + + using var scope = _serviceProvider.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); + + return dbContext.Imports.Where(x => x.RemoteUrl == server).ToList(); + } + + public void RemoveAllByIds(List ids) + { + using var scope = _serviceProvider.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); + + dbContext.RemoveRange(dbContext.Imports.Where(x => ids.Contains(x.Id))); + dbContext.SaveChanges(); + } + + public void RemoveById(Guid id) + => RemoveAllByIds(new List { id }); + } +} diff --git a/Pal.Client/DependencyInjection/DependencyInjectionContext.cs b/Pal.Client/DependencyInjectionContext.cs similarity index 57% rename from Pal.Client/DependencyInjection/DependencyInjectionContext.cs rename to Pal.Client/DependencyInjectionContext.cs index 6d2812f..7d4bbb1 100644 --- a/Pal.Client/DependencyInjection/DependencyInjectionContext.cs +++ b/Pal.Client/DependencyInjectionContext.cs @@ -1,4 +1,8 @@ -using System.Globalization; +using System; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; using Dalamud.Data; using Dalamud.Game; using Dalamud.Game.ClientState; @@ -7,17 +11,22 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Game.Command; using Dalamud.Game.Gui; using Dalamud.Interface.Windowing; +using Dalamud.Logging; using Dalamud.Plugin; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Pal.Client.Commands; using Pal.Client.Configuration; +using Pal.Client.Database; +using Pal.Client.DependencyInjection; using Pal.Client.Net; using Pal.Client.Properties; using Pal.Client.Rendering; using Pal.Client.Scheduled; using Pal.Client.Windows; -namespace Pal.Client.DependencyInjection +namespace Pal.Client { /// /// DI-aware Plugin. @@ -25,6 +34,8 @@ namespace Pal.Client.DependencyInjection // ReSharper disable once UnusedType.Global internal sealed class DependencyInjectionContext : IDalamudPlugin { + private readonly string _sqliteConnectionString; + private readonly CancellationTokenSource _initCts = new(); private ServiceProvider? _serviceProvider; public string Name => Localization.Palace_Pal; @@ -39,6 +50,9 @@ namespace Pal.Client.DependencyInjection CommandManager commandManager, DataManager dataManager) { + PluginLog.Information("Building service container"); + + CancellationToken token = _initCts.Token; IServiceCollection services = new ServiceCollection(); // dalamud @@ -54,6 +68,11 @@ namespace Pal.Client.DependencyInjection services.AddSingleton(dataManager); services.AddSingleton(new WindowSystem(typeof(DependencyInjectionContext).AssemblyQualifiedName)); + // EF core + _sqliteConnectionString = + $"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), "palace-pal.data.sqlite3")}"; + services.AddDbContext(o => o.UseSqlite(_sqliteConnectionString)); + // plugin-specific services.AddSingleton(); services.AddSingleton(); @@ -64,11 +83,12 @@ namespace Pal.Client.DependencyInjection services.AddTransient(); services.AddSingleton(); - // territory handling + // territory & marker related services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // windows & related services services.AddSingleton(); @@ -97,7 +117,7 @@ namespace Pal.Client.DependencyInjection ValidateScopes = true, }); - // initialize plugin + #if RELEASE // You're welcome to remove this code in your fork, but please make sure that: // - none of the links accessible within FFXIV open the original repo (e.g. in the plugin installer), and @@ -108,24 +128,61 @@ namespace Pal.Client.DependencyInjection _serviceProvider.GetService(); #endif - // set up legacy services - LocalState.PluginInterface = pluginInterface; - LocalState.Mode = _serviceProvider.GetRequiredService().Mode; + // This is not ideal as far as loading the plugin goes, because there's no way to check for errors and + // tell Dalamud that no, the plugin isn't ready -- so the plugin will count as properly initialized, + // even if it's not. + // + // There's 2-3 seconds of slowdown primarily caused by the sqlite init, but that needs to happen for + // config stuff. + PluginLog.Information("Service container built, triggering async init"); + Task.Run(async () => + { + try + { + PluginLog.Information("Starting async init"); - // windows that have logic to open on startup - _serviceProvider.GetRequiredService(); + // initialize database + await using (var scope = _serviceProvider.CreateAsyncScope()) + { + PluginLog.Log("Loading database & running migrations"); + await using var dbContext = scope.ServiceProvider.GetRequiredService(); + await dbContext.Database.MigrateAsync(); - // initialize components that are mostly self-contained/self-registered - _serviceProvider.GetRequiredService(); - _serviceProvider.GetRequiredService(); - _serviceProvider.GetRequiredService(); - _serviceProvider.GetRequiredService(); + PluginLog.Log("Completed database migrations"); + } - _serviceProvider.GetRequiredService(); + token.ThrowIfCancellationRequested(); + + // set up legacy services + LocalState.PluginConfigDirectory = pluginInterface.GetPluginConfigDirectory(); + LocalState.Mode = _serviceProvider.GetRequiredService().Mode; + + // windows that have logic to open on startup + _serviceProvider.GetRequiredService(); + + // initialize components that are mostly self-contained/self-registered + _serviceProvider.GetRequiredService(); + _serviceProvider.GetRequiredService(); + _serviceProvider.GetRequiredService(); + _serviceProvider.GetRequiredService(); + + token.ThrowIfCancellationRequested(); + _serviceProvider.GetRequiredService(); + + PluginLog.Information("Async init complete"); + } + catch (Exception e) + { + PluginLog.Error(e, "Async load failed"); + chatGui.PrintError($"Async loading failed: {e}"); + } + }); } public void Dispose() { + _initCts.Cancel(); + // ensure we're not calling dispose recursively on ourselves if (_serviceProvider != null) { @@ -133,6 +190,10 @@ namespace Pal.Client.DependencyInjection _serviceProvider = null; serviceProvider.Dispose(); + + // ensure we're not keeping the file open longer than the plugin is loaded + using (SqliteConnection sqliteConnection = new(_sqliteConnectionString)) + SqliteConnection.ClearPool(sqliteConnection); } } } diff --git a/Pal.Client/LocalState.cs b/Pal.Client/LocalState.cs index 41a125d..8534c50 100644 --- a/Pal.Client/LocalState.cs +++ b/Pal.Client/LocalState.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; -using Dalamud.Plugin; using Pal.Client.Configuration; using Pal.Client.Extensions; @@ -19,7 +18,7 @@ namespace Pal.Client private static readonly JsonSerializerOptions JsonSerializerOptions = new() { IncludeFields = true }; private const int CurrentVersion = 4; - internal static DalamudPluginInterface PluginInterface { get; set; } + internal static string PluginConfigDirectory { get; set; } = null!; internal static EMode Mode { get; set; } public uint TerritoryType { get; set; } @@ -126,7 +125,7 @@ namespace Pal.Client public string GetSaveLocation() => GetSaveLocation(TerritoryType); - private static string GetSaveLocation(uint territoryType) => Path.Join(PluginInterface.GetPluginConfigDirectory(), $"{territoryType}.json"); + private static string GetSaveLocation(uint territoryType) => Path.Join(PluginConfigDirectory, $"{territoryType}.json"); public static void ForEach(Action action) { diff --git a/Pal.Client/Pal.Client.csproj b/Pal.Client/Pal.Client.csproj index fd6869a..2edf178 100644 --- a/Pal.Client/Pal.Client.csproj +++ b/Pal.Client/Pal.Client.csproj @@ -25,15 +25,11 @@ - + - - - ResXFileCodeGenerator - Localization.Designer.cs - + @@ -68,44 +64,51 @@ $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll - false + false $(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll - false + false $(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll - false + false $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll - false + false $(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll - false + false $(AppData)\XIVLauncher\addon\Hooks\dev\Newtonsoft.Json.dll - false + false $(AppData)\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll - false + false - - True - True - Localization.resx - + + ResXFileCodeGenerator + Localization.Designer.cs + + + True + True + Localization.resx + - + + + + diff --git a/Pal.Client/Palace Pal.json b/Pal.Client/Palace Pal.json index 77ce336..79e7ff4 100644 --- a/Pal.Client/Palace Pal.json +++ b/Pal.Client/Palace Pal.json @@ -6,4 +6,4 @@ "RepoUrl": "https://github.com/carvelli/PalacePal", "IconUrl": "https://raw.githubusercontent.com/carvelli/Dalamud-Plugins/master/dist/Palace Pal.png", "Tags": [ "potd", "palace", "hoh", "splatoon" ] -} \ No newline at end of file +} diff --git a/Pal.Client/Plugin.cs b/Pal.Client/Plugin.cs index ddd2399..74cb8f4 100644 --- a/Pal.Client/Plugin.cs +++ b/Pal.Client/Plugin.cs @@ -5,7 +5,6 @@ using Pal.Client.Windows; using System; using System.Globalization; using System.Linq; -using Dalamud.Logging; using Pal.Client.Properties; using ECommons; using Microsoft.Extensions.DependencyInjection; @@ -31,8 +30,6 @@ namespace Pal.Client IPalacePalConfiguration configuration, RenderAdapter renderAdapter) { - PluginLog.Information("Initializing Palace Pal"); - _serviceProvider = serviceProvider; _pluginInterface = pluginInterface; _configuration = configuration; diff --git a/Pal.Client/README.md b/Pal.Client/README.md new file mode 100644 index 0000000..52bd208 --- /dev/null +++ b/Pal.Client/README.md @@ -0,0 +1,15 @@ +# Palace Pal + +## Client Build Notes + +### Database Migrations + +Since EF core needs all dll files to be present, including Dalamud ones, +there's a special `EF` configuration that exempts them from setting +`false` during the build. + +To use with `dotnet ef` commands, specify it as `-c EF`, for example: + +```shell +dotnet ef migrations add MigrationName --configuration EF +``` diff --git a/Pal.Client/Scheduled/QueuedImport.cs b/Pal.Client/Scheduled/QueuedImport.cs index ce13b30..afa99bf 100644 --- a/Pal.Client/Scheduled/QueuedImport.cs +++ b/Pal.Client/Scheduled/QueuedImport.cs @@ -7,10 +7,11 @@ using System.Linq; using System.Numerics; using Dalamud.Game.Gui; using Dalamud.Logging; -using Pal.Client.Configuration; +using Pal.Client.Database; using Pal.Client.DependencyInjection; using Pal.Client.Extensions; using Pal.Client.Properties; +using Pal.Client.Windows; namespace Pal.Client.Scheduled { @@ -30,17 +31,20 @@ namespace Pal.Client.Scheduled internal sealed class Handler : IQueueOnFrameworkThread.Handler { private readonly ChatGui _chatGui; - private readonly IPalacePalConfiguration _configuration; - private readonly ConfigurationManager _configurationManager; private readonly FloorService _floorService; + private readonly ImportService _importService; + private readonly ConfigWindow _configWindow; - public Handler(ChatGui chatGui, IPalacePalConfiguration configuration, - ConfigurationManager configurationManager, FloorService floorService) + public Handler( + ChatGui chatGui, + FloorService floorService, + ImportService importService, + ConfigWindow configWindow) { _chatGui = chatGui; - _configuration = configuration; - _configurationManager = configurationManager; _floorService = floorService; + _importService = importService; + _configWindow = configWindow; } protected override void Run(QueuedImport import, ref bool recreateLayout, ref bool saveMarkers) @@ -53,11 +57,9 @@ namespace Pal.Client.Scheduled if (!Validate(import)) return; - var oldExportIds = string.IsNullOrEmpty(import.Export.ServerUrl) - ? _configuration.ImportHistory.Where(x => x.RemoteUrl == import.Export.ServerUrl) - .Select(x => x.Id) - .Where(x => x != Guid.Empty).ToList() - : new List(); + List oldExportIds = _importService.FindForServer(import.Export.ServerUrl) + .Select(x => x.Id) + .ToList(); foreach (var remoteFloor in import.Export.Floors) { @@ -70,17 +72,19 @@ namespace Pal.Client.Scheduled localState.Save(); } - _configuration.ImportHistory.RemoveAll(hist => - oldExportIds.Contains(hist.Id) || hist.Id == import.ExportId); - _configuration.ImportHistory.Add(new ConfigurationV1.ImportHistoryEntry + _importService.RemoveAllByIds(oldExportIds); + _importService.RemoveById(import.ExportId); + _importService.Add(new ImportHistory { Id = import.ExportId, RemoteUrl = import.Export.ServerUrl, ExportedAt = import.Export.CreatedAt.ToDateTime(), ImportedAt = DateTime.UtcNow, }); - _configurationManager.Save(_configuration); + _configWindow.UpdateLastImport(); + PluginLog.Information( + $"Imported {import.ExportId} for {import.ImportedTraps} traps, {import.ImportedHoardCoffers} hoard coffers"); _chatGui.PalMessage(string.Format(Localization.ImportCompleteStatistics, import.ImportedTraps, import.ImportedHoardCoffers)); } @@ -101,9 +105,9 @@ namespace Pal.Client.Scheduled return false; } - if (!Guid.TryParse(import.Export.ExportId, out Guid exportId) || import.ExportId == Guid.Empty) + if (!Guid.TryParse(import.Export.ExportId, out Guid exportId) || exportId == Guid.Empty) { - PluginLog.Error("Import: Invalid export id"); + PluginLog.Error($"Import: Invalid export id ({import.Export.ExportId})"); _chatGui.PalError(Localization.Error_ImportFailed_InvalidFile); return false; } diff --git a/Pal.Client/Scheduled/QueuedUndoImport.cs b/Pal.Client/Scheduled/QueuedUndoImport.cs index 37eb4c2..61b82b4 100644 --- a/Pal.Client/Scheduled/QueuedUndoImport.cs +++ b/Pal.Client/Scheduled/QueuedUndoImport.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Pal.Client.Configuration; using Pal.Client.DependencyInjection; +using Pal.Client.Windows; using Pal.Common; namespace Pal.Client.Scheduled @@ -17,13 +18,15 @@ namespace Pal.Client.Scheduled internal sealed class Handler : IQueueOnFrameworkThread.Handler { - private readonly IPalacePalConfiguration _configuration; + private readonly ImportService _importService; private readonly FloorService _floorService; + private readonly ConfigWindow _configWindow; - public Handler(IPalacePalConfiguration configuration, FloorService floorService) + public Handler(ImportService importService, FloorService floorService, ConfigWindow configWindow) { - _configuration = configuration; + _importService = importService; _floorService = floorService; + _configWindow = configWindow; } protected override void Run(QueuedUndoImport queued, ref bool recreateLayout, ref bool saveMarkers) @@ -38,7 +41,8 @@ namespace Pal.Client.Scheduled localState.Save(); } - _configuration.ImportHistory.RemoveAll(hist => hist.Id == queued.ExportId); + _importService.RemoveById(queued.ExportId); + _configWindow.UpdateLastImport(); } } } diff --git a/Pal.Client/Windows/ConfigWindow.cs b/Pal.Client/Windows/ConfigWindow.cs index 67b1e71..bd0b00f 100644 --- a/Pal.Client/Windows/ConfigWindow.cs +++ b/Pal.Client/Windows/ConfigWindow.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using Dalamud.Game.Gui; using Pal.Client.Properties; using Pal.Client.Configuration; +using Pal.Client.Database; using Pal.Client.DependencyInjection; using Pal.Client.Extensions; @@ -40,6 +41,7 @@ namespace Pal.Client.Windows private readonly DebugState _debugState; private readonly ChatGui _chatGui; private readonly RemoteApi _remoteApi; + private readonly ImportService _importService; private int _mode; private int _renderer; @@ -55,6 +57,7 @@ namespace Pal.Client.Windows private string? _saveExportDialogStartPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); private readonly FileDialogManager _importDialog; private readonly FileDialogManager _exportDialog; + private ImportHistory? _lastImport; private CancellationTokenSource? _testConnectionCts; @@ -68,7 +71,8 @@ namespace Pal.Client.Windows FloorService floorService, DebugState debugState, ChatGui chatGui, - RemoteApi remoteApi) + RemoteApi remoteApi, + ImportService importService) : base(WindowId) { _windowSystem = windowSystem; @@ -81,6 +85,7 @@ namespace Pal.Client.Windows _debugState = debugState; _chatGui = chatGui; _remoteApi = remoteApi; + _importService = importService; LanguageChanged(); @@ -117,6 +122,8 @@ namespace Pal.Client.Windows _hoardConfig = new ConfigurableMarker(_configuration.DeepDungeons.HoardCoffers); _silverConfig = new ConfigurableMarker(_configuration.DeepDungeons.SilverCoffers); _connectionText = null; + + UpdateLastImport(); } public override void OnClose() @@ -278,13 +285,14 @@ namespace Pal.Client.Windows DoImport(_openImportPath); ImGui.EndDisabled(); - var importHistory = _configuration.ImportHistory.OrderByDescending(x => x.ImportedAt) - .ThenBy(x => x.Id).FirstOrDefault(); + ImportHistory? importHistory = _lastImport; if (importHistory != null) { ImGui.Separator(); ImGui.TextWrapped(string.Format(Localization.Config_UndoImportExplanation1, - importHistory.ImportedAt, importHistory.RemoteUrl, importHistory.ExportedAt)); + importHistory.ImportedAt.ToLocalTime(), + importHistory.RemoteUrl, + importHistory.ExportedAt.ToUniversalTime())); ImGui.TextWrapped(Localization.Config_UndoImportExplanation2); if (ImGui.Button(Localization.Config_UndoImport)) UndoImport(importHistory.Id); @@ -466,6 +474,11 @@ namespace Pal.Client.Windows _frameworkService.EarlyEventQueue.Enqueue(new QueuedUndoImport(importId)); } + internal void UpdateLastImport() + { + _lastImport = _importService.FindLast(); + } + private void DoExport(string destinationPath) { Task.Run(async () =>