using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; using MediaBrowser.Model.QuickConnect; namespace MediaBrowser.Controller.QuickConnect; /// /// A process-local . It is the single-instance default, and the /// fallback a shared store degrades to while its backend is unreachable, so quick connect keeps /// working for clients whose three legs happen to land on one instance. /// public sealed class InMemoryQuickConnectStore : IQuickConnectStore { private readonly ConcurrentDictionary> _requests = new(StringComparer.Ordinal); private readonly ConcurrentDictionary> _authorizations = new(StringComparer.Ordinal); /// public Task GetRequestBySecretAsync(string secret, CancellationToken cancellationToken = default) { Expire(); return Task.FromResult(_requests.TryGetValue(secret, out var entry) ? entry.Value : null); } /// public Task GetRequestByCodeAsync(string code, CancellationToken cancellationToken = default) { Expire(); return Task.FromResult(_requests.Values .Select(entry => entry.Value) .FirstOrDefault(request => string.Equals(request.Code, code, StringComparison.Ordinal))); } /// public Task SetRequestAsync(QuickConnectResult request, DateTime expiresUtc, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); Expire(); _requests[request.Secret] = new Entry(expiresUtc, request); return Task.CompletedTask; } /// public Task SetAuthorizationAsync(string secret, AuthenticationResult authenticationResult, DateTime expiresUtc, CancellationToken cancellationToken = default) { Expire(); _authorizations[secret] = new Entry(expiresUtc, authenticationResult); return Task.CompletedTask; } /// public Task TryConsumeAuthorizationAsync(string secret, CancellationToken cancellationToken = default) { Expire(); if (!_authorizations.TryRemove(secret, out var entry) || entry.ExpiresUtc <= DateTime.UtcNow) { return Task.FromResult(null); } return Task.FromResult(entry.Value); } private void Expire() { var now = DateTime.UtcNow; foreach (var (secret, entry) in _requests) { if (entry.ExpiresUtc <= now) { _requests.TryRemove(secret, out _); } } foreach (var (secret, entry) in _authorizations) { if (entry.ExpiresUtc <= now) { _authorizations.TryRemove(secret, out _); } } } private sealed record Entry(DateTime ExpiresUtc, T Value); }