mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-04-19 03:27:29 +02:00
Initial version.
This commit is contained in:
parent
193aaa1a56
commit
b72c67a53e
228 changed files with 25924 additions and 0 deletions
785
TINKLib/Repository/CopriCallsHttps.cs
Normal file
785
TINKLib/Repository/CopriCallsHttps.cs
Normal file
|
@ -0,0 +1,785 @@
|
|||
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Repository.Exception;
|
||||
using TINK.Model.Repository.Request;
|
||||
using TINK.Model.Repository.Response;
|
||||
using TINK.Model.Logging;
|
||||
using TINK.Repository.Response;
|
||||
|
||||
namespace TINK.Model.Repository
|
||||
{
|
||||
/// <summary> Object which manages calls to copri. </summary>
|
||||
public class CopriCallsHttps : ICopriServer
|
||||
{
|
||||
/// <summary> Builds requests.</summary>
|
||||
private IRequestBuilder requestBuilder;
|
||||
|
||||
/// <summary> Initializes a instance of the copri calls https object. </summary>
|
||||
/// <param name="p_oCopriHost">Host to connect to. </param>
|
||||
/// <param name="p_strMerchantId">Id of the merchant.</param>
|
||||
/// <param name="userAgent">Holds the name and version of the TINKApp.</param>
|
||||
/// <param name="sessionCookie">Session cookie if user is logged in, null otherwise.</param>
|
||||
public CopriCallsHttps(
|
||||
Uri p_oCopriHost,
|
||||
string p_strMerchantId,
|
||||
string userAgent,
|
||||
string sessionCookie = null)
|
||||
{
|
||||
m_oCopriHost = p_oCopriHost
|
||||
?? throw new System.Exception($"Can not construct {GetType().ToString()}- object. Uri of copri host must not be null.");
|
||||
|
||||
UserAgent = !string.IsNullOrEmpty(userAgent)
|
||||
? userAgent
|
||||
: throw new System.Exception($"Can not construct {GetType().ToString()}- object. User agent must not be null or empty.");
|
||||
|
||||
requestBuilder = string.IsNullOrEmpty(sessionCookie)
|
||||
? new RequestBuilder(p_strMerchantId) as IRequestBuilder
|
||||
: new RequestBuilderLoggedIn(p_strMerchantId, sessionCookie);
|
||||
}
|
||||
|
||||
/// <summary> Holds the URL for rest calls.</summary>
|
||||
private Uri m_oCopriHost;
|
||||
|
||||
/// <summary> Spacifies 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">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 async Task<AuthorizationResponse> DoAuthorizationAsync(
|
||||
string mailAddress,
|
||||
string password,
|
||||
string deviceId)
|
||||
{
|
||||
return 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()
|
||||
{
|
||||
return await DoAuthoutAsync(m_oCopriHost.AbsoluteUri, requestBuilder.DoAuthout(), UserAgent);
|
||||
}
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public async Task<BikesAvailableResponse> GetBikesAvailableAsync()
|
||||
{
|
||||
return await GetBikesAvailableAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetBikesAvailable(), UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by acctive 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<StationsAllResponse> GetStationsAsync()
|
||||
{
|
||||
return 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(int bikeId)
|
||||
=> await GetAuthKeysAsync(m_oCopriHost.AbsoluteUri, requestBuilder.CalculateAuthKeys(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(
|
||||
int bikeId,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return await DoReserveAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoReserve(bikeId),
|
||||
UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Gets canel 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<ReservationCancelReturnResponse> DoCancelReservationAsync(
|
||||
int bikeId,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return 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(
|
||||
int bikeId,
|
||||
Uri operatorUri)
|
||||
=> await GetAuthKeysAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.CalculateAuthKeys(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(
|
||||
int bikeId,
|
||||
LocationDto location,
|
||||
lock_state state,
|
||||
double batteryLevel,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return await DoUpdateLockingStateAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.UpateLockingState(bikeId, location, state, batteryLevel),
|
||||
UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Gets booking request request. </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="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Requst on booking request.</returns>
|
||||
public async Task<ReservationBookingResponse> DoBookAsync(
|
||||
int bikeId,
|
||||
Guid guid,
|
||||
double batteryPercentage,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return await DoBookAsync(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoBook(bikeId, guid, batteryPercentage),
|
||||
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<ReservationCancelReturnResponse> DoReturn(
|
||||
int bikeId,
|
||||
LocationDto location,
|
||||
Uri operatorUri)
|
||||
{
|
||||
return await DoReturn(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoReturn(bikeId, location),
|
||||
UserAgent);
|
||||
}
|
||||
|
||||
/// <summary> Submits feedback to copri server. </summary>
|
||||
/// <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 message,
|
||||
bool isBikeBroken,
|
||||
Uri operatorUri) =>
|
||||
await DoSubmitFeedback(
|
||||
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
|
||||
requestBuilder.DoSubmitFeedback(message, isBikeBroken),
|
||||
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 l_oResponseText = string.Empty;
|
||||
try
|
||||
{
|
||||
l_oResponseText = await PostAsync(
|
||||
copriHost,
|
||||
command,
|
||||
userAgent,
|
||||
displayCommand); // Do not include password into exception output when an error occurres.
|
||||
}
|
||||
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 JsonConvert.DeserializeObject<ResponseContainer<AuthorizationResponse>>(l_oResponseText)?.tinkjson;
|
||||
#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 p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oLogoutResponse;
|
||||
try
|
||||
{
|
||||
l_oLogoutResponse = await PostAsync(p_strCopriHost, p_oCommand, 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 JsonConvert.DeserializeObject<ResponseContainer<AuthorizationoutResponse>>(l_oLogoutResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list of stations from file.
|
||||
/// </summary>
|
||||
/// <param name="p_strCopriHost">URL of the copri host to connect to.</param>
|
||||
/// <param name="p_oCommand">Command to get stations.</param>
|
||||
/// <returns>List of files.</returns>
|
||||
public static async Task<StationsAllResponse> GetStationsAsync(
|
||||
string p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oStationsAllResponse;
|
||||
try
|
||||
{
|
||||
l_oStationsAllResponse = await PostAsync(p_strCopriHost, p_oCommand, 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 JsonConvert.DeserializeObject<ResponseContainer<StationsAllResponse>>(l_oStationsAllResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes from Copri. </summary>
|
||||
/// <param name="p_strCopriHost">URL of the copri host to connect to.</param>
|
||||
/// <param name="p_oCommand">Command to get bikes.</param>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public static async Task<BikesAvailableResponse> GetBikesAvailableAsync(
|
||||
string p_strCopriHost,
|
||||
string l_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(p_strCopriHost, l_oCommand, 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.DeserializeBikesAvailableResponse(l_oBikesAvaialbeResponse);
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by acctive user from Copri.</summary>
|
||||
/// <param name="p_strCopriHost">URL of the copri host to connect to.</param>
|
||||
/// <param name="p_oCommand">Command to post.</param>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
public static async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync(
|
||||
string p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesOccupiedResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesOccupiedResponse = await PostAsync(p_strCopriHost, p_oCommand, 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.DeserializeBikesOccupiedResponse(l_oBikesOccupiedResponse);
|
||||
#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 p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Schlosssuche wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Schlosssuche wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#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 p_strCopriHost,
|
||||
string p_oCommand,
|
||||
string userAgent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(p_strCopriHost, p_oCommand, userAgent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Reservierung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Reservierung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> Gets canel 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<ReservationCancelReturnResponse> 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 JsonConvert.DeserializeObject<ResponseContainer<ReservationCancelReturnResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<ReservationBookingResponse> DoUpdateLockingStateAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string agent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<ReservationBookingResponse> DoBookAsync(
|
||||
string copriHost,
|
||||
string command,
|
||||
string agent = null)
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
string l_oBikesAvaialbeResponse;
|
||||
try
|
||||
{
|
||||
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
|
||||
}
|
||||
catch (System.Exception l_oException)
|
||||
{
|
||||
if (l_oException.GetIsConnectFailureException())
|
||||
{
|
||||
throw new WebConnectFailureException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
if (l_oException.GetIsForbiddenException())
|
||||
{
|
||||
throw new WebForbiddenException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", l_oException);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
// Extract bikes from response.
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<ReservationBookingResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static async Task<ReservationCancelReturnResponse> DoReturn(
|
||||
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("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 JsonConvert.DeserializeObject<ResponseContainer<ReservationCancelReturnResponse>>(l_oBikesAvaialbeResponse)?.tinkjson;
|
||||
#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 JsonConvert.DeserializeObject<ResponseContainer<SubmitFeedbackResponse>>(userFeedbackResponse)?.tinkjson;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary> http get- request.</summary>
|
||||
/// <param name="Url">Ulr 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> http- post request.</summary>
|
||||
/// <param name="p_strCommand">Command to send.</param>
|
||||
/// <param name="p_oDisplayCommand">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 p_strCommand,
|
||||
string userAgent = null,
|
||||
Func<string> p_oDisplayCommand = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(p_strCommand))
|
||||
{
|
||||
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 = p_oDisplayCommand ?? delegate () { return p_strCommand; };
|
||||
|
||||
try
|
||||
{
|
||||
#if !WINDOWS_UWP
|
||||
var l_strHost = uRL;
|
||||
|
||||
// Returns a http request.
|
||||
var l_oRequest = WebRequest.CreateHttp(l_strHost);
|
||||
|
||||
l_oRequest.Method = "POST";
|
||||
l_oRequest.ContentType = "application/x-www-form-urlencoded";
|
||||
l_oRequest.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.
|
||||
l_oRequest.KeepAlive = true;
|
||||
|
||||
byte[] l_oPostData = Encoding.UTF8.GetBytes(p_strCommand);
|
||||
|
||||
l_oRequest.ContentLength = l_oPostData.Length;
|
||||
|
||||
// Get the request stream.
|
||||
using (Stream l_oDataStream = await l_oRequest.GetRequestStreamAsync())
|
||||
{
|
||||
// Write the data to the request stream.
|
||||
await l_oDataStream.WriteAsync(l_oPostData, 0, l_oPostData.Length);
|
||||
}
|
||||
|
||||
// Get the response.
|
||||
var l_oResponse = await l_oRequest.GetResponseAsync() as HttpWebResponse;
|
||||
|
||||
if (l_oResponse == null)
|
||||
{
|
||||
throw new System.Exception(string.Format("Reserve request failed. Response form from server was not of expected type."));
|
||||
}
|
||||
|
||||
if (l_oResponse.StatusCode != HttpStatusCode.OK)
|
||||
{
|
||||
throw new CommunicationException(string.Format(
|
||||
"Posting request {0} failed. Expected status code is {1} but was {2}.",
|
||||
displayCommandFunc(),
|
||||
HttpStatusCode.OK,
|
||||
l_oResponse.StatusCode));
|
||||
}
|
||||
|
||||
string response = string.Empty;
|
||||
|
||||
// Get the request stream.
|
||||
using (Stream l_oDataStream = l_oResponse.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.
|
||||
l_oResponse.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 l_oException)
|
||||
{
|
||||
Log.ForContext<CopriCallsHttps>().InformationOrError("Posting command {DisplayCommand} to host {URL} failed. {Exception}.", displayCommandFunc(), uRL, l_oException);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
281
TINKLib/Repository/CopriCallsMonkeyStore.cs
Normal file
281
TINKLib/Repository/CopriCallsMonkeyStore.cs
Normal file
|
@ -0,0 +1,281 @@
|
|||
using MonkeyCache.FileStore;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Connector;
|
||||
using TINK.Model.Repository.Request;
|
||||
using TINK.Model.Repository.Response;
|
||||
using TINK.Model.Services.CopriApi;
|
||||
using TINK.Repository.Response;
|
||||
|
||||
namespace TINK.Model.Repository
|
||||
{
|
||||
public class CopriCallsMonkeyStore : ICopriCache
|
||||
{
|
||||
/// <summary> Prevents concurrent communictation. </summary>
|
||||
private object monkeyLock = new object();
|
||||
|
||||
/// <summary> Builds requests.</summary>
|
||||
private IRequestBuilder requestBuilder;
|
||||
|
||||
public const string BIKESAVAILABLE = @"{
|
||||
""copri_version"" : ""3.0.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"" : ""Konrad,TINK"",
|
||||
""authcookie"" : """",
|
||||
""response_state"" : ""OK"",
|
||||
""bikes_occupied"" : {},
|
||||
""copri_version"" : ""3.0.0.0"",
|
||||
""apiserver"" : ""https://app.tink-konstanz.de""
|
||||
}";
|
||||
|
||||
public const string STATIONS = @"{
|
||||
""apiserver"" : ""https://app.tink-konstanz.de"",
|
||||
""authcookie"" : """",
|
||||
""response"" : ""stations_all"",
|
||||
""copri_version"" : ""3.0.0.0"",
|
||||
""stations"" : {},
|
||||
""response_state"" : ""OK""
|
||||
}";
|
||||
|
||||
/// <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> Initializes a instance of the copri monkey store object. </summary>
|
||||
/// <param name="p_strMerchantId">Id of the merchant.</param>
|
||||
/// <param name="sessionCookie">Session cookie if user is logged in, null otherwise.</param>
|
||||
public CopriCallsMonkeyStore(
|
||||
string merchantId,
|
||||
string sessionCookie = null,
|
||||
TimeSpan? expiresAfter = null)
|
||||
{
|
||||
ExpiresAfter = expiresAfter ?? TimeSpan.FromSeconds(1);
|
||||
|
||||
requestBuilder = string.IsNullOrEmpty(sessionCookie)
|
||||
? new RequestBuilder(merchantId) as IRequestBuilder
|
||||
: new RequestBuilderLoggedIn(merchantId, sessionCookie);
|
||||
|
||||
// Ensure that store holds valid entries.
|
||||
if (!Barrel.Current.Exists(requestBuilder.GetBikesAvailable()))
|
||||
{
|
||||
AddToCache(JsonConvert.DeserializeObject<BikesAvailableResponse>(BIKESAVAILABLE), new TimeSpan(0));
|
||||
}
|
||||
|
||||
// Do not query bikes occupied if no user is logged in (leads to not implemented exception)
|
||||
if (!string.IsNullOrEmpty(sessionCookie) && !Barrel.Current.Exists(requestBuilder.GetBikesOccupied()))
|
||||
{
|
||||
AddToCache(JsonConvert.DeserializeObject<BikesReservedOccupiedResponse>(BIKESOCCUPIED), new TimeSpan(0));
|
||||
}
|
||||
|
||||
if (!Barrel.Current.Exists(requestBuilder.GetStations()))
|
||||
{
|
||||
AddToCache(JsonConvert.DeserializeObject<StationsAllResponse>(STATIONS), new TimeSpan(0));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<ReservationBookingResponse> DoReserveAsync(int bikeId, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception("Reservierung im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<ReservationCancelReturnResponse> DoCancelReservationAsync(int p_iBikeId, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception("Abbrechen einer Reservierung im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<ReservationBookingResponse> CalculateAuthKeysAsync(int bikeId, Uri operatorUri)
|
||||
=> throw new System.Exception("Schlosssuche im Offlinemodus nicht möglich!");
|
||||
|
||||
public Task<ReservationBookingResponse> UpdateLockingStateAsync(
|
||||
int bikeId,
|
||||
LocationDto geolocation,
|
||||
lock_state state,
|
||||
double batteryLevel,
|
||||
Uri operatorUri)
|
||||
=> throw new System.Exception("Aktualisierung des Schlossstatuses im Offlinemodus nicht möglich!");
|
||||
|
||||
public Task<ReservationBookingResponse> DoBookAsync(int bikeId, Guid guid, double batteryPercentage, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception("Buchung im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<ReservationCancelReturnResponse> DoReturn(int bikeId, LocationDto geolocation, Uri operatorUri)
|
||||
{
|
||||
throw new System.Exception("Rückgabe im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<SubmitFeedbackResponse> DoSubmitFeedback(string message, bool isBikeBroken, Uri operatorUri) =>
|
||||
throw new System.Exception("Übermittlung von Feedback im Offlinemodus nicht möglich!");
|
||||
|
||||
public Task<AuthorizationResponse> DoAuthorizationAsync(string p_strMailAddress, string p_strPassword, string p_strDeviceId)
|
||||
{
|
||||
throw new System.Exception("Anmelden im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public Task<AuthorizationoutResponse> DoAuthoutAsync()
|
||||
{
|
||||
throw new System.Exception("Abmelden im Offlinemodus nicht möglich!");
|
||||
}
|
||||
|
||||
public async Task<BikesAvailableResponse> GetBikesAvailableAsync()
|
||||
{
|
||||
var l_oBikesAvailableTask = new TaskCompletionSource<BikesAvailableResponse>();
|
||||
lock (monkeyLock)
|
||||
{
|
||||
l_oBikesAvailableTask.SetResult(Barrel.Current.Get<BikesAvailableResponse>(requestBuilder.GetBikesAvailable()));
|
||||
}
|
||||
return await l_oBikesAvailableTask.Task;
|
||||
}
|
||||
|
||||
public async Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var l_oBikesOccupiedTask = new TaskCompletionSource<BikesReservedOccupiedResponse>();
|
||||
lock (monkeyLock)
|
||||
{
|
||||
l_oBikesOccupiedTask.SetResult(Barrel.Current.Get<BikesReservedOccupiedResponse>(requestBuilder.GetBikesOccupied()));
|
||||
}
|
||||
|
||||
return await l_oBikesOccupiedTask.Task;
|
||||
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
// No user logged in.
|
||||
await Task.CompletedTask;
|
||||
return ResponseHelper.GetBikesOccupiedNone();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<StationsAllResponse> GetStationsAsync()
|
||||
{
|
||||
var l_oStationsAllTask = new TaskCompletionSource<StationsAllResponse>();
|
||||
lock (monkeyLock)
|
||||
{
|
||||
l_oStationsAllTask.SetResult(Barrel.Current.Get<StationsAllResponse>(requestBuilder.GetStations()));
|
||||
}
|
||||
return await l_oStationsAllTask.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(StationsAllResponse stations)
|
||||
{
|
||||
AddToCache(stations, ExpiresAfter);
|
||||
}
|
||||
|
||||
/// <summary> Adds a stations all response to cache.</summary>
|
||||
/// <param name="stations">Stations to add.</param>
|
||||
/// <param name="expiresAfter">Time after which anser is considered to be expired.</param>
|
||||
private void AddToCache(StationsAllResponse stations, TimeSpan expiresAfter)
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
Barrel.Current.Add(
|
||||
requestBuilder.GetStations(),
|
||||
JsonConvert.SerializeObject(stations),
|
||||
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>
|
||||
public void AddToCache(BikesAvailableResponse bikes)
|
||||
{
|
||||
AddToCache(bikes, ExpiresAfter);
|
||||
}
|
||||
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
/// <param name="expiresAfter">Time after which anser is considered to be expired.</param>
|
||||
private void AddToCache(BikesAvailableResponse bikes, TimeSpan expiresAfter)
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
Barrel.Current.Add(
|
||||
requestBuilder.GetBikesAvailable(),
|
||||
JsonConvert.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)
|
||||
{
|
||||
AddToCache(bikes, ExpiresAfter);
|
||||
|
||||
}
|
||||
/// <summary> Adds a bikes response to cache.</summary>
|
||||
/// <param name="bikes">Bikes to add.</param>
|
||||
/// <param name="expiresAfter">Time after which anser is considered to be expired.</param>
|
||||
private void AddToCache(BikesReservedOccupiedResponse bikes, TimeSpan expiresAfter)
|
||||
{
|
||||
lock (monkeyLock)
|
||||
{
|
||||
Barrel.Current.Add(
|
||||
requestBuilder.GetBikesOccupied(),
|
||||
JsonConvert.SerializeObject(bikes),
|
||||
expiresAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
TINKLib/Repository/CopriCallsStatic.cs
Normal file
28
TINKLib/Repository/CopriCallsStatic.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using TINK.Model.Repository.Response;
|
||||
using TINK.Repository.Response;
|
||||
|
||||
namespace TINK.Model.Repository
|
||||
{
|
||||
public static class CopriCallsStatic
|
||||
{
|
||||
/// <summary>
|
||||
/// Deserializes JSON from response string.
|
||||
/// </summary>
|
||||
/// <param name="p_strResponse">Response to deserialize.</param>
|
||||
/// <returns></returns>
|
||||
public static BikesAvailableResponse DeserializeBikesAvailableResponse(string p_strResponse)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<BikesAvailableResponse>>(p_strResponse)?.tinkjson;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes JSON from response string.
|
||||
/// </summary>
|
||||
/// <param name="p_strResponse">Response to deserialize.</param>
|
||||
/// <returns></returns>
|
||||
public static BikesReservedOccupiedResponse DeserializeBikesOccupiedResponse(string p_strResponse)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<ResponseContainer<BikesReservedOccupiedResponse>>(p_strResponse)?.tinkjson;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
namespace TINK.Model.Repository.Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Is fired with reqest used a cookie which is not defined.
|
||||
/// Reasons for cookie to be not defined might be
|
||||
/// - user used more thant 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)
|
||||
{
|
||||
}
|
||||
|
||||
public static bool IsAuthcookieNotDefined(
|
||||
Response.ResponseBase reponse,
|
||||
string actionText,
|
||||
out AuthcookieNotDefinedException exception)
|
||||
{
|
||||
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,15 @@
|
|||
namespace TINK.Model.Repository.Exception
|
||||
{
|
||||
public class InvalidAuthorizationResponseException : InvalidResponseException<Response.ResponseBase>
|
||||
{
|
||||
/// <summary>Constructs a authorization exceptions. </summary>
|
||||
/// <param name="p_strMail">Mail address to create a detailed error message.</param>
|
||||
public InvalidAuthorizationResponseException(string p_strMail, Response.ResponseBase p_oResponse) :
|
||||
base(string.Format("Kann Benutzer {0} nicht anmelden. Mailadresse unbekannt oder Passwort ungültig.", p_strMail), p_oResponse)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> Holds error description if user/ password combination is not valid. </summary>
|
||||
public const string AUTH_FAILURE_STATUS_MESSAGE_UPPERCASE = "FAILURE: CANNOT GENERATE AUTHCOOKIE";
|
||||
}
|
||||
}
|
42
TINKLib/Repository/Exception/BookingDeclinedException.cs
Normal file
42
TINKLib/Repository/Exception/BookingDeclinedException.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace TINK.Model.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; }
|
||||
}
|
||||
}
|
6
TINKLib/Repository/Exception/CallNotRequiredException.cs
Normal file
6
TINKLib/Repository/Exception/CallNotRequiredException.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace TINK.Model.Repository.Exception
|
||||
{
|
||||
public class CallNotRequiredException : System.Exception
|
||||
{
|
||||
}
|
||||
}
|
28
TINKLib/Repository/Exception/CommunicationException.cs
Normal file
28
TINKLib/Repository/Exception/CommunicationException.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
namespace TINK.Model.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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
11
TINKLib/Repository/Exception/DeserializationException.cs
Normal file
11
TINKLib/Repository/Exception/DeserializationException.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using TINK.Model.Repository.Exception;
|
||||
|
||||
namespace TINK.Repository.Exception
|
||||
{
|
||||
public class DeserializationException : CommunicationException
|
||||
{
|
||||
public DeserializationException(System.Exception ex) : base(ex.Message, ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
34
TINKLib/Repository/Exception/InvalidResponseException.cs
Normal file
34
TINKLib/Repository/Exception/InvalidResponseException.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
namespace TINK.Model.Repository.Exception
|
||||
{
|
||||
public class InvalidResponseException<T> : InvalidResponseException
|
||||
{
|
||||
/// <summary> Constructs an invalid response Exception. </summary>
|
||||
/// <param name="p_strActionWhichFailed">Describes the action which failed.</param>
|
||||
/// <param name="p_oResponse">Response from copri.</param>
|
||||
public InvalidResponseException(string p_strActionWhichFailed, T p_oResponse)
|
||||
: base(string.Format(
|
||||
"{0}{1}",
|
||||
p_strActionWhichFailed,
|
||||
p_oResponse == null ? string.Format(" Response des Typs {0} ist null.", typeof(T).Name.ToString()) : string.Empty))
|
||||
{
|
||||
Response = p_oResponse;
|
||||
}
|
||||
|
||||
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="p_strMessage">Exception.</param>
|
||||
public InvalidResponseException(string p_strMessage) : base(p_strMessage)
|
||||
{ }
|
||||
}
|
||||
}
|
28
TINKLib/Repository/Exception/NoGPSDataException.cs
Normal file
28
TINKLib/Repository/Exception/NoGPSDataException.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using TINK.Model.Repository.Exception;
|
||||
|
||||
namespace TINK.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;
|
||||
}
|
||||
}
|
||||
}
|
39
TINKLib/Repository/Exception/NotAtStationException.cs
Normal file
39
TINKLib/Repository/Exception/NotAtStationException.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace TINK.Model.Repository.Exception
|
||||
{
|
||||
public class NotAtStationException : InvalidResponseException
|
||||
{
|
||||
/// <summary> COPRI response status regular expression. </summary>
|
||||
public const string RETURNBIKE_FAILURE_STATUS_MESSAGE_UPPERCASE = "(FAILURE 2178: BIKE [0-9]+ OUT OF GEO FENCING\\. )([0-9]+)( METER DISTANCE TO NEXT STATION )([0-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 match = Regex.Match(
|
||||
responseState.ToUpper(),
|
||||
RETURNBIKE_FAILURE_STATUS_MESSAGE_UPPERCASE);
|
||||
if (match.Groups.Count != 5
|
||||
|| !int.TryParse(match.Groups[2].ToString(), out int meters)
|
||||
|| !int.TryParse(match.Groups[4].ToString(), out int stationNr))
|
||||
{
|
||||
exception = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
exception = new NotAtStationException { Distance = meters, StationNr = stationNr };
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Holds the maximum count of bikes allowed to reserve/ book.</summary>
|
||||
public int Distance { get; private set; }
|
||||
|
||||
/// <summary> Holds the maximum count of bikes allowed to reserve/ book.</summary>
|
||||
public int StationNr { get; private set; }
|
||||
}
|
||||
}
|
15
TINKLib/Repository/Exception/ResponseException.cs
Normal file
15
TINKLib/Repository/Exception/ResponseException.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using TINK.Model.Repository.Response;
|
||||
|
||||
namespace TINK.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;
|
||||
}
|
||||
}
|
10
TINKLib/Repository/Exception/ReturnBikeException.cs
Normal file
10
TINKLib/Repository/Exception/ReturnBikeException.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using TINK.Model.Repository.Response;
|
||||
|
||||
namespace TINK.Repository.Exception
|
||||
{
|
||||
public class ReturnBikeException : ResponseException
|
||||
{
|
||||
public ReturnBikeException(ReservationCancelReturnResponse response, string message) : base(response, message)
|
||||
{ }
|
||||
}
|
||||
}
|
24
TINKLib/Repository/Exception/WebConnectFailureException.cs
Normal file
24
TINKLib/Repository/Exception/WebConnectFailureException.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
namespace TINK.Model.Repository.Exception
|
||||
{
|
||||
public class WebConnectFailureException : CommunicationException
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a hint to fix communication problem.
|
||||
/// </summary>
|
||||
public static string GetHintToPossibleExceptionsReasons
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Ist WLAN verfügbar/ Mobilfunknetz vefügbar und mobile Daten aktiviert / ... ?";
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Constructs a communication exeption object.
|
||||
/// </summary>
|
||||
/// <param name="p_strMessage"></param>
|
||||
/// <param name="p_oException"></param>
|
||||
public WebConnectFailureException(string p_strMessage, System.Exception p_oException) : base(p_strMessage, p_oException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
42
TINKLib/Repository/Exception/WebExceptionHelper.cs
Normal file
42
TINKLib/Repository/Exception/WebExceptionHelper.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System.Net;
|
||||
|
||||
namespace TINK.Model.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="p_oException">Expection to check.</param>
|
||||
/// <returns>True if exception if caused by an connection error. </returns>
|
||||
public static bool GetIsConnectFailureException(this System.Exception p_oException)
|
||||
{
|
||||
var l_oException = p_oException as WebException;
|
||||
if (l_oException == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return l_oException.Status == WebExceptionStatus.ConnectFailure // Happens if WLAN and mobile data is off/ Router denies internet access/ ...
|
||||
|| l_oException.Status == WebExceptionStatus.NameResolutionFailure // Happens sometimes when not WLAN and no mobil connection are available (bad connection in lift).
|
||||
|| l_oException.Status == WebExceptionStatus.ReceiveFailure; // Happened when modile was connected to WLAN
|
||||
}
|
||||
|
||||
/// <summary> Gets if a exception is caused by clicking too fast.</summary>
|
||||
/// <param name="p_oException">Expection to check.</param>
|
||||
/// <returns>True if exception if caused by a fast click sequence. </returns>
|
||||
public static bool GetIsForbiddenException(this System.Exception p_oException)
|
||||
{
|
||||
if (!(p_oException is WebException l_oException))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(l_oException?.Response is HttpWebResponse l_oResponse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return l_oException.Status == WebExceptionStatus.ProtocolError
|
||||
&& l_oResponse.StatusCode == HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
}
|
14
TINKLib/Repository/Exception/WebForbiddenException.cs
Normal file
14
TINKLib/Repository/Exception/WebForbiddenException.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace TINK.Model.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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
121
TINKLib/Repository/ICopriServer.cs
Normal file
121
TINKLib/Repository/ICopriServer.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using TINK.Model.Repository.Request;
|
||||
using TINK.Model.Repository.Response;
|
||||
using TINK.Repository.Response;
|
||||
|
||||
namespace TINK.Model.Repository
|
||||
{
|
||||
/// <summary> Interface to communicate with copri server.</summary>
|
||||
public interface ICopriServerBase
|
||||
{
|
||||
/// <summary> Logs 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>
|
||||
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(
|
||||
int bikeId,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Cancels reservation of bik. </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<ReservationCancelReturnResponse> DoCancelReservationAsync(
|
||||
int 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(
|
||||
int 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(
|
||||
int bikeId,
|
||||
LocationDto location,
|
||||
lock_state state,
|
||||
double batteryPercentage,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary> Books a bike. </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="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
Task<ReservationBookingResponse> DoBookAsync(
|
||||
int bikeId,
|
||||
Guid guid,
|
||||
double batteryPercentage,
|
||||
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<ReservationCancelReturnResponse> DoReturn(
|
||||
int bikeId,
|
||||
LocationDto location,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <summary>
|
||||
/// Submits feedback to copri server.
|
||||
/// </summary>
|
||||
/// <param name="isBikeBroken">True if bike is broken.</param>
|
||||
/// <param name="message">General purpose message or error description.</param>
|
||||
Task<SubmitFeedbackResponse> DoSubmitFeedback(
|
||||
string message,
|
||||
bool isBikeBroken,
|
||||
Uri operatorUri);
|
||||
|
||||
/// <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<StationsAllResponse> GetStationsAsync();
|
||||
|
||||
/// <summary> Gets a list of bikes from Copri. </summary>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
Task<BikesAvailableResponse> GetBikesAvailableAsync();
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by acctive user from Copri.</summary>
|
||||
/// <returns>Response holding list of bikes.</returns>
|
||||
Task<BikesReservedOccupiedResponse> GetBikesOccupiedAsync();
|
||||
}
|
||||
}
|
122
TINKLib/Repository/Request/IRequestBuilder.cs
Normal file
122
TINKLib/Repository/Request/IRequestBuilder.cs
Normal file
|
@ -0,0 +1,122 @@
|
|||
using System;
|
||||
|
||||
namespace TINK.Model.Repository.Request
|
||||
{
|
||||
/// <summary> Defines members to create requests.</summary>
|
||||
public interface IRequestBuilder
|
||||
{
|
||||
/// <summary> Holds the id denoting the merchant (TINK 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">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>Requst which holds auth cookie <see cref="RequstBase.authcookie"/></remarks>
|
||||
string DoAuthorization(
|
||||
string mailAddress,
|
||||
string password,
|
||||
string deviceId);
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
/// <param name="p_strMerchantId">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>
|
||||
/// <returns>Request to query list of bikes available.</returns>
|
||||
string GetBikesAvailable();
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by acctive 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>Requst to reserve bike.</returns>
|
||||
string DoReserve(int bikeId);
|
||||
|
||||
/// <summary> Gets request to cancel reservation. </summary>
|
||||
/// <param name="bikeId">Id of the bike to cancel reservation for.</param>
|
||||
/// <returns>Requst on cancel booking request.</returns>
|
||||
string DoCancelReservation(int 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 CalculateAuthKeys(int 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="location">Geolocation of lock when state change occurred.</param>
|
||||
/// <param name="state">New locking state.</param>
|
||||
/// <returns>Request to update locking state.</returns>
|
||||
string UpateLockingState(
|
||||
int bikeId,
|
||||
LocationDto location,
|
||||
lock_state state,
|
||||
double batteryPercentage);
|
||||
|
||||
/// <summary> Gets booking request 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>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
string DoBook(int bikeId, Guid guid, double batteryPercentage);
|
||||
|
||||
/// <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>Requst on returning request.</returns>
|
||||
string DoReturn(int bikeId, LocationDto location);
|
||||
|
||||
/// <summary>
|
||||
/// Gets request for submiting feedback to copri server.
|
||||
/// </summary>
|
||||
/// <param name="message">General purpose message or error description.</param>
|
||||
/// <param name="isBikeBroken">True if bike is broken.</param>
|
||||
string DoSubmitFeedback(string message = null, bool isBikeBroken = false);
|
||||
}
|
||||
|
||||
/// <summary> Copri locking states</summary>
|
||||
public enum lock_state
|
||||
{
|
||||
locked,
|
||||
unlocked
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
TINKLib/Repository/Request/RequestBuilder.cs
Normal file
112
TINKLib/Repository/Request/RequestBuilder.cs
Normal file
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using TINK.Model.Repository.Exception;
|
||||
|
||||
namespace TINK.Model.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"></param>
|
||||
public RequestBuilder(
|
||||
string merchantId)
|
||||
{
|
||||
MerchantId = !string.IsNullOrEmpty(merchantId)
|
||||
? merchantId
|
||||
: throw new ArgumentException("Merchant id must not be null.", nameof(merchantId));
|
||||
}
|
||||
|
||||
/// <summary> Holds the id denoting the merchant (TINK app). </summary>
|
||||
public string MerchantId { get; }
|
||||
|
||||
/// <summary> Holds the session cookie if a user is logged in. </summary>
|
||||
public string SessionCookie => string.Empty;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return string.Format(
|
||||
"request=authorization&merchant_id={0}&user_id={1}&user_pw={2}&hw_id={3}",
|
||||
MerchantId,
|
||||
WebUtility.UrlEncode(mailAddress),
|
||||
WebUtility.UrlEncode(password),
|
||||
deviceId);
|
||||
}
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
public string DoAuthout()
|
||||
{
|
||||
throw new CallNotRequiredException();
|
||||
}
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <returns>Request to query list of bikes available.</returns>
|
||||
public string GetBikesAvailable()
|
||||
{
|
||||
return GetBikesAvailable(MerchantId);
|
||||
}
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <returns>Request to query list of bikes available.</returns>
|
||||
public static string GetBikesAvailable(string merchantId, string sessionCookie = null)
|
||||
{
|
||||
return $"request=bikes_available&system=all&authcookie={sessionCookie ?? string.Empty}{merchantId}";
|
||||
}
|
||||
|
||||
/// <summary> Get list of stations from file. </summary>
|
||||
/// <returns>Request to query list of station.</returns>
|
||||
public string GetStations()
|
||||
{
|
||||
return GetStations(MerchantId);
|
||||
}
|
||||
|
||||
/// <summary> Get list of stations from file. </summary>
|
||||
/// <returns>Request to query list of station.</returns>
|
||||
public static string GetStations(string merchantId, string sessionCookie = null)
|
||||
{
|
||||
return $"request=stations_available&authcookie={sessionCookie ?? string.Empty}{merchantId}";
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by acctive 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="p_iBikeId">Id of the bike to book.</param>
|
||||
/// <returns>Response on booking request.</returns>
|
||||
public string DoReserve(int p_iBikeId) => 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(int 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 CalculateAuthKeys(int bikeId) => throw new NotSupportedException();
|
||||
|
||||
public string UpateLockingState(int bikeId, LocationDto geolocation, lock_state state, double batteryPercentage)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public string DoBook(int bikeId, Guid guid, double batteryPercentage) => throw new NotSupportedException();
|
||||
|
||||
public string DoReturn(int bikeId, LocationDto geolocation) => throw new NotSupportedException();
|
||||
|
||||
/// <summary> Gets submit feedback request. </summary>
|
||||
/// <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 message = null,
|
||||
bool isBikeBroken = false) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
170
TINKLib/Repository/Request/RequestBuilderLoggedIn.cs
Normal file
170
TINKLib/Repository/Request/RequestBuilderLoggedIn.cs
Normal file
|
@ -0,0 +1,170 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using TINK.Model.Repository.Exception;
|
||||
|
||||
namespace TINK.Model.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"></param>
|
||||
public RequestBuilderLoggedIn(
|
||||
string merchantId,
|
||||
string sessionCookie)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary> Holds the id denoting the merchant (TINK app). </summary>
|
||||
public string MerchantId { get; }
|
||||
|
||||
/// <summary> Holds the session cookie if a user is logged in. </summary>
|
||||
public string SessionCookie { 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)
|
||||
{
|
||||
throw new CallNotRequiredException();
|
||||
}
|
||||
|
||||
/// <summary> Logs user out. </summary>
|
||||
public string DoAuthout()
|
||||
{
|
||||
return $"request=authout&authcookie={SessionCookie}{MerchantId}";
|
||||
}
|
||||
|
||||
/// <summary>Gets bikes available.</summary>
|
||||
/// <returns>Request to query list of bikes available.</returns>
|
||||
public string GetBikesAvailable()
|
||||
{
|
||||
return RequestBuilder.GetBikesAvailable(MerchantId, SessionCookie);
|
||||
}
|
||||
|
||||
/// <summary> Gets a list of bikes reserved/ booked by acctive user from Copri.</summary>
|
||||
/// <returns>Request to query list of bikes occupied.</returns>
|
||||
public string GetBikesOccupied()
|
||||
{
|
||||
return !string.IsNullOrEmpty(SessionCookie)
|
||||
? $"request=user_bikes_occupied&system=all&genkey=1&authcookie={SessionCookie}{MerchantId}"
|
||||
: "request=bikes_available";
|
||||
}
|
||||
|
||||
/// <summary> Get list of stations from file. </summary>
|
||||
/// <returns>Request to query list of station.</returns>
|
||||
public string GetStations()
|
||||
{
|
||||
return $"request=stations_available&authcookie={SessionCookie ?? string.Empty}{MerchantId}";
|
||||
}
|
||||
|
||||
/// <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>Requst to reserve bike.</returns>
|
||||
public string DoReserve(int bikeId)
|
||||
=> $"request=booking_request&bike={bikeId}&authcookie={SessionCookie}{MerchantId}";
|
||||
|
||||
/// <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>Requst on cancel booking request.</returns>
|
||||
public string DoCancelReservation(int p_iBikeId)
|
||||
=> $"request=booking_cancel&bike={p_iBikeId}&authcookie={SessionCookie}{MerchantId}";
|
||||
|
||||
/// <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 CalculateAuthKeys(int bikeId)
|
||||
=> $"request=booking_update&bike={bikeId}&authcookie={SessionCookie}{MerchantId}&genkey=1";
|
||||
|
||||
/// <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>
|
||||
/// <returns>Request to update locking state.</returns>
|
||||
public string UpateLockingState(int bikeId, LocationDto geolocation, lock_state state, double batteryPercentage)
|
||||
{
|
||||
return $"request=booking_update&bike={bikeId}{GetLocationKey(geolocation)}&lock_state={state}{GetBatteryPercentageKey(batteryPercentage)}&authcookie={SessionCookie}{MerchantId}";
|
||||
}
|
||||
|
||||
/// <summary> Gets booking request 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>
|
||||
/// <returns>Request to booking bike.</returns>
|
||||
public string DoBook(int bikeId, Guid guid, double batteryPercentage)
|
||||
=> $"request=booking_update&bike={bikeId}&authcookie={SessionCookie}{MerchantId}&Ilockit_GUID={guid}&state=occupied&lock_state=unlocked{GetBatteryPercentageKey(batteryPercentage)}";
|
||||
|
||||
/// <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>Requst on returning request.</returns>
|
||||
public string DoReturn(int bikeId, LocationDto geolocation)
|
||||
{
|
||||
return $"request=booking_update&bike={bikeId}&authcookie={SessionCookie}{MerchantId}&state=available{GetLocationKey(geolocation)}&lock_state=locked";
|
||||
}
|
||||
|
||||
/// <summary> Gets submit feedback request. </summary>
|
||||
/// <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 message = null,
|
||||
bool isBikeBroken = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(message) && !isBikeBroken)
|
||||
{
|
||||
// User just acknoledged biked returned message.
|
||||
return "request=user_feedback";
|
||||
}
|
||||
|
||||
if (isBikeBroken == false)
|
||||
{
|
||||
// Bike is ok and user entered a feedback message.
|
||||
return $"request=user_feedback&message={WebUtility.UrlEncode(message)}";
|
||||
}
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(message))
|
||||
{
|
||||
// User just marked bike as broken without comment.
|
||||
return $"request=user_feedback&bike_broken=1";
|
||||
}
|
||||
|
||||
// Bike is marked as broken and user added a comment.
|
||||
return $"request=user_feedback&bike_broken=1&message={WebUtility.UrlEncode(message)}";
|
||||
}
|
||||
|
||||
private string GetBatteryPercentageKey(double batteryPercentage) => !double.IsNaN(batteryPercentage)
|
||||
? $"&voltage={batteryPercentage.ToString(CultureInfo.InvariantCulture)}"
|
||||
: string.Empty;
|
||||
|
||||
private string GetLocationKey(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}";
|
||||
}
|
||||
}
|
||||
}
|
15
TINKLib/Repository/Response/AuthorizationResponse.cs
Normal file
15
TINKLib/Repository/Response/AuthorizationResponse.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
public class AuthorizationResponse : ResponseBase
|
||||
{
|
||||
[DataMember]
|
||||
public int debuglevel { get; private set; }
|
||||
|
||||
/// <summary> Holds the group of the bike (TINK, Konrad, ...).</summary>
|
||||
[DataMember]
|
||||
public string user_group { get; private set; }
|
||||
}
|
||||
}
|
9
TINKLib/Repository/Response/AuthorizationoutResponse.cs
Normal file
9
TINKLib/Repository/Response/AuthorizationoutResponse.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
public class AuthorizationoutResponse : ResponseBase
|
||||
{
|
||||
}
|
||||
}
|
22
TINKLib/Repository/Response/BikeInfoAvailable.cs
Normal file
22
TINKLib/Repository/Response/BikeInfoAvailable.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
public class BikeInfoAvailable : BikeInfoBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Position of the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public string gps { 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; }
|
||||
}
|
||||
}
|
76
TINKLib/Repository/Response/BikeInfoBase.cs
Normal file
76
TINKLib/Repository/Response/BikeInfoBase.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using System.Runtime.Serialization;
|
||||
using TINK.Repository.Response;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds info about a single bike.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class BikeInfoBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public int bike { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id of the station.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public int? 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 "TINK", "Konrad".
|
||||
/// </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 manual lock. </td><td>TextToTypeHelper.GetIsNonBikeComputerBike</td></tr>
|
||||
/// <tr><td> </td><td>Bike with a bord computer. </td><td></td></tr>
|
||||
/// <tr><td>? </td><td>Bike with a bluetooth lock.</td><td></td></tr>
|
||||
/// </table>
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public string system { get; private set; }
|
||||
|
||||
#if !NOTARIFFDESCRIPTION
|
||||
/// <summary> Holds the tariff information for a bike. </summary>
|
||||
[DataMember]
|
||||
public TariffDescription tariff_description { get; private set; }
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Textual description of response.
|
||||
/// </summary>
|
||||
/// <returns>Object as text.</returns>
|
||||
public new 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)}.";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
32
TINKLib/Repository/Response/BikeInfoReservedBooked.cs
Normal file
32
TINKLib/Repository/Response/BikeInfoReservedBooked.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
public class BikeInfoReservedOrBooked : BikeInfoAvailable
|
||||
{
|
||||
/// <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;}
|
||||
|
||||
}
|
||||
}
|
18
TINKLib/Repository/Response/BikesAvailableResponse.cs
Normal file
18
TINKLib/Repository/Response/BikesAvailableResponse.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information about all bikes and is used for deserialization of copri answer.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class BikesAvailableResponse : ResponseBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary of bikes.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Dictionary<int, BikeInfoAvailable> bikes { get; private set; }
|
||||
}
|
||||
}
|
14
TINKLib/Repository/Response/BikesReservedOccupiedResponse.cs
Normal file
14
TINKLib/Repository/Response/BikesReservedOccupiedResponse.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
public class BikesReservedOccupiedResponse : ResponseBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary of bikes.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Dictionary<int, BikeInfoReservedOrBooked> bikes_occupied { get; private set; }
|
||||
}
|
||||
}
|
27
TINKLib/Repository/Response/JsonConvert.cs
Normal file
27
TINKLib/Repository/Response/JsonConvert.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using TINK.Repository.Exception;
|
||||
|
||||
namespace TINK.Repository.Response
|
||||
{
|
||||
public static class JsonConvert
|
||||
{
|
||||
/// <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)
|
||||
{
|
||||
throw new DeserializationException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static string SerializeObject(object value) => Newtonsoft.Json.JsonConvert.SerializeObject(value);
|
||||
}
|
||||
}
|
15
TINKLib/Repository/Response/ReservationBookingResponse.cs
Normal file
15
TINKLib/Repository/Response/ReservationBookingResponse.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information about a booking request and is used for deserialization of copri answer.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class ReservationBookingResponse : BikesReservedOccupiedResponse
|
||||
{
|
||||
/// <summary> Booking code for BC- bikes. </summary>
|
||||
[DataMember]
|
||||
public string timeCode { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information about a cancel booking request and is used for deserialization of copri answer.
|
||||
/// </summary>
|
||||
public class ReservationCancelReturnResponse : BikesReservedOccupiedResponse
|
||||
{
|
||||
}
|
||||
}
|
32
TINKLib/Repository/Response/ResponseBase.cs
Normal file
32
TINKLib/Repository/Response/ResponseBase.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
public class ResponseBase
|
||||
{
|
||||
[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; }
|
||||
|
||||
[DataMember]
|
||||
public string copri_version { get; private set; }
|
||||
|
||||
/// <summary> Textual description of response. </summary>
|
||||
public new 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}\".";
|
||||
}
|
||||
}
|
||||
}
|
25
TINKLib/Repository/Response/ResponseContainer.cs
Normal file
25
TINKLib/Repository/Response/ResponseContainer.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
[DataContract]
|
||||
public class ResponseContainer<T>
|
||||
{
|
||||
[DataMember]
|
||||
public T tinkjson { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Serializes object to string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (tinkjson == null)
|
||||
{
|
||||
return "Response container does not hold no entry.";
|
||||
}
|
||||
|
||||
return tinkjson.ToString();
|
||||
}
|
||||
}
|
||||
}
|
240
TINKLib/Repository/Response/ResponseHelper.cs
Normal file
240
TINKLib/Repository/Response/ResponseHelper.cs
Normal file
|
@ -0,0 +1,240 @@
|
|||
using System.Linq;
|
||||
using TINK.Model.Repository.Exception;
|
||||
using TINK.MultilingualResources;
|
||||
using TINK.Repository.Exception;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
public static class ResponseHelper
|
||||
{
|
||||
public const string RESPONSE_OK = "OK";
|
||||
|
||||
/// <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 cancel reservation. </summary>
|
||||
public const string BIKES_CANCELREQUEST_ACTIONTEXT = "Aufheben der Reservierung 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 addess 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">Sessiong cookie of logged in user.</param>
|
||||
/// <param name="bookingResponse">Response to check.</param>
|
||||
/// <returns></returns>
|
||||
public static BikeInfoReservedOrBooked GetIsReserveResponseOk(
|
||||
this ReservationBookingResponse bookingResponse,
|
||||
int bikeId)
|
||||
{
|
||||
GetIsResponseOk(bookingResponse, string.Format(AppResources.ExceptionTextReservationBikeFailedGeneral, 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.ExceptionTextReservationBikeFailedUnavailalbe,
|
||||
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,
|
||||
int bikeId)
|
||||
{
|
||||
GetIsResponseOk(bookingResponse, string.Format(AppResources.ExceptionTextRentingBikeFailedGeneral, 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.ExceptionTextRentingBikeFailedUnavailalbe,
|
||||
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 T GetIsResponseOk<T>(this T response, string textOfAction) where T : ResponseBase
|
||||
{
|
||||
if (response == 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 cancel or booking request is ok.</summary>
|
||||
/// <param name="cancelBookingResponse">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 ReservationCancelReturnResponse GetIsCancelReservationResponseOk(
|
||||
this ReservationCancelReturnResponse cancelBookingResponse,
|
||||
int bikeId)
|
||||
{
|
||||
GetIsResponseOk<ResponseBase>(cancelBookingResponse, BIKES_CANCELREQUEST_ACTIONTEXT);
|
||||
|
||||
// Get bike which has to be booked.
|
||||
var l_oBikeInfo = cancelBookingResponse?.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId);
|
||||
if (l_oBikeInfo != null)
|
||||
{
|
||||
throw new ReturnBikeException(
|
||||
cancelBookingResponse,
|
||||
$"{BIKES_CANCELREQUEST_ACTIONTEXT} Aufruf wurde erfolgreich ausgeführt, aber Rad ist noch in Liste der reservierten Räder enthalten.");
|
||||
}
|
||||
|
||||
return cancelBookingResponse;
|
||||
}
|
||||
|
||||
/// <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 ReservationCancelReturnResponse GetIsReturnBikeResponseOk(
|
||||
this ReservationCancelReturnResponse returnBikeResponse,
|
||||
int 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 = BIKES_OCCUPIED_REQUEST_NONE_FILE.Replace(@"""authcookie"": """"", @"""authcookie"": """ + (p_strSesstionCookie ?? string.Empty) + @"""");
|
||||
return CopriCallsStatic.DeserializeBikesOccupiedResponse(l_oJson);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds an empty bikes occupied response.
|
||||
/// </summary>
|
||||
private const string BIKES_OCCUPIED_REQUEST_NONE_FILE = @"
|
||||
{
|
||||
""tinkjson"": {
|
||||
""response_state"": ""OK"",
|
||||
""bikes_occupied"": { },
|
||||
""authcookie"": """",
|
||||
""response"": ""user_bikes_occupied"",
|
||||
""apiserver"": ""https://tinkwwp.copri-bike.de""
|
||||
}
|
||||
}";
|
||||
}
|
||||
}
|
43
TINKLib/Repository/Response/StationsAllResponse.cs
Normal file
43
TINKLib/Repository/Response/StationsAllResponse.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Model.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the information about all stations and is used for deserialization of copri answer.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class StationsAllResponse : ResponseBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds info about a single station.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class StationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique id of the station.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public int 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 string gps { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary of bikes.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Dictionary<int, StationInfo> stations { get; private set; }
|
||||
}
|
||||
}
|
8
TINKLib/Repository/Response/SubmitFeedbackResponse.cs
Normal file
8
TINKLib/Repository/Response/SubmitFeedbackResponse.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using TINK.Model.Repository.Response;
|
||||
|
||||
namespace TINK.Repository.Response
|
||||
{
|
||||
public class SubmitFeedbackResponse : ResponseBase
|
||||
{
|
||||
}
|
||||
}
|
47
TINKLib/Repository/Response/TariffDescription.cs
Normal file
47
TINKLib/Repository/Response/TariffDescription.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using System.Runtime.Serialization;
|
||||
|
||||
namespace TINK.Repository.Response
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds tariff info for a single bike.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public record TariffDescription
|
||||
{
|
||||
/// <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; }
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue