From d5dc55a0c4f4bc53b50e2ea36984da1580f89958 Mon Sep 17 00:00:00 2001 From: Liza Carvelli Date: Wed, 22 Feb 2023 17:21:48 +0100 Subject: [PATCH] Db: Fix various things around local persistence/net interactions --- .../DependencyInjection/FrameworkService.cs | 28 ++- .../DependencyInjection/TerritoryState.cs | 1 - Pal.Client/Floors/EphemeralLocation.cs | 5 + Pal.Client/Floors/FloorService.cs | 2 +- Pal.Client/Floors/MemoryLocation.cs | 12 +- Pal.Client/Floors/MemoryTerritory.cs | 4 +- Pal.Client/Floors/PersistentLocation.cs | 5 + Pal.Client/Floors/Tasks/DbTask.cs | 12 +- Pal.Client/Floors/Tasks/LoadTerritory.cs | 13 +- .../Tasks/{MarkAsSeen.cs => MarkLocalSeen.cs} | 9 +- Pal.Client/Floors/Tasks/MarkRemoteSeen.cs | 24 ++- Pal.Client/Floors/Tasks/SaveNewLocations.cs | 15 +- Pal.Client/Net/RemoteApi.AccountService.cs | 196 ++++++++++-------- Pal.Client/Net/RemoteApi.PalaceService.cs | 2 +- Pal.Client/Net/RemoteApi.cs | 2 +- Pal.Client/Plugin.cs | 2 +- Pal.Client/Scheduled/QueuedSyncResponse.cs | 23 +- Pal.Client/Windows/ConfigWindow.cs | 4 +- 18 files changed, 235 insertions(+), 124 deletions(-) rename Pal.Client/Floors/Tasks/{MarkAsSeen.cs => MarkLocalSeen.cs} (64%) diff --git a/Pal.Client/DependencyInjection/FrameworkService.cs b/Pal.Client/DependencyInjection/FrameworkService.cs index c501410..0a8880e 100644 --- a/Pal.Client/DependencyInjection/FrameworkService.cs +++ b/Pal.Client/DependencyInjection/FrameworkService.cs @@ -12,6 +12,7 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Logging; using ImGuiNET; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Pal.Client.Configuration; using Pal.Client.Extensions; using Pal.Client.Floors; @@ -25,6 +26,7 @@ namespace Pal.Client.DependencyInjection internal sealed class FrameworkService : IDisposable { private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; private readonly Framework _framework; private readonly ConfigurationManager _configurationManager; private readonly IPalacePalConfiguration _configuration; @@ -42,6 +44,7 @@ namespace Pal.Client.DependencyInjection public FrameworkService( IServiceProvider serviceProvider, + ILogger logger, Framework framework, ConfigurationManager configurationManager, IPalacePalConfiguration configuration, @@ -54,6 +57,7 @@ namespace Pal.Client.DependencyInjection RemoteApi remoteApi) { _serviceProvider = serviceProvider; + _logger = logger; _framework = framework; _configurationManager = configurationManager; _configuration = configuration; @@ -92,8 +96,11 @@ namespace Pal.Client.DependencyInjection if (_territoryState.LastTerritory != _clientState.TerritoryType) { + MemoryTerritory? oldTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory); + if (oldTerritory != null) + oldTerritory.SyncState = ESyncState.NotAttempted; + _territoryState.LastTerritory = _clientState.TerritoryType; - _territoryState.TerritorySyncState = ESyncState.NotAttempted; NextUpdateObjects.Clear(); _floorService.ChangeTerritory(_territoryState.LastTerritory); @@ -106,11 +113,12 @@ namespace Pal.Client.DependencyInjection if (!_territoryState.IsInDeepDungeon() || !_floorService.IsReady(_territoryState.LastTerritory)) return; - if (_configuration.Mode == EMode.Online && - _territoryState.TerritorySyncState == ESyncState.NotAttempted) + ETerritoryType territoryType = (ETerritoryType)_territoryState.LastTerritory; + MemoryTerritory memoryTerritory = _floorService.GetTerritoryIfReady(territoryType)!; + if (_configuration.Mode == EMode.Online && memoryTerritory.SyncState == ESyncState.NotAttempted) { - _territoryState.TerritorySyncState = ESyncState.Started; - Task.Run(async () => await DownloadMarkersForTerritory(_territoryState.LastTerritory)); + memoryTerritory.SyncState = ESyncState.Started; + Task.Run(async () => await DownloadLocationsForTerritory(_territoryState.LastTerritory)); } while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued)) @@ -120,7 +128,6 @@ namespace Pal.Client.DependencyInjection IReadOnlyList visibleEphemeralMarkers) = GetRelevantGameObjects(); - ETerritoryType territoryType = (ETerritoryType)_territoryState.LastTerritory; HandlePersistentLocations(territoryType, visiblePersistentMarkers, recreateLayout); if (_floorService.MergeEphemeralLocations(visibleEphemeralMarkers, recreateLayout)) @@ -188,7 +195,7 @@ namespace Pal.Client.DependencyInjection private void UploadLocations() { MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory); - if (memoryTerritory == null) + if (memoryTerritory == null || memoryTerritory.SyncState != ESyncState.Complete) return; List locationsToUpload = memoryTerritory.Locations @@ -296,10 +303,11 @@ namespace Pal.Client.DependencyInjection #region Up-/Download - private async Task DownloadMarkersForTerritory(ushort territoryId) + private async Task DownloadLocationsForTerritory(ushort territoryId) { try { + _logger.LogInformation("Downloading territory {Territory} from server", (ETerritoryType)territoryId); var (success, downloadedMarkers) = await _remoteApi.DownloadRemoteMarkers(territoryId); LateEventQueue.Enqueue(new QueuedSyncResponse { @@ -319,6 +327,8 @@ namespace Pal.Client.DependencyInjection { try { + _logger.LogInformation("Uploading {Count} locations for territory {Territory} to server", + locationsToUpload.Count, (ETerritoryType)territoryId); var (success, uploadedLocations) = await _remoteApi.UploadLocations(territoryId, locationsToUpload); LateEventQueue.Enqueue(new QueuedSyncResponse { @@ -339,6 +349,8 @@ namespace Pal.Client.DependencyInjection { try { + _logger.LogInformation("Syncing {Count} seen locations for territory {Territory} to server", + locationsToUpdate.Count, (ETerritoryType)territoryId); var success = await _remoteApi.MarkAsSeen(territoryId, locationsToUpdate); LateEventQueue.Enqueue(new QueuedSyncResponse { diff --git a/Pal.Client/DependencyInjection/TerritoryState.cs b/Pal.Client/DependencyInjection/TerritoryState.cs index 75c3197..43852b8 100644 --- a/Pal.Client/DependencyInjection/TerritoryState.cs +++ b/Pal.Client/DependencyInjection/TerritoryState.cs @@ -17,7 +17,6 @@ namespace Pal.Client.DependencyInjection } public ushort LastTerritory { get; set; } - public ESyncState TerritorySyncState { get; set; } public PomanderState PomanderOfSight { get; set; } = PomanderState.Inactive; public PomanderState PomanderOfIntuition { get; set; } = PomanderState.Inactive; diff --git a/Pal.Client/Floors/EphemeralLocation.cs b/Pal.Client/Floors/EphemeralLocation.cs index ccf8a4b..c4d8f20 100644 --- a/Pal.Client/Floors/EphemeralLocation.cs +++ b/Pal.Client/Floors/EphemeralLocation.cs @@ -20,5 +20,10 @@ namespace Pal.Client.Floors { return !Equals(a, b); } + + public override string ToString() + { + return $"EphemeralLocation(Position={Position}, Type={Type})"; + } } } diff --git a/Pal.Client/Floors/FloorService.cs b/Pal.Client/Floors/FloorService.cs index 535b418..8b53d75 100644 --- a/Pal.Client/Floors/FloorService.cs +++ b/Pal.Client/Floors/FloorService.cs @@ -111,7 +111,7 @@ namespace Pal.Client.Floors } if (markAsSeen.Count > 0) - new MarkAsSeen(_serviceScopeFactory, territory, markAsSeen).Start(); + new MarkLocalSeen(_serviceScopeFactory, territory, markAsSeen).Start(); if (newLocations.Count > 0) new SaveNewLocations(_serviceScopeFactory, territory, newLocations).Start(); diff --git a/Pal.Client/Floors/MemoryLocation.cs b/Pal.Client/Floors/MemoryLocation.cs index 296bfcd..5b9a6ca 100644 --- a/Pal.Client/Floors/MemoryLocation.cs +++ b/Pal.Client/Floors/MemoryLocation.cs @@ -22,8 +22,8 @@ namespace Pal.Client.Floors { Unknown, - Hoard, Trap, + Hoard, SilverCoffer, } @@ -52,5 +52,15 @@ namespace Pal.Client.Floors _ => throw new ArgumentOutOfRangeException(nameof(objectType), objectType, null) }; } + + public static ObjectType ToObjectType(this MemoryLocation.EType type) + { + return type switch + { + MemoryLocation.EType.Trap => ObjectType.Trap, + MemoryLocation.EType.Hoard => ObjectType.Hoard, + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) + }; + } } } diff --git a/Pal.Client/Floors/MemoryTerritory.cs b/Pal.Client/Floors/MemoryTerritory.cs index dc835ec..d0708ae 100644 --- a/Pal.Client/Floors/MemoryTerritory.cs +++ b/Pal.Client/Floors/MemoryTerritory.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Pal.Client.Configuration; +using Pal.Client.Scheduled; using Pal.Common; namespace Pal.Client.Floors @@ -18,7 +19,8 @@ namespace Pal.Client.Floors public ETerritoryType TerritoryType { get; } public bool IsReady { get; set; } - public bool IsLoading { get; set; } + public bool IsLoading { get; set; } // probably merge this with IsReady as enum + public ESyncState SyncState { get; set; } = ESyncState.NotAttempted; public ConcurrentBag Locations { get; } = new(); public object LockObj { get; } = new(); diff --git a/Pal.Client/Floors/PersistentLocation.cs b/Pal.Client/Floors/PersistentLocation.cs index d1db189..99cc5dd 100644 --- a/Pal.Client/Floors/PersistentLocation.cs +++ b/Pal.Client/Floors/PersistentLocation.cs @@ -44,5 +44,10 @@ namespace Pal.Client.Floors { return !Equals(a, b); } + + public override string ToString() + { + return $"PersistentLocation(Position={Position}, Type={Type})"; + } } } diff --git a/Pal.Client/Floors/Tasks/DbTask.cs b/Pal.Client/Floors/Tasks/DbTask.cs index 017f96d..c224fee 100644 --- a/Pal.Client/Floors/Tasks/DbTask.cs +++ b/Pal.Client/Floors/Tasks/DbTask.cs @@ -1,10 +1,13 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Pal.Client.Database; namespace Pal.Client.Floors.Tasks { - internal abstract class DbTask + internal abstract class DbTask + where T : DbTask { private readonly IServiceScopeFactory _serviceScopeFactory; @@ -18,12 +21,13 @@ namespace Pal.Client.Floors.Tasks Task.Run(() => { using var scope = _serviceScopeFactory.CreateScope(); + ILogger logger = scope.ServiceProvider.GetRequiredService>(); using var dbContext = scope.ServiceProvider.GetRequiredService(); - Run(dbContext); + Run(dbContext, logger); }); } - protected abstract void Run(PalClientContext dbContext); + protected abstract void Run(PalClientContext dbContext, ILogger logger); } } diff --git a/Pal.Client/Floors/Tasks/LoadTerritory.cs b/Pal.Client/Floors/Tasks/LoadTerritory.cs index caa4291..b9f0958 100644 --- a/Pal.Client/Floors/Tasks/LoadTerritory.cs +++ b/Pal.Client/Floors/Tasks/LoadTerritory.cs @@ -4,11 +4,12 @@ using System.Linq; using System.Numerics; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Pal.Client.Database; namespace Pal.Client.Floors.Tasks { - internal sealed class LoadTerritory : DbTask + internal sealed class LoadTerritory : DbTask { private readonly MemoryTerritory _territory; @@ -18,19 +19,27 @@ namespace Pal.Client.Floors.Tasks _territory = territory; } - protected override void Run(PalClientContext dbContext) + protected override void Run(PalClientContext dbContext, ILogger logger) { lock (_territory.LockObj) { if (_territory.IsReady) + { + logger.LogInformation("Territory {Territory} is already loaded", _territory.TerritoryType); return; + } + logger.LogInformation("Loading territory {Territory}", _territory.TerritoryType); List locations = dbContext.Locations .Where(o => o.TerritoryType == (ushort)_territory.TerritoryType) .Include(o => o.ImportedBy) .Include(o => o.RemoteEncounters) + .AsSplitQuery() .ToList(); _territory.Initialize(locations.Select(ToMemoryLocation)); + + logger.LogInformation("Loaded {Count} locations for territory {Territory}", locations.Count, + _territory.TerritoryType); } } diff --git a/Pal.Client/Floors/Tasks/MarkAsSeen.cs b/Pal.Client/Floors/Tasks/MarkLocalSeen.cs similarity index 64% rename from Pal.Client/Floors/Tasks/MarkAsSeen.cs rename to Pal.Client/Floors/Tasks/MarkLocalSeen.cs index 3e1b767..c2f4dd7 100644 --- a/Pal.Client/Floors/Tasks/MarkAsSeen.cs +++ b/Pal.Client/Floors/Tasks/MarkLocalSeen.cs @@ -2,16 +2,17 @@ using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Pal.Client.Database; namespace Pal.Client.Floors.Tasks { - internal sealed class MarkAsSeen : DbTask + internal sealed class MarkLocalSeen : DbTask { private readonly MemoryTerritory _territory; private readonly IReadOnlyList _locations; - public MarkAsSeen(IServiceScopeFactory serviceScopeFactory, MemoryTerritory territory, + public MarkLocalSeen(IServiceScopeFactory serviceScopeFactory, MemoryTerritory territory, IReadOnlyList locations) : base(serviceScopeFactory) { @@ -19,10 +20,12 @@ namespace Pal.Client.Floors.Tasks _locations = locations; } - protected override void Run(PalClientContext dbContext) + protected override void Run(PalClientContext dbContext, ILogger logger) { lock (_territory.LockObj) { + logger.LogInformation("Marking {Count} locations as seen locally in territory {Territory}", _locations.Count, + _territory.TerritoryType); dbContext.Locations .Where(loc => _locations.Any(l => l.LocalId == loc.LocalId)) .ExecuteUpdate(loc => loc.SetProperty(l => l.Seen, true)); diff --git a/Pal.Client/Floors/Tasks/MarkRemoteSeen.cs b/Pal.Client/Floors/Tasks/MarkRemoteSeen.cs index 16a20c8..7a63741 100644 --- a/Pal.Client/Floors/Tasks/MarkRemoteSeen.cs +++ b/Pal.Client/Floors/Tasks/MarkRemoteSeen.cs @@ -1,11 +1,13 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Pal.Client.Database; namespace Pal.Client.Floors.Tasks { - internal sealed class MarkRemoteSeen : DbTask + internal sealed class MarkRemoteSeen : DbTask { private readonly MemoryTerritory _territory; private readonly IReadOnlyList _locations; @@ -22,16 +24,26 @@ namespace Pal.Client.Floors.Tasks _accountId = accountId; } - protected override void Run(PalClientContext dbContext) + protected override void Run(PalClientContext dbContext, ILogger logger) { lock (_territory.LockObj) { - List locationsToUpdate = dbContext.Locations - .Where(loc => _locations.Any(l => - l.LocalId == loc.LocalId && loc.RemoteEncounters.All(r => r.AccountId != _accountId))) - .ToList(); + logger.LogInformation("Marking {Count} locations as seen remotely on {Account} in territory {Territory}", + _locations.Count, _accountId, _territory.TerritoryType); + + List locationIds = _locations.Select(x => x.LocalId).Where(x => x != null).Cast().ToList(); + List locationsToUpdate = + dbContext.Locations + .Include(x => x.RemoteEncounters) + .Where(x => locationIds.Contains(x.LocalId)) + .ToList() + .Where(x => x.RemoteEncounters.All(encounter => encounter.AccountId != _accountId)) + .ToList(); foreach (var clientLocation in locationsToUpdate) + { clientLocation.RemoteEncounters.Add(new RemoteEncounter(clientLocation, _accountId)); + } + dbContext.SaveChanges(); } } diff --git a/Pal.Client/Floors/Tasks/SaveNewLocations.cs b/Pal.Client/Floors/Tasks/SaveNewLocations.cs index 4489740..5d1dd1d 100644 --- a/Pal.Client/Floors/Tasks/SaveNewLocations.cs +++ b/Pal.Client/Floors/Tasks/SaveNewLocations.cs @@ -2,12 +2,13 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Pal.Client.Database; using Pal.Common; namespace Pal.Client.Floors.Tasks { - internal sealed class SaveNewLocations : DbTask + internal sealed class SaveNewLocations : DbTask { private readonly MemoryTerritory _territory; private readonly List _newLocations; @@ -20,16 +21,22 @@ namespace Pal.Client.Floors.Tasks _newLocations = newLocations; } - protected override void Run(PalClientContext dbContext) + protected override void Run(PalClientContext dbContext, ILogger logger) { - Run(_territory, dbContext, _newLocations); + Run(_territory, dbContext, logger, _newLocations); } - public static void Run(MemoryTerritory territory, PalClientContext dbContext, + public static void Run( + MemoryTerritory territory, + PalClientContext dbContext, + ILogger logger, List locations) { lock (territory.LockObj) { + logger.LogInformation("Saving {Count} new locations for territory {Territory}", locations.Count, + territory.TerritoryType); + Dictionary mapping = locations.ToDictionary(x => x, x => ToDatabaseLocation(x, territory.TerritoryType)); dbContext.Locations.AddRange(mapping.Values); diff --git a/Pal.Client/Net/RemoteApi.AccountService.cs b/Pal.Client/Net/RemoteApi.AccountService.cs index a62fe1b..98842e2 100644 --- a/Pal.Client/Net/RemoteApi.AccountService.cs +++ b/Pal.Client/Net/RemoteApi.AccountService.cs @@ -11,13 +11,15 @@ using System.Threading.Tasks; using Pal.Client.Extensions; using Pal.Client.Properties; using Pal.Client.Configuration; -using Pal.Client.DependencyInjection; namespace Pal.Client.Net { internal partial class RemoteApi { - private async Task<(bool Success, string Error)> TryConnect(CancellationToken cancellationToken, ILoggerFactory? loggerFactory = null, bool retry = true) + private readonly SemaphoreSlim connectLock = new(1, 1); + + private async Task<(bool Success, string Error)> TryConnect(CancellationToken cancellationToken, + ILoggerFactory? loggerFactory = null, bool retry = true) { using IDisposable? logScope = _logger.BeginScope("TryConnect"); @@ -27,7 +29,8 @@ namespace Pal.Client.Net return (false, Localization.ConnectionError_NotOnline); } - if (_channel == null || !(_channel.State == ConnectivityState.Ready || _channel.State == ConnectivityState.Idle)) + if (_channel == null || + !(_channel.State == ConnectivityState.Ready || _channel.State == ConnectivityState.Idle)) { Dispose(); @@ -48,97 +51,122 @@ namespace Pal.Client.Net cancellationToken.ThrowIfCancellationRequested(); - var accountClient = new AccountService.AccountServiceClient(_channel); - IAccountConfiguration? configuredAccount = _configuration.FindAccount(RemoteUrl); - if (configuredAccount == null) + _logger.LogTrace("Acquiring connect lock"); + await connectLock.WaitAsync(cancellationToken); + _logger.LogTrace("Obtained connect lock"); + + try { - _logger.LogInformation("No account information saved for {Url}, creating new account", RemoteUrl); - var createAccountReply = await accountClient.CreateAccountAsync(new CreateAccountRequest(), headers: UnauthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken); - if (createAccountReply.Success) + var accountClient = new AccountService.AccountServiceClient(_channel); + IAccountConfiguration? configuredAccount = _configuration.FindAccount(RemoteUrl); + if (configuredAccount == null) { - if (!Guid.TryParse(createAccountReply.AccountId, out Guid accountId)) - throw new InvalidOperationException("invalid account id returned"); - - configuredAccount = _configuration.CreateAccount(RemoteUrl, accountId); - _logger.LogInformation("Account created with id {AccountId}", accountId.ToPartialId()); - - _configurationManager.Save(_configuration); - } - else - { - _logger.LogError("Account creation failed with error {Error}", createAccountReply.Error); - if (createAccountReply.Error == CreateAccountError.UpgradeRequired && !_warnedAboutUpgrade) + _logger.LogInformation("No account information saved for {Url}, creating new account", RemoteUrl); + var createAccountReply = await accountClient.CreateAccountAsync(new CreateAccountRequest(), + headers: UnauthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), + cancellationToken: cancellationToken); + if (createAccountReply.Success) { - _chat.Error(Localization.ConnectionError_OldVersion); - _warnedAboutUpgrade = true; - } - return (false, string.Format(Localization.ConnectionError_CreateAccountFailed, createAccountReply.Error)); - } - } + if (!Guid.TryParse(createAccountReply.AccountId, out Guid accountId)) + throw new InvalidOperationException("invalid account id returned"); - cancellationToken.ThrowIfCancellationRequested(); + configuredAccount = _configuration.CreateAccount(RemoteUrl, accountId); + _logger.LogInformation("Account created with id {AccountId}", accountId.ToPartialId()); - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (configuredAccount == null) - { - _logger.LogWarning("No account to login with"); - return (false, Localization.ConnectionError_CreateAccountReturnedNoId); - } - - if (!_loginInfo.IsValid) - { - _logger.LogInformation("Logging in with account id {AccountId}", configuredAccount.AccountId.ToPartialId()); - LoginReply loginReply = await accountClient.LoginAsync(new LoginRequest { AccountId = configuredAccount.AccountId.ToString() }, headers: UnauthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken); - if (loginReply.Success) - { - _logger.LogInformation("Login successful with account id: {AccountId}", configuredAccount.AccountId.ToPartialId()); - _loginInfo = new LoginInfo(loginReply.AuthToken); - - bool save = configuredAccount.EncryptIfNeeded(); - - List newRoles = _loginInfo.Claims?.Roles.ToList() ?? new(); - if (!newRoles.SequenceEqual(configuredAccount.CachedRoles)) - { - configuredAccount.CachedRoles = newRoles; - save = true; - } - - if (save) _configurationManager.Save(_configuration); - } - else - { - _logger.LogError("Login failed with error {Error}", loginReply.Error); - _loginInfo = new LoginInfo(null); - if (loginReply.Error == LoginError.InvalidAccountId) + } + else { - _configuration.RemoveAccount(RemoteUrl); - _configurationManager.Save(_configuration); - if (retry) + _logger.LogError("Account creation failed with error {Error}", createAccountReply.Error); + if (createAccountReply.Error == CreateAccountError.UpgradeRequired && !_warnedAboutUpgrade) { - _logger.LogInformation("Attempting connection retry without account id"); - return await TryConnect(cancellationToken, retry: false); + _chat.Error(Localization.ConnectionError_OldVersion); + _warnedAboutUpgrade = true; } - else - return (false, Localization.ConnectionError_InvalidAccountId); + + return (false, + string.Format(Localization.ConnectionError_CreateAccountFailed, createAccountReply.Error)); } - if (loginReply.Error == LoginError.UpgradeRequired && !_warnedAboutUpgrade) - { - _chat.Error(Localization.ConnectionError_OldVersion); - _warnedAboutUpgrade = true; - } - return (false, string.Format(Localization.ConnectionError_LoginFailed, loginReply.Error)); } - } - if (!_loginInfo.IsValid) + cancellationToken.ThrowIfCancellationRequested(); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (configuredAccount == null) + { + _logger.LogWarning("No account to login with"); + return (false, Localization.ConnectionError_CreateAccountReturnedNoId); + } + + if (!_loginInfo.IsValid) + { + _logger.LogInformation("Logging in with account id {AccountId}", + configuredAccount.AccountId.ToPartialId()); + LoginReply loginReply = await accountClient.LoginAsync( + new LoginRequest { AccountId = configuredAccount.AccountId.ToString() }, + headers: UnauthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), + cancellationToken: cancellationToken); + + if (loginReply.Success) + { + _logger.LogInformation("Login successful with account id: {AccountId}", + configuredAccount.AccountId.ToPartialId()); + _loginInfo = new LoginInfo(loginReply.AuthToken); + + bool save = configuredAccount.EncryptIfNeeded(); + + List newRoles = _loginInfo.Claims?.Roles.ToList() ?? new(); + if (!newRoles.SequenceEqual(configuredAccount.CachedRoles)) + { + configuredAccount.CachedRoles = newRoles; + save = true; + } + + if (save) + _configurationManager.Save(_configuration); + } + else + { + _logger.LogError("Login failed with error {Error}", loginReply.Error); + _loginInfo = new LoginInfo(null); + if (loginReply.Error == LoginError.InvalidAccountId) + { + _configuration.RemoveAccount(RemoteUrl); + _configurationManager.Save(_configuration); + if (retry) + { + _logger.LogInformation("Attempting connection retry without account id"); + return await TryConnect(cancellationToken, retry: false); + } + else + return (false, Localization.ConnectionError_InvalidAccountId); + } + + if (loginReply.Error == LoginError.UpgradeRequired && !_warnedAboutUpgrade) + { + _chat.Error(Localization.ConnectionError_OldVersion); + _warnedAboutUpgrade = true; + } + + return (false, string.Format(Localization.ConnectionError_LoginFailed, loginReply.Error)); + } + } + + if (!_loginInfo.IsValid) + { + _logger.LogError("Login state is loggedIn={LoggedIn}, expired={Expired}", _loginInfo.IsLoggedIn, + _loginInfo.IsExpired); + return (false, Localization.ConnectionError_LoginReturnedNoToken); + } + + cancellationToken.ThrowIfCancellationRequested(); + return (true, string.Empty); + } + finally { - _logger.LogError("Login state is loggedIn={LoggedIn}, expired={Expired}", _loginInfo.IsLoggedIn, _loginInfo.IsExpired); - return (false, Localization.ConnectionError_LoginReturnedNoToken); + _logger.LogTrace("Releasing connectLock"); + connectLock.Release(); } - - cancellationToken.ThrowIfCancellationRequested(); - return (true, string.Empty); } private async Task Connect(CancellationToken cancellationToken) @@ -159,7 +187,8 @@ namespace Pal.Client.Net _logger.LogInformation("Connection established, trying to verify auth token"); var accountClient = new AccountService.AccountServiceClient(_channel); - await accountClient.VerifyAsync(new VerifyRequest(), headers: AuthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken); + await accountClient.VerifyAsync(new VerifyRequest(), headers: AuthorizedHeaders(), + deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken); _logger.LogInformation("Verification returned no errors."); return Localization.ConnectionSuccessful; @@ -182,7 +211,10 @@ namespace Pal.Client.Net public bool IsLoggedIn { get; } public string? AuthToken { get; } public JwtClaims? Claims { get; } - private DateTimeOffset ExpiresAt => Claims?.ExpiresAt.Subtract(TimeSpan.FromMinutes(5)) ?? DateTimeOffset.MinValue; + + private DateTimeOffset ExpiresAt => + Claims?.ExpiresAt.Subtract(TimeSpan.FromMinutes(5)) ?? DateTimeOffset.MinValue; + public bool IsExpired => ExpiresAt < DateTimeOffset.UtcNow; public bool IsValid => IsLoggedIn && !IsExpired; diff --git a/Pal.Client/Net/RemoteApi.PalaceService.cs b/Pal.Client/Net/RemoteApi.PalaceService.cs index cb4dbde..b706ecc 100644 --- a/Pal.Client/Net/RemoteApi.PalaceService.cs +++ b/Pal.Client/Net/RemoteApi.PalaceService.cs @@ -36,7 +36,7 @@ namespace Pal.Client.Net }; uploadRequest.Objects.AddRange(locations.Select(m => new PalaceObject { - Type = (ObjectType)m.Type, + Type = m.Type.ToObjectType(), X = m.Position.X, Y = m.Position.Y, Z = m.Position.Z diff --git a/Pal.Client/Net/RemoteApi.cs b/Pal.Client/Net/RemoteApi.cs index 6815210..70654da 100644 --- a/Pal.Client/Net/RemoteApi.cs +++ b/Pal.Client/Net/RemoteApi.cs @@ -11,7 +11,7 @@ namespace Pal.Client.Net internal sealed partial class RemoteApi : IDisposable { #if DEBUG - public const string RemoteUrl = "http://localhost:5145"; + public const string RemoteUrl = "http://localhost:5415"; #else //public const string RemoteUrl = "https://pal.liza.sh"; #endif diff --git a/Pal.Client/Plugin.cs b/Pal.Client/Plugin.cs index 62c5adf..c4b3503 100644 --- a/Pal.Client/Plugin.cs +++ b/Pal.Client/Plugin.cs @@ -36,7 +36,7 @@ namespace Pal.Client private readonly IServiceScope _rootScope; private readonly DependencyInjectionLoader _loader; - private Action? _loginAction = null; + private Action? _loginAction; public Plugin( DalamudPluginInterface pluginInterface, diff --git a/Pal.Client/Scheduled/QueuedSyncResponse.cs b/Pal.Client/Scheduled/QueuedSyncResponse.cs index beed1aa..519f5f1 100644 --- a/Pal.Client/Scheduled/QueuedSyncResponse.cs +++ b/Pal.Client/Scheduled/QueuedSyncResponse.cs @@ -9,6 +9,7 @@ using Pal.Client.Extensions; using Pal.Client.Floors; using Pal.Client.Floors.Tasks; using Pal.Client.Net; +using Pal.Common; namespace Pal.Client.Scheduled { @@ -47,12 +48,21 @@ namespace Pal.Client.Scheduled { recreateLayout = true; + _logger.LogDebug( + "Sync response for territory {Territory} of type {Type}, success = {Success}, response objects = {Count}", + (ETerritoryType)queued.TerritoryType, queued.Type, queued.Success, queued.Locations.Count); + var memoryTerritory = _floorService.GetTerritoryIfReady(queued.TerritoryType); + if (memoryTerritory == null) + { + _logger.LogWarning("Discarding sync response for territory {Territory} as it isn't ready", + (ETerritoryType)queued.TerritoryType); + return; + } + try { var remoteMarkers = queued.Locations; - var memoryTerritory = _floorService.GetTerritoryIfReady(queued.TerritoryType); - if (memoryTerritory != null && _configuration.Mode == EMode.Online && queued.Success && - remoteMarkers.Count > 0) + if (_configuration.Mode == EMode.Online && queued.Success && remoteMarkers.Count > 0) { switch (queued.Type) { @@ -117,16 +127,17 @@ namespace Pal.Client.Scheduled if (queued.Type == SyncType.Download) { if (queued.Success) - _territoryState.TerritorySyncState = ESyncState.Complete; + memoryTerritory.SyncState = ESyncState.Complete; else - _territoryState.TerritorySyncState = ESyncState.Failed; + memoryTerritory.SyncState = ESyncState.Failed; } } catch (Exception e) { + _logger.LogError(e, "Sync failed for territory {Territory}", (ETerritoryType)queued.TerritoryType); _debugState.SetFromException(e); if (queued.Type == SyncType.Download) - _territoryState.TerritorySyncState = ESyncState.Failed; + memoryTerritory.SyncState = ESyncState.Failed; } } } diff --git a/Pal.Client/Windows/ConfigWindow.cs b/Pal.Client/Windows/ConfigWindow.cs index 77b6e13..4e68c62 100644 --- a/Pal.Client/Windows/ConfigWindow.cs +++ b/Pal.Client/Windows/ConfigWindow.cs @@ -375,12 +375,12 @@ namespace Pal.Client.Windows { if (_territoryState.IsInDeepDungeon()) { + MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory); ImGui.Text($"You are in a deep dungeon, territory type {_territoryState.LastTerritory}."); - ImGui.Text($"Sync State = {_territoryState.TerritorySyncState}"); + ImGui.Text($"Sync State = {memoryTerritory?.SyncState.ToString() ?? "Unknown"}"); ImGui.Text($"{_debugState.DebugMessage}"); ImGui.Indent(); - MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory); if (memoryTerritory != null) { if (_trapConfig.Show)