sharee.bike-App/SharedBusinessLogic/Model/Bikes/BikeInfoNS/BluetoothLock/Command/ConnectAndGetStateCommand.cs
2024-04-09 12:53:23 +02:00

184 lines
5.3 KiB
C#

using System;
using System.Threading.Tasks;
using Serilog;
using ShareeBike.MultilingualResources;
using ShareeBike.Services.BluetoothLock;
using ShareeBike.Services.BluetoothLock.Exception;
using ShareeBike.Services.BluetoothLock.Tdo;
using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
using ShareeBike.ViewModel.Bikes;
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
public static class ConnectAndGetStateCommand
{
/// <summary>
/// Possible steps of connecting or disconnecting a lock.
/// </summary>
public enum Step
{
ConnectLock,
GetLockingState,
}
/// <summary>
/// Possible steps of connecting or disconnecting a lock.
/// </summary>
public enum State
{
BluetoothOff,
NoLocationPermission,
LocationServicesOff,
OutOfReachError,
GeneralConnectLockError,
}
/// <summary>
/// Interface to notify view model about steps/ state changes of connecting or disconnecting lock process.
/// </summary>
public interface IConnectAndGetStateCommandListener
{
/// <summary>
/// Reports current step.
/// </summary>
/// <param name="currentStep">Current step to report.</param>
void ReportStep(Step currentStep);
/// <summary>
/// Reports current state.
/// </summary>
/// <param name="currentState">Current state to report.</param>
/// <param name="message">Message describing the current state.</param>
/// <returns></returns>
Task ReportStateAsync(State currentState, string message);
}
/// <summary>
/// Connect or disconnect lock.
/// </summary>
/// <param name="listener"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task InvokeAsync<T>(
IBikeInfoMutable bike,
ILocksService lockService,
IConnectAndGetStateCommandListener listener = null)
{
// Invokes member to notify about step being started.
void InvokeCurrentStep(Step step)
{
if (listener == null)
return;
try
{
listener.ReportStep(step);
}
catch (Exception exception)
{
Log.ForContext<T>().Error("An exception {@exception} was thrown invoking step-action for step {step} ", exception, step);
}
}
// Invokes member to notify about state change.
async Task InvokeCurrentStateAsync(State state, string message)
{
if (listener == null)
return;
try
{
await listener.ReportStateAsync(state, message);
}
catch (Exception exception)
{
Log.ForContext<T>().Error("An exception {@exception} was thrown invoking state-action for state {state} ", exception, state);
}
}
// Connect lock
InvokeCurrentStep(Step.ConnectLock);
LockInfoTdo result = null;
var continueConnect = true;
var retryCount = 1;
while (continueConnect && result == null)
{
try
{
result = await lockService.ConnectAsync(
new LockInfoAuthTdo.Builder { Id = bike.LockInfo.Id, Guid = bike.LockInfo.Guid, K_seed = bike.LockInfo.Seed, K_u = bike.LockInfo.UserKey }.Build(),
lockService.TimeOut.GetSingleConnect(retryCount));
Log.ForContext<T>().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", bike.Id, bike.LockInfo.State);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Connection to lock of bike {bikeId} failed. ", bike.Id);
if (exception is ConnectBluetoothNotOnException)
{
continueConnect = false;
Log.ForContext<T>().Error("Bluetooth not on.");
await InvokeCurrentStateAsync(State.BluetoothOff, exception.Message);
}
else if (exception is ConnectLocationPermissionMissingException)
{
continueConnect = false;
Log.ForContext<T>().Error("Location permission missing.");
await InvokeCurrentStateAsync(State.NoLocationPermission, exception.Message);
}
else if (exception is ConnectLocationOffException)
{
continueConnect = false;
Log.ForContext<T>().Error("Location services not on.");
await InvokeCurrentStateAsync(State.LocationServicesOff, exception.Message);
}
else if (exception is OutOfReachException)
{
continueConnect = false;
Log.ForContext<T>().Error("Lock is out of reach.");
await InvokeCurrentStateAsync(State.OutOfReachError, exception.Message);
}
else
{
Log.ForContext<T>().Error("{@exception}", exception);
string message;
if (retryCount < 2)
{
message = AppResources.ErrorConnectLock;
}
else if (retryCount < 3)
{
message = AppResources.ErrorConnectLockEscalationLevel1;
}
else
{
message = AppResources.ErrorConnectLockEscalationLevel2;
continueConnect = false;
}
await InvokeCurrentStateAsync(State.GeneralConnectLockError, message);
}
if (continueConnect)
{
retryCount++;
continue;
}
throw;
}
}
// Get locking state
InvokeCurrentStep(Step.GetLockingState);
bike.LockInfo.State = result?.State?.GetLockingState() ?? LockingState.UnknownDisconnected;
if (bike.LockInfo.State == LockingState.UnknownDisconnected)
{
// Do not display any messages here, because search is implicit.
Log.ForContext<T>().Error("Lock is still not connected.");
}
bike.LockInfo.Guid = result?.Guid ?? new Guid();
}
}
}