using System.Reactive.Linq;
using System.Reactive.Concurrency;
namespace StarsAssistant.Helpers;
public class FsWatcher
{
///
/// Throttle a FileSystemObserver and eliminate all duplicates.
///
///
/// Curtesy of https://endjin.com/blog/2024/05/observe-file-system-changes-with-rx-dotnet
///
/// An observer created with ObserveFileSystem
/// Throttling window, defaults to 1 second.
/// Scheduler to user for throttling, default is set by Quiescent
/// A throttled observer with duplicate elimination.
public static IObservable> ThrottleAndDistinctObserver (
IObservable watcher,
int inactivitySeconds = 1,
IScheduler? scheduler = null)
{
return watcher
.Quiescent(TimeSpan.FromSeconds(inactivitySeconds), scheduler)
.Select(changes => changes.DistinctBy(x => (x.ChangeType, x.FullPath)));
}
///
/// Helper to convert a FileSystemWatcher into an obervable stream. See FileSystemWatcher
/// documentation for further details on the parameters.
///
///
/// Curtesy of https://endjin.com/blog/2024/05/observe-file-system-changes-with-rx-dotnet
///
/// The base directory to watch over.
/// An optional list of filter expressions to look for, default is no filter.
/// Scan subdirectories, default is exclude subdirs
/// Set this to override the default event selection to filter.
/// An observable for further use, usually throttled by Quiescent.
public static IObservable ObserveFileSystem(
string directoryPath,
IEnumerable? filters = null,
bool includeSubdirectories = false,
NotifyFilters? notifyFilter = null)
{
return
// Observable.Defer enables us to avoid doing any work
// until we have a subscriber.
Observable.Defer(() =>
{
FileSystemWatcher fsw = new(directoryPath);
if (filters != null)
foreach (string filter in filters)
fsw.Filters.Add(filter);
if (notifyFilter != null)
fsw.NotifyFilter = (NotifyFilters) notifyFilter;
fsw.EnableRaisingEvents = true;
fsw.IncludeSubdirectories = includeSubdirectories;
return Observable.Return(fsw);
})
// Once the preceding part emits the FileSystemWatcher
// (which will happen when someone first subscribes), we
// want to wrap all the events as IObservables, for which
// we'll use a projection. To avoid ending up with an
// IObservable>, we use
// SelectMany, which effectively flattens it by one level.
.SelectMany(fsw =>
Observable.Merge(new[]
{
Observable.FromEventPattern(
h => fsw.Created += h, h => fsw.Created -= h),
Observable.FromEventPattern(
h => fsw.Changed += h, h => fsw.Changed -= h),
Observable.FromEventPattern(
h => fsw.Renamed += h, h => fsw.Renamed -= h),
Observable.FromEventPattern(
h => fsw.Deleted += h, h => fsw.Deleted -= h)
})
// FromEventPattern supplies both the sender and the event
// args. Extract just the latter.
.Select(ep => ep.EventArgs)
// The Finally here ensures the watcher gets shut down once
// we have no subscribers.
.Finally(fsw.Dispose))
// This combination of Publish and RefCount means that multiple
// subscribers will get to share a single FileSystemWatcher,
// but that it gets shut down if all subscribers unsubscribe.
.Publish()
.RefCount();
}
}