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

292 lines
8.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Serilog;
using ShareeBike.Model.Connector;
using ShareeBike.Repository.Exception;
using ShareeBike.Services.BluetoothLock;
using ShareeBike.Services.BluetoothLock.Exception;
using ShareeBike.Services.BluetoothLock.Tdo;
using ShareeBike.Services.Geolocation;
using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
public static class OpenCommand
{
/// <summary>
/// Possible steps of opening a lock.
/// </summary>
public enum Step
{
OpeningLock,
WaitStopPolling,
GetLockInfos,
UpdateLockingState,
}
/// <summary>
/// Possible states of opening a lock.
/// </summary>
public enum State
{
StopPollingFailed,
OutOfReachError,
CouldntOpenBoldStatusIsUnknownError,
CouldntOpenBoldIsBlockedError,
CouldntOpenInconsistentStateError,
GeneralOpenError,
WebConnectFailed,
ResponseIsInvalid,
BackendUpdateFailed
}
/// <summary>
/// Interface to notify view model about steps/ state changes of opening process.
/// </summary>
public interface IOpenCommandListener
{
/// <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>
/// Opens the lock and updates copri.
/// </summary>
/// <param name="listener">Interface to notify view model about steps/ state changes of opening process.</param>
/// <param name="stopPolling">Task which stops polling.</param>
public static async Task InvokeAsync<T>(
IBikeInfoMutable bike,
ILocksService lockService,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
IOpenCommandListener listener,
Task stopPollingTask)
{
// 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);
}
}
// Wait polling task to stop (on finished or on canceled).
async Task WaitForPendingTasks()
{
// Step: Wait until getting geolocation has completed.
InvokeCurrentStep(Step.WaitStopPolling);
Log.ForContext<T>().Information($"Waiting on stop polling to finish...");
try
{
await Task.WhenAll(new List<Task> { stopPollingTask ?? Task.CompletedTask });
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Wait for polling task to finish failed. {@exception}", exception);
await InvokeCurrentStateAsync(State.StopPollingFailed, exception.Message);
return;
}
Log.ForContext<T>().Information($"Stop polling finished.");
return;
}
// Get lock infos
async Task GetLockInfos()
{
Log.ForContext<T>().Debug($"Starting step {Step.GetLockInfos}...");
InvokeCurrentStep(Step.GetLockInfos);
// get current charging level
try
{
bike.LockInfo.BatteryPercentage = await lockService[bike.LockInfo.Id].GetBatteryPercentageAsync();
Log.ForContext<T>().Information("Lock infos of bike {bikeId} read successfully.", bike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Lock infos could not be read.", bike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<T>().Debug("Lock is out of reach");
}
else
{
Log.ForContext<T>().Debug("{@exception}", exception);
}
}
// get version infos.
var versionTdo = lockService[bike.LockInfo.Id].VersionInfo;
if (versionTdo != null)
{
bike.LockInfo.VersionInfo = new VersionInfo.Builder
{
FirmwareVersion = versionTdo.FirmwareVersion,
HardwareVersion = versionTdo.HardwareVersion,
LockVersion = versionTdo.LockVersion,
}.Build();
}
}
// Updates locking state
async Task UpdateLockingState()
{
// Step: Update backend.
InvokeCurrentStep(Step.UpdateLockingState);
try
{
await connectorFactory(true).Command.UpdateLockingStateAsync(bike);
Log.ForContext<T>().Information("Backend updated for bike {bikeId} successfully.", bike.Id);
}
catch (Exception exception)
{
Log.ForContext<ReservedOpen>().Information("Updating backend for bike {bikeId} failed.", bike.Id);
//BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
if (exception is WebConnectFailureException)
{
// Copri server is not reachable.
Log.ForContext<T>().Debug("Copri server not reachable.");
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
return;
}
else if (exception is ResponseException copriException)
{
// Copri server is not reachable.
Log.ForContext<T>().Debug("Message: {Message} Details: {Details}", copriException.Message, copriException.Response);
await InvokeCurrentStateAsync(State.ResponseIsInvalid, exception.Message);
return;
}
else
{
Log.ForContext<T>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", bike.Id, exception);
await InvokeCurrentStateAsync(State.BackendUpdateFailed, exception.Message);
return;
}
}
}
//// Start Action
//// Step: Open lock.
Log.ForContext<T>().Information($"Starting step {Step.OpeningLock}...");
InvokeCurrentStep(Step.OpeningLock);
LockitLockingState? lockingState;
try
{
lockingState = await lockService[bike.LockInfo.Id].OpenAsync();
Log.ForContext<T>().Information("Lock of bike {bikeId} opened successfully.", bike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be opened.", bike.Id);
if (exception is OutOfReachException)
{
Log.ForContext<T>().Debug("Lock is out of reach");
await InvokeCurrentStateAsync(State.OutOfReachError, exception.Message);
}
else if (exception is CouldntOpenBoldStatusIsUnknownException)
{
Log.ForContext<T>().Debug("Lock status is unknown.");
await InvokeCurrentStateAsync(State.CouldntOpenBoldStatusIsUnknownError, exception.Message);
}
else if (exception is CouldntOpenBoldIsBlockedException)
{
Log.ForContext<T>().Debug("Bold is blocked.}");
await InvokeCurrentStateAsync(State.CouldntOpenBoldIsBlockedError, exception.Message);
}
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
&& inconsistentState.State == LockingState.Closed)
{
Log.ForContext<T>().Debug("Lock reports that it is still closed.}");
await InvokeCurrentStateAsync(State.CouldntOpenInconsistentStateError, exception.Message);
}
else
{
Log.ForContext<T>().Debug("{@exception}", exception);
await InvokeCurrentStateAsync(State.GeneralOpenError, exception.Message);
}
// Update current state from exception
bike.LockInfo.State = exception is StateAwareException stateAwareException
? stateAwareException.State
: LockingState.UnknownDisconnected;
if (!isConnectedDelegate())
{
// Lock state can not be updated because there is no connected.
throw;
}
if (exception is OutOfReachException)
{
// Locking state can not be updated because lock is not connected.
throw;
}
//// Step: Get Lock Battery and Version info
await GetLockInfos();
//// Step: Wait for stop polling finished
await WaitForPendingTasks();
//// Step: Update backend.
//Do this even if current lock state is closed (lock state must not necessarily be closed before try to close, i.e. something undefined between open and opened).
await UpdateLockingState();
throw;
}
bike.LockInfo.State = lockingState?.GetLockingState() ?? LockingState.UnknownDisconnected;
if (!isConnectedDelegate())
{
return;
}
//// Step: Get Lock Battery and Version info
await GetLockInfos();
//// Step: Wait for stop polling finished
await WaitForPendingTasks();
//// Step: Update backend.
await UpdateLockingState();
}
}
}