private void LoadData(ModelIdentifierBase id, ModelSource source, Subject <IOperationState <Stream> > target, CancellationToken ct = default) { ICache cache = GetCache(id); switch (source) { case ModelSource.Server: target.OnResult(it => cache?.Save(id, it)); LoadImplementation(id, target, ct); break; case ModelSource.Disk: if (cache == null) { target.OnNextError(new OperationError(message: "Disk cache not set!"), 0, ModelSource.Disk); target.OnError(new InvalidOperationException("Disk cache not set!")); //target.OnCompleted(); } else { cache.Load(id, target, ct); } break; default: throw new Exception("Unknown source: " + source); } }
public static IDisposable SubscribeStateChange <TResult>(this IObservable <IOperationState <TResult> > self, Action <TResult> onResult = null, Action <double> onProgress = null, Action <OperationError> onError = null, Action <IOperationState <TResult> > onCompleted = null) where TResult : class { TResult result = null; ModelSource source = ModelSource.Unknown; ModelIdentifierBase id = null; double progress = 0; OperationError error = null; bool isCancelled = false; return(self .Subscribe(state => { TryFire(onProgress, ref progress, state.Progress); TryFire(onError, ref error, state.Error); if (TryFire(onResult, ref result, state.Result)) { id = state.ResultIdentifier; source = state.ResultSource; } isCancelled = state.IsCancelled; }, () => onCompleted?.Invoke(new OperationState <TResult>(result, progress, error, isCancelled, source, id)) )); }
private OperationEntry CreateOperationEntry <T>(ModelIdentifierBase id, ModelSource source, CancellationToken ct, OperationKey key) where T : class { var combinedCts = new CancellationTokenSource(); var newOperation = GetModel <T>(id, source, combinedCts.Token); IDisposable connectDisposable = null; IDisposable subscriptionDisposable = null; void onFinished() { _ongoingOperations.TryRemove(key, out OperationEntry obj); subscriptionDisposable?.Dispose(); connectDisposable?.Dispose(); } subscriptionDisposable = newOperation.Subscribe(__ => { }, __ => onFinished(), onFinished); _operationsObserver.OnNext(newOperation); var newEntry = new OperationEntry(newOperation, ct, () => { combinedCts.Cancel(); onFinished(); } ); return(newEntry); }
private IObservable <IOperationState <T> > GetFromCache <T>(ModelIdentifierBase id, CancellationToken ct) where T : class { var result = _cache?.Get <T>(id); return(result != null? Observable.Return(new OperationState <T>(result, 100, id : id, source : ModelSource.Memory)) : Get <T>(id, ModelSource.Disk, ct)); }
public IObservable <IOperationState <object> > Write(ModelIdentifierBase id, UpdateContainer update, ModelSource target, CancellationToken ct = default) { var replayStates = new ReplaySubject <IOperationState <object> >(); WriteImplementation(id, update, target, replayStates, ct); return(replayStates); }
public T Get <T>(ModelIdentifierBase id) where T : class { if (_cache.TryGetValue(id, out object item)) { return((T)item); } return(null); }
public override bool Equals(ModelIdentifierBase other) { if (other == null) { return(false); } return(base.Equals(other) || typeof(ModelIdentifier <T>) == other.GetType()); }
private IObservable <IOperationState <T> > GetModel <T>(ModelIdentifierBase id, ModelSource source, CancellationToken ct = default) where T : class { var operation = _loader.Load(id, source, ct: ct); operation .WhereResultChanged() .Where(state => state.ResultProgress == 100) .Subscribe(state => { var result = state.Result; var resultId = state.ResultIdentifier; if (id.Equals(resultId)) { // We want to run the updates within the synced AddOrUpdate, but only if we actually have updates for this model. UpdateContainer _ = null; if (_updates.TryGetValue(resultId, out _) && _ != null) { _updates.AddOrUpdate(resultId, (UpdateContainer)null, (__, modelUpdates) => { var updateableResult = result as IUpdateableModel <T>; modelUpdates.Original = updateableResult.CloneForUpdate(); modelUpdates.ForEach(entry => result = entry.Update(result) as T); modelUpdates.Updated = result; return(modelUpdates); } ); } } // Result from disk probably shouldn't overwrite result from server in the memory cache? if (_cache != null) { _cache.Set(resultId, result); } }); return(operation // TODO: Should we start with an empty operationstate ? .Select(state => { var isMatch = id.Equals(state.ResultIdentifier); return new OperationState <T>( isMatch ? state.Result as T : null, state.Progress, state.Error, state.IsCancelled, state.ResultSource, isMatch ? state.ResultIdentifier : null, isMatch ? state.ResultProgress : 0 ); }) .TakeWhile(s => s.Progress <= 100)); }
private IObservable <IOperationState <T> > GetFirstFromCacheThenServer <T>(ModelIdentifierBase id, CancellationToken ct) where T : class { var resultFromCache = GetFromCache <T>(id, ct) .Where(it => it.Error == null && !it.IsCancelled) .Select(it => new OperationState <T>(it.Result, 0.5 * it.Progress, it.Error, it.IsCancelled, it.ResultSource)); var resultFromServer = Get <T>(id, ModelSource.Server, ct) .Select(it => new OperationState <T>(it.Result, 0.5 * (100 + it.Progress), it.Error, it.IsCancelled, it.ResultSource)); return(resultFromCache.Merge(resultFromServer)); }
protected override object ParseImplementation(ModelIdentifierBase id, Stream data) { using (var reader = XmlReader.Create(data, new XmlReaderSettings { IgnoreProcessingInstructions = true, DtdProcessing = DtdProcessing.Ignore })) { var parser = new DataContractSerializer(type); var obj = parser.ReadObject(reader); return(obj); } }
public IObservable <IOperationState <object> > Load(ModelIdentifierBase id, ModelSource source, double loadOperationProgressShare = 0.8, CancellationToken ct = default) { var loadOperationStates = new Subject <IOperationState <Stream> >(); var parseOperationStates = new Subject <IOperationState <object> >(); // Loading and parsing might be running in parallel so we need some logic to combine the state updates var combinedReplayStates = new ReplaySubject <IOperationState <object> >(); Observable.CombineLatest( loadOperationStates .OnResult(buffer => ParseImplementation(id, buffer, parseOperationStates, ct)) .StartWith(null as IOperationState <Stream>), parseOperationStates .StartWith(null as IOperationState <object>), (load, parse) => Tuple.Create(load, parse) ) .Buffer(2, 1) .Where(pairs => pairs.Count == 2) // Last buffer .Select(pairs => { var olds = pairs[0]; var oldLoad = olds?.Item1; var oldParse = olds?.Item2; var news = pairs[1]; var newLoad = news.Item1; var newParse = news.Item2; var loadError = newLoad.Error ?? oldLoad?.Error; var parseError = newParse?.Error ?? oldParse?.Error; var latestError = oldLoad != newLoad ? loadError ?? parseError : parseError ?? loadError; return(new OperationState <object>( newParse?.Result, newParse?.Progress == 100 ? 100 : newLoad.Progress *loadOperationProgressShare + ((newParse?.Progress ?? 0) * (1.0 - loadOperationProgressShare)), latestError, false, // TODO: Cancelled? source, newParse?.ResultIdentifier, newParse?.ResultProgress ?? 0)); }) .Subscribe(combinedReplayStates); LoadData(id, source, loadOperationStates, ct); return(combinedReplayStates); }
public void Parse(ModelIdentifierBase id, Stream data, IObserver <IOperationState <object> > target) { Task.Run(() => { try { ParseImplementation(id, data, target); } catch (Exception e) { target.OnNextError(new OperationError(e), 100, ModelSource.Server); target.OnCompleted(); } }); }
private IObservable <IOperationState <T> > Get <T>(ModelIdentifierBase id, ModelSource source, CancellationToken ct = default) where T : class { var key = new OperationKey(id, source); var entry = _ongoingOperations.AddOrUpdate(key, _ => CreateOperationEntry <T>(id, source, ct, key), (_, oldEntry) => oldEntry.TryRegisterCancellation(ct) ? oldEntry : CreateOperationEntry <T>(id, source, ct, key) ); var operation = (IObservable <IOperationState <T> >)entry.Operation; return(operation .TakeWhile(_ => !ct.IsCancellationRequested) .Concat(Observable.Defer(() => ct.IsCancellationRequested ? Observable.Return(new OperationState <T>(isCancelled: true)) : Observable.Empty <OperationState <T> >()))); }
protected abstract void LoadImplementation(ModelIdentifierBase id, IObserver <IOperationState <Stream> > target, CancellationToken ct = default);
private IObservable <IOperationState <T> > GetFromCacheWithServerFallback <T>(ModelIdentifierBase id, CancellationToken ct) where T : class { var resultFromCache = GetFromCache <T>(id, ct); return(resultFromCache.WithFallback(() => Get <T>(id, ModelSource.Server, ct))); }
protected abstract void WriteImplementation(ModelIdentifierBase id, UpdateContainer update, ModelSource target, IObserver <IOperationState <object> > operation, CancellationToken ct = default);
public UpdateKey(ModelIdentifierBase identifier, object updateId) { Identifier = identifier; UpdateId = updateId; }
protected override void ParseImplementation(ModelIdentifierBase id, Stream data, IObserver <IOperationState <object> > target) { target.OnCompleteResult(ParseImplementation(id, data), id, 100, ModelSource.Server); target.OnCompleted(); }
public void Set <T>(ModelIdentifierBase id, T model) where T : class { _cache.AddOrUpdate(id, model, (_, __) => model); }
protected abstract void ParseImplementation(ModelIdentifierBase id, Stream data, IObserver <IOperationState <object> > target);
public static IObserver <IOperationState <TResult> > OnIncompleteResult <TResult>(this IObserver <IOperationState <TResult> > self, TResult result, ModelIdentifierBase id, double progress, double resultProgress, ModelSource source = ModelSource.Unknown) where TResult : class { self.OnNext(new OperationState <TResult>(result: result, id: id, progress: progress, source: source, resultProgress: resultProgress)); return(self); }
protected abstract object ParseImplementation(ModelIdentifierBase id, Stream data);
public OperationKey(ModelIdentifierBase identifier, ModelSource source) { Identifier = identifier; Source = source; }
protected abstract void ParseImplementation(ModelIdentifierBase id, Stream data, IObserver <IOperationState <object> > target, CancellationToken ct = default);
public override bool Equals(ModelIdentifierBase other) { return(this == other || (other as KeyedModelIdentifier <T, TKey>)?.Key.Equals(Key) == true); }
protected virtual ICache GetCache(ModelIdentifierBase id) { return(_cache); }