DI: Split QueueHandler into multiple classes

This commit is contained in:
Liza 2023-02-16 10:25:33 +01:00
parent 29aefee135
commit 7d04cd7575
8 changed files with 352 additions and 269 deletions

View File

@ -69,7 +69,6 @@ namespace Pal.Client.DependencyInjection
services.AddSingleton<FrameworkService>(); services.AddSingleton<FrameworkService>();
services.AddSingleton<ChatService>(); services.AddSingleton<ChatService>();
services.AddSingleton<FloorService>(); services.AddSingleton<FloorService>();
services.AddSingleton<QueueHandler>();
// windows & related services // windows & related services
services.AddSingleton<AgreementWindow>(); services.AddSingleton<AgreementWindow>();
@ -82,6 +81,12 @@ namespace Pal.Client.DependencyInjection
services.AddSingleton<SplatoonRenderer>(); services.AddSingleton<SplatoonRenderer>();
services.AddSingleton<RenderAdapter>(); services.AddSingleton<RenderAdapter>();
// queue handling
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedImport>, QueuedImport.Handler>();
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedUndoImport>, QueuedUndoImport.Handler>();
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedConfigUpdate>, QueuedConfigUpdate.Handler>();
services.AddTransient<IQueueOnFrameworkThread.Handler<QueuedSyncResponse>, QueuedSyncResponse.Handler>();
// set up the current UI language before creating anything // set up the current UI language before creating anything
Localization.Culture = new CultureInfo(pluginInterface.UiLanguage); Localization.Culture = new CultureInfo(pluginInterface.UiLanguage);

View File

@ -9,7 +9,9 @@ using Dalamud.Game;
using Dalamud.Game.ClientState; using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Logging;
using ImGuiNET; using ImGuiNET;
using Microsoft.Extensions.DependencyInjection;
using Pal.Client.Configuration; using Pal.Client.Configuration;
using Pal.Client.Extensions; using Pal.Client.Extensions;
using Pal.Client.Net; using Pal.Client.Net;
@ -20,6 +22,7 @@ namespace Pal.Client.DependencyInjection
{ {
internal sealed class FrameworkService : IDisposable internal sealed class FrameworkService : IDisposable
{ {
private readonly IServiceProvider _serviceProvider;
private readonly Framework _framework; private readonly Framework _framework;
private readonly ConfigurationManager _configurationManager; private readonly ConfigurationManager _configurationManager;
private readonly IPalacePalConfiguration _configuration; private readonly IPalacePalConfiguration _configuration;
@ -28,7 +31,6 @@ namespace Pal.Client.DependencyInjection
private readonly FloorService _floorService; private readonly FloorService _floorService;
private readonly DebugState _debugState; private readonly DebugState _debugState;
private readonly RenderAdapter _renderAdapter; private readonly RenderAdapter _renderAdapter;
private readonly QueueHandler _queueHandler;
private readonly ObjectTable _objectTable; private readonly ObjectTable _objectTable;
private readonly RemoteApi _remoteApi; private readonly RemoteApi _remoteApi;
@ -36,7 +38,9 @@ namespace Pal.Client.DependencyInjection
internal Queue<IQueueOnFrameworkThread> LateEventQueue { get; } = new(); internal Queue<IQueueOnFrameworkThread> LateEventQueue { get; } = new();
internal ConcurrentQueue<nint> NextUpdateObjects { get; } = new(); internal ConcurrentQueue<nint> NextUpdateObjects { get; } = new();
public FrameworkService(Framework framework, public FrameworkService(
IServiceProvider serviceProvider,
Framework framework,
ConfigurationManager configurationManager, ConfigurationManager configurationManager,
IPalacePalConfiguration configuration, IPalacePalConfiguration configuration,
ClientState clientState, ClientState clientState,
@ -44,10 +48,10 @@ namespace Pal.Client.DependencyInjection
FloorService floorService, FloorService floorService,
DebugState debugState, DebugState debugState,
RenderAdapter renderAdapter, RenderAdapter renderAdapter,
QueueHandler queueHandler,
ObjectTable objectTable, ObjectTable objectTable,
RemoteApi remoteApi) RemoteApi remoteApi)
{ {
_serviceProvider = serviceProvider;
_framework = framework; _framework = framework;
_configurationManager = configurationManager; _configurationManager = configurationManager;
_configuration = configuration; _configuration = configuration;
@ -56,7 +60,6 @@ namespace Pal.Client.DependencyInjection
_floorService = floorService; _floorService = floorService;
_debugState = debugState; _debugState = debugState;
_renderAdapter = renderAdapter; _renderAdapter = renderAdapter;
_queueHandler = queueHandler;
_objectTable = objectTable; _objectTable = objectTable;
_remoteApi = remoteApi; _remoteApi = remoteApi;
@ -84,7 +87,7 @@ namespace Pal.Client.DependencyInjection
bool saveMarkers = false; bool saveMarkers = false;
while (EarlyEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued)) while (EarlyEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
_queueHandler.Handle(queued, ref recreateLayout, ref saveMarkers); HandleQueued(queued, ref recreateLayout, ref saveMarkers);
if (_territoryState.LastTerritory != _clientState.TerritoryType) if (_territoryState.LastTerritory != _clientState.TerritoryType)
{ {
@ -111,12 +114,13 @@ namespace Pal.Client.DependencyInjection
} }
while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued)) while (LateEventQueue.TryDequeue(out IQueueOnFrameworkThread? queued))
_queueHandler.Handle(queued, ref recreateLayout, ref saveMarkers); HandleQueued(queued, ref recreateLayout, ref saveMarkers);
var currentFloor = _floorService.GetFloorMarkers(_territoryState.LastTerritory); var currentFloor = _floorService.GetFloorMarkers(_territoryState.LastTerritory);
IList<Marker> visibleMarkers = GetRelevantGameObjects(); IList<Marker> visibleMarkers = GetRelevantGameObjects();
HandlePersistentMarkers(currentFloor, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers, recreateLayout); HandlePersistentMarkers(currentFloor, visibleMarkers.Where(x => x.IsPermanent()).ToList(), saveMarkers,
recreateLayout);
HandleEphemeralMarkers(visibleMarkers.Where(x => !x.IsPermanent()).ToList(), recreateLayout); HandleEphemeralMarkers(visibleMarkers.Where(x => !x.IsPermanent()).ToList(), recreateLayout);
} }
catch (Exception e) catch (Exception e)
@ -126,7 +130,9 @@ namespace Pal.Client.DependencyInjection
} }
#region Render Markers #region Render Markers
private void HandlePersistentMarkers(LocalState currentFloor, IList<Marker> visibleMarkers, bool saveMarkers, bool recreateLayout)
private void HandlePersistentMarkers(LocalState currentFloor, IList<Marker> visibleMarkers, bool saveMarkers,
bool recreateLayout)
{ {
var currentFloorMarkers = currentFloor.Markers; var currentFloorMarkers = currentFloor.Markers;
@ -145,7 +151,8 @@ namespace Pal.Client.DependencyInjection
// This requires you to have seen a trap/hoard marker once per floor to synchronize this for older local states, // This requires you to have seen a trap/hoard marker once per floor to synchronize this for older local states,
// markers discovered afterwards are automatically marked seen. // markers discovered afterwards are automatically marked seen.
if (partialAccountId != null && knownMarker is { NetworkId: { }, RemoteSeenRequested: false } && !knownMarker.RemoteSeenOn.Contains(partialAccountId)) if (partialAccountId != null && knownMarker is { NetworkId: { }, RemoteSeenRequested: false } &&
!knownMarker.RemoteSeenOn.Contains(partialAccountId))
updateSeenMarkers = true; updateSeenMarkers = true;
continue; continue;
@ -156,9 +163,10 @@ namespace Pal.Client.DependencyInjection
saveMarkers = true; saveMarkers = true;
} }
if (!recreateLayout && currentFloorMarkers.Count > 0 && (_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander || _configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander)) if (!recreateLayout && currentFloorMarkers.Count > 0 &&
(_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander ||
_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander))
{ {
try try
{ {
foreach (var marker in currentFloorMarkers) foreach (var marker in currentFloorMarkers)
@ -183,7 +191,9 @@ namespace Pal.Client.DependencyInjection
if (updateSeenMarkers && partialAccountId != null) if (updateSeenMarkers && partialAccountId != null)
{ {
var markersToUpdate = currentFloorMarkers.Where(x => x is { Seen: true, NetworkId: { }, RemoteSeenRequested: false } && !x.RemoteSeenOn.Contains(partialAccountId)).ToList(); var markersToUpdate = currentFloorMarkers.Where(x =>
x is { Seen: true, NetworkId: { }, RemoteSeenRequested: false } &&
!x.RemoteSeenOn.Contains(partialAccountId)).ToList();
foreach (var marker in markersToUpdate) foreach (var marker in markersToUpdate)
marker.RemoteSeenRequested = true; marker.RemoteSeenRequested = true;
Task.Run(async () => await SyncSeenMarkersForTerritory(_territoryState.LastTerritory, markersToUpdate)); Task.Run(async () => await SyncSeenMarkersForTerritory(_territoryState.LastTerritory, markersToUpdate));
@ -195,12 +205,14 @@ namespace Pal.Client.DependencyInjection
if (_territoryState.TerritorySyncState == SyncState.Complete) if (_territoryState.TerritorySyncState == SyncState.Complete)
{ {
var markersToUpload = currentFloorMarkers.Where(x => x.IsPermanent() && x.NetworkId == null && !x.UploadRequested).ToList(); var markersToUpload = currentFloorMarkers
.Where(x => x.IsPermanent() && x.NetworkId == null && !x.UploadRequested).ToList();
if (markersToUpload.Count > 0) if (markersToUpload.Count > 0)
{ {
foreach (var marker in markersToUpload) foreach (var marker in markersToUpload)
marker.UploadRequested = true; marker.UploadRequested = true;
Task.Run(async () => await UploadMarkersForTerritory(_territoryState.LastTerritory, markersToUpload)); Task.Run(async () =>
await UploadMarkersForTerritory(_territoryState.LastTerritory, markersToUpload));
} }
} }
} }
@ -212,15 +224,18 @@ namespace Pal.Client.DependencyInjection
List<IRenderElement> elements = new(); List<IRenderElement> elements = new();
foreach (var marker in currentFloorMarkers) foreach (var marker in currentFloorMarkers)
{ {
if (marker.Seen || _configuration.Mode == EMode.Online || marker is { WasImported: true, Imports.Count: > 0 }) if (marker.Seen || _configuration.Mode == EMode.Online ||
marker is { WasImported: true, Imports.Count: > 0 })
{ {
if (marker.Type == Marker.EType.Trap) if (marker.Type == Marker.EType.Trap)
{ {
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), _configuration.DeepDungeons.Traps); CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers),
_configuration.DeepDungeons.Traps);
} }
else if (marker.Type == Marker.EType.Hoard) else if (marker.Type == Marker.EType.Hoard)
{ {
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), _configuration.DeepDungeons.HoardCoffers); CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers),
_configuration.DeepDungeons.HoardCoffers);
} }
} }
} }
@ -234,8 +249,10 @@ namespace Pal.Client.DependencyInjection
private void HandleEphemeralMarkers(IList<Marker> visibleMarkers, bool recreateLayout) private void HandleEphemeralMarkers(IList<Marker> visibleMarkers, bool recreateLayout)
{ {
recreateLayout |= _floorService.EphemeralMarkers.Any(existingMarker => visibleMarkers.All(x => x != existingMarker)); recreateLayout |=
recreateLayout |= visibleMarkers.Any(visibleMarker => _floorService.EphemeralMarkers.All(x => x != visibleMarker)); _floorService.EphemeralMarkers.Any(existingMarker => visibleMarkers.All(x => x != existingMarker));
recreateLayout |=
visibleMarkers.Any(visibleMarker => _floorService.EphemeralMarkers.All(x => x != visibleMarker));
if (recreateLayout) if (recreateLayout)
{ {
@ -249,7 +266,8 @@ namespace Pal.Client.DependencyInjection
if (marker.Type == Marker.EType.SilverCoffer && _configuration.DeepDungeons.SilverCoffers.Show) if (marker.Type == Marker.EType.SilverCoffer && _configuration.DeepDungeons.SilverCoffers.Show)
{ {
CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers), _configuration.DeepDungeons.SilverCoffers); CreateRenderElement(marker, elements, DetermineColor(marker, visibleMarkers),
_configuration.DeepDungeons.SilverCoffers);
} }
} }
@ -264,9 +282,13 @@ namespace Pal.Client.DependencyInjection
{ {
switch (marker.Type) switch (marker.Type)
{ {
case Marker.EType.Trap when _territoryState.PomanderOfSight == PomanderState.Inactive || !_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander || visibleMarkers.Any(x => x == marker): case Marker.EType.Trap when _territoryState.PomanderOfSight == PomanderState.Inactive ||
!_configuration.DeepDungeons.Traps.OnlyVisibleAfterPomander ||
visibleMarkers.Any(x => x == marker):
return _configuration.DeepDungeons.Traps.Color; return _configuration.DeepDungeons.Traps.Color;
case Marker.EType.Hoard when _territoryState.PomanderOfIntuition == PomanderState.Inactive || !_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander || visibleMarkers.Any(x => x == marker): case Marker.EType.Hoard when _territoryState.PomanderOfIntuition == PomanderState.Inactive ||
!_configuration.DeepDungeons.HoardCoffers.OnlyVisibleAfterPomander ||
visibleMarkers.Any(x => x == marker):
return _configuration.DeepDungeons.HoardCoffers.Color; return _configuration.DeepDungeons.HoardCoffers.Color;
case Marker.EType.SilverCoffer: case Marker.EType.SilverCoffer:
return _configuration.DeepDungeons.SilverCoffers.Color; return _configuration.DeepDungeons.SilverCoffers.Color;
@ -278,7 +300,8 @@ namespace Pal.Client.DependencyInjection
} }
} }
private void CreateRenderElement(Marker marker, List<IRenderElement> elements, uint color, MarkerConfiguration config) private void CreateRenderElement(Marker marker, List<IRenderElement> elements, uint color,
MarkerConfiguration config)
{ {
if (!config.Show) if (!config.Show)
return; return;
@ -287,9 +310,11 @@ namespace Pal.Client.DependencyInjection
marker.RenderElement = element; marker.RenderElement = element;
elements.Add(element); elements.Add(element);
} }
#endregion #endregion
#region Up-/Download #region Up-/Download
private async Task DownloadMarkersForTerritory(ushort territoryId) private async Task DownloadMarkersForTerritory(ushort territoryId)
{ {
try try
@ -346,6 +371,7 @@ namespace Pal.Client.DependencyInjection
_debugState.SetFromException(e); _debugState.SetFromException(e);
} }
} }
#endregion #endregion
private IList<Marker> GetRelevantGameObjects() private IList<Marker> GetRelevantGameObjects()
@ -388,5 +414,13 @@ namespace Pal.Client.DependencyInjection
return result; return result;
} }
private void HandleQueued(IQueueOnFrameworkThread queued, ref bool recreateLayout, ref bool saveMarkers)
{
Type handlerType = typeof(IQueueOnFrameworkThread.Handler<>).MakeGenericType(queued.GetType());
var handler = (IQueueOnFrameworkThread.IHandler)_serviceProvider.GetRequiredService(handlerType);
handler.RunIfCompatible(queued, ref recreateLayout, ref saveMarkers);
}
} }
} }

View File

@ -1,6 +1,31 @@
namespace Pal.Client.Scheduled using Dalamud.Logging;
namespace Pal.Client.Scheduled
{ {
internal interface IQueueOnFrameworkThread internal interface IQueueOnFrameworkThread
{ {
internal interface IHandler
{
void RunIfCompatible(IQueueOnFrameworkThread queued, ref bool recreateLayout, ref bool saveMarkers);
}
internal abstract class Handler<T> : IHandler
where T : IQueueOnFrameworkThread
{
protected abstract void Run(T queued, ref bool recreateLayout, ref bool saveMarkers);
public void RunIfCompatible(IQueueOnFrameworkThread queued, ref bool recreateLayout, ref bool saveMarkers)
{
if (queued is T t)
{
PluginLog.Information($"Handling {queued.GetType()} with handler {GetType()}");
Run(t, ref recreateLayout, ref saveMarkers);
}
else
{
PluginLog.Error($"Could not use queue handler {GetType()} with type {queued.GetType()}");
}
}
}
} }
} }

View File

@ -1,203 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Gui;
using Dalamud.Logging;
using Pal.Client.Configuration;
using Pal.Client.DependencyInjection;
using Pal.Client.Extensions;
using Pal.Client.Net;
using Pal.Client.Properties;
using Pal.Common;
namespace Pal.Client.Scheduled
{
// TODO The idea was to split this from the queue objects, should be in individual classes tho
internal sealed class QueueHandler
{
private readonly ConfigurationManager _configurationManager;
private readonly IPalacePalConfiguration _configuration;
private readonly FloorService _floorService;
private readonly TerritoryState _territoryState;
private readonly DebugState _debugState;
private readonly ChatGui _chatGui;
public QueueHandler(
ConfigurationManager configurationManager,
IPalacePalConfiguration configuration,
FloorService floorService,
TerritoryState territoryState,
DebugState debugState,
ChatGui chatGui)
{
_configurationManager = configurationManager;
_configuration = configuration;
_floorService = floorService;
_territoryState = territoryState;
_debugState = debugState;
_chatGui = chatGui;
}
public void Handle(IQueueOnFrameworkThread queued, ref bool recreateLayout, ref bool saveMarkers)
{
if (queued is QueuedConfigUpdate)
{
ConfigUpdate(ref recreateLayout, ref saveMarkers);
}
else if (queued is QueuedSyncResponse queuedSyncResponse)
{
SyncResponse(queuedSyncResponse);
recreateLayout = true;
saveMarkers = true;
}
else if (queued is QueuedImport queuedImport)
{
Import(queuedImport);
recreateLayout = true;
saveMarkers = true;
}
else if (queued is QueuedUndoImport queuedUndoImport)
{
UndoImport(queuedUndoImport);
recreateLayout = true;
saveMarkers = true;
}
else
throw new InvalidOperationException();
}
private void ConfigUpdate(ref bool recreateLayout, ref bool saveMarkers)
{
if (_configuration.Mode == EMode.Offline)
{
LocalState.UpdateAll();
_floorService.FloorMarkers.Clear();
_floorService.EphemeralMarkers.Clear();
_territoryState.LastTerritory = 0;
recreateLayout = true;
saveMarkers = true;
}
}
private void SyncResponse(QueuedSyncResponse queued)
{
try
{
var remoteMarkers = queued.Markers;
var currentFloor = _floorService.GetFloorMarkers(queued.TerritoryType);
if (_configuration.Mode == EMode.Online && queued.Success && remoteMarkers.Count > 0)
{
switch (queued.Type)
{
case SyncType.Download:
case SyncType.Upload:
foreach (var remoteMarker in remoteMarkers)
{
// Both uploads and downloads return the network id to be set, but only the downloaded marker is new as in to-be-saved.
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
if (localMarker != null)
{
localMarker.NetworkId = remoteMarker.NetworkId;
continue;
}
if (queued.Type == SyncType.Download)
currentFloor.Markers.Add(remoteMarker);
}
break;
case SyncType.MarkSeen:
var partialAccountId =
_configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
if (partialAccountId == null)
break;
foreach (var remoteMarker in remoteMarkers)
{
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
if (localMarker != null)
localMarker.RemoteSeenOn.Add(partialAccountId);
}
break;
}
}
// don't modify state for outdated floors
if (_territoryState.LastTerritory != queued.TerritoryType)
return;
if (queued.Type == SyncType.Download)
{
if (queued.Success)
_territoryState.TerritorySyncState = SyncState.Complete;
else
_territoryState.TerritorySyncState = SyncState.Failed;
}
}
catch (Exception e)
{
_debugState.SetFromException(e);
if (queued.Type == SyncType.Download)
_territoryState.TerritorySyncState = SyncState.Failed;
}
}
private void Import(QueuedImport queued)
{
try
{
if (!queued.Validate(_chatGui))
return;
var oldExportIds = string.IsNullOrEmpty(queued.Export.ServerUrl)
? _configuration.ImportHistory.Where(x => x.RemoteUrl == queued.Export.ServerUrl).Select(x => x.Id)
.Where(x => x != Guid.Empty).ToList()
: new List<Guid>();
foreach (var remoteFloor in queued.Export.Floors)
{
ushort territoryType = (ushort)remoteFloor.TerritoryType;
var localState = _floorService.GetFloorMarkers(territoryType);
localState.UndoImport(oldExportIds);
queued.ImportFloor(remoteFloor, localState);
localState.Save();
}
_configuration.ImportHistory.RemoveAll(hist =>
oldExportIds.Contains(hist.Id) || hist.Id == queued.ExportId);
_configuration.ImportHistory.Add(new ConfigurationV1.ImportHistoryEntry
{
Id = queued.ExportId,
RemoteUrl = queued.Export.ServerUrl,
ExportedAt = queued.Export.CreatedAt.ToDateTime(),
ImportedAt = DateTime.UtcNow,
});
_configurationManager.Save(_configuration);
_chatGui.Print(string.Format(Localization.ImportCompleteStatistics, queued.ImportedTraps,
queued.ImportedHoardCoffers));
}
catch (Exception e)
{
PluginLog.Error(e, "Import failed");
_chatGui.PalError(string.Format(Localization.Error_ImportFailed, e));
}
}
private void UndoImport(QueuedUndoImport queued)
{
foreach (ETerritoryType territoryType in typeof(ETerritoryType).GetEnumValues())
{
var localState = _floorService.GetFloorMarkers((ushort)territoryType);
localState.UndoImport(new List<Guid> { queued.ExportId });
localState.Save();
}
_configuration.ImportHistory.RemoveAll(hist => hist.Id == queued.ExportId);
}
}
}

