Ejemplo n.º 1
0
        public IChangeSet <TObject, TKey> Change(IChangeSet <ExpirableItem <TObject, TKey>, TKey> updates)
        {
            _cache.Clone(updates);

            var itemstoexpire = _cache.KeyValues
                                .OrderByDescending(exp => exp.Value.ExpireAt)
                                .Skip(_sizeLimit)
                                .Select(exp => new Change <TObject, TKey>(ChangeReason.Remove, exp.Key, exp.Value.Value))
                                .ToList();

            if (itemstoexpire.Count > 0)
            {
                _cache.Remove(itemstoexpire.Select(exp => exp.Key));
            }

            var notifications = _cache.CaptureChanges();
            var changed       = notifications.Select(update => new Change <TObject, TKey>
                                                     (
                                                         update.Reason,
                                                         update.Key,
                                                         update.Current.Value,
                                                         update.Previous.HasValue ? update.Previous.Value.Value : Optional <TObject> .None
                                                     ));

            return(new ChangeSet <TObject, TKey>(changed));
        }
        public void Add()
        {
            var person = new Person("Adult1", 50);

            _updater.AddOrUpdate(person, "Adult1");
            IChangeSet <Person, string> updates = _cache.CaptureChanges();

            _cache.Lookup("Adult1").Value.Should().Be(person);
            _cache.Count.Should().Be(1);
            1.Should().Be(updates.Count, "Should be 1 updates");
            updates.First().Should().Be(new Change <Person, string>(ChangeReason.Add, person.Name, person), "Should be 1 updates");
        }
Ejemplo n.º 3
0
        private IChangeSet <TDestination, TKey> ProcessUpdates(ChangeAwareCache <TDestination, TKey> cache, IEnumerable <TransformResult> transformedItems)
        {
            foreach (var result in transformedItems)
            {
                if (result.Success)
                {
                    switch (result.Change.Reason)
                    {
                    case ChangeReason.Add:
                    case ChangeReason.Update:
                        cache.AddOrUpdate(result.Destination.ValueOrDefault(), result.Key);
                        break;

                    case ChangeReason.Remove:
                        cache.Remove(result.Key);
                        break;

                    case ChangeReason.Refresh:
                        cache.Refresh(result.Key);
                        break;
                    }
                }
                else
                {
                    _exceptionCallback(new Error <TSource, TKey>(result.Error, result.Change.Current, result.Change.Key));
                }
            }

            return(cache.CaptureChanges());
        }
