using System; using System.Collections.Generic; using System.Threading.Tasks; using TINK.Model.Bikes.BikeInfoNS.BluetoothLock; using TINK.Model.Connector.Updater; using TINK.Model.Device; using TINK.Model.User.Account; using TINK.Repository; using TINK.Repository.Exception; using TINK.Repository.Request; using TINK.Repository.Response; using TINK.Services.CopriApi; namespace TINK.Model.Connector { public class CommandLoggedIn : BaseLoggedIn, ICommand { /// True if connector has access to copri server, false if cached values are used. public bool IsConnected => CopriServer.IsConnected; /// Is raised whenever login state has changed. public event LoginStateChangedEventHandler LoginStateChanged; /// Constructs a copri query object. /// Server which implements communication. public CommandLoggedIn(ICopriServerBase p_oCopriServer, string sessionCookie, string mail, Func dateTimeProvider) : base(p_oCopriServer, sessionCookie, mail, dateTimeProvider) { } /// /// Logs user in. /// If log in succeeds either and session might be updated if it was no more valid (logged in by an different device). /// If log in fails (password modified) session cookie is set to empty. /// If communication fails an TINK.Repository.Exception is thrown. /// /// Account to use for login. public Task DoLogin(string mail, string password, string deviceId) { if (string.IsNullOrEmpty(mail)) { throw new ArgumentNullException("Can not loging user. Mail address must not be null or empty."); } throw new Exception($"Fehler beim Anmelden von unter {mail}. Benutzer {Mail} ist bereits angemeldet."); } /// Logs user out. public async Task DoLogout() { AuthorizationoutResponse l_oResponse = null; try { l_oResponse = (await CopriServer.DoAuthoutAsync()).GetIsResponseOk(); } catch (AuthcookieNotDefinedException) { // Cookie is no more defined, i.e. no need to logout user at copri because user is already logged out. // Just ignore this error. // User logged out, log in state changed. Notify parent object to update. LoginStateChanged?.Invoke(this, new LoginStateChangedEventArgs()); return; } catch (Exception) { throw; } // User logged out, log in state changed. Notify parent object to update. LoginStateChanged?.Invoke(this, new LoginStateChangedEventArgs()); } /// /// Request to reserve a bike. /// /// Bike to book. public async Task DoReserve(Bikes.BikeInfoNS.BC.IBikeInfoMutable bike) { if (bike == null) { throw new ArgumentNullException("Can not reserve bike. No bike object available."); } BikeInfoReservedOrBooked response; try { response = (await CopriServer.DoReserveAsync(bike.Id, bike.OperatorUri)).GetIsReserveResponseOk(bike.Id); } catch (Exception) { // Exception was not expected or too many subsequent excepitons detected. throw; } bike.Load(response, Mail, Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None); } /// Request to cancel a reservation. /// Bike to cancel reservation. public async Task DoCancelReservation( Bikes.BikeInfoNS.BC.IBikeInfoMutable bike) { if (bike == null) { throw new ArgumentNullException("Can not cancel reservation of bike. No bike object available."); } ReservationCancelReturnResponse response; try { response = (await CopriServer.DoCancelReservationAsync(bike.Id, bike.OperatorUri)).GetIsCancelReservationResponseOk(bike.Id); } catch (Exception) { // Exception was not expected or too many subsequent excepitons detected. throw; } bike.Load(Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None); } /// Get authentication keys. /// Bike to get new keys for. public async Task CalculateAuthKeys(Bikes.BikeInfoNS.BluetoothLock.IBikeInfoMutable bike) { if (bike == null) { throw new ArgumentNullException("Can not calculate auth keys. No bike object available."); } switch (bike.State.Value) { case State.InUseStateEnum.Reserved: case State.InUseStateEnum.Booked: break; default: throw new ArgumentNullException($"Can not calculate auth keys. Unexpected bike state {bike.State.Value} detected."); } BikeInfoReservedOrBooked response; Guid guid = (bike is BikeInfoMutable btBike) ? btBike.LockInfo.Guid : new Guid(); try { response = (await CopriServer.CalculateAuthKeysAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id); } catch (Exception) { // Exception was not expected or too many subsequent excepitons detected. throw; } UpdaterJSON.Load( bike, response, Mail, Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None); } /// Notifies COPRI about start of returning sequence. /// Operator specific call. /// Bike to return. /// Response on notification about start of returning sequence. public async Task StartReturningBike(Bikes.BikeInfoNS.BC.IBikeInfoMutable bike) { if (bike == null) { throw new ArgumentNullException("Can not notify about start returning bike. No bike object available."); } try { (await CopriServer.StartReturningBike( bike.Id, bike.OperatorUri)).GetIsResponseOk("Start returning bike"); } catch (Exception) { // Exception was not expected or too many subsequent excepitons detected. throw; } } /// Updates COPRI lock state for a booked bike. /// Bike to update locking state for. /// Location of the bike. /// Response on updating locking state. public async Task UpdateLockingStateAsync( IBikeInfoMutable bike, LocationDto location) { if (bike == null) { throw new ArgumentNullException("Can not update locking state of bike. No bike object available."); } if (bike.State.Value != State.InUseStateEnum.Booked) { throw new ArgumentNullException($"Can not update locking state of bike. Unexpected booking state {bike.State} detected."); } lock_state? state = null; switch (bike.LockInfo.State) { case LockingState.Open: state = lock_state.unlocked; break; case LockingState.Closed: state = lock_state.locked; break; } if (!state.HasValue) { throw new ArgumentNullException($"Can not update locking state of bike. Unexpected locking state {bike.LockInfo.State} detected."); } try { (await CopriServer.UpdateLockingStateAsync( bike.Id, state.Value, bike.OperatorUri, location, bike.LockInfo.BatteryPercentage, bike.LockInfo.VersionInfo)).GetIsBookingResponseOk(bike.Id); } catch (Exception) { // Exception was not expected or too many subsequent excepitons detected. throw; } } /// Request to book a bike. /// Bike to book. public async Task DoBook(Bikes.BikeInfoNS.BC.IBikeInfoMutable bike) { if (bike == null) { throw new ArgumentNullException(nameof(bike), "Can not book bike. No bike object available."); } BikeInfoReservedOrBooked response; var btBike = bike as BikeInfoMutable; Guid guid = btBike != null ? btBike.LockInfo.Guid : new Guid(); double batteryPercentage = btBike != null ? btBike.LockInfo.BatteryPercentage : double.NaN; response = (await CopriServer.DoBookAsync( bike.Id, guid, batteryPercentage, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id); bike.Load( response, Mail, Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None); } /// /// Books a bike and opens the lock. /// /// Bike to book and open. public async Task BookAndOpenAync(Bikes.BikeInfoNS.CopriLock.IBikeInfoMutable bike) => await Polling.BookAndOpenAync(CopriServer, bike, Mail); /// Request to return a bike. /// Bike to return. /// Position of the bike for bluetooth locks. /// Provides info about hard and software. public async Task DoReturn( Bikes.BikeInfoNS.BC.IBikeInfoMutable bike, LocationDto location = null, ISmartDevice smartDevice = null) { if (bike == null) { throw new ArgumentNullException("Can not return bike. No bike object available."); } DoReturnResponse response = (await CopriServer.DoReturn(bike.Id, location, smartDevice, bike.OperatorUri)).GetIsReturnBikeResponseOk(bike.Id); bike.Load(Bikes.BikeInfoNS.BC.NotifyPropertyChangedLevel.None); return response?.Create() ?? new BookingFinishedModel(); } /// Request to return bike and close the lock. /// Bike to return. /// Provides info about hard and software. public async Task ReturnAndCloseAsync(Bikes.BikeInfoNS.CopriLock.IBikeInfoMutable bike, ISmartDevice smartDevice = null) => await Polling.ReturnAndCloseAync(CopriServer, smartDevice, bike); /// /// Submits feedback to copri server. /// /// Feedback to submit. #if USCSHARP9 public async Task DoSubmitFeedback(ICommand.IUserFeedback userFeedback, Uri opertorUri) => await CopriServer.DoSubmitFeedback(userFeedback.BikeId, userFeedback.Message, userFeedback.IsBikeBroken, opertorUri); #else /// Submits feedback for a renting operation. public async Task DoSubmitFeedback( IUserFeedback userFeedback, Uri opertorUri) => await CopriServer.DoSubmitFeedback(userFeedback.BikeId, userFeedback.CurrentChargeBars, userFeedback.Message, userFeedback.IsBikeBroken, opertorUri); #endif /// Submits mini survey to copri server. /// Collection of answers. public async Task DoSubmitMiniSurvey(IDictionary answers) => await CopriServer.DoSubmitMiniSurvey(answers); public async Task OpenLockAsync(Bikes.BikeInfoNS.CopriLock.IBikeInfoMutable bike) => await CopriServer.OpenAync(bike); public async Task CloseLockAsync(Bikes.BikeInfoNS.CopriLock.IBikeInfoMutable bike) => await CopriServer.CloseAync(bike); } }