public ActiveProcessesService(TimeSpan refreshInterval, IScheduler scheduler) { Ensure.ArgumentNotNull(refreshInterval, nameof(refreshInterval)); Ensure.ArgumentNotNull(scheduler, nameof(scheduler)); _scheduler = scheduler; _refreshInterval = refreshInterval; _scheduler.SchedulePeriodic(_refreshInterval, () => { var processes = Process.GetProcesses(); _processNames.Edit(innerList => { innerList.Clear(); innerList.AddRange(processes.Select(x => x.ProcessName)); }); _processNames.Edit(innerList => { innerList.Clear(); innerList.AddRange(processes.Select(x => x.MainWindowTitle)); }); }); }
/// <summary> /// Removes a validation from the validations collection. /// </summary> /// <param name="validation">Validation component to be removed from the collection.</param> public void Remove(IValidationComponent validation) => _validationSource.Edit(list => { if (list.Contains(validation)) { list.Remove(validation); } });
/// <summary> /// Binds the results to the specified <see cref="IObservableList{T}"/>. Unlike /// binding to a <see cref="ReadOnlyObservableCollection{T}"/> which loses the /// ability to refresh items, binding to an <see cref="IObservableList{T}"/>. /// allows for refresh changes to be preserved and keeps the list read-only. /// </summary> /// <typeparam name="TObject">The type of the object.</typeparam> /// <typeparam name="TKey">The type of the key.</typeparam> /// <param name="source">The source.</param> /// <returns>The <paramref name="source"/> changeset for continued chaining.</returns> /// <exception cref="System.ArgumentNullException">source</exception> public static IObservable <IChangeSet <TObject, TKey> > BindToObservableList <TObject, TKey>( this IObservable <IChangeSet <TObject, TKey> > source, out IObservableList <TObject> observableList) { if (source == null) { throw new ArgumentNullException(nameof(source)); } // Load our source list with the change set. // Each changeset we need to convert to remove the key. var sourceList = new SourceList <TObject>(); // Output our readonly observable list, preventing the sourcelist from being editted from anywhere else. observableList = sourceList; // Return a observable that will connect to the source so we can properly dispose when the pipeline ends. return(Observable.Create <IChangeSet <TObject, TKey> >(observer => { return source .Do(changes => sourceList.Edit(editor => editor.Clone(changes.RemoveKey(editor)))) .Finally(() => sourceList.Dispose()) .SubscribeSafe(observer); })); }
public static IObservable <IChangeSet <TElement> > SelectedItemsAsObservable <TControl, TElement>(this TControl control) where TControl : ListViewBase { return(Observable.Create <IChangeSet <TElement> >(o => { var disposes = new CompositeDisposable(); var items = new SourceList <TElement>(); control.Events() .SelectionChanged .Subscribe(x => { items.Edit(y => { y.RemoveMany(x.RemovedItems.Cast <TElement>()); y.AddRange(x.AddedItems.Cast <TElement>()); }); }) .DisposeWith(disposes); items.Connect() .Subscribe(o) .DisposeWith(disposes); return disposes; })); }
private async Task SetRecommendationsAsync() { try { var profile = await _userProfileContainer.GetAsync(); var statusId = profile?.StatusId; if (statusId != null) { IsBusy = true; var recommendationsList = await _recommendationsService.GetRecommendationsAsync(statusId.Value, LifecycleToken); _models.Edit((innerList) => { innerList.Clear(); innerList.AddRange(recommendationsList.Data); }); } } catch (Exception e) { await _errorHandler.HandleAsync(e); } finally { IsBusy = false; } }
public MainViewModel() { _locationSource.Edit(innerList => { innerList.Add(new LocationViewModel() { LocationNumber = "", LocationName = "", LOB = "", ReportDate = "" }); }); _driverSource.Edit(innerList => { innerList.Add(new DriverViewModel() { DriverName = "" }); }); _locationSource.Connect() .Bind(out _locations) .Subscribe(); _driverSource.Connect() .Bind(out _drivers) .Subscribe(); }
public DetailViewModel() { _sources = new SourceList <UserTask>(); _sources.Connect() .Bind(out _items) .Subscribe(); _sources.AddRange(GetDemoTask()); Save = ReactiveCommand.Create(() => { FileService.Save(_sources.Items.ToList()); }); Insert = ReactiveCommand.Create(() => { _sources.Edit(x => { x.Add(new UserTask() { Contents = $"{DateTime.Now.ToString("hh:mm:ss")}", }); }); }); }
public FilterDynamicDataViewModel() { this.WhenActivated((CompositeDisposable disposables) => { _searchResultSource = new SourceList <RssEntry>().DisposeWith(disposables); _searchResultSource .Connect() .ObserveOn(RxApp.MainThreadScheduler) .Bind(out var searchResultsBinding) .Subscribe() .DisposeWith(disposables); this.SearchResults = searchResultsBinding; Search = ReactiveCommand .CreateFromTask( async(ct) => { var worldNews = await RssDownloader.DownloadRss("https://www.reddit.com/r/worldnews/new/.rss", ct).ConfigureAwait(false); _searchResultSource .Edit( innerList => { innerList.Clear(); innerList.AddRange(worldNews); }); }) .DisposeWith(disposables); }); }
public EndpointsViewModel() { sourceEndpoints = new SourceList<Endpoint>(); var filter = Observable.Return("") .Concat(this.ChangedProperty<string>(nameof(Search)).Select(pc => pc.After)) //.Throttle(TimeSpan.FromMilliseconds(500)) .Select(BuildFilter); sourceEndpoints.Connect() .Filter(filter) .Sort(SortExpressionComparer<Endpoint>.Ascending(e => e.Name)) .ObserveOnUI() .Bind(out endpoints) .Subscribe(); ServiceControl.Instance.Endpoints() .SubscribeOnBackground() .Subscribe(es => { sourceEndpoints.Edit(inner => { inner.Clear(); inner.AddRange(es.DeserializeCollection<Endpoint>()); }); }); }
public void Reload(BoxScheme scheme) { _boardsSource.Edit(x => { x.Clear(); x.AddRange(scheme.Boards.Select(y => new BoardImportSchemeViewModel { Id = y.Id, Name = y.Name, IsSelected = true })); }); _columnsSource.Edit(x => { x.Clear(); x.AddRange(scheme.Columns.Select(y => new ColumnImportSchemeViewModel { Id = y.Id, BoardId = y.BoardId, Name = y.Name, IsSelected = true })); }); _rowsSource.Edit(x => { x.Clear(); x.AddRange(scheme.Rows.Select(y => new RowImportSchemeViewModel { Id = y.Id, BoardId = y.BoardId, Name = y.Name, IsSelected = true })); }); SelectedBoard = _boardsSource.Items.FirstOrDefault(); }
/// <summary> /// Binds the results to the specified <see cref="IObservableList{T}"/>. Unlike /// binding to a <see cref="ReadOnlyObservableCollection{T}"/> which loses the /// ability to refresh items, binding to an <see cref="IObservableList{T}"/>. /// allows for refresh changes to be preserved and keeps the list read-only. /// </summary> /// <typeparam name="TObject">The type of the object.</typeparam> /// <typeparam name="TKey">The type of the key.</typeparam> /// <param name="source">The source.</param> /// <returns>The <paramref name="source"/> changeset for continued chaining.</returns> /// <exception cref="System.ArgumentNullException">source</exception> public static IObservable <ISortedChangeSet <TObject, TKey> > BindToObservableList <TObject, TKey>( this IObservable <ISortedChangeSet <TObject, TKey> > source, out IObservableList <TObject> observableList) { if (source == null) { throw new ArgumentNullException(nameof(source)); } // Load our source list with the change set. // Each changeset we need to convert to remove the key. var sourceList = new SourceList <TObject>(); // Output our readonly observable list, preventing the sourcelist from being editted from anywhere else. observableList = sourceList; // Return a observable that will connect to the source so we can properly dispose when the pipeline ends. return(Observable.Create <ISortedChangeSet <TObject, TKey> >(observer => { return source .Do(changes => { switch (changes.SortedItems.SortReason) { case SortReason.InitialLoad: sourceList.AddRange(changes.SortedItems.Select(kv => kv.Value)); break; case SortReason.ComparerChanged: case SortReason.DataChanged: case SortReason.Reorder: sourceList.Edit(editor => editor.Clone(changes.RemoveKey(editor))); break; case SortReason.Reset: sourceList.Edit(editor => { editor.Clear(); editor.AddRange(changes.SortedItems.Select(kv => kv.Value)); }); break; } }) .Finally(() => sourceList.Dispose()) .SubscribeSafe(observer); })); }
public void Delete(UserDetailsViewModel viewModel) { _userService .DeleteUser(viewModel.Id) .Subscribe(_ => { _users .Edit(innerList => { var existing = innerList.FirstOrDefault(x => x.Id == viewModel.Id); if (existing != null) { innerList.Remove(existing); } }); }); }
public void SingleSubscriberThatThrows() { var s = new SourceList <int>(); s.Connect().Subscribe(i => { throw new NotImplementedException(); }); Assert.Throws <NotImplementedException>(() => s.Edit(u => u.Add(1))); }
public static void ClearAndAddRange <T>(this SourceList <T> list, IEnumerable <T> data) { list.Edit(il => { il.Clear(); il.AddRange(data); }); }
public async Task GetPage(string query, int pageNumber) { if (string.IsNullOrEmpty(query)) { RemainingPages.OnNext(0); _moviesInternalList.Clear(); _searchActive = false; return; } if (query == _query && pageNumber == _pageNumber) { return; } // Cancel search if (_searchActive) { _cts.Cancel(); _cts = new CancellationTokenSource(); } try { _searchActive = true; var result = await _dataService.GetItemsAsync(query, pageNumber, _cts.Token); if (_query != query) { _moviesInternalList.Clear(); } _query = query; _pageNumber = pageNumber; if (result.Response) { _moviesInternalList.Edit(list => list.AddRange(result.Movies)); RemainingPages.OnNext(result.RemainingPages); } else { RemainingPages.OnNext(0); } } catch (OperationCanceledException) { Debug.WriteLine("Cancelled previous search"); _searchActive = false; await GetPage(query, pageNumber); } catch (Exception e) { RemainingPages.OnNext(0); Debug.WriteLine("Something unexpected happened while fetching data"); } finally { _searchActive = false; } }
public TradeModel(IEnumerable <ExchangeTrade> exchangeTrades = null) { Trades = new SourceList <ExchangeTrade>(); tradeObservable = Trades.Connect(); if (exchangeTrades != null) { Trades.Edit(innerList => innerList.AddRange(exchangeTrades)); } }
public SortDynamicDataViewModel() { this.WhenActivated((CompositeDisposable disposables) => { _searchResultSource = new SourceList <RssEntry>().DisposeWith(disposables); var sorter = this.WhenAnyValue(x => x.SelectedSortType) .Select( SelectedSortType => { switch (SelectedSortType) { case SortType.DateTimeAscending: return(SortExpressionComparer <RssEntry> .Ascending(x => x.Updated)); case SortType.DateTimeDescending: return(SortExpressionComparer <RssEntry> .Descending(x => x.Updated)); case SortType.TitleAscending: return(SortExpressionComparer <RssEntry> .Ascending(x => x.Title)); case SortType.TitleDescending: default: return(SortExpressionComparer <RssEntry> .Descending(x => x.Title)); } }); _searchResultSource .Connect() .SubscribeOn(RxApp.TaskpoolScheduler) .Sort(sorter) .ObserveOn(RxApp.MainThreadScheduler) .Bind(out var searchResultsBinding) .Subscribe() .DisposeWith(disposables); this.SearchResults = searchResultsBinding; Search = ReactiveCommand .CreateFromTask( async(ct) => { var worldNews = await RssDownloader.DownloadRss("https://www.reddit.com/r/worldnews/new/.rss", ct).ConfigureAwait(false); _searchResultSource .Edit( innerList => { innerList.Clear(); innerList.AddRange(worldNews); }); }) .DisposeWith(disposables); }); }
public void ExceptionShouldBeThrownInSubscriberCode() { var changesCountFromSubscriber1 = 0; var changesCountFromSubscriber2 = 0; var source = new SourceList <int>(); var observable = source.Connect().RefCount(); observable.Subscribe(i => changesCountFromSubscriber1++); observable.Subscribe(i => { throw new NotImplementedException(); }); observable.Subscribe(i => changesCountFromSubscriber2++); source.Edit(l => l.Add(1)); source.Edit(l => l.Add(2)); Assert.That(changesCountFromSubscriber1, Is.EqualTo(2)); Assert.That(changesCountFromSubscriber2, Is.EqualTo(2)); }
public async Task CreateData(CancellationToken ct) { var rng = new Random(); while (!ct.IsCancellationRequested) { _dataList.Edit(innerList => { innerList.Clear(); Observable.Range(0, 20).Subscribe(index => { var number = rng.Next(-10000, 10000); innerList.Add(number); }); }); await Task.Yield(); } }
public void MultipleSubscribersHandlingErrorsWithOneThrowing_MultipleEdits() { var firstSubscriberCallCount = 0; var secondSubscriberCallCount = 0; var firstSubscriberExceptionHandleCount = 0; var secondSubscriberExceptionHandleCount = 0; var s = new SourceList <int>(); s.Connect().Subscribe(i => firstSubscriberCallCount++, e => firstSubscriberExceptionHandleCount++); s.Connect().Subscribe(i => { throw new NotImplementedException(); }); s.Connect().Subscribe(i => secondSubscriberCallCount++, e => secondSubscriberExceptionHandleCount++); s.Edit(u => u.Add(1)); s.Edit(u => u.Add(1)); Assert.That(firstSubscriberCallCount, Is.EqualTo(2)); Assert.That(secondSubscriberCallCount, Is.EqualTo(2)); Assert.That(firstSubscriberExceptionHandleCount, Is.EqualTo(1)); Assert.That(secondSubscriberExceptionHandleCount, Is.EqualTo(1)); }
public void ReadonlyCollectionDynamicData() { var scheduler = new TestScheduler(); var list = new SourceList <int>(); var filterFunc = new BehaviorSubject <Func <int, bool> >(x => true); list.Connect() //if you comment out the filter or the observeon then this passes .Filter(filterFunc.ObserveOn(scheduler)) .ObserveOn(scheduler) .Bind(out var oc) .Subscribe(); list.Edit(updateAction => { updateAction.Add(1); updateAction.Add(2); updateAction.Add(3); }); scheduler.Start(); oc.Count.Should().Be(3); list.Edit(updateAction => { updateAction.Remove(1); updateAction.Remove(2); updateAction.Remove(3); updateAction.Count.Should().Be(0); updateAction.Add(4); updateAction.Add(5); updateAction.Add(6); }); scheduler.Start(); //This fails and those other update actions aren't removed oc.Count.Should().Be(3); }
public LineScroller(FileInfo file, IObservable <ILineProvider> latest, IObservable <ScrollRequest> scrollRequest, ILogger logger, IScheduler scheduler = null) { if (file == null) { throw new ArgumentNullException(nameof(file)); } if (latest == null) { throw new ArgumentNullException(nameof(latest)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } logger.Info($"Constructing file tailer for {file.FullName}"); var lines = new SourceList <Line>(); Lines = lines.AsObservableList(); var locker = new object(); scrollRequest = scrollRequest.Synchronize(locker); var aggregator = latest.CombineLatest(scrollRequest, (currentLines, scroll) => currentLines.ReadLines(scroll).ToArray()) .Subscribe(currentPage => { var previous = lines.Items.ToArray(); var added = currentPage.Except(previous, Line.TextStartComparer).ToArray(); var removed = previous.Except(currentPage, Line.TextStartComparer).ToArray(); lines.Edit(innerList => { if (removed.Any()) { innerList.RemoveMany(removed); } if (added.Any()) { innerList.AddRange(added); } }); }); _cleanUp = new CompositeDisposable(Lines, lines, aggregator); }
public DashboardViewModelCore(DustService dustService = null, ExcelGenerator excelGenerator = null, IScreen screen = null) { _excelGenerator = excelGenerator ?? Locator.Current.GetService <ExcelGenerator>(); _dustService = dustService ?? Locator.Current.GetService <DustService>(); HostScreen = screen ?? Locator.Current.GetService <IScreen>(); var canLoadDataCommand = this.WhenAnyValue(p => p.SelectedStation) .Select(p => p != null); LoadDataCommand = ReactiveCommand.CreateFromObservable <Station, Record[]>(station => { //Info = new DashboardInfo(); _values.Clear(); IObservable <Record[]> s = _dustService.GetAvailableParametersAsync(station) .Select(@params => @params.Select(p => p.Param).ToArray()) .Select(@params => _dustService.GetStationRecordsAsync(station.Code, @params)).Switch(); return(Observable.Start(() => s, RxApp.TaskpoolScheduler).Switch().TakeUntil(CancelCommand));; }, canLoadDataCommand); CancelCommand = ReactiveCommand.Create( () => { }, this.LoadDataCommand.IsExecuting); this.WhenActivated(cleanup => { var share = _values.Connect().Publish().RefCount(); share.ToCollection().Select(records => { return(Enum.GetValues(typeof(RecordType)).Cast <RecordType>() .Select(type => records.LastOrDefault(r => r.Type == type))); }).BindTo(this, vm => vm.LastRecords).DisposeWith(cleanup); share.Bind(StationData).Subscribe().DisposeWith(cleanup); LoadDataCommand.ThrownExceptions.Subscribe(ShowError).DisposeWith(cleanup); LoadDataCommand.Subscribe(records => { _values.Edit(e => e.AddRange(records)); }).DisposeWith(cleanup); var canSaveToExcelCommand = StationData.ObserveCollectionChanges().Select(_ => StationData.Count > 0); SaveToExcelCommand = ReactiveCommand.CreateFromTask <IEnumerable <Record> >(data => _excelGenerator.CreateExcel(SelectedStation.Code, data), canSaveToExcelCommand); SaveToExcelCommand.ThrownExceptions.Subscribe(ShowError); this.WhenAnyValue(p => p.SelectedStation).Where(p => p != null).Do(_ => CancelCommand.Execute().Subscribe()).InvokeCommand(LoadDataCommand).DisposeWith(cleanup);; }); }
public void UpdateRows(List <Row> rows) { _rowsSource.Edit(x => { x.Clear(); if (rows != null) { x.AddRange(rows.Select(y => new RowImportSchemeViewModel { Id = y.Id, BoardId = y.BoardId, Name = y.Name, IsSelected = true })); } }); }
public void UpdateColumns(List <Column> columns) { _columnsSource.Edit(x => { x.Clear(); if (columns != null) { x.AddRange(columns.Select(y => new ColumnImportSchemeViewModel { Id = y.Id, BoardId = y.BoardId, Name = y.Name, IsSelected = true })); } }); }
/// <summary> /// Use this constructor to create instances in your code. /// Note: leave the APInitOb null. Aplay sets this object if initialized by aplay. /// if you want to determine in the constructor if the object is user created or by aplay - check IsInitializedByAPlay /// </summary> public ProjectManager() { //Todo: Read that shit and think about it. //Solange es keine Möglichkeit gibt die Änderungen der AplayList direkt //per Extension-Methode in einen SourceCache umzuwandeln findet die Umwandlung hier statt und nicht im VM. _projectsRx = new SourceList <Project>(); _projectsRx.Edit(e => { e.AddRange(Projects); }); _cleanup = new CompositeDisposable(_projectsRx); //var asl = new APlaySourceList(handler => this.ProjectsAddEventHandler += handler); }
public void UpdateBoards(List <Board> boards) { _boardsSource.Edit(x => { x.Clear(); if (boards != null) { x.AddRange(boards.Select(y => new BoardImportSchemeViewModel { Id = y.Id, Name = y.Name, IsSelected = true, IsEnabled = true })); } }); SelectedBoard = _boardsSource.Items.FirstOrDefault(); }
public void Upsert(RawQueryHistory item) { _historySourceList.Edit(list => { var lastHistory = list .OrderByDescending(p => p.LastRunAt) .FirstOrDefault(p => RawQueryHistory.RawSourceComparer.Equals(item, p)); if (lastHistory != null) { lastHistory.LastRunAt = DateTime.UtcNow; } else { list.Add(item); } }); }
public void SortAfterFilterList() { var source = new SourceList <Person>(); var filterSubject = new BehaviorSubject <Func <Person, bool> >(p => true); var agg = source.Connect().Filter(filterSubject).Transform(x => new ViewModel(x.Name)).Sort(new ViewModel.Comparer()).AsAggregator(); source.Edit( x => { x.Add(new Person("A", 1, "F")); x.Add(new Person("a", 1, "M")); x.Add(new Person("B", 1, "F")); x.Add(new Person("b", 1, "M")); }); filterSubject.OnNext(p => p.Name.Equals("a", StringComparison.OrdinalIgnoreCase)); }
public void MultipleSubscribersHandlingErrorsWithOneThrowing() { var firstSubscriberCallCount = 0; var secondSubscriberCallCount = 0; var firstSubscriberExceptionHandleCount = 0; var secondSubscriberExceptionHandleCount = 0; var s = new SourceList <int>(); s.Connect().Subscribe(i => firstSubscriberCallCount++, e => firstSubscriberExceptionHandleCount++); s.Connect().Subscribe(i => { throw new NotImplementedException(); }, ex => {}); s.Connect().Subscribe(i => secondSubscriberCallCount++, e => secondSubscriberExceptionHandleCount++); s.Edit(u => u.Add(1)); firstSubscriberExceptionHandleCount.Should().Be(1); secondSubscriberExceptionHandleCount.Should().Be(1); firstSubscriberCallCount.Should().Be(1); secondSubscriberCallCount.Should().Be(1); }
public AddinViewModel(ISafeguard safeguard, IExcel excel) { Safeguard = safeguard; Excel = excel; var canAuthenticate = this.WhenNotBusyAnd( this.WhenAnyValue(x => x.IsAuthenticated, x => x.NetworkAddress) .Select(x => !x.Item1 && !string.IsNullOrEmpty(x.Item2))); var authenticate = ReactiveCommand.Create(DoAuthenticate, canAuthenticate); AuthenticateCommand = authenticate; var canLogout = this.WhenNotBusyAnd(this.WhenAnyValue(x => x.IsAuthenticated)); var logout = ReactiveCommand.Create(DoLogout, canLogout); LogoutCommand = logout; var canExecuteReport = this.WhenNotBusyAnd(this.WhenAnyValue(x => x.SelectedReport).Select(x => x != null)); var execute = ReactiveCommand.CreateFromTask(DoExecute, canExecuteReport); ExecuteReportCommand = execute; _reports.Connect() .Bind(Reports) .Subscribe(); safeguard.GetReports().ContinueWith(task => { if (task.IsCompleted) { _reports.Edit(inner => { foreach (var report in task.Result) { inner.Add(new Report(report)); } }); } SelectedReport = Reports.First(); }); }
public FileTailer(FileInfo file, IObservable<FileSearchResult> filter, IObservable<ScrollRequest> scrollRequest, ILogger logger, IScheduler scheduler = null) { if (file == null) throw new ArgumentNullException(nameof(file)); if (filter == null) throw new ArgumentNullException(nameof(filter)); if (logger == null) throw new ArgumentNullException(nameof(logger)); logger.Info($"Constructing file tailer for {file.FullName}"); var lines = new SourceList<Line>(); Lines = lines.AsObservableList(); var isBusy = new Subject<bool>(); IsSearching = isBusy.AsObservable(); var locker = new object(); scrollRequest = scrollRequest.Synchronize(locker); var fileWatcher = file.WatchFile(scheduler: scheduler) .DistinctUntilChanged() .TakeWhile(notification => notification.Exists).Repeat() .Replay(1).RefCount(); var indexer = fileWatcher.Index().Replay(1).RefCount(); //compare latest lines and latest filter and only take the filtered results it is not empty var latestLines = indexer.Cast<ILineProvider>().Synchronize(locker); var latestFilter = filter.Cast<ILineProvider>().Synchronize(locker); var latest = latestLines.CombineLatest(latestFilter, (l, f) => f.IsEmpty ? l : f); MatchedLines = latest.Select(provider => provider.Count); TotalLines = latestLines.Select(x => x.Count); FileSize = fileWatcher.Select(notification => notification.Size); IsLoading = indexer.Take(1).Select(_ => false).StartWith(true); var aggregator = latest.CombineLatest(scrollRequest, (currentLines, scroll) => { return currentLines.ReadLines(scroll).ToArray(); }) .Subscribe(currentPage => { var previous = lines.Items.ToArray(); var added = currentPage.Except(previous).ToArray(); var removed = previous.Except(currentPage).ToArray(); lines.Edit(innerList => { if (removed.Any()) innerList.RemoveMany(removed); if (added.Any()) innerList.AddRange(added); }); }); //var aggregator = latest.CombineLatest(scrollRequest, (currentLines, scroll) => //{ // //TODO: Read entire page, the check which lines should be added and which shold be removed // //as part of that work, get the maximum inded [this is the head!] // // Debug.WriteLine($"{scroll.Mode}, {scroll.FirstIndex}, {scroll.PageSize}"); // var currentPage = currentLines.GetIndicies(scroll).ToArray(); // var previous = lines.Items.Select(l => l.LineInfo).ToArray(); // var removed = previous.Except(currentPage, LineInfo.LineIndexComparer).ToArray(); // var added = currentPage.Except(previous, LineInfo.LineIndexComparer).ToArray(); // //calculated added and removed lines // var removedLines = lines.Items.Where(l => removed.Contains(l.LineInfo)).ToArray(); // Func<long, DateTime?> isTail = l => // { // //account for time with tail (i.e. add time to ILineProvider.TailStartsAt ) // var tail = currentLines.TailStartsAt; // var onTail = tail != -1 && l >= tail; // return onTail ? DateTime.Now : (DateTime?)null; // }; // //Console.WriteLine(); // //finally we can load the line from the file todo: Add encdoing back in // var newLines = file.ReadLine(added, (lineIndex, text, position) => new Line(lineIndex, text, isTail(position)), Encoding.UTF8).ToArray(); // return new { NewLines = newLines, OldLines = removedLines }; //}) //.Where(fn => fn.NewLines.Length + fn.OldLines.Length > 0) //.Subscribe(changes => //{ // //update observable list // lines.Edit(innerList => //{ // if (changes.OldLines.Any()) innerList.RemoveMany(changes.OldLines); // if (changes.NewLines.Any()) innerList.AddRange(changes.NewLines); //}); //}); _cleanUp = new CompositeDisposable(Lines, lines, aggregator, Disposable.Create(() => isBusy.OnCompleted())); }
public FileTailer(FileInfo file, IObservable<string> textToMatch, IObservable<ScrollRequest> scrollRequest, IScheduler scheduler=null) { if (file == null) throw new ArgumentNullException(nameof(file)); if (textToMatch == null) throw new ArgumentNullException(nameof(textToMatch)); //create list of lines which contain the observable text var matchedLines = textToMatch .Select(searchText => { Func<string, bool> predicate = null; if (!string.IsNullOrEmpty(searchText)) predicate = s => s.Contains(searchText, StringComparison.OrdinalIgnoreCase); return file.WatchFile(scheduler:scheduler ?? Scheduler.Default) .TakeWhile(notification => notification.Exists) .Repeat() .ScanFile(predicate); }).Switch() .Replay(1).RefCount(); MatchedLines = matchedLines.Select(x => x.MatchingLines.Length); TotalLines = matchedLines.Select(x => x.TotalLines); //todo: plug in file missing or error into the screen var lines = new SourceList<Line>(); Lines = lines.AsObservableList(); //this is the beast! Dynamically combine lines requested by the consumer //with the lines which exist in the file. This enables proper virtualisation of the file var scroller = matchedLines .CombineLatest(scrollRequest, (scanResult, request) => new {scanResult , request }) .Subscribe(x => { var mode = x.request.Mode; var pageSize = x.request.PageSize; var endOfTail = x.scanResult.EndOfTail; var isInitial = x.scanResult.Index==0; var allLines = x.scanResult.MatchingLines; var previousPage = lines.Items.Select(l => new LineIndex(l.Number, l.Index)).ToArray(); //Otherwise take the page size and start index from the request var currentPage = (mode == ScrollingMode.Tail ? allLines.Skip(allLines.Length-pageSize).Take(pageSize).ToArray() : allLines.Skip(x.request.FirstIndex).Take(pageSize)).ToArray(); var added = currentPage.Except(previousPage).ToArray(); var removed = previousPage.Except(currentPage).Select(li=>li.Line).ToArray(); if (added.Length + removed.Length == 0) return; try { var addedLines = file.ReadLines(added, (lineIndex, text) => { var isEndOfTail = !isInitial && lineIndex.Line > endOfTail; return new Line(lineIndex.Line, lineIndex.Index, text, isEndOfTail ? DateTime.Now : (DateTime?)null); }).ToArray(); //get old lines from the current collection var removedLines = lines.Items.Where(l => removed.Contains(l.Number)).ToArray(); //finally relect changes in the list lines.Edit(innerList => { innerList.RemoveMany(removedLines); innerList.AddRange(addedLines); }); } catch (Exception) { //Very small chance of an error here but if one is causght the next successful read will recify this //TODO: 1. Feedback to user that steaming has stopped //TODO: 2. Place the ReadLines(..) method with the select of an observable } }); _cleanUp = new CompositeDisposable(Lines, scroller, lines); }
public FileTailer(FileInfo file, IObservable<string> textToMatch, IObservable<ScrollRequest> scrollRequest, IScheduler scheduler=null) { if (file == null) throw new ArgumentNullException(nameof(file)); if (textToMatch == null) throw new ArgumentNullException(nameof(textToMatch)); var lines = new SourceList<Line>(); Lines = lines.AsObservableList(); var matcher = textToMatch.Select(searchText => { if (string.IsNullOrEmpty(searchText) || searchText.Length < 3) return Observable.Return(LineMatches.None); return file.WatchFile(scheduler: scheduler) .TakeWhile(notification => notification.Exists) .Repeat() .Match(s => s.Contains(searchText, StringComparison.OrdinalIgnoreCase)); }).Switch() .Replay(1).RefCount(); //temp mess for a few days var indexer = file.WatchFile(scheduler: scheduler) .TakeWhile(notification => notification.Exists) .Repeat() .Index() .Replay(1).RefCount(); //count matching lines (all if no filter is specified) MatchedLines = indexer.CombineLatest(matcher, (indicies, matches) => matches == LineMatches.None ? indicies.Count : matches.Count); //count total line TotalLines = indexer.Select(x => x.Count); //todo: plug in file missing or error into the screen var locker = new object(); var theBeast = indexer.Synchronize(locker) .CombineLatest(matcher.Synchronize(locker), scrollRequest.Synchronize(locker),(idx, mtch, scroll) => new CombinedResult(scroll, mtch, idx)) .Select(result => { var scroll = result.Scroll; var allLines = result.Incidies; var matched = result.MatchedLines; IEnumerable<LineIndex> indices; if (result.MatchedLines.ChangedReason == LineMatchChangedReason.None) { indices = scroll.Mode == ScrollingMode.Tail ? allLines.GetTail(scroll) : allLines.GetFromIndex(scroll); } else { indices = scroll.Mode == ScrollingMode.Tail ? allLines.GetTail(scroll, matched) : allLines.GetFromIndex(scroll, matched); } return file.ReadLines(indices, (lineIndex, text) => { var isEndOfTail = allLines.ChangedReason != LinesChangedReason.Loaded && lineIndex.Line > allLines.TailStartsAt; return new Line(lineIndex.Line, lineIndex.Index, text,isEndOfTail ? DateTime.Now : (DateTime?) null); }).ToArray(); }) //.RetryWithBackOff((error, attempts) => //{ // //TODO: Log // return TimeSpan.FromSeconds(1); //}) .Subscribe(newPage => { //update observable list lines.Edit(innerList => { var removed = innerList.Except(newPage).ToArray(); var added = newPage.Except(innerList).ToArray(); if (removed.Any()) innerList.RemoveMany(removed); if (added.Any()) innerList.AddRange(added); }); }); ////this is the beast! Dynamically combine lines requested by the consumer ////with the lines which exist in the file. This enables proper virtualisation of the file //var scroller = matchedLines // .CombineLatest(scrollRequest, (scanResult, request) => new {scanResult , request }) // .Subscribe(x => // { // var mode = x.request.Mode; // var pageSize = x.request.PageSize; // var endOfTail = x.scanResult.EndOfTail; // var isInitial = x.scanResult.Index==0; // var allLines = x.scanResult.MatchingLines; // var previousPage = lines.Items.Select(l => new LineIndex(l.Number, l.Index, 0, 0)).ToArray(); // //Otherwise take the page size and start index from the request // var currentPage = (mode == ScrollingMode.Tail // ? allLines.Skip(allLines.Length-pageSize).Take(pageSize).ToArray() // : allLines.Skip(x.request.FirstIndex).Take(pageSize)).ToArray(); // var added = currentPage.Except(previousPage).ToArray(); // var removed = previousPage.Except(currentPage).Select(li=>li.Line).ToArray(); // if (added.Length + removed.Length == 0) return; // try // { // var addedLines = file.ReadLines(added, (lineIndex, text) => // { // var isEndOfTail = !isInitial && lineIndex.Line > endOfTail; // return new Line(lineIndex.Line, lineIndex.Index, text, isEndOfTail ? DateTime.Now : (DateTime?)null); // }).ToArray(); // //get old lines from the current collection // var removedLines = lines.Items.Where(l => removed.Contains(l.Number)).ToArray(); // //finally relect changes in the list // lines.Edit(innerList => // { // innerList.RemoveMany(removedLines); // innerList.AddRange(addedLines); // }); // } // catch (Exception) // { // //Very small chance of an error here but if one is causght the next successful read will recify this // //TODO: 1. Feedback to user that steaming has stopped // //TODO: 2. Place the ReadLines(..) method with the select of an observable // } // }); _cleanUp = new CompositeDisposable(Lines, lines); }
public FileTailer(FileInfo file, IObservable<string> textToMatch, IObservable<ScrollRequest> scrollRequest, IScheduler scheduler=null) { if (file == null) throw new ArgumentNullException(nameof(file)); if (textToMatch == null) throw new ArgumentNullException(nameof(textToMatch)); var lines = new SourceList<Line>(); Lines = lines.AsObservableList(); var locker = new object(); scrollRequest = scrollRequest.Synchronize(locker); var metronome = Observable .Interval(TimeSpan.FromMilliseconds(250), scheduler ?? Scheduler.Default) .ToUnit() .Replay().RefCount(); //temp mess for a few days var indexer = file.WatchFile(metronome) .TakeWhile(notification => notification.Exists) .Repeat() .Index() .Synchronize(locker) .Replay(1).RefCount(); var matcher = textToMatch.Select(searchText => { if (string.IsNullOrEmpty(searchText) || searchText.Length < 3) return Observable.Return(LineMatches.None); return file.WatchFile(metronome) .TakeWhile(notification => notification.Exists) .Repeat() .Match(s => s.Contains(searchText, StringComparison.OrdinalIgnoreCase)); }).Switch() .Synchronize(locker) .Replay(1).RefCount(); //count matching lines (all if no filter is specified) MatchedLines = indexer.CombineLatest(matcher, (indicies, matches) => matches == LineMatches.None ? indicies.Count : matches.Count); //count total line TotalLines = indexer.Select(x => x.Count); FileSize = file.WatchFile(metronome).Select(notification => notification.Size); var aggregator = indexer.CombineLatest(matcher, scrollRequest,(idx, mtch, scroll) => new CombinedResult(scroll, mtch, idx)) .Select(result => { var scroll = result.Scroll; var indicies = result.Incidies; var matched = result.MatchedLines; IEnumerable<LineIndex> indices; if (result.MatchedLines.ChangedReason == LineMatchChangedReason.None) { indices = scroll.Mode == ScrollingMode.Tail ? indicies.GetTail(scroll) : indicies.GetFromIndex(scroll); } else { indices = scroll.Mode == ScrollingMode.Tail ? indicies.GetTail(scroll, matched) : indicies.GetFromIndex(scroll, matched); } var currentPage = indices.ToArray(); var previous = lines.Items.Select(l => l.LineIndex).ToArray(); var removed = previous.Except(currentPage).ToArray(); var removedLines = lines.Items.Where(l=> removed.Contains(l.LineIndex)).ToArray(); var added = currentPage.Except(previous).ToArray(); //finally we can load the line from the file var newLines = file.ReadLines(added, (lineIndex, text) => { var isEndOfTail = indicies.ChangedReason != LinesChangedReason.Loaded && lineIndex.Line > indicies.TailStartsAt; return new Line(lineIndex, text, isEndOfTail ? DateTime.Now : (DateTime?) null); }, indicies.Encoding).ToArray(); return new { NewLines = newLines, OldLines = removedLines }; }) .RetryWithBackOff((Exception error, int attempts) => { //todo: plug in file missing or error into the screen return TimeSpan.FromSeconds(1); }) .Where(fn=> fn.NewLines.Length + fn.OldLines.Length > 0) .Subscribe(changes => { //update observable list lines.Edit(innerList => { if (changes.OldLines.Any()) innerList.RemoveMany(changes.OldLines); if (changes.NewLines.Any()) innerList.AddRange(changes.NewLines); }); }); _cleanUp = new CompositeDisposable(Lines, lines, aggregator); }