using System; using System.Threading.Tasks; using Serilog; using ShareeBike.MultilingualResources; using ShareeBike.Services.BluetoothLock; using ShareeBike.Services.BluetoothLock.Exception; using ShareeBike.Services.BluetoothLock.Tdo; using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler; using ShareeBike.ViewModel.Bikes; namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command { public static class ConnectAndGetStateCommand { /// /// Possible steps of connecting or disconnecting a lock. /// public enum Step { ConnectLock, GetLockingState, } /// /// Possible steps of connecting or disconnecting a lock. /// public enum State { BluetoothOff, NoLocationPermission, LocationServicesOff, OutOfReachError, GeneralConnectLockError, } /// /// Interface to notify view model about steps/ state changes of connecting or disconnecting lock process. /// public interface IConnectAndGetStateCommandListener { /// /// Reports current step. /// /// Current step to report. void ReportStep(Step currentStep); /// /// Reports current state. /// /// Current state to report. /// Message describing the current state. /// Task ReportStateAsync(State currentState, string message); } /// /// Connect or disconnect lock. /// /// /// /// public static async Task InvokeAsync( IBikeInfoMutable bike, ILocksService lockService, IConnectAndGetStateCommandListener listener = null) { // Invokes member to notify about step being started. void InvokeCurrentStep(Step step) { if (listener == null) return; try { listener.ReportStep(step); } catch (Exception exception) { Log.ForContext().Error("An exception {@exception} was thrown invoking step-action for step {step} ", exception, step); } } // Invokes member to notify about state change. async Task InvokeCurrentStateAsync(State state, string message) { if (listener == null) return; try { await listener.ReportStateAsync(state, message); } catch (Exception exception) { Log.ForContext().Error("An exception {@exception} was thrown invoking state-action for state {state} ", exception, state); } } // Connect lock InvokeCurrentStep(Step.ConnectLock); LockInfoTdo result = null; var continueConnect = true; var retryCount = 1; while (continueConnect && result == null) { try { result = await lockService.ConnectAsync( new LockInfoAuthTdo.Builder { Id = bike.LockInfo.Id, Guid = bike.LockInfo.Guid, K_seed = bike.LockInfo.Seed, K_u = bike.LockInfo.UserKey }.Build(), lockService.TimeOut.GetSingleConnect(retryCount)); Log.ForContext().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", bike.Id, bike.LockInfo.State); } catch (Exception exception) { Log.ForContext().Information("Connection to lock of bike {bikeId} failed. ", bike.Id); if (exception is ConnectBluetoothNotOnException) { continueConnect = false; Log.ForContext().Error("Bluetooth not on."); await InvokeCurrentStateAsync(State.BluetoothOff, exception.Message); } else if (exception is ConnectLocationPermissionMissingException) { continueConnect = false; Log.ForContext().Error("Location permission missing."); await InvokeCurrentStateAsync(State.NoLocationPermission, exception.Message); } else if (exception is ConnectLocationOffException) { continueConnect = false; Log.ForContext().Error("Location services not on."); await InvokeCurrentStateAsync(State.LocationServicesOff, exception.Message); } else if (exception is OutOfReachException) { continueConnect = false; Log.ForContext().Error("Lock is out of reach."); await InvokeCurrentStateAsync(State.OutOfReachError, exception.Message); } else { Log.ForContext().Error("{@exception}", exception); string message; if (retryCount < 2) { message = AppResources.ErrorConnectLock; } else if (retryCount < 3) { message = AppResources.ErrorConnectLockEscalationLevel1; } else { message = AppResources.ErrorConnectLockEscalationLevel2; continueConnect = false; } await InvokeCurrentStateAsync(State.GeneralConnectLockError, message); } if (continueConnect) { retryCount++; continue; } throw; } } // Get locking state InvokeCurrentStep(Step.GetLockingState); bike.LockInfo.State = result?.State?.GetLockingState() ?? LockingState.UnknownDisconnected; if (bike.LockInfo.State == LockingState.UnknownDisconnected) { // Do not display any messages here, because search is implicit. Log.ForContext().Error("Lock is still not connected."); } bike.LockInfo.Guid = result?.Guid ?? new Guid(); } } }