using System;
using System.Threading;
using System.Threading.Tasks;
// ReSharper disable UnusedMember.Global - public API
namespace IPCamLib
{
///
/// Receives frames from an ICamera as well as status updates.
///
public interface IRetryableCameraObserver : ICameraObserver
{
///
/// Called when a new Fetch attempt has started.
///
Task OnFetch();
///
/// Called when the stream was disconnected.
///
/// Contains the exception, if any occured. Null for graceful disconnects and timeouts.
/// The delay until the next Fetch is attempted.
Task OnDisconnected(Exception exception, TimeSpan retryDelay);
}
///
/// Implements retry logic for an ICamera instance.
///
public class RetryableCamera
{
private readonly ICamera camera;
///
/// Creates a new instance of a RetryableCamera.
///
/// The camera instance to fetch from.
public RetryableCamera(ICamera camera)
{
this.camera = camera;
}
///
/// Starts receiving frames and will retry when disconnected.
///
/// The observer implementation to receive frames and status updates.
/// A CancellationToken which will stop the camera stream when cancelled.
public async Task Fetch(IRetryableCameraObserver observer, CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
// TODO incremental back-off?
var retryDelay = TimeSpan.FromSeconds(5);
var exception = false;
try
{
await observer.OnFetch();
await camera.Fetch(observer, cancellationToken);
}
catch (TaskCanceledException)
{
// Empty by design
}
catch (Exception e)
{
await observer.OnDisconnected(e, retryDelay);
exception = true;
}
if (!exception && !cancellationToken.IsCancellationRequested)
await observer.OnDisconnected(null, retryDelay);
await Task.Delay(retryDelay, cancellationToken);
}
}
}
}