mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-06-21 13:36: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
35
SharedBusinessLogic/Repository/AppContextInfo.cs
Normal file
35
SharedBusinessLogic/Repository/AppContextInfo.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
|
||||
namespace ShareeBike.Repository
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds info passed to COPRI.
|
||||
/// </summary>
|
||||
public class AppContextInfo
|
||||
{
|
||||
public AppContextInfo(string merchantId, string name, Version version)
|
||||
{
|
||||
Name = !string.IsNullOrEmpty(name)
|
||||
? name
|
||||
: throw new ArgumentNullException(nameof(name));
|
||||
|
||||
Version = version;
|
||||
|
||||
MerchantId = !string.IsNullOrEmpty(merchantId)
|
||||
? merchantId
|
||||
: throw new ArgumentNullException(nameof(merchantId));
|
||||
}
|
||||
|
||||
/// <summary> Name of the app. </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary> Version of the app.</summary>
|
||||
public Version Version { get; private set; }
|
||||
|
||||
/// <summary> Merchang id of the app. </summary>
|
||||
public string MerchantId { get; private set; }
|
||||
|
||||
/// <summary> Returns http- user agent.</summary>
|
||||
public string UserAgent { get => $"{Name}/{Version}"; }
|
||||
}
|
||||
}
|
67
SharedBusinessLogic/Repository/CopriCallsHelper.cs
Normal file
67
SharedBusinessLogic/Repository/CopriCallsHelper.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using ShareeBike.Repository.Response;
|
||||
using ShareeBike.Repository;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SharedBusinessLogic.Tests.Framework.Repository
|
||||
{
|
||||
public static class CopriCallsHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Filters bikes available response by station.
|
||||
/// </summary>
|
||||
/// <param name="bikes"></param>
|
||||
/// <param name="stationId"></param>
|
||||
/// <returns></returns>
|
||||
public static BikesAvailableResponse FilterByStation(this BikesAvailableResponse bikes, string stationId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stationId))
|
||||
{
|
||||
// If filter is off there is nothing to filter.
|
||||
return bikes;
|
||||
}
|
||||
|
||||
var response = CopriCallsStatic.DeserializeResponse<BikesAvailableResponse>(
|
||||
$"{{ \"{nameof(VersionindependentResponse.shareejson)}\" : {JsonConvert.SerializeObject(bikes)} }}");
|
||||
|
||||
response.bikes.Clear();
|
||||
|
||||
var allBikes = bikes.bikes.Values.Where(x => x.station == stationId).ToList();
|
||||
foreach (var bike in allBikes)
|
||||
{
|
||||
response.bikes.Add(bike.bike, bike);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters bikes available response by station.
|
||||
/// </summary>
|
||||
/// <param name="bikes"></param>
|
||||
/// <param name="stationId"></param>
|
||||
/// <returns></returns>
|
||||
public static BikesAvailableResponse FilterByBike(this BikesAvailableResponse bikes, string bikeId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(bikeId))
|
||||
{
|
||||
// If filter is off there is nothing to filter.
|
||||
return bikes;
|
||||
}
|
||||
|
||||
var response = CopriCallsStatic.DeserializeResponse<BikesAvailableResponse>(
|
||||
$"{{ \"{nameof(VersionindependentResponse.shareejson)}\" : {JsonConvert.SerializeObject(bikes)} }}");
|
||||
|
||||
response.bikes.Clear();
|
||||
|
||||
var allBikes = bikes.bikes.Values.Where(x => Regex.IsMatch(x.bike, bikeId)).ToList();
|
||||
foreach (var bike in allBikes)
|
||||
{
|
||||
response.bikes.Add(bike.bike, bike);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
917
SharedBusinessLogic/Repository/CopriCallsHttps.cs
Normal file
917
SharedBusinessLogic/Repository/CopriCallsHttps.cs
Normal file
|
@ -0,0 +1,917 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.Logging;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Repository.Request;
|
||||
using ShareeBike.Repository.Response;
|
||||
using ShareeBike.Repository.Response.Stations;
|
||||
|
||||
namespace ShareeBike.Repository
|
||||
{
|
||||
/// <summary> Object which manages calls to copri. </summary>
|
||||
public class CopriCallsHttps : ICopriServer
|
||||
{
|
||||
/// <summary> Builds requests.</summary>
|
||||
private readonly IRequestBuilder requestBuilder;
|
||||
|
||||
/// <summary> Initializes a instance of the copri calls https object. </summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="appContextInfo">Provides app related info (app name and version, merchant id) to pass to COPRI.</param>
|
||||
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
|
||||
/// <param name="smartDevice">Holds info about smart device.</param>
|
||||
/// <param name="sessionCookie">Session cookie if user is logged in, null otherwise.</param>
|
||||
public CopriCallsHttps(
|
||||
Uri copriHost,
|
||||
AppContextInfo appContextInfo,
|
||||
string uiIsoLangugageName,
|
||||
string sessionCookie = null,
|
||||
ISmartDevice smartDevice = null)
|
||||
{
|
||||
m_oCopriHost = copriHost
|
||||
?? throw new System.Exception($"Can not construct {GetType()}- object. Uri of copri host must not be null.");
|
||||
|
||||
UserAgent = appContextInfo != null
|
||||
? appContextInfo.UserAgent
|
||||
: throw new System.Exception($"Can not construct {GetType()}- object. User agent must not be null or empty.");
|
||||
|
||||
requestBuilder = string.IsNullOrEmpty(sessionCookie)
|
||||
? new RequestBuilder(appContextInfo.MerchantId, uiIsoLangugageName, smartDevice) as IRequestBuilder
|
||||
: new RequestBuilderLoggedIn(appContextInfo.MerchantId, uiIsoLangugageName, sessionCookie, smartDevice);
|
||||
}
|
||||
|
||||
/// <summary> Holds the URL for rest calls.</summary>
|
||||
private readonly Uri m_oCopriHost;
|
||||
|
||||
/// <summary> Specifies name and version of app. </summary>
|
||||
private string UserAgent { get; }
|
||||
|
||||
/// <summary> Returns true because value requested form copri server are returned. </summary>
|
||||
public bool IsConnected => true;
|
||||
|
||||
/// <summary> Gets the merchant id.</summary>
|
||||
public string MerchantId => requestBuilder.MerchantId;
|
||||
|
||||
/// <summary> Gets the session cookie if user is logged in, an empty string otherwise. </summary>
|
||||
public string SessionCookie => requestBuilder.SessionCookie;
|
||||
|
||||
/// <summary> Logs user in. </summary>
|
||||
/// <param name="mailAddress">Mail address of user to log in.</param>
|
||||
/// <param name="password">Password to log in.</param>
|
||||
/// <param name="deviceId">Id specifying user and hardware.</param>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
public async Task<AuthorizationResponse> DoAuthorizationAsync(
|
||||
string mailAddress,
|
||||
string password,
|
||||
string deviceId)
|
||||
=> await DoAuthorizationAsync(
|
||||
m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoAuthorization(mailAddress, password, deviceId),
|
||||
() => requestBuilder.DoAuthorization(mailAddress, "********", deviceId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
public async Task<AuthorizationoutResponse> DoAuthoutAsync()
|
||||
=> await DoAuthoutAsync(m_oCopriHost.AbsoluteUri, requestBuilder.DoAuthout(), UserAgent);
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
|
||||
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null, string stationId = null, string bikeId = null)
|
||||
=> await GetBikesAvailableAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.GetBikesAvailable(stationId, bikeId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by active user. </summary>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await GetBikesOccupiedAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetBikesOccupied(), UserAgent);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// No user logged in.
|
||||
await Task.CompletedTask;
|
||||
return ResponseHelper.GetBikesOccupiedNone();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Get list of stations. </summary>
|
||||
/// <returns>List of files.</returns>
|
||||
public async Task<StationsAvailableResponse> GetStationsAsync()
|
||||
=> await GetStationsAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetStations(), UserAgent);
|
||||
|
||||
/// <summary> Get authentication keys. </summary>
|
||||
/// <param name="bikeId">Id of the bike to get keys for.</param>
|
||||
/// <returns>Response holding authentication keys.</returns>
|
||||
public async Task<ReservationBookingResponse> GetAuthKeys(string bikeId)
|
||||
=> await GetAuthKeysAsync(m_oCopriHost.AbsoluteUri, requestBuilder.CalculateAuthParameters(bikeId), UserAgent);
|
||||
|
||||
|
||||
/// <summary> Gets booking request response.</summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Booking response.</returns>
|
||||
public async Task<ReservationBookingResponse> DoReserveAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> await DoReserveAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoReserve(bikeId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Gets cancel booking request response.</summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on cancel booking request.</returns>
|
||||
public async Task<BookingActionResponse> DoCancelReservationAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> await DoCancelReservationAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoCancelReservation(bikeId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Get authentication keys. </summary>
|
||||
/// <param name="bikeId">Id of the bike to get keys for.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response holding authentication keys.</returns>
|
||||
public async Task<ReservationBookingResponse> CalculateAuthKeysAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> await GetAuthKeysAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.CalculateAuthParameters(bikeId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Notifies COPRI about start of returning sequence. </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>+
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on notification about start of returning sequence.</returns>
|
||||
public async Task<ResponseBase> StartReturningBike(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> await DoStartReturningBike(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.StartReturningBike(bikeId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Updates lock state for a booked bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to update locking state for.</param>
|
||||
/// <param name="location">Geolocation of lock.</param>
|
||||
/// <param name="state">New locking state.</param>
|
||||
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on updating locking state.</returns>
|
||||
public async Task<ReservationBookingResponse> UpdateLockingStateAsync(
|
||||
string bikeId,
|
||||
lock_state state,
|
||||
Uri operatorUri,
|
||||
LocationDto location,
|
||||
double batteryLevel,
|
||||
IVersionInfo versionInfo) =>
|
||||
await DoUpdateLockingStateAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.UpdateLockingState(bikeId, state, location, batteryLevel, versionInfo),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Gets booking request. </summary>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="guid">Used to publish GUID from app to copri. Used for initial setup of bike in copri.</param>
|
||||
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
|
||||
/// <param name="nextAction">If not null next locking action which is performed after booking.</param>
|
||||
/// <returns>Request on booking request.</returns>
|
||||
public async Task<ReservationBookingResponse> DoBookAsync(
|
||||
Uri operatorUri,
|
||||
string bikeId,
|
||||
Guid guid,
|
||||
double batteryPercentage,
|
||||
LockingAction? nextAction = null)
|
||||
=> await DoBookAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoBook(bikeId, guid, batteryPercentage, nextAction),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Books a bike and starts opening bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public async Task<ReservationBookingResponse> BookAvailableAndStartOpeningAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> await DoBookAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.BookAvailableAndStartOpening(bikeId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Books a bike and starts opening bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public async Task<ReservationBookingResponse> BookReservedAndStartOpeningAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> await DoBookAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.BookReservedAndStartOpening(bikeId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Returns a bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="location">Geolocation of lock.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on returning request.</returns>
|
||||
public async Task<DoReturnResponse> DoReturn(
|
||||
string bikeId,
|
||||
LocationDto location,
|
||||
Uri operatorUri)
|
||||
=> await DoReturn(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoReturn(bikeId, location),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Returns a bike and starts closing. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on returning request.</returns>
|
||||
public async Task<DoReturnResponse> ReturnAndStartClosingAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> await DoReturn(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.ReturnAndStartClosing(bikeId),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Submits feedback to copri server. </summary>
|
||||
/// <param name="bikeId">Id of the bike to which the feedback is related to.</param>
|
||||
/// <param name="currentChargeBars">Null if bike has no engine or charge is unknown. Otherwise the charge filling level of the drive battery.</param>
|
||||
/// <param name="isBikeBroken">True if bike is broken.</param>
|
||||
/// <param name="message">General purpose message or error description.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on submitting feedback request.</returns>
|
||||
public async Task<SubmitFeedbackResponse> DoSubmitFeedback(
|
||||
string bikeId,
|
||||
int? currentChargeBars,
|
||||
string message,
|
||||
bool isBikeBroken,
|
||||
Uri operatorUri) =>
|
||||
await DoSubmitFeedback(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoSubmitFeedback(bikeId, currentChargeBars, message, isBikeBroken),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Submits mini survey to copri server. </summary>
|
||||
/// <param name="answers">Collection of answers.</param>
|
||||
public Task<ResponseBase> DoSubmitMiniSurvey(IDictionary<string, string> answers)
|
||||
=> DoSubmitMiniSurvey(
|
||||
m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoSubmitMiniSurvey(answers),
|
||||
UserAgent);
|
||||
|
||||
/// <summary> Logs user in. </summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user in.</param>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
public static async Task<AuthorizationResponse> DoAuthorizationAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
Func<string> displayCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
/// Extract session cookie from response.
|
||||
string response = string.Empty;
|
||||
try
|
||||
{
|
||||
response = await PostAsync(
|
||||
copriHost,
|
||||
command,
|
||||
userAgent,
|
||||
displayCommand); // Do not include password into exception output when an error occurs.
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Login fehlgeschlagen aufgrund eines Netzwerkfehlers.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Login fehlgeschlagen aufgrund eines Netzwerkfehlers.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
return CopriCallsStatic.DeserializeResponse<AuthorizationResponse>(response, (version) => new UnsupportedCopriVersionDetectedException());
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user out.</param>
|
||||
public static async Task<AuthorizationoutResponse> DoAuthoutAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string logoutResponse;
|
||||
try
|
||||
{
|
||||
logoutResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Login fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Login fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
/// Extract session cookie from response.
|
||||
return CopriCallsStatic.DeserializeResponse<AuthorizationoutResponse>(logoutResponse, (version) => new UnsupportedCopriVersionDetectedException());
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list of stations from file.
|
||||
/// </summary>
|
||||
/// <param name="copriHost">URL of the copri host to connect to.</param>
|
||||
/// <param name="command">Command to get stations.</param>
|
||||
/// <returns>List of files.</returns>
|
||||
public static async Task<StationsAvailableResponse> GetStationsAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string response;
|
||||
try
|
||||
{
|
||||
response = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return CopriCallsStatic.DeserializeResponse(
|
||||
response,
|
||||
(version) => CopriCallsMonkeyStoreHelper.GetEmptyStationsAllResponse(version));
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes from Copri. </summary>
|
||||
/// <param name="copriHost">URL of the copri host to connect to.</param>
|
||||
/// <param name="command">Command to get bikes.</param>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public static async Task<BikesAvailableResponse> GetBikesAvailableAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string response;
|
||||
try
|
||||
{
|
||||
response = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Abfage der verfügbaren Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return CopriCallsStatic.DeserializeResponse(
|
||||
response,
|
||||
(version) => CopriCallsMonkeyStoreHelper.GetEmptyBikesAvailableResponse(version));
|
||||
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
|
||||
/// <param name="copriHost">URL of the copri host to connect to.</param>
|
||||
/// <param name="command">Command to post.</param>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public static async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string response;
|
||||
try
|
||||
{
|
||||
response = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Abfage der reservierten/ gebuchten Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Abfage der reservierten/ gebuchten Räder fehlgeschlagen wegen Netzwerkfehler.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return CopriCallsStatic.DeserializeResponse(
|
||||
response,
|
||||
(version) => CopriCallsMonkeyStoreHelper.GetEmptyBikesReservedOccupiedResponse(version));
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Get auth keys from COPRI. </summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user in.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public static async Task<ReservationBookingResponse> GetAuthKeysAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string bikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
bikesAvaialbeResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
if (exception.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Schlosssuche wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
if (exception.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Schlosssuche wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(bikesAvaialbeResponse)?.shareejson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
/// <summary> Gets booking request response. </summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user in.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public static async Task<ReservationBookingResponse> DoReserveAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string bikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
bikesAvaialbeResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
if (exception.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Reservierung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
if (exception.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Reservierung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(bikesAvaialbeResponse)?.shareejson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Gets cancel booking request response.</summary>
|
||||
/// <param name="copriHost">Host to connect to. </param>
|
||||
/// <param name="command">Command to log user in.</param>
|
||||
/// <returns>Response on cancel booking request.</returns>
|
||||
public static async Task<BookingActionResponse> DoCancelReservationAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Reservierung des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Reservierung des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<BookingActionResponse>>(l_oBikesAvaialbeResponse)?.shareejson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<ResponseBase> DoStartReturningBike(
|
||||
string copriHost,
|
||||
string command,
|
||||
string agent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string response;
|
||||
try
|
||||
{
|
||||
response = await PostAsync(copriHost, command, agent);
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
if (exception.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Benachrichtigung von Start der Rückgabe wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
if (exception.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Benachrichtigung von Start der Rückgabe wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ResponseBase>>(response)?.shareejson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<ReservationBookingResponse> DoUpdateLockingStateAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string agent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string bikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
bikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
if (exception.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
if (exception.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(bikesAvaialbeResponse)?.shareejson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<ReservationBookingResponse> DoBookAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string agent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string bikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
bikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
if (exception.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
if (exception.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", exception);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(bikesAvaialbeResponse)?.shareejson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<DoReturnResponse> DoReturn(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string doReturnResponse;
|
||||
try
|
||||
{
|
||||
doReturnResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Rückgabe des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Rückgabe des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<DoReturnResponse>>(doReturnResponse)?.shareejson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public async Task<SubmitFeedbackResponse> DoSubmitFeedback(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string userFeedbackResponse;
|
||||
try
|
||||
{
|
||||
userFeedbackResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Senden der Rückmeldung aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Senden der Rückmeldung aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<SubmitFeedbackResponse>>(userFeedbackResponse)?.shareejson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Submits mini survey to copri server. </summary>
|
||||
public async Task<ResponseBase> DoSubmitMiniSurvey(
|
||||
string copriHost,
|
||||
string command,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string miniSurveyResponse;
|
||||
try
|
||||
{
|
||||
miniSurveyResponse = await PostAsync(copriHost, command, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Senden der Miniumfrage aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Senden der der Miniumfrage aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<ResponseBase>>(miniSurveyResponse)?.shareejson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Https get- request.</summary>
|
||||
/// <param name="Url">Url to get info from.</param>
|
||||
/// <returns>response from server</returns>
|
||||
public static async Task<string> Get(string Url)
|
||||
{
|
||||
string result = string.Empty;
|
||||
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(Url);
|
||||
myRequest.Method = "GET";
|
||||
using (var myResponse = await myRequest.GetResponseAsync())
|
||||
{
|
||||
using (var sr = new StreamReader(myResponse.GetResponseStream(), Encoding.UTF8))
|
||||
{
|
||||
result = sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary> Https- post request.</summary>
|
||||
/// <param name="command">Command to send.</param>
|
||||
/// <param name="displayCommand">Command to display/ log used for error handling.</param>
|
||||
/// <param name="uRL">Address of server to communicate with.</param>
|
||||
/// <returns>Response as text.</returns>
|
||||
/// <changelog> An unused member PostAsyncHttpClient using HttpClient for posting was removed 2020-04-02.</changelog>
|
||||
private static async Task<string> PostAsync(
|
||||
string uRL,
|
||||
string command,
|
||||
string userAgent = null,
|
||||
Func<string> displayCommand = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(command))
|
||||
{
|
||||
Log.ForContext<CopriCallsHttps>().Fatal("Can not post command. Command must not be null or empty.");
|
||||
|
||||
throw new ArgumentException("Can not post command. Command must not be null or empty.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(uRL))
|
||||
{
|
||||
Log.ForContext<CopriCallsHttps>().Fatal("Can not post command. Host must not be null or empty.");
|
||||
|
||||
throw new ArgumentException("Can not post command. Host must not be null or empty.");
|
||||
}
|
||||
|
||||
// Get display version of command to used for display/ logging (password should never be included in output)
|
||||
Func<string> displayCommandFunc = displayCommand ?? delegate () { return command; };
|
||||
|
||||
try
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
var l_strHost = uRL;
|
||||
|
||||
// Returns a https request.
|
||||
var request = WebRequest.CreateHttp(l_strHost);
|
||||
|
||||
request.Method = "POST";
|
||||
request.ContentType = "application/x-www-form-urlencoded";
|
||||
request.UserAgent = userAgent;
|
||||
|
||||
// Workaround for issue https://bugzilla.xamarin.com/show_bug.cgi?id=57705
|
||||
// If not KeepAlive is set to true Stream.Write leads arbitrarily to an object disposed exception.
|
||||
request.KeepAlive = true;
|
||||
|
||||
byte[] postData = Encoding.UTF8.GetBytes(command);
|
||||
|
||||
request.ContentLength = postData.Length;
|
||||
|
||||
// Get the request stream.
|
||||
using (Stream dataStream = await request.GetRequestStreamAsync())
|
||||
{
|
||||
// Write the data to the request stream.
|
||||
await dataStream.WriteAsync(postData, 0, postData.Length);
|
||||
}
|
||||
|
||||
// Get the response.
|
||||
var webResponse = await request.GetResponseAsync() as HttpWebResponse;
|
||||
|
||||
if (webResponse == null)
|
||||
{
|
||||
throw new System.Exception(string.Format("Reserve request failed. Response form from server was not of expected type."));
|
||||
}
|
||||
|
||||
if (webResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new CommunicationException(string.Format(
|
||||
"Posting request {0} failed. Expected status code is {1} but was {2}.",
|
||||
displayCommandFunc(),
|
||||
HttpStatusCode.OK,
|
||||
webResponse.StatusCode));
|
||||
}
|
||||
|
||||
string response = string.Empty;
|
||||
|
||||
// Get the request stream.
|
||||
using (Stream l_oDataStream = webResponse.GetResponseStream())
|
||||
using (StreamReader l_oReader = new StreamReader(l_oDataStream))
|
||||
{
|
||||
// Read the content.
|
||||
response = l_oReader.ReadToEnd();
|
||||
|
||||
// Display the content.
|
||||
Console.WriteLine(response);
|
||||
|
||||
// Clean up the streams.
|
||||
webResponse.Close();
|
||||
}
|
||||
|
||||
Log.ForContext<CopriCallsHttps>().Verbose("Post command {DisplayCommand} to host {URL} received {ResponseText:j}.", displayCommandFunc(), uRL, response);
|
||||
|
||||
return response;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
Log.ForContext<CopriCallsHttps>().InformationOrError("Posting command {DisplayCommand} to host {URL} failed. {Exception}.", displayCommandFunc(), uRL, exception);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
443
SharedBusinessLogic/Repository/CopriCallsMonkeyStore.cs
Normal file
443
SharedBusinessLogic/Repository/CopriCallsMonkeyStore.cs
Normal file
|
@ -0,0 +1,443 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MonkeyCache.FileStore;
|
||||
using SharedBusinessLogic.Tests.Framework.Repository;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Model.Services.CopriApi;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Request;
|
||||
using ShareeBike.Repository.Response;
|
||||
using ShareeBike.Repository.Response.Stations;
|
||||
|
||||
namespace ShareeBike.Repository
|
||||
{
|
||||
public class CopriCallsMonkeyStore : ICopriCache
|
||||
{
|
||||
/// <summary> Prevents concurrent communication. </summary>
|
||||
private object monkeyLock = new object();
|
||||
|
||||
/// <summary> Builds requests.</summary>
|
||||
private IRequestBuilder requestBuilder;
|
||||
|
||||
public const string BIKESAVAILABLE = @"{
|
||||
""copri_version"" : ""4.1.0.0"",
|
||||
""bikes"" : {},
|
||||
""response_state"" : ""OK"",
|
||||
""apiserver"" : ""https://app.tink-konstanz.de"",
|
||||
""authcookie"" : """",
|
||||
""response"" : ""bikes_available""
|
||||
}";
|
||||
|
||||
public const string BIKESOCCUPIED = @"{
|
||||
""debuglevel"" : ""1"",
|
||||
""user_id"" : """",
|
||||
""response"" : ""user_bikes_occupied"",
|
||||
""user_group"" : [ ""Citybike"", ""ShareeBike"" ],
|
||||
""authcookie"" : """",
|
||||
""response_state"" : ""OK"",
|
||||
""bikes_occupied"" : {},
|
||||
""copri_version"" : ""4.1.0.0"",
|
||||
""apiserver"" : ""https://app.tink-konstanz.de""
|
||||
}";
|
||||
|
||||
/// <summary> Version COPRI 4.0. or earlier</summary>
|
||||
public const string STATIONSALL = @"{
|
||||
""apiserver"" : """",
|
||||
""authcookie"" : """",
|
||||
""response"" : ""stations_all"",
|
||||
""copri_version"" : ""4.1.0.0"",
|
||||
""stations"" : {},
|
||||
""response_state"" : ""OK"",
|
||||
""bikes_occupied"" : {}
|
||||
}";
|
||||
|
||||
/// <summary>
|
||||
/// Holds the seconds after which station and bikes info is considered to be invalid.
|
||||
/// Default value 1s.
|
||||
/// </summary>
|
||||
private TimeSpan ExpiresAfter { get; }
|
||||
|
||||
/// <summary> Returns false because cached values are returned. </summary>
|
||||
public bool IsConnected => false;
|
||||
|
||||
/// <summary> Gets the merchant id.</summary>
|
||||
public string MerchantId => requestBuilder.MerchantId;
|
||||
|
||||
/// <summary> Gets the merchant id.</summary>
|
||||
public string SessionCookie => requestBuilder.SessionCookie;
|
||||
|
||||
/// <summary>
|
||||
/// Holds a cache of copri, i.e. stations and bikes.
|
||||
/// </summary>
|
||||
private readonly CopriResponseModel copriModel;
|
||||
|
||||
/// <summary> Initializes a instance of the copri monkey store object. </summary>
|
||||
/// <param name="merchantId">Id of the merchant. Used to access </param>
|
||||
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
|
||||
/// <param name="sessionCookie">Session cookie if user is logged in, null otherwise.</param>
|
||||
/// <param name="smartDevice">Holds info about smart device.</param>
|
||||
public CopriCallsMonkeyStore(
|
||||
string merchantId,
|
||||
string uiIsoLangugageName,
|
||||
string sessionCookie = null,
|
||||
ISmartDevice smartDevice = null,
|
||||
TimeSpan? expiresAfter = null)
|
||||
{
|
||||
ExpiresAfter = expiresAfter ?? TimeSpan.FromSeconds(1);
|
||||
|
||||
requestBuilder = string.IsNullOrEmpty(sessionCookie)
|
||||
? new RequestBuilder(merchantId, uiIsoLangugageName, smartDevice) as IRequestBuilder
|
||||
: new RequestBuilderLoggedIn(merchantId, uiIsoLangugageName, sessionCookie, smartDevice);
|
||||
|
||||
var bikesAvailableEntryExists = Barrel.Current.Exists(requestBuilder.GetBikesAvailable());
|
||||
var bikesAvailable = bikesAvailableEntryExists
|
||||
? Barrel.Current.Get<BikesAvailableResponse>(requestBuilder.GetBikesAvailable())
|
||||
: JsonConvertRethrow.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE);
|
||||
// Ensure that store holds valid entries.
|
||||
if (!bikesAvailableEntryExists)
|
||||
{
|
||||
AddToCache(bikesAvailable, new TimeSpan(0));
|
||||
}
|
||||
|
||||
// Do not query bikes occupied if no user is logged in (leads to not implemented exception)
|
||||
var isLoggedIn = !string.IsNullOrEmpty(sessionCookie);
|
||||
var readBikesOccupiedFromCache = isLoggedIn && Barrel.Current.Exists(requestBuilder.GetBikesOccupied());
|
||||
var bikesOccupied = readBikesOccupiedFromCache
|
||||
? Barrel.Current.Get<BikesReservedOccupiedResponse>(requestBuilder.GetBikesOccupied())
|
||||
: JsonConvertRethrow.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED);
|
||||
|
||||
if (isLoggedIn && !readBikesOccupiedFromCache)
|
||||
{
|
||||
AddToCache(bikesOccupied, new TimeSpan(0));
|
||||
}
|
||||
|
||||
var stationsEntryExists = Barrel.Current.Exists(requestBuilder.GetStations());
|
||||
var stations = stationsEntryExists
|
||||
? Barrel.Current.Get<StationsAvailableResponse>(requestBuilder.GetStations())
|
||||
: JsonConvertRethrow.DeserializeObject<StationsAvailableResponse>(STATIONSALL);
|
||||
|
||||
if (!stationsEntryExists)
|
||||
{
|
||||
AddToCache(stations, new TimeSpan(0));
|
||||
}
|
||||
|
||||
copriModel = new CopriResponseModel(bikesAvailable, bikesOccupied, stations);
|
||||
}
|
||||
|
||||
public Task<ReservationBookingResponse> DoReserveAsync(string bikeId, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
}
|
||||
|
||||
public Task<BookingActionResponse> DoCancelReservationAsync(string bikeId, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
}
|
||||
|
||||
public Task<ReservationBookingResponse> CalculateAuthKeysAsync(string bikeId, Uri operatorUri)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
public Task<ResponseBase> StartReturningBike(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
public Task<ReservationBookingResponse> UpdateLockingStateAsync(
|
||||
string bikeId,
|
||||
lock_state state,
|
||||
Uri operatorUri,
|
||||
LocationDto geolocation,
|
||||
double batteryLevel,
|
||||
IVersionInfo versionInfo)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
public Task<ReservationBookingResponse> DoBookAsync(Uri operatorUri, string bikeId, Guid guid, double batteryPercentage, LockingAction? nextAction = null)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
/// <summary> Books a bike and starts opening bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public Task<ReservationBookingResponse> BookAvailableAndStartOpeningAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
/// <summary> Books a bike and starts opening bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public Task<ReservationBookingResponse> BookReservedAndStartOpeningAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
public Task<DoReturnResponse> DoReturn(
|
||||
string bikeId,
|
||||
LocationDto geolocation,
|
||||
Uri operatorUri)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
/// <summary> Returns a bike and starts closing. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on returning request.</returns>
|
||||
public Task<DoReturnResponse> ReturnAndStartClosingAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string bikeId, int? currentChargeBars, string message, bool isBikeBroken, Uri operatorUri)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
/// <summary> Submits mini survey to copri server. </summary>
|
||||
/// <param name="answers">Collection of answers.</param>
|
||||
public Task<ResponseBase> DoSubmitMiniSurvey(IDictionary<string, string> answers)
|
||||
=> throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
|
||||
public Task<AuthorizationResponse> DoAuthorizationAsync(string p_strMailAddress, string p_strPassword, string p_strDeviceId)
|
||||
{
|
||||
throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
}
|
||||
|
||||
public Task<AuthorizationoutResponse> DoAuthoutAsync()
|
||||
{
|
||||
throw new System.Exception(AppResources.ErrorNoWeb);
|
||||
}
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
|
||||
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
public async Task<BikesAvailableResponse> GetBikesAvailableAsync(
|
||||
Uri operatorUri = null,
|
||||
string stationId = null,
|
||||
string bikeId = null)
|
||||
{
|
||||
var bikesAvailableTask = new TaskCompletionSource<BikesAvailableResponse>();
|
||||
bikesAvailableTask.SetResult(copriModel.BikesAll
|
||||
.FilterByStation(stationId)
|
||||
.FilterByBike(bikeId));
|
||||
|
||||
return await bikesAvailableTask.Task;
|
||||
}
|
||||
|
||||
public async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var bikesOccupiedTask = new TaskCompletionSource<BikesReservedOccupiedResponse>();
|
||||
bikesOccupiedTask.SetResult(copriModel.BikesReservedOccupied);
|
||||
return await bikesOccupiedTask.Task;
|
||||
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// No user logged in.
|
||||
await Task.CompletedTask;
|
||||
return ResponseHelper.GetBikesOccupiedNone();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<StationsAvailableResponse> GetStationsAsync()
|
||||
{
|
||||
var stationsAllTask = new TaskCompletionSource<StationsAvailableResponse>();
|
||||
stationsAllTask.SetResult(copriModel.Stations);
|
||||
return await stationsAllTask.Task;
|
||||
}
|
||||
|
||||
/// <summary> Gets a value indicating whether stations are expired or not.</summary>
|
||||
public bool IsStationsExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
return Barrel.Current.IsExpired(requestBuilder.GetStations());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Adds a stations all response to cache.</summary>
|
||||
/// <param name="stations">Stations to add.</param>
|
||||
public void AddToCache(StationsAvailableResponse stations)
|
||||
{
|
||||
var updateTarget = copriModel.Update(stations);
|
||||
|
||||
AddToCache(copriModel.Stations, ExpiresAfter);
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.BikesAvailableResponse))
|
||||
{
|
||||
AddToCache(copriModel.BikesAll, ExpiresAfter);
|
||||
}
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.BikesReservedOccupiedResponse))
|
||||
{
|
||||
AddToCache(copriModel.BikesReservedOccupied, ExpiresAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Adds a stations all response to cache.</summary>
|
||||
/// <param name="stations">Stations to add.</param>
|
||||
/// <param name="expiresAfter">Time after which answer is considered to be expired.</param>
|
||||
private void AddToCache(StationsAvailableResponse stations, TimeSpan expiresAfter)
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
Barrel.Current.Add(
|
||||
requestBuilder.GetStations(),
|
||||
JsonConvertRethrow.SerializeObject(stations),
|
||||
expiresAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates cache from bike which changed rental state.
|
||||
/// </summary>
|
||||
/// <param name="response">Response to update from.</param>
|
||||
public void Update(BikeInfoReservedOrBooked response)
|
||||
{
|
||||
var updateTarget = copriModel.Update(response);
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.StationsAvailableResponse))
|
||||
{
|
||||
AddToCache(copriModel.Stations, ExpiresAfter);
|
||||
}
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.BikesAvailableResponse))
|
||||
{
|
||||
AddToCache(copriModel.BikesAll, ExpiresAfter);
|
||||
}
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.BikesReservedOccupiedResponse))
|
||||
{
|
||||
AddToCache(copriModel.BikesReservedOccupied, ExpiresAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Updates cache from bike which changed rental state (reservation/ booking canceled). </summary>
|
||||
public void Update(BookingActionResponse response)
|
||||
{
|
||||
var updateTarget = copriModel.Update(response);
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.StationsAvailableResponse))
|
||||
{
|
||||
AddToCache(copriModel.Stations, ExpiresAfter);
|
||||
}
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.BikesAvailableResponse))
|
||||
{
|
||||
AddToCache(copriModel.BikesAll, ExpiresAfter);
|
||||
}
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.BikesReservedOccupiedResponse))
|
||||
{
|
||||
AddToCache(copriModel.BikesReservedOccupied, ExpiresAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets a value indicating whether stations are expired or not.</summary>
|
||||
public bool IsBikesAvailableExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
return Barrel.Current.IsExpired(requestBuilder.GetBikesAvailable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
|
||||
/// <param name="stationId"> Id of station which was used for filtering bikes. Null if no filtering was applied.</param>
|
||||
/// <param name="bikeId"> Id of bike which was used for filtering bikes. Null if no filtering was applied.</param>
|
||||
public void AddToCache(
|
||||
BikesAvailableResponse bikes,
|
||||
Uri operatorUri = null,
|
||||
string stationId = null,
|
||||
string bikeId = null)
|
||||
{
|
||||
var updateTarget = copriModel.Update(bikes, stationId, bikeId);
|
||||
|
||||
AddToCache(copriModel.BikesAll, ExpiresAfter);
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.BikesReservedOccupiedResponse))
|
||||
{
|
||||
AddToCache(copriModel.BikesReservedOccupied, ExpiresAfter);
|
||||
}
|
||||
|
||||
if (updateTarget.HasFlag(UpdateTarget.StationsAvailableResponse))
|
||||
{
|
||||
AddToCache(copriModel.Stations, ExpiresAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
/// <param name="expiresAfter">Time after which answer is considered to be expired.</param>
|
||||
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
|
||||
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
private void AddToCache(
|
||||
BikesAvailableResponse bikes,
|
||||
TimeSpan expiresAfter)
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
Barrel.Current.Add(
|
||||
$"{requestBuilder.GetBikesAvailable()}",
|
||||
JsonConvertRethrow.SerializeObject(bikes),
|
||||
expiresAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets a value indicating whether stations are expired or not.</summary>
|
||||
public bool IsBikesOccupiedExpired
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
return Barrel.Current.IsExpired(requestBuilder.GetBikesOccupied());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
public void AddToCache(BikesReservedOccupiedResponse bikes)
|
||||
{
|
||||
// Update model in order to ensure a consistent state.
|
||||
var updateTarget = copriModel.Update(bikes);
|
||||
|
||||
// Update cache.
|
||||
AddToCache(copriModel.BikesReservedOccupied, ExpiresAfter);
|
||||
if (updateTarget.HasFlag(UpdateTarget.BikesAvailableResponse))
|
||||
{
|
||||
AddToCache(copriModel.BikesAll, ExpiresAfter);
|
||||
}
|
||||
if (updateTarget.HasFlag(UpdateTarget.StationsAvailableResponse))
|
||||
{
|
||||
AddToCache(copriModel.Stations, ExpiresAfter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
/// <param name="expiresAfter">Time after which answer is considered to be expired.</param>
|
||||
private void AddToCache(BikesReservedOccupiedResponse bikes, TimeSpan expiresAfter)
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
Barrel.Current.Add(
|
||||
requestBuilder.GetBikesOccupied(),
|
||||
JsonConvertRethrow.SerializeObject(bikes),
|
||||
expiresAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using ShareeBike.Repository.Response;
|
||||
using ShareeBike.Repository.Response.Stations;
|
||||
|
||||
namespace ShareeBike.Repository
|
||||
{
|
||||
public static class CopriCallsMonkeyStoreHelper
|
||||
{
|
||||
/// <summary> Gets an empty response. </summary>
|
||||
/// <param name="copriVersion">Version of empty response.</param>
|
||||
/// <returns>Response.</returns>
|
||||
public static BikesAvailableResponse GetEmptyBikesAvailableResponse(string copriVersion)
|
||||
=> JsonConvertRethrow.DeserializeObject<BikesAvailableResponse>(CopriCallsMonkeyStore.BIKESAVAILABLE.Replace("4.1.0.0", copriVersion));
|
||||
|
||||
/// <summary> Gets an empty response. </summary>
|
||||
/// <param name="copriVersion">Version of empty response.</param>
|
||||
/// <returns>Response.</returns>
|
||||
public static StationsAvailableResponse GetEmptyStationsAllResponse(string copriVersion)
|
||||
=> JsonConvertRethrow.DeserializeObject<StationsAvailableResponse>(CopriCallsMonkeyStore.STATIONSALL.Replace("4.1.0.0", copriVersion));
|
||||
|
||||
/// <summary> Gets an empty response. </summary>
|
||||
/// <param name="copriVersion">Version of empty response.</param>
|
||||
/// <returns>Response.</returns>
|
||||
public static BikesReservedOccupiedResponse GetEmptyBikesReservedOccupiedResponse(string copriVersion)
|
||||
=> JsonConvertRethrow.DeserializeObject<BikesReservedOccupiedResponse>(CopriCallsMonkeyStore.BIKESOCCUPIED.Replace("4.1.0.0", copriVersion));
|
||||
}
|
||||
}
|
67
SharedBusinessLogic/Repository/CopriCallsStatic.cs
Normal file
67
SharedBusinessLogic/Repository/CopriCallsStatic.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Repository.Response;
|
||||
|
||||
namespace ShareeBike.Repository
|
||||
{
|
||||
public static class CopriCallsStatic
|
||||
{
|
||||
#if !USCSHARP9
|
||||
private static Version UNSUPPORTEDFUTURECOPRIVERSIONLOWER = new Version(4, 1);
|
||||
#else
|
||||
private static Version UNSUPPORTEDFUTURECOPRIVERSIONLOWER = new(4, 1);
|
||||
#endif
|
||||
|
||||
#if !USCSHARP9
|
||||
private static Version UNSUPPORTEDFUTURECOPRIVERSIONUPPER = new Version(4, 2);
|
||||
#else
|
||||
private static Version UNSUPPORTEDFUTURECOPRIVERSIONUPPER = new(4, 2);
|
||||
#endif
|
||||
public static Version UnsupportedVersionLower => UNSUPPORTEDFUTURECOPRIVERSIONLOWER;
|
||||
|
||||
public static Version UnsupportedVersionUpper => UNSUPPORTEDFUTURECOPRIVERSIONUPPER;
|
||||
|
||||
/// <summary> Deserializes response JSON if response is of supported version or provides default response otherwise. </summary>
|
||||
/// <typeparam name="T">Type of response object.</typeparam>
|
||||
/// <param name="response">Response JSON.</param>
|
||||
/// <param name="emptyResponseFactory">Factory providing default delegate.</param>
|
||||
/// <returns>Response object.</returns>
|
||||
public static T DeserializeResponse<T>(this string response, Func<string, T> emptyResponseFactory) where T : class
|
||||
{
|
||||
// Get COPRI version from response.
|
||||
var bikeInfoBase = JsonConvertRethrow.DeserializeObject<VersionindependentResponse>(response)?.shareejson;
|
||||
|
||||
if (bikeInfoBase.GetCopriVersion() < UNSUPPORTEDFUTURECOPRIVERSIONLOWER
|
||||
|| bikeInfoBase.GetCopriVersion() >= UNSUPPORTEDFUTURECOPRIVERSIONUPPER)
|
||||
{
|
||||
return emptyResponseFactory?.Invoke(bikeInfoBase.copri_version) ?? null;
|
||||
}
|
||||
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<T>>(response)?.shareejson;
|
||||
}
|
||||
|
||||
/// <summary> Deserializes response JSON if response is of supported version or throws an exception. </summary>
|
||||
/// <typeparam name="T">Type of response object.</typeparam>
|
||||
/// <param name="response">Response JSON.</param>
|
||||
/// <param name="unsupportedVersionExectpion">Exception to fire.</param>
|
||||
/// <returns>Response object.</returns>
|
||||
public static T DeserializeResponse<T>(
|
||||
this string response,
|
||||
Func<string, System.Exception> unsupportedVersionExectpion = null) where T : class
|
||||
{
|
||||
|
||||
// Get COPRI version from response.
|
||||
var bikeInfoBase = JsonConvertRethrow.DeserializeObject<VersionindependentResponse>(response)?.shareejson;
|
||||
|
||||
if (bikeInfoBase.GetCopriVersion() < UNSUPPORTEDFUTURECOPRIVERSIONLOWER
|
||||
|| bikeInfoBase.GetCopriVersion() >= UNSUPPORTEDFUTURECOPRIVERSIONUPPER)
|
||||
{
|
||||
Log.Error($"Unsupported copri version {bikeInfoBase.copri_version} detected on attempt to log in.");
|
||||
throw unsupportedVersionExectpion?.Invoke(bikeInfoBase.copri_version) ?? new System.Exception($"Unsupported COPRI version {bikeInfoBase.copri_version} detected.");
|
||||
}
|
||||
|
||||
return JsonConvertRethrow.DeserializeObject<ResponseContainer<T>>(response)?.shareejson;
|
||||
}
|
||||
}
|
||||
}
|
591
SharedBusinessLogic/Repository/CopriResponseModel.cs
Normal file
591
SharedBusinessLogic/Repository/CopriResponseModel.cs
Normal file
|
@ -0,0 +1,591 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Serilog;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Repository.Response;
|
||||
using ShareeBike.Repository.Response.Stations;
|
||||
using ShareeBike.Repository.Response.Stations.Station;
|
||||
|
||||
namespace ShareeBike.Repository
|
||||
{
|
||||
[Flags]
|
||||
public enum UpdateTarget
|
||||
{
|
||||
None = 0,
|
||||
BikesAvailableResponse = 1,
|
||||
BikesReservedOccupiedResponse = 2,
|
||||
StationsAvailableResponse = 4,
|
||||
All = BikesAvailableResponse | BikesReservedOccupiedResponse | StationsAvailableResponse
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages station and bikes information.
|
||||
/// Bike is kept in a response- oriented way which is redundant an has to be synchronized for this reason when updated.
|
||||
/// </summary>
|
||||
public class CopriResponseModel
|
||||
{
|
||||
public CopriResponseModel(
|
||||
BikesAvailableResponse bikesAll,
|
||||
BikesReservedOccupiedResponse bikesReservedOccupied,
|
||||
StationsAvailableResponse stations)
|
||||
{
|
||||
BikesAll = bikesAll ?? new BikesAvailableResponse();
|
||||
BikesReservedOccupied = bikesReservedOccupied ?? new BikesReservedOccupiedResponse();
|
||||
Stations = stations ?? new StationsAvailableResponse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds
|
||||
/// - part of/ or all available bikes and
|
||||
/// - part of/ or all reserved bikes of logged in user if user is logged in and
|
||||
/// - part of/ or all occupied bikes of logged in user if user is logged in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Available bikes: Full information is only contained in <see cref="BikesAll"/>.
|
||||
/// Available bikes: Count of available bikes per station is kept in <see cref="Stations"/>.
|
||||
/// Reserved and occupied bikes: Are contained as well in <see cref="BikesReservedOccupied"/>.
|
||||
/// Reserved and occupied bikes: Are contained as well in <see cref="Stations"/>.
|
||||
/// </remarks>
|
||||
public BikesAvailableResponse BikesAll { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds all reserved and occupied bikes of logged in user if not used without being logged in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reserved and occupied bikes: Are contained as well in <see cref="BikesAll"/>.
|
||||
/// Reserved and occupied bikes: Are contained as well in <see cref="Stations"/>.
|
||||
/// </remarks>
|
||||
public BikesReservedOccupiedResponse BikesReservedOccupied { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// All bike stations, count of available bikes at each station
|
||||
/// and all reserved and occupied bikes of logged in user if not used without being logged in.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Count of available bikes a each station: Information contained in <see cref="BikesAll"/>.
|
||||
/// Reserved and occupied bikes: Are contained as well in <see cref="BikesReservedOccupied"/>.
|
||||
/// </remarks>
|
||||
public StationsAvailableResponse Stations { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates model from response.
|
||||
/// </summary>
|
||||
/// <param name="bikesAll">
|
||||
/// Holds
|
||||
/// - part of/ or all available bikes and
|
||||
/// - part of/ or all reserved bikes of logged in user if user is logged in and
|
||||
/// - part of/ or all occupied bikes of logged in user if user is logged in.
|
||||
/// </param>
|
||||
/// <returns>Parts of model which have to be updated.</returns>
|
||||
public UpdateTarget Update(
|
||||
BikesAvailableResponse bikesAllResponse,
|
||||
string stationId = null,
|
||||
string bikeId = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stationId))
|
||||
{
|
||||
return UpdateFromBikesAtStation(
|
||||
bikesAllResponse,
|
||||
stationId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(bikeId))
|
||||
{
|
||||
return UpdateFromBikesAtStation(
|
||||
bikesAllResponse,
|
||||
bikesAllResponse.bikes.Values.FirstOrDefault(x => x.bike.ToUpper() == bikeId.ToUpper())?.station);
|
||||
}
|
||||
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.ForContext<CopriResponseModel>().Error(
|
||||
"Updating response model form {@Type} for station id {@StationId} and bike id {BikeId} failed. {@Exception}",
|
||||
nameof(BikesAvailableResponse),
|
||||
stationId,
|
||||
bikeId,
|
||||
ex);
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates cache from bike which changed rental state from available or reserved to booked.
|
||||
/// </summary>
|
||||
/// <param name="response"></param>
|
||||
public UpdateTarget Update(BikeInfoReservedOrBooked response)
|
||||
{
|
||||
try
|
||||
{
|
||||
var updateTarget = UpdateTarget.None;
|
||||
if (response == null || string.IsNullOrEmpty(response.bike))
|
||||
{
|
||||
// Can nor update station or remove bike
|
||||
return updateTarget;
|
||||
}
|
||||
|
||||
// Update station if required i.e. decrease count of available bikes if required.
|
||||
updateTarget = UpdateAvailableBikesCount(
|
||||
BikesAll.bikes.Values.FirstOrDefault(b => b.bike == response?.bike)?.station,
|
||||
i => i - 1, // Decrease count of available bikes.
|
||||
updateTarget);
|
||||
|
||||
// A booked bike is no more available.
|
||||
BikesAll.bikes.Remove(response.bike);
|
||||
|
||||
// Update Stations with latest reserved/ rented bike.
|
||||
Stations.bikes_occupied.Remove(response.bike);
|
||||
Stations.bikes_occupied.Add(response.bike, response);
|
||||
|
||||
// Update BikesAll with latest reserved/ rented bike.
|
||||
BikesAll.bikes_occupied.Remove(response.bike);
|
||||
BikesAll.bikes_occupied.Add(response.bike, response);
|
||||
|
||||
// Update BikesReservedOccupied with latest reserved/ rented bike.
|
||||
BikesReservedOccupied.bikes_occupied.Remove(response.bike);
|
||||
BikesReservedOccupied.bikes_occupied.Add(response.bike, response);
|
||||
|
||||
return updateTarget | UpdateTarget.BikesAvailableResponse | UpdateTarget.BikesReservedOccupiedResponse;
|
||||
} catch (System.Exception ex)
|
||||
{
|
||||
Log.ForContext<CopriResponseModel>().Error(
|
||||
"Updating response model form {@Type} failed. {@Exception}",
|
||||
nameof(BikeInfoReservedOrBooked),
|
||||
ex);
|
||||
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Updates cache from bike which changed rental state (reservation/ booking canceled). </summary>
|
||||
public UpdateTarget Update(BookingActionResponse response)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (response == null || string.IsNullOrEmpty(response.bike))
|
||||
{
|
||||
// Can nor update station or remove bike
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
|
||||
// Remove available bike from Stations.
|
||||
Stations.bikes_occupied.Remove(response.bike);
|
||||
|
||||
// Remove available bike from BikesAll.
|
||||
BikesAll.bikes_occupied.Remove(response.bike);
|
||||
|
||||
// Remove available bike from BikesReservedOccupied.
|
||||
BikesReservedOccupied.bikes_occupied.Remove(response.bike);
|
||||
|
||||
return UpdateTarget.StationsAvailableResponse | UpdateTarget.BikesAvailableResponse | UpdateTarget.BikesReservedOccupiedResponse;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.ForContext<CopriResponseModel>().Error(
|
||||
"Updating response model form {@Type} failed. {@Exception}",
|
||||
nameof(BookingActionResponse),
|
||||
ex);
|
||||
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates model from response.
|
||||
/// </summary>
|
||||
/// <param name="bikesAll">
|
||||
/// Holds
|
||||
/// - all available bikes located at given station and
|
||||
/// - all reserved bikes of logged in user if user is logged in located at given and
|
||||
/// - all occupied bikes of logged in user if user is logged in located at given .
|
||||
/// </param>
|
||||
/// <returns>Parts of model which have to be updated.</returns>
|
||||
private UpdateTarget UpdateFromBikesAtStation(
|
||||
BikesAvailableResponse bikesAllResponse,
|
||||
string stationId)
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronizes a dictionary with another, i.e.
|
||||
/// - adds values which do not yet exist
|
||||
/// - updates existing values with new ones and
|
||||
/// - removes bikes which do no more exist
|
||||
/// </summary>
|
||||
/// <param name="source">Source dictionary to be read from.</param>
|
||||
/// <param name="target">Target directory to be written to.</param>
|
||||
void Synchonize<T>(
|
||||
ComparableDictionary<T> sourceDict,
|
||||
ComparableDictionary<T> targetDict,
|
||||
Action updateRequiredAction) where T : BikeInfoBase, IEquatable<T>
|
||||
{
|
||||
if (targetDict == null)
|
||||
{
|
||||
// Nothing to do if target dictionary is empty.
|
||||
return;
|
||||
}
|
||||
|
||||
// Add/ update bikes
|
||||
foreach (var sourceBike in sourceDict ?? new ComparableDictionary<T>())
|
||||
{
|
||||
// Check if bikes already exists.
|
||||
var targetBike = targetDict.Values.FirstOrDefault(x => x.bike == sourceBike.Value.bike);
|
||||
if (targetBike != null)
|
||||
{
|
||||
// Check if bikes equals
|
||||
if (sourceBike.Value.Equals(targetBike))
|
||||
{
|
||||
// Bikes equal, process next.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove all bikes from target which part of source to be added later.
|
||||
targetDict.Remove(sourceBike.Key);
|
||||
}
|
||||
|
||||
targetDict.Add(sourceBike.Key, sourceBike.Value);
|
||||
updateRequiredAction?.Invoke();
|
||||
}
|
||||
|
||||
// Remove bikes from target dictionary which are no more at station
|
||||
var bikesToRemove = targetDict
|
||||
.Where(x => x.Value.station == stationId)
|
||||
.Where(x => sourceDict?.Values?.FirstOrDefault(y => y.station == stationId) == null);
|
||||
|
||||
foreach (var bike in bikesToRemove)
|
||||
{
|
||||
targetDict.Remove(bike.Key);
|
||||
updateRequiredAction?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
if (BikesAll == bikesAllResponse
|
||||
|| bikesAllResponse == null)
|
||||
{
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
|
||||
var updateTarget = UpdateTarget.None;
|
||||
|
||||
// Update bikes all by updating existing bikes and adding new ones.
|
||||
Synchonize(
|
||||
bikesAllResponse.bikes,
|
||||
BikesAll.bikes,
|
||||
() => { updateTarget |= UpdateTarget.BikesAvailableResponse; });
|
||||
|
||||
Synchonize(
|
||||
bikesAllResponse.bikes_occupied,
|
||||
BikesAll.bikes_occupied,
|
||||
() => { updateTarget |= UpdateTarget.BikesAvailableResponse; });
|
||||
|
||||
// Update stations property count of available bikes for selected station.
|
||||
UpdateAvailableBikesCount(
|
||||
bikesAllResponse.bikes.Count,
|
||||
Stations.stations.Values.FirstOrDefault(x => x.station == stationId),
|
||||
() => updateTarget |= UpdateTarget.StationsAvailableResponse);
|
||||
|
||||
// Add/ update stations bikes reserved and stations bikes occupied.
|
||||
Synchonize(
|
||||
bikesAllResponse.bikes_occupied,
|
||||
Stations.bikes_occupied,
|
||||
() => updateTarget |= UpdateTarget.StationsAvailableResponse);
|
||||
|
||||
// Add/ update reserved and occupied bikes.
|
||||
Synchonize(
|
||||
bikesAllResponse.bikes_occupied,
|
||||
BikesReservedOccupied.bikes_occupied,
|
||||
() => updateTarget |= UpdateTarget.BikesReservedOccupiedResponse);
|
||||
|
||||
// Remove available bikes from bikes available response if there are: A bike reserved or occupied can not be available at the same time.
|
||||
Remove(
|
||||
bikesAllResponse.bikes_occupied.Values.Select(x => x.bike).ToList(),
|
||||
BikesAll.bikes,
|
||||
() => updateTarget |= UpdateTarget.BikesAvailableResponse);
|
||||
|
||||
// Remove reserved and occupied bikes from bikes available response if there are: A bike available can not be reserved or occupied at the same time.
|
||||
Remove(
|
||||
bikesAllResponse.bikes.Values.Select(x => x.bike).ToList(),
|
||||
BikesAll.bikes_occupied,
|
||||
() => updateTarget |= UpdateTarget.BikesAvailableResponse);
|
||||
|
||||
// Remove reserved and occupied bikes from stations response if there are: A bike available can not be reserved or occupied at the same time.
|
||||
Remove(
|
||||
bikesAllResponse.bikes.Values.Select(x => x.bike).ToList(),
|
||||
Stations.bikes_occupied,
|
||||
() => updateTarget |= UpdateTarget.StationsAvailableResponse);
|
||||
|
||||
return updateTarget;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates model from reserved and occupied bikes.
|
||||
/// </summary>
|
||||
/// <param name="bikesReservedOccupiedResponse">Bikes to update with.</param>
|
||||
/// <returns>Parts of model which have to be updated.</returns>
|
||||
public UpdateTarget Update(BikesReservedOccupiedResponse bikesReservedOccupiedResponse)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (bikesReservedOccupiedResponse == BikesReservedOccupied
|
||||
|| bikesReservedOccupiedResponse == null)
|
||||
{
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
|
||||
var updateTarget = UpdateTarget.BikesReservedOccupiedResponse;
|
||||
BikesReservedOccupied = bikesReservedOccupiedResponse; ;
|
||||
|
||||
// Update bikes reserved or occupied from bikes reserved or occupied response.
|
||||
Write(
|
||||
bikesReservedOccupiedResponse.bikes_occupied,
|
||||
BikesAll.bikes_occupied,
|
||||
() => updateTarget |= UpdateTarget.BikesAvailableResponse);
|
||||
|
||||
// Update stations from bikes reserved or occupied response.
|
||||
Write(
|
||||
bikesReservedOccupiedResponse.bikes_occupied,
|
||||
Stations.bikes_occupied,
|
||||
() => updateTarget |= UpdateTarget.StationsAvailableResponse);
|
||||
|
||||
// Remove available bikes from bikes available response: A bike reserved/ occupied can not be available at the same time.
|
||||
Remove(
|
||||
bikesReservedOccupiedResponse.bikes_occupied.Values.Select(x => x.bike).ToList(),
|
||||
BikesAll.bikes,
|
||||
() => updateTarget |= UpdateTarget.BikesAvailableResponse);
|
||||
|
||||
// Due to possible change of bikes available from bikes available response (step above), count of bikes from stations might need to be updated.
|
||||
UpdateAvailableBikesCount(
|
||||
BikesAll.bikes,
|
||||
Stations.stations,
|
||||
() => updateTarget |= UpdateTarget.StationsAvailableResponse);
|
||||
|
||||
return updateTarget;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.ForContext<CopriResponseModel>().Error(
|
||||
"Updating response model form {@Type} failed. {@Exception}",
|
||||
nameof(BikesReservedOccupiedResponse),
|
||||
ex);
|
||||
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates model from stations response.
|
||||
/// </summary>
|
||||
/// <param name="stationsResponse">Response to update from.</param>
|
||||
/// <returns></returns>
|
||||
public UpdateTarget Update(StationsAvailableResponse stationsResponse)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Remove bike from dictionary if located at a list of stations.
|
||||
// This can only be done randomly because only the count of bikes without bike ids is known (task #901).
|
||||
void LimitAvailableBikesCountByStation(
|
||||
IList<StationInfo> stations,
|
||||
ComparableBikeDictionary<BikeInfoAvailable> bikes,
|
||||
Action updateRequiredAction)
|
||||
{
|
||||
// Group available bikes by station.
|
||||
var groupedBikes = bikes.GroupBy(x => x.Value.station).ToList();
|
||||
|
||||
foreach (var station in stations)
|
||||
{
|
||||
var bikesAtStation = groupedBikes.FirstOrDefault(x => x.Key == station.station);
|
||||
|
||||
var toReviseCount = bikesAtStation?.ToList()?.Count ?? 0;
|
||||
if (string.IsNullOrEmpty(bikesAtStation?.Key)
|
||||
|| !station.TryGetBikesAvailableCount(out var trueCount)
|
||||
|| toReviseCount <= trueCount)
|
||||
{
|
||||
// Either
|
||||
// - there is no bikes at current station or
|
||||
// - invalid format detected
|
||||
// - count of bikes is ok
|
||||
continue;
|
||||
}
|
||||
|
||||
var surplus = toReviseCount - trueCount;
|
||||
|
||||
// There are move bikes available at current station in bikes available response than the station object reports.
|
||||
// Randomly remove surplus bikes.
|
||||
var keys = bikesAtStation.Select(x => x.Key).ToList();
|
||||
for (var i = 0; i < surplus; i++)
|
||||
{
|
||||
bikes.Remove(keys[i]);
|
||||
}
|
||||
|
||||
updateRequiredAction?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
if (Stations == stationsResponse
|
||||
|| stationsResponse == null)
|
||||
{
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
|
||||
var updateTarget = UpdateTarget.StationsAvailableResponse;
|
||||
Stations = stationsResponse;
|
||||
|
||||
// Ensure that bikes which have become occupied are no more in list of available bikes
|
||||
foreach (var bike in Stations.bikes_occupied.Values)
|
||||
{
|
||||
if (!BikesAll.bikes.ContainsKey(bike.bike)) continue;
|
||||
|
||||
BikesAll.bikes.Remove(bike.bike);
|
||||
}
|
||||
|
||||
// Ensure that there are not more bikes assigned to one station in bikes available object than there are reported in station response.
|
||||
// An example for this case is that a bike is brought to garage.
|
||||
LimitAvailableBikesCountByStation(
|
||||
Stations.stations.Values.ToList(),
|
||||
BikesAll.bikes,
|
||||
() => updateTarget |= UpdateTarget.BikesAvailableResponse);
|
||||
|
||||
// Update bikes reserved and bikes occupied from stations response.
|
||||
Write(
|
||||
Stations.bikes_occupied,
|
||||
BikesAll.bikes_occupied,
|
||||
() => updateTarget |= UpdateTarget.BikesAvailableResponse);
|
||||
|
||||
// Update bikes reserved or occupied from stations response.
|
||||
Write(
|
||||
Stations.bikes_occupied,
|
||||
BikesReservedOccupied.bikes_occupied,
|
||||
() => updateTarget |= UpdateTarget.BikesReservedOccupiedResponse);
|
||||
|
||||
return updateTarget;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.ForContext<CopriResponseModel>().Error(
|
||||
"Updating response model form {@Type} failed. {@Exception}",
|
||||
nameof(StationsAvailableResponse),
|
||||
ex);
|
||||
|
||||
return UpdateTarget.None;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes bikes from a dictionary.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of dictionary values to remove.</typeparam>
|
||||
/// <param name="bikesToRemove">Bikes to remove.</param>
|
||||
/// <param name="target">Dictionary to removed bikes from.</param>
|
||||
/// <param name="updateRequiredAction">Action to be invoked if any bike gets removed from dictionary.</param>
|
||||
private void Remove<T>(
|
||||
IList<string> bikesToRemove,
|
||||
ComparableDictionary<T> target,
|
||||
Action updateRequiredAction) where T : BikeInfoBase
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var bike in bikesToRemove)
|
||||
{
|
||||
if (!target.Remove(bike))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
updateRequiredAction?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a dictionary to another if required.
|
||||
/// </summary>
|
||||
/// <param name="source">Source dictionary to be read from.</param>
|
||||
/// <param name="target">Target directory to be written to.</param>
|
||||
void Write(
|
||||
ComparableDictionary<BikeInfoReservedOrBooked> source,
|
||||
ComparableDictionary<BikeInfoReservedOrBooked> target,
|
||||
Action updateRequiredAction)
|
||||
{
|
||||
if (target == null || source == target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target.Clear();
|
||||
|
||||
foreach (var bike in source ?? new ComparableBikeDictionary<BikeInfoReservedOrBooked>())
|
||||
{
|
||||
target.Add(bike.Key, bike.Value);
|
||||
}
|
||||
|
||||
updateRequiredAction?.Invoke();
|
||||
}
|
||||
|
||||
private void UpdateAvailableBikesCount(
|
||||
ComparableBikeDictionary<BikeInfoAvailable> bikesAvailable,
|
||||
ComparableDictionary<StationInfo> stations,
|
||||
Action updateRequiredAction)
|
||||
{
|
||||
foreach (var station in stations.Values)
|
||||
{
|
||||
UpdateAvailableBikesCount(
|
||||
bikesAvailable.Values.Where(x => x.station == station.station).Count(),
|
||||
station,
|
||||
updateRequiredAction);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the count of available bikes of a given station.
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the station to update count for.</param>
|
||||
/// <param name="countDelegate">Delegate to return the new count.</param>
|
||||
/// <param name="updateRequiredAction">Update action required</param>
|
||||
private UpdateTarget UpdateAvailableBikesCount(
|
||||
string id,
|
||||
Func<int, int> countDelegate,
|
||||
UpdateTarget currentUpdateTarget)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
// Bike was not available before.
|
||||
return currentUpdateTarget;
|
||||
}
|
||||
|
||||
var station = Stations.stations.Values.FirstOrDefault(s => s.station == id);
|
||||
if (station == null)
|
||||
{
|
||||
// There is no matching station.
|
||||
return currentUpdateTarget;
|
||||
}
|
||||
|
||||
// Decrement count of stations.
|
||||
station.SetBikesAvailableCount(countDelegate(station.GetBikesAvailableCount() ?? 1));
|
||||
return currentUpdateTarget | UpdateTarget.StationsAvailableResponse;
|
||||
}
|
||||
private void UpdateAvailableBikesCount(
|
||||
int bikesAvailableCount,
|
||||
StationInfo station,
|
||||
Action updateRequiredAction)
|
||||
{
|
||||
if (station == null
|
||||
|| !station.TryGetBikesAvailableCount(out var targetBikesCount)
|
||||
|| targetBikesCount == bikesAvailableCount)
|
||||
{
|
||||
// Either
|
||||
// - there is no matching station or
|
||||
// - station count if not numeric or
|
||||
// - count matches
|
||||
return;
|
||||
}
|
||||
|
||||
station.SetBikesAvailableCount(bikesAvailableCount);
|
||||
updateRequiredAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Is fired with request used a cookie which is not defined.
|
||||
/// Reasons for cookie to be not defined might be
|
||||
/// - user used more that 8 different devices (copri invalidates cookies in this case)
|
||||
/// - user account has been deleted?
|
||||
/// </summary>
|
||||
public class AuthcookieNotDefinedException : InvalidResponseException<Response.ResponseBase>
|
||||
{
|
||||
/// <summary>Constructs a authorization exceptions. </summary>
|
||||
/// <param name="p_strTextOfAction">Text describing request which is shown if validation fails.</param>
|
||||
public AuthcookieNotDefinedException(string p_strTextOfAction, Response.ResponseBase response) :
|
||||
base($"{p_strTextOfAction}\r\nDie Sitzung ist abgelaufen. Bitte neu anmelden.", response)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether authcookie is defined or not.
|
||||
/// </summary>
|
||||
/// <param name="reponse">Response to check</param>
|
||||
/// <param name="actionText">Text holding context in which authcookie is checked.</param>
|
||||
/// <param name="exception">Exception thrown if cookie is not defined.</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsAuthcookieNotDefined(
|
||||
Response.ResponseBase reponse,
|
||||
string actionText,
|
||||
out AuthcookieNotDefinedException exception)
|
||||
{
|
||||
if (reponse == null || reponse.response_state == null)
|
||||
{
|
||||
// Empty response or response without response state is no authcookie not defined exception.
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!reponse.response_state.ToUpper().Contains(AUTH_FAILURE_QUERY_AUTHCOOKIENOTDEFIED.ToUpper())
|
||||
&& !reponse.response_state.ToUpper().Contains(AUTH_FAILURE_BOOK_AUTICOOKIENOTDEFIED.ToUpper())
|
||||
&& !reponse.response_state.ToUpper().Contains(AUTH_FAILURE_BIKESOCCUPIED_AUTICOOKIENOTDEFIED.ToUpper())
|
||||
&& !reponse.response_state.ToUpper().Contains(AUTH_FAILURE_LOGOUT_AUTHCOOKIENOTDEFIED.ToUpper()))
|
||||
{
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
exception = new AuthcookieNotDefinedException(actionText, reponse);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Holds error description if session expired. From COPRI 4.0.0.0 1001 is the only authcookie not defined error. </summary>
|
||||
private const string AUTH_FAILURE_QUERY_AUTHCOOKIENOTDEFIED = "Failure 1001: authcookie not defined";
|
||||
|
||||
/// <summary> Holds error description if session expired (Applies to COPRI < 4.0.0.0) </summary>
|
||||
private const string AUTH_FAILURE_BOOK_AUTICOOKIENOTDEFIED = "Failure 1002: authcookie not defined";
|
||||
|
||||
/// <summary> Holds error description if session expired (Applies to COPRI < 4.0.0.0) </summary>
|
||||
private const string AUTH_FAILURE_BIKESOCCUPIED_AUTICOOKIENOTDEFIED = "Failure 1003: authcookie not defined";
|
||||
|
||||
/// <summary> Holds error description if session expired. (Applies to COPRI < 4.0.0.0)</summary>
|
||||
private const string AUTH_FAILURE_LOGOUT_AUTHCOOKIENOTDEFIED = "Failure 1004: authcookie not defined";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using ShareeBike.MultilingualResources;
|
||||
|
||||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class InvalidAuthorizationResponseException : InvalidResponseException<Response.ResponseBase>
|
||||
{
|
||||
/// <summary>Constructs a authorization exceptions. </summary>
|
||||
/// <param name="mail">Mail address to create a detailed error message.</param>
|
||||
public InvalidAuthorizationResponseException(string mail, Response.ResponseBase response) :
|
||||
base(string.Format(AppResources.ErrorAccountInvalidAuthorization, mail), response)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> Holds error description if user/ password combination is not valid. </summary>
|
||||
public const string AUTH_FAILURE_STATUS_MESSAGE_UPPERCASE = "FAILURE: CANNOT GENERATE AUTHCOOKIE";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
/// <summary> Handles booking request which fail due to too many bikes requested/ booked.</summary>
|
||||
public class BookingDeclinedException : InvalidResponseException
|
||||
{
|
||||
/// <summary> Holds error description if user/ password combination is not valid. </summary>
|
||||
public const string BOOKING_FAILURE_STATUS_MESSAGE_UPPERCASE = "(OK: BOOKING_REQUEST DECLINED\\. MAX COUNT OF )([0-9]+)( OCCUPIED BIKES HAS BEEN REACHED)";
|
||||
|
||||
/// <summary> Prevents invalid use of exception. </summary>
|
||||
private BookingDeclinedException() : base(typeof(BookingDeclinedException).Name)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> Prevents invalid use of exception. </summary>
|
||||
public BookingDeclinedException(int maxBikesCount) : base(typeof(BookingDeclinedException).Name)
|
||||
{
|
||||
MaxBikesCount = maxBikesCount;
|
||||
}
|
||||
|
||||
public static bool IsBookingDeclined(string responseState, out BookingDeclinedException exception)
|
||||
{
|
||||
// Check if there are too many bikes requested/ booked.
|
||||
var match = Regex.Match(
|
||||
responseState.ToUpper(),
|
||||
BOOKING_FAILURE_STATUS_MESSAGE_UPPERCASE);
|
||||
if (match.Groups.Count != 4
|
||||
|| !int.TryParse(match.Groups[2].ToString(), out int maxBikesCount))
|
||||
{
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
exception = new BookingDeclinedException(maxBikesCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Holds the maximum count of bikes allowed to reserve/ book.</summary>
|
||||
public int MaxBikesCount { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class CallNotRequiredException : System.Exception
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class CommunicationException : System.Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a communication exception object.
|
||||
/// </summary>
|
||||
public CommunicationException()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a communication exeption object.
|
||||
/// </summary>
|
||||
/// <param name="p_strMessage">Error message.</param>
|
||||
public CommunicationException(string p_strMessage) : base(p_strMessage)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a communication exeption object.
|
||||
/// </summary>
|
||||
/// <param name="p_strMessage">Error message.</param>
|
||||
/// <param name="p_oException">Inner exceptions.</param>
|
||||
public CommunicationException(string p_strMessage, System.Exception p_oException) : base(p_strMessage, p_oException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using ShareeBike.MultilingualResources;
|
||||
|
||||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class DeserializationException : CommunicationException
|
||||
{
|
||||
public DeserializationException(System.Exception ex) : base(
|
||||
string.Format(AppResources.ErrorDeserializingServerResponse, ex.Message),
|
||||
ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class InvalidResponseException<T> : InvalidResponseException
|
||||
{
|
||||
/// <summary> Constructs an invalid response Exception. </summary>
|
||||
/// <param name="actionWhichFailed">Describes the action which failed.</param>
|
||||
/// <param name="response">Response from copri.</param>
|
||||
public InvalidResponseException(string actionWhichFailed, T response)
|
||||
: base(string.Format(
|
||||
"{0}{1}",
|
||||
actionWhichFailed,
|
||||
response == null ? string.Format(" Response of type {0} is null.", typeof(T).Name.ToString()) : string.Empty))
|
||||
{
|
||||
Response = response;
|
||||
}
|
||||
|
||||
public T Response { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base exception for all generic invalid response exceptions.
|
||||
/// </summary>
|
||||
public class InvalidResponseException : CommunicationException
|
||||
{
|
||||
/// <summary> Prevents an invalid instance to be created. </summary>
|
||||
private InvalidResponseException()
|
||||
{ }
|
||||
|
||||
/// <summary> Constructs a invalid response execption.</summary>
|
||||
/// <param name="message">Exception.</param>
|
||||
public InvalidResponseException(string message) : base(message)
|
||||
{ }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class NoGPSDataException : InvalidResponseException
|
||||
{
|
||||
/// <summary> COPRI response status. </summary>
|
||||
public const string RETURNBIKE_FAILURE_STATUS_MESSAGE_UPPERCASE = "FAILURE 2245: NO GPS DATA, STATE CHANGE FORBIDDEN.";
|
||||
|
||||
/// <summary> Prevents invalid use of exception. </summary>
|
||||
private NoGPSDataException() : base(typeof(NoGPSDataException).Name)
|
||||
{
|
||||
}
|
||||
|
||||
public static bool IsNoGPSData(string responseState, out NoGPSDataException exception)
|
||||
{
|
||||
// Check if there are too many bikes requested/ booked.
|
||||
if (!responseState.Trim().ToUpper().StartsWith(RETURNBIKE_FAILURE_STATUS_MESSAGE_UPPERCASE))
|
||||
{
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
exception = new NoGPSDataException();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class NotAtStationException : InvalidResponseException
|
||||
{
|
||||
public const string RETURNBIKE_FAILURE_STATUS_MESSAGE_CODE = "FAILURE 2178:";
|
||||
|
||||
/// <summary> COPRI response status regular expression to extract detail information. </summary>
|
||||
public const string RETURNBIKE_FAILURE_STATUS_MESSAGE_UPPERCASE = "(BIKE [A-Za-z0-9_]+ OUT OF GEO FENCING\\. )([0-9]+)( METER DISTANCE TO NEXT STATION )([A-Za-z0-9_]+)";
|
||||
|
||||
/// <summary> Prevents invalid use of exception. </summary>
|
||||
private NotAtStationException() : base(typeof(NotAtStationException).Name)
|
||||
{
|
||||
}
|
||||
|
||||
public static bool IsNotAtStation(string responseState, out NotAtStationException exception)
|
||||
{
|
||||
// Check if there are too many bikes requested/ booked.
|
||||
var response = responseState.Trim().ToUpper();
|
||||
|
||||
if (!response.StartsWith(RETURNBIKE_FAILURE_STATUS_MESSAGE_CODE))
|
||||
{
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var match = Regex.Match(
|
||||
responseState.ToUpper(),
|
||||
RETURNBIKE_FAILURE_STATUS_MESSAGE_UPPERCASE);
|
||||
if (match.Groups.Count != 5
|
||||
|| !int.TryParse(match.Groups[2].ToString(), out int meters))
|
||||
{
|
||||
exception = new NotAtStationException();
|
||||
return true;
|
||||
}
|
||||
|
||||
exception = new NotAtStationException {
|
||||
Distance = meters,
|
||||
StationNr = match.Groups[4].ToString()
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Holds the distance to next station.</summary>
|
||||
public int? Distance { get; private set; } = null;
|
||||
|
||||
/// <summary> Holds the id of next station.</summary>
|
||||
public string StationNr { get; private set; } = string.Empty;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using ShareeBike.Repository.Response;
|
||||
|
||||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class ResponseException : System.Exception
|
||||
{
|
||||
private readonly ResponseBase _response;
|
||||
public ResponseException(ResponseBase response, string message) : base(message)
|
||||
{
|
||||
_response = response;
|
||||
}
|
||||
|
||||
public string Response => _response.response_text;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using ShareeBike.Repository.Response;
|
||||
|
||||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class ReturnBikeException : ResponseException
|
||||
{
|
||||
public ReturnBikeException(BikesReservedOccupiedResponse response, string message) : base(response, message)
|
||||
{ }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class UnsupportedCopriVersionDetectedException : System.Exception
|
||||
{
|
||||
public UnsupportedCopriVersionDetectedException() : base("Unsupported app version detected.")
|
||||
{ }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using ShareeBike.MultilingualResources;
|
||||
|
||||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class WebConnectFailureException : CommunicationException
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a communication exception object.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="exception"></param>
|
||||
public WebConnectFailureException(string message, System.Exception exception) : base(message, exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System.Net;
|
||||
|
||||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public static class WebExceptionHelper
|
||||
{
|
||||
/// <summary> Gets if a exception is caused by an error connecting to copri (LAN or mobile data off/ not reachable, proxy, ...).</summary>
|
||||
/// <param name="exception">Expection to check.</param>
|
||||
/// <returns>True if exception if caused by an connection error. </returns>
|
||||
public static bool GetIsConnectFailureException(this System.Exception exception)
|
||||
{
|
||||
#if !USCSHARP9
|
||||
if (!(exception is WebException webException))
|
||||
#else
|
||||
if (exception is not WebException webException)
|
||||
#endif
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return webException.Status == WebExceptionStatus.ConnectFailure // Happens if WLAN and mobile data is off/ Router denies internet access/ ...
|
||||
|| webException.Status == WebExceptionStatus.NameResolutionFailure // Happens sometimes when not WLAN and no mobil connection are available (bad connection in lift).
|
||||
|| webException.Status == WebExceptionStatus.ReceiveFailure; // Happened when mobile was connected to WLAN
|
||||
}
|
||||
|
||||
/// <summary> Gets if a exception is caused by clicking too fast.</summary>
|
||||
/// <param name="exception">Expection to check.</param>
|
||||
/// <returns>True if exception if caused by a fast click sequence. </returns>
|
||||
public static bool GetIsForbiddenException(this System.Exception exception)
|
||||
{
|
||||
#if !USCSHARP9
|
||||
if (!(exception is WebException webException))
|
||||
#else
|
||||
if (exception is not WebException webException)
|
||||
#endif
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if !USCSHARP9
|
||||
if (!(webException?.Response is HttpWebResponse response))
|
||||
#else
|
||||
if (webException?.Response is not HttpWebResponse response)
|
||||
#endif
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return webException.Status == WebExceptionStatus.ProtocolError
|
||||
&& response.StatusCode == HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
namespace ShareeBike.Repository.Exception
|
||||
{
|
||||
public class WebForbiddenException : CommunicationException
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a communication exeption object.
|
||||
/// </summary>
|
||||
/// <param name="p_strMessage"></param>
|
||||
/// <param name="p_oException"></param>
|
||||
public WebForbiddenException(string p_strMessage, System.Exception p_oException) : base($"{p_strMessage}\r\nSchnell getippt?\r\nBitte die App etwas langsamer bedienen...", p_oException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
172
SharedBusinessLogic/Repository/ICopriServer.cs
Normal file
172
SharedBusinessLogic/Repository/ICopriServer.cs
Normal file
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Repository.Request;
|
||||
using ShareeBike.Repository.Response;
|
||||
using ShareeBike.Repository.Response.Stations;
|
||||
|
||||
namespace ShareeBike.Repository
|
||||
{
|
||||
/// <summary> Interface to communicate with copri server.</summary>
|
||||
public interface ICopriServerBase
|
||||
{
|
||||
/// <summary> Logs user in. </summary>
|
||||
/// <param name="mailAddress">Mail address of user to log in.</param>
|
||||
/// <param name="password">Password to log in.</param>
|
||||
/// <param name="deviceId">Id specifying user and hardware.</param>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
Task<AuthorizationResponse> DoAuthorizationAsync(
|
||||
string mailAddress,
|
||||
string password,
|
||||
string deviceId);
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
Task<AuthorizationoutResponse> DoAuthoutAsync();
|
||||
|
||||
/// <summary> Reserves bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to reserve.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on reserving request.</returns>
|
||||
Task<ReservationBookingResponse> DoReserveAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Cancels reservation of bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to reserve.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on cancel reservation request.</returns>
|
||||
Task<BookingActionResponse> DoCancelReservationAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Get authentication keys. </summary>
|
||||
/// <param name="bikeId">Id of the bike to get keys for.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response holding authentication keys.</returns>
|
||||
Task<ReservationBookingResponse> CalculateAuthKeysAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Notifies COPRI about start of returning sequence. </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>+
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on notification about start of returning sequence.</returns>
|
||||
Task<ResponseBase> StartReturningBike(
|
||||
string bikeId,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Updates COPRI lock state for a booked bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to update locking state for.</param>
|
||||
/// <param name="location">Geolocation of lock.</param>
|
||||
/// <param name="state">New locking state.</param>
|
||||
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on updating locking state.</returns>
|
||||
Task<ReservationBookingResponse> UpdateLockingStateAsync(
|
||||
string bikeId,
|
||||
lock_state state,
|
||||
Uri operatorUri,
|
||||
LocationDto location = null,
|
||||
double batteryPercentage = double.NaN,
|
||||
IVersionInfo versionInfo = null);
|
||||
|
||||
/// <summary> Books a bluetooth bike. </summary>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="guid">Used to publish GUID from app to copri. Used for initial setup of bike in copri.</param>
|
||||
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
|
||||
/// <param name="nextAction">If not null next locking action which is performed after booking.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
Task<ReservationBookingResponse> DoBookAsync(
|
||||
Uri operatorUri,
|
||||
string bikeId,
|
||||
Guid guid,
|
||||
double batteryPercentage,
|
||||
LockingAction? nextAction = null);
|
||||
|
||||
/// <summary> Books a bike and starts opening bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
Task<ReservationBookingResponse> BookAvailableAndStartOpeningAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Books a bike and starts opening bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
Task<ReservationBookingResponse> BookReservedAndStartOpeningAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Returns a bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="location">Geolocation of lock.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on returning request.</returns>
|
||||
Task<DoReturnResponse> DoReturn(
|
||||
string bikeId,
|
||||
LocationDto location,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Returns a bike and starts closing. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="smartDevice">Provides info about hard and software.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on returning request.</returns>
|
||||
Task<DoReturnResponse> ReturnAndStartClosingAsync(
|
||||
string bikeId,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary>
|
||||
/// Submits feedback to copri server.
|
||||
/// </summary>
|
||||
/// <param name="bikeId">Id of the bike to submit feedback for.</param>
|
||||
/// <param name="currentChargeBars">Null if bike has no engine or charge is unknown. Otherwise the charge filling level of the drive battery.</param>
|
||||
/// <param name="isBikeBroken">True if bike is broken.</param>
|
||||
/// <param name="message">General purpose message or error description.</param>
|
||||
Task<SubmitFeedbackResponse> DoSubmitFeedback(
|
||||
string bikeId,
|
||||
int? currentChargeBars,
|
||||
string message,
|
||||
bool isBikeBroken,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Submits mini survey to copri server. </summary>
|
||||
/// <param name="answers">Collection of answers.</param>
|
||||
Task<ResponseBase> DoSubmitMiniSurvey(IDictionary<string, string> answers);
|
||||
|
||||
/// <summary> True if connector has access to copri server, false if cached values are used. </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary> Gets the session cookie if user is logged in, an empty string otherwise. </summary>
|
||||
string SessionCookie { get; }
|
||||
|
||||
/// <summary> Holds the id of the merchant. </summary>
|
||||
string MerchantId { get; }
|
||||
}
|
||||
|
||||
/// <summary> Interface to communicate with copri server.</summary>
|
||||
public interface ICopriServer : ICopriServerBase
|
||||
{
|
||||
/// <summary> Get list of stations. </summary>
|
||||
/// <returns>List of all stations.</returns>
|
||||
Task<StationsAvailableResponse> GetStationsAsync();
|
||||
|
||||
/// <summary> Gets a list of bikes from Copri. </summary>
|
||||
/// <param name="operatorUri">Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host.</param>
|
||||
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
/// <param name="bikeId"> Id of bike which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
Task<BikesAvailableResponse> GetBikesAvailableAsync(Uri operatorUri = null, string stationId = null, string bikeId = null);
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync();
|
||||
}
|
||||
}
|
177
SharedBusinessLogic/Repository/Request/IRequestBuilder.cs
Normal file
177
SharedBusinessLogic/Repository/Request/IRequestBuilder.cs
Normal file
|
@ -0,0 +1,177 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
|
||||
namespace ShareeBike.Repository.Request
|
||||
{
|
||||
/// <summary> Defines members to create requests.</summary>
|
||||
public interface IRequestBuilder
|
||||
{
|
||||
/// <summary> Holds the id denoting the merchant (ShareeBike app). </summary>
|
||||
string MerchantId { get; }
|
||||
|
||||
/// <summary> Gets the session cookie if user is logged in, an empty string otherwise. </summary>
|
||||
string SessionCookie { get; }
|
||||
|
||||
/// <summary> Gets request to log user in. </summary>
|
||||
/// <param name="mailAddress">Mail address of user to log in.</param>
|
||||
/// <param name="password">Password to log in.</param>
|
||||
/// <param name="deviceId">Id specifying user and hardware.</param>
|
||||
/// <remarks>Request which holds auth cookie <see cref="RequstBase.authcookie"/></remarks>
|
||||
string DoAuthorization(
|
||||
string mailAddress,
|
||||
string password,
|
||||
string deviceId);
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
/// <param name="merchantId">Id of the merchant.</param>
|
||||
/// <param name="p_strSessionCookie"> Cookie which identifies user.</param>
|
||||
string DoAuthout();
|
||||
|
||||
/// <summary> Get list of stations from file. </summary>
|
||||
/// <returns>Request to query list of station.</returns>
|
||||
string GetStations();
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
/// <param name="bikeId"> Id of bike to get.</param>
|
||||
/// <returns>Request to query list of bikes available.</returns>
|
||||
string GetBikesAvailable(string stationId = null, string bikeId = null);
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
|
||||
/// <returns>Request to query list of bikes occupied.</returns>
|
||||
string GetBikesOccupied();
|
||||
|
||||
/// <summary> Gets reservation request (synonym: reservation == request == reservieren). </summary>
|
||||
/// <param name="bikeId">Id of the bike to reserve.</param>
|
||||
/// <returns>Request to reserve bike.</returns>
|
||||
string DoReserve(string bikeId);
|
||||
|
||||
/// <summary> Gets request to cancel reservation. </summary>
|
||||
/// <param name="bikeId">Id of the bike to cancel reservation for.</param>
|
||||
/// <returns>Request on cancel booking request.</returns>
|
||||
string DoCancelReservation(string bikeId);
|
||||
|
||||
/// <summary> Request to get keys. </summary>
|
||||
/// <param name="bikeId">Id of the bike to get keys for.</param>
|
||||
/// <returns>Request to get keys.</returns>
|
||||
string CalculateAuthParameters(string bikeId);
|
||||
|
||||
/// <summary> Gets the request for notifying about start of returning sequence. </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <returns>Request to notify about start of returning sequence.</returns>
|
||||
string StartReturningBike(string bikeId);
|
||||
|
||||
/// <summary> Gets the request for updating lock state for a booked bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to update locking state for.</param>
|
||||
/// <param name="state">New locking state.</param>
|
||||
/// <param name="location">Geolocation of lock when state change occurred.</param>
|
||||
/// <param name="versionInfo">Information about lock (firmware version, hardware version, ...).</param>
|
||||
/// <returns>Request to update locking state.</returns>
|
||||
string UpdateLockingState(
|
||||
string bikeId,
|
||||
lock_state state,
|
||||
LocationDto location = null,
|
||||
double batteryPercentage = double.NaN,
|
||||
IVersionInfo versionInfo = null);
|
||||
|
||||
/// <summary> Gets the booking request (synonym: booking == renting == mieten). </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="guid">Used to publish GUID from app to copri. Used for initial setup of bike in copri.</param>
|
||||
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
|
||||
/// <param name="nextAction">If not null next locking action which is performed after booking.</param>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
string DoBook(string bikeId, Guid guid, double batteryPercentage, LockingAction? nextAction = null);
|
||||
|
||||
/// <summary> Gets the request to book and start opening the bike (synonym: booking == renting == mieten). </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
string BookAvailableAndStartOpening(string bikeId);
|
||||
|
||||
/// <summary> Gets the request to book and start opening the bike (synonym: booking == renting == mieten). </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
string BookReservedAndStartOpening(string bikeId);
|
||||
|
||||
/// <summary> Gets request for returning the bike. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="location">Geolocation of lock when returning bike.</param>
|
||||
/// <returns>Request on returning request.</returns>
|
||||
string DoReturn(string bikeId, LocationDto location);
|
||||
|
||||
/// <summary> Returns a bike and starts closing. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="smartDevice">Provides info about hard and software.</param>
|
||||
/// <returns>Response to send to copri.</returns>
|
||||
string ReturnAndStartClosing(string bikeId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets request for submitting feedback to copri server.
|
||||
/// </summary>
|
||||
/// <param name="bikeId">Id of the bike to which the feedback is related to.</param>
|
||||
/// <param name="currentChargeBars">Null if bike has no engine or charge is unknown. Otherwise the charge filling level of the drive battery.</param>
|
||||
/// <param name="message">General purpose message or error description.</param>
|
||||
/// <param name="isBikeBroken">True if bike is broken.</param>
|
||||
string DoSubmitFeedback(
|
||||
string bikeId,
|
||||
int? currentChargeBars,
|
||||
string message = null,
|
||||
bool isBikeBroken = false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets request for submitting mini survey to copri server.
|
||||
/// </summary>
|
||||
/// <param name="answers">Collection of answers.</param>
|
||||
string DoSubmitMiniSurvey(IDictionary<string, string> answers);
|
||||
}
|
||||
|
||||
/// <summary> Copri locking states</summary>
|
||||
public enum lock_state
|
||||
{
|
||||
/// <summary> Request to backend to close lock in context of pausing ride.</summary>
|
||||
locking,
|
||||
|
||||
/// <summary> Lock is closed.</summary>
|
||||
locked,
|
||||
|
||||
/// <summary> Request to backend to close lock either in context of resuming ride or starting a rental.</summary>
|
||||
unlocking,
|
||||
|
||||
/// <summary> Lock is open.</summary>
|
||||
unlocked,
|
||||
|
||||
/// <summary> Lock is unknown state.</summary>
|
||||
unspecific,
|
||||
}
|
||||
|
||||
/// <summary> Holds location info.</summary>
|
||||
public class LocationDto
|
||||
{
|
||||
public double Latitude { get; private set; }
|
||||
|
||||
public double Longitude { get; private set; }
|
||||
|
||||
/// <summary> Accuracy of location in meters.</summary>
|
||||
public double? Accuracy { get; private set; }
|
||||
|
||||
public TimeSpan Age { get; private set; }
|
||||
|
||||
public class Builder
|
||||
{
|
||||
public double Latitude { get; set; }
|
||||
|
||||
public double Longitude { get; set; }
|
||||
|
||||
public double? Accuracy { get; set; }
|
||||
|
||||
public TimeSpan Age { get; set; }
|
||||
|
||||
public LocationDto Build()
|
||||
{
|
||||
return new LocationDto { Latitude = Latitude, Longitude = Longitude, Accuracy = Accuracy, Age = Age };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
SharedBusinessLogic/Repository/Request/QueryBuilderHelper.cs
Normal file
28
SharedBusinessLogic/Repository/Request/QueryBuilderHelper.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
namespace ShareeBike.Repository.Request
|
||||
{
|
||||
public static class QueryBuilderHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the htlm query string element for session id.
|
||||
/// </summary>
|
||||
/// <returns>Query string.</returns>
|
||||
public static string GetSessionIdQueryElement(
|
||||
string op,
|
||||
string merchantId,
|
||||
string sessionCookie = null)
|
||||
=> !string.IsNullOrEmpty(merchantId) || !string.IsNullOrEmpty(sessionCookie)
|
||||
? $"{op}sessionid={(!string.IsNullOrEmpty(sessionCookie) ? sessionCookie : string.Empty)}{(!string.IsNullOrEmpty(merchantId) ? merchantId : string.Empty)}"
|
||||
: string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the htlm query string element for the language.
|
||||
/// </summary>
|
||||
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
|
||||
/// <returns>Query string.</returns>
|
||||
public static string GetLanguageQueryElement(string op, string uiIsoLangugageName)
|
||||
=> !string.IsNullOrEmpty(uiIsoLangugageName)
|
||||
? $"{op}lang={uiIsoLangugageName}"
|
||||
: string.Empty;
|
||||
}
|
||||
}
|
165
SharedBusinessLogic/Repository/Request/RequestBuilder.cs
Normal file
165
SharedBusinessLogic/Repository/Request/RequestBuilder.cs
Normal file
|
@ -0,0 +1,165 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Repository.Exception;
|
||||
|
||||
namespace ShareeBike.Repository.Request
|
||||
{
|
||||
/// <summary> Creates requests if no user is logged in.</summary>
|
||||
public class RequestBuilder : IRequestBuilder
|
||||
{
|
||||
/// <summary> Constructs a object for building requests. </summary>
|
||||
/// <param name="merchantId">Holds the id denoting the merchant.</param>
|
||||
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
|
||||
/// <param name="smartDevice">Holds info about smart device.</param>
|
||||
public RequestBuilder(
|
||||
string merchantId,
|
||||
string uiIsoLangugageName,
|
||||
ISmartDevice smartDevice = null)
|
||||
{
|
||||
MerchantId = !string.IsNullOrEmpty(merchantId)
|
||||
? merchantId
|
||||
: throw new ArgumentException("Merchant id must not be null.", nameof(merchantId));
|
||||
|
||||
UiIsoLanguageNameParameter = RequestBuilderHelper.GetLanguageParameter(WebUtility.UrlEncode(uiIsoLangugageName));
|
||||
|
||||
AuthCookieParameter = $"&authcookie={WebUtility.UrlEncode(MerchantId)}";
|
||||
|
||||
SmartDevice = smartDevice;
|
||||
}
|
||||
|
||||
/// <summary>Holds the id denoting the merchant.</summary>
|
||||
public string MerchantId { get; }
|
||||
|
||||
/// <summary> Holds the session cookie if a user is logged in. </summary>
|
||||
public string SessionCookie => string.Empty;
|
||||
|
||||
/// <summary> Holds the current ui two letter ISO language name. </summary>
|
||||
private string UiIsoLanguageNameParameter { get; }
|
||||
|
||||
/// <summary> Auth cookie parameter. </summary>
|
||||
private string AuthCookieParameter { get; }
|
||||
|
||||
/// <summary>Holds info about smart device.</summary>
|
||||
private ISmartDevice SmartDevice { get; }
|
||||
|
||||
/// <summary> Gets request to log user in. </summary>
|
||||
/// <param name="mailAddress">Mailaddress of user to log in.</param>
|
||||
/// <param name="password">Password to log in.</param>
|
||||
/// <param name="deviceId">Id specifying user and hardware.</param>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
public string DoAuthorization(
|
||||
string mailAddress,
|
||||
string password,
|
||||
string deviceId)
|
||||
=> "request=authorization" +
|
||||
$"&merchant_id={MerchantId}" +
|
||||
$"&user_id={WebUtility.UrlEncode(mailAddress)}" +
|
||||
$"&user_pw={WebUtility.UrlEncode(password)}" +
|
||||
$"&hw_id={WebUtility.UrlEncode(deviceId)}" +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
public string DoAuthout()
|
||||
=> throw new CallNotRequiredException();
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
/// <param name="bikeId"> Id of bike to get.</param>
|
||||
/// <returns>Request to query list of bikes available.</returns>
|
||||
public string GetBikesAvailable(string stationId = null, string bikeId = null)
|
||||
=> "request=bikes_available&system=all" +
|
||||
stationId.GetStationId() +
|
||||
bikeId.GetBikeId() +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Get list of stations from file. </summary>
|
||||
/// <returns>Request to query list of station.</returns>
|
||||
public string GetStations()
|
||||
=> "request=stations_available" +
|
||||
AuthCookieParameter +
|
||||
SmartDevice.GetSmartDeviceParameters() +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
|
||||
/// <returns>Request to query list of bikes occupied.</returns>
|
||||
public string GetBikesOccupied()
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary> Gets booking request response. </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public string DoReserve(string bikeId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary> Gets cancel booking request response. </summary>
|
||||
/// <param name="p_iBikeId">Id of the bike to book.</param>
|
||||
/// <returns>Response on cancel booking request.</returns>
|
||||
public string DoCancelReservation(string p_iBikeId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary> Request to calculate authentication keys. </summary>
|
||||
/// <param name="bikeId">Id of the bike to get keys for.</param>
|
||||
/// <returns>Response on request.</returns>
|
||||
public string CalculateAuthParameters(string bikeId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary> Gets the request for notifying about start of returning sequence. </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <returns>Request to notify about start of returning sequence.</returns>
|
||||
public string StartReturningBike(string bikeId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary>
|
||||
/// Not supported if user is not logged in. Lock state is only updated after open/ close which is only possible if user is logged in.
|
||||
/// </summary>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
public string UpdateLockingState(string bikeId, lock_state state, LocationDto geolocation, double batteryPercentage, IVersionInfo versionInfo)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public string DoBook(string bikeId, Guid guid, double batteryPercentage, LockingAction? nextAction = null)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary> Gets the request to book and start opening the bike (synonym: booking == renting == mieten). </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
public string BookAvailableAndStartOpening(string bikeId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary> Gets the request to book and start opening the bike (synonym: booking == renting == mieten). </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
public string BookReservedAndStartOpening(string bikeId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public string DoReturn(string bikeId, LocationDto geolocation)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary> Returns a bike and starts closing. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="smartDevice">Provides info about hard and software.</param>
|
||||
/// <returns>Response to send to copri.</returns>
|
||||
public string ReturnAndStartClosing(string bikeId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary> Gets submit feedback request. </summary>
|
||||
/// <param name="bikeId">Id of the bike to which the feedback is related to.</param>
|
||||
/// <param name="message">General purpose message or error description.</param>
|
||||
/// <param name="isBikeBroken">True if bike is broken.</param>
|
||||
/// <returns>Submit feedback request.</returns>
|
||||
public string DoSubmitFeedback(string bikeId, int? currentChargeBars, string message = null, bool isBikeBroken = false)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <summary>
|
||||
/// Gets request for submitting mini survey to copri server.
|
||||
/// </summary>
|
||||
/// <param name="answers">Collection of answers.</param>
|
||||
public string DoSubmitMiniSurvey(IDictionary<string, string> answers) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
|
||||
using System.Net;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
|
||||
namespace ShareeBike.Repository.Request
|
||||
{
|
||||
public static class RequestBuilderHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the REST language parameter.
|
||||
/// </summary>
|
||||
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
|
||||
/// <returns></returns>
|
||||
public static string GetLanguageParameter(string uiIsoLangugageName)
|
||||
=> !string.IsNullOrEmpty(uiIsoLangugageName)
|
||||
? $"&lang={uiIsoLangugageName}"
|
||||
: string.Empty;
|
||||
|
||||
public static lock_state? GetLockState(this LockingAction? action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case LockingAction.Open:
|
||||
return lock_state.unlocking;
|
||||
|
||||
case LockingAction.Close:
|
||||
return lock_state.locking;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets the smart device parameters. </summary>
|
||||
/// <returns>in a format which is url encode invariant.</returns>
|
||||
public static string GetSmartDeviceParameters(this ISmartDevice smartDevice)
|
||||
=> smartDevice != null
|
||||
? $"{(!string.IsNullOrEmpty(smartDevice.Manufacturer) ? $"&user_device_manufacturer={WebUtility.UrlEncode(smartDevice.Manufacturer)}" : string.Empty)}" +
|
||||
$"{(!string.IsNullOrEmpty(smartDevice.Model) ? $"&user_device_model={WebUtility.UrlEncode(smartDevice.Model)}" : string.Empty)}" +
|
||||
$"{(!string.IsNullOrEmpty(smartDevice.Platform.ToString()) ? $"&user_device_platform={WebUtility.UrlEncode(smartDevice.Platform.ToString())}" : string.Empty)}" +
|
||||
$"{(!string.IsNullOrEmpty(smartDevice.VersionText) ? $"&user_device_version={WebUtility.UrlEncode(smartDevice.VersionText)}" : string.Empty)}" +
|
||||
$"{(!string.IsNullOrEmpty(smartDevice.Identifier) ? $"&user_device_id={WebUtility.UrlEncode(smartDevice.Identifier)}" : string.Empty)}"
|
||||
: string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Converts from one locking state enum to another.
|
||||
/// </summary>
|
||||
/// <param name="state">Locking state to convert.</param>
|
||||
/// <returns>Target state.</returns>
|
||||
public static lock_state? GetLockState(this Model.Bikes.BikeInfoNS.BluetoothLock.LockingState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Model.Bikes.BikeInfoNS.BluetoothLock.LockingState.Open:
|
||||
return lock_state.unlocked;
|
||||
|
||||
case Model.Bikes.BikeInfoNS.BluetoothLock.LockingState.Closed:
|
||||
return lock_state.locked;
|
||||
|
||||
case Model.Bikes.BikeInfoNS.BluetoothLock.LockingState.UnknownFromHardwareError:
|
||||
return lock_state.unspecific;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets the station id filter. </summary>
|
||||
/// <returns>Station id filter.</returns>
|
||||
public static string GetStationId(this string stationId)
|
||||
=> !string.IsNullOrEmpty(stationId)
|
||||
? $"&station={WebUtility.UrlEncode(stationId)}"
|
||||
: string.Empty;
|
||||
|
||||
/// <summary> Gets the bike id filter. </summary>
|
||||
/// <returns>Bike id filter.</returns>
|
||||
public static string GetBikeId(this string bikeId)
|
||||
=> !string.IsNullOrEmpty(bikeId)
|
||||
? $"&bike={WebUtility.UrlEncode(bikeId)}"
|
||||
: string.Empty;
|
||||
}
|
||||
}
|
344
SharedBusinessLogic/Repository/Request/RequestBuilderLoggedIn.cs
Normal file
344
SharedBusinessLogic/Repository/Request/RequestBuilderLoggedIn.cs
Normal file
|
@ -0,0 +1,344 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Device;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Services.Logging;
|
||||
|
||||
namespace ShareeBike.Repository.Request
|
||||
{
|
||||
/// <summary> Creates requests if a user is logged in.</summary>
|
||||
public class RequestBuilderLoggedIn : IRequestBuilder
|
||||
{
|
||||
/// <summary> Constructs a object for building requests. </summary>
|
||||
/// <param name="merchantId">Holds the id denoting the merchant.</param>
|
||||
/// <param name="uiIsoLangugageName">Two letter ISO language name.</param>
|
||||
/// <param name="smartDevice">Holds info about smart device.</param>
|
||||
public RequestBuilderLoggedIn(
|
||||
string merchantId,
|
||||
string uiIsoLangugageName,
|
||||
string sessionCookie,
|
||||
ISmartDevice smartDevice = null)
|
||||
{
|
||||
MerchantId = !string.IsNullOrEmpty(merchantId)
|
||||
? merchantId
|
||||
: throw new ArgumentException("Merchant id must not be null.", nameof(merchantId));
|
||||
|
||||
SessionCookie = !string.IsNullOrEmpty(sessionCookie)
|
||||
? sessionCookie
|
||||
: throw new ArgumentException("Session cookie must not be null.", nameof(sessionCookie));
|
||||
|
||||
UiIsoLanguageNameParameter = RequestBuilderHelper.GetLanguageParameter(WebUtility.UrlEncode(uiIsoLangugageName));
|
||||
|
||||
AuthCookieParameter = $"&authcookie={WebUtility.UrlEncode(SessionCookie)}{WebUtility.UrlEncode(MerchantId)}";
|
||||
|
||||
SmartDevice = smartDevice;
|
||||
}
|
||||
|
||||
/// <summary> Holds the id denoting the merchant. </summary>
|
||||
public string MerchantId { get; }
|
||||
|
||||
/// <summary> Holds the session cookie if a user is logged in. </summary>
|
||||
public string SessionCookie { get; }
|
||||
|
||||
/// <summary> Holds the current ui two letter ISO language name. </summary>
|
||||
public string UiIsoLanguageNameParameter { get; }
|
||||
|
||||
/// <summary> Auth cookie parameter. </summary>
|
||||
private string AuthCookieParameter { get; }
|
||||
|
||||
/// <summary>Holds info about smart device.</summary>
|
||||
private ISmartDevice SmartDevice { get; }
|
||||
|
||||
/// <summary> Gets request to log user in. </summary>
|
||||
/// <param name="mailAddress">Mail address of user to log in.</param>
|
||||
/// <param name="password">Password to log in.</param>
|
||||
/// <param name="deviceId">Id specifying user and hardware.</param>
|
||||
/// <remarks>Response which holds auth cookie <see cref="ResponseBase.authcookie"/></remarks>
|
||||
public string DoAuthorization(
|
||||
string mailAddress,
|
||||
string password,
|
||||
string deviceId)
|
||||
=> throw new CallNotRequiredException();
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
public string DoAuthout()
|
||||
=> "request=authout" +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <param name="stationId"> Id of station which is used for filtering bikes. Null if no filtering should be applied.</param>
|
||||
/// <param name="bikeId"> Id of bike to get.</param>
|
||||
/// <returns>Request to query list of bikes available.</returns>
|
||||
public string GetBikesAvailable(string stationId = null, string bikeId = null)
|
||||
=> "request=bikes_available&system=all" +
|
||||
stationId.GetStationId() +
|
||||
bikeId.GetBikeId() +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by active user from Copri.</summary>
|
||||
/// <returns>Request to query list of bikes occupied.</returns>
|
||||
public string GetBikesOccupied()
|
||||
=> "request=user_bikes_occupied&system=all&genkey=1" +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Get list of stations from file. </summary>
|
||||
/// <returns>Request to query list of station.</returns>
|
||||
public string GetStations()
|
||||
=> "request=stations_available" +
|
||||
AuthCookieParameter +
|
||||
SmartDevice.GetSmartDeviceParameters() +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets reservation request (synonym: reservation == request == reservieren). </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to reserve.</param>
|
||||
/// <returns>Request to reserve bike.</returns>
|
||||
public string DoReserve(string bikeId)
|
||||
=> "request=booking_request" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
AuthCookieParameter +
|
||||
SmartDevice.GetSmartDeviceParameters() +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets request to cancel reservation. </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to cancel reservation for.</param>
|
||||
/// <returns>Request on cancel booking request.</returns>
|
||||
public string DoCancelReservation(string bikeId)
|
||||
=> "request=booking_cancel" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Request to get keys. </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to get keys for.</param>
|
||||
/// <returns>Request to get keys.</returns>
|
||||
public string CalculateAuthParameters(string bikeId)
|
||||
=> "request=booking_update" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
AuthCookieParameter +
|
||||
"&genkey=1" +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets the request for notifying about start of returning sequence. </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <returns>Request to notify about start of returning sequence.</returns>
|
||||
public string StartReturningBike(string bikeId)
|
||||
=> "request=booking_update" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
GetLockStateParameter(lock_state.locking) +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets the request for updating lock state for a booked bike. </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to update locking state for.</param>
|
||||
/// <param name="state">New locking state.</param>
|
||||
/// <param name="versionInfo">Information about lock (firmware version, hardware version, ...).</param>
|
||||
/// <returns>Request to update locking state.</returns>
|
||||
public string UpdateLockingState(
|
||||
string bikeId,
|
||||
lock_state state,
|
||||
LocationDto geolocation,
|
||||
double batteryPercentage,
|
||||
IVersionInfo versionInfo)
|
||||
=> "request=booking_update" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
GetLocationParameters(geolocation) +
|
||||
GetLockStateParameter(state) +
|
||||
GetBatteryPercentageParameters(batteryPercentage) +
|
||||
GetVersionInfoParameter(versionInfo) +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
|
||||
/// <summary> Gets booking request (synonym: booking == renting == mieten). </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <param name="guid">Used to publish GUID from app to copri. Used for initial setup of bike in copri.</param>
|
||||
/// <param name="batteryPercentage">Holds the filling level percentage of the battery.</param>
|
||||
/// <param name="nextAction">If not null next locking action which is performed after booking.</param>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
public string DoBook(string bikeId, Guid guid, double batteryPercentage, LockingAction? nextAction = null)
|
||||
=> "request=booking_update" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
AuthCookieParameter +
|
||||
$"&Ilockit_GUID={guid}" +
|
||||
"&state=occupied" +
|
||||
GetLockStateParameter(nextAction.GetLockState()) +
|
||||
GetBatteryPercentageParameters(batteryPercentage) +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets the request to book and start opening the bike (synonym: booking == renting == mieten). </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
public string BookAvailableAndStartOpening(string bikeId)
|
||||
=> "request=booking_request" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
AuthCookieParameter +
|
||||
"&state=occupied" +
|
||||
GetLockStateParameter(lock_state.unlocking) +
|
||||
SmartDevice.GetSmartDeviceParameters() +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets the request to book and start opening the bike (synonym: booking == renting == mieten). </summary>
|
||||
/// <param name="bikeId">Id of the bike to book.</param>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
public string BookReservedAndStartOpening(string bikeId)
|
||||
=> "request=booking_update" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
AuthCookieParameter +
|
||||
"&state=occupied" +
|
||||
GetLockStateParameter(lock_state.unlocking) +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets request for returning the bike. </summary>
|
||||
/// <remarks> Operator specific call.</remarks>
|
||||
/// <param name="bikeId">Id of bike to return.</param>
|
||||
/// <param name="geolocation">Geolocation of lock when returning bike.</param>
|
||||
/// <returns>Request on returning request.</returns>
|
||||
public string DoReturn(string bikeId, LocationDto geolocation)
|
||||
=> "request=booking_update" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
AuthCookieParameter +
|
||||
"&state=available" +
|
||||
GetLocationParameters(geolocation) +
|
||||
GetLockStateParameter(lock_state.locked) +
|
||||
SmartDevice.GetSmartDeviceParameters() +
|
||||
GetLog() +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
|
||||
/// <summary> Returns a bike and starts closing. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="smartDevice">Provides info about hard and software.</param>
|
||||
/// <returns>Response to send to copri.</returns>
|
||||
public string ReturnAndStartClosing(string bikeId)
|
||||
=> "request=booking_update" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
AuthCookieParameter +
|
||||
"&state=available" +
|
||||
GetLockStateParameter(lock_state.locking) +
|
||||
SmartDevice.GetSmartDeviceParameters() +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
/// <summary> Gets submit feedback request. </summary>
|
||||
/// <param name="bikeId">Id of the bike to return.</param>
|
||||
/// <param name="message">General purpose message or error description.</param>
|
||||
/// <param name="isBikeBroken">True if bike is broken.</param>
|
||||
/// <returns>Submit feedback request.</returns>
|
||||
public string DoSubmitFeedback(
|
||||
string bikeId,
|
||||
int? currentChargeBars = null,
|
||||
string message = null,
|
||||
bool isBikeBroken = false)
|
||||
{
|
||||
string GetIsBikeBroken()
|
||||
=> isBikeBroken ? "&bike_broken=1" : string.Empty;
|
||||
|
||||
string GetMessage()
|
||||
=> !string.IsNullOrEmpty(message) ? $"&message={WebUtility.UrlEncode(message)}" : string.Empty;
|
||||
|
||||
string GetCurrentChargeBars()
|
||||
=> currentChargeBars != null ? $"&charge_current_bars={currentChargeBars.Value}" : string.Empty;
|
||||
|
||||
return "request=user_feedback" +
|
||||
GetBikeIdParameter(bikeId) +
|
||||
GetCurrentChargeBars() +
|
||||
GetIsBikeBroken() +
|
||||
GetMessage() +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets request for submitting mini survey to copri server.
|
||||
/// </summary>
|
||||
/// <param name="answers">Collection of answers.</param>
|
||||
public string DoSubmitMiniSurvey(IDictionary<string, string> answers)
|
||||
{
|
||||
// Remove entires which invalid keys or values.
|
||||
var validAnsers = answers?.Where(x => !string.IsNullOrEmpty(x.Key?.Trim()) && !string.IsNullOrEmpty(x.Value?.Trim()));
|
||||
|
||||
// Create quersy
|
||||
if (validAnsers == null || validAnsers.Count() <= 0)
|
||||
return "request=user_minianswer" +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
|
||||
return "request=user_minianswer" +
|
||||
$"&{string.Join("&", validAnsers.Select(x => $"{x.Key}={WebUtility.UrlEncode(x.Value)}"))}" +
|
||||
AuthCookieParameter +
|
||||
UiIsoLanguageNameParameter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets bike id parameter.
|
||||
/// </summary>
|
||||
/// <param name="bikeId">Id of bike.</param>
|
||||
/// <returns>bike id parameter in a format which is urlencode invariant.</returns>
|
||||
private static string GetBikeIdParameter(string bikeId)
|
||||
=> $"&bike={WebUtility.UrlEncode(bikeId)}";
|
||||
|
||||
/// <summary>
|
||||
/// Gets parameter holding lock state or null if state is unknown.
|
||||
/// </summary>
|
||||
/// <param name="lockState">Null or state of lock.</param>
|
||||
/// <returns>Lock state in a format which is urlencode invariant.</returns>
|
||||
private static string GetLockStateParameter(lock_state? lockState)
|
||||
=> lockState.HasValue ? $"&lock_state={lockState}" : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the battery level percentage parameter if percentage is not NaN an empty string otherwise.
|
||||
/// </summary>
|
||||
/// <returns>Gets battery percentage parameters in a format which is urlencode invariant or an empty string if percentage percentge is NaN.</returns>
|
||||
private static string GetBatteryPercentageParameters(double batteryPercentage) => !double.IsNaN(batteryPercentage)
|
||||
? $"&voltage={batteryPercentage.ToString(CultureInfo.InvariantCulture)}"
|
||||
: string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version info parameter or an empty string if version info is not available.
|
||||
/// </summary>
|
||||
/// <param name="versionInfo">Lock version info.</param>
|
||||
/// <returns>Version info in a format which is urlencode invariant or empty. </returns>
|
||||
private static string GetVersionInfoParameter(IVersionInfo versionInfo) => versionInfo?.FirmwareVersion > 0 || versionInfo?.HardwareVersion > 0 || versionInfo?.LockVersion > 0
|
||||
? $"&firmware=HW%20{versionInfo.HardwareVersion}%3BFW%20{versionInfo.FirmwareVersion}%3BLock%20{versionInfo.LockVersion}"
|
||||
: string.Empty;
|
||||
|
||||
/// <summary> Gets the geolocation parameter. </summary>
|
||||
/// <param name="geolocation">Geolocation or null.</param>
|
||||
/// <returns>Empty string if geolocation is null otherwise parameter including latitude, longitude and age in a format which is urlencode invariant.</returns>
|
||||
private static string GetLocationParameters(LocationDto geolocation)
|
||||
{
|
||||
if (geolocation == null)
|
||||
return string.Empty;
|
||||
|
||||
if (geolocation.Accuracy == null)
|
||||
return $"&gps={geolocation.Latitude.ToString(CultureInfo.InvariantCulture)},{geolocation.Longitude.ToString(CultureInfo.InvariantCulture)}&gps_age={geolocation.Age.TotalSeconds}";
|
||||
|
||||
return $"&gps={geolocation.Latitude.ToString(CultureInfo.InvariantCulture)},{geolocation.Longitude.ToString(CultureInfo.InvariantCulture)}&gps_accuracy={geolocation.Accuracy.Value.ToString(CultureInfo.InvariantCulture)}&gps_age={geolocation.Age.TotalSeconds}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets logging entries from serilog.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static string GetLog()
|
||||
{
|
||||
var messages = string.Join("\n", MemoryStackSink.PopAllMessages());
|
||||
return !string.IsNullOrEmpty(messages)
|
||||
? "&app_debug=" + WebUtility.UrlEncode(string.Join("\n", messages))
|
||||
: string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class AuthorizationResponse : ResponseBase
|
||||
{
|
||||
[DataMember]
|
||||
public int debuglevel { get; private set; }
|
||||
|
||||
/// <summary> Holds the group of the bike (ShareeBike, Citybike, ...).</summary>
|
||||
[DataMember]
|
||||
public string[] user_group { get; private set; }
|
||||
|
||||
/// <summary> Holds value of 0 if agb were not acknowledged.</summary>
|
||||
[DataMember]
|
||||
public string agb_checked { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class AuthorizationoutResponse : ResponseBase
|
||||
{
|
||||
}
|
||||
}
|
31
SharedBusinessLogic/Repository/Response/BikeInfoAvailable.cs
Normal file
31
SharedBusinessLogic/Repository/Response/BikeInfoAvailable.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class BikeInfoAvailable : BikeInfoBase, IEquatable<BikeInfoAvailable>
|
||||
{
|
||||
/// <summary>Mini survey for bikes which were rented before and for which feedback is pending.</summary>
|
||||
[DataMember]
|
||||
public MiniSurveyResponse user_miniquery { get; private set; }
|
||||
|
||||
/// <summary> Information about Co2- saving for bikes which were rented before and for which feedback is pending.</summary>
|
||||
[DataMember]
|
||||
public string co2saving { get; private set; }
|
||||
|
||||
public static bool operator ==(BikeInfoAvailable first, BikeInfoAvailable second)
|
||||
=> JsonConvert.SerializeObject(first) == JsonConvert.SerializeObject(second);
|
||||
|
||||
public static bool operator !=(BikeInfoAvailable first, BikeInfoAvailable second)
|
||||
=> !(first == second);
|
||||
|
||||
public override bool Equals(object obj) => obj is BikeInfoAvailable target && target == this;
|
||||
|
||||
public override int GetHashCode() => JsonConvert.SerializeObject(this).GetHashCode();
|
||||
|
||||
public bool Equals(BikeInfoAvailable other) => other == this;
|
||||
}
|
||||
}
|
113
SharedBusinessLogic/Repository/Response/BikeInfoBase.cs
Normal file
113
SharedBusinessLogic/Repository/Response/BikeInfoBase.cs
Normal file
|
@ -0,0 +1,113 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds info about a single bike.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class BikeInfoBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string bike { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Position of the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Position gps { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id of the station.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string station { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the localized (German) description of the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string description { get; private set; }
|
||||
|
||||
/// <summary> Holds the group of the bike.</summary>
|
||||
/// <remarks>
|
||||
/// Copri returns values "ShareeBike", "Citybike".
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public string[] bike_group { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rental state.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string state { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the uri where to reserve/ rent the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string uri_operator { get; private set; }
|
||||
|
||||
/// <summary> Holds whether bike is equipped with computer or if bike is a lock bike.</summary>
|
||||
/// <remarks>
|
||||
/// <table>
|
||||
/// <tr><th>Value </th><th>Type of bike </th><th>Member to extract info.</th></tr>
|
||||
/// <tr><td>LOCK </td><td>Bike with manualHtml lock. </td><td>TextToTypeHelper.GetIsNonBikeComputerBike</td></tr>
|
||||
/// <tr><td>BC </td><td>Bike with a bord computer. </td><td></td></tr>
|
||||
/// <tr><td>Ilockit </td><td>Bike with a bluetooth lock.</td><td></td></tr>
|
||||
/// <tr><td>sigo </td><td>Sigo bike.</td><td></td></tr>
|
||||
/// </table>
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public string system { get; private set; }
|
||||
|
||||
/// <summary> Holds the tariff information for a bike. </summary>
|
||||
/// <remarks> This member is obsolete. Use <cref="rental_description"> instead.</cref></remarks>
|
||||
[DataMember]
|
||||
public TariffDescription tariff_description { get; private set; }
|
||||
|
||||
/// <summary> Holds the rental information for a bike. </summary>
|
||||
[DataMember]
|
||||
public RentalDescription rental_description { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
/// <summary> Describes type of the bike.</summary>
|
||||
public BikeType bike_type { get; private set; }
|
||||
|
||||
/// <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>
|
||||
[DataMember]
|
||||
public string aa_ride { get; private set; }
|
||||
|
||||
/// <summary> Loading state of motor battery in % ]0..100[. </summary>
|
||||
[DataMember]
|
||||
public string bike_charge { get; private set; }
|
||||
|
||||
/// <summary> Locking state. </summary>
|
||||
[DataMember]
|
||||
public string lock_state { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
/// <summary> Full advertisement name.</summary>
|
||||
public string Ilockit_ID { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
/// <summary> Full advertisement name.</summary>
|
||||
public string Ilockit_GUID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Textual description of response.
|
||||
/// </summary>
|
||||
/// <returns>Object as text.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Bike {bike}{(station != null ? $", at station {station}" : string.Empty)}{(!string.IsNullOrEmpty(description) ? $", {description}" : string.Empty)}{(!string.IsNullOrEmpty(state) ? $", status={state}" : string.Empty)}.";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class BikeInfoReservedOrBooked : BikeInfoBase, IEquatable<BikeInfoReservedOrBooked>
|
||||
{
|
||||
/// <summary>
|
||||
/// Date from when bike was reserved from/ booked from.
|
||||
/// Format: 2017-11-28 11:01:51.637747+01
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string start_time { get; private set; }
|
||||
|
||||
/// <summary> Booking code if bike is BC-bike.</summary>
|
||||
[DataMember]
|
||||
public string timeCode { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
/// <summary> Seed used to generate key for connecting to bluetooth lock.</summary>
|
||||
public string K_seed { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
/// <summary> Key for connect to bluetooth lock as user.</summary>
|
||||
public string K_u { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
/// <summary> Key for connect to bluetooth lock as admin.</summary>
|
||||
public string K_a { get; private set; }
|
||||
|
||||
public static bool operator ==(BikeInfoReservedOrBooked first, BikeInfoReservedOrBooked second)
|
||||
=> JsonConvert.SerializeObject(first) == JsonConvert.SerializeObject(second);
|
||||
|
||||
public static bool operator !=(BikeInfoReservedOrBooked first, BikeInfoReservedOrBooked second)
|
||||
=> !(first == second);
|
||||
|
||||
public override bool Equals(object obj) => obj is BikeInfoReservedOrBooked target && target == this;
|
||||
|
||||
public override int GetHashCode() => JsonConvert.SerializeObject(this).GetHashCode();
|
||||
|
||||
public bool Equals(BikeInfoReservedOrBooked other) => other == this;
|
||||
}
|
||||
}
|
89
SharedBusinessLogic/Repository/Response/BikeType.cs
Normal file
89
SharedBusinessLogic/Repository/Response/BikeType.cs
Normal file
|
@ -0,0 +1,89 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds info about a single bike.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class BikeType
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the engine.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// Manufacturer: ...
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string manufacturer { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the engine.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class Battery
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the current charging level in bars.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string charge_current_bars { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the current charging level of the battery in percent.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string charge_current_percent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the maximum charging level of the battery in bars.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string charge_max_bars { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds whether backend is aware of battery charging level.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string backend_accessible { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds whether to display battery level or not.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string hidden { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Category of the bike. Possible entries: "city", "cargo", ...
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string category { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of wheels. There are trikes (3 wheels) and two wheeled bikes.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string wheels { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds engine information. .
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Engine engine { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds battery information .
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Battery battery { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information about all bikes and is used for deserialization of copri answer.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class BikesAvailableResponse : ResponseBase
|
||||
{
|
||||
public BikesAvailableResponse()
|
||||
{
|
||||
bikes = new ComparableBikeDictionary<BikeInfoAvailable> ();
|
||||
bikes_occupied = new ComparableBikeDictionary<BikeInfoReservedOrBooked> ();
|
||||
}
|
||||
|
||||
/// <summary> Dictionary of bikes available.</summary>
|
||||
[DataMember]
|
||||
public ComparableBikeDictionary<BikeInfoAvailable> bikes { get; private set; }
|
||||
|
||||
/// <summary> Dictionary of bikes reserved or booked.</summary>
|
||||
[DataMember]
|
||||
public ComparableBikeDictionary<BikeInfoReservedOrBooked> bikes_occupied { get; private set; }
|
||||
|
||||
public static bool operator== (BikesAvailableResponse first, BikesAvailableResponse second)
|
||||
=> JsonConvert.SerializeObject(first) == JsonConvert.SerializeObject(second);
|
||||
|
||||
public static bool operator !=(BikesAvailableResponse first, BikesAvailableResponse second)
|
||||
=> !(first == second);
|
||||
|
||||
public override bool Equals(object obj) => obj is BikesAvailableResponse target && target == this;
|
||||
|
||||
public override int GetHashCode() => JsonConvert.SerializeObject(this).GetHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information about a booking request (reserve, cancel reservation, book or cancel booking) and is used for deserialization of copri answer.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class BikesReservedOccupiedResponse : BookingActionResponse
|
||||
{
|
||||
public BikesReservedOccupiedResponse()
|
||||
{
|
||||
bikes_occupied = new ComparableBikeDictionary<BikeInfoReservedOrBooked> ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of bikes.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public ComparableBikeDictionary<BikeInfoReservedOrBooked> bikes_occupied { get; private set; }
|
||||
|
||||
public static bool operator ==(BikesReservedOccupiedResponse first, BikesReservedOccupiedResponse second)
|
||||
=> JsonConvert.SerializeObject(first) == JsonConvert.SerializeObject(second);
|
||||
|
||||
public static bool operator !=(BikesReservedOccupiedResponse first, BikesReservedOccupiedResponse second)
|
||||
=> !(first == second);
|
||||
|
||||
public override bool Equals(object obj) => obj is BikesReservedOccupiedResponse target && target == this;
|
||||
|
||||
public override int GetHashCode() => JsonConvert.SerializeObject(this).GetHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information about a booking request (reserve, cancel reservation, book or cancel booking) and is used for deserialization of copri answer.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class BookingActionResponse : ResponseBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of the bike which was target of the booking request.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string bike { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
public class ComparableBikeDictionary<S> : ComparableDictionary<S> where S : BikeInfoBase
|
||||
{
|
||||
|
||||
[OnDeserialized]
|
||||
internal void OnDeserializedMethod(StreamingContext context)
|
||||
{
|
||||
var toCorrectDictionary = this.Where( x =>
|
||||
!string.IsNullOrEmpty(x.Value.bike)
|
||||
&& x.Key != x.Value.bike).ToArray();
|
||||
|
||||
foreach (var element in toCorrectDictionary)
|
||||
{
|
||||
Remove(element.Key);
|
||||
if (ContainsKey(element.Value.bike))
|
||||
{
|
||||
// Remove duplicates.
|
||||
Remove(element.Value.bike);
|
||||
continue;
|
||||
}
|
||||
Add(element.Value.bike, element.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a bike from dictionary.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of dictionary values to remove.</typeparam>
|
||||
/// <param name="bikeId">If of bike to remove.</param>
|
||||
/// <returns>True if at least one bike was removed, false if no bike was removed.</returns>
|
||||
public bool RemoveByBikeId<T>(
|
||||
string bikeId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(bikeId))
|
||||
{
|
||||
// Nothing to do.
|
||||
return false;
|
||||
}
|
||||
|
||||
var bikeToRemove = this.FirstOrDefault(x => bikeId == x.Value.bike);
|
||||
if (string.IsNullOrEmpty(bikeToRemove.Key))
|
||||
{
|
||||
// Bike is not contained in dictionary.
|
||||
return false;
|
||||
}
|
||||
|
||||
return Remove(bikeToRemove.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bike by bike id from dictionary.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of dictionary values to remove.</typeparam>
|
||||
/// <param name="bikeId">If of bike to get.</param>
|
||||
/// <returns>True if at least one bike was removed, false if no bike was removed.</returns>
|
||||
public S GetByBikeId(
|
||||
string bikeId)
|
||||
=> this.FirstOrDefault(x => bikeId == x.Value.bike).Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds a dictionary of comparable elements.
|
||||
/// </summary>
|
||||
/// <typeparam name="S">Type of the element.</typeparam>
|
||||
[DataContract]
|
||||
public class ComparableDictionary<S> : Dictionary<string, S>, IEquatable<ComparableDictionary<S>>
|
||||
{
|
||||
public bool Equals(ComparableDictionary<S> other)
|
||||
{
|
||||
return this.OrderBy(x => x.Key).SequenceEqual(other.OrderBy(x => x.Key));
|
||||
}
|
||||
|
||||
public static bool operator ==(ComparableDictionary<S> first, ComparableDictionary<S> second)
|
||||
=> JsonConvert.SerializeObject(first) == JsonConvert.SerializeObject(second);
|
||||
|
||||
public static bool operator !=(ComparableDictionary<S> first, ComparableDictionary<S> second)
|
||||
=> !(first == second);
|
||||
|
||||
public override bool Equals(object obj) => obj is ComparableDictionary<S> target && target == this;
|
||||
|
||||
public override int GetHashCode() => JsonConvert.SerializeObject(this).GetHashCode();
|
||||
|
||||
}
|
||||
}
|
13
SharedBusinessLogic/Repository/Response/CopriVersion.cs
Normal file
13
SharedBusinessLogic/Repository/Response/CopriVersion.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class CopriVersion
|
||||
{
|
||||
[DataMember]
|
||||
public string copri_version { get; private set; }
|
||||
}
|
||||
}
|
77
SharedBusinessLogic/Repository/Response/DoReturnResponse.cs
Normal file
77
SharedBusinessLogic/Repository/Response/DoReturnResponse.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class DoReturnResponse : BikesReservedOccupiedResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds information about the returned bike.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class BikeReturned
|
||||
{
|
||||
/// <summary>
|
||||
/// Rental state.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string state { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id of the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string bike { get; private set; }
|
||||
|
||||
/// <summary> Locking state. </summary>
|
||||
[DataMember]
|
||||
public string lock_state { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id of the station.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string station { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Amount of Co2Saving.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string co2saving { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Driven Distance.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string distance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Duration of finished rental.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string real_clock { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Accruing costs for rental.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string total_price { get; private set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary> Mini survey.</summary>
|
||||
[DataMember]
|
||||
public MiniSurveyResponse user_miniquery { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string co2saving { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds information about the returned bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public BikeReturned bike_returned { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
public static class JsonConvertRethrow
|
||||
{
|
||||
/// <summary>
|
||||
/// Deserializes COPRI responses in a consitent way for entire app.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of object to serialize to.</typeparam>
|
||||
/// <param name="response">JSON to deserialize.</param>
|
||||
/// <returns>Deserialized object.</returns>
|
||||
public static T DeserializeObject<T>(string response)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(response);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error("Deserializing response failed. {@Exception}",ex);
|
||||
throw new DeserializationException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static string SerializeObject(object value) => Newtonsoft.Json.JsonConvert.SerializeObject(value);
|
||||
}
|
||||
}
|
19
SharedBusinessLogic/Repository/Response/MapSpan.cs
Normal file
19
SharedBusinessLogic/Repository/Response/MapSpan.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary> Holds information about map area to display.</summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class MapSpan
|
||||
{
|
||||
/// <summary> Center position of the map. </summary>
|
||||
[DataMember]
|
||||
public Position center { get; private set; }
|
||||
|
||||
/// <summary> Radius to the map area. </summary>
|
||||
[DataMember]
|
||||
public string radius { get; private set; }
|
||||
}
|
||||
}
|
34
SharedBusinessLogic/Repository/Response/MiniSurvey.cs
Normal file
34
SharedBusinessLogic/Repository/Response/MiniSurvey.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
public class MiniSurveyResponse
|
||||
{
|
||||
[DataContract]
|
||||
public class Question
|
||||
{
|
||||
[DataMember]
|
||||
public string quest_text { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string type { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public Dictionary<string, string> query { get; private set; }
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public string title { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string subtitle { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string footer { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public Dictionary<string, Question> questions { get; private set; }
|
||||
}
|
||||
}
|
19
SharedBusinessLogic/Repository/Response/Position.cs
Normal file
19
SharedBusinessLogic/Repository/Response/Position.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary> Holds position info. </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class Position
|
||||
{
|
||||
/// <summary> Latitude position (bike, station, map center...). </summary>
|
||||
[DataMember]
|
||||
public string latitude { get; private set; }
|
||||
|
||||
/// <summary> Longitude position (bike, station, map center...). </summary>
|
||||
[DataMember]
|
||||
public string longitude { get; private set; }
|
||||
}
|
||||
}
|
49
SharedBusinessLogic/Repository/Response/RentalDescription.cs
Normal file
49
SharedBusinessLogic/Repository/Response/RentalDescription.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Successor of TarifDescription- object.
|
||||
/// Manages tariff- and rental info.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class RentalDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the tariff.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id of the tariff.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string id { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the time span in minutes for which a bike can be reserved.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string reserve_timerange { get; private set; }
|
||||
|
||||
/// <summary> Holds tariff entires to show to user.</summary>
|
||||
[DataMember]
|
||||
public Dictionary<
|
||||
string /* Key of tariff object for sorting purposes*/,
|
||||
string[] /* Holds two Elements: first element is the description of the element (example: "Max Gebühr"), second is the value (example: "9.00 € / Tag")*/>
|
||||
tarif_elements
|
||||
{ get; private set; }
|
||||
|
||||
/// <summary> Holds tariff entires to show to user.</summary>
|
||||
[DataMember]
|
||||
public Dictionary<
|
||||
string /* Key of info object for sorting purposes*/,
|
||||
string[] /* Holds two Elements: first element is the key of the element (example: "Tracking"), second is the value (example: "Ich stimme der Speicherung (Tracking) meiner Fahrstrecke ....")*/>
|
||||
rental_info
|
||||
{ get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information about a booking request and is used for deserialization of copri answer.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class ReservationBookingResponse : BikesReservedOccupiedResponse
|
||||
{
|
||||
/// <summary> Booking code for BC- bikes. </summary>
|
||||
[DataMember]
|
||||
public string timeCode { get; private set; }
|
||||
}
|
||||
}
|
59
SharedBusinessLogic/Repository/Response/ResponseBase.cs
Normal file
59
SharedBusinessLogic/Repository/Response/ResponseBase.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class ResponseBase : CopriVersion
|
||||
{
|
||||
[DataMember]
|
||||
public string response_state { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string response { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string response_text { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string authcookie { get; private set; }
|
||||
|
||||
/// <summary> Message shown to user.</summary>
|
||||
[DataMember]
|
||||
public string merchant_message { get; private set; }
|
||||
|
||||
/// <summary> Initial map display area.</summary>
|
||||
[DataMember]
|
||||
public MapSpan init_map { get; private set; }
|
||||
|
||||
/// <summary> Url of page holding agb info. </summary>
|
||||
[DataMember]
|
||||
public string agb_html { get; private set; }
|
||||
|
||||
/// <summary> Url of page holding instructions how to rent bikes. </summary>
|
||||
[DataMember]
|
||||
public string bike_info_html { get; private set; }
|
||||
|
||||
/// <summary> Url of page holding privacy info. </summary>
|
||||
[DataMember]
|
||||
public string privacy_html { get; private set; }
|
||||
|
||||
/// <summary> Url of page holding impress info. </summary>
|
||||
[DataMember]
|
||||
public string impress_html { get; private set; }
|
||||
|
||||
/// <summary> Url of page holding tariff info. </summary>
|
||||
[DataMember]
|
||||
public string tariff_info_html { get; private set; }
|
||||
|
||||
/// <summary> Textual description of response. </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Response state is \"{response_state ?? string.Empty}\", " +
|
||||
$"auth cookie is \"{authcookie ?? string.Empty}\" and response is \"{response_text ?? string.Empty}\", " +
|
||||
$"code \"{response ?? string.Empty}\"" +
|
||||
$"response text \"{response_text ?? string.Empty}\".";
|
||||
}
|
||||
}
|
||||
}
|
27
SharedBusinessLogic/Repository/Response/ResponseContainer.cs
Normal file
27
SharedBusinessLogic/Repository/Response/ResponseContainer.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class ResponseContainer<T>
|
||||
{
|
||||
[DataMember]
|
||||
public T shareejson { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Serializes object to string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (shareejson == null)
|
||||
{
|
||||
return "Response container does not hold no entry.";
|
||||
}
|
||||
|
||||
return shareejson.ToString();
|
||||
}
|
||||
}
|
||||
}
|
230
SharedBusinessLogic/Repository/Response/ResponseHelper.cs
Normal file
230
SharedBusinessLogic/Repository/Response/ResponseHelper.cs
Normal file
|
@ -0,0 +1,230 @@
|
|||
using System.Linq;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using ShareeBike.Repository.Exception;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
public static class ResponseHelper
|
||||
{
|
||||
public const string RESPONSE_OK = "OK";
|
||||
|
||||
/// <summary> Holds the description of the action return bike. </summary>
|
||||
public const string RESPONSE_AUTHCOOKIE_EXPRIED = "Failure 1001:";
|
||||
|
||||
/// <summary> Holds the description of the action logout. </summary>
|
||||
public const string BIKES_LOGOUT_ACTIONTEXT = "Abmeldung fehlgeschlagen.";
|
||||
|
||||
/// <summary> Holds the description of the action get stations available. </summary>
|
||||
public const string STATIONS_AVAILABLE_ACTIONTEXT = "Abfrage der verfügbaren Stationen fehlgeschlagen.";
|
||||
|
||||
/// <summary> Holds the description of the action get bikes available. </summary>
|
||||
public const string BIKES_AVAILABLE_ACTIONTEXT = "Abfrage der verfügbaren Fahrräder fehlgeschlagen.";
|
||||
|
||||
/// <summary> Holds the description of the action get bikes occupied. </summary>
|
||||
public const string BIKES_OCCUPIED_ACTIONTEXT = "Abfrage der reservierten/ gebuchten Fahrräder fehlgeschlagen.";
|
||||
|
||||
/// <summary> Holds the description of the action return bike. </summary>
|
||||
public const string BIKES_RETURNBIKE_ACTIONTEXT = "Rückgabe des Rads fehlgeschlagen.";
|
||||
|
||||
/// <summary>
|
||||
/// Checks if log in response is ok.
|
||||
/// </summary>
|
||||
/// <param name="response">Response to check whether it is valid.</param>
|
||||
/// <param name="mail">Mail address used to create details error message in case log in response is invalid.</param>
|
||||
/// <returns></returns>
|
||||
public static AuthorizationResponse GetIsResponseOk(this AuthorizationResponse response, string mail)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
throw new InvalidResponseException<AuthorizationResponse>("Anmeldung fehlgeschlagen.", null);
|
||||
}
|
||||
|
||||
if (response.response_state.ToUpper() == InvalidAuthorizationResponseException.AUTH_FAILURE_STATUS_MESSAGE_UPPERCASE)
|
||||
{
|
||||
throw new InvalidAuthorizationResponseException(mail, response);
|
||||
}
|
||||
else if (!response.response_state.Trim().ToUpper().StartsWith(RESPONSE_OK))
|
||||
{
|
||||
throw new InvalidResponseException<AuthorizationResponse>($"Anmeldung {mail} fehlgeschlagen.\r\nServer Antwort: {response.response_text}", response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if log out response is ok.
|
||||
/// </summary>
|
||||
/// <param name="p_oResponse">Response to check whether it is valid.</param>
|
||||
/// <returns></returns>
|
||||
public static AuthorizationoutResponse GetIsResponseOk(this AuthorizationoutResponse p_oResponse)
|
||||
{
|
||||
if (AuthcookieNotDefinedException.IsAuthcookieNotDefined(p_oResponse, BIKES_LOGOUT_ACTIONTEXT, out AuthcookieNotDefinedException exception))
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
GetIsResponseOk(p_oResponse, BIKES_LOGOUT_ACTIONTEXT);
|
||||
|
||||
if (p_oResponse.authcookie != "1")
|
||||
{
|
||||
throw new InvalidResponseException<AuthorizationoutResponse>(
|
||||
BIKES_LOGOUT_ACTIONTEXT,
|
||||
p_oResponse);
|
||||
}
|
||||
|
||||
return p_oResponse;
|
||||
}
|
||||
|
||||
/// <summary>Gets if a call to reserve bike succeeded or not by checking a booking response.</summary>
|
||||
/// <param name="bikeId">Id of bike which should be booked.</param>
|
||||
/// <param name="sessionCookie">Session cookie of logged in user.</param>
|
||||
/// <param name="bookingResponse">Response to check.</param>
|
||||
/// <returns></returns>
|
||||
public static BikeInfoReservedOrBooked GetIsReserveResponseOk(
|
||||
this ReservationBookingResponse bookingResponse,
|
||||
string bikeId)
|
||||
{
|
||||
GetIsResponseOk(bookingResponse, string.Format(AppResources.ErrorReservingBike, bikeId));
|
||||
|
||||
if (BookingDeclinedException.IsBookingDeclined(bookingResponse.response_state, out BookingDeclinedException exception))
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
// Get bike which has to be booked.
|
||||
var bikeInfoRequestedOccupied = bookingResponse?.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
|
||||
if (bikeInfoRequestedOccupied == null)
|
||||
{
|
||||
throw new System.Exception(string.Format(
|
||||
AppResources.ErrorReservingBikeUnavailalbe,
|
||||
bikeId,
|
||||
!string.IsNullOrWhiteSpace(bookingResponse?.response_text) ? $"\r\n{bookingResponse.response_text}" : string.Empty));
|
||||
}
|
||||
|
||||
return bikeInfoRequestedOccupied;
|
||||
}
|
||||
|
||||
/// <summary> Gets if a booking call succeeded or not by checking a booking response. </summary>
|
||||
/// <param name="bikeId">Id of bike which should be booked.</param>
|
||||
/// <param name="bookingResponse">Response to check.</param>
|
||||
/// <returns></returns>
|
||||
public static BikeInfoReservedOrBooked GetIsBookingResponseOk(
|
||||
this ReservationBookingResponse bookingResponse,
|
||||
string bikeId)
|
||||
{
|
||||
GetIsResponseOk(bookingResponse, string.Format(AppResources.ErrorRentingBike, bikeId));
|
||||
|
||||
// Get bike which has to be booked.
|
||||
var bikeInfoRequestedOccupied = bookingResponse?.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
|
||||
if (bikeInfoRequestedOccupied == null)
|
||||
{
|
||||
throw new System.Exception(string.Format(
|
||||
AppResources.ErrorRentingBikeUnavailalbe,
|
||||
bikeId,
|
||||
!string.IsNullOrWhiteSpace(bookingResponse?.response_text) ? $"\r\n{bookingResponse.response_text}" : string.Empty));
|
||||
}
|
||||
|
||||
return bikeInfoRequestedOccupied;
|
||||
}
|
||||
|
||||
/// <summary> Gets if request is ok.</summary>
|
||||
/// <param name="response">Response to verify.</param>
|
||||
/// <param name="textOfAction">Text describing request which is shown if validation fails.</param>
|
||||
/// <returns>Verified response.</returns>
|
||||
public static BikesReservedOccupiedResponse GetIsResponseOk(this BikesReservedOccupiedResponse response, string textOfAction)
|
||||
{
|
||||
if (response == null || response.response_state == null)
|
||||
{
|
||||
throw new InvalidResponseException<BikesReservedOccupiedResponse>(textOfAction, null);
|
||||
}
|
||||
|
||||
if (AuthcookieNotDefinedException.IsAuthcookieNotDefined(response, textOfAction, out AuthcookieNotDefinedException exception))
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
if (response.response_state.Trim().ToUpper().StartsWith(RESPONSE_AUTHCOOKIE_EXPRIED.ToUpper()))
|
||||
{
|
||||
throw new AuthcookieNotDefinedException(
|
||||
$"{textOfAction}\r\n{AppResources.ErrorAccountInvalidAuthorization}",
|
||||
response);
|
||||
}
|
||||
|
||||
GetIsResponseOk((ResponseBase)response, textOfAction);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary> Gets if request is ok.</summary>
|
||||
/// <param name="response">Response to verify.</param>
|
||||
/// <param name="textOfAction">Text describing request which is shown if validation fails.</param>
|
||||
/// <returns>Verified response.</returns>
|
||||
public static T GetIsResponseOk<T>(this T response, string textOfAction) where T : ResponseBase
|
||||
{
|
||||
if (response == null || response.response_state == null)
|
||||
{
|
||||
throw new InvalidResponseException<T>(textOfAction, null);
|
||||
}
|
||||
|
||||
if (AuthcookieNotDefinedException.IsAuthcookieNotDefined(response, textOfAction, out AuthcookieNotDefinedException exception))
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
if (!response.response_state.Trim().ToUpper().StartsWith(RESPONSE_OK))
|
||||
{
|
||||
throw new InvalidResponseException<T>(
|
||||
$"{textOfAction}\r\nServer Antwort: {response.response_text}",
|
||||
response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary> Gets if return bike request is ok.</summary>
|
||||
/// <param name="returnBikeResponse">Response to verify.</param>
|
||||
/// <param name="textOfAction">Text describing request which is shown if validation fails.</param>
|
||||
/// <param name="bikeId">Id of bike.</param>
|
||||
/// <returns>Verified response.</returns>
|
||||
public static DoReturnResponse GetIsReturnBikeResponseOk(
|
||||
this DoReturnResponse returnBikeResponse,
|
||||
string bikeId)
|
||||
{
|
||||
// Check if bike is at station.
|
||||
if (NotAtStationException.IsNotAtStation(returnBikeResponse.response_state.ToUpper(), out NotAtStationException notAtStationException))
|
||||
{
|
||||
throw notAtStationException;
|
||||
}
|
||||
|
||||
// Check if GPS data was send to copri.
|
||||
if (NoGPSDataException.IsNoGPSData(returnBikeResponse.response_state.ToUpper(), out NoGPSDataException noGPSDataException))
|
||||
{
|
||||
throw noGPSDataException;
|
||||
}
|
||||
|
||||
GetIsResponseOk<ResponseBase>(returnBikeResponse, BIKES_RETURNBIKE_ACTIONTEXT);
|
||||
|
||||
// Get bike which has to be booked.
|
||||
var l_oBikeInfo = returnBikeResponse?.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
|
||||
if (l_oBikeInfo != null)
|
||||
{
|
||||
throw new ReturnBikeException(
|
||||
returnBikeResponse,
|
||||
$"{BIKES_RETURNBIKE_ACTIONTEXT} Aufruf wurde erfolgreich ausgeführt, aber Rad ist noch in Liste der gemieteten Räder enthalten.");
|
||||
}
|
||||
|
||||
return returnBikeResponse;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the response for bikes occupied request with no bikes reserved.
|
||||
/// </summary>
|
||||
/// <param name="p_strSesstionCookie"></param>
|
||||
/// <returns></returns>
|
||||
public static BikesReservedOccupiedResponse GetBikesOccupiedNone(string p_strSesstionCookie = null)
|
||||
{
|
||||
var l_oJson = CopriCallsMonkeyStore.BIKESOCCUPIED.Replace(@"""authcookie"": """"", @"""authcookie"": """ + (p_strSesstionCookie ?? string.Empty) + @"""");
|
||||
return CopriCallsStatic.DeserializeResponse(@"{ ""shareejson"" : " + l_oJson + "}", (version) => new BikesReservedOccupiedResponse());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ShareeBike.Repository.Response.Stations.Station
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds a single bike group.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class BikeGroup
|
||||
{
|
||||
/// <summary> Holds the count of bikes for the group. </summary>
|
||||
[DataMember]
|
||||
public string bike_count { get; private set;}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the group identifying the bike group type.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string bike_group { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ShareeBike.Repository.Response.Stations.Station
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds info about operator data.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class OperatorData
|
||||
{
|
||||
[DataMember]
|
||||
public string operator_name { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string operator_phone { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string operator_hours { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string operator_email { get; private set; }
|
||||
|
||||
|
||||
[DataMember]
|
||||
public string operator_color { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response.Stations.Station
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds info about a single station.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class StationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique id of the station.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string station { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string[] station_group { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public string description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Position of the station.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Position gps { get; private set; }
|
||||
|
||||
[DataMember]
|
||||
public OperatorData operator_data { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the count of available bikes at the station.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string bike_count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the count type of station, i.e. which bikes are located at station.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Dictionary<string, BikeGroup> station_type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the uri of the operator which manages the bikes at station/ the station.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string uri_operator { get; private set; }
|
||||
|
||||
public static bool operator ==(StationInfo first, StationInfo second)
|
||||
=> JsonConvert.SerializeObject(first) == JsonConvert.SerializeObject(second);
|
||||
|
||||
public static bool operator !=(StationInfo first, StationInfo second)
|
||||
=> !(first == second);
|
||||
|
||||
public override bool Equals(object obj) => obj is StationInfo target && target == this;
|
||||
|
||||
public override int GetHashCode() => JsonConvert.SerializeObject(this).GetHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using ShareeBike.Repository.Response.Stations.Station;
|
||||
|
||||
namespace ShareeBike.Repository.Response.Stations
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information about all stations and is used for deserialization of copri answer.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class StationsAvailableResponse : ResponseBase
|
||||
{
|
||||
public StationsAvailableResponse()
|
||||
{
|
||||
stations = new ComparableDictionary< StationInfo>();
|
||||
bikes_occupied = new ComparableBikeDictionary<BikeInfoReservedOrBooked> { };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of bikes.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public ComparableDictionary< StationInfo> stations { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of bikes reserved (requested) and rented (occupied) by current user if user is logged in and has reserved or rented bikes.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public ComparableBikeDictionary<BikeInfoReservedOrBooked> bikes_occupied { get; private set; }
|
||||
|
||||
public static bool operator ==(StationsAvailableResponse first, StationsAvailableResponse second)
|
||||
=> JsonConvert.SerializeObject(first) == JsonConvert.SerializeObject(second);
|
||||
|
||||
public static bool operator !=(StationsAvailableResponse first, StationsAvailableResponse second)
|
||||
=> !(first == second);
|
||||
|
||||
public override bool Equals(object obj) => obj is StationsAvailableResponse target && target == this;
|
||||
|
||||
public override int GetHashCode() => JsonConvert.SerializeObject(this).GetHashCode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using Newtonsoft.Json;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class SubmitFeedbackResponse : ResponseBase
|
||||
{
|
||||
}
|
||||
}
|
57
SharedBusinessLogic/Repository/Response/TariffDescription.cs
Normal file
57
SharedBusinessLogic/Repository/Response/TariffDescription.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds tariff info for a single bike.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
#if USCSHARP9
|
||||
public record TariffDescription
|
||||
#else
|
||||
public class TariffDescription
|
||||
#endif
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the tariff.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of the tariff.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string number { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs per hour in euro.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string eur_per_hour { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs of the abo per month.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string abo_eur_per_month { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs per hour in euro.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string free_hours { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum fee per day.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string max_eur_per_day { get; private set; }
|
||||
|
||||
/// <summary> Text which informs users about GPS tracking if tracking is on. </summary>
|
||||
[DataMember]
|
||||
public string track_info { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
|
||||
public class VersionindependentResponse
|
||||
{
|
||||
private CopriVersion _shareejson;
|
||||
|
||||
/// <summary> Root element for versions 4.0 and older. </summary>
|
||||
[DataMember]
|
||||
public CopriVersion tinkjson { get; private set; }
|
||||
|
||||
/// <summary> Root element from 4.1 and later. </summary>
|
||||
[DataMember]
|
||||
public CopriVersion shareejson
|
||||
{
|
||||
get => _shareejson ?? tinkjson;
|
||||
private set { _shareejson = value; }
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue