using Serilog;
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using TINK.Repository.Exception;
using TINK.Repository.Request;
using TINK.Repository.Response;
using TINK.Model.Logging;
using TINK.Model.Device;
using System.Collections.Generic;
namespace TINK.Repository
{
/// Object which manages calls to copri.
public class CopriCallsHttps : ICopriServer
{
/// Builds requests.
private IRequestBuilder requestBuilder;
/// Initializes a instance of the copri calls https object.
/// Host to connect to.
/// Provides app related info (app name and version, merchantid) to pass to COPRI.
/// Session cookie if user is logged in, null otherwise.
public CopriCallsHttps(
Uri copriHost,
AppContextInfo appContextInfo,
string sessionCookie = null)
{
m_oCopriHost = copriHost
?? throw new System.Exception($"Can not construct {GetType()}- object. Uri of copri host must not be null.");
UserAgent = appContextInfo != null
? appContextInfo.UserAgent
: throw new System.Exception($"Can not construct {GetType()}- object. User agent must not be null or empty.");
requestBuilder = string.IsNullOrEmpty(sessionCookie)
? new RequestBuilder(appContextInfo.MerchantId) as IRequestBuilder
: new RequestBuilderLoggedIn(appContextInfo.MerchantId, sessionCookie);
}
/// Holds the URL for rest calls.
private Uri m_oCopriHost;
/// Spacifies name and version of app.
private string UserAgent { get; }
/// Returns true because value requested form copri server are returned.
public bool IsConnected => true;
/// Gets the merchant id.
public string MerchantId => requestBuilder.MerchantId;
/// Gets the session cookie if user is logged in, an empty string otherwise.
public string SessionCookie => requestBuilder.SessionCookie;
/// Logs user in.
/// Mailaddress of user to log in.
/// Password to log in.
/// Id specifying user and hardware.
/// Response which holds auth cookie
public async Task DoAuthorizationAsync(
string mailAddress,
string password,
string deviceId)
{
return await DoAuthorizationAsync(
m_oCopriHost.AbsoluteUri,
requestBuilder.DoAuthorization(mailAddress, password, deviceId),
() => requestBuilder.DoAuthorization(mailAddress, "********", deviceId),
UserAgent);
}
/// Logs user out.
/// Response which holds auth cookie
public async Task DoAuthoutAsync()
{
return await DoAuthoutAsync(m_oCopriHost.AbsoluteUri, requestBuilder.DoAuthout(), UserAgent);
}
/// Gets bikes available.
/// Response holding list of bikes.
public async Task GetBikesAvailableAsync()
{
return await GetBikesAvailableAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetBikesAvailable(), UserAgent);
}
/// Gets a list of bikes reserved/ booked by acctive user.
/// Response holding list of bikes.
public async Task GetBikesOccupiedAsync()
{
try
{
return await GetBikesOccupiedAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetBikesOccupied(), UserAgent);
}
catch (NotSupportedException)
{
// No user logged in.
await Task.CompletedTask;
return ResponseHelper.GetBikesOccupiedNone();
}
}
/// Get list of stations.
/// List of files.
public async Task GetStationsAsync()
{
var stations = await GetStationsAsync(m_oCopriHost.AbsoluteUri, requestBuilder.GetStations(), UserAgent);
return stations;
}
/// Get authentication keys.
/// Id of the bike to get keys for.
/// Response holding authentication keys.
public async Task GetAuthKeys(string bikeId)
=> await GetAuthKeysAsync(m_oCopriHost.AbsoluteUri, requestBuilder.CalculateAuthParameters(bikeId), UserAgent);
/// Gets booking request response.
/// Id of the bike to book.
/// Holds the uri of the operator or null, in case of single operator setup.
/// Booking response.
public async Task DoReserveAsync(
string bikeId,
Uri operatorUri)
{
return await DoReserveAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.DoReserve(bikeId),
UserAgent);
}
/// Gets canel booking request response.
/// Id of the bike to book.
/// Holds the uri of the operator or null, in case of single operator setup.
/// Response on cancel booking request.
public async Task DoCancelReservationAsync(
string bikeId,
Uri operatorUri)
{
return await DoCancelReservationAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.DoCancelReservation(bikeId),
UserAgent);
}
/// Get authentication keys.
/// Id of the bike to get keys for.
/// Holds the uri of the operator or null, in case of single operator setup.
/// Response holding authentication keys.
public async Task CalculateAuthKeysAsync(
string bikeId,
Uri operatorUri)
=> await GetAuthKeysAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.CalculateAuthParameters(bikeId),
UserAgent);
/// Notifies COPRI about start of returning sequence.
/// Operator specific call.
/// Id of the bike to return.+
/// Holds the uri of the operator or null, in case of single operator setup.
/// Response on notification about start of returning sequence.
public async Task StartReturningBike(
string bikeId,
Uri operatorUri)
=> await DoStartReturningBike(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.StartReturningBike(bikeId),
UserAgent);
/// Updates lock state for a booked bike.
/// Id of the bike to update locking state for.
/// Geolocation of lock.
/// New locking state.
/// Holds the filling level percentage of the battery.
/// Holds the uri of the operator or null, in case of single operator setup.
/// Response on updating locking state.
public async Task UpdateLockingStateAsync(
string bikeId,
LocationDto location,
lock_state state,
double batteryLevel,
Uri operatorUri)=>
await DoUpdateLockingStateAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.UpateLockingState(bikeId, location, state, batteryLevel),
UserAgent);
/// Gets booking request request.
/// Id of the bike to book.
/// Used to publish GUID from app to copri. Used for initial setup of bike in copri.
/// Holds the filling level percentage of the battery.
/// Holds the uri of the operator or null, in case of single operator setup.
/// Requst on booking request.
public async Task DoBookAsync(
string bikeId,
Guid guid,
double batteryPercentage,
Uri operatorUri)
{
return await DoBookAsync(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.DoBook(bikeId, guid, batteryPercentage),
UserAgent);
}
/// Returns a bike.
/// Id of the bike to return.
/// Geolocation of lock.
/// Provides info about hard and software.
/// Holds the uri of the operator or null, in case of single operator setup.
/// Response on returning request.
public async Task DoReturn(
string bikeId,
LocationDto location,
ISmartDevice smartDevice,
Uri operatorUri)
{
return await DoReturn(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.DoReturn(bikeId, location, smartDevice),
UserAgent);
}
/// Submits feedback to copri server.
/// Id of the bike to which the feedback is related to.
/// True if bike is broken.
/// General purpose message or error description.
/// Holds the uri of the operator or null, in case of single operator setup.
/// Response on submitting feedback request.
public async Task DoSubmitFeedback(
string bikeId,
string message,
bool isBikeBroken,
Uri operatorUri) =>
await DoSubmitFeedback(
operatorUri?.AbsoluteUri ?? m_oCopriHost.AbsoluteUri,
requestBuilder.DoSubmitFeedback(bikeId, message, isBikeBroken),
UserAgent);
/// Submits mini survey to copri server.
/// Collection of answers.
public Task DoSubmitMiniSurvey(IDictionary answers)
=> DoSubmitMiniSurvey(
m_oCopriHost.AbsoluteUri,
requestBuilder.DoSubmitMiniSurvey(answers),
UserAgent);
/// Logs user in.
/// Host to connect to.
/// Command to log user in.
/// Response which holds auth cookie
public static async Task DoAuthorizationAsync(
string copriHost,
string command,
Func displayCommand,
string userAgent = null)
{
#if !WINDOWS_UWP
/// Extract session cookie from response.
string response = string.Empty;
try
{
response = await PostAsync(
copriHost,
command,
userAgent,
displayCommand); // Do not include password into exception output when an error 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 CopriCallsStatic.DeserializeResponse(response, (version) => new UnsupportedCopriVersionDetectedException());
#else
return null;
#endif
}
/// Logs user out.
/// Host to connect to.
/// Command to log user out.
public static async Task 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 CopriCallsStatic.DeserializeResponse(l_oLogoutResponse, (version) => new UnsupportedCopriVersionDetectedException());
#else
return null;
#endif
}
///
/// Get list of stations from file.
///
/// URL of the copri host to connect to.
/// Command to get stations.
/// List of files.
public static async Task GetStationsAsync(
string p_strCopriHost,
string p_oCommand,
string userAgent = null)
{
#if !WINDOWS_UWP
string response;
try
{
response = 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 CopriCallsStatic.DeserializeResponse(
response,
(version) => CopriCallsMonkeyStore.GetEmptyStationsAllResponse(version));
#else
return null;
#endif
}
/// Gets a list of bikes from Copri.
/// URL of the copri host to connect to.
/// Command to get bikes.
/// Response holding list of bikes.
public static async Task GetBikesAvailableAsync(
string p_strCopriHost,
string l_oCommand,
string userAgent = null)
{
#if !WINDOWS_UWP
string response;
try
{
response = 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.DeserializeResponse(
response,
(version) => CopriCallsMonkeyStore.GetEmptyBikesAvailableResponse(version));
#else
return null;
#endif
}
/// Gets a list of bikes reserved/ booked by acctive user from Copri.
/// URL of the copri host to connect to.
/// Command to post.
/// Response holding list of bikes.
public static async Task GetBikesOccupiedAsync(
string p_strCopriHost,
string p_oCommand,
string userAgent = null)
{
#if !WINDOWS_UWP
string response;
try
{
response = 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.DeserializeResponse(
response,
(version) => CopriCallsMonkeyStore.GetEmptyBikesReservedOccupiedResponse(version));
#else
return null;
#endif
}
/// Get auth keys from COPRI.
/// Host to connect to.
/// Command to log user in.
/// Response on booking request.
public static async Task 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 JsonConvertRethrow.DeserializeObject>(l_oBikesAvaialbeResponse)?.shareejson;
#else
return null;
#endif
}
/// Gets booking request response.
/// Host to connect to.
/// Command to log user in.
/// Response on booking request.
public static async Task 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 JsonConvertRethrow.DeserializeObject>(l_oBikesAvaialbeResponse)?.shareejson;
#else
return null;
#endif
}
/// Gets canel booking request response.
/// Host to connect to.
/// Command to log user in.
/// Response on cancel booking request.
public static async Task DoCancelReservationAsync(
string copriHost,
string command,
string userAgent = null)
{
#if !WINDOWS_UWP
string l_oBikesAvaialbeResponse;
try
{
l_oBikesAvaialbeResponse = await PostAsync(copriHost, command, userAgent);
}
catch (System.Exception l_oException)
{
if (l_oException.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Reservierung des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
}
if (l_oException.GetIsForbiddenException())
{
throw new WebForbiddenException("Reservierung des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
}
throw;
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject>(l_oBikesAvaialbeResponse)?.shareejson;
#else
return null;
#endif
}
public static async Task DoStartReturningBike(
string copriHost,
string command,
string agent = null)
{
#if !WINDOWS_UWP
string response;
try
{
response = await PostAsync(copriHost, command, agent);
}
catch (System.Exception exception)
{
if (exception.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Benachrichtigung von Start der Rückgabe wegen Netzwerkfehler fehlgeschlagen.", exception);
}
if (exception.GetIsForbiddenException())
{
throw new WebForbiddenException("Benachrichtigung von Start der Rückgabe wegen Netzwerkfehler fehlgeschlagen.", exception);
}
throw;
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject>(response)?.shareejson;
#else
return null;
#endif
}
public static async Task DoUpdateLockingStateAsync(
string copriHost,
string command,
string agent = null)
{
#if !WINDOWS_UWP
string bikesAvaialbeResponse;
try
{
bikesAvaialbeResponse = await PostAsync(copriHost, command, agent);
}
catch (System.Exception exception)
{
if (exception.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", exception);
}
if (exception.GetIsForbiddenException())
{
throw new WebForbiddenException("Aktualisierung des Schlossstatuses wegen Netzwerkfehler fehlgeschlagen.", exception);
}
throw;
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject>(bikesAvaialbeResponse)?.shareejson;
#else
return null;
#endif
}
public static async Task 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 exception)
{
if (exception.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", exception);
}
if (exception.GetIsForbiddenException())
{
throw new WebForbiddenException("Buchung des Fahrrads wegen Netzwerkfehler fehlgeschlagen.", exception);
}
throw;
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject>(l_oBikesAvaialbeResponse)?.shareejson;
#else
return null;
#endif
}
public static async Task DoReturn(
string copriHost,
string command,
string userAgent = null)
{
#if !WINDOWS_UWP
string doReturnResponse;
try
{
doReturnResponse = await PostAsync(copriHost, command, userAgent);
}
catch (System.Exception l_oException)
{
if (l_oException.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Rückgabe des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
}
if (l_oException.GetIsForbiddenException())
{
throw new WebForbiddenException("Rückgabe des Fahrrads aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
}
throw;
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject>(doReturnResponse)?.shareejson;
#else
return null;
#endif
}
public async Task DoSubmitFeedback(
string copriHost,
string command,
string userAgent = null)
{
#if !WINDOWS_UWP
string userFeedbackResponse;
try
{
userFeedbackResponse = await PostAsync(copriHost, command, userAgent);
}
catch (System.Exception l_oException)
{
if (l_oException.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Senden der Rückmeldung aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
}
if (l_oException.GetIsForbiddenException())
{
throw new WebForbiddenException("Senden der Rückmeldung aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
}
throw;
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject>(userFeedbackResponse)?.shareejson;
#else
return null;
#endif
}
/// Submits mini survey to copri server.
public async Task DoSubmitMiniSurvey(
string copriHost,
string command,
string userAgent = null)
{
#if !WINDOWS_UWP
string miniSurveyResponse;
try
{
miniSurveyResponse = await PostAsync(copriHost, command, userAgent);
}
catch (System.Exception l_oException)
{
if (l_oException.GetIsConnectFailureException())
{
throw new WebConnectFailureException("Senden der Miniumfrage aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
}
if (l_oException.GetIsForbiddenException())
{
throw new WebForbiddenException("Senden der der Miniumfrage aufgrund eines Netzwerkfehlers fehlgeschlagen.", l_oException);
}
throw;
}
// Extract bikes from response.
return JsonConvertRethrow.DeserializeObject>(miniSurveyResponse)?.shareejson;
#else
return null;
#endif
}
/// http get- request.
/// Ulr to get info from.
/// response from server
public static async Task 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;
}
/// http- post request.
/// Command to send.
/// Command to display/ log used for error handling.
/// Address of server to communicate with.
/// Response as text.
/// An unused member PostAsyncHttpClient using HttpClient for posting was removed 2020-04-02.
private static async Task PostAsync(
string uRL,
string command,
string userAgent = null,
Func displayCommand = null)
{
if (string.IsNullOrEmpty(command))
{
Log.ForContext().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().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 displayCommandFunc = displayCommand ?? delegate () { return command; };
try
{
#if !WINDOWS_UWP
var l_strHost = uRL;
// Returns a http request.
var request = WebRequest.CreateHttp(l_strHost);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.UserAgent = userAgent;
// Workaround for issue https://bugzilla.xamarin.com/show_bug.cgi?id=57705
// If not KeepAlive is set to true Stream.Write leads arbitrarily to an object disposed exception.
request.KeepAlive = true;
byte[] postData = Encoding.UTF8.GetBytes(command);
request.ContentLength = postData.Length;
// Get the request stream.
using (Stream dataStream = await request.GetRequestStreamAsync())
{
// Write the data to the request stream.
await dataStream.WriteAsync(postData, 0, postData.Length);
}
// Get the response.
var webResponse = await request.GetResponseAsync() as HttpWebResponse;
if (webResponse == null)
{
throw new System.Exception(string.Format("Reserve request failed. Response form from server was not of expected type."));
}
if (webResponse.StatusCode != HttpStatusCode.OK)
{
throw new CommunicationException(string.Format(
"Posting request {0} failed. Expected status code is {1} but was {2}.",
displayCommandFunc(),
HttpStatusCode.OK,
webResponse.StatusCode));
}
string response = string.Empty;
// Get the request stream.
using (Stream l_oDataStream = webResponse.GetResponseStream())
using (StreamReader l_oReader = new StreamReader(l_oDataStream))
{
// Read the content.
response = l_oReader.ReadToEnd();
// Display the content.
Console.WriteLine(response);
// Clean up the streams.
webResponse.Close();
}
Log.ForContext().Verbose("Post command {DisplayCommand} to host {URL} received {ResponseText:j}.", displayCommandFunc(), uRL, response);
return response;
#else
return null;
#endif
}
catch (System.Exception exception)
{
Log.ForContext().InformationOrError("Posting command {DisplayCommand} to host {URL} failed. {Exception}.", displayCommandFunc(), uRL, exception);
throw;
}
}
}
}