sharee.bike-App/TINKLib/Model/Connector/Updater/UpdaterJSON.cs
2021-05-13 20:03:07 +02:00

497 lines
22 KiB
C#

using System;
using TINK.Model.Bike;
using TINK.Model.Station;
using TINK.Model.Repository.Response;
using TINK.Model.User.Account;
using System.Collections.Generic;
using TINK.Model.State;
using TINK.Model.Repository.Exception;
using Serilog;
using BikeInfo = TINK.Model.Bike.BC.BikeInfo;
using IBikeInfoMutable = TINK.Model.Bikes.Bike.BC.IBikeInfoMutable;
using System.Globalization;
namespace TINK.Model.Connector
{
/// <summary>
/// Connects TINK app to copri using JSON as input data format.
/// </summary>
/// <todo>Rename to UpdateFromCopri.</todo>
public static class UpdaterJSON
{
/// <summary> Loads a bike object from copri server cancel reservation/ booking update request.</summary>
/// <param name="bike">Bike object to load response into.</param>
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
public static void Load(
this IBikeInfoMutable bike,
Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel)
{
bike.State.Load(InUseStateEnum.Disposable, notifyLevel: notifyLevel);
}
/// <summary>
/// Gets all statsion for station provider and add them into station list.
/// </summary>
/// <param name="p_oStationList">List of stations to update.</param>
public static StationDictionary GetStationsAllMutable(this StationsAllResponse p_oStationsAllResponse)
{
// Get stations from Copri/ file/ memory, ....
if (p_oStationsAllResponse == null
|| p_oStationsAllResponse.stations == null)
{
// Latest list of stations could not be retrieved from provider.
return new StationDictionary();
}
Version.TryParse(p_oStationsAllResponse.copri_version, out Version l_oCopriVersion);
var l_oStations = new StationDictionary(p_oVersion: l_oCopriVersion);
foreach (var l_oStation in p_oStationsAllResponse.stations)
{
if (l_oStations.GetById(l_oStation.Value.station) != null)
{
// Can not add station to list of station. Id is not unique.
throw new InvalidResponseException<StationsAllResponse>(
string.Format("Station id {0} is not unique.", l_oStation.Value.station), p_oStationsAllResponse);
}
l_oStations.Add(new Station.Station(
l_oStation.Value.station,
l_oStation.Value.GetGroup(),
l_oStation.Value.GetPosition(),
l_oStation.Value.description));
}
return l_oStations;
}
/// <summary> Gets account object from login response.</summary>
/// <param name="merchantId">Needed to extract cookie from autorization response.</param>
/// <param name="loginResponse">Response to get session cookie and debug level from.</param>
/// <param name="mail">Mail address needed to construct a complete account object (is not part of response).</param>
/// <param name="password">Password needed to construct a complete account object (is not part of response).</param>
public static IAccount GetAccount(
this AuthorizationResponse loginResponse,
string merchantId,
string mail,
string password)
{
if (loginResponse == null)
{
throw new ArgumentNullException("p_oLoginResponse");
}
return new Account(
mail,
password,
loginResponse.authcookie?.Replace(merchantId, ""),
loginResponse.GetGroup(),
loginResponse.debuglevel == 1
? Permissions.All :
(Permissions)loginResponse.debuglevel) ;
}
/// <summary> Load bike object from booking response. </summary>
/// <param name="bike">Bike object to load from response.</param>
/// <param name="bikeInfo">Booking response.</param>
/// <param name="mailAddress">Mail address of user which books bike.</param>
/// <param name="p_strSessionCookie">Session cookie of user which books bike.</param>
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
public static void Load(
this IBikeInfoMutable bike,
BikeInfoReservedOrBooked bikeInfo,
string mailAddress,
Func<DateTime> dateTimeProvider,
Bikes.Bike.BC.NotifyPropertyChangedLevel notifyLevel = Bikes.Bike.BC.NotifyPropertyChangedLevel.All)
{
var l_oDateTimeProvider = dateTimeProvider != null
? dateTimeProvider
: () => DateTime.Now;
if (bike is Bike.BluetoothLock.BikeInfoMutable btBikeInfo)
{
btBikeInfo.LockInfo.Load(
bikeInfo.GetBluetoothLockId(),
bikeInfo.GetBluetoothLockGuid(),
bikeInfo.GetSeed(),
bikeInfo.GetUserKey(),
bikeInfo.GetAdminKey());
}
var l_oState = bikeInfo.GetState();
switch (l_oState)
{
case InUseStateEnum.Disposable:
bike.State.Load(
InUseStateEnum.Disposable,
notifyLevel: notifyLevel);
break;
case InUseStateEnum.Reserved:
bike.State.Load(
InUseStateEnum.Reserved,
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.timeCode,
notifyLevel);
break;
case InUseStateEnum.Booked:
bike.State.Load(
InUseStateEnum.Booked,
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.timeCode,
notifyLevel);
break;
default:
throw new Exception(string.Format("Unexpected bike state detected. state is {0}.", l_oState));
}
}
/// <summary> Gets bikes available from copri server response.</summary>
/// <param name="p_oBikesAvailableResponse">Response to create collection from.</param>
/// <returns>New collection of available bikes.</returns>
public static BikeCollection GetBikesAvailable(
this BikesAvailableResponse p_oBikesAvailableResponse)
{
return GetBikesAll(
p_oBikesAvailableResponse,
new BikesReservedOccupiedResponse(), // There are no occupied bikes.
string.Empty,
() => DateTime.Now);
}
/// <summary> Gets bikes occupied from copri server response. </summary>
/// <param name="p_oBikesAvailable">Response to create bikes from.</param>
/// <returns>New collection of occupied bikes.</returns>
public static BikeCollection GetBikesOccupied(
this BikesReservedOccupiedResponse p_oBikesOccupiedResponse,
string p_strMail,
Func<DateTime> p_oDateTimeProvider)
{
return GetBikesAll(
new BikesAvailableResponse(),
p_oBikesOccupiedResponse,
p_strMail,
p_oDateTimeProvider);
}
/// <summary> Gets bikes occupied from copri server response. </summary>
/// <param name="p_oBikesAvailable">Response to create bikes from.</param>
/// <returns>New collection of occupied bikes.</returns>
public static BikeCollection GetBikesAll(
BikesAvailableResponse p_oBikesAvailableResponse,
BikesReservedOccupiedResponse p_oBikesOccupiedResponse,
string p_strMail,
Func<DateTime> p_oDateTimeProvider)
{
var l_oBikesDictionary = new Dictionary<int, BikeInfo>();
var l_oDuplicates = new Dictionary<int, BikeInfo>();
// Get bikes from Copri/ file/ memory, ....
if (p_oBikesAvailableResponse != null
&& p_oBikesAvailableResponse.bikes != null)
{
foreach (var bikeInfoResponse in p_oBikesAvailableResponse.bikes.Values)
{
var l_oBikeInfo = BikeInfoFactory.Create(bikeInfoResponse);
if (l_oBikeInfo == null)
{
// Response is not valid.
continue;
}
if (l_oBikesDictionary.ContainsKey(l_oBikeInfo.Id))
{
// Duplicates are not allowed.
Log.Error($"Duplicate bike with id {l_oBikeInfo.Id} detected evaluating bikes available. Bike status is {l_oBikeInfo.State.Value}.");
if (!l_oDuplicates.ContainsKey(l_oBikeInfo.Id))
{
l_oDuplicates.Add(l_oBikeInfo.Id, l_oBikeInfo);
}
continue;
}
l_oBikesDictionary.Add(l_oBikeInfo.Id, l_oBikeInfo);
}
}
// Get bikes from Copri/ file/ memory, ....
if (p_oBikesOccupiedResponse != null
&& p_oBikesOccupiedResponse.bikes_occupied != null)
{
foreach (var l_oBikeInfoResponse in p_oBikesOccupiedResponse.bikes_occupied.Values)
{
BikeInfo l_oBikeInfo = BikeInfoFactory.Create(
l_oBikeInfoResponse,
p_strMail,
p_oDateTimeProvider);
if (l_oBikeInfo == null)
{
continue;
}
if (l_oBikesDictionary.ContainsKey(l_oBikeInfo.Id))
{
// Duplicates are not allowed.
Log.Error($"Duplicate bike with id {l_oBikeInfo.Id} detected evaluating bikes occupied. Bike status is {l_oBikeInfo.State.Value}.");
if (!l_oDuplicates.ContainsKey(l_oBikeInfo.Id))
{
l_oDuplicates.Add(l_oBikeInfo.Id, l_oBikeInfo);
}
continue;
}
l_oBikesDictionary.Add(l_oBikeInfo.Id, l_oBikeInfo);
}
}
// Remove entries which are not unique.
foreach (var l_oDuplicate in l_oDuplicates)
{
l_oBikesDictionary.Remove(l_oDuplicate.Key);
}
return new BikeCollection(l_oBikesDictionary);
}
}
/// <summary>
/// Constructs bike info instances/ bike info derived instances.
/// </summary>
public static class BikeInfoFactory
{
public static BikeInfo Create(BikeInfoAvailable bikeInfo)
{
if (bikeInfo.GetIsManualLockBike())
{
// Manual lock bikes are no more supported.
Log.Error(
$"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. " +
"Manual lock bikes are no more supported." +
$"Bike number: {bikeInfo.bike}{(bikeInfo.station != null ? $"station number {bikeInfo.station}" : string.Empty)}."
);
return null;
}
switch (bikeInfo.GetState())
{
case InUseStateEnum.Disposable:
break;
default:
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Unexpected state {bikeInfo.GetState()} detected.");
return null;
}
if (bikeInfo.station == null)
{
// Bike available must always have a station id because bikes can only be returned at a station.
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. No station info set.");
return null;
}
try
{
return !bikeInfo.GetIsBluetoothLockBike()
? new BikeInfo(
bikeInfo.bike,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription) null),
#endif
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description)
: new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike,
bikeInfo.GetBluetoothLockId(),
bikeInfo.GetBluetoothLockGuid(),
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description);
}
catch (ArgumentException ex)
{
// Contructor reported invalid arguemts (missing lock id, ....).
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Invalid response detected. Available bike with id {bikeInfo.bike} skipped. {ex.Message}");
return null;
}
}
/// <summary> Creates a bike info object from copri response. </summary>
/// <param name="bikeInfo">Copri response. </param>
/// <param name="mailAddress">Mail address of user.</param>
/// <param name="dateTimeProvider">Date and time provider function.</param>
/// <returns></returns>
public static BikeInfo Create(
BikeInfoReservedOrBooked bikeInfo,
string mailAddress,
Func<DateTime> dateTimeProvider)
{
if (bikeInfo.GetIsManualLockBike())
{
// Manual lock bikes are no more supported.
Log.Error(
$"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. " +
"Manual lock bikes are no more supported." +
$"Bike number: {bikeInfo.bike}{(bikeInfo.station != null ? $", station number {bikeInfo.station}" : string.Empty)}."
);
return null;
}
// Check if bike is a bluetooth lock bike.
var isBluetoothBike = bikeInfo.GetIsBluetoothLockBike();
int lockSerial = bikeInfo.GetBluetoothLockId();
Guid lockGuid = bikeInfo.GetBluetoothLockGuid();
switch (bikeInfo.GetState())
{
case InUseStateEnum.Reserved:
try
{
return !isBluetoothBike
? new BikeInfo(
bikeInfo.bike,
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.timeCode,
dateTimeProvider)
: new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike,
lockSerial,
lockGuid,
bikeInfo.GetUserKey(),
bikeInfo.GetAdminKey(),
bikeInfo.GetSeed(),
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
dateTimeProvider,
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike());
}
catch (ArgumentException ex)
{
// Contructor reported invalid arguemts (missing lock id, ....).
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoReservedOrBooked)} argument. Invalid response detected. Reserved bike with id {bikeInfo.bike} skipped. {ex.Message}");
return null;
}
case InUseStateEnum.Booked:
try
{
return !isBluetoothBike
? new BikeInfo(
bikeInfo.bike,
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike(),
bikeInfo.description,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.timeCode)
: new Bike.BluetoothLock.BikeInfo(
bikeInfo.bike,
lockSerial,
bikeInfo.GetBluetoothLockGuid(),
bikeInfo.GetUserKey(),
bikeInfo.GetAdminKey(),
bikeInfo.GetSeed(),
bikeInfo.GetFrom(),
mailAddress,
bikeInfo.station,
bikeInfo.GetOperatorUri(),
#if !NOTARIFFDESCRIPTION
Create(bikeInfo.tariff_description),
#else
Create((TINK.Repository.Response.TariffDescription)null),
#endif
bikeInfo.GetIsDemo(),
bikeInfo.GetGroup(),
bikeInfo.GetWheelType(),
bikeInfo.GetTypeOfBike());
}
catch (ArgumentException ex)
{
// Contructor reported invalid arguemts (missing lock id, ....).
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoReservedOrBooked)} argument. Invalid response detected. Booked bike with id {bikeInfo.bike} skipped. {ex.Message}");
return null;
}
default:
Log.Error($"Can not create new {nameof(BikeInfo)}-object from {nameof(BikeInfoAvailable)} argument. Unexpected state {bikeInfo.GetState()} detected.");
return null;
}
}
public static Bikes.Bike.TariffDescription Create(this TINK.Repository.Response.TariffDescription tariffDesciption)
{
return new Bikes.Bike.TariffDescription
{
Name = tariffDesciption?.name,
Number = int.TryParse(tariffDesciption?.number, out int number) ? number : null,
FreeTimePerSession = double.TryParse(tariffDesciption?.free_hours, NumberStyles.Any, CultureInfo.InvariantCulture, out double freeHours) ? TimeSpan.FromHours(freeHours) : TimeSpan.Zero,
FeeEuroPerHour = double.TryParse(tariffDesciption?.eur_per_hour, NumberStyles.Any, CultureInfo.InvariantCulture, out double euroPerHour) ? euroPerHour : double.NaN,
AboEuroPerMonth = double.TryParse(tariffDesciption?.abo_eur_per_month, NumberStyles.Any, CultureInfo.InvariantCulture, out double aboEuroPerMonth) ? aboEuroPerMonth : double.NaN,
MaxFeeEuroPerDay = double.TryParse(tariffDesciption?.max_eur_per_day, NumberStyles.Any, CultureInfo.InvariantCulture, out double maxEuroPerDay) ? maxEuroPerDay : double.NaN,
};
}
}
}