Merge tag '0.9.17' into mare-classic

* tag '0.9.17':
  add census popup on connection
  api update
  census update
  heave fewer redraws as main method for data application, minor fixes
  remove unnecessary exists check
  add visibility for loaded mods size for pair, use menu bar for settings, remove settings button
  fix staging issues
  add download throttling, change header of mare, fix reverting players when going offline/paused when not visible
  use name for glamourer revert
  fix startup breaking
  add inner exception stacktraces
  calc correct button size
  wording
  add permission popup ui
  fix getting identifier during zoning
  indent nonscaled
  remove unnecessary usings
  ui icon boogaloo
  fix cache dict wtf
  add normalized icons
  add owner/moderator/pinned user icons
  check tokentime more precisely in both directions
  more cleanup
  fix sorting and cleanup
  make local groups more usable for pause/resume
  fix outlined font
  rework creation of popout windows into factory and some refactoring in general
  make syncshell admin ui to standalone window
  remove close button on intro ui
  do not allow to open main ui without finishing setup
  readonly bla
  wait for plugin disposal
  fix palette
  wording
  fix palette application and add experimental less redraws option
  some minor fixes
  check for timezone
  idk
  adjust token handling
  fix total user count in syncshell (distinct by UIDs)
  fix text alignment
  fix some shit maybe
  idk some fixes I guess
  fix offset for transfer bar at the bottom, use async collections, clear filter on tab change + add button to clear, require ctrl for align syncshells
  blah
  Some display options for DTR tooltip (#66)
  add ordering
  adjust api to latest
  rework main ui
  add total count on mouseover, make syncshell windows non-blocking
  fix token for character change, add online count to syncshells and groups
  argh
  fix broken font in header
  add more options for the compactui
  fix icons and text of buttons being static in place
  remove logspam
This commit is contained in:
Loporrit
2023-12-08 13:44:18 +00:00
54 changed files with 1646 additions and 743 deletions

View File

@@ -19,6 +19,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
private readonly FileCompactor _fileCompactor;
private readonly FileCacheManager _fileDbManager;
private readonly FileTransferOrchestrator _orchestrator;
private readonly List<ThrottledStream> _activeDownloadStreams;
public FileDownloadManager(ILogger<FileDownloadManager> logger, MareMediator mediator,
FileTransferOrchestrator orchestrator,
@@ -28,6 +29,18 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
_orchestrator = orchestrator;
_fileDbManager = fileCacheManager;
_fileCompactor = fileCompactor;
_activeDownloadStreams = [];
Mediator.Subscribe<DownloadLimitChangedMessage>(this, (msg) =>
{
if (!_activeDownloadStreams.Any()) return;
var newLimit = _orchestrator.DownloadLimitPerSlot();
Logger.LogTrace("Setting new Download Speed Limit to {newLimit}", newLimit);
foreach (var stream in _activeDownloadStreams)
{
stream.BandwidthLimit = newLimit;
}
});
}
public List<DownloadFileTransfer> CurrentDownloads { get; private set; } = [];
@@ -71,6 +84,14 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
protected override void Dispose(bool disposing)
{
CancelDownload();
foreach (var stream in _activeDownloadStreams)
{
try
{
stream.Dispose();
}
catch { }
}
base.Dispose(disposing);
}
@@ -133,6 +154,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
}
}
ThrottledStream? stream = null;
try
{
var fileStream = File.Create(tempPath);
@@ -142,7 +164,10 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
var buffer = new byte[bufferSize];
var bytesRead = 0;
var stream = await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false);
var limit = _orchestrator.DownloadLimitPerSlot();
Logger.LogTrace("Starting Download of {id} with a speed limit of {limit}", requestId, limit);
stream = new ThrottledStream(await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false), limit);
_activeDownloadStreams.Add(stream);
while ((bytesRead = await stream.ReadAsync(buffer, ct).ConfigureAwait(false)) > 0)
{
ct.ThrowIfCancellationRequested();
@@ -171,6 +196,14 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
}
throw;
}
finally
{
if (stream != null)
{
_activeDownloadStreams.Remove(stream);
await stream.DisposeAsync().ConfigureAwait(false);
}
}
}
private async Task DownloadFilesInternal(GameObjectHandler gameObjectHandler, List<FileReplacementData> fileReplacement, CancellationToken ct)

View File

