mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-06-22 13:57:28 +02:00
Version 3.0.381
This commit is contained in:
parent
f963c0a219
commit
3a363acf3a
1525 changed files with 60589 additions and 125098 deletions
|
@ -0,0 +1,288 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Repository.Request;
|
||||
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 CloseCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of closing a lock.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
StartStopingPolling,
|
||||
StartingQueryingLocation,
|
||||
ClosingLock,
|
||||
WaitStopPollingQueryLocation,
|
||||
/// <summary>
|
||||
/// Notifies back end about modified lock state.
|
||||
/// </summary>
|
||||
UpdateLockingState,
|
||||
QueryLocationTerminated
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible states of closing a lock.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
OutOfReachError,
|
||||
CouldntCloseMovingError,
|
||||
CouldntCloseBoltBlockedError,
|
||||
GeneralCloseError,
|
||||
StartGeolocationException,
|
||||
WaitGeolocationException,
|
||||
WebConnectFailed,
|
||||
ResponseIsInvalid,
|
||||
BackendUpdateFailed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of closing process.
|
||||
/// </summary>
|
||||
public interface ICloseCommandListener
|
||||
{
|
||||
/// <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>
|
||||
/// Closes the lock and updates copri.
|
||||
/// </summary>
|
||||
/// <param name="listener">Interface to notify view model about steps/ state changes of closing process.</param>
|
||||
/// <param name="stopPolling">Task which stops polling.</param>
|
||||
public static async Task InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
ICloseCommandListener 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 for geolocation and polling task to stop (on finished or on canceled).
|
||||
async Task<IGeolocation> WaitForPendingTasks(Task<IGeolocation> locationTask)
|
||||
{
|
||||
// Step: Wait until getting geolocation has completed.
|
||||
InvokeCurrentStep(Step.WaitStopPollingQueryLocation);
|
||||
Log.ForContext<T>().Information($"Waiting on steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} to finish...");
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(new List<Task> { locationTask, stopPollingTask ?? Task.CompletedTask });
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// No location information available.
|
||||
Log.ForContext<T>().Information("Canceling query location/ wait for polling task to finish failed. {@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.WaitGeolocationException, exception.Message);
|
||||
InvokeCurrentStep(Step.QueryLocationTerminated);
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.ForContext<T>().Information($"Steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} finished.");
|
||||
InvokeCurrentStep(Step.QueryLocationTerminated);
|
||||
return locationTask.Result;
|
||||
}
|
||||
|
||||
// Updates locking state
|
||||
async Task UpdateLockingState(IGeolocation location, DateTime timeStamp)
|
||||
{
|
||||
// Step: Update backend.
|
||||
InvokeCurrentStep(Step.UpdateLockingState);
|
||||
try
|
||||
{
|
||||
await connectorFactory(true).Command.UpdateLockingStateAsync(
|
||||
bike,
|
||||
location != null
|
||||
? new LocationDto.Builder
|
||||
{
|
||||
Latitude = location.Latitude,
|
||||
Longitude = location.Longitude,
|
||||
Accuracy = location.Accuracy ?? double.NaN,
|
||||
Age = timeStamp.Subtract(location.Timestamp.DateTime),
|
||||
}.Build()
|
||||
: null);
|
||||
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: Start query geolocation data.
|
||||
Log.ForContext<T>().Debug($"Starting step {Step.StartingQueryingLocation}...");
|
||||
InvokeCurrentStep(Step.StartingQueryingLocation);
|
||||
var ctsLocation = new CancellationTokenSource();
|
||||
Task<IGeolocation> currentLocationTask = Task.FromResult<IGeolocation>(null);
|
||||
var timeStampNow = DateTime.Now;
|
||||
try
|
||||
{
|
||||
currentLocationTask = geolocation.GetAsync(ctsLocation.Token, timeStampNow);
|
||||
Log.ForContext<T>().Information("Starting query location successful.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// No location information available.
|
||||
Log.ForContext<T>().Information("Starting query location failed. {@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.StartGeolocationException, exception.Message);
|
||||
}
|
||||
|
||||
//// Step: Close lock.
|
||||
IGeolocation currentLocation;
|
||||
Log.ForContext<T>().Information($"Starting step {Step.ClosingLock}...");
|
||||
InvokeCurrentStep(Step.ClosingLock);
|
||||
LockitLockingState? lockingState;
|
||||
try
|
||||
{
|
||||
lockingState = await lockService[bike.LockInfo.Id].CloseAsync();
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} closed successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be closed.", 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 CouldntCloseMovingException)
|
||||
{
|
||||
Log.ForContext<T>().Debug("Lock is moving.");
|
||||
await InvokeCurrentStateAsync(State.CouldntCloseMovingError, exception.Message);
|
||||
}
|
||||
else if (exception is CouldntCloseBoltBlockedException)
|
||||
{
|
||||
Log.ForContext<T>().Debug("Bold is blocked.}");
|
||||
await InvokeCurrentStateAsync(State.CouldntCloseBoltBlockedError, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Debug("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralCloseError, exception.Message);
|
||||
}
|
||||
|
||||
// Signal cts to cancel getting geolocation.
|
||||
ctsLocation.Cancel();
|
||||
|
||||
// Wait until getting geolocation and stop polling has completed.
|
||||
currentLocation = await WaitForPendingTasks(currentLocationTask);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Update backend.
|
||||
// Do this even if current lock state is open (lock state must not necessarily be open before try to open, i.e. something undefined between open and closed).
|
||||
await UpdateLockingState(currentLocation, timeStampNow);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
//// Step: Wait until getting geolocation and stop polling has completed.
|
||||
currentLocation = await WaitForPendingTasks(currentLocationTask);
|
||||
|
||||
bike.LockInfo.State = lockingState?.GetLockingState() ?? LockingState.UnknownDisconnected;
|
||||
|
||||
// Keep geolocation where closing action occurred.
|
||||
bike.LockInfo.Location = currentLocation;
|
||||
|
||||
if (!isConnectedDelegate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//// Step: Update backend.
|
||||
await UpdateLockingState(currentLocation, timeStampNow);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command
|
||||
{
|
||||
public static class DisconnectCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of connecting or disconnecting a lock.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
DisconnectLock,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of connecting or disconnecting a lock.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
GeneralDisconnectError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of connecting or disconnecting lock process.
|
||||
/// </summary>
|
||||
public interface IDisconnectCommandListener
|
||||
{
|
||||
/// <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,
|
||||
IDisconnectCommandListener 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);
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect lock
|
||||
InvokeCurrentStep(Step.DisconnectLock);
|
||||
if (bike.LockInfo.State != LockingState.UnknownDisconnected)
|
||||
{
|
||||
try
|
||||
{
|
||||
bike.LockInfo.State = await lockService.DisconnectAsync(bike.LockInfo.Id, bike.LockInfo.Guid);
|
||||
Log.ForContext<T>().Information("Lock from bike {bikeId} disconnected successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Lock from bike {bikeId} can not be disconnected. {@exception}", bike.Id, exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralDisconnectError, exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue