aspnetcore/src/Microsoft.DotNet.Watcher.Tools/DotNetWatcher.cs

102 lines
3.9 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.Watcher.Internal;
using Microsoft.Extensions.Tools.Internal;
namespace Microsoft.DotNet.Watcher
{
public class DotNetWatcher
{
private readonly IReporter _reporter;
private readonly ProcessRunner _processRunner;
public DotNetWatcher(IReporter reporter)
{
Ensure.NotNull(reporter, nameof(reporter));
_reporter = reporter;
_processRunner = new ProcessRunner(reporter);
}
public async Task WatchAsync(ProcessSpec processSpec, IFileSetFactory fileSetFactory,
CancellationToken cancellationToken)
{
Ensure.NotNull(processSpec, nameof(processSpec));
var cancelledTaskSource = new TaskCompletionSource<object>();
cancellationToken.Register(state => ((TaskCompletionSource<object>) state).TrySetResult(null),
cancelledTaskSource);
while (true)
{
var fileSet = await fileSetFactory.CreateAsync(cancellationToken);
if (fileSet == null)
{
_reporter.Error("Failed to find a list of files to watch");
return;
}
if (cancellationToken.IsCancellationRequested)
{
return;
}
using (var currentRunCancellationSource = new CancellationTokenSource())
using (var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
currentRunCancellationSource.Token))
using (var fileSetWatcher = new FileSetWatcher(fileSet))
{
var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
var args = ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments);
_reporter.Verbose($"Running {processSpec.ShortDisplayName()} with the following arguments: {args}");
_reporter.Output("Started");
var finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);
// Regardless of the which task finished first, make sure everything is cancelled
// and wait for dotnet to exit. We don't want orphan processes
currentRunCancellationSource.Cancel();
await Task.WhenAll(processTask, fileSetTask);
if (processTask.Result == 0)
{
_reporter.Output("Exited");
}
else
{
_reporter.Error($"Exited with error code {processTask.Result}");
}
if (finishedTask == cancelledTaskSource.Task || cancellationToken.IsCancellationRequested)
{
return;
}
if (finishedTask == processTask)
{
_reporter.Warn("Waiting for a file to change before restarting dotnet...");
// Now wait for a file to change before restarting process
await fileSetWatcher.GetChangedFileAsync(cancellationToken);
}
if (!string.IsNullOrEmpty(fileSetTask.Result))
{
_reporter.Output($"File changed: {fileSetTask.Result}");
}
}
}
}
}
}