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. /// Collection of bikes. public async Task> GetBikesAsync() { var bikesAvailableResponse = await Server.GetBikesAvailable(); 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, (await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values, Mail, DateTimeProvider, Bikes.BikeInfoNS.BC.DataSource.Cache), bikesAvailableResponse.GeneralData, bikesAvailableResponse.Exception); } var bikesOccupiedResponse = await Server.GetBikesOccupied(); 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, 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); 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); } } }