public IObservable <IChangeSet <TObject, TKey> > Run() { return(Observable.Create <IChangeSet <TObject, TKey> >(observer => { long orderItemWasAdded = -1; var locker = new object(); 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()); })); }
public void Remove(TObject item) { if (_keySelector == null) { throw new KeySelectorException("A key selector must be specified"); } var key = _keySelector.GetKey(item); _cache.Remove(key); }
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); }
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.Length > 0) { errors.ForEach(t => _exceptionCallback?.Invoke(new Error <TSource, TKey>(t.Error, t.Change.Current, t.Change.Key))); } foreach (var result in transformedItems.Where(t => t.Success)) { TKey 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)); }
public static IChangeSet <TObject, TKey> RefreshFilteredFrom <TObject, TKey>(this ChangeAwareCache <TObject, TKey> filtered, Cache <TObject, TKey> allData, Func <TObject, bool> predicate) where TKey : notnull { if (allData.Count == 0) { return(ChangeSet <TObject, TKey> .Empty); } foreach (var kvp in allData.KeyValues) { var existing = filtered.Lookup(kvp.Key); var matches = predicate(kvp.Value); if (matches) { if (!existing.HasValue) { filtered.Add(kvp.Value, kvp.Key); } } else { if (existing.HasValue) { filtered.Remove(kvp.Key); } } } return(filtered.CaptureChanges()); }
private void ProcessItem(ChangeAwareCache <TObject, TKey> target, MergeContainer[] sourceLists, TObject item, TKey key) { //TODO: Check whether individual items should be updated var cached = target.Lookup(key); var shouldBeInResult = MatchesConstraint(sourceLists, key); if (shouldBeInResult) { if (!cached.HasValue) { target.AddOrUpdate(item, key); } else if (!ReferenceEquals(item, cached.Value)) { target.AddOrUpdate(item, key); } } else { if (cached.HasValue) { target.Remove(key); } } }
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.Value, 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()); }
private IChangeSet <TDestination, TKey> ProcessUpdates(ChangeAwareCache <TransformedItemContainer, 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.Container.Value, 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)); } } 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)); }
private IChangeSet <TObject, TKey> UpdateCombined(IChangeSet <TObject, TKey> updates) { //child caches have been updated before we reached this point. foreach (var update in updates) { TKey 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; bool 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()); }
public IObservable <IChangeSet <TObject, TKey> > Run() { return(Observable.Create <IChangeSet <TObject, TKey> >( observer => { long orderItemWasAdded = -1; var locker = new object(); if (_expireAfter is null && _limitSizeTo < 1) { return _source.Scan( new ChangeAwareCache <TObject, TKey>(), (state, latest) => { if (latest is IList <TObject> list) { // zero allocation enumerator var enumerableList = EnumerableIList.Create(list); if (!_singleValueSource) { state.Remove(state.Keys.Except(enumerableList.Select(_keySelector)).ToList()); } foreach (var item in enumerableList) { state.AddOrUpdate(item, _keySelector(item)); } } else { var enumerable = latest.ToList(); if (!_singleValueSource) { state.Remove(state.Keys.Except(enumerable.Select(_keySelector)).ToList()); } foreach (var item in enumerable) { state.AddOrUpdate(item, _keySelector(item)); } } 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 is null ? Observable.Never <IChangeSet <ExpirableItem <TObject, TKey>, TKey> >() : sizeLimited).Filter(ei => ei.ExpireAt != DateTime.MaxValue).MergeMany( grouping => { var expireAt = grouping.ExpireAt.Subtract(_scheduler.Now.DateTime); return Observable.Timer(expireAt, _scheduler).Select(_ => grouping); }).Synchronize(locker).Select( item => { cache.Remove(item.Key); return cache.CaptureChanges(); }); var publisher = sizeLimited.Merge(timeLimited).Cast(ei => ei.Value).NotEmpty().SubscribeSafe(observer); return new CompositeDisposable(publisher, sizeLimited.Connect()); })); }
public static void FilterChanges <TObject, TKey>(this ChangeAwareCache <TObject, TKey> cache, IChangeSet <TObject, TKey> changes, Func <TObject, bool> predicate) where TKey : notnull { foreach (var change in changes.ToConcreteType()) { var key = change.Key; switch (change.Reason) { case ChangeReason.Add: { var current = change.Current; if (predicate(current)) { cache.AddOrUpdate(current, key); } } break; case ChangeReason.Update: { var current = change.Current; if (predicate(current)) { cache.AddOrUpdate(current, key); } else { cache.Remove(key); } } break; case ChangeReason.Remove: cache.Remove(key); break; case ChangeReason.Refresh: { var existing = cache.Lookup(key); if (predicate(change.Current)) { if (!existing.HasValue) { cache.AddOrUpdate(change.Current, key); } else { cache.Refresh(key); } } else { if (existing.HasValue) { cache.Remove(key); } } } break; } } }