public void ScrollToSpecificLine() { var file = Path.GetTempFileName(); var info = new FileInfo(file); var textMatch = Observable.Return((string)null); var autoTailer = new ReplaySubject <ScrollRequest>(1); autoTailer.OnNext(new ScrollRequest(10, 15)); File.AppendAllLines(file, Enumerable.Range(1, 100).Select(i => $"{i}").ToArray()); using (var tailer = new FileTailer(info, textMatch, autoTailer)) { tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(Enumerable.Range(15, 10)); autoTailer.OnNext(new ScrollRequest(15, 50)); File.Delete(file); tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(Enumerable.Range(50, 15)); } }
public void AutoTailWithFilter() { var file = Path.GetTempFileName(); var info = new FileInfo(file); var scheduler = new TestScheduler(); var textMatch = Observable.Return((string)"1"); var autoTailer = Observable.Return(new ScrollRequest(10)); File.AppendAllLines(file, Enumerable.Range(1, 100).Select(i => i.ToString()).ToArray()); using (var tailer = new FileTailer(info, textMatch, autoTailer, scheduler)) { //lines which contain "1" var expectedLines = Enumerable.Range(1, 100) .Select(i => i.ToString()) .Where(s => s.Contains("1")) .Reverse() .Take(10) .Reverse() .Select(int.Parse) .ToArray(); tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(expectedLines); File.AppendAllLines(file, Enumerable.Range(101, 10).Select(i => i.ToString())); //lines which contain "1" expectedLines = Enumerable.Range(1, 110) .Select(i => i.ToString()) .Where(s => s.Contains("1")) .Reverse() .Take(10) .Reverse() .Select(int.Parse) .ToArray(); scheduler.AdvanceByMilliSeconds(250); File.Delete(file); tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(expectedLines); } }
public void AutoTail() { var file = Path.GetTempFileName(); var info = new FileInfo(file); var scheduler = new TestScheduler(); var textMatch = Observable.Return((string)null); var autoTailer = Observable.Return(new ScrollRequest(10)); File.AppendAllLines(file, Enumerable.Range(1, 100).Select(i => i.ToString()).ToArray()); using (var tailer = new FileTailer(info, textMatch, autoTailer, scheduler)) { tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(Enumerable.Range(91, 10)); File.AppendAllLines(file, Enumerable.Range(101, 10).Select(i => i.ToString())); scheduler.AdvanceByMilliSeconds(250); File.Delete(file); tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(Enumerable.Range(101, 10)); } }
public void AutoTailWithFilter() { var file = Path.GetTempFileName(); var info = new FileInfo(file); var textMatch = Observable.Return("1"); var autoTailer = Observable.Return(new ScrollRequest(10)); File.AppendAllLines(file, Enumerable.Range(1, 100).Select(i => $"{i}").ToArray()); using (var tailer = new FileTailer(info, textMatch, autoTailer)) { // lines that contain "1" var expectedLines = Enumerable.Range(1, 100) .Select(i => $"{i}") .Where(s => s.Contains("1")) .Reverse() .Take(10) .Select(int.Parse) .ToArray(); tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(expectedLines); File.AppendAllLines(file, Enumerable.Range(101, 10).Select(i => $"{i}")); Thread.Sleep(TimeSpan.FromSeconds(1)); File.Delete(file); // lines that contain "1" expectedLines = Enumerable.Range(1, 110) .Select(i => $"{i}") .Where(s => s.Contains("1")) .Reverse() .Take(10) .Select(int.Parse) .ToArray(); tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(expectedLines); } }
public void AutoTail() { var file = Path.GetTempFileName(); var info = new FileInfo(file); var textMatch = Observable.Return((string)null); var autoTailer = Observable.Return(new ScrollRequest(10)); File.AppendAllLines(file, Enumerable.Range(1, 100).Select(i => $"{i}").ToArray()); using (var tailer = new FileTailer(info, textMatch, autoTailer)) { tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(Enumerable.Range(91, 10)); File.AppendAllLines(file, Enumerable.Range(101, 10).Select(i => $"{i}")); Thread.Sleep(TimeSpan.FromSeconds(1)); File.Delete(file); tailer.Lines.Items.Select(l => l.Number).ShouldAllBeEquivalentTo(Enumerable.Range(101, 10)); } }
public FileTailerViewModel(ILogger logger, ISchedulerProvider schedulerProvider, FileInfo fileInfo) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (fileInfo == null) { throw new ArgumentNullException(nameof(fileInfo)); } var filterRequest = this.WhenValueChanged(vm => vm.SearchText).Throttle(TimeSpan.FromMilliseconds(125)); var autoChanged = this.WhenValueChanged(vm => vm.AutoTail); var scroller = _userScrollRequested .CombineLatest(autoChanged, (user, auto) => { var mode = AutoTail ? ScrollingMode.Tail : ScrollingMode.User; return(new ScrollRequest(mode, user.PageSize, user.FirstIndex)); }) .Sample(TimeSpan.FromMilliseconds(150)) .DistinctUntilChanged(); var tailer = new FileTailer(fileInfo, filterRequest, scroller); //create user display for count line count var lineCounter = tailer.TotalLines.CombineLatest(tailer.MatchedLines, (total, matched) => { return(total == matched ? $"File has {total.ToString("#,###")} lines" : $"Showing {matched.ToString("#,###")} of {total.ToString("#,###")} lines"); }) .Subscribe(text => LineCountText = text); //load lines into observable collection var loader = tailer.Lines.Connect() .Transform(line => new LineProxy(line)) .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy.Number)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data) .Subscribe(changes => logger.Info($"Rows changed {changes.Adds} adds, {changes.Removes} removed"), ex => logger.Error(ex, "There is a problem with bind data")); //monitor matching lines and start index, var matchedLinesMonitor = tailer.MatchedLines .Subscribe(matched => MatchedLineCount = matched); //track first visible index var firstIndexMonitor = tailer.Lines.Connect() .QueryWhenChanged(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Min()) .Subscribe(first => FirstIndex = first); _cleanUp = new CompositeDisposable(tailer, lineCounter, loader, firstIndexMonitor, matchedLinesMonitor, Disposable.Create(() => { _userScrollRequested.OnCompleted(); })); }
public FileTailerViewModel(ILogger logger, ISchedulerProvider schedulerProvider, FileInfo fileInfo) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (fileInfo == null) { throw new ArgumentNullException(nameof(fileInfo)); } File = fileInfo.FullName; AutoTail = true; var filterRequest = this.WhenValueChanged(vm => vm.SearchText).Throttle(TimeSpan.FromMilliseconds(125)); var autoTail = this.WhenValueChanged(vm => vm.AutoTail) .CombineLatest(_userScrollRequested, (auto, user) => auto ? new ScrollRequest(user.Rows) : new ScrollRequest(user.Rows, user.FirstIndex + 1)) .DistinctUntilChanged(); var tailer = new FileTailer(fileInfo, filterRequest, autoTail); var lineCounter = tailer .TotalLinesCount .CombineLatest(tailer.MatchedLinesCount, (total, matched) => total == matched ? $"File has {total:#,###} lines" : $"Showing {matched:#,###} of {total:#,###} lines") .Subscribe(text => LineCountText = text); // load lines into observable collection var loader = tailer.Lines.Connect() .Buffer(TimeSpan.FromMilliseconds(125)).FlattenBufferResult() .Transform(line => new LineProxy(line)) .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy.Number)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data) .Do(_ => AutoScroller.ScrollToEnd()) .Subscribe(a => logger.Info(a.Adds.ToString()), ex => logger.Error(ex, "Opps")); // monitor matching lines and start index // update local values so the virtual scroll panel can bind to them var matchedLinesMonitor = tailer.MatchedLinesCount .Subscribe(matched => MatchedLinesCount = matched); var firstIndexMonitor = tailer.Lines.Connect() .QueryWhenChanged(lines => { // use zero based index rather than line number return(lines.Count == 0 ? 0 : lines.Select(l => l.Number).Min() - 1); }).Subscribe(first => FirstRow = first - 1); _cleanup = new CompositeDisposable( tailer, lineCounter, loader, matchedLinesMonitor, firstIndexMonitor, Disposable.Create(() => _userScrollRequested.OnCompleted())); }