mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2024-11-16 23:26:26 +01:00
179 lines
5.2 KiB
C#
179 lines
5.2 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 ex)
|
||
|
{
|
||
|
Log.ForContext<T>().Error("An exception {exception} was thrown invoking step- action for set {step} ", ex, 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 ex)
|
||
|
{
|
||
|
Log.ForContext<T>().Error("An exception {exception} was thrown invoking state- action for set {state} ", ex, state);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 {bike} but returning failed. There is no geolocation information available.", bike);
|
||
|
|
||
|
await InvokeCurrentStateAsync(State.DisconnetedNoLocationError, "");
|
||
|
|
||
|
// Disconnect lock.
|
||
|
InvokeCurrentStep(Step.DisconnectingLockOnDisconnectedNoLocationError);
|
||
|
try
|
||
|
{
|
||
|
bike.LockInfo.State = await lockService.DisconnectAsync(bike.LockInfo.Id, bike.LockInfo.Guid);
|
||
|
}
|
||
|
catch (Exception exception)
|
||
|
{
|
||
|
Log.ForContext<T>().Error("Lock can not be disconnected. {Exception}", 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);
|
||
|
}
|
||
|
catch (Exception ex)
|
||
|
{
|
||
|
// No location information available.
|
||
|
Log.ForContext<T>().Information("Returning closed bike {Bike} is not possible. Geolocation query failed. {Exception}", bike, ex);
|
||
|
|
||
|
await InvokeCurrentStateAsync(State.QueryLocationFailed, ex.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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|