View File

@ -1,6 +1,37 @@
namespace Pal.Client.Scheduled using Pal.Client.Configuration;
using Pal.Client.DependencyInjection;
namespace Pal.Client.Scheduled
{ {
internal sealed class QueuedConfigUpdate : IQueueOnFrameworkThread internal sealed class QueuedConfigUpdate : IQueueOnFrameworkThread
{ {
internal sealed class Handler : IQueueOnFrameworkThread.Handler<QueuedConfigUpdate>
{
private readonly IPalacePalConfiguration _configuration;
private readonly FloorService _floorService;
private readonly TerritoryState _territoryState;
public Handler(IPalacePalConfiguration configuration, FloorService floorService,
TerritoryState territoryState)
{
_configuration = configuration;
_floorService = floorService;
_territoryState = territoryState;
}
protected override void Run(QueuedConfigUpdate queued, ref bool recreateLayout, ref bool saveMarkers)
{
if (_configuration.Mode == EMode.Offline)
{
LocalState.UpdateAll();
_floorService.FloorMarkers.Clear();
_floorService.EphemeralMarkers.Clear();
_territoryState.LastTerritory = 0;
recreateLayout = true;
saveMarkers = true;
}
}
}
} }
} }

View File

@ -1,20 +1,25 @@
using Account; using Account;
using Pal.Common; using Pal.Common;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Game.Gui; using Dalamud.Game.Gui;
using Dalamud.Logging;
using Pal.Client.Configuration;
using Pal.Client.DependencyInjection;
using Pal.Client.Extensions;
using Pal.Client.Properties; using Pal.Client.Properties;
namespace Pal.Client.Scheduled namespace Pal.Client.Scheduled
{ {
internal sealed class QueuedImport : IQueueOnFrameworkThread internal sealed class QueuedImport : IQueueOnFrameworkThread
{ {
public ExportRoot Export { get; } private ExportRoot Export { get; }
public Guid ExportId { get; private set; } private Guid ExportId { get; set; }
public int ImportedTraps { get; private set; } private int ImportedTraps { get; set; }
public int ImportedHoardCoffers { get; private set; } private int ImportedHoardCoffers { get; set; }
public QueuedImport(string sourcePath) public QueuedImport(string sourcePath)
{ {
@ -22,35 +27,100 @@ namespace Pal.Client.Scheduled
Export = ExportRoot.Parser.ParseFrom(input); Export = ExportRoot.Parser.ParseFrom(input);
} }
public bool Validate(ChatGui chatGui) internal sealed class Handler : IQueueOnFrameworkThread.Handler<QueuedImport>
{ {
if (Export.ExportVersion != ExportConfig.ExportVersion) private readonly ChatGui _chatGui;
private readonly IPalacePalConfiguration _configuration;
private readonly ConfigurationManager _configurationManager;
private readonly FloorService _floorService;
public Handler(ChatGui chatGui, IPalacePalConfiguration configuration,
ConfigurationManager configurationManager, FloorService floorService)
{ {
chatGui.PrintError(Localization.Error_ImportFailed_IncompatibleVersion); _chatGui = chatGui;
_configuration = configuration;
_configurationManager = configurationManager;
_floorService = floorService;
}
protected override void Run(QueuedImport import, ref bool recreateLayout, ref bool saveMarkers)
{
recreateLayout = true;
saveMarkers = true;
try
{
if (!Validate(import))
return;
var oldExportIds = string.IsNullOrEmpty(import.Export.ServerUrl)
? _configuration.ImportHistory.Where(x => x.RemoteUrl == import.Export.ServerUrl)
.Select(x => x.Id)
.Where(x => x != Guid.Empty).ToList()
: new List<Guid>();
foreach (var remoteFloor in import.Export.Floors)
{
ushort territoryType = (ushort)remoteFloor.TerritoryType;
var localState = _floorService.GetFloorMarkers(territoryType);
localState.UndoImport(oldExportIds);
ImportFloor(import, remoteFloor, localState);
localState.Save();
}
_configuration.ImportHistory.RemoveAll(hist =>
oldExportIds.Contains(hist.Id) || hist.Id == import.ExportId);
_configuration.ImportHistory.Add(new ConfigurationV1.ImportHistoryEntry
{
Id = import.ExportId,
RemoteUrl = import.Export.ServerUrl,
ExportedAt = import.Export.CreatedAt.ToDateTime(),
ImportedAt = DateTime.UtcNow,
});
_configurationManager.Save(_configuration);
_chatGui.Print(string.Format(Localization.ImportCompleteStatistics, import.ImportedTraps,
import.ImportedHoardCoffers));
}
catch (Exception e)
{
PluginLog.Error(e, "Import failed");
_chatGui.PalError(string.Format(Localization.Error_ImportFailed, e));
}
}
private bool Validate(QueuedImport import)
{
if (import.Export.ExportVersion != ExportConfig.ExportVersion)
{
_chatGui.PrintError(Localization.Error_ImportFailed_IncompatibleVersion);
return false; return false;
} }
if (!Guid.TryParse(Export.ExportId, out Guid exportId) || ExportId == Guid.Empty) if (!Guid.TryParse(import.Export.ExportId, out Guid exportId) || import.ExportId == Guid.Empty)
{ {
chatGui.PrintError(Localization.Error_ImportFailed_InvalidFile); _chatGui.PrintError(Localization.Error_ImportFailed_InvalidFile);
return false; return false;
} }
ExportId = exportId; import.ExportId = exportId;
if (string.IsNullOrEmpty(Export.ServerUrl)) if (string.IsNullOrEmpty(import.Export.ServerUrl))
{ {
// If we allow for backups as import/export, this should be removed // If we allow for backups as import/export, this should be removed
chatGui.PrintError(Localization.Error_ImportFailed_InvalidFile); _chatGui.PrintError(Localization.Error_ImportFailed_InvalidFile);
return false; return false;
} }
return true; return true;
} }
public void ImportFloor(ExportFloor remoteFloor, LocalState localState) private void ImportFloor(QueuedImport import, ExportFloor remoteFloor, LocalState localState)
{ {
var remoteMarkers = remoteFloor.Objects.Select(m => new Marker((Marker.EType)m.Type, new Vector3(m.X, m.Y, m.Z)) { WasImported = true }); var remoteMarkers = remoteFloor.Objects.Select(m =>
new Marker((Marker.EType)m.Type, new Vector3(m.X, m.Y, m.Z)) { WasImported = true });
foreach (var remoteMarker in remoteMarkers) foreach (var remoteMarker in remoteMarkers)
{ {
Marker? localMarker = localState.Markers.SingleOrDefault(x => x == remoteMarker); Marker? localMarker = localState.Markers.SingleOrDefault(x => x == remoteMarker);
@ -60,12 +130,13 @@ namespace Pal.Client.Scheduled
localMarker = remoteMarker; localMarker = remoteMarker;
if (localMarker.Type == Marker.EType.Trap) if (localMarker.Type == Marker.EType.Trap)
ImportedTraps++; import.ImportedTraps++;
else if (localMarker.Type == Marker.EType.Hoard) else if (localMarker.Type == Marker.EType.Hoard)
ImportedHoardCoffers++; import.ImportedHoardCoffers++;
} }
remoteMarker.Imports.Add(ExportId); remoteMarker.Imports.Add(import.ExportId);
}
} }
} }
} }

View File

@ -1,4 +1,10 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq;
using Pal.Client.Configuration;
using Pal.Client.DependencyInjection;
using Pal.Client.Extensions;
using Pal.Client.Net;
namespace Pal.Client.Scheduled namespace Pal.Client.Scheduled
{ {
@ -8,6 +14,89 @@ namespace Pal.Client.Scheduled
public required ushort TerritoryType { get; init; } public required ushort TerritoryType { get; init; }
public required bool Success { get; init; } public required bool Success { get; init; }
public required List<Marker> Markers { get; init; } public required List<Marker> Markers { get; init; }
internal sealed class Handler : IQueueOnFrameworkThread.Handler<QueuedSyncResponse>
{
private readonly IPalacePalConfiguration _configuration;
private readonly FloorService _floorService;
private readonly TerritoryState _territoryState;
private readonly DebugState _debugState;
public Handler(IPalacePalConfiguration configuration, FloorService floorService, TerritoryState territoryState, DebugState debugState)
{
_configuration = configuration;
_floorService = floorService;
_territoryState = territoryState;
_debugState = debugState;
}
protected override void Run(QueuedSyncResponse queued, ref bool recreateLayout, ref bool saveMarkers)
{
recreateLayout = true;
saveMarkers = true;
try
{
var remoteMarkers = queued.Markers;
var currentFloor = _floorService.GetFloorMarkers(queued.TerritoryType);
if (_configuration.Mode == EMode.Online && queued.Success && remoteMarkers.Count > 0)
{
switch (queued.Type)
{
case SyncType.Download:
case SyncType.Upload:
foreach (var remoteMarker in remoteMarkers)
{
// Both uploads and downloads return the network id to be set, but only the downloaded marker is new as in to-be-saved.
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
if (localMarker != null)
{
localMarker.NetworkId = remoteMarker.NetworkId;
continue;
}
if (queued.Type == SyncType.Download)
currentFloor.Markers.Add(remoteMarker);
}
break;
case SyncType.MarkSeen:
var partialAccountId =
_configuration.FindAccount(RemoteApi.RemoteUrl)?.AccountId.ToPartialId();
if (partialAccountId == null)
break;
foreach (var remoteMarker in remoteMarkers)
{
Marker? localMarker = currentFloor.Markers.SingleOrDefault(x => x == remoteMarker);
if (localMarker != null)
localMarker.RemoteSeenOn.Add(partialAccountId);
}
break;
}
}
// don't modify state for outdated floors
if (_territoryState.LastTerritory != queued.TerritoryType)
return;
if (queued.Type == SyncType.Download)
{
if (queued.Success)
_territoryState.TerritorySyncState = SyncState.Complete;
else
_territoryState.TerritorySyncState = SyncState.Failed;
}
}
catch (Exception e)
{
_debugState.SetFromException(e);
if (queued.Type == SyncType.Download)
_territoryState.TerritorySyncState = SyncState.Failed;
}
}
}
} }
public enum SyncState public enum SyncState

View File

@ -1,4 +1,8 @@
using System; using System;
using System.Collections.Generic;
using Pal.Client.Configuration;
using Pal.Client.DependencyInjection;
using Pal.Common;
namespace Pal.Client.Scheduled namespace Pal.Client.Scheduled
{ {
@ -9,6 +13,33 @@ namespace Pal.Client.Scheduled
ExportId = exportId; ExportId = exportId;
} }
public Guid ExportId { get; } private Guid ExportId { get; }
internal sealed class Handler : IQueueOnFrameworkThread.Handler<QueuedUndoImport>
{
private readonly IPalacePalConfiguration _configuration;
private readonly FloorService _floorService;
public Handler(IPalacePalConfiguration configuration, FloorService floorService)
{
_configuration = configuration;
_floorService = floorService;
}
protected override void Run(QueuedUndoImport queued, ref bool recreateLayout, ref bool saveMarkers)
{
recreateLayout = true;
saveMarkers = true;
foreach (ETerritoryType territoryType in typeof(ETerritoryType).GetEnumValues())
{
var localState = _floorService.GetFloorMarkers((ushort)territoryType);
localState.UndoImport(new List<Guid> { queued.ExportId });
localState.Save();
}
_configuration.ImportHistory.RemoveAll(hist => hist.Id == queued.ExportId);
}
}
} }
} }