using System; using System.Collections.Generic; using System.Threading.Tasks; using MonkeyCache.FileStore; using TINK.Model.Bikes.BikeInfoNS.BluetoothLock; using TINK.Model.Connector; using TINK.Model.Device; using TINK.Model.Services.CopriApi; using TINK.Repository.Request; using TINK.Repository.Response; namespace TINK.Repository { public class CopriCallsMonkeyStore : ICopriCache { /// Prevents concurrent communictation. private object monkeyLock = new object(); /// Builds requests. private IRequestBuilder requestBuilder; public const string BIKESAVAILABLE = @"{ ""copri_version"" : ""4.1.0.0"", ""bikes"" : {}, ""response_state"" : ""OK"", ""apiserver"" : ""https://app.tink-konstanz.de"", ""authcookie"" : """", ""response"" : ""bikes_available"" }"; /// Gets an empty response. /// Version of empty response. /// Response. public static BikesAvailableResponse GetEmptyBikesAvailableResponse(string copriVersion) => JsonConvertRethrow.DeserializeObject(BIKESAVAILABLE.Replace("4.1.0.0", copriVersion)); #if !COPRIVERSION41 public const string BIKESOCCUPIED = @"{ ""debuglevel"" : ""1"", ""user_id"" : """", ""response"" : ""user_bikes_occupied"", ""user_group"" : [ ""Konrad"", ""TINK"" ], ""authcookie"" : """", ""response_state"" : ""OK"", ""bikes_occupied"" : {}, ""copri_version"" : ""4.1.0.0"", ""apiserver"" : ""https://app.tink-konstanz.de"" }"; #endif /// Gets an empty response. /// Version of empty response. /// Response. public static BikesReservedOccupiedResponse GetEmptyBikesReservedOccupiedResponse(string copriVersion) => JsonConvertRethrow.DeserializeObject(BIKESOCCUPIED.Replace("4.1.0.0", copriVersion)); #if !COPRIVERSION41 /// Version COPRI 4.0. or earlier public const string STATIONSALL = @"{ ""apiserver"" : """", ""authcookie"" : """", ""response"" : ""stations_all"", ""copri_version"" : ""4.1.0.0"", ""stations"" : {}, ""response_state"" : ""OK"" }"; #else /// Version COPRI 4.1 or later. public const string STATIONSALL = @"{ ""uri_primary"": """" ""uri_operator_array"": [ ] ""authcookie"" : """", ""response"" : ""stations_all"", ""copri_version"" : ""4.1.0.0"", ""stations"" : {}, ""response_state"" : ""OK"" }"; #endif #if !COPRIVERSION41 /// Version COPRI 4.0. or earlier public const string AUTHORIZATION = @"{ ""user_group"" : [ """" ], ""response"" : ""authorization"", ""response_state"" : ""OK"", ""copri_version"" : ""4.1.0.0"" }"; #else /// Version COPRI 4.0. or earlier public const string AUTHORIZATION = @"{ ""user_group"" : [], ""response"" : ""authorization"", ""response_state"" : ""OK"", ""copri_version"" : ""4.1.0.0"" }"; #endif /// Gets an empty response. /// Version of empty response. /// Response. public static StationsAvailableResponse GetEmptyStationsAllResponse(string copriVersion) => JsonConvertRethrow.DeserializeObject(STATIONSALL.Replace("4.1.0.0", copriVersion)); /// /// Holds the seconds after which station and bikes info is considered to be invalid. /// Default value 1s. /// private TimeSpan ExpiresAfter { get; } /// Returns false because cached values are returned. public bool IsConnected => false; /// Gets the merchant id. public string MerchantId => requestBuilder.MerchantId; /// Gets the merchant id. public string SessionCookie => requestBuilder.SessionCookie; /// Initializes a instance of the copri monkey store object. /// Id of the merchant. /// Two letter ISO language name. /// Session cookie if user is logged in, null otherwise. public CopriCallsMonkeyStore( string merchantId, string uiIsoLangugageName, string sessionCookie = null, TimeSpan? expiresAfter = null) { ExpiresAfter = expiresAfter ?? TimeSpan.FromSeconds(1); requestBuilder = string.IsNullOrEmpty(sessionCookie) ? new RequestBuilder(merchantId, uiIsoLangugageName) as IRequestBuilder : new RequestBuilderLoggedIn(merchantId, uiIsoLangugageName, sessionCookie); // Ensure that store holds valid entries. if (!Barrel.Current.Exists(requestBuilder.GetBikesAvailable())) { AddToCache(JsonConvertRethrow.DeserializeObject(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(JsonConvertRethrow.DeserializeObject(BIKESOCCUPIED), new TimeSpan(0)); } if (!Barrel.Current.Exists(requestBuilder.GetStations())) { AddToCache(JsonConvertRethrow.DeserializeObject(STATIONSALL), new TimeSpan(0)); } } public Task DoReserveAsync(string bikeId, Uri operatorUri) { throw new System.Exception("Reservierung im Offlinemodus nicht möglich!"); } public Task DoCancelReservationAsync(string bikeId, Uri operatorUri) { throw new System.Exception("Abbrechen einer Reservierung im Offlinemodus nicht möglich!"); } public Task CalculateAuthKeysAsync(string bikeId, Uri operatorUri) => throw new System.Exception("Schlosssuche im Offlinemodus nicht möglich!"); public Task StartReturningBike( string bikeId, Uri operatorUri) => throw new System.Exception("Benachrichtigung von start der Rückgabe im Offlinemodus nicht möglich!"); public Task UpdateLockingStateAsync( string bikeId, lock_state state, Uri operatorUri, LocationDto geolocation, double batteryLevel, IVersionInfo versionInfo) => throw new System.Exception("Aktualisierung des Schlossstatuses im Offlinemodus nicht möglich!"); public Task DoBookAsync(Uri operatorUri, string bikeId, Guid guid, double batteryPercentage, LockingAction? nextAction = null) => throw new System.Exception("Buchung im Offlinemodus nicht möglich!"); /// Books a bike and starts opening bike. /// Id of the bike to book. /// Holds the uri of the operator or null, in case of single operator setup. /// Response on booking request. public Task BookAvailableAndStartOpeningAsync( string bikeId, Uri operatorUri) => throw new System.Exception("Buchung von verfügbarem Rad mit Start von Schlossöffnen ist im Offlinemodus nicht möglich!"); /// Books a bike and starts opening bike. /// Id of the bike to book. /// Holds the uri of the operator or null, in case of single operator setup. /// Response on booking request. public Task BookReservedAndStartOpeningAsync( string bikeId, Uri operatorUri) => throw new System.Exception("Buchung von reserviertem Rad mit Start von Schlossöffnen ist im Offlinemodus nicht möglich!"); public Task DoReturn( string bikeId, LocationDto geolocation, ISmartDevice smartDevice, Uri operatorUri) => throw new System.Exception("Rückgabe im Offlinemodus nicht möglich!"); /// Returns a bike and starts closing. /// Id of the bike to return. /// 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 Task ReturnAndStartClosingAsync( string bikeId, ISmartDevice smartDevice, Uri operatorUri) => throw new System.Exception("Rückgabe mit Schloss schließen Befehl im Offlinemodus nicht möglich!"); public Task DoSubmitFeedback(string bikeId, int? currentChargeBars, string message, bool isBikeBroken, Uri operatorUri) => throw new System.Exception("Übermittlung von Feedback im Offlinemodus nicht möglich!"); /// Submits mini survey to copri server. /// Collection of answers. public Task DoSubmitMiniSurvey(IDictionary answers) => throw new System.Exception("Übermittlung von der Miniumfrage im Offlinemodus nicht möglich!"); public Task DoAuthorizationAsync(string p_strMailAddress, string p_strPassword, string p_strDeviceId) { throw new System.Exception("Anmelden im Offlinemodus nicht möglich!"); } public Task DoAuthoutAsync() { throw new System.Exception("Abmelden im Offlinemodus nicht möglich!"); } public async Task GetBikesAvailableAsync() { var l_oBikesAvailableTask = new TaskCompletionSource(); lock (monkeyLock) { l_oBikesAvailableTask.SetResult(Barrel.Current.Get(requestBuilder.GetBikesAvailable())); } return await l_oBikesAvailableTask.Task; } public async Task GetBikesOccupiedAsync() { try { var l_oBikesOccupiedTask = new TaskCompletionSource(); lock (monkeyLock) { l_oBikesOccupiedTask.SetResult(Barrel.Current.Get(requestBuilder.GetBikesOccupied())); } return await l_oBikesOccupiedTask.Task; } catch (NotSupportedException) { // No user logged in. await Task.CompletedTask; return ResponseHelper.GetBikesOccupiedNone(); } } public async Task GetStationsAsync() { var l_oStationsAllTask = new TaskCompletionSource(); lock (monkeyLock) { l_oStationsAllTask.SetResult(Barrel.Current.Get(requestBuilder.GetStations())); } return await l_oStationsAllTask.Task; } /// Gets a value indicating whether stations are expired or not. public bool IsStationsExpired { get { lock (monkeyLock) { return Barrel.Current.IsExpired(requestBuilder.GetStations()); } } } /// Adds a stations all response to cache. /// Stations to add. public void AddToCache(StationsAvailableResponse stations) { AddToCache(stations, ExpiresAfter); } /// Adds a stations all response to cache. /// Stations to add. /// Time after which anser is considered to be expired. private void AddToCache(StationsAvailableResponse stations, TimeSpan expiresAfter) { lock (monkeyLock) { Barrel.Current.Add( requestBuilder.GetStations(), JsonConvertRethrow.SerializeObject(stations), expiresAfter); } } /// Gets a value indicating whether stations are expired or not. public bool IsBikesAvailableExpired { get { lock (monkeyLock) { return Barrel.Current.IsExpired(requestBuilder.GetBikesAvailable()); } } } /// Adds a bikes response to cache. /// Bikes to add. public void AddToCache(BikesAvailableResponse bikes) { AddToCache(bikes, ExpiresAfter); } /// Adds a bikes response to cache. /// Bikes to add. /// Time after which anser is considered to be expired. private void AddToCache(BikesAvailableResponse bikes, TimeSpan expiresAfter) { lock (monkeyLock) { Barrel.Current.Add( requestBuilder.GetBikesAvailable(), JsonConvertRethrow.SerializeObject(bikes), expiresAfter); } } /// Gets a value indicating whether stations are expired or not. public bool IsBikesOccupiedExpired { get { lock (monkeyLock) { return Barrel.Current.IsExpired(requestBuilder.GetBikesOccupied()); } } } /// Adds a bikes response to cache. /// Bikes to add. public void AddToCache(BikesReservedOccupiedResponse bikes) { AddToCache(bikes, ExpiresAfter); } /// Adds a bikes response to cache. /// Bikes to add. /// Time after which anser is considered to be expired. private void AddToCache(BikesReservedOccupiedResponse bikes, TimeSpan expiresAfter) { lock (monkeyLock) { Barrel.Current.Add( requestBuilder.GetBikesOccupied(), JsonConvertRethrow.SerializeObject(bikes), expiresAfter); } } } }