From 7bccec0bae93d9b09d70343114934bd45a0566c6 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Wed, 22 Feb 2023 20:29:58 +0100 Subject: [PATCH] Db: lmport --- .../Configuration/Legacy/JsonMigration.cs | 5 +- Pal.Client/Database/Cleanup.cs | 67 +++++++ Pal.Client/Database/ClientLocation.cs | 13 +- ...ChangeLocationImportedToSource.Designer.cs | 148 ++++++++++++++ ...22191929_ChangeLocationImportedToSource.cs | 28 +++ .../PalClientContextModelSnapshot.cs | 6 +- .../DependencyInjection/FrameworkService.cs | 11 +- .../DependencyInjection/ImportService.cs | 180 ++++++++++++------ Pal.Client/DependencyInjectionContext.cs | 1 + Pal.Client/DependencyInjectionLoader.cs | 30 ++- Pal.Client/Floors/FloorService.cs | 26 ++- Pal.Client/Floors/MemoryTerritory.cs | 36 ++-- Pal.Client/Floors/PersistentLocation.cs | 2 + Pal.Client/Floors/Tasks/DbTask.cs | 3 +- Pal.Client/Floors/Tasks/LoadTerritory.cs | 17 +- Pal.Client/Floors/Tasks/SaveNewLocations.cs | 1 + Pal.Client/Net/RemoteApi.PalaceService.cs | 2 + Pal.Client/Net/RemoteApi.cs | 2 +- Pal.Client/Scheduled/QueuedImport.cs | 35 ++-- Pal.Client/Windows/ConfigWindow.cs | 5 +- Pal.Common/ETerritoryType.cs | 3 + 21 files changed, 505 insertions(+), 116 deletions(-) create mode 100644 Pal.Client/Database/Cleanup.cs create mode 100644 Pal.Client/Database/Migrations/20230222191929_ChangeLocationImportedToSource.Designer.cs create mode 100644 Pal.Client/Database/Migrations/20230222191929_ChangeLocationImportedToSource.cs diff --git a/Pal.Client/Configuration/Legacy/JsonMigration.cs b/Pal.Client/Configuration/Legacy/JsonMigration.cs index c657043..c9b9a6a 100644 --- a/Pal.Client/Configuration/Legacy/JsonMigration.cs +++ b/Pal.Client/Configuration/Legacy/JsonMigration.cs @@ -115,7 +115,10 @@ namespace Pal.Client.Configuration.Legacy .Distinct() .ToList(), - Imported = o.WasImported, + // if we have a location not encountered locally, which also wasn't imported, + // it very likely is a download (but we have no information to track this). + Source = o.Seen ? ClientLocation.ESource.SeenLocally : + o.Imports.Count > 0 ? ClientLocation.ESource.Import : ClientLocation.ESource.Download, SinceVersion = o.SinceVersion ?? "0.0", }; diff --git a/Pal.Client/Database/Cleanup.cs b/Pal.Client/Database/Cleanup.cs new file mode 100644 index 0000000..40db00f --- /dev/null +++ b/Pal.Client/Database/Cleanup.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Pal.Client.Configuration; +using Pal.Common; + +namespace Pal.Client.Database +{ + internal sealed class Cleanup + { + private readonly ILogger _logger; + private readonly IPalacePalConfiguration _configuration; + + public Cleanup(ILogger logger, IPalacePalConfiguration configuration) + { + _logger = logger; + _configuration = configuration; + } + + public void Purge(PalClientContext dbContext) + { + var toDelete = dbContext.Locations + .Include(o => o.ImportedBy) + .Include(o => o.RemoteEncounters) + .AsSplitQuery() + .Where(DefaultPredicate()) + .Where(AnyRemoteEncounter()) + .ToList(); + _logger.LogInformation("Cleaning up {Count} outdated locations", toDelete.Count); + dbContext.Locations.RemoveRange(toDelete); + } + + public void Purge(PalClientContext dbContext, ETerritoryType territoryType) + { + var toDelete = dbContext.Locations + .Include(o => o.ImportedBy) + .Include(o => o.RemoteEncounters) + .AsSplitQuery() + .Where(o => o.TerritoryType == (ushort)territoryType) + .Where(DefaultPredicate()) + .Where(AnyRemoteEncounter()) + .ToList(); + _logger.LogInformation("Cleaning up {Count} outdated locations for territory {Territory}", toDelete.Count, + territoryType); + dbContext.Locations.RemoveRange(toDelete); + } + + private Expression> DefaultPredicate() + { + return o => !o.Seen && + o.ImportedBy.Count == 0 && + o.Source != ClientLocation.ESource.SeenLocally && + o.Source != ClientLocation.ESource.ExplodedLocally; + } + + private Expression> AnyRemoteEncounter() + { + if (_configuration.Mode == EMode.Offline) + return o => true; + else + // keep downloaded markers + return o => o.Source != ClientLocation.ESource.Download; + } + } +} diff --git a/Pal.Client/Database/ClientLocation.cs b/Pal.Client/Database/ClientLocation.cs index e545edd..ab748f5 100644 --- a/Pal.Client/Database/ClientLocation.cs +++ b/Pal.Client/Database/ClientLocation.cs @@ -31,9 +31,9 @@ namespace Pal.Client.Database public List ImportedBy { get; set; } = new(); /// - /// Whether this location was originally imported. + /// Determines where this location is originally from. /// - public bool Imported { get; set; } + public ESource Source { get; set; } /// @@ -46,5 +46,14 @@ namespace Pal.Client.Database Trap = 1, Hoard = 2, } + + public enum ESource + { + Unknown = 0, + SeenLocally = 1, + ExplodedLocally = 2, + Import = 3, + Download = 4, + } } } diff --git a/Pal.Client/Database/Migrations/20230222191929_ChangeLocationImportedToSource.Designer.cs b/Pal.Client/Database/Migrations/20230222191929_ChangeLocationImportedToSource.Designer.cs new file mode 100644 index 0000000..0924e17 --- /dev/null +++ b/Pal.Client/Database/Migrations/20230222191929_ChangeLocationImportedToSource.Designer.cs @@ -0,0 +1,148 @@ +// +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("20230222191929_ChangeLocationImportedToSource")] + partial class ChangeLocationImportedToSource + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.3"); + + modelBuilder.Entity("ClientLocationImportHistory", b => + { + b.Property("ImportedById") + .HasColumnType("TEXT"); + + b.Property("ImportedLocationsLocalId") + .HasColumnType("INTEGER"); + + b.HasKey("ImportedById", "ImportedLocationsLocalId"); + + b.HasIndex("ImportedLocationsLocalId"); + + b.ToTable("LocationImports", (string)null); + }); + + modelBuilder.Entity("Pal.Client.Database.ClientLocation", b => + { + b.Property("LocalId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Seen") + .HasColumnType("INTEGER"); + + b.Property("SinceVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("TerritoryType") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("X") + .HasColumnType("REAL"); + + b.Property("Y") + .HasColumnType("REAL"); + + b.Property("Z") + .HasColumnType("REAL"); + + b.HasKey("LocalId"); + + b.ToTable("Locations"); + }); + + 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"); + }); + + modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccountId") + .IsRequired() + .HasMaxLength(13) + .HasColumnType("TEXT"); + + b.Property("ClientLocationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ClientLocationId"); + + b.ToTable("RemoteEncounters"); + }); + + modelBuilder.Entity("ClientLocationImportHistory", b => + { + b.HasOne("Pal.Client.Database.ImportHistory", null) + .WithMany() + .HasForeignKey("ImportedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Pal.Client.Database.ClientLocation", null) + .WithMany() + .HasForeignKey("ImportedLocationsLocalId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Pal.Client.Database.RemoteEncounter", b => + { + b.HasOne("Pal.Client.Database.ClientLocation", "ClientLocation") + .WithMany("RemoteEncounters") + .HasForeignKey("ClientLocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ClientLocation"); + }); + + modelBuilder.Entity("Pal.Client.Database.ClientLocation", b => + { + b.Navigation("RemoteEncounters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Pal.Client/Database/Migrations/20230222191929_ChangeLocationImportedToSource.cs b/Pal.Client/Database/Migrations/20230222191929_ChangeLocationImportedToSource.cs new file mode 100644 index 0000000..20cf1e0 --- /dev/null +++ b/Pal.Client/Database/Migrations/20230222191929_ChangeLocationImportedToSource.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Pal.Client.Database.Migrations +{ + /// + public partial class ChangeLocationImportedToSource : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Imported", + table: "Locations", + newName: "Source"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Source", + table: "Locations", + newName: "Imported"); + } + } +} diff --git a/Pal.Client/Database/Migrations/PalClientContextModelSnapshot.cs b/Pal.Client/Database/Migrations/PalClientContextModelSnapshot.cs index 963f393..55e0dff 100644 --- a/Pal.Client/Database/Migrations/PalClientContextModelSnapshot.cs +++ b/Pal.Client/Database/Migrations/PalClientContextModelSnapshot.cs @@ -38,9 +38,6 @@ namespace Pal.Client.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); - b.Property("Imported") - .HasColumnType("INTEGER"); - b.Property("Seen") .HasColumnType("INTEGER"); @@ -48,6 +45,9 @@ namespace Pal.Client.Database.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("Source") + .HasColumnType("INTEGER"); + b.Property("TerritoryType") .HasColumnType("INTEGER"); diff --git a/Pal.Client/DependencyInjection/FrameworkService.cs b/Pal.Client/DependencyInjection/FrameworkService.cs index 0a8880e..781bf28 100644 --- a/Pal.Client/DependencyInjection/FrameworkService.cs +++ b/Pal.Client/DependencyInjection/FrameworkService.cs @@ -14,6 +14,7 @@ using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Pal.Client.Configuration; +using Pal.Client.Database; using Pal.Client.Extensions; using Pal.Client.Floors; using Pal.Client.Net; @@ -390,7 +391,8 @@ namespace Pal.Client.DependencyInjection { Type = MemoryLocation.EType.Trap, Position = obj.Position, - Seen = true + Seen = true, + Source = ClientLocation.ESource.SeenLocally, }); break; @@ -400,7 +402,8 @@ namespace Pal.Client.DependencyInjection { Type = MemoryLocation.EType.Hoard, Position = obj.Position, - Seen = true + Seen = true, + Source = ClientLocation.ESource.SeenLocally, }); break; @@ -409,7 +412,7 @@ namespace Pal.Client.DependencyInjection { Type = MemoryLocation.EType.SilverCoffer, Position = obj.Position, - Seen = true + Seen = true, }); break; } @@ -425,6 +428,8 @@ namespace Pal.Client.DependencyInjection Type = MemoryLocation.EType.Trap, Position = obj.Position, Seen = true, + Source = ClientLocation.ESource.ExplodedLocally, + }); } } diff --git a/Pal.Client/DependencyInjection/ImportService.cs b/Pal.Client/DependencyInjection/ImportService.cs index 4d074dd..d704afb 100644 --- a/Pal.Client/DependencyInjection/ImportService.cs +++ b/Pal.Client/DependencyInjection/ImportService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using System.Threading; using System.Threading.Tasks; using Account; @@ -17,94 +18,149 @@ namespace Pal.Client.DependencyInjection { private readonly IServiceProvider _serviceProvider; private readonly FloorService _floorService; + private readonly Cleanup _cleanup; - public ImportService(IServiceProvider serviceProvider, FloorService floorService) + public ImportService( + IServiceProvider serviceProvider, + FloorService floorService, + Cleanup cleanup) { _serviceProvider = serviceProvider; _floorService = floorService; + _cleanup = cleanup; } - /* - public void Add(ImportHistory history) - { - using var scope = _serviceProvider.CreateScope(); - using var dbContext = scope.ServiceProvider.GetRequiredService(); - - dbContext.Imports.Add(history); - dbContext.SaveChanges(); - } - */ - public async Task FindLast(CancellationToken token = default) { await using var scope = _serviceProvider.CreateAsyncScope(); await using var dbContext = scope.ServiceProvider.GetRequiredService(); - return await dbContext.Imports.OrderByDescending(x => x.ImportedAt).ThenBy(x => x.Id).FirstOrDefaultAsync(cancellationToken: token); + return await dbContext.Imports.OrderByDescending(x => x.ImportedAt).ThenBy(x => x.Id) + .FirstOrDefaultAsync(cancellationToken: token); } - /* - 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 (int traps, int hoard) Import(ExportRoot import) { - using var scope = _serviceProvider.CreateScope(); - using var dbContext = scope.ServiceProvider.GetRequiredService(); - - dbContext.Imports.RemoveRange(dbContext.Imports.Where(x => x.RemoteUrl == import.ServerUrl).ToList()); - - ImportHistory importHistory = new ImportHistory + try { - Id = Guid.Parse(import.ExportId), - RemoteUrl = import.ServerUrl, - ExportedAt = import.CreatedAt.ToDateTime(), - ImportedAt = DateTime.UtcNow, - }; - dbContext.Imports.Add(importHistory); + _floorService.SetToImportState(); - int traps = 0; - int hoard = 0; - foreach (var floor in import.Floors) - { - ETerritoryType territoryType = (ETerritoryType)floor.TerritoryType; + using var scope = _serviceProvider.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); - List existingLocations = dbContext.Locations - .Where(loc => loc.TerritoryType == floor.TerritoryType) - .ToList() - .Select(LoadTerritory.ToMemoryLocation) - .ToList(); - foreach (var newLocation in floor.Objects) + dbContext.Imports.RemoveRange(dbContext.Imports.Where(x => x.RemoteUrl == import.ServerUrl).ToList()); + dbContext.SaveChanges(); + + ImportHistory importHistory = new ImportHistory { - throw new NotImplementedException(); - } - } - // TODO filter here, update territories - dbContext.SaveChanges(); + Id = Guid.Parse(import.ExportId), + RemoteUrl = import.ServerUrl, + ExportedAt = import.CreatedAt.ToDateTime(), + ImportedAt = DateTime.UtcNow, + }; + dbContext.Imports.Add(importHistory); - _floorService.ResetAll(); - return (traps, hoard); + int traps = 0; + int hoard = 0; + foreach (var floor in import.Floors) + { + ETerritoryType territoryType = (ETerritoryType)floor.TerritoryType; + + List existingLocations = dbContext.Locations + .Where(loc => loc.TerritoryType == floor.TerritoryType) + .ToList() + .Select(LoadTerritory.ToMemoryLocation) + .ToList(); + foreach (var exportLocation in floor.Objects) + { + PersistentLocation persistentLocation = new PersistentLocation + { + Type = ToMemoryType(exportLocation.Type), + Position = new Vector3(exportLocation.X, exportLocation.Y, exportLocation.Z), + Source = ClientLocation.ESource.Unknown, + }; + + var existingLocation = existingLocations.FirstOrDefault(x => x == persistentLocation); + if (existingLocation != null) + { + var clientLoc = dbContext.Locations.FirstOrDefault(o => o.LocalId == existingLocation.LocalId); + clientLoc?.ImportedBy.Add(importHistory); + + continue; + } + + ClientLocation clientLocation = new ClientLocation + { + TerritoryType = (ushort)territoryType, + Type = ToClientLocationType(exportLocation.Type), + X = exportLocation.X, + Y = exportLocation.Y, + Z = exportLocation.Z, + Seen = false, + Source = ClientLocation.ESource.Import, + ImportedBy = new List { importHistory }, + SinceVersion = typeof(Plugin).Assembly.GetName().Version!.ToString(2), + }; + dbContext.Locations.Add(clientLocation); + + if (exportLocation.Type == ExportObjectType.Trap) + traps++; + else if (exportLocation.Type == ExportObjectType.Hoard) + hoard++; + } + } + + dbContext.SaveChanges(); + + _cleanup.Purge(dbContext); + dbContext.SaveChanges(); + + return (traps, hoard); + } + finally + { + _floorService.ResetAll(); + } + } + + private MemoryLocation.EType ToMemoryType(ExportObjectType exportLocationType) + { + return exportLocationType switch + { + ExportObjectType.Trap => MemoryLocation.EType.Trap, + ExportObjectType.Hoard => MemoryLocation.EType.Hoard, + _ => throw new ArgumentOutOfRangeException(nameof(exportLocationType), exportLocationType, null) + }; + } + + private ClientLocation.EType ToClientLocationType(ExportObjectType exportLocationType) + { + return exportLocationType switch + { + ExportObjectType.Trap => ClientLocation.EType.Trap, + ExportObjectType.Hoard => ClientLocation.EType.Hoard, + _ => throw new ArgumentOutOfRangeException(nameof(exportLocationType), exportLocationType, null) + }; } public void RemoveById(Guid id) { - using var scope = _serviceProvider.CreateScope(); - using var dbContext = scope.ServiceProvider.GetRequiredService(); + try + { + _floorService.SetToImportState(); + using var scope = _serviceProvider.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); - dbContext.RemoveRange(dbContext.Imports.Where(x => x.Id == id)); + dbContext.RemoveRange(dbContext.Imports.Where(x => x.Id == id)); + dbContext.SaveChanges(); - // TODO filter here, update territories - dbContext.SaveChanges(); - - _floorService.ResetAll(); + _cleanup.Purge(dbContext); + dbContext.SaveChanges(); + } + finally + { + _floorService.ResetAll(); + } } } } diff --git a/Pal.Client/DependencyInjectionContext.cs b/Pal.Client/DependencyInjectionContext.cs index 33d9724..1ce57e5 100644 --- a/Pal.Client/DependencyInjectionContext.cs +++ b/Pal.Client/DependencyInjectionContext.cs @@ -96,6 +96,7 @@ namespace Pal.Client $"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), "palace-pal.data.sqlite3")}"; services.AddDbContext(o => o.UseSqlite(_sqliteConnectionString)); services.AddTransient(); + services.AddScoped(); // plugin-specific services.AddScoped(); diff --git a/Pal.Client/DependencyInjectionLoader.cs b/Pal.Client/DependencyInjectionLoader.cs index 5a0eec1..9466eab 100644 --- a/Pal.Client/DependencyInjectionLoader.cs +++ b/Pal.Client/DependencyInjectionLoader.cs @@ -55,7 +55,9 @@ namespace Pal.Client cancellationToken.ThrowIfCancellationRequested(); await RunMigrations(cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + await RunCleanup(_logger); cancellationToken.ThrowIfCancellationRequested(); // v1 migration: config migration for import history, json migration for markers @@ -174,18 +176,28 @@ namespace Pal.Client private async Task RunMigrations(CancellationToken cancellationToken) { - // initialize database - await using (var scope = _serviceProvider.CreateAsyncScope()) - { - _logger.LogInformation("Loading database & running migrations"); - await using var dbContext = scope.ServiceProvider.GetRequiredService(); + await using var scope = _serviceProvider.CreateAsyncScope(); - // takes 2-3 seconds with initializing connections, loading driver etc. - await dbContext.Database.MigrateAsync(cancellationToken); - _logger.LogInformation("Completed database migrations"); - } + _logger.LogInformation("Loading database & running migrations"); + await using var dbContext = scope.ServiceProvider.GetRequiredService(); + + // takes 2-3 seconds with initializing connections, loading driver etc. + await dbContext.Database.MigrateAsync(cancellationToken); + _logger.LogInformation("Completed database migrations"); } + private async Task RunCleanup(ILogger logger) + { + await using var scope = _serviceProvider.CreateAsyncScope(); + await using var dbContext = scope.ServiceProvider.GetRequiredService(); + var cleanup = scope.ServiceProvider.GetRequiredService(); + + cleanup.Purge(dbContext); + + await dbContext.SaveChangesAsync(); + } + + public enum ELoadState { Initializing, diff --git a/Pal.Client/Floors/FloorService.cs b/Pal.Client/Floors/FloorService.cs index 8b53d75..bfb2b58 100644 --- a/Pal.Client/Floors/FloorService.cs +++ b/Pal.Client/Floors/FloorService.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Pal.Client.Configuration; +using Pal.Client.Database; using Pal.Client.Extensions; using Pal.Client.Floors.Tasks; using Pal.Client.Net; @@ -14,19 +15,23 @@ namespace Pal.Client.Floors internal sealed class FloorService { private readonly IPalacePalConfiguration _configuration; + private readonly Cleanup _cleanup; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IReadOnlyDictionary _territories; private ConcurrentBag _ephemeralLocations = new(); - public FloorService(IPalacePalConfiguration configuration, IServiceScopeFactory serviceScopeFactory) + public FloorService(IPalacePalConfiguration configuration, Cleanup cleanup, + IServiceScopeFactory serviceScopeFactory) { _configuration = configuration; + _cleanup = cleanup; _serviceScopeFactory = serviceScopeFactory; _territories = Enum.GetValues().ToDictionary(o => o, o => new MemoryTerritory(o)); } public IReadOnlyCollection EphemeralLocations => _ephemeralLocations; + public bool IsImportRunning { get; private set; } public void ChangeTerritory(ushort territoryType) { @@ -39,10 +44,10 @@ namespace Pal.Client.Floors private void ChangeTerritory(ETerritoryType newTerritory) { var territory = _territories[newTerritory]; - if (!territory.IsReady && !territory.IsLoading) + if (territory.ReadyState == MemoryTerritory.EReadyState.NotLoaded) { - territory.IsLoading = true; - new LoadTerritory(_serviceScopeFactory, territory).Start(); + territory.ReadyState = MemoryTerritory.EReadyState.Loading; + new LoadTerritory(_serviceScopeFactory, _cleanup, territory).Start(); } } @@ -57,7 +62,7 @@ namespace Pal.Client.Floors public MemoryTerritory? GetTerritoryIfReady(ETerritoryType territoryType) { var territory = _territories[territoryType]; - if (!territory.IsReady) + if (territory.ReadyState != MemoryTerritory.EReadyState.Ready) return null; return territory; @@ -137,11 +142,22 @@ namespace Pal.Client.Floors public void ResetAll() { + IsImportRunning = false; foreach (var memoryTerritory in _territories.Values) { lock (memoryTerritory.LockObj) memoryTerritory.Reset(); } } + + public void SetToImportState() + { + IsImportRunning = true; + foreach (var memoryTerritory in _territories.Values) + { + lock (memoryTerritory.LockObj) + memoryTerritory.ReadyState = MemoryTerritory.EReadyState.Importing; + } + } } } diff --git a/Pal.Client/Floors/MemoryTerritory.cs b/Pal.Client/Floors/MemoryTerritory.cs index d0708ae..e440bc8 100644 --- a/Pal.Client/Floors/MemoryTerritory.cs +++ b/Pal.Client/Floors/MemoryTerritory.cs @@ -18,8 +18,7 @@ namespace Pal.Client.Floors } public ETerritoryType TerritoryType { get; } - public bool IsReady { get; set; } - public bool IsLoading { get; set; } // probably merge this with IsReady as enum + public EReadyState ReadyState { get; set; } = EReadyState.NotLoaded; public ESyncState SyncState { get; set; } = ESyncState.NotAttempted; public ConcurrentBag Locations { get; } = new(); @@ -31,21 +30,34 @@ namespace Pal.Client.Floors foreach (var location in locations) Locations.Add(location); - IsReady = true; - IsLoading = false; - } - - public IEnumerable GetRemovableLocations(EMode mode) - { - // TODO there was better logic here; - return Locations.Where(x => !x.Seen); + ReadyState = EReadyState.Ready; } public void Reset() { Locations.Clear(); - IsReady = false; - IsLoading = false; + SyncState = ESyncState.NotAttempted; + ReadyState = EReadyState.NotLoaded; + } + + public enum EReadyState + { + NotLoaded, + + /// + /// Currently loading from the database. + /// + Loading, + + /// + /// Locations loaded, no import running. + /// + Ready, + + /// + /// Import running, should probably not interact with this too much. + /// + Importing, } } } diff --git a/Pal.Client/Floors/PersistentLocation.cs b/Pal.Client/Floors/PersistentLocation.cs index 99cc5dd..e6f8ad6 100644 --- a/Pal.Client/Floors/PersistentLocation.cs +++ b/Pal.Client/Floors/PersistentLocation.cs @@ -31,6 +31,8 @@ namespace Pal.Client.Floors /// public bool RemoteSeenRequested { get; set; } + public ClientLocation.ESource Source { get; init; } + public override bool Equals(object? obj) => obj is PersistentLocation && base.Equals(obj); public override int GetHashCode() => base.GetHashCode(); diff --git a/Pal.Client/Floors/Tasks/DbTask.cs b/Pal.Client/Floors/Tasks/DbTask.cs index c224fee..64074fe 100644 --- a/Pal.Client/Floors/Tasks/DbTask.cs +++ b/Pal.Client/Floors/Tasks/DbTask.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Pal.Client.Database; diff --git a/Pal.Client/Floors/Tasks/LoadTerritory.cs b/Pal.Client/Floors/Tasks/LoadTerritory.cs index b9f0958..7e11b2f 100644 --- a/Pal.Client/Floors/Tasks/LoadTerritory.cs +++ b/Pal.Client/Floors/Tasks/LoadTerritory.cs @@ -11,11 +11,15 @@ namespace Pal.Client.Floors.Tasks { internal sealed class LoadTerritory : DbTask { + private readonly Cleanup _cleanup; private readonly MemoryTerritory _territory; - public LoadTerritory(IServiceScopeFactory serviceScopeFactory, MemoryTerritory territory) + public LoadTerritory(IServiceScopeFactory serviceScopeFactory, + Cleanup cleanup, + MemoryTerritory territory) : base(serviceScopeFactory) { + _cleanup = cleanup; _territory = territory; } @@ -23,13 +27,19 @@ namespace Pal.Client.Floors.Tasks { lock (_territory.LockObj) { - if (_territory.IsReady) + if (_territory.ReadyState != MemoryTerritory.EReadyState.Loading) { - logger.LogInformation("Territory {Territory} is already loaded", _territory.TerritoryType); + logger.LogInformation("Territory {Territory} is in state {State}", _territory.TerritoryType, + _territory.ReadyState); return; } logger.LogInformation("Loading territory {Territory}", _territory.TerritoryType); + + // purge outdated locations + _cleanup.Purge(dbContext, _territory.TerritoryType); + + // load good locations List locations = dbContext.Locations .Where(o => o.TerritoryType == (ushort)_territory.TerritoryType) .Include(o => o.ImportedBy) @@ -51,6 +61,7 @@ namespace Pal.Client.Floors.Tasks Type = ToMemoryLocationType(location.Type), Position = new Vector3(location.X, location.Y, location.Z), Seen = location.Seen, + Source = location.Source, RemoteSeenOn = location.RemoteEncounters.Select(o => o.AccountId).ToList(), }; } diff --git a/Pal.Client/Floors/Tasks/SaveNewLocations.cs b/Pal.Client/Floors/Tasks/SaveNewLocations.cs index 5d1dd1d..345986a 100644 --- a/Pal.Client/Floors/Tasks/SaveNewLocations.cs +++ b/Pal.Client/Floors/Tasks/SaveNewLocations.cs @@ -59,6 +59,7 @@ namespace Pal.Client.Floors.Tasks Y = location.Position.Y, Z = location.Position.Z, Seen = location.Seen, + Source = location.Source, SinceVersion = typeof(Plugin).Assembly.GetName().Version!.ToString(2), }; } diff --git a/Pal.Client/Net/RemoteApi.PalaceService.cs b/Pal.Client/Net/RemoteApi.PalaceService.cs index b706ecc..161b3f5 100644 --- a/Pal.Client/Net/RemoteApi.PalaceService.cs +++ b/Pal.Client/Net/RemoteApi.PalaceService.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Numerics; using System.Threading; using System.Threading.Tasks; +using Pal.Client.Database; using Pal.Client.Floors; namespace Pal.Client.Net @@ -69,6 +70,7 @@ namespace Pal.Client.Net Type = obj.Type.ToMemoryType(), Position = new Vector3(obj.X, obj.Y, obj.Z), NetworkId = Guid.Parse(obj.NetworkId), + Source = ClientLocation.ESource.Download, }; } diff --git a/Pal.Client/Net/RemoteApi.cs b/Pal.Client/Net/RemoteApi.cs index 70654da..7f94781 100644 --- a/Pal.Client/Net/RemoteApi.cs +++ b/Pal.Client/Net/RemoteApi.cs @@ -13,7 +13,7 @@ namespace Pal.Client.Net #if DEBUG public const string RemoteUrl = "http://localhost:5415"; #else - //public const string RemoteUrl = "https://pal.liza.sh"; + public const string RemoteUrl = "http://localhost:5415"; #endif private readonly string _userAgent = $"{typeof(RemoteApi).Assembly.GetName().Name?.Replace(" ", "")}/{typeof(RemoteApi).Assembly.GetName().Version?.ToString(2)}"; diff --git a/Pal.Client/Scheduled/QueuedImport.cs b/Pal.Client/Scheduled/QueuedImport.cs index 89a947b..8c07149 100644 --- a/Pal.Client/Scheduled/QueuedImport.cs +++ b/Pal.Client/Scheduled/QueuedImport.cs @@ -2,11 +2,11 @@ using Pal.Common; using System; using System.IO; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Pal.Client.Database; using Pal.Client.DependencyInjection; -using Pal.Client.Floors; using Pal.Client.Properties; using Pal.Client.Windows; @@ -55,20 +55,31 @@ namespace Pal.Client.Scheduled if (!Validate(import)) return; - - using (var scope = _serviceScopeFactory.CreateScope()) + Task.Run(() => { - using var dbContext = scope.ServiceProvider.GetRequiredService(); - (import.ImportedTraps, import.ImportedHoardCoffers) = _importService.Import(import.Export); - } + try + { + using (var scope = _serviceScopeFactory.CreateScope()) + { + using var dbContext = scope.ServiceProvider.GetRequiredService(); + (import.ImportedTraps, import.ImportedHoardCoffers) = + _importService.Import(import.Export); + } - _configWindow.UpdateLastImport(); + _configWindow.UpdateLastImport(); - _logger.LogInformation( - "Imported {ExportId} for {Traps} traps, {Hoard} hoard coffers", import.ExportId, - import.ImportedTraps, import.ImportedHoardCoffers); - _chat.Message(string.Format(Localization.ImportCompleteStatistics, import.ImportedTraps, - import.ImportedHoardCoffers)); + _logger.LogInformation( + "Imported {ExportId} for {Traps} traps, {Hoard} hoard coffers", import.ExportId, + import.ImportedTraps, import.ImportedHoardCoffers); + _chat.Message(string.Format(Localization.ImportCompleteStatistics, import.ImportedTraps, + import.ImportedHoardCoffers)); + } + catch (Exception e) + { + _logger.LogError(e, "Import failed in inner task"); + _chat.Error(string.Format(Localization.Error_ImportFailed, e)); + } + }); } catch (Exception e) { diff --git a/Pal.Client/Windows/ConfigWindow.cs b/Pal.Client/Windows/ConfigWindow.cs index 4e68c62..c3b279b 100644 --- a/Pal.Client/Windows/ConfigWindow.cs +++ b/Pal.Client/Windows/ConfigWindow.cs @@ -284,7 +284,7 @@ namespace Pal.Client.Windows null; // only use this once, FileDialogManager will save path between calls } - ImGui.BeginDisabled(string.IsNullOrEmpty(_openImportPath) || !File.Exists(_openImportPath)); + ImGui.BeginDisabled(string.IsNullOrEmpty(_openImportPath) || !File.Exists(_openImportPath) || _floorService.IsImportRunning); if (ImGui.Button(Localization.Config_StartImport)) DoImport(_openImportPath); ImGui.EndDisabled(); @@ -298,8 +298,11 @@ namespace Pal.Client.Windows importHistory.RemoteUrl, importHistory.ExportedAt.ToUniversalTime())); ImGui.TextWrapped(Localization.Config_UndoImportExplanation2); + + ImGui.BeginDisabled(_floorService.IsImportRunning); if (ImGui.Button(Localization.Config_UndoImport)) UndoImport(importHistory.Id); + ImGui.EndDisabled(); } ImGui.EndTabItem(); diff --git a/Pal.Common/ETerritoryType.cs b/Pal.Common/ETerritoryType.cs index 9796f86..99e3ce6 100644 --- a/Pal.Common/ETerritoryType.cs +++ b/Pal.Common/ETerritoryType.cs @@ -1,7 +1,10 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; namespace Pal.Common { + [SuppressMessage("ReSharper", "UnusedMember.Global")] + [SuppressMessage("ReSharper", "InconsistentNaming")] public enum ETerritoryType : ushort { Palace_1_10 = 561,