refactor + add resiliency against penumbra/api shutdowns

This commit is contained in:
Stanley Dimant
2022-06-25 21:39:20 +02:00
parent 8c9f816e6d
commit 3bba240cd5
9 changed files with 169 additions and 150 deletions

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
using System.Threading;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
@@ -15,18 +16,31 @@ namespace MareSynchronos.Factories
{
private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager;
private readonly FileReplacementFactory _factory;
public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager, FileReplacementFactory factory)
public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager)
{
Logger.Debug("Creating " + nameof(CharacterDataFactory));
_dalamudUtil = dalamudUtil;
_ipcManager = ipcManager;
_factory = factory;
}
public unsafe CharacterData BuildCharacterData()
private FileReplacement CreateBaseFileReplacement()
{
return new FileReplacement(_ipcManager.PenumbraModDirectory()!);
}
public CharacterData BuildCharacterData()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized)
{
throw new ArgumentException("Player is not present or Penumbra is not connected");
}
return CreateCharacterData();
}
private unsafe CharacterData CreateCharacterData()
{
Stopwatch st = Stopwatch.StartNew();
var cache = new CharacterData();
@@ -47,7 +61,7 @@ namespace MareSynchronos.Factories
var mdlPath = new Utf8String(mdl->ResourceHandle->FileName()).ToString();
FileReplacement cachedMdlResource = _factory.Create();
FileReplacement cachedMdlResource = CreateBaseFileReplacement();
cachedMdlResource.GamePaths = _ipcManager.PenumbraReverseResolvePath(mdlPath, _dalamudUtil.PlayerName);
cachedMdlResource.SetResolvedPath(mdlPath);
//PluginLog.Verbose("Resolving for model " + mdlPath);
@@ -59,10 +73,10 @@ namespace MareSynchronos.Factories
var mtrl = (Material*)mdl->Materials[mtrlIdx];
if (mtrl == null) continue;
//var mtrlFileResource = factory.Create();
//var mtrlFileResource = factory.CreateBaseFileReplacement();
var mtrlPath = new Utf8String(mtrl->ResourceHandle->FileName()).ToString().Split("|")[2];
//PluginLog.Verbose("Resolving for material " + mtrlPath);
var cachedMtrlResource = _factory.Create();
var cachedMtrlResource = CreateBaseFileReplacement();
cachedMtrlResource.GamePaths = _ipcManager.PenumbraReverseResolvePath(mtrlPath, _dalamudUtil.PlayerName);
cachedMtrlResource.SetResolvedPath(mtrlPath);
cache.AddAssociatedResource(cachedMtrlResource, cachedMdlResource, null!);
@@ -74,7 +88,7 @@ namespace MareSynchronos.Factories
if (string.IsNullOrEmpty(texPath.ToString())) continue;
var cachedTexResource = _factory.Create();
var cachedTexResource = CreateBaseFileReplacement();
cachedTexResource.GamePaths = new[] { texPath };
cachedTexResource.SetResolvedPath(_ipcManager.PenumbraResolvePath(texPath, _dalamudUtil.PlayerName)!);
if (!cachedTexResource.HasFileReplacement)

View File

@@ -1,28 +0,0 @@
using MareSynchronos.Managers;
using MareSynchronos.Models;
using MareSynchronos.Utils;
namespace MareSynchronos.Factories
{
public class FileReplacementFactory
{
private readonly IpcManager _ipcManager;
public FileReplacementFactory(IpcManager ipcManager)
{
Logger.Debug("Creating " + nameof(FileReplacementFactory));
this._ipcManager = ipcManager;
}
public FileReplacement Create()
{
if (!_ipcManager.CheckPenumbraApi())
{
throw new System.Exception();
}
return new FileReplacement(_ipcManager.PenumbraModDirectory()!);
}
}
}

View File

@@ -37,6 +37,46 @@ public class CharacterCacheManager : IDisposable
_apiController = apiController;
_dalamudUtil = dalamudUtil;
_ipcManager = ipcManager;
_clientState.Login += ClientStateOnLogin;
_clientState.Logout += ClientStateOnLogout;
_apiController.CharacterReceived += ApiControllerOnCharacterReceived;
_apiController.PairedClientOnline += ApiControllerOnPairedClientOnline;
_apiController.PairedClientOffline += ApiControllerOnPairedClientOffline;
_apiController.PairedWithOther += ApiControllerOnPairedWithOther;
_apiController.UnpairedFromOther += ApiControllerOnUnpairedFromOther;
_apiController.Disconnected += ApiControllerOnDisconnected;
_ipcManager.PenumbraDisposed += IpcManagerOnPenumbraDisposed;
if (clientState.IsLoggedIn)
{
ClientStateOnLogin(null, EventArgs.Empty);
}
}
private void IpcManagerOnPenumbraDisposed(object? sender, EventArgs e)
{
foreach (var character in _onlineCachedPlayers.ToList())
{
RestoreCharacter(character);
character.IsVisible = false;
}
}
private void ApiControllerOnDisconnected(object? sender, EventArgs e)
{
RestoreAllCharacters();
}
private void ClientStateOnLogin(object? sender, EventArgs e)
{
_framework.Update += FrameworkOnUpdate;
}
private void ClientStateOnLogout(object? sender, EventArgs e)
{
_framework.Update -= FrameworkOnUpdate;
}
public void AddInitialPairs(List<string> apiTaskResult)
@@ -55,8 +95,15 @@ public class CharacterCacheManager : IDisposable
_apiController.PairedClientOffline -= ApiControllerOnPairedClientOffline;
_apiController.PairedWithOther -= ApiControllerOnPairedWithOther;
_apiController.UnpairedFromOther -= ApiControllerOnUnpairedFromOther;
_ipcManager.PenumbraDisposed -= ApiControllerOnDisconnected;
_framework.Update -= FrameworkOnUpdate;
_clientState.Login -= ClientStateOnLogin;
_clientState.Logout -= ClientStateOnLogout;
RestoreAllCharacters();
}
private void RestoreAllCharacters()
{
foreach (var character in _onlineCachedPlayers.ToList())
{
RestoreCharacter(character);
@@ -65,18 +112,6 @@ public class CharacterCacheManager : IDisposable
_onlineCachedPlayers.Clear();
}
public void Initialize()
{
_onlineCachedPlayers.Clear();
_apiController.CharacterReceived += ApiControllerOnCharacterReceived;
_apiController.PairedClientOnline += ApiControllerOnPairedClientOnline;
_apiController.PairedClientOffline += ApiControllerOnPairedClientOffline;
_apiController.PairedWithOther += ApiControllerOnPairedWithOther;
_apiController.UnpairedFromOther += ApiControllerOnUnpairedFromOther;
_framework.Update += FrameworkOnUpdate;
}
public async Task UpdatePlayersFromService(Dictionary<string, int> playerJobIds)
{
await _apiController.GetCharacterData(playerJobIds);
@@ -190,6 +225,7 @@ public class CharacterCacheManager : IDisposable
if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(2)) return;
_localVisiblePlayers.Clear();
if (!_ipcManager.Initialized) return;
foreach (var obj in _objectTable)
{
if (obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;

View File

@@ -70,7 +70,6 @@ namespace MareSynchronos.Managers
{
var apiTask = _apiController.SendCharacterName(_dalamudUtil.PlayerNameHashed);
_lastSentHash = string.Empty;
_characterCacheManager.Initialize();
Task.WaitAll(apiTask);
@@ -81,8 +80,6 @@ namespace MareSynchronos.Managers
private void ApiController_Disconnected(object? sender, EventArgs args)
{
_characterCacheManager.Dispose();
Logger.Debug(nameof(ApiController_Disconnected));
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
@@ -126,6 +123,12 @@ namespace MareSynchronos.Managers
return;
}
if (!_ipcManager.Initialized)
{
PluginLog.Warning("Penumbra not active, doing nothing.");
return;
}
_playerChangedTask = Task.Run(async () =>
{
Stopwatch st = Stopwatch.StartNew();
@@ -152,28 +155,21 @@ namespace MareSynchronos.Managers
Logger.Debug("Watcher Player Changed");
Task.Run(() =>
{
try
// fix for redraw from anamnesis
while (!_dalamudUtil.IsPlayerPresent)
{
// fix for redraw from anamnesis
while (!_dalamudUtil.IsPlayerPresent)
{
Logger.Debug("Waiting Until Player is Present");
Thread.Sleep(100);
}
if (actor.Name.ToString() == _dalamudUtil.PlayerName)
{
Logger.Debug("Watcher: PlayerChanged");
PlayerChanged(actor.Name.ToString());
}
else
{
Logger.Debug("PlayerChanged: " + actor.Name.ToString());
}
Logger.Debug("Waiting Until Player is Present");
Thread.Sleep(100);
}
catch (Exception ex)
if (actor.Name.ToString() == _dalamudUtil.PlayerName)
{
PluginLog.Error(ex, "Actor was null or broken " + actor);
Logger.Debug("Watcher: PlayerChanged");
PlayerChanged(actor.Name.ToString());
}
else
{
Logger.Debug("PlayerChanged: " + actor.Name.ToString());
}
});
}

View File

@@ -26,17 +26,32 @@ namespace MareSynchronos.Managers
_ipcManager = ipcManager;
_pluginConfiguration = pluginConfiguration;
if (_ipcManager.CheckPenumbraApi()
&& _pluginConfiguration.AcceptedAgreement
&& !string.IsNullOrEmpty(_pluginConfiguration.CacheFolder)
&& _pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri)
&& !string.IsNullOrEmpty(_ipcManager.PenumbraModDirectory()))
StartWatchersAndScan();
_ipcManager.PenumbraInitialized += IpcManagerOnPenumbraInitialized;
_ipcManager.PenumbraDisposed += IpcManagerOnPenumbraDisposed;
}
private void StartWatchersAndScan()
{
if (_ipcManager.Initialized && _pluginConfiguration.HasValidSetup)
{
Logger.Debug("Penumbra is active, configuration is valid, starting watchers and scan");
StartWatchers();
StartInitialScan();
}
}
private void IpcManagerOnPenumbraInitialized(object? sender, EventArgs e)
{
StartWatchersAndScan();
}
private void IpcManagerOnPenumbraDisposed(object? sender, EventArgs e)
{
StopWatchersAndScan();
}
public long CurrentFileProgress { get; private set; }
public long FileCacheSize { get; set; }
public bool IsScanRunning => !_scanTask?.IsCompleted ?? false;
@@ -68,6 +83,14 @@ namespace MareSynchronos.Managers
{
Logger.Debug("Disposing " + nameof(FileCacheManager));
_ipcManager.PenumbraInitialized -= IpcManagerOnPenumbraInitialized;
_ipcManager.PenumbraDisposed -= IpcManagerOnPenumbraDisposed;
StopWatchersAndScan();
}
private void StopWatchersAndScan()
{
_cacheDirWatcher?.Dispose();
_penumbraDirWatcher?.Dispose();
_scanCancellationTokenSource?.Cancel();
@@ -81,6 +104,7 @@ namespace MareSynchronos.Managers
public void StartWatchers()
{
if (!_ipcManager.Initialized || !_pluginConfiguration.HasValidSetup) return;
Logger.Debug("Starting File System Watchers");
_penumbraDirWatcher?.Dispose();
_cacheDirWatcher?.Dispose();
@@ -91,7 +115,6 @@ namespace MareSynchronos.Managers
InternalBufferSize = 65536
};
_penumbraDirWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
//_penumbraDirWatcher.Created += OnCreated;
_penumbraDirWatcher.Deleted += OnDeleted;
_penumbraDirWatcher.Changed += OnModified;
_penumbraDirWatcher.Filters.Add("*.mtrl");
@@ -107,7 +130,6 @@ namespace MareSynchronos.Managers
InternalBufferSize = 65536
};
_cacheDirWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
//_cacheDirWatcher.Created += OnCreated;
_cacheDirWatcher.Deleted += OnDeleted;
_cacheDirWatcher.Changed += OnModified;
_cacheDirWatcher.Filters.Add("*.mtrl");
@@ -134,49 +156,6 @@ namespace MareSynchronos.Managers
return false;
}
private void OnCreated(object sender, FileSystemEventArgs e)
{
var fi = new FileInfo(e.FullPath);
using var db = new FileCacheContext();
var ext = fi.Extension.ToLower();
if (ext is ".mdl" or ".tex" or ".mtrl")
{
Logger.Debug("File created: " + e.FullPath);
try
{
var createdFileCache = Create(fi.FullName.ToLower());
db.Add(createdFileCache);
}
catch (FileLoadException)
{
Logger.Debug("File was still being written to.");
}
}
else
{
if (Directory.Exists(e.FullPath))
{
Logger.Debug("Folder added: " + e.FullPath);
var newFiles = Directory.EnumerateFiles(e.FullPath, "*.*", SearchOption.AllDirectories)
.Where(f => f.EndsWith(".tex", StringComparison.OrdinalIgnoreCase) ||
f.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase) ||
f.EndsWith(".mtrl", StringComparison.OrdinalIgnoreCase)).ToList();
foreach (var file in newFiles)
{
Logger.Debug("Adding " + file);
db.Add(Create(file));
}
}
}
db.SaveChanges();
if (e.FullPath.Contains(_pluginConfiguration.CacheFolder, StringComparison.OrdinalIgnoreCase))
{
Task.Run(RecalculateFileCacheSize);
}
}
private void OnDeleted(object sender, FileSystemEventArgs e)
{
var fi = new FileInfo(e.FullPath);

View File

@@ -17,6 +17,7 @@ namespace MareSynchronos.Managers
private readonly ICallGateSubscriber<string, string, bool, (int, string)> _penumbraCreateTemporaryCollection;
private readonly ICallGateSubscriber<string, string> _penumbraGetMetaManipulations;
private readonly ICallGateSubscriber<object> _penumbraInit;
private readonly ICallGateSubscriber<object> _penumbraDispose;
private readonly ICallGateSubscriber<IntPtr, int, object?> _penumbraObjectIsRedrawn;
private readonly ICallGateSubscriber<string, int, object>? _penumbraRedraw;
private readonly ICallGateSubscriber<string, int> _penumbraRemoveTemporaryCollection;
@@ -30,6 +31,7 @@ namespace MareSynchronos.Managers
Logger.Debug("Creating " + nameof(IpcManager));
_penumbraInit = pi.GetIpcSubscriber<object>("Penumbra.Initialized");
_penumbraDispose = pi.GetIpcSubscriber<object>("Penumbra.Disposed");
_penumbraResolvePath = pi.GetIpcSubscriber<string, string, string>("Penumbra.ResolveCharacterPath");
_penumbraResolveModDir = pi.GetIpcSubscriber<string>("Penumbra.GetModDirectory");
_penumbraRedraw = pi.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
@@ -44,7 +46,8 @@ namespace MareSynchronos.Managers
pi.GetIpcSubscriber<string, string>("Penumbra.GetMetaManipulations");
_penumbraObjectIsRedrawn.Subscribe(RedrawEvent);
_penumbraInit.Subscribe(RedrawSelf);
_penumbraInit.Subscribe(PenumbraInit);
_penumbraDispose.Subscribe(PenumbraDispose);
_penumbraSetTemporaryMod =
pi
@@ -56,12 +59,17 @@ namespace MareSynchronos.Managers
_penumbraRemoveTemporaryCollection =
pi.GetIpcSubscriber<string, int>("Penumbra.RemoveTemporaryCollection");
Initialized = true;
if (Initialized)
{
PenumbraInitialized?.Invoke(null, EventArgs.Empty);
}
}
public event EventHandler? PenumbraInitialized;
public event EventHandler? PenumbraDisposed;
public event EventHandler? PenumbraRedrawEvent;
public bool Initialized { get; private set; } = false;
public bool Initialized => CheckPenumbraApi();
public bool CheckGlamourerApi()
{
try
@@ -78,18 +86,22 @@ namespace MareSynchronos.Managers
{
try
{
return _penumbraApiVersion.InvokeFunc() >= 4;
return _penumbraApiVersion.InvokeFunc() >= 5;
}
catch
{
return false;
}
}
public void Dispose()
{
Logger.Debug("Disposing " + nameof(IpcManager));
Uninitialize();
_penumbraDispose.Unsubscribe(PenumbraDispose);
_penumbraInit.Unsubscribe(PenumbraInit);
_penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent);
Logger.Debug("IPC Manager disposed");
}
public void GlamourerApplyCharacterCustomization(string customization, string characterName)
@@ -174,17 +186,15 @@ namespace MareSynchronos.Managers
PenumbraRedrawEvent?.Invoke(objectTableIndex, EventArgs.Empty);
}
private void RedrawSelf()
private void PenumbraInit()
{
PenumbraInitialized?.Invoke(null, EventArgs.Empty);
_penumbraRedraw!.InvokeAction("self", 0);
}
private void Uninitialize()
private void PenumbraDispose()
{
_penumbraInit.Unsubscribe(RedrawSelf);
_penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent);
Initialized = false;
Logger.Debug("IPC Manager disposed");
PenumbraDisposed?.Invoke(null, EventArgs.Empty);
}
}
}

View File

@@ -22,6 +22,7 @@ namespace MareSynchronos
private readonly ApiController _apiController;
private readonly ClientState _clientState;
private readonly CommandManager _commandManager;
private readonly Framework _framework;
private readonly Configuration _configuration;
private readonly FileCacheManager _fileCacheManager;
private readonly IntroUi _introUi;
@@ -32,7 +33,7 @@ namespace MareSynchronos
private readonly WindowSystem _windowSystem;
private CharacterManager? _characterManager;
private readonly DalamudUtil _dalamudUtil;
private readonly CharacterCacheManager _characterCacheManager;
private CharacterCacheManager? _characterCacheManager;
private readonly IPlayerWatcher _playerWatcher;
private readonly DownloadUi _downloadUi;
@@ -42,6 +43,7 @@ namespace MareSynchronos
Logger.Debug("Launching " + Name);
_pluginInterface = pluginInterface;
_commandManager = commandManager;
_framework = framework;
_objectTable = objectTable;
_clientState = clientState;
_configuration = _pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
@@ -49,18 +51,16 @@ namespace MareSynchronos
_windowSystem = new WindowSystem("MareSynchronos");
new FileCacheContext().Dispose(); // make sure db is initialized I guess
// those can be initialized outside of game login
_apiController = new ApiController(_configuration);
_ipcManager = new IpcManager(_pluginInterface);
_fileCacheManager = new FileCacheManager(_ipcManager, _configuration);
_dalamudUtil = new DalamudUtil(_clientState, _objectTable);
_characterCacheManager = new CharacterCacheManager(_clientState, framework, _objectTable, _apiController,
_dalamudUtil, _ipcManager);
_playerWatcher = PlayerWatchFactory.Create(framework, _clientState, _objectTable);
_playerWatcher.Enable();
var uiSharedComponent =
new UiShared(_ipcManager, _apiController, _fileCacheManager, _configuration);
_pluginUi = new PluginUi(_windowSystem, uiSharedComponent, _configuration, _apiController);
_introUi = new IntroUi(_windowSystem, uiSharedComponent, _configuration, _fileCacheManager);
_introUi.FinishedRegistration += (_, _) =>
@@ -71,7 +71,9 @@ namespace MareSynchronos
};
_downloadUi = new DownloadUi(_windowSystem, _configuration, _apiController);
new FileCacheContext().Dispose(); // make sure db is initialized I guess
_dalamudUtil = new DalamudUtil(_clientState, _objectTable);
_playerWatcher = PlayerWatchFactory.Create(framework, _clientState, _objectTable);
_playerWatcher.Enable();
clientState.Login += ClientState_Login;
clientState.Logout += ClientState_Logout;
@@ -87,8 +89,8 @@ namespace MareSynchronos
{
_pluginUi.IsOpen = false;
_introUi.IsOpen = true;
_characterCacheManager.Dispose();
_characterManager!.Dispose();
_characterCacheManager?.Dispose();
_characterManager?.Dispose();
}
public string Name => "Mare Synchronos";
@@ -102,7 +104,6 @@ namespace MareSynchronos
_clientState.Login -= ClientState_Login;
_clientState.Logout -= ClientState_Logout;
_pluginUi?.Dispose();
_introUi?.Dispose();
_downloadUi?.Dispose();
@@ -110,7 +111,7 @@ namespace MareSynchronos
_fileCacheManager?.Dispose();
_ipcManager?.Dispose();
_characterManager?.Dispose();
_characterCacheManager.Dispose();
_characterCacheManager?.Dispose();
_playerWatcher.Disable();
_playerWatcher.Dispose();
}
@@ -139,6 +140,7 @@ namespace MareSynchronos
private void ClientState_Logout(object? sender, EventArgs e)
{
Logger.Debug("Client logout");
_characterCacheManager?.Dispose();
_characterManager?.Dispose();
_pluginInterface.UiBuilder.Draw -= Draw;
_pluginInterface.UiBuilder.OpenConfigUi -= OpenConfigUi;
@@ -159,7 +161,9 @@ namespace MareSynchronos
try
{
var characterCacheFactory =
new CharacterDataFactory(_dalamudUtil, _ipcManager, new FileReplacementFactory(_ipcManager));
new CharacterDataFactory(_dalamudUtil, _ipcManager);
_characterCacheManager = new CharacterCacheManager(_clientState, _framework, _objectTable,
_apiController, _dalamudUtil, _ipcManager);
_characterManager = new CharacterManager(_apiController, _objectTable, _ipcManager,
characterCacheFactory, _characterCacheManager, _dalamudUtil, _playerWatcher);
_characterManager.StartWatchingPlayer();

View File

@@ -31,6 +31,11 @@ namespace MareSynchronos.Utils
public Dictionary<string, PlayerCharacter> GetLocalPlayers()
{
if (!_clientState.IsLoggedIn)
{
return new Dictionary<string, PlayerCharacter>();
}
Dictionary<string, PlayerCharacter> allLocalPlayers = new();
foreach (var obj in _objectTable)
{
@@ -46,6 +51,8 @@ namespace MareSynchronos.Utils
public unsafe void WaitWhileCharacterIsDrawing(IntPtr characterAddress)
{
if (!_clientState.IsLoggedIn) return;
var obj = (GameObject*)characterAddress;
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
@@ -59,6 +66,6 @@ namespace MareSynchronos.Utils
Thread.Sleep(500);
}
public void WaitWhileSelfIsDrawing() => WaitWhileCharacterIsDrawing(_clientState.LocalPlayer!.Address);
public void WaitWhileSelfIsDrawing() => WaitWhileCharacterIsDrawing(_clientState.LocalPlayer?.Address ?? new IntPtr());
}
}

View File

@@ -55,8 +55,8 @@ namespace MareSynchronos.WebAPI
public ConcurrentDictionary<string, (long, long)> CurrentDownloads { get; } = new();
public ConcurrentDictionary<string, (long, long)> CurrentUploads { get; } = new();
public bool IsConnected => !string.IsNullOrEmpty(UID);
public bool IsDownloading { get; private set; } = false;
public bool IsUploading { get; private set; } = false;
public bool IsDownloading { get; private set; }
public bool IsUploading { get; private set; }
public List<ClientPairDto> PairedClients { get; set; } = new();
public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri) ? _pluginConfiguration.ClientSecret[ApiUri] : "-";
public bool ServerAlive =>
@@ -95,7 +95,6 @@ namespace MareSynchronos.WebAPI
public async Task<byte[]> DownloadFile(string hash)
{
IsDownloading = true;
var reader = await _fileHub!.StreamAsChannelAsync<byte[]>("DownloadFile", hash);
List<byte> downloadedData = new();
int i = 0;
@@ -108,12 +107,13 @@ namespace MareSynchronos.WebAPI
}
}
IsDownloading = false;
return downloadedData.ToArray();
}
public async Task DownloadFiles(List<FileReplacementDto> fileReplacementDto)
{
IsDownloading = true;
foreach (var file in fileReplacementDto)
{
var fileSize = await _fileHub!.InvokeAsync<long>("GetFileSize", file.Hash);
@@ -149,6 +149,7 @@ namespace MareSynchronos.WebAPI
}
CurrentDownloads.Clear();
IsDownloading = false;
}
public async Task GetCharacterData(Dictionary<string, int> hashedCharacterNames)