@@ -19,6 +19,7 @@ public class FileTransferOrchestrator : DisposableMediatorSubscriberBase
private readonly TokenProvider _tokenProvider;
private int _availableDownloadSlots;
private SemaphoreSlim _downloadSemaphore;
private int CurrentlyUsedDownloadSlots => _availableDownloadSlots - _downloadSemaphore.CurrentCount;
public FileTransferOrchestrator(ILogger<FileTransferOrchestrator> logger, MareConfigService mareConfig,
MareMediator mediator, TokenProvider tokenProvider) : base(logger, mediator)
@@ -72,6 +73,7 @@ public class FileTransferOrchestrator : DisposableMediatorSubscriberBase
public void ReleaseDownloadSlot()
{
_downloadSemaphore.Release();
Mediator.Publish(new DownloadLimitChangedMessage());
}
public async Task<HttpResponseMessage> SendRequestAsync(HttpMethod method, Uri uri,
@@ -110,12 +112,28 @@ public class FileTransferOrchestrator : DisposableMediatorSubscriberBase
}
await _downloadSemaphore.WaitAsync(token).ConfigureAwait(false);
Mediator.Publish(new DownloadLimitChangedMessage());
}
public long DownloadLimitPerSlot()
{
var limit = _mareConfig.Current.DownloadSpeedLimitInBytes;
if (limit <= 0) return 0;
limit = _mareConfig.Current.DownloadSpeedType switch
{
MareConfiguration.Models.DownloadSpeeds.Bps => limit,
MareConfiguration.Models.DownloadSpeeds.KBps => limit * 1024,
MareConfiguration.Models.DownloadSpeeds.MBps => limit * 1024 * 1024,
_ => limit,
};
var dividedLimit = limit / (CurrentlyUsedDownloadSlots == 0 ? 1 : CurrentlyUsedDownloadSlots);
return dividedLimit == 0 ? 1 : dividedLimit;
}
private async Task<HttpResponseMessage> SendRequestInternalAsync(HttpRequestMessage requestMessage,
CancellationToken? ct = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await _tokenProvider.GetOrUpdateToken(ct!.Value).ConfigureAwait(false));
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenProvider.GetToken());
if (requestMessage.Content != null && requestMessage.Content is not StreamContent && requestMessage.Content is not ByteArrayContent)
{

View File

@@ -198,7 +198,7 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
Logger.LogDebug("Verifying {count} files", unverifiedUploadHashes.Count);
var filesToUpload = await FilesSend([.. unverifiedUploadHashes], visiblePlayers.Select(p => p.UID).ToList(), uploadToken).ConfigureAwait(false);
foreach (var file in filesToUpload.Where(f => !f.IsForbidden))
foreach (var file in filesToUpload.Where(f => !f.IsForbidden).DistinctBy(f => f.Hash))
{
try
{

View File

@@ -0,0 +1,217 @@
using Microsoft.Extensions.Logging;
namespace MareSynchronos.WebAPI.Files
{
/// <summary>
/// Class for streaming data with throttling support.
/// Borrowed from https://github.com/bezzad/Downloader
/// </summary>
internal class ThrottledStream : Stream
{
public static long Infinite => long.MaxValue;
private readonly Stream _baseStream;
private long _bandwidthLimit;
private Bandwidth _bandwidth;
private CancellationTokenSource _bandwidthChangeTokenSource = new CancellationTokenSource();
/// <summary>
/// Initializes a new instance of the <see cref="T:ThrottledStream" /> class.
/// </summary>
/// <param name="baseStream">The base stream.</param>
/// <param name="bandwidthLimit">The maximum bytes per second that can be transferred through the base stream.</param>
/// <exception cref="ArgumentNullException">Thrown when <see cref="baseStream" /> is a null reference.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="BandwidthLimit" /> is a negative value.</exception>
public ThrottledStream(Stream baseStream, long bandwidthLimit)
{
if (bandwidthLimit < 0)
{
throw new ArgumentOutOfRangeException(nameof(bandwidthLimit),
bandwidthLimit, "The maximum number of bytes per second can't be negative.");
}
_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
BandwidthLimit = bandwidthLimit;
}
/// <summary>
/// Bandwidth Limit (in B/s)
/// </summary>
/// <value>The maximum bytes per second.</value>
public long BandwidthLimit
{
get => _bandwidthLimit;
set
{
if (_bandwidthLimit == value) return;
_bandwidthLimit = value <= 0 ? Infinite : value;
_bandwidth ??= new Bandwidth();
_bandwidth.BandwidthLimit = _bandwidthLimit;
_bandwidthChangeTokenSource.Cancel();
_bandwidthChangeTokenSource.Dispose();
_bandwidthChangeTokenSource = new();
}
}
/// <inheritdoc />
public override bool CanRead => _baseStream.CanRead;
/// <inheritdoc />
public override bool CanSeek => _baseStream.CanSeek;
/// <inheritdoc />
public override bool CanWrite => _baseStream.CanWrite;
/// <inheritdoc />
public override long Length => _baseStream.Length;
/// <inheritdoc />
public override long Position
{
get => _baseStream.Position;
set => _baseStream.Position = value;
}
/// <inheritdoc />
public override void Flush()
{
_baseStream.Flush();
}
/// <inheritdoc />
public override long Seek(long offset, SeekOrigin origin)
{
return _baseStream.Seek(offset, origin);
}
/// <inheritdoc />
public override void SetLength(long value)
{
_baseStream.SetLength(value);
}
/// <inheritdoc />
public override int Read(byte[] buffer, int offset, int count)
{
Throttle(count).Wait();
return _baseStream.Read(buffer, offset, count);
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
await Throttle(count, cancellationToken).ConfigureAwait(false);
return await _baseStream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public override void Write(byte[] buffer, int offset, int count)
{
Throttle(count).Wait();
_baseStream.Write(buffer, offset, count);
}
/// <inheritdoc />
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
await Throttle(count, cancellationToken).ConfigureAwait(false);
await _baseStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
public override void Close()
{
_baseStream.Close();
base.Close();
}
private async Task Throttle(int transmissionVolume, CancellationToken token = default)
{
// Make sure the buffer isn't empty.
if (BandwidthLimit > 0 && transmissionVolume > 0)
{
// Calculate the time to sleep.
_bandwidth.CalculateSpeed(transmissionVolume);
await Sleep(_bandwidth.PopSpeedRetrieveTime(), token).ConfigureAwait(false);
}
}
private async Task Sleep(int time, CancellationToken token = default)
{
try
{
if (time > 0)
{
var bandWidthtoken = _bandwidthChangeTokenSource.Token;
var linked = CancellationTokenSource.CreateLinkedTokenSource(token, bandWidthtoken).Token;
await Task.Delay(time, linked).ConfigureAwait(false);
}
}
catch (TaskCanceledException)
{
// ignore
}
}
/// <inheritdoc />
public override string ToString()
{
return _baseStream.ToString();
}
private class Bandwidth
{
private long _count;
private int _lastSecondCheckpoint;
private long _lastTransferredBytesCount;
private int _speedRetrieveTime;
public double Speed { get; private set; }
public double AverageSpeed { get; private set; }
public long BandwidthLimit { get; set; }
public Bandwidth()
{
BandwidthLimit = long.MaxValue;
Reset();
}
public void CalculateSpeed(long receivedBytesCount)
{
int elapsedTime = Environment.TickCount - _lastSecondCheckpoint + 1;
receivedBytesCount = Interlocked.Add(ref _lastTransferredBytesCount, receivedBytesCount);
double momentSpeed = receivedBytesCount * 1000 / elapsedTime; // B/s
if (1000 < elapsedTime)
{
Speed = momentSpeed;
AverageSpeed = ((AverageSpeed * _count) + Speed) / (_count + 1);
_count++;
SecondCheckpoint();
}
if (momentSpeed >= BandwidthLimit)
{
var expectedTime = receivedBytesCount * 1000 / BandwidthLimit;
Interlocked.Add(ref _speedRetrieveTime, (int)expectedTime - elapsedTime);
}
}
public int PopSpeedRetrieveTime()
{
return Interlocked.Exchange(ref _speedRetrieveTime, 0);
}
public void Reset()
{
SecondCheckpoint();
_count = 0;
Speed = 0;
AverageSpeed = 0;
}
private void SecondCheckpoint()
{
Interlocked.Exchange(ref _lastSecondCheckpoint, Environment.TickCount);
Interlocked.Exchange(ref _lastTransferredBytesCount, 0);
}
}
}
}

View File

@@ -103,6 +103,7 @@ public partial class ApiController
sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}");
}
Logger.LogDebug("Chara data contained: {nl} {data}", Environment.NewLine, sb.ToString());
await UserPushData(new(visibleCharacters, character)).ConfigureAwait(false);
}
}

View File

@@ -2,6 +2,7 @@
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Dto;
using MareSynchronos.API.Dto.User;
using MareSynchronos.API.SignalR;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services;
@@ -33,6 +34,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
private bool _initialized;
private HubConnection? _mareHub;
private ServerState _serverState;
private CensusUpdateMessage? _lastCensus;
public ApiController(ILogger<ApiController> logger, HubFactory hubFactory, DalamudUtilService dalamudUtil,
PairManager pairManager, ServerConfigurationManager serverManager, MareMediator mediator,
@@ -48,9 +50,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) => DalamudUtilOnLogOut());
Mediator.Subscribe<HubClosedMessage>(this, (msg) => MareHubOnClosed(msg.Exception));
Mediator.Subscribe<HubReconnectedMessage>(this, (msg) => _ = Task.Run(MareHubOnReconnected));
Mediator.Subscribe<HubReconnectedMessage>(this, async (msg) => await MareHubOnReconnected().ConfigureAwait(false));
Mediator.Subscribe<HubReconnectingMessage>(this, (msg) => MareHubOnReconnecting(msg.Exception));
Mediator.Subscribe<CyclePauseMessage>(this, (msg) => _ = CyclePause(msg.UserData));
Mediator.Subscribe<CensusUpdateMessage>(this, (msg) => _lastCensus = msg);
ServerState = ServerState.Offline;
@@ -185,7 +188,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
$"Your client is outdated ({currentClientVer.Major}.{currentClientVer.Minor}.{currentClientVer.Build}), current is: " +
$"{_connectionDto.CurrentClientVersion.Major}.{_connectionDto.CurrentClientVersion.Minor}.{_connectionDto.CurrentClientVersion.Build}. " +
$"Please keep your Mare Synchronos client up-to-date.",
Dalamud.Interface.Internal.Notifications.NotificationType.Error));
Dalamud.Interface.Internal.Notifications.NotificationType.Warning));
}
await LoadIninitialPairs().ConfigureAwait(false);
@@ -210,6 +213,12 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
Logger.LogInformation("Failed to establish connection, retrying");
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token).ConfigureAwait(false);
}
catch (InvalidOperationException ex)
{
Logger.LogWarning(ex, "InvalidOperationException on connection");
await StopConnection(ServerState.Disconnected).ConfigureAwait(false);
return;
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Exception on Connection");
@@ -369,13 +378,13 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
try
{
InitializeApiHooks();
await LoadIninitialPairs().ConfigureAwait(false);
_connectionDto = await GetConnectionDto().ConfigureAwait(false);
if (_connectionDto.ServerVersion != IMareHub.ApiVersion)
{
await StopConnection(ServerState.VersionMisMatch).ConfigureAwait(false);
return;
}
await LoadIninitialPairs().ConfigureAwait(false);
await LoadOnlinePairs().ConfigureAwait(false);
ServerState = ServerState.Connected;
}

View File

@@ -1,7 +1,4 @@
using Dalamud.Plugin.Services;
using MareSynchronos.API.SignalR;
using MareSynchronos.Interop;
using MareSynchronos.MareConfiguration;
using MareSynchronos.API.SignalR;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.WebAPI.SignalR.Utils;
@@ -16,21 +13,19 @@ namespace MareSynchronos.WebAPI.SignalR;
public class HubFactory : MediatorSubscriberBase
{
private readonly MareConfigService _configService;
private readonly IPluginLog _pluginLog;
private readonly ILoggerProvider _loggingProvider;
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly TokenProvider _tokenProvider;
private HubConnection? _instance;
private bool _isDisposed = false;
public HubFactory(ILogger<HubFactory> logger, MareMediator mediator,
ServerConfigurationManager serverConfigurationManager, MareConfigService configService,
TokenProvider tokenProvider, IPluginLog pluginLog) : base(logger, mediator)
ServerConfigurationManager serverConfigurationManager,
TokenProvider tokenProvider, ILoggerProvider pluginLog) : base(logger, mediator)
{
_serverConfigurationManager = serverConfigurationManager;
_configService = configService;
_tokenProvider = tokenProvider;
_pluginLog = pluginLog;
_loggingProvider = pluginLog;
}
public async Task DisposeHubAsync()
@@ -92,7 +87,7 @@ public class HubFactory : MediatorSubscriberBase
.WithAutomaticReconnect(new ForeverRetryPolicy(Mediator))
.ConfigureLogging(a =>
{
a.ClearProviders().AddProvider(new DalamudLoggingProvider(_configService, _pluginLog));
a.ClearProviders().AddProvider(_loggingProvider);
a.SetMinimumLevel(LogLevel.Information);
})
.Build();

View File

@@ -1,3 +1,9 @@
namespace MareSynchronos.WebAPI.SignalR;
public record JwtIdentifier(string ApiUrl, string SecretKey);
public record JwtIdentifier(string ApiUrl, string CharaHash, string SecretKey)
{
public override string ToString()
{
return "{JwtIdentifier; Url: " + ApiUrl + ", Chara: " + CharaHash + ", HasSecretKey: " + !string.IsNullOrEmpty(SecretKey) + "}";
}
}

View File

@@ -1,5 +1,6 @@
using MareSynchronos.API.Routes;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Utils;
using Microsoft.Extensions.Logging;
@@ -10,7 +11,7 @@ using System.Reflection;
namespace MareSynchronos.WebAPI.SignalR;
public sealed class TokenProvider : IDisposable
public sealed class TokenProvider : IDisposable, IMediatorSubscriber
{
private readonly DalamudUtilService _dalamudUtil;
private readonly HttpClient _httpClient;
@@ -18,24 +19,38 @@ public sealed class TokenProvider : IDisposable
private readonly ServerConfigurationManager _serverManager;
private readonly ConcurrentDictionary<JwtIdentifier, string> _tokenCache = new();
public TokenProvider(ILogger<TokenProvider> logger, ServerConfigurationManager serverManager, DalamudUtilService dalamudUtil)
public TokenProvider(ILogger<TokenProvider> logger, ServerConfigurationManager serverManager, DalamudUtilService dalamudUtil, MareMediator mareMediator)
{
_logger = logger;
_serverManager = serverManager;
_dalamudUtil = dalamudUtil;
_httpClient = new();
var ver = Assembly.GetExecutingAssembly().GetName().Version;
Mediator = mareMediator;
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) =>
{
_lastJwtIdentifier = null;
_tokenCache.Clear();
});
Mediator.Subscribe<DalamudLoginMessage>(this, (_) =>
{
_lastJwtIdentifier = null;
_tokenCache.Clear();
});
_httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MareSynchronos", ver!.Major + "." + ver!.Minor + "." + ver!.Build));
}
private JwtIdentifier CurrentIdentifier => new(_serverManager.CurrentApiUrl, _serverManager.GetSecretKey()!);
public MareMediator Mediator { get; }
private JwtIdentifier? _lastJwtIdentifier;
public void Dispose()
{
Mediator.UnsubscribeAll(this);
_httpClient.Dispose();
}
public async Task<string> GetNewToken(CancellationToken token)
public async Task<string> GetNewToken(JwtIdentifier identifier, CancellationToken token)
{
Uri tokenUri;
string response = string.Empty;
@@ -58,16 +73,19 @@ public sealed class TokenProvider : IDisposable
response = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
result.EnsureSuccessStatusCode();
_tokenCache[CurrentIdentifier] = response;
_tokenCache[identifier] = response;
}
catch (HttpRequestException ex)
{
_tokenCache.TryRemove(CurrentIdentifier, out _);
_tokenCache.TryRemove(identifier, out _);
_logger.LogError(ex, "GetNewToken: Failure to get token");
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
Mediator.Publish(new NotificationMessage("Error refreshing token", "Your authentication token could not be renewed. Try reconnecting to Mare manually.",
Dalamud.Interface.Internal.Notifications.NotificationType.Error));
Mediator.Publish(new DisconnectedMessage());
throw new MareAuthFailureException(response);
}
@@ -78,12 +96,54 @@ public sealed class TokenProvider : IDisposable
return response;
}
private JwtIdentifier? GetIdentifier()
{
JwtIdentifier jwtIdentifier;
try
{
jwtIdentifier = new(_serverManager.CurrentApiUrl,
_dalamudUtil.GetPlayerNameHashedAsync().GetAwaiter().GetResult(),
_serverManager.GetSecretKey()!);
_lastJwtIdentifier = jwtIdentifier;
}
catch (Exception ex)
{
if (_lastJwtIdentifier == null)
{
_logger.LogError("GetOrUpdate: No last identifier found, aborting");
return null;
}
_logger.LogWarning(ex, "GetOrUpdate: Could not get JwtIdentifier for some reason or another, reusing last identifier {identifier}", _lastJwtIdentifier);
jwtIdentifier = _lastJwtIdentifier;
}
_logger.LogDebug("GetOrUpdate: Using identifier {identifier}", jwtIdentifier);
return jwtIdentifier;
}
public string? GetToken()
{
JwtIdentifier? jwtIdentifier = GetIdentifier();
if (jwtIdentifier == null) return null;
if (_tokenCache.TryGetValue(jwtIdentifier, out var token))
{
return token;
}
throw new InvalidOperationException("No token present");
}
public async Task<string?> GetOrUpdateToken(CancellationToken ct)
{
if (_tokenCache.TryGetValue(CurrentIdentifier, out var token))
JwtIdentifier? jwtIdentifier = GetIdentifier();
if (jwtIdentifier == null) return null;
if (_tokenCache.TryGetValue(jwtIdentifier, out var token))
return token;
_logger.LogTrace("GetOrUpdate: Getting new token");
return await GetNewToken(ct).ConfigureAwait(false);
return await GetNewToken(jwtIdentifier, ct).ConfigureAwait(false);
}
}