private async Task Transform(ChangeAwareList <TransformedItemContainer> transformed, IChangeSet <TSource> changes) { if (changes is null) { throw new ArgumentNullException(nameof(changes)); } foreach (var item in changes) { switch (item.Reason) { case ListChangeReason.Add: { var change = item.Item; if (change.CurrentIndex < 0 | change.CurrentIndex >= transformed.Count) { var container = await _containerFactory(item.Item.Current).ConfigureAwait(false); transformed.Add(container); } else { var container = await _containerFactory(item.Item.Current).ConfigureAwait(false); transformed.Insert(change.CurrentIndex, container); } break; } case ListChangeReason.AddRange: { var tasks = item.Range.Select(_containerFactory); var containers = await Task.WhenAll(tasks).ConfigureAwait(false); transformed.AddOrInsertRange(containers, item.Range.Index); break; } case ListChangeReason.Replace: { var change = item.Item; var container = await _containerFactory(item.Item.Current).ConfigureAwait(false); if (change.CurrentIndex == change.PreviousIndex) { transformed[change.CurrentIndex] = container; } else { transformed.RemoveAt(change.PreviousIndex); transformed.Insert(change.CurrentIndex, container); } break; } case ListChangeReason.Remove: { var change = item.Item; bool hasIndex = change.CurrentIndex >= 0; if (hasIndex) { transformed.RemoveAt(item.Item.CurrentIndex); } else { var toRemove = transformed.FirstOrDefault(t => ReferenceEquals(t.Source, t)); if (toRemove is not null) { transformed.Remove(toRemove); } } break; } case ListChangeReason.RemoveRange: { if (item.Range.Index >= 0) { transformed.RemoveRange(item.Range.Index, item.Range.Count); } else { var toRemove = transformed.Where(t => ReferenceEquals(t.Source, t)).ToArray(); transformed.RemoveMany(toRemove); } break; } case ListChangeReason.Clear: { // i.e. need to store transformed reference so we can correctly clear var toClear = new Change <TransformedItemContainer>(ListChangeReason.Clear, transformed); transformed.ClearOrRemoveMany(toClear); break; } case ListChangeReason.Moved: { var change = item.Item; bool hasIndex = change.CurrentIndex >= 0; if (!hasIndex) { throw new UnspecifiedIndexException("Cannot move as an index was not specified"); } if (transformed is IExtendedList <TransformedItemContainer> collection) { collection.Move(change.PreviousIndex, change.CurrentIndex); } else { var current = transformed[change.PreviousIndex]; transformed.RemoveAt(change.PreviousIndex); transformed.Insert(change.CurrentIndex, current); } break; } } } }
private IChangeSet <IGroup <TObject, TGroupKey> > Process(ChangeAwareList <IGroup <TObject, TGroupKey> > result, IDictionary <TGroupKey, Group <TObject, TGroupKey> > groupCollection, IChangeSet <ItemWithGroupKey> changes) { foreach (var grouping in changes.Unified().GroupBy(change => change.Current.Group)) { //lookup group and if created, add to result set var currentGroup = grouping.Key; var lookup = GetCache(groupCollection, currentGroup); var groupCache = lookup.Group; if (lookup.WasCreated) { result.Add(groupCache); } //start a group edit session, so all changes are batched groupCache.Edit( list => { //iterate through the group's items and process foreach (var change in grouping) { switch (change.Reason) { case ListChangeReason.Add: { list.Add(change.Current.Item); break; } case ListChangeReason.Replace: { var previousItem = change.Previous.Value.Item; var previousGroup = change.Previous.Value.Group; //check whether an item changing has resulted in a different group if (previousGroup.Equals(currentGroup)) { //find and replace var index = list.IndexOf(previousItem); list[index] = change.Current.Item; } else { //add to new group list.Add(change.Current.Item); //remove from old group groupCollection.Lookup(previousGroup) .IfHasValue(g => { g.Edit(oldList => oldList.Remove(previousItem)); if (g.List.Count != 0) { return; } groupCollection.Remove(g.GroupKey); result.Remove(g); }); } break; } case ListChangeReason.Refresh: { //1. Check whether item was in the group and should not be now (or vice versa) var currentItem = change.Current.Item; var previousGroup = change.Current.PreviousGroup.Value; //check whether an item changing has resulted in a different group if (previousGroup.Equals(currentGroup)) { // Propagate refresh event var cal = (ChangeAwareList <TObject>)list; cal.Refresh(currentItem); } else { //add to new group list.Add(currentItem); //remove from old group if empty groupCollection.Lookup(previousGroup) .IfHasValue(g => { g.Edit(oldList => oldList.Remove(currentItem)); if (g.List.Count != 0) { return; } groupCollection.Remove(g.GroupKey); result.Remove(g); }); } break; } case ListChangeReason.Remove: { list.Remove(change.Current.Item); break; } case ListChangeReason.Clear: { list.Clear(); break; } } } }); if (groupCache.List.Count == 0) { groupCollection.Remove(groupCache.GroupKey); result.Remove(groupCache); } } return(result.CaptureChanges()); }
private IChangeSet <TValue> Process(Dictionary <TValue, int> values, ChangeAwareList <TValue> result, IChangeSet <ItemWithValue <T, TValue> > changes) { Action <TValue> addAction = value => values.Lookup(value) .IfHasValue(count => values[value] = count + 1) .Else(() => { values[value] = 1; result.Add(value); }); Action <TValue> removeAction = value => { var counter = values.Lookup(value); if (!counter.HasValue) { return; } //decrement counter var newCount = counter.Value - 1; values[value] = newCount; if (newCount != 0) { return; } //if there are none, then remove and notify result.Remove(value); }; foreach (var change in changes) { switch (change.Reason) { case ListChangeReason.Add: { var value = change.Item.Current.Value; addAction(value); break; } case ListChangeReason.AddRange: { change.Range.Select(item => item.Value).ForEach(addAction); break; } // case ListChangeReason.Evaluate: case ListChangeReason.Replace: { var value = change.Item.Current.Value; var previous = change.Item.Previous.Value.Value; if (value.Equals(previous)) { continue; } removeAction(previous); addAction(value); break; } case ListChangeReason.Remove: { var previous = change.Item.Current.Value; removeAction(previous); break; } case ListChangeReason.RemoveRange: { change.Range.Select(item => item.Value).ForEach(removeAction); break; } case ListChangeReason.Clear: { result.Clear(); values.Clear(); break; } } } return(result.CaptureChanges()); }
public IObservable <IChangeSet <T> > Run() { return(Observable.Create <IChangeSet <T> >(observer => { if (_expireAfter == null && _limitSizeTo < 1) { return _source.Scan(new ChangeAwareList <T>(), (state, latest) => { var items = latest.AsArray(); if (items.Length == 1) { state.Add(items); } else { state.AddRange(items); } return state; }) .Select(state => state.CaptureChanges()) .SubscribeSafe(observer); } long orderItemWasAdded = -1; var locker = new object(); var sourceList = new ChangeAwareList <ExpirableItem <T> >(); var sizeLimited = _source.Synchronize(locker) .Scan(sourceList, (state, latest) => { var items = latest.AsArray(); var expirable = items.Select(t => CreateExpirableItem(t, ref orderItemWasAdded)); if (items.Length == 1) { sourceList.Add(expirable); } else { sourceList.AddRange(expirable); } if (_limitSizeTo > 0 && state.Count > _limitSizeTo) { //remove oldest items [these will always be the first x in the list] var toRemove = state.Count - _limitSizeTo; state.RemoveRange(0, toRemove); } return state; }) .Select(state => state.CaptureChanges()) .Publish(); var timeLimited = (_expireAfter == null ? Observable.Never <IChangeSet <ExpirableItem <T> > >() : 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 => { sourceList.RemoveMany(grouping.Items); return sourceList.CaptureChanges(); }); var publisher = sizeLimited .Merge(timeLimited) .Cast(ei => ei.Item) .NotEmpty() .SubscribeSafe(observer); return new CompositeDisposable(publisher, sizeLimited.Connect()); })); }