File: Util\FSWGen.cs
Web Access
Project: src\src\runtime\src\tools\hotreload-delta-gen\Microsoft.DotNet.HotReload.Utils.Generator\Microsoft.DotNet.HotReload.Utils.Generator.csproj (Microsoft.DotNet.HotReload.Utils.Generator)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace Microsoft.DotNet.HotReload.Utils.Generator.Util;
public class FSWGen : IDisposable {

    Channel<System.IO.FileSystemEventArgs>? _channel;
    readonly System.IO.FileSystemWatcher _fsw;

    public FSWGen (string directoryPath, string filter)
    {
        _channel = Channel.CreateUnbounded<System.IO.FileSystemEventArgs> (new UnboundedChannelOptions { SingleReader = true, AllowSynchronousContinuations = true});
        _fsw = new System.IO.FileSystemWatcher(directoryPath, filter) {
            NotifyFilter = System.IO.NotifyFilters.LastWrite /* FIXME: generalize */
        };
        _fsw.Changed += OnChanged;
        // _fsw.Created += OnChanged;
        // _fsw.Deleted += OnChanged; // FIXME: deletion is interesting, actually
    }

    private void OnChanged (object sender, System.IO.FileSystemEventArgs eventArgs)
    {
        _channel?.Writer.WriteAsync (eventArgs).AsTask().Wait();
    }

    ~FSWGen () => Dispose (false);

    public void Dispose () {
        Dispose (true);
        GC.SuppressFinalize (this);
    }

    public virtual void Dispose (bool disposing)
    {
        if (disposing) {
            _fsw.EnableRaisingEvents = false;
            _fsw.Dispose();

            _channel?.Writer.Complete();
            _channel = null;
        }
    }


    enum WhenAnyResult {
        Completion,
        Read
    }
    public async IAsyncEnumerable<System.IO.FileSystemEventArgs> Watch ([EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        try {
            _fsw.EnableRaisingEvents = true;
            var completion = _channel!.Reader.Completion.ContinueWith((t) => WhenAnyResult.Completion);
            while (true) {
                var readOne = _channel!.Reader.ReadAsync(cancellationToken).AsTask();
                Task<WhenAnyResult> t = await Task.WhenAny(completion, readOne.ContinueWith((t) => WhenAnyResult.Read)).ConfigureAwait(false);
                cancellationToken.ThrowIfCancellationRequested();
                switch (t.Result) {
                    case WhenAnyResult.Completion:
                        yield break;
                    case WhenAnyResult.Read:
                        yield return readOne.Result;
                        break;
                }
            }
        } finally {
            var fsw = _fsw;
            if (fsw != null)
                fsw.EnableRaisingEvents = false;
        }
    }
}