namespace OpenNest.Data;
///
/// Loads the details of the nest highlighted in the Database-mode nest browser. Moving
/// the highlight starts a new load and cancels the previous one; a superseded load's
/// result or failure is discarded, so details never belong to a nest that is no longer
/// highlighted. Call from one thread (the UI thread).
///
public sealed class NestDetailsSession : IDisposable
{
private readonly Func> _load;
private CancellationTokenSource? _pending;
private int _generation;
private bool _disposed;
///
/// Builds the details of one nest; throws when the nest cannot be read (for example
/// when it no longer exists on the server).
///
public NestDetailsSession(Func> load)
{
ArgumentNullException.ThrowIfNull(load);
_load = load;
}
/// The nest of the latest load; null before the first load and after .
public Guid? NestId { get; private set; }
/// Details of ; null while loading, after a failure and after .
public NestDetails? Details { get; private set; }
/// True while the latest load is outstanding; superseded loads do not count.
public bool IsLoading { get; private set; }
///
/// Loads one nest's details. Returns true when they were applied, false when a later
/// load or superseded this one. A failure of the latest load
/// propagates; failures of superseded loads are ignored.
///
public async Task LoadAsync(Guid id)
{
ObjectDisposedException.ThrowIf(_disposed, this);
// Supersede before cancelling: a load that completes synchronously when cancelled
// must already see itself as superseded. Each source is released by its own load.
var cancellation = Supersede();
var generation = _generation;
NestId = id;
IsLoading = true;
try
{
var details = await _load(id, cancellation.Token);
if (generation != _generation)
return false;
Details = details;
return true;
}
catch (Exception) when (generation != _generation)
{
return false;
}
finally
{
if (ReferenceEquals(_pending, cancellation))
_pending = null;
if (generation == _generation)
IsLoading = false;
cancellation.Dispose();
}
}
/// Forgets the current details and discards any load in flight (no nest highlighted).
public void Clear()
{
if (_disposed)
return;
_generation++;
var pending = _pending;
_pending = null;
NestId = null;
Details = null;
IsLoading = false;
pending?.Cancel();
}
private CancellationTokenSource Supersede()
{
var previous = _pending;
var cancellation = new CancellationTokenSource();
_pending = cancellation;
_generation++;
Details = null;
previous?.Cancel();
return cancellation;
}
/// Cancels any load in flight (which releases its own source when it ends).
public void Dispose()
{
if (_disposed)
return;
Clear();
_disposed = true;
}
}