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