2

I am using Rx to monitor changes to a file.

         |  window1   | |  window2   | | window3 |
  Time   01 02 03 04 05 06 07 08 09 10 11 12 13 14 15

 Input       M  M     M     M     M              R
Output                M              M           R

 Input       M  M     M     M     M        M     R
Output                M              M     M     R         

(M - Modify, R - Rename)

Top Example: No remaining modification in the last window (11 - 14).

Bottom Example: Remaining modification in the last window (at t = 12).

I the above examples I sample every 5 sec to throttle Modify operations to a file. i.e. in every 5sec window I take the latest Modify operation only.

I would like sampling to stop as soon as a Rename operation occurs and return the latest modification in the current window (if any) and the rename event.

Some things to note:

  1. The last window need not be 5sec long.
  2. The last window can return more than a single event.

Here's what I have so far.

var events = source
            .GroupBy(e => e.FullPath)
            .SelectMany(g => g.Sample(TimeSpan.FromSeconds(5)));
Naximus
  • 579
  • 6
  • 18
  • As per my answer, output for scenario 2 requires final M to be emitted at T=14 along with the R (impossible to know it's the last modify until then). – James World Oct 27 '14 at 15:56

1 Answers1

1

Here's a solution - a bit rushed so I am sure it can be tidied, but it seems to work. This uses Nuget packages rx-testing and ix-main [sic - required for IEnumerable<T>.TakeLast()]. I've mapped your examples as presented, but represented one unit of time as 100 ticks.

Note the second scenario in your question has a mistake - it's impossible to emit the last modified event until the rename event is received. My output has both events emitted at time 1400 ticks because of this.

The main query is as follows (assuming actions contains the FileAction events):

Publish the actions so we can subscribe multiple times:

actions.Publish(source => {

Window the actions in 500 tick intervals, but cut the window off on a Rename:

  return source.Window(
    () => Observable.Timer(TimeSpan.FromTicks(500), scheduler)
                    .Merge(source.Where(x => x == FileAction.Rename)
                                 .Select(_ => 0L)))

Stop the stream of windows on a Rename too:

    .TakeUntil(source.Where(x => x == FileAction.Rename))

Flatten the windows into a stream of lists consisting of up to their last two events:

    .SelectMany(x => x.TakeLastBuffer(2))

Look for a rename as the last item, if it is a rename then grab the last 2 items, otherwise just the last. Note this is carefully crafted to deal with empty lists or lists of 1 item. Also, use SelectMany again to flatten the lists out so we are left with a stream of actions.

    .SelectMany(l => {
      return l.LastOrDefault() == FileAction.Rename
        ? l.TakeLast(2)
        : l.TakeLast(1);
    });
})

You can subscribe to this to get the events requested. Here is a full sample:

void Main()
{
    var tests = new Tests();
    tests.Scenario();
}

// Note inheritance of ReactiveTest to get OnXXX helpers
public class Tests : ReactiveTest
{
    public enum FileAction
    {
        Modify,
        Rename
    }

    public void Scenario()
    {
        var scheduler = new TestScheduler();
        var actions = scheduler.CreateHotObservable<FileAction>(
            OnNext(200, FileAction.Modify),
            OnNext(300, FileAction.Modify),
            OnNext(500, FileAction.Modify),
            OnNext(700, FileAction.Modify),
            OnNext(900, FileAction.Modify),
            // Uncomment next line for
            // scenario with mod + rename in last window
            // OnNext(1200, FileAction.Modify),
            OnNext(1400, FileAction.Rename));

        actions.Publish(source => {
            return source.Window(
                () => Observable.Timer(TimeSpan.FromTicks(500), scheduler)
                                .Merge(source.Where(x => x == FileAction.Rename)
                                            .Select(_ => 0L)))
                .TakeUntil(source.Where(x => x == FileAction.Rename))
                .SelectMany(x => x.TakeLastBuffer(2))
                .SelectMany(l => {
                    return l.LastOrDefault() == FileAction.Rename
                        ? l.TakeLast(2)
                        : l.TakeLast(1);
                });
        })
        .Timestamp(scheduler) /* to show timings */
        .Subscribe(x => Console.WriteLine(
            "Event: {0} Timestamp: {1}",
            x.Value, x.Timestamp.Ticks));

        scheduler.Start();

    }
}

This outputs:

Event: Modify Timestamp: 500
Event: Modify Timestamp: 1000
Event: Rename Timestamp: 1400

If you uncomment the marked line for the second scenario you get:

Event: Modify Timestamp: 500
Event: Modify Timestamp: 1000
Event: Modify Timestamp: 1400
Event: Rename Timestamp: 1400
James World
  • 29,019
  • 9
  • 86
  • 120