using System; 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; namespace TINK.Model.Connector { /// Provides query functionality for a logged in user. public class CachedQueryLoggedIn : BaseLoggedIn, IQuery { /// Cached copri server. 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 typ. Type detected is {copriServer.GetType()}."); } } /// Gets all stations including postions. public async Task> GetBikesAndStationsAsync() { var stationsResponse = await Server.GetStations(); if (stationsResponse.Source == typeof(CopriCallsMonkeyStore) || stationsResponse.Exception != null) { // Stations were read from cache ==> get bikes availbalbe and occupied from cache as well to avoid inconsistencies return new Result( stationsResponse.Source, new StationsAndBikesContainer( stationsResponse.Response.GetStationsAllMutable(), UpdaterJSON.GetBikesAll( (await Server.GetBikesAvailable(true)).Response?.bikes?.Values, (await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values, Mail, DateTimeProvider)), stationsResponse.GeneralData, stationsResponse.Exception); } var bikesAvailableResponse = await Server.GetBikesAvailable(); if (bikesAvailableResponse.Source == typeof(CopriCallsMonkeyStore) || bikesAvailableResponse.Exception != null) { // Bikes avilable were read from cache ==> get bikes occupied from cache as well to avoid inconsistencies return new Result( bikesAvailableResponse.Source, new StationsAndBikesContainer( (await Server.GetStations(true)).Response.GetStationsAllMutable(), UpdaterJSON.GetBikesAll(bikesAvailableResponse.Response?.bikes?.Values, (await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values, Mail, DateTimeProvider)), 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 return new Result( bikesOccupiedResponse.Source, new StationsAndBikesContainer( (await Server.GetStations(true)).Response.GetStationsAllMutable(), UpdaterJSON.GetBikesAll( (await Server.GetBikesAvailable(true)).Response?.bikes?.Values, bikesOccupiedResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider)), bikesOccupiedResponse.GeneralData, bikesOccupiedResponse.Exception); } // Both types bikes could read from copri => update cache Server.AddToCache(stationsResponse); Server.AddToCache(bikesAvailableResponse); Server.AddToCache(bikesOccupiedResponse); var exceptions = new[] { stationsResponse?.Exception, bikesAvailableResponse?.Exception, bikesOccupiedResponse?.Exception }.Where(x => x != null).ToArray(); var stationsMutable = stationsResponse.Response.GetStationsAllMutable(); var bikesMutable = UpdaterJSON.GetBikesAll( bikesAvailableResponse.Response?.bikes?.Values, bikesOccupiedResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider); return new Result( stationsResponse.Source, new StationsAndBikesContainer(stationsMutable, bikesMutable), stationsResponse.GeneralData, exceptions.Length > 0 ? new AggregateException(exceptions) : null); } /// 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, UpdaterJSON.GetBikesAll( bikesAvailableResponse.Response?.bikes?.Values?.Where(bike => bike.GetState() == State.InUseStateEnum.FeedbackPending), (await Server.GetBikesOccupied(true))?.Response?.bikes_occupied?.Values, Mail, DateTimeProvider), 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, UpdaterJSON.GetBikesAll( (await Server.GetBikesAvailable(true)).Response?.bikes?.Values?.Where(bike => bike.GetState() == State.InUseStateEnum.FeedbackPending), bikesOccupiedResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider), 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, UpdaterJSON.GetBikesAll( bikesAvailableResponse?.Response.bikes?.Values?.Select(bike => bike)?.Where(bike => bike.GetState() == State.InUseStateEnum.FeedbackPending), bikesOccupiedResponse?.Response?.bikes_occupied?.Values, Mail, DateTimeProvider), 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, UpdaterJSON.GetBikesAll( bikesAvailableResponse.Response?.bikes?.Values, (await Server.GetBikesOccupied(true)).Response?.bikes_occupied?.Values, Mail, DateTimeProvider), 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, UpdaterJSON.GetBikesAll( (await Server.GetBikesAvailable(true)).Response?.bikes?.Values, bikesOccupiedResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider), 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, UpdaterJSON.GetBikesAll( bikesAvailableResponse.Response?.bikes?.Values, bikesOccupiedResponse.Response?.bikes_occupied?.Values, Mail, DateTimeProvider), bikesAvailableResponse.GeneralData, bikesAvailableResponse.Exception != null || bikesOccupiedResponse.Exception != null ? new AggregateException(new[] { bikesAvailableResponse.Exception, bikesOccupiedResponse.Exception }) : null); } } }