using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Serilog; using TINK.Model.Bikes; using TINK.Model.Connector.Updater; using TINK.Model.Services.CopriApi; using TINK.Repository; using TINK.Repository.Response; namespace TINK.Model.Connector { /// Provides query functionality for a logged in user. public class CachedQueryLoggedIn : BaseLoggedIn, IQuery { /// Cached copri server (connection to copri backed up by cache). private ICachedCopriServer Server { get; } /// Constructs a copri query object. /// Server which implements communication. public CachedQueryLoggedIn(ICopriServerBase copriServer, string sessionCookie, string mail, Func dateTimeProvider) : base(copriServer, sessionCookie, mail, dateTimeProvider) { Server = copriServer as ICachedCopriServer; if (Server == null) { throw new ArgumentException($"Copri server is not of expected type. Type detected is {copriServer.GetType()}."); } } /// Gets all stations including positions. public async Task> GetBikesAndStationsAsync() { BikeCollection GetBikeCollection(IEnumerable bikeInfoEnumerable, Bikes.BikeInfoNS.BC.DataSource dataSource) => BikeCollectionFactory.GetBikesAll( null, // Bikes available are no more of interest because count of available bikes at each given station is was added to station object. bikeInfoEnumerable ?? new Dictionary().Values, Mail, DateTimeProvider, dataSource); var stationsResponse = await Server.GetStations(); if (stationsResponse.Source == typeof(CopriCallsMonkeyStore) || stationsResponse.Exception != null) { // Stations were read from cache ==> get bikes available and occupied from cache as well to avoid inconsistencies return new Result( stationsResponse.Source, new StationsAndBikesContainer( stationsResponse.Response.GetStationsAllMutable(), GetBikeCollection(stationsResponse.Response.bikes_occupied?.Values, Bikes.BikeInfoNS.BC.DataSource.Cache)), stationsResponse.GeneralData, stationsResponse.Exception); } // Both types bikes could read from copri => update cache Server.AddToCache(stationsResponse); return new Result( stationsResponse.Source, new StationsAndBikesContainer( stationsResponse.Response.GetStationsAllMutable(), GetBikeCollection(stationsResponse.Response.bikes_occupied?.Values, Bikes.BikeInfoNS.BC.DataSource.Copri)), stationsResponse.GeneralData, stationsResponse?.Exception); } /// Gets bikes occupied. /// Collection of bikes. public async Task> GetBikesOccupiedAsync() { var bikesAvailableResponse = await Server.GetBikesAvailable(false); if (bikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore) || bikesAvailableResponse.Exception != null) { // Bikes available were read from cache ==> get bikes occupied from cache as well to avoid inconsistencies. Log.ForContext().Debug("Bikes available read from cache. Reading bikes occupied from cache as well."); return new Result( bikesAvailableResponse.Source, BikeCollectionFactory.GetBikesAll( bikesAvailableResponse.Response?.bikes?.Values?.Where(bike => bike.GetState() == State.InUseStateEnum.FeedbackPending), (await Server.GetBikesOccupied(true))?.Response?.bikes_occupied?.Values, Mail, DateTimeProvider, Bikes.BikeInfoNS.BC.DataSource.Cache), bikesAvailableResponse.GeneralData, bikesAvailableResponse.Exception); } var bikesOccupiedResponse = await Server.GetBikesOccupied(false); if (bikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore) || bikesOccupiedResponse.Exception != null) { // Bikes occupied were read from cache ==> get bikes available from cache as well to avoid inconsistencies Log.ForContext().Debug("Bikes occupied read from cache. Reread bikes available from cache as well."); return new Result( bikesOccupiedResponse.Source, BikeCollectionFactory.GetBikesAll( (await Server.GetBikesAvailable(true)).Response?.bikes?.Values?.Where(bike => bike.GetState() == State.InUseStateEnum.FeedbackPending), bikesOccupiedResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider, Bikes.BikeInfoNS.BC.DataSource.Cache), bikesOccupiedResponse.GeneralData, bikesOccupiedResponse.Exception); } // Both types bikes could read from copri => update bikes occupied cache. // // Do not add bikes available to cache because this might lead to conflicts calls GetBikesAsync() and bikes with FeedbackPending state are of no use offline. Server.AddToCache(bikesOccupiedResponse); return new Result( bikesOccupiedResponse.Source, BikeCollectionFactory.GetBikesAll( bikesAvailableResponse?.Response.bikes?.Values?.Select(bike => bike)?.Where(bike => bike.GetState() == State.InUseStateEnum.FeedbackPending), bikesOccupiedResponse?.Response?.bikes_occupied?.Values, Mail, DateTimeProvider, Bikes.BikeInfoNS.BC.DataSource.Copri), bikesOccupiedResponse.GeneralData, bikesOccupiedResponse.Exception); } /// Gets bikes available and bikes occupied. /// Uri of the operator host to get bikes from or null if bikes have to be gotten form primary host. /// Id of station which is used for filtering bikes. Null if no filtering should be applied. /// Id of bike which is used for filtering bikes. Null if no filtering should be applied. /// Collection of bikes. public async Task> GetBikesAsync(Uri operatorUri = null, string stationId = null, string bikeId = null) { var bikesAvailableResponse = await Server.GetBikesAvailable(operatorUri: operatorUri, stationId: stationId, bikeId: bikeId); if (bikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore) || bikesAvailableResponse.Exception != null) { // Bikes were read from cache. Log.ForContext().Debug("Bikes available and bikes occupied from cache invoking one single call."); return new Result( bikesAvailableResponse.Source, BikeCollectionFactory.GetBikesAll( bikesAvailableResponse.Response?.bikes?.Values, operatorUri?.AbsoluteUri == null ? (await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values // Get bikes occupied from cache as well to avoid inconsistencies. : bikesAvailableResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider, Bikes.BikeInfoNS.BC.DataSource.Cache), bikesAvailableResponse.GeneralData, bikesAvailableResponse.Exception); } if (operatorUri?.AbsoluteUri != null) { // Both types bikes could read from copri successfully => update cache Server.AddToCache(bikesAvailableResponse, operatorUri, stationId, bikeId); Log.ForContext().Debug("Bikes available and occupied read successfully from server invoking one single request."); return new Result( bikesAvailableResponse.Source, BikeCollectionFactory.GetBikesAll( bikesAvailableResponse.Response?.bikes?.Values, bikesAvailableResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider, Bikes.BikeInfoNS.BC.DataSource.Copri), bikesAvailableResponse.GeneralData, bikesAvailableResponse.Exception != null ? new AggregateException(new[] { bikesAvailableResponse.Exception }) : null); } /// Legacy implementation: GetBikesOccupied are not returned in call. /// A separate call is required to retrieve all bikes. var bikesOccupiedResponse = await Server.GetBikesOccupied(); /* Only query bikes occupied if operator uri is unknown. */ if (bikesOccupiedResponse.Source == typeof(CopriCallsMonkeyStore) || bikesOccupiedResponse.Exception != null) { // Bikes occupied were read from cache ==> get bikes available from cache as well to avoid inconsistencies Log.ForContext().Debug("Bikes occupied read from cache. Reread bikes available from cache as well."); return new Result( bikesOccupiedResponse.Source, BikeCollectionFactory.GetBikesAll( (await Server.GetBikesAvailable(true, operatorUri, stationId, bikeId)).Response?.bikes?.Values, bikesOccupiedResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider, Bikes.BikeInfoNS.BC.DataSource.Cache), bikesOccupiedResponse.GeneralData, bikesOccupiedResponse.Exception); } // Both types bikes could read from copri => update cache Server.AddToCache(bikesAvailableResponse, operatorUri, stationId, bikeId); Server.AddToCache(bikesOccupiedResponse); Log.ForContext().Debug("Bikes available and occupied read successfully from server."); return new Result( bikesAvailableResponse.Source, BikeCollectionFactory.GetBikesAll( bikesAvailableResponse.Response?.bikes?.Values, bikesOccupiedResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider, Bikes.BikeInfoNS.BC.DataSource.Copri), bikesAvailableResponse.GeneralData, bikesAvailableResponse.Exception != null || bikesOccupiedResponse.Exception != null ? new AggregateException(new[] { bikesAvailableResponse.Exception, bikesOccupiedResponse.Exception }) : null); } } }