sharee.bike-App/TINKLib/Model/Bikes/BikeInfoNS/BluetoothLock/Command/GetLockedLocationCommand.cs
2023-11-06 12:23:09 +01:00

179 lines
5.5 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using TINK.Repository.Request;
using TINK.Services.BluetoothLock;
using TINK.Services.Geolocation;
namespace TINK.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
/// <summary>
/// Provides functionality to get the locked bike location.
/// </summary>
public static class GetLockedLocationCommand
{
/// <summary>
/// Possible steps of closing a lock.
/// </summary>
public enum Step
{
StartingQueryLocation,
DisconnectingLockOnDisconnectedNoLocationError,
}
/// <summary>
/// Possible steps of closing a lock.
/// </summary>
public enum State
{
DisconnetedNoLocationError,
DisconnectError,
QueryLocationSucceeded,
QueryLocationFailed,
}
/// <summary>
/// Interface to notify view model about steps/ state changes of closing process.
/// </summary>
public interface IGetLockedLocationCommandListener
{
/// <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>
/// Get current location.
/// </summary>
/// <param name="listener"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task<LocationDto> InvokeAsync<T>(
IBikeInfoMutable bike,
IGeolocationService geolocation,
ILocksService lockService,
Func<DateTime> dateTimeProvider = null,
IGetLockedLocationCommandListener 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);
}
}
//// Start Action
//// Step: Start query geolocation data.
InvokeCurrentStep(Step.StartingQueryLocation);
// Get geolocation which was requested when closing lock.
IGeolocation closingLockLocation = bike.LockInfo.Location;
if (closingLockLocation != null)
{
// Location was available when closing bike. No further actions required.
return new LocationDto.Builder
{
Latitude = closingLockLocation.Latitude,
Longitude = closingLockLocation.Longitude,
Accuracy = closingLockLocation.Accuracy ?? double.NaN,
Age = bike.LockInfo.LastLockingStateChange is DateTime lastLockState1 ? lastLockState1.Subtract(closingLockLocation.Timestamp.DateTime) : TimeSpan.MaxValue,
}.Build();
}
// Check if bike is around => geolocation information can be queried
var deviceState = lockService[bike.LockInfo.Id].GetDeviceState();
if (deviceState != DeviceState.Connected)
{
// Geolocation can not be queried because bike is not around.
Log.ForContext<T>().Information("User selected booked bike {bikeId} but returning failed. There is no geolocation information available.", bike.Id);
await InvokeCurrentStateAsync(State.DisconnetedNoLocationError, "");
//// Step: Disconnect lock.
InvokeCurrentStep(Step.DisconnectingLockOnDisconnectedNoLocationError);
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.DisconnectError, exception.Message);
}
await InvokeCurrentStateAsync(State.QueryLocationSucceeded, "");
throw new Exception();
}
// Query geolocation.
var ctsLocation = new CancellationTokenSource();
try
{
closingLockLocation = await geolocation.GetAsync(ctsLocation.Token, DateTime.Now);
Log.ForContext<T>().Information("Query location of lock from bike {bikeId} successful.");
}
catch (Exception exception)
{
// No location information available.
Log.ForContext<T>().Information("Geolocation query failed. {@exception}", exception);
await InvokeCurrentStateAsync(State.QueryLocationFailed, exception.Message);
throw;
}
await InvokeCurrentStateAsync(State.QueryLocationSucceeded, string.Empty);
// Update last lock state time
// save geolocation data for sending to backend
var currentLocationDto = closingLockLocation != null
? new LocationDto.Builder
{
Latitude = closingLockLocation.Latitude,
Longitude = closingLockLocation.Longitude,
Accuracy = closingLockLocation.Accuracy ?? double.NaN,
Age = (dateTimeProvider != null ? dateTimeProvider() : DateTime.Now).Subtract(closingLockLocation.Timestamp.DateTime),
}.Build()
: null;
return currentLocationDto;
}
}
}