mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-06-22 22:07: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
159
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BikeNS/Bike.cs
Normal file
159
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BikeNS/Bike.cs
Normal file
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS
|
||||
{
|
||||
/// <summary> Count of wheels. </summary>
|
||||
/// <remarks> Numeric values of enum must match count of wheels</remarks>
|
||||
public enum WheelType
|
||||
{
|
||||
Mono = 1,
|
||||
Two = 2,
|
||||
Trike = 3,
|
||||
Quad = 4
|
||||
}
|
||||
|
||||
/// <summary> Type of bike. </summary>
|
||||
public enum TypeOfBike
|
||||
{
|
||||
Allround = 0,
|
||||
Cargo = 1,
|
||||
City = 2,
|
||||
}
|
||||
|
||||
/// <summary> Holds whether bike is a AA bike (bike must be always returned a the same station) or AB bike (start and end stations can be different stations).</summary>
|
||||
public enum AaRideType
|
||||
{
|
||||
NoAaRide = 0,
|
||||
AaRide = 1,
|
||||
}
|
||||
|
||||
/// <summary> Holds the model of lock. </summary>
|
||||
public enum LockModel
|
||||
{
|
||||
ILockIt, // haveltec GbmH Brandenburg, Germany bluetooth lock
|
||||
BordComputer, // TeilRad BC
|
||||
Sigo, // Sigo GmbH Darmstadt, Germany bike lock
|
||||
}
|
||||
|
||||
/// <summary> Holds the type of lock. </summary>
|
||||
public enum LockType
|
||||
{
|
||||
Backend, // Backend, i.e. COPRI controls lock (open, close, ...)
|
||||
Bluethooth, // Lock is controlled.
|
||||
}
|
||||
|
||||
public class Bike : IEquatable<Bike>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a bike.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique id of bike.</param>
|
||||
public Bike(
|
||||
string id,
|
||||
LockModel lockModel,
|
||||
WheelType? wheelType = null,
|
||||
TypeOfBike? typeOfBike = null,
|
||||
AaRideType? aaRideType = null,
|
||||
string description = null)
|
||||
{
|
||||
WheelType = wheelType;
|
||||
TypeOfBike = typeOfBike;
|
||||
AaRideType = aaRideType;
|
||||
LockModel = lockModel;
|
||||
Id = id;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the unique id of the bike;
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the count of wheels.
|
||||
/// </summary>
|
||||
public WheelType? WheelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the type of bike.
|
||||
/// </summary>
|
||||
public TypeOfBike? TypeOfBike { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether bike is a AA bike (bike must be always returned a the same station) or AB bike (start and end stations can be different stations).
|
||||
/// </summary>
|
||||
public AaRideType? AaRideType { get; }
|
||||
|
||||
/// <summary> Gets the model of the lock. </summary>
|
||||
public LockModel LockModel { get; private set; }
|
||||
|
||||
/// <summary> Holds the description of the bike. </summary>
|
||||
public string Description { get; }
|
||||
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var l_oBike = obj as Bike;
|
||||
if (l_oBike == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals(l_oBike);
|
||||
}
|
||||
|
||||
/// <summary> Converts the instance to text.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return WheelType == null || TypeOfBike == null
|
||||
? $"Id={Id}{(!string.IsNullOrEmpty(Description) ? $", {Description}" : "")}"
|
||||
: $"Id={Id}{(WheelType != null ? $", wheel(s)={WheelType}" : string.Empty)}{(TypeOfBike != null ? $"type={TypeOfBike}" : "")}.";
|
||||
}
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public bool Equals(Bike other)
|
||||
{
|
||||
return other != null &&
|
||||
Id == other.Id &&
|
||||
WheelType == other.WheelType &&
|
||||
TypeOfBike == other.TypeOfBike
|
||||
&& Description == other.Description;
|
||||
}
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public static bool operator ==(Bike bike1, Bike bike2)
|
||||
{
|
||||
return EqualityComparer<Bike>.Default.Equals(bike1, bike2);
|
||||
}
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public static bool operator !=(Bike bike1, Bike bike2)
|
||||
{
|
||||
return !(bike1 == bike2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates hash code for bike object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = -390870100;
|
||||
hashCode = hashCode * -1521134295 + Id.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + WheelType?.GetHashCode() ?? 0;
|
||||
hashCode = hashCode * -1521134295 + TypeOfBike?.GetHashCode() ?? 0;
|
||||
hashCode = hashCode * -1521134295 + Description?.GetHashCode() ?? 0;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS
|
||||
{
|
||||
public static class BikeExtension
|
||||
{
|
||||
public static LockType GetLockType(this LockModel model)
|
||||
{
|
||||
switch (model)
|
||||
{
|
||||
case LockModel.ILockIt:
|
||||
return LockType.Bluethooth;
|
||||
|
||||
case LockModel.Sigo:
|
||||
return LockType.Backend;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported lock model {model} detected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functionality to get the locked bike location.
|
||||
/// </summary>
|
||||
public static class AuthCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
Authenticate,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
WebConnectFailed,
|
||||
GeneralAuthError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of closing process.
|
||||
/// </summary>
|
||||
public interface IAuthCommandListener
|
||||
{
|
||||
/// <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 InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IAuthCommandListener listener)
|
||||
{
|
||||
// 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: Authenticate user
|
||||
InvokeCurrentStep(Step.Authenticate);
|
||||
|
||||
try
|
||||
{
|
||||
// Repeat reservation to get a new seed/ k_user value.
|
||||
await connectorFactory(true).Command.CalculateAuthKeys(bike);
|
||||
Log.ForContext<T>().Information("Calculation of AuthKeys successfully.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Error("Calculation of AuthKeys failed (Copri server not reachable).");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("Calculation of AuthKeys failed. {@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralAuthError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
public static class CancelReservationCommand
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of returning a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
CancelReservation,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of returning a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
WebConnectFailed,
|
||||
InvalidResponse,
|
||||
GeneralCancelReservationError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of returning bike process.
|
||||
/// </summary>
|
||||
public interface ICancelReservationCommandListener
|
||||
{
|
||||
/// <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 InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
ICancelReservationCommandListener listener)
|
||||
{
|
||||
// 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
|
||||
// Cancel Reservation
|
||||
InvokeCurrentStep(Step.CancelReservation);
|
||||
|
||||
try
|
||||
{
|
||||
await connectorFactory(true).Command.DoCancelReservation(bike);
|
||||
Log.ForContext<T>().Information("User canceled reservation of bike {bikeId} successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<ReservedDisconnected>().Information("User selected reserved bike {bikeId} but cancel reservation failed.", bike.Id);
|
||||
if (exception is InvalidAuthorizationResponseException)
|
||||
{
|
||||
// Copri response is invalid.
|
||||
Log.ForContext<T>().Error("Invalid auth. response.");
|
||||
await InvokeCurrentStateAsync(State.InvalidResponse, exception.Message);
|
||||
}
|
||||
else if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Error("Copri server not reachable.");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralCancelReservationError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Repository.Request;
|
||||
using System.Threading;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
public static class EndRentalCommand
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of returning a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
GetLocation,
|
||||
ReturnBike,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of returning a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
GPSNotSupportedException,
|
||||
GPSNotEnabledException,
|
||||
NoGPSPermissionsException,
|
||||
GeneralQueryLocationFailed,
|
||||
|
||||
WebConnectFailed,
|
||||
NotAtStation,
|
||||
NoGPSData,
|
||||
ResponseException,
|
||||
GeneralEndRentalError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of returning bike process.
|
||||
/// </summary>
|
||||
public interface IEndRentalCommandListener
|
||||
{
|
||||
/// <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>
|
||||
/// End rental.
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async Task<BookingFinishedModel> InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<DateTime> dateTimeProvider = null,
|
||||
Func<bool> isConnectedDelegate = null,
|
||||
Func<bool, IConnector> connectorFactory = null,
|
||||
IEndRentalCommandListener 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
|
||||
|
||||
// Get Location
|
||||
//// Step: Start query geolocation data.
|
||||
InvokeCurrentStep(Step.GetLocation);
|
||||
|
||||
// Get geolocation which was requested when closing lock.
|
||||
var closingLockLocation = bike.LockInfo.Location;
|
||||
|
||||
LocationDto endRentalLocation;
|
||||
if (closingLockLocation != null)
|
||||
{
|
||||
// Location was available when closing bike. No further actions required.
|
||||
endRentalLocation =
|
||||
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();
|
||||
}
|
||||
else
|
||||
{
|
||||
IGeolocation newLockLocation = null;
|
||||
|
||||
// Check if bike is around
|
||||
var deviceState = lockService[bike.LockInfo.Id].GetDeviceState();
|
||||
|
||||
// Geolocation can not be queried because bike is not around.
|
||||
if (deviceState != DeviceState.Connected)
|
||||
{
|
||||
Log.ForContext<T>().Information("There is no geolocation information available since lock of bike {bikeId} is not connected", bike.Id);
|
||||
// no GPS data exception.
|
||||
//NoGPSDataException.IsNoGPSData("There is no geolocation information available since lock is not connected", out NoGPSDataException exception);
|
||||
await InvokeCurrentStateAsync(State.NoGPSData, "There is no geolocation information available since lock is not connected");
|
||||
|
||||
return null; // return empty BookingFinishedModel
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bike is around -> Query geolocation.
|
||||
var ctsLocation = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
newLockLocation = await geolocation.GetAsync(ctsLocation.Token, DateTime.Now);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// No location information available.
|
||||
Log.ForContext<T>().Information("Geolocation query failed.");
|
||||
if (exception is FeatureNotSupportedException)
|
||||
{
|
||||
Log.ForContext<T>().Error("Location service are not supported on device.");
|
||||
await InvokeCurrentStateAsync(State.GPSNotSupportedException, exception.Message);
|
||||
}
|
||||
if (exception is FeatureNotEnabledException)
|
||||
{
|
||||
Log.ForContext<T>().Error("Location service are off.");
|
||||
await InvokeCurrentStateAsync(State.GPSNotEnabledException, exception.Message);
|
||||
}
|
||||
if (exception is PermissionException)
|
||||
{
|
||||
Log.ForContext<T>().Error("No location service permissions granted.");
|
||||
await InvokeCurrentStateAsync(State.NoGPSPermissionsException, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Information("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralQueryLocationFailed, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Update last lock state time
|
||||
// save geolocation data for sending to backend
|
||||
endRentalLocation = newLockLocation != null
|
||||
? new LocationDto.Builder
|
||||
{
|
||||
Latitude = newLockLocation.Latitude,
|
||||
Longitude = newLockLocation.Longitude,
|
||||
Accuracy = newLockLocation.Accuracy ?? double.NaN,
|
||||
Age = (dateTimeProvider != null ? dateTimeProvider() : DateTime.Now).Subtract(newLockLocation.Timestamp.DateTime),
|
||||
}.Build()
|
||||
: null;
|
||||
}
|
||||
|
||||
// Return bike
|
||||
InvokeCurrentStep(Step.ReturnBike);
|
||||
BookingFinishedModel bookingFinished;
|
||||
try
|
||||
{
|
||||
bookingFinished = await connectorFactory(true).Command.DoReturn(
|
||||
bike,
|
||||
endRentalLocation);
|
||||
Log.ForContext<T>().Information("Rental of bike {bikeId} was terminated successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Rental of bike {bikeId} can not be terminated.", bike.Id);
|
||||
if (exception is WebConnectFailureException)
|
||||
{
|
||||
// No web.
|
||||
Log.ForContext<T>().Error("Copri server not reachable. No web.");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else if (exception is NotAtStationException notAtStationException)
|
||||
{
|
||||
// not at station.
|
||||
Log.ForContext<T>().Error("COPRI returned out of GEO fencing error. Position send to COPRI {position}.", endRentalLocation);
|
||||
await InvokeCurrentStateAsync(State.NotAtStation, string.Format(AppResources.ErrorEndRentalNotAtStation, notAtStationException.StationNr, notAtStationException.Distance));
|
||||
|
||||
// reset location -> at next try query new location data
|
||||
bike.LockInfo.Location = null;
|
||||
}
|
||||
else if (exception is NoGPSDataException)
|
||||
{
|
||||
// no GPS data.
|
||||
Log.ForContext<T>().Error("COPRI returned a no-GPS-data error.");
|
||||
await InvokeCurrentStateAsync(State.NoGPSData, exception.Message);
|
||||
|
||||
// reset location -> at next try query new location data
|
||||
bike.LockInfo.Location = null;
|
||||
}
|
||||
else if (exception is ResponseException copriException)
|
||||
{
|
||||
// COPRI exception.
|
||||
Log.ForContext<T>().Error("COPRI returned an error. {response}", copriException.Response);
|
||||
await InvokeCurrentStateAsync(State.ResponseException, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralEndRentalError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
return bookingFinished;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
public static class StartRentalCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
RentBike,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
WebConnectFailed,
|
||||
GeneralStartRentalError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of closing process.
|
||||
/// </summary>
|
||||
public interface IStartRentalCommandListener
|
||||
{
|
||||
/// <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 InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IStartRentalCommandListener listener)
|
||||
{
|
||||
// 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: Book bike
|
||||
InvokeCurrentStep(Step.RentBike);
|
||||
try
|
||||
{
|
||||
if (bike.LockInfo.State != LockingState.Open)
|
||||
{
|
||||
await connectorFactory(true).Command.DoBookAsync(bike, LockingAction.Open);
|
||||
}
|
||||
else
|
||||
{
|
||||
await connectorFactory(true).Command.DoBookAsync(bike);
|
||||
}
|
||||
Log.ForContext<T>().Information("User booked bike {bikeId} successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Booking of bike {bikeId} failed.", bike.Id);
|
||||
if (exception is WebConnectFailureException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Error("Copri server not reachable.");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralStartRentalError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functionality to get the locked bike location.
|
||||
/// </summary>
|
||||
public static class StartReservationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
ReserveBike,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
TooManyBikesError,
|
||||
WebConnectFailed,
|
||||
GeneralStartReservationError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of closing process.
|
||||
/// </summary>
|
||||
public interface IStartReservationCommandListener
|
||||
{
|
||||
/// <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 InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IStartReservationCommandListener listener)
|
||||
{
|
||||
// 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: Reserve Bike
|
||||
InvokeCurrentStep(Step.ReserveBike);
|
||||
|
||||
try
|
||||
{
|
||||
await connectorFactory(true).Command.DoReserve(bike);
|
||||
Log.ForContext<T>().Information("User reserved bike {bikeId} successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Request to reserve bike {bikeId} declined.", bike.Id);
|
||||
if (exception is BookingDeclinedException)
|
||||
{
|
||||
// Too many bikes booked.
|
||||
Log.ForContext<T>().Error("Maximum count of bikes {exception.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
|
||||
await InvokeCurrentStateAsync(State.TooManyBikesError, (exception as BookingDeclinedException).MaxBikesCount.ToString());
|
||||
}
|
||||
else if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Error("Copri server not reachable.");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralStartReservationError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue