Initial version.

This commit is contained in:
Oliver Hauff 2021-05-13 20:03:07 +02:00
parent 193aaa1a56
commit b72c67a53e
228 changed files with 25924 additions and 0 deletions

View 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;
}
}
}
}

View 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);
}
}
}
}

View 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;
}
}
}

View file

@ -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";
}
}

View file

@ -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";
}
}

View 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; }
}
}

View file

@ -0,0 +1,6 @@
namespace TINK.Model.Repository.Exception
{
public class CallNotRequiredException : System.Exception
{
}
}

View 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)
{
}
}
}

View 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)
{
}
}
}

View 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)
{ }
}
}

View 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;
}
}
}

View 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; }
}
}

View 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;
}
}

View 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)
{ }
}
}

View 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)
{
}
}
}

View 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;
}
}
}

View 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)
{
}
}
}

View 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();
}
}

View 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 };
}
}
}
}

View 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();
}
}

View 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}";
}
}
}

View 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; }
}
}

View file

@ -0,0 +1,9 @@
using System.Runtime.Serialization;
namespace TINK.Model.Repository.Response
{
[DataContract]
public class AuthorizationoutResponse : ResponseBase
{
}
}

View 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; }
}
}

View 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)}.";
}
}
}

View 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;}
}
}

View 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; }
}
}

View 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; }
}
}

View 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);
}
}

View 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; }
}
}

View file

@ -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
{
}
}

View 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}\".";
}
}
}

View 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();
}
}
}

View 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""
}
}";
}
}

View 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; }
}
}

View file

@ -0,0 +1,8 @@
using TINK.Model.Repository.Response;
namespace TINK.Repository.Response
{
public class SubmitFeedbackResponse : ResponseBase
{
}
}

View 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; }
}
}