Ejemplo n.º 4
0
        public static IChangeSet <TObject, TKey> RefreshFilteredFrom <TObject, TKey>(
            this ChangeAwareCache <TObject, TKey> filtered,
            Cache <TObject, TKey> allData,
            Func <TObject, bool> predicate)
        {
            foreach (var kvp in allData.KeyValues)
            {
                var exisiting = filtered.Lookup(kvp.Key);
                var matches   = predicate(kvp.Value);

                if (matches)
                {
                    if (!exisiting.HasValue)
                    {
                        filtered.AddOrUpdate(kvp.Value, kvp.Key);
                    }
                }
                else
                {
                    if (exisiting.HasValue)
                    {
                        filtered.Remove(kvp.Key);
                    }
                }
            }
            return(filtered.CaptureChanges());
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Initializes a new instance of the <see cref="LockFreeObservableCache{TObject, TKey}"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        public LockFreeObservableCache(IObservable <IChangeSet <TObject, TKey> > source)
        {
            _updater = new CacheUpdater <TObject, TKey>(_innerCache);

            var loader = source.Select(changes =>
            {
                _innerCache.Clone(changes);
                return(_innerCache.CaptureChanges());
            }).SubscribeSafe(_changes);

            _cleanUp = Disposable.Create(() =>
            {
                loader.Dispose();
                _changes.OnCompleted();
                _countChanged.OnCompleted();
            });
        }
Ejemplo n.º 6
0
        public static IObservable <IChangeSet <TCache, TKey> > TransformAndCache <TObject, TKey, TCache>(
            this IObservable <IChangeSet <TObject, TKey> > obs,
            Func <TKey, TObject, TCache> onAdded,
            Action <Change <TObject, TKey>, TCache> onUpdated)
        {
            var cache = new ChangeAwareCache <TCache, TKey>();

            return(obs
                   .Select(changeSet =>
            {
                foreach (var change in changeSet)
                {
                    switch (change.Reason)
                    {
                    case ChangeReason.Add:
                    case ChangeReason.Update:
                    case ChangeReason.Refresh:
                        var lookup = cache.Lookup(change.Key);
                        TCache val;
                        if (lookup.HasValue)
                        {
                            val = lookup.Value;
                        }
                        else
                        {
                            val = onAdded(change.Key, change.Current);
                            cache.Add(val, change.Key);
                        }
                        onUpdated(change, val);
                        break;

                    case ChangeReason.Remove:
                        cache.Remove(change.Key);
                        break;

                    case ChangeReason.Moved:
                        break;

                    default:
                        throw new NotImplementedException();
                    }
                }
                return cache.CaptureChanges();
            })
                   .Where(cs => cs.Count > 0));
        }
Ejemplo n.º 7
0
        public IObservable <IChangeSet <TObject, TKey> > Run()
        {
            return(Observable.Create <IChangeSet <TObject, TKey> >(observer =>
            {
                var allData = new Cache <TObject, TKey>();
                var filteredData = new ChangeAwareCache <TObject, TKey>();
                Func <TObject, bool> predicate = t => false;

                var locker = new object();

                var refresher = LatestPredicateObservable()
                                .Synchronize(locker)
                                .Select(p =>
                {
                    //set the local predicate
                    predicate = p;

                    //reapply filter using all data from the cache
                    return filteredData.RefreshFilteredFrom(allData, predicate);
                });

                var dataChanged = _source
                                  // .Finally(observer.OnCompleted)
                                  .Synchronize(locker)
                                  .Select(changes =>
                {
                    //maintain all data [required to re-apply filter]
                    allData.Clone(changes);

                    //maintain filtered data
                    filteredData.FilterChanges(changes, predicate);

                    //get latest changes
                    return filteredData.CaptureChanges();
                });

                return refresher
                .Merge(dataChanged)
                .NotEmpty()
                .SubscribeSafe(observer);
            }));
        }
Ejemplo n.º 8
0
        private IChangeSet <TDestination, TKey> ProcessUpdates(ChangeAwareCache <TransformedItemContainer, TKey> cache, TransformResult[] transformedItems)
        {
            //check for errors and callback if a handler has been specified
            var errors = transformedItems.Where(t => !t.Success).ToArray();

            if (errors.Any())
            {
                errors.ForEach(t => _exceptionCallback(new Error <TSource, TKey>(t.Error, t.Change.Current, t.Change.Key)));
            }

            foreach (var result in transformedItems.Where(t => t.Success))
            {
                var key = result.Key;
                switch (result.Change.Reason)
                {
                case ChangeReason.Add:
                case ChangeReason.Update:
                    cache.AddOrUpdate(result.Container.Value, key);
                    break;

                case ChangeReason.Remove:
                    cache.Remove(key);
                    break;

                case ChangeReason.Refresh:
                    cache.Refresh(key);
                    break;
                }
            }

            var changes     = cache.CaptureChanges();
            var transformed = changes.Select(change => new Change <TDestination, TKey>(change.Reason,
                                                                                       change.Key,
                                                                                       change.Current.Destination,
                                                                                       change.Previous.Convert(x => x.Destination),
                                                                                       change.CurrentIndex,
                                                                                       change.PreviousIndex));

            return(new ChangeSet <TDestination, TKey>(transformed));
        }
Ejemplo n.º 9
0
        private IObservable <IChangeSet <TDestination, TDestinationKey> > CreateWithChangeset()
        {
            return(Observable.Create <IChangeSet <TDestination, TDestinationKey> >(observer =>
            {
                var result = new ChangeAwareCache <TDestination, TDestinationKey>();


                var transformed = _source.Transform((t, key) =>
                {
                    //Only skip initial for first time Adds where there is isinitial data records
                    var locker = new object();
                    var collection = _manyselector(t);
                    var changes = _childChanges(t).Synchronize(locker).Skip(1);
                    return new ManyContainer(() =>
                    {
                        lock (locker)
                            return collection.Select(m => new DestinationContainer(m, _keySelector(m)));
                    }, changes);
                }).Publish();

                var outerLock = new object();
                var intial = transformed
                             .Synchronize(outerLock)
                             .Select(changes => new ChangeSet <TDestination, TDestinationKey>(new DestinationEnumerator(changes)));

                var subsequent = transformed
                                 .MergeMany(x => x.Changes)
                                 .Synchronize(outerLock);

                var allChanges = intial.Merge(subsequent).Select(changes =>
                {
                    result.Clone(changes);
                    return result.CaptureChanges();
                });

                return new CompositeDisposable(allChanges.SubscribeSafe(observer), transformed.Connect());
            }));
        }
Ejemplo n.º 10
0
        public IObservable <IChangeSet <TObject, TKey> > Run()
        {
            return(Observable.Create <IChangeSet <TObject, TKey> >(observer =>
            {
                long orderItemWasAdded = -1;
                var locker = new object();

                if (_expireAfter == null && _limitSizeTo < 1)
                {
                    return _source.Scan(new ChangeAwareCache <TObject, TKey>(), (state, latest) =>
                    {
                        latest.ForEach(t => state.AddOrUpdate(t, _keySelector(t)));
                        return state;
                    })
                    .Select(state => state.CaptureChanges())
                    .SubscribeSafe(observer);
                }

                var cache = new ChangeAwareCache <ExpirableItem <TObject, TKey>, TKey>();
                var sizeLimited = _source.Synchronize(locker)
                                  .Scan(cache, (state, latest) =>
                {
                    latest.Select(t =>
                    {
                        var key = _keySelector(t);
                        return CreateExpirableItem(t, key, ref orderItemWasAdded);
                    }).ForEach(ei => cache.AddOrUpdate(ei, ei.Key));

                    if (_limitSizeTo > 0 && state.Count > _limitSizeTo)
                    {
                        var toRemove = state.Count - _limitSizeTo;

                        //remove oldest items
                        cache.KeyValues
                        .OrderBy(exp => exp.Value.Index)
                        .Take(toRemove)
                        .ForEach(ei => cache.Remove(ei.Key));
                    }
                    return state;
                })
                                  .Select(state => state.CaptureChanges())
                                  .Publish();

                var timeLimited = (_expireAfter == null ? Observable.Never <IChangeSet <ExpirableItem <TObject, TKey>, TKey> >() : sizeLimited)
                                  .Filter(ei => ei.ExpireAt != DateTime.MaxValue)
                                  .GroupWithImmutableState(ei => ei.ExpireAt)
                                  .MergeMany(grouping =>
                {
                    var expireAt = grouping.Key.Subtract(_scheduler.Now.DateTime);
                    return Observable.Timer(expireAt, _scheduler).Select(_ => grouping);
                })
                                  .Synchronize(locker)
                                  .Select(grouping =>
                {
                    cache.Remove(grouping.Keys);
                    return cache.CaptureChanges();
                });

                var publisher = sizeLimited
                                .Merge(timeLimited)
                                .Cast(ei => ei.Value)
                                .NotEmpty()
                                .SubscribeSafe(observer);

                return new CompositeDisposable(publisher, sizeLimited.Connect());
            }));
        }
Ejemplo n.º 11
0
        public IObservable <IChangeSet <TObject, TKey> > Run()
        {
            return(Observable.Create <IChangeSet <TObject, TKey> >(observer =>
            {
                var locker = new object();

                //this is the resulting cache which produces all notifications
                var resultCache = new ChangeAwareCache <TObject, TKey>();


                //Transform to a merge container.
                //This populates a RefTracker when the original source is subscribed to
                var sourceLists = _source.Connect()
                                  .Synchronize(locker)
                                  .Transform(changeset => new MergeContainer(changeset))
                                  .AsObservableList();

                //merge the items back together
                var allChanges = sourceLists.Connect()
                                 .MergeMany(mc => mc.Source)
                                 .Synchronize(locker)
                                 .Subscribe(changes =>
                {
                    //Populate result list and chck for changes
                    UpdateResultList(resultCache, sourceLists.Items.AsArray(), changes);

                    var notifications = resultCache.CaptureChanges();
                    if (notifications.Count != 0)
                    {
                        observer.OnNext(notifications);
                    }
                });

                //when an list is removed, need to
                var removedItem = sourceLists.Connect()
                                  .OnItemRemoved(mc =>
                {
                    //Remove items if required
                    ProcessChanges(resultCache, sourceLists.Items.AsArray(), mc.Cache.KeyValues);

                    if (_type == CombineOperator.And || _type == CombineOperator.Except)
                    {
                        var itemsToCheck = sourceLists.Items.SelectMany(mc2 => mc2.Cache.KeyValues);
                        ProcessChanges(resultCache, sourceLists.Items.AsArray(), itemsToCheck);
                    }

                    var notifications = resultCache.CaptureChanges();
                    if (notifications.Count != 0)
                    {
                        observer.OnNext(notifications);
                    }
                })
                                  .Subscribe();

                //when an list is added or removed, need to
                var sourceChanged = sourceLists.Connect()
                                    .WhereReasonsAre(ListChangeReason.Add, ListChangeReason.AddRange)
                                    .ForEachItemChange(mc =>
                {
                    ProcessChanges(resultCache, sourceLists.Items.AsArray(), mc.Current.Cache.KeyValues);

                    if (_type == CombineOperator.And || _type == CombineOperator.Except)
                    {
                        ProcessChanges(resultCache, sourceLists.Items.AsArray(), resultCache.KeyValues.ToArray());
                    }

                    var notifications = resultCache.CaptureChanges();
                    if (notifications.Count != 0)
                    {
                        observer.OnNext(notifications);
                    }
                })
                                    .Subscribe();

                return new CompositeDisposable(sourceLists, allChanges, removedItem, sourceChanged);
            }));
        }
Ejemplo n.º 12
0
            /// <summary>
            /// Sorts using the specified sorter. Will return null if there are no changes
            /// </summary>
            /// <param name="sortReason">The sort reason.</param>
            /// <param name="changes">The changes.</param>
            /// <returns></returns>
            private ISortedChangeSet <TObject, TKey> DoSort(SortReason sortReason, IChangeSet <TObject, TKey> changes = null)
            {
                if (changes != null)
                {
                    _cache.Clone(changes);
                    changes           = _cache.CaptureChanges();
                    _haveReceivedData = true;
                    if (_comparer == null)
                    {
                        return(null);
                    }
                }

                //if the comparer is not set, return nothing
                if (_comparer == null || !_haveReceivedData)
                {
                    return(null);
                }

                if (!_initialised)
                {
                    sortReason   = SortReason.InitialLoad;
                    _initialised = true;
                }
                else if (changes != null && (_resetThreshold > 0 && changes.Count >= _resetThreshold))
                {
                    sortReason = SortReason.Reset;
                }

                IChangeSet <TObject, TKey> changeSet;

                switch (sortReason)
                {
                case SortReason.InitialLoad:
                {
                    //For the first batch, changes may have arrived before the comparer was set.
                    //therefore infer the first batch of changes from the cache
                    _calculator = new IndexCalculator <TObject, TKey>(_comparer, _optimisations);
                    changeSet   = _calculator.Load(_cache);
                }
                break;

                case SortReason.Reset:
                {
                    _calculator.Reset(_cache);
                    changeSet = changes;
                }
                break;

                case SortReason.DataChanged:
                {
                    changeSet = _calculator.Calculate(changes);
                }
                break;

                case SortReason.ComparerChanged:
                {
                    changeSet = _calculator.ChangeComparer(_comparer);
                    if (_resetThreshold > 0 && _cache.Count >= _resetThreshold)
                    {
                        sortReason = SortReason.Reset;
                        _calculator.Reset(_cache);
                    }
                    else
                    {
                        sortReason = SortReason.Reorder;
                        changeSet  = _calculator.Reorder();
                    }
                }
                break;

                case SortReason.Reorder:
                {
                    changeSet = _calculator.Reorder();
                }
                break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(sortReason));
                }

                Debug.Assert(changeSet != null, "changeSet != null");
                if ((sortReason == SortReason.InitialLoad || sortReason == SortReason.DataChanged) &&
                    changeSet.Count == 0)
                {
                    return(null);
                }

                if (sortReason == SortReason.Reorder && changeSet.Count == 0)
                {
                    return(null);
                }

                _sorted = new KeyValueCollection <TObject, TKey>(_calculator.List, _comparer, sortReason, _optimisations);
                return(new SortedChangeSet <TObject, TKey>(_sorted, changeSet));
            }
Ejemplo n.º 13
0
 public IChangeSet <TObject, TKey> AsChangeSet()
 {
     return(_cache.CaptureChanges());
 }
Ejemplo n.º 14
0
        private IChangeSet <TObject, TKey> UpdateCombined(IChangeSet <TObject, TKey> updates)
        {
            //child caches have been updated before we reached this point.

            foreach (var update in updates)
            {
                var key = update.Key;
                switch (update.Reason)
                {
                case ChangeReason.Add:
                case ChangeReason.Update:
                {
                    // get the current key.
                    //check whether the item should belong to the cache
                    var cached    = _combinedCache.Lookup(key);
                    var contained = cached.HasValue;
                    var match     = MatchesConstraint(key);

                    if (match)
                    {
                        if (contained)
                        {
                            if (!ReferenceEquals(update.Current, cached.Value))
                            {
                                _combinedCache.AddOrUpdate(update.Current, key);
                            }
                        }
                        else
                        {
                            _combinedCache.AddOrUpdate(update.Current, key);
                        }
                    }
                    else
                    {
                        if (contained)
                        {
                            _combinedCache.Remove(key);
                        }
                    }
                }
                break;

                case ChangeReason.Remove:
                {
                    var cached           = _combinedCache.Lookup(key);
                    var contained        = cached.HasValue;
                    var shouldBeIncluded = MatchesConstraint(key);

                    if (shouldBeIncluded)
                    {
                        var firstOne = _sourceCaches.Select(s => s.Lookup(key))
                                       .SelectValues()
                                       .First();

                        if (!cached.HasValue)
                        {
                            _combinedCache.AddOrUpdate(firstOne, key);
                        }
                        else if (!ReferenceEquals(firstOne, cached.Value))
                        {
                            _combinedCache.AddOrUpdate(firstOne, key);
                        }
                    }
                    else
                    {
                        if (contained)
                        {
                            _combinedCache.Remove(key);
                        }
                    }
                }
                break;

                case ChangeReason.Refresh:
                {
                    _combinedCache.Refresh(key);
                }
                break;
                }
            }
            return(_combinedCache.CaptureChanges());
        }
Ejemplo n.º 15
0
        public IObservable <IChangeSet <TObject, TKey> > Run()
        {
            return(Observable.Create <IChangeSet <TObject, TKey> >
                   (
                       observer =>
            {
                var result = new ChangeAwareCache <TObject, TKey>();
                var locker = new object();
                var paused = _intialPauseState;
                var timeoutDisposer = new SerialDisposable();
                var intervalTimerDisposer = new SerialDisposable();

                void ResumeAction()
                {
                    //publish changes (if there are any)
                    var changes = result.CaptureChanges();
                    if (changes.Count > 0)
                    {
                        observer.OnNext(changes);
                    }
                }

                IDisposable IntervalFunction()
                {
                    return _intervalTimer
                    .Synchronize(locker)
                    .Finally(() => paused = false)
                    .Subscribe(_ =>
                    {
                        paused = false;
                        ResumeAction();
                        if (_intervalTimer != null)
                        {
                            paused = true;
                        }
                    });
                }

                if (_intervalTimer != null)
                {
                    intervalTimerDisposer.Disposable = IntervalFunction();
                }

                var pausedHander = _pauseIfTrueSelector
                                   // .StartWith(initalp)
                                   .Synchronize(locker)
                                   .Subscribe(p =>
                {
                    paused = p;
                    if (!p)
                    {
                        //pause window has closed, so reset timer
                        if (_timeOut.HasValue)
                        {
                            timeoutDisposer.Disposable = Disposable.Empty;
                        }
                        ResumeAction();
                    }
                    else
                    {
                        if (_timeOut.HasValue)
                        {
                            timeoutDisposer.Disposable = Observable.Timer(_timeOut.Value, _scheduler)
                                                         .Synchronize(locker)
                                                         .Subscribe(_ =>
                            {
                                paused = false;
                                ResumeAction();
                            });
                        }
                    }
                });

                var publisher = _source
                                .Synchronize(locker)
                                .Subscribe(changes =>
                {
                    result.Clone(changes);

                    //publish if not paused
                    if (!paused)
                    {
                        observer.OnNext(result.CaptureChanges());
                    }
                });

                return new CompositeDisposable(publisher, pausedHander, timeoutDisposer, intervalTimerDisposer);
            }
                   ));
        }