예제 #1
0
        /// <summary>
        /// This method attempts to returned a cached value, while
        /// simultaneously calling a Func to return the latest value. When the
        /// latest data comes back, it replaces what was previously in the
        /// cache.
        ///
        /// This method is best suited for loading dynamic data from the
        /// Internet, while still showing the user earlier data.
        ///
        /// This method returns an IObservable that may return *two* results
        /// (first the cached data, then the latest data). Therefore, it's
        /// important for UI applications that in your Subscribe method, you
        /// write the code to merge the second result when it comes in.
        /// </summary>
        /// <param name="key">The key to store the returned result under.</param>
        /// <param name="fetchFunc"></param>
        /// <param name="fetchPredicate">An optional Func to determine whether
        /// the updated item should be fetched. If the cached version isn't found,
        /// this parameter is ignored and the item is always fetched.</param>
        /// <param name="absoluteExpiration">An optional expiration date.</param>
        /// <returns>An Observable stream containing either one or two
        /// results (possibly a cached version, then the latest version)</returns>
        public static IObservable <T> GetAndFetchLatest <T>(this IBlobCache This,
                                                            string key,
                                                            Func <IObservable <T> > fetchFunc,
                                                            Func <DateTimeOffset, bool> fetchPredicate = null,
                                                            DateTimeOffset?absoluteExpiration          = null)
        {
            bool foundItemInCache;
            var  fail = Observable.Defer(() => This.GetCreatedAt(key))
                        .Select(x => fetchPredicate != null && x != null ? fetchPredicate(x.Value) : true)
                        .Where(x => x != false)
                        .SelectMany(_ => fetchFunc())
                        .Finally(() => This.Invalidate(key))
                        .Do(x => This.InsertObject(key, x, absoluteExpiration));

            var result = This.GetObjectAsync <T>(key).Select(x => new Tuple <T, bool>(x, true))
                         .Catch(Observable.Return(new Tuple <T, bool>(default(T), false)));

            return(result.SelectMany(x =>
            {
                foundItemInCache = x.Item2;
                return x.Item2 ?
                Observable.Return(x.Item1) :
                Observable.Empty <T>();
            }).Concat(fail).Multicast(new ReplaySubject <T>()).RefCount());
        }
예제 #2
0
 /// <summary>
 /// Attempt to return an object from the cache. If the item doesn't
 /// exist or returns an error, call a Func to return the latest
 /// version of an object and insert the result in the cache.
 ///
 /// For most Internet applications, this method is the best method to
 /// call to fetch static data (i.e. images) from the network.
 /// </summary>
 /// <param name="key">The key to associate with the object.</param>
 /// <param name="fetchFunc">A Func which will asynchronously return
 /// the latest value for the object should the cache not contain the
 /// key.
 ///
 /// Observable.Start is the most straightforward way (though not the
 /// most efficient!) to implement this Func.</param>
 /// <param name="absoluteExpiration">An optional expiration date.</param>
 /// <returns>A Future result representing the deserialized object from
 /// the cache.</returns>
 public static IObservable <T> GetOrFetchObject <T>(this IBlobCache This, string key, Func <IObservable <T> > fetchFunc, DateTimeOffset?absoluteExpiration = null)
 {
     return(This.GetObjectAsync <T>(key).Catch <T, Exception>(_ =>
     {
         object dontcare;
         return ((IObservable <T>)inflightFetchRequests.GetOrAdd(key, __ => (object)fetchFunc()))
         .Do(x => This.InsertObject(key, x, absoluteExpiration))
         .Finally(() => inflightFetchRequests.TryRemove(key, out dontcare))
         .Multicast(new AsyncSubject <T>()).RefCount();
     }));
 }
예제 #3
0
        /// <summary>
        /// Return all objects of a specific Type in the cache.
        /// </summary>
        /// <returns>A Future result representing all objects in the cache
        /// with the specified Type.</returns>
        public static IObservable <IEnumerable <T> > GetAllObjects <T>(this IBlobCache This)
        {
            // NB: This isn't exactly thread-safe, but it's Close Enough(tm)
            // We make up for the fact that the keys could get kicked out
            // from under us via the Catch below
            var matchingKeys = This.GetAllKeys()
                               .Where(x => x.StartsWith(GetTypePrefixedKey("", typeof(T))))
                               .ToArray();

            return(matchingKeys.ToObservable()
                   .SelectMany(x => This.GetObjectAsync <T>(x, true).Catch(Observable.Empty <T>()))
                   .ToList()
                   .Select(x => (IEnumerable <T>)x));
        }
예제 #4
0
        /// <summary>
        /// Attempt to return an object from the cache. If the item doesn't
        /// exist or returns an error, call a Func to return the latest
        /// version of an object and insert the result in the cache.
        ///
        /// For most Internet applications, this method is the best method to
        /// call to fetch static data (i.e. images) from the network.
        /// </summary>
        /// <param name="key">The key to associate with the object.</param>
        /// <param name="fetchFunc">A Func which will asynchronously return
        /// the latest value for the object should the cache not contain the
        /// key.
        ///
        /// Observable.Start is the most straightforward way (though not the
        /// most efficient!) to implement this Func.</param>
        /// <param name="absoluteExpiration">An optional expiration date.</param>
        /// <returns>A Future result representing the deserialized object from
        /// the cache.</returns>
        public static IObservable <T> GetOrFetchObject <T>(this IBlobCache This, string key, Func <IObservable <T> > fetchFunc, DateTimeOffset?absoluteExpiration = null)
        {
            return(This.GetObjectAsync <T>(key).Catch <T, Exception>(_ =>
            {
                object dontcare;
                var prefixedKey = This.GetHashCode().ToString() + key;

                var result = Observable.Defer(() => fetchFunc())
                             .Do(x => This.InsertObject(key, x, absoluteExpiration))
                             .Finally(() => inflightFetchRequests.TryRemove(prefixedKey, out dontcare))
                             .Multicast(new AsyncSubject <T>()).RefCount();

                return (IObservable <T>)inflightFetchRequests.GetOrAdd(prefixedKey, result);
            }));
        }
예제 #5
0
        public static IObservable <IDictionary <string, T> > GetObjectsAsync <T>(this IBlobCache This, IEnumerable <string> keys, bool noTypePrefix = false)
        {
            var bulkCache = This as IObjectBulkBlobCache;

            if (bulkCache != null)
            {
                return(bulkCache.GetObjectsAsync <T>(keys));
            }

            return(keys.ToObservable()
                   .SelectMany(x =>
            {
                return This.GetObjectAsync <T>(x)
                .Select(y => new KeyValuePair <string, T>(x, y))
                .Catch <KeyValuePair <string, T>, KeyNotFoundException>(_ => Observable.Empty <KeyValuePair <string, T> >());
            })
                   .ToDictionary(k => k.Key, v => v.Value));
        }
예제 #6
0
        /// <summary>
        /// Return all objects of a specific Type in the cache.
        /// </summary>
        /// <returns>A Future result representing all objects in the cache
        /// with the specified Type.</returns>
        public static IObservable <IEnumerable <T> > GetAllObjects <T>(this IBlobCache This)
        {
            var objCache = This as IObjectBlobCache;

            if (objCache != null)
            {
                return(objCache.GetAllObjects <T>());
            }

            // NB: This isn't exactly thread-safe, but it's Close Enough(tm)
            // We make up for the fact that the keys could get kicked out
            // from under us via the Catch below
            var matchingKeys = This.GetAllKeys()
                               .ToArray();

            return(matchingKeys.ToObservable()
                   .SelectMany(x => This.GetObjectAsync <T>(x, true).Catch(Observable.Empty <T>()))
                   .ToList()
                   .Where(x => x is T)
                   .Select(x => (IEnumerable <T>)x));
        }
예제 #7
0
        /// <summary>
        /// This method attempts to returned a cached value, while
        /// simultaneously calling a Func to return the latest value. When the
        /// latest data comes back, it replaces what was previously in the
        /// cache.
        ///
        /// This method is best suited for loading dynamic data from the
        /// Internet, while still showing the user earlier data.
        ///
        /// This method returns an IObservable that may return *two* results
        /// (first the cached data, then the latest data). Therefore, it's
        /// important for UI applications that in your Subscribe method, you
        /// write the code to merge the second result when it comes in.
        ///
        /// This also means that await'ing this method is a Bad Idea(tm), always
        /// use Subscribe.
        /// </summary>
        /// <param name="key">The key to store the returned result under.</param>
        /// <param name="fetchFunc"></param>
        /// <param name="fetchPredicate">An optional Func to determine whether
        /// the updated item should be fetched. If the cached version isn't found,
        /// this parameter is ignored and the item is always fetched.</param>
        /// <param name="absoluteExpiration">An optional expiration date.</param>
        /// <param name="shouldInvalidateOnError">If this is true, the cache will
        /// be cleared when an exception occurs in fetchFunc</param>
        /// <returns>An Observable stream containing either one or two
        /// results (possibly a cached version, then the latest version)</returns>
        public static IObservable <T> GetAndFetchLatest <T>(this IBlobCache This,
                                                            string key,
                                                            Func <IObservable <T> > fetchFunc,
                                                            Func <DateTimeOffset, bool> fetchPredicate = null,
                                                            DateTimeOffset?absoluteExpiration          = null,
                                                            bool shouldInvalidateOnError = false)
        {
            var fetch = Observable.Defer(() => This.GetObjectCreatedAt <T>(key))
                        .Select(x => fetchPredicate == null || x == null || fetchPredicate(x.Value))
                        .Where(x => x != false)
                        .SelectMany(async _ => {
                var ret = default(T);
                try {
                    ret = await fetchFunc();
                } catch (Exception) {
                    if (shouldInvalidateOnError)
                    {
                        This.InvalidateObject <T>(key);
                    }
                    throw;
                }

                await This.InvalidateObject <T>(key);
                await This.InsertObject(key, ret, absoluteExpiration);
                return(ret);
            });

            var result = This.GetObjectAsync <T>(key).Select(x => new Tuple <T, bool>(x, true))
                         .Catch(Observable.Return(new Tuple <T, bool>(default(T), false)));

            return(result.SelectMany(x =>
            {
                return x.Item2 ?
                Observable.Return(x.Item1) :
                Observable.Empty <T>();
            }).Concat(fetch).Multicast(new ReplaySubject <T>()).RefCount());
        }