Db: Fix various things around local persistence/net interactions

This commit is contained in:
Liza 2023-02-22 17:21:48 +01:00
parent 802e0c4cde
commit d5dc55a0c4
18 changed files with 235 additions and 124 deletions

View File

@ -12,6 +12,7 @@ using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging; using Dalamud.Logging;
using ImGuiNET; using ImGuiNET;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Pal.Client.Configuration; using Pal.Client.Configuration;
using Pal.Client.Extensions; using Pal.Client.Extensions;
using Pal.Client.Floors; using Pal.Client.Floors;
@ -25,6 +26,7 @@ namespace Pal.Client.DependencyInjection
internal sealed class FrameworkService : IDisposable internal sealed class FrameworkService : IDisposable
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly ILogger<FrameworkService> _logger;
private readonly Framework _framework; private readonly Framework _framework;
private readonly ConfigurationManager _configurationManager; private readonly ConfigurationManager _configurationManager;
private readonly IPalacePalConfiguration _configuration; private readonly IPalacePalConfiguration _configuration;
@ -42,6 +44,7 @@ namespace Pal.Client.DependencyInjection
public FrameworkService( public FrameworkService(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
ILogger<FrameworkService> logger,
Framework framework, Framework framework,
ConfigurationManager configurationManager, ConfigurationManager configurationManager,
IPalacePalConfiguration configuration, IPalacePalConfiguration configuration,
@ -54,6 +57,7 @@ namespace Pal.Client.DependencyInjection
RemoteApi remoteApi) RemoteApi remoteApi)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_logger = logger;
_framework = framework; _framework = framework;
_configurationManager = configurationManager; _configurationManager = configurationManager;
_configuration = configuration; _configuration = configuration;
@ -92,8 +96,11 @@ namespace Pal.Client.DependencyInjection
if (_territoryState.LastTerritory != _clientState.TerritoryType) if (_territoryState.LastTerritory != _clientState.TerritoryType)
{ {
MemoryTerritory? oldTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
if (oldTerritory != null)
oldTerritory.SyncState = ESyncState.NotAttempted;
_territoryState.LastTerritory = _clientState.TerritoryType; _territoryState.LastTerritory = _clientState.TerritoryType;
_territoryState.TerritorySyncState = ESyncState.NotAttempted;
NextUpdateObjects.Clear(); NextUpdateObjects.Clear();
_floorService.ChangeTerritory(_territoryState.LastTerritory); _floorService.ChangeTerritory(_territoryState.LastTerritory);
@ -106,11 +113,12 @@ namespace Pal.Client.DependencyInjection
if (!_territoryState.IsInDeepDungeon() || !_floorService.IsReady(_territoryState.LastTerritory)) if (!_territoryState.IsInDeepDungeon() || !_floorService.IsReady(_territoryState.LastTerritory))
return; return;
if (_configuration.Mode == EMode.Online && ETerritoryType territoryType = (ETerritoryType)_territoryState.LastTerritory;
_territoryState.TerritorySyncState == ESyncState.NotAttempted) MemoryTerritory memoryTerritory = _floorService.GetTerritoryIfReady(territoryType)!;
if (_configuration.Mode == EMode.Online && memoryTerritory.SyncState == ESyncState.NotAttempted)
{ {
_territoryState.TerritorySyncState = ESyncState.Started; memoryTerritory.SyncState = ESyncState.Started;
Task.Run(async () => await DownloadMarkersForTerritory(_territoryState.LastTerritory)); Task.Run(async () => await DownloadLocationsForTerritory(_territoryState.LastTerritory));
} }
while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued)) while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
@ -120,7 +128,6 @@ namespace Pal.Client.DependencyInjection
IReadOnlyList<EphemeralLocation> visibleEphemeralMarkers) = IReadOnlyList<EphemeralLocation> visibleEphemeralMarkers) =
GetRelevantGameObjects(); GetRelevantGameObjects();
ETerritoryType territoryType = (ETerritoryType)_territoryState.LastTerritory;
HandlePersistentLocations(territoryType, visiblePersistentMarkers, recreateLayout); HandlePersistentLocations(territoryType, visiblePersistentMarkers, recreateLayout);
if (_floorService.MergeEphemeralLocations(visibleEphemeralMarkers, recreateLayout)) if (_floorService.MergeEphemeralLocations(visibleEphemeralMarkers, recreateLayout))
@ -188,7 +195,7 @@ namespace Pal.Client.DependencyInjection
private void UploadLocations() private void UploadLocations()
{ {
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory); MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
if (memoryTerritory == null) if (memoryTerritory == null || memoryTerritory.SyncState != ESyncState.Complete)
return; return;
List<PersistentLocation> locationsToUpload = memoryTerritory.Locations List<PersistentLocation> locationsToUpload = memoryTerritory.Locations
@ -296,10 +303,11 @@ namespace Pal.Client.DependencyInjection
#region Up-/Download #region Up-/Download
private async Task DownloadMarkersForTerritory(ushort territoryId) private async Task DownloadLocationsForTerritory(ushort territoryId)
{ {
try try
{ {
_logger.LogInformation("Downloading territory {Territory} from server", (ETerritoryType)territoryId);
var (success, downloadedMarkers) = await _remoteApi.DownloadRemoteMarkers(territoryId); var (success, downloadedMarkers) = await _remoteApi.DownloadRemoteMarkers(territoryId);
LateEventQueue.Enqueue(new QueuedSyncResponse LateEventQueue.Enqueue(new QueuedSyncResponse
{ {
@ -319,6 +327,8 @@ namespace Pal.Client.DependencyInjection
{ {
try try
{ {
_logger.LogInformation("Uploading {Count} locations for territory {Territory} to server",
locationsToUpload.Count, (ETerritoryType)territoryId);
var (success, uploadedLocations) = await _remoteApi.UploadLocations(territoryId, locationsToUpload); var (success, uploadedLocations) = await _remoteApi.UploadLocations(territoryId, locationsToUpload);
LateEventQueue.Enqueue(new QueuedSyncResponse LateEventQueue.Enqueue(new QueuedSyncResponse
{ {
@ -339,6 +349,8 @@ namespace Pal.Client.DependencyInjection
{ {
try try
{ {
_logger.LogInformation("Syncing {Count} seen locations for territory {Territory} to server",
locationsToUpdate.Count, (ETerritoryType)territoryId);
var success = await _remoteApi.MarkAsSeen(territoryId, locationsToUpdate); var success = await _remoteApi.MarkAsSeen(territoryId, locationsToUpdate);
LateEventQueue.Enqueue(new QueuedSyncResponse LateEventQueue.Enqueue(new QueuedSyncResponse
{ {

View File

@ -17,7 +17,6 @@ namespace Pal.Client.DependencyInjection
} }
public ushort LastTerritory { get; set; } public ushort LastTerritory { get; set; }
public ESyncState TerritorySyncState { get; set; }
public PomanderState PomanderOfSight { get; set; } = PomanderState.Inactive; public PomanderState PomanderOfSight { get; set; } = PomanderState.Inactive;
public PomanderState PomanderOfIntuition { get; set; } = PomanderState.Inactive; public PomanderState PomanderOfIntuition { get; set; } = PomanderState.Inactive;

View File

@ -20,5 +20,10 @@ namespace Pal.Client.Floors
{ {
return !Equals(a, b); return !Equals(a, b);
} }
public override string ToString()
{
return $"EphemeralLocation(Position={Position}, Type={Type})";
}
} }
} }

View File

@ -111,7 +111,7 @@ namespace Pal.Client.Floors
} }
if (markAsSeen.Count > 0) if (markAsSeen.Count > 0)
new MarkAsSeen(_serviceScopeFactory, territory, markAsSeen).Start(); new MarkLocalSeen(_serviceScopeFactory, territory, markAsSeen).Start();
if (newLocations.Count > 0) if (newLocations.Count > 0)
new SaveNewLocations(_serviceScopeFactory, territory, newLocations).Start(); new SaveNewLocations(_serviceScopeFactory, territory, newLocations).Start();

View File

@ -22,8 +22,8 @@ namespace Pal.Client.Floors
{ {
Unknown, Unknown,
Hoard,
Trap, Trap,
Hoard,
SilverCoffer, SilverCoffer,
} }
@ -52,5 +52,15 @@ namespace Pal.Client.Floors
_ => throw new ArgumentOutOfRangeException(nameof(objectType), objectType, null) _ => 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)
};
}
} }
} }

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Pal.Client.Configuration; using Pal.Client.Configuration;
using Pal.Client.Scheduled;
using Pal.Common; using Pal.Common;
namespace Pal.Client.Floors namespace Pal.Client.Floors
@ -18,7 +19,8 @@ namespace Pal.Client.Floors
public ETerritoryType TerritoryType { get; } public ETerritoryType TerritoryType { get; }
public bool IsReady { get; set; } 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<PersistentLocation> Locations { get; } = new(); public ConcurrentBag<PersistentLocation> Locations { get; } = new();
public object LockObj { get; } = new(); public object LockObj { get; } = new();

View File

@ -44,5 +44,10 @@ namespace Pal.Client.Floors
{ {
return !Equals(a, b); return !Equals(a, b);
} }
public override string ToString()
{
return $"PersistentLocation(Position={Position}, Type={Type})";
}
} }
} }

View File

@ -1,10 +1,13 @@
using System.Threading.Tasks; using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Pal.Client.Database; using Pal.Client.Database;
namespace Pal.Client.Floors.Tasks namespace Pal.Client.Floors.Tasks
{ {
internal abstract class DbTask internal abstract class DbTask<T>
where T : DbTask<T>
{ {
private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IServiceScopeFactory _serviceScopeFactory;
@ -18,12 +21,13 @@ namespace Pal.Client.Floors.Tasks
Task.Run(() => Task.Run(() =>
{ {
using var scope = _serviceScopeFactory.CreateScope(); using var scope = _serviceScopeFactory.CreateScope();
ILogger<T> logger = scope.ServiceProvider.GetRequiredService<ILogger<T>>();
using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>(); using var dbContext = scope.ServiceProvider.GetRequiredService<PalClientContext>();
Run(dbContext); Run(dbContext, logger);
}); });
} }
protected abstract void Run(PalClientContext dbContext); protected abstract void Run(PalClientContext dbContext, ILogger<T> logger);
} }
} }

View File

@ -4,11 +4,12 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Pal.Client.Database; using Pal.Client.Database;
namespace Pal.Client.Floors.Tasks namespace Pal.Client.Floors.Tasks
{ {
internal sealed class LoadTerritory : DbTask internal sealed class LoadTerritory : DbTask<LoadTerritory>
{ {
private readonly MemoryTerritory _territory; private readonly MemoryTerritory _territory;
@ -18,19 +19,27 @@ namespace Pal.Client.Floors.Tasks
_territory = territory; _territory = territory;
} }
protected override void Run(PalClientContext dbContext) protected override void Run(PalClientContext dbContext, ILogger<LoadTerritory> logger)
{ {
lock (_territory.LockObj) lock (_territory.LockObj)
{ {
if (_territory.IsReady) if (_territory.IsReady)
{
logger.LogInformation("Territory {Territory} is already loaded", _territory.TerritoryType);
return; return;
}
logger.LogInformation("Loading territory {Territory}", _territory.TerritoryType);
List<ClientLocation> locations = dbContext.Locations List<ClientLocation> locations = dbContext.Locations
.Where(o => o.TerritoryType == (ushort)_territory.TerritoryType) .Where(o => o.TerritoryType == (ushort)_territory.TerritoryType)
.Include(o => o.ImportedBy) .Include(o => o.ImportedBy)
.Include(o => o.RemoteEncounters) .Include(o => o.RemoteEncounters)
.AsSplitQuery()
.ToList(); .ToList();
_territory.Initialize(locations.Select(ToMemoryLocation)); _territory.Initialize(locations.Select(ToMemoryLocation));
logger.LogInformation("Loaded {Count} locations for territory {Territory}", locations.Count,
_territory.TerritoryType);
} }
} }

View File

@ -2,16 +2,17 @@
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Pal.Client.Database; using Pal.Client.Database;
namespace Pal.Client.Floors.Tasks namespace Pal.Client.Floors.Tasks
{ {
internal sealed class MarkAsSeen : DbTask internal sealed class MarkLocalSeen : DbTask<MarkLocalSeen>
{ {
private readonly MemoryTerritory _territory; private readonly MemoryTerritory _territory;
private readonly IReadOnlyList<PersistentLocation> _locations; private readonly IReadOnlyList<PersistentLocation> _locations;
public MarkAsSeen(IServiceScopeFactory serviceScopeFactory, MemoryTerritory territory, public MarkLocalSeen(IServiceScopeFactory serviceScopeFactory, MemoryTerritory territory,
IReadOnlyList<PersistentLocation> locations) IReadOnlyList<PersistentLocation> locations)
: base(serviceScopeFactory) : base(serviceScopeFactory)
{ {
@ -19,10 +20,12 @@ namespace Pal.Client.Floors.Tasks
_locations = locations; _locations = locations;
} }
protected override void Run(PalClientContext dbContext) protected override void Run(PalClientContext dbContext, ILogger<MarkLocalSeen> logger)
{ {
lock (_territory.LockObj) lock (_territory.LockObj)
{ {
logger.LogInformation("Marking {Count} locations as seen locally in territory {Territory}", _locations.Count,
_territory.TerritoryType);
dbContext.Locations dbContext.Locations
.Where(loc => _locations.Any(l => l.LocalId == loc.LocalId)) .Where(loc => _locations.Any(l => l.LocalId == loc.LocalId))
.ExecuteUpdate(loc => loc.SetProperty(l => l.Seen, true)); .ExecuteUpdate(loc => loc.SetProperty(l => l.Seen, true));

View File

@ -1,11 +1,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Pal.Client.Database; using Pal.Client.Database;
namespace Pal.Client.Floors.Tasks namespace Pal.Client.Floors.Tasks
{ {
internal sealed class MarkRemoteSeen : DbTask internal sealed class MarkRemoteSeen : DbTask<MarkRemoteSeen>
{ {
private readonly MemoryTerritory _territory; private readonly MemoryTerritory _territory;
private readonly IReadOnlyList<PersistentLocation> _locations; private readonly IReadOnlyList<PersistentLocation> _locations;
@ -22,16 +24,26 @@ namespace Pal.Client.Floors.Tasks
_accountId = accountId; _accountId = accountId;
} }
protected override void Run(PalClientContext dbContext) protected override void Run(PalClientContext dbContext, ILogger<MarkRemoteSeen> logger)
{ {
lock (_territory.LockObj) lock (_territory.LockObj)
{ {
List<ClientLocation> locationsToUpdate = dbContext.Locations logger.LogInformation("Marking {Count} locations as seen remotely on {Account} in territory {Territory}",
.Where(loc => _locations.Any(l => _locations.Count, _accountId, _territory.TerritoryType);
l.LocalId == loc.LocalId && loc.RemoteEncounters.All(r => r.AccountId != _accountId)))
.ToList(); List<int> locationIds = _locations.Select(x => x.LocalId).Where(x => x != null).Cast<int>().ToList();
List<ClientLocation> 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) foreach (var clientLocation in locationsToUpdate)
{
clientLocation.RemoteEncounters.Add(new RemoteEncounter(clientLocation, _accountId)); clientLocation.RemoteEncounters.Add(new RemoteEncounter(clientLocation, _accountId));
}
dbContext.SaveChanges(); dbContext.SaveChanges();
} }
} }

View File

@ -2,12 +2,13 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Pal.Client.Database; using Pal.Client.Database;
using Pal.Common; using Pal.Common;
namespace Pal.Client.Floors.Tasks namespace Pal.Client.Floors.Tasks
{ {
internal sealed class SaveNewLocations : DbTask internal sealed class SaveNewLocations : DbTask<SaveNewLocations>
{ {
private readonly MemoryTerritory _territory; private readonly MemoryTerritory _territory;
private readonly List<PersistentLocation> _newLocations; private readonly List<PersistentLocation> _newLocations;
@ -20,16 +21,22 @@ namespace Pal.Client.Floors.Tasks
_newLocations = newLocations; _newLocations = newLocations;
} }
protected override void Run(PalClientContext dbContext) protected override void Run(PalClientContext dbContext, ILogger<SaveNewLocations> logger)
{ {
Run(_territory, dbContext, _newLocations); Run(_territory, dbContext, logger, _newLocations);
} }
public static void Run(MemoryTerritory territory, PalClientContext dbContext, public static void Run<T>(
MemoryTerritory territory,
PalClientContext dbContext,
ILogger<T> logger,
List<PersistentLocation> locations) List<PersistentLocation> locations)
{ {
lock (territory.LockObj) lock (territory.LockObj)
{ {
logger.LogInformation("Saving {Count} new locations for territory {Territory}", locations.Count,
territory.TerritoryType);
Dictionary<PersistentLocation, ClientLocation> mapping = Dictionary<PersistentLocation, ClientLocation> mapping =
locations.ToDictionary(x => x, x => ToDatabaseLocation(x, territory.TerritoryType)); locations.ToDictionary(x => x, x => ToDatabaseLocation(x, territory.TerritoryType));
dbContext.Locations.AddRange(mapping.Values); dbContext.Locations.AddRange(mapping.Values);

View File

@ -11,13 +11,15 @@ using System.Threading.Tasks;
using Pal.Client.Extensions; using Pal.Client.Extensions;
using Pal.Client.Properties; using Pal.Client.Properties;
using Pal.Client.Configuration; using Pal.Client.Configuration;
using Pal.Client.DependencyInjection;
namespace Pal.Client.Net namespace Pal.Client.Net
{ {
internal partial class RemoteApi 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"); using IDisposable? logScope = _logger.BeginScope("TryConnect");
@ -27,7 +29,8 @@ namespace Pal.Client.Net
return (false, Localization.ConnectionError_NotOnline); 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(); Dispose();
@ -48,97 +51,122 @@ namespace Pal.Client.Net
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var accountClient = new AccountService.AccountServiceClient(_channel); _logger.LogTrace("Acquiring connect lock");
IAccountConfiguration? configuredAccount = _configuration.FindAccount(RemoteUrl); await connectLock.WaitAsync(cancellationToken);
if (configuredAccount == null) _logger.LogTrace("Obtained connect lock");
try
{ {
_logger.LogInformation("No account information saved for {Url}, creating new account", RemoteUrl); var accountClient = new AccountService.AccountServiceClient(_channel);
var createAccountReply = await accountClient.CreateAccountAsync(new CreateAccountRequest(), headers: UnauthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10), cancellationToken: cancellationToken); IAccountConfiguration? configuredAccount = _configuration.FindAccount(RemoteUrl);
if (createAccountReply.Success) if (configuredAccount == null)
{ {
if (!Guid.TryParse(createAccountReply.AccountId, out Guid accountId)) _logger.LogInformation("No account information saved for {Url}, creating new account", RemoteUrl);
throw new InvalidOperationException("invalid account id returned"); var createAccountReply = await accountClient.CreateAccountAsync(new CreateAccountRequest(),
headers: UnauthorizedHeaders(), deadline: DateTime.UtcNow.AddSeconds(10),
configuredAccount = _configuration.CreateAccount(RemoteUrl, accountId); cancellationToken: cancellationToken);
_logger.LogInformation("Account created with id {AccountId}", accountId.ToPartialId()); if (createAccountReply.Success)
_configurationManager.Save(_configuration);
}
else
{
_logger.LogError("Account creation failed with error {Error}", createAccountReply.Error);
if (createAccountReply.Error == CreateAccountError.UpgradeRequired && !_warnedAboutUpgrade)
{ {
_chat.Error(Localization.ConnectionError_OldVersion); if (!Guid.TryParse(createAccountReply.AccountId, out Guid accountId))
_warnedAboutUpgrade = true; throw new InvalidOperationException("invalid account id returned");
}
return (false, string.Format(Localization.ConnectionError_CreateAccountFailed, createAccountReply.Error));
}
}
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<string> newRoles = _loginInfo.Claims?.Roles.ToList() ?? new();
if (!newRoles.SequenceEqual(configuredAccount.CachedRoles))
{
configuredAccount.CachedRoles = newRoles;
save = true;
}
if (save)
_configurationManager.Save(_configuration); _configurationManager.Save(_configuration);
} }
else else
{
_logger.LogError("Login failed with error {Error}", loginReply.Error);
_loginInfo = new LoginInfo(null);
if (loginReply.Error == LoginError.InvalidAccountId)
{ {
_configuration.RemoveAccount(RemoteUrl); _logger.LogError("Account creation failed with error {Error}", createAccountReply.Error);
_configurationManager.Save(_configuration); if (createAccountReply.Error == CreateAccountError.UpgradeRequired && !_warnedAboutUpgrade)
if (retry)
{ {
_logger.LogInformation("Attempting connection retry without account id"); _chat.Error(Localization.ConnectionError_OldVersion);
return await TryConnect(cancellationToken, retry: false); _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<string> 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); _logger.LogTrace("Releasing connectLock");
return (false, Localization.ConnectionError_LoginReturnedNoToken); connectLock.Release();
} }
cancellationToken.ThrowIfCancellationRequested();
return (true, string.Empty);
} }
private async Task<bool> Connect(CancellationToken cancellationToken) private async Task<bool> Connect(CancellationToken cancellationToken)
@ -159,7 +187,8 @@ namespace Pal.Client.Net
_logger.LogInformation("Connection established, trying to verify auth token"); _logger.LogInformation("Connection established, trying to verify auth token");
var accountClient = new AccountService.AccountServiceClient(_channel); 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."); _logger.LogInformation("Verification returned no errors.");
return Localization.ConnectionSuccessful; return Localization.ConnectionSuccessful;
@ -182,7 +211,10 @@ namespace Pal.Client.Net
public bool IsLoggedIn { get; } public bool IsLoggedIn { get; }
public string? AuthToken { get; } public string? AuthToken { get; }
public JwtClaims? Claims { 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 IsExpired => ExpiresAt < DateTimeOffset.UtcNow;
public bool IsValid => IsLoggedIn && !IsExpired; public bool IsValid => IsLoggedIn && !IsExpired;

View File

@ -36,7 +36,7 @@ namespace Pal.Client.Net
}; };
uploadRequest.Objects.AddRange(locations.Select(m => new PalaceObject uploadRequest.Objects.AddRange(locations.Select(m => new PalaceObject
{ {
Type = (ObjectType)m.Type, Type = m.Type.ToObjectType(),
X = m.Position.X, X = m.Position.X,
Y = m.Position.Y, Y = m.Position.Y,
Z = m.Position.Z Z = m.Position.Z

View File

@ -11,7 +11,7 @@ namespace Pal.Client.Net
internal sealed partial class RemoteApi : IDisposable internal sealed partial class RemoteApi : IDisposable
{ {
#if DEBUG #if DEBUG
public const string RemoteUrl = "http://localhost:5145"; public const string RemoteUrl = "http://localhost:5415";
#else #else
//public const string RemoteUrl = "https://pal.liza.sh"; //public const string RemoteUrl = "https://pal.liza.sh";
#endif #endif

View File

@ -36,7 +36,7 @@ namespace Pal.Client
private readonly IServiceScope _rootScope; private readonly IServiceScope _rootScope;
private readonly DependencyInjectionLoader _loader; private readonly DependencyInjectionLoader _loader;
private Action? _loginAction = null; private Action? _loginAction;
public Plugin( public Plugin(
DalamudPluginInterface pluginInterface, DalamudPluginInterface pluginInterface,

View File

@ -9,6 +9,7 @@ using Pal.Client.Extensions;
using Pal.Client.Floors; using Pal.Client.Floors;
using Pal.Client.Floors.Tasks; using Pal.Client.Floors.Tasks;
using Pal.Client.Net; using Pal.Client.Net;
using Pal.Common;
namespace Pal.Client.Scheduled namespace Pal.Client.Scheduled
{ {
@ -47,12 +48,21 @@ namespace Pal.Client.Scheduled
{ {
recreateLayout = true; 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 try
{ {
var remoteMarkers = queued.Locations; var remoteMarkers = queued.Locations;
var memoryTerritory = _floorService.GetTerritoryIfReady(queued.TerritoryType); if (_configuration.Mode == EMode.Online && queued.Success && remoteMarkers.Count > 0)
if (memoryTerritory != null && _configuration.Mode == EMode.Online && queued.Success &&
remoteMarkers.Count > 0)
{ {
switch (queued.Type) switch (queued.Type)
{ {
@ -117,16 +127,17 @@ namespace Pal.Client.Scheduled
if (queued.Type == SyncType.Download) if (queued.Type == SyncType.Download)
{ {
if (queued.Success) if (queued.Success)
_territoryState.TerritorySyncState = ESyncState.Complete; memoryTerritory.SyncState = ESyncState.Complete;
else else
_territoryState.TerritorySyncState = ESyncState.Failed; memoryTerritory.SyncState = ESyncState.Failed;
} }
} }
catch (Exception e) catch (Exception e)
{ {
_logger.LogError(e, "Sync failed for territory {Territory}", (ETerritoryType)queued.TerritoryType);
_debugState.SetFromException(e); _debugState.SetFromException(e);
if (queued.Type == SyncType.Download) if (queued.Type == SyncType.Download)
_territoryState.TerritorySyncState = ESyncState.Failed; memoryTerritory.SyncState = ESyncState.Failed;
} }
} }
} }

View File

@ -375,12 +375,12 @@ namespace Pal.Client.Windows
{ {
if (_territoryState.IsInDeepDungeon()) if (_territoryState.IsInDeepDungeon())
{ {
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
ImGui.Text($"You are in a deep dungeon, territory type {_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.Text($"{_debugState.DebugMessage}");
ImGui.Indent(); ImGui.Indent();
MemoryTerritory? memoryTerritory = _floorService.GetTerritoryIfReady(_territoryState.LastTerritory);
if (memoryTerritory != null) if (memoryTerritory != null)
{ {
if (_trapConfig.Show) if (_trapConfig.Show)