diff --git a/Pal.Client/Configuration/ConfigurationManager.cs b/Pal.Client/Configuration/ConfigurationManager.cs index 4b4db7a..1c28c1d 100644 --- a/Pal.Client/Configuration/ConfigurationManager.cs +++ b/Pal.Client/Configuration/ConfigurationManager.cs @@ -9,6 +9,7 @@ using Dalamud.Plugin; using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Pal.Client.Configuration.Legacy; using Pal.Client.Database; using NJson = Newtonsoft.Json; diff --git a/Pal.Client/Configuration/ConfigurationV7.cs b/Pal.Client/Configuration/ConfigurationV7.cs index a6032d6..236fea9 100644 --- a/Pal.Client/Configuration/ConfigurationV7.cs +++ b/Pal.Client/Configuration/ConfigurationV7.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.Json.Serialization; namespace Pal.Client.Configuration { @@ -17,10 +16,6 @@ namespace Pal.Client.Configuration public RendererConfiguration Renderer { get; set; } = new(); public List Accounts { get; set; } = new(); - [JsonIgnore] - [Obsolete] - public List ImportHistory { get; set; } = new(); - public IAccountConfiguration CreateAccount(string server, Guid accountId) { var account = new AccountConfigurationV7(server, accountId); diff --git a/Pal.Client/Configuration/ConfigurationV1.cs b/Pal.Client/Configuration/Legacy/ConfigurationV1.cs similarity index 95% rename from Pal.Client/Configuration/ConfigurationV1.cs rename to Pal.Client/Configuration/Legacy/ConfigurationV1.cs index ef98640..6b94807 100644 --- a/Pal.Client/Configuration/ConfigurationV1.cs +++ b/Pal.Client/Configuration/Legacy/ConfigurationV1.cs @@ -1,6 +1,4 @@ -using Dalamud.Logging; -using Newtonsoft.Json; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -8,8 +6,9 @@ using System.Linq; using System.Numerics; using Dalamud.Plugin; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; -namespace Pal.Client.Configuration +namespace Pal.Client.Configuration.Legacy { [Obsolete] public sealed class ConfigurationV1 @@ -90,7 +89,7 @@ namespace Pal.Client.Configuration // 2.2 had a bug that would mark chests as traps, there's no easy way to detect this -- or clean this up. // Not a problem for online players, but offline players might be fucked. //bool changedAnyFile = false; - LocalState.ForEach(s => + JsonFloorState.ForEach(s => { foreach (var marker in s.Markers) marker.SinceVersion = "0.0"; @@ -100,7 +99,7 @@ namespace Pal.Client.Configuration { s.Backup(suffix: "bak"); - s.Markers = new ConcurrentBag(s.Markers.Where(m => m.SinceVersion != "0.0" || m.Type == Marker.EType.Hoard || m.WasImported)); + s.Markers = new ConcurrentBag(s.Markers.Where(m => m.SinceVersion != "0.0" || m.Type == JsonMarker.EType.Hoard || m.WasImported)); s.Save(); //changedAnyFile = true; @@ -131,7 +130,7 @@ namespace Pal.Client.Configuration if (Version == 5) { - LocalState.UpdateAll(); + JsonFloorState.UpdateAll(); Version = 6; Save(pluginInterface); diff --git a/Pal.Client/Configuration/Legacy/JsonFloorState.cs b/Pal.Client/Configuration/Legacy/JsonFloorState.cs new file mode 100644 index 0000000..1199b18 --- /dev/null +++ b/Pal.Client/Configuration/Legacy/JsonFloorState.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using Pal.Client.Extensions; +using Pal.Common; + +namespace Pal.Client.Configuration.Legacy +{ + /// + /// Legacy JSON file for marker locations. + /// + [Obsolete] + public sealed class JsonFloorState + { + private static readonly JsonSerializerOptions JsonSerializerOptions = new() { IncludeFields = true }; + private const int CurrentVersion = 4; + + private static string _pluginConfigDirectory; + private static EMode _mode = EMode.Online; // might not be true, but this is 'less strict filtering' for migrations + + internal static void SetContextProperties(string pluginConfigDirectory) + { + _pluginConfigDirectory = pluginConfigDirectory; + } + + public uint TerritoryType { get; set; } + public ConcurrentBag Markers { get; set; } = new(); + + public JsonFloorState(uint territoryType) + { + TerritoryType = territoryType; + } + + private void ApplyFilters() + { + if (_mode == EMode.Offline) + Markers = new ConcurrentBag(Markers.Where(x => x.Seen || (x.WasImported && x.Imports.Count > 0))); + else + // ensure old import markers are removed if they are no longer part of a "current" import + // this MAY remove markers the server sent you (and that you haven't seen), but this should be fixed the next time you enter the zone + Markers = new ConcurrentBag(Markers.Where(x => x.Seen || !x.WasImported || x.Imports.Count > 0)); + } + + public static JsonFloorState? Load(uint territoryType) + { + string path = GetSaveLocation(territoryType); + if (!File.Exists(path)) + return null; + + string content = File.ReadAllText(path); + if (content.Length == 0) + return null; + + JsonFloorState localState; + int version = 1; + if (content[0] == '[') + { + // v1 only had a list of markers, not a JSON object as root + localState = new JsonFloorState(territoryType) + { + Markers = new ConcurrentBag(JsonSerializer.Deserialize>(content, JsonSerializerOptions) ?? new()), + }; + } + else + { + var save = JsonSerializer.Deserialize(content, JsonSerializerOptions); + if (save == null) + return null; + + localState = new JsonFloorState(territoryType) + { + Markers = new ConcurrentBag(save.Markers.Where(o => o.Type == JsonMarker.EType.Trap || o.Type == JsonMarker.EType.Hoard)), + }; + version = save.Version; + } + + localState.ApplyFilters(); + + if (version <= 3) + { + foreach (var marker in localState.Markers) + marker.RemoteSeenOn = marker.RemoteSeenOn.Select(x => x.ToPartialId()).ToList(); + } + + if (version < CurrentVersion) + localState.Save(); + + return localState; + } + + public void Save() + { + string path = GetSaveLocation(TerritoryType); + + ApplyFilters(); + SaveImpl(path); + } + + public void Backup(string suffix) + { + string path = $"{GetSaveLocation(TerritoryType)}.{suffix}"; + if (!File.Exists(path)) + { + SaveImpl(path); + } + } + + private void SaveImpl(string path) + { + foreach (var marker in Markers) + { + if (string.IsNullOrEmpty(marker.SinceVersion)) + marker.SinceVersion = typeof(Plugin).Assembly.GetName().Version!.ToString(2); + } + + if (Markers.Count == 0) + File.Delete(path); + else + { + File.WriteAllText(path, JsonSerializer.Serialize(new SaveFile + { + Version = CurrentVersion, + Markers = new HashSet(Markers) + }, JsonSerializerOptions)); + } + } + + public string GetSaveLocation() => GetSaveLocation(TerritoryType); + + private static string GetSaveLocation(uint territoryType) => Path.Join(_pluginConfigDirectory, $"{territoryType}.json"); + + public static void ForEach(Action action) + { + foreach (ETerritoryType territory in typeof(ETerritoryType).GetEnumValues()) + { + JsonFloorState? localState = Load((ushort)territory); + if (localState != null) + action(localState); + } + } + + public static void UpdateAll() + { + ForEach(s => s.Save()); + } + + public void UndoImport(List importIds) + { + // When saving a floor state, any markers not seen, not remote seen, and not having an import id are removed; + // so it is possible to remove "wrong" markers by not having them be in the current import. + foreach (var marker in Markers) + marker.Imports.RemoveAll(importIds.Contains); + } + + public sealed class SaveFile + { + public int Version { get; set; } + public HashSet Markers { get; set; } = new(); + } + } +} diff --git a/Pal.Client/Configuration/Legacy/JsonMarker.cs b/Pal.Client/Configuration/Legacy/JsonMarker.cs new file mode 100644 index 0000000..06b4607 --- /dev/null +++ b/Pal.Client/Configuration/Legacy/JsonMarker.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Pal.Client.Configuration.Legacy +{ + [Obsolete] + public class JsonMarker + { + public EType Type { get; set; } = EType.Unknown; + public Vector3 Position { get; set; } + public bool Seen { get; set; } + public List RemoteSeenOn { get; set; } = new(); + public List Imports { get; set; } = new(); + public bool WasImported { get; set; } + public string? SinceVersion { get; set; } + + public enum EType + { + Unknown = 0, + Trap = 1, + Hoard = 2, + Debug = 3, + } + } +} diff --git a/Pal.Client/DependencyInjectionContext.cs b/Pal.Client/DependencyInjectionContext.cs index 8d31745..c8e3f0c 100644 --- a/Pal.Client/DependencyInjectionContext.cs +++ b/Pal.Client/DependencyInjectionContext.cs @@ -19,6 +19,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Pal.Client.Commands; using Pal.Client.Configuration; +using Pal.Client.Configuration.Legacy; using Pal.Client.Database; using Pal.Client.DependencyInjection; using Pal.Client.DependencyInjection.Logging; @@ -39,7 +40,11 @@ namespace Pal.Client { public static DalamudLoggerProvider LoggerProvider { get; } = new(); + /// + /// Initialized as temporary logger, will be overriden once context is ready with a logger that supports scopes. + /// private readonly ILogger _logger = LoggerProvider.CreateLogger(); + private readonly string _sqliteConnectionString; private readonly CancellationTokenSource _initCts = new(); private ServiceProvider? _serviceProvider; @@ -56,9 +61,14 @@ namespace Pal.Client CommandManager commandManager, DataManager dataManager) { - // Temporary logger, will be overriden later _logger.LogInformation("Building service container"); + // set up legacy services +#pragma warning disable CS0612 + JsonFloorState.SetContextProperties(pluginInterface.GetPluginConfigDirectory()); +#pragma warning restore CS0612 + + // set up logging CancellationToken token = _initCts.Token; IServiceCollection services = new ServiceCollection(); services.AddLogging(builder => @@ -67,6 +77,7 @@ namespace Pal.Client .AddFilter("Grpc", LogLevel.Debug) .ClearProviders() .AddProvider(LoggerProvider)); + // dalamud services.AddSingleton(this); services.AddSingleton(pluginInterface); @@ -171,10 +182,6 @@ namespace Pal.Client 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();