/// <summary> /// Attempts to push API responses into the cache store. /// </summary> /// <typeparam name="T">The strong type of the expected API result.</typeparam> /// <param name="builder">The object that builds the API route endpoint.</param> /// <param name="response">The API response.</param> /// <returns>True if the operation was successful, false otherwise.</returns> public bool Push <T>(IApiEndpointBuilder builder, IApiResponse <T> response) where T : class { if (builder == null) { throw new ArgumentNullException("builder"); } string endpoint = builder.ToString(); if (endpoint.NullOrEmpty()) { return(false); } IResponseCacheItem item; if (Cache.TryGetValue(endpoint, out item)) { if (item is IResponseCacheItem <T> ) // avoid raising exceptions when improperly invoked. { ((IResponseCacheItem <T>)item).UpdateResponse(response); return(true); } return(false); } else { item = new ResponseCacheItem <T>(response, builder); return(Cache.TryAdd(endpoint, item)); } }
/// <summary> /// Attempts to access the internal cache and retrieve a response cache item without querying the API. /// <para>If the endpoint is not present in the cache yet, null is returned, but the endpoint is added to the cache.</para> /// <para>If the endpoint is present, it means the request is being processed. In this case we will wait on the processing to end before returning a result.</para> /// </summary> /// <typeparam name="T">The strong type of the expected API result.</typeparam> /// <param name="builder">The object that builds the API route endpoint.</param> /// <param name="pushEmpty">True to push an empty value into the cache if the endpoint isn't present yet.</param> /// <param name="retries">The amount of failed push attempts.</param> /// <returns>Returns an API response cache item if successful, null otherwise.</returns> private IResponseCacheItem <T> Get <T>(IApiEndpointBuilder builder, bool pushEmpty, int retries) where T : class { if (builder == null) { throw new ArgumentNullException("builder"); } string endpoint = builder.ToString(); IResponseCacheItem cacheItem; if (Cache.TryGetValue(endpoint, out cacheItem)) { while (cacheItem.IsFresh && cacheItem.State == CacheItemStateEnum.Processing) { using (AutoResetEvent signal = new AutoResetEvent(false)) { signal.WaitOne(50); } } if (cacheItem.IsFresh && cacheItem.State == CacheItemStateEnum.Cached) { return(cacheItem as IResponseCacheItem <T>); // avoid raising exceptions when improperly invoked. } IResponseCacheItem value; Cache.TryRemove(endpoint, out value); } if (!pushEmpty || Push <T>(builder, null) || retries > 1) // max retries, sanity. { return(null); } else { return(Get <T>(builder, true, ++retries)); // retry push. } }
/// <summary> /// When necessary, throttles requests in order to prevent flooding the API endpoints. /// </summary> /// <typeparam name="T">The strong type of the expected API result against which to deserialize JSON.</typeparam> /// <param name="builder">The object that builds the API route endpoint.</param> /// <returns>The API response object.</returns> public IApiResponse <T> Throttle <T>(IApiEndpointBuilder builder) where T : class { while (ConcurrentRequests >= MaxConcurrentRequests) { using (AutoResetEvent signal = new AutoResetEvent(false)) { signal.WaitOne(100); } } if (_requestStartTime.Count >= MaxRequestsInTimeframe) { DateTime start; if (_requestStartTime.TryDequeue(out start)) { TimeSpan delta = DateTime.Now - start; TimeSpan delay = SlidingTimeframe - delta; if (delay > TimeSpan.Zero) { using (AutoResetEvent signal = new AutoResetEvent(false)) { signal.WaitOne(delay); } } } } _requestStartTime.Enqueue(DateTime.Now); return(PerformRequest <T>(builder.ToString())); }
/// <summary> /// Creates a cache item out of an API response. /// </summary> /// <param name="response">The API response object.</param> /// <param name="builder">The API endpoint builder that generated the endpoint the response came from.</param> public ResponseCacheItem(IApiResponse <T> response, IApiEndpointBuilder builder) { if (builder == null) { throw new ArgumentNullException("builder"); } LifeSpan = builder.Client.Cache.GetCacheLifeSpan(builder) ?? DefaultLifeSpan; UpdateResponse(response); }
/// <summary> /// Gets the life span for cache items based on the provided instance of <see cref="IApiEndpointBuilder"/>. /// </summary> /// <param name="builder">The object that builds the API route endpoint.</param> /// <returns>The life span duration after which a cache item is no longer considered fresh.</returns> public TimeSpan?GetCacheLifeSpan(IApiEndpointBuilder builder) { if (builder == null) { throw new ArgumentNullException("builder"); } ApiMethodEnum method = builder.ApiMethod; return(method.GetCacheLifeSpan(Client)); }
/// <summary> /// Processes a request, attempting to retrieve results from the cache whenever possible, and making API calls otherwise. /// </summary> /// <typeparam name="T">The strong type of the expected API result against which to deserialize JSON.</typeparam> /// <param name="builder">The object that builds the API route endpoint.</param> /// <returns>A wrapper object around the result object. It's source is either the API, the internal cache, or raised exceptions.</returns> public IBridgeResponseCollection <T> ProcessRequest <T>(IApiEndpointBuilder builder) where T : class { if (builder == null) { throw new ArgumentNullException("builder"); } IApiResponse <T> response = GetResponse <T>(builder); IBridgeResponseCollection <T> result = new BridgeResponseCollection <T>(this, builder, response); return(result); }
/// <summary> /// Attempts to fetch the response object from the cache instead of directly from the API. /// </summary> /// <typeparam name="T">The strong type of the expected API result against which to deserialize JSON.</typeparam> /// <param name="builder">The object that builds the API route endpoint.</param> /// <returns>The API response object.</returns> private IApiResponse <T> FetchFromCache <T>(IApiEndpointBuilder builder) where T : class { IResponseCacheItem <T> cacheItem = Client.Cache.Get <T>(builder, true); if (cacheItem != null) { IApiResponse <T> response = cacheItem.Response; response.Source = ResultSourceEnum.Cache; return(response); } return(null); }
/// <summary> /// Creates a Bridge response object that contains a collection of result items. /// </summary> /// <param name="requestHandler">The request handler.</param> /// <param name="builder">The API endpoint builder.</param> /// <param name="response">The API response object.</param> internal BridgeResponseCollection(IRequestHandler requestHandler, IApiEndpointBuilder builder, IApiResponse <T> response) { if (requestHandler == null) { throw new ArgumentNullException("requestHandler"); } if (builder == null) { throw new ArgumentNullException("builder"); } if (response == null) { throw new ArgumentNullException("response"); } _requestHandler = requestHandler; EndpointBuilder = builder; Response = response; }
/// <summary> /// Produces the response object based on the API endpoint builder provided. /// </summary> /// <typeparam name="T">The strong type of the expected API result against which to deserialize JSON.</typeparam> /// <param name="builder">The object that builds the API route endpoint.</param> /// <returns>The API response object.</returns> private IApiResponse <T> GetResponse <T>(IApiEndpointBuilder builder) where T : class { if (builder == null) { throw new ArgumentNullException("builder"); } IApiResponse <T> response = null; try { response = InternalProcessing <T>(builder); // Source = Api | Cache } catch (BridgeException api) { response = new ApiResponse <T> // Source = BridgeException { Exception = api, Source = ResultSourceEnum.BridgeException }; } catch (WebException web) { response = HandleWebException <T>(web); // Source = ApiException } finally { if (response != null) { response.SourceClient = Client; if (response.Source != ResultSourceEnum.Cache) { Client.Cache.Push(builder, response); } } } return(response); }
/// <summary> /// Checks the cache and then performs the actual request, if required. /// </summary> /// <typeparam name="T">The strong type of the expected API result against which to deserialize JSON.</typeparam> /// <param name="builder">The object that builds the API route endpoint.</param> /// <returns>The API response object.</returns> private IApiResponse <T> InternalProcessing <T>(IApiEndpointBuilder builder) where T : class { IApiResponse <T> response = FetchFromCache <T>(builder); return(response ?? FetchFromThrottler <T>(builder)); }
/// <summary> /// Throttling ensures the API isn't flooded with requests and prevents us from getting cut off from the API. /// </summary> /// <typeparam name="T">The strong type of the expected API result against which to deserialize JSON.</typeparam> /// <param name="builder">The object that builds the API route endpoint.</param> /// <returns>The API response object.</returns> private IApiResponse <T> FetchFromThrottler <T>(IApiEndpointBuilder builder) where T : class { return(Client.Throttler.Throttle <T>(builder)); }
/// <summary> /// Attempts to access the internal cache and retrieve a response cache item without querying the API. /// <para>If the endpoint is not present in the cache yet, null is returned, but the endpoint is added to the cache.</para> /// <para>If the endpoint is present, it means the request is being processed. In this case we will wait on the processing to end before returning a result.</para> /// </summary> /// <typeparam name="T">The strong type of the expected API result.</typeparam> /// <param name="builder">The object that builds the API route endpoint.</param> /// <param name="pushEmpty">True to push an empty value into the cache if the endpoint isn't present yet.</param> /// <returns>Returns an API response cache item if successful, null otherwise.</returns> public IResponseCacheItem <T> Get <T>(IApiEndpointBuilder builder, bool pushEmpty = false) where T : class { return(Get <T>(builder, pushEmpty, 0)); }