File: HotReload\StaticFileHandler.cs
Web Access
Project: ..\..\..\src\BuiltInTools\dotnet-watch\dotnet-watch.csproj (dotnet-watch)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Build.Graph;
using Microsoft.DotNet.HotReload;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.DotNet.Watch
{
    internal sealed class StaticFileHandler(ILogger logger, ProjectNodeMap projectMap, BrowserRefreshServerFactory browserConnector)
    {
        public async ValueTask<bool> HandleFileChangesAsync(IReadOnlyList<ChangedFile> files, CancellationToken cancellationToken)
        {
            var allFilesHandled = true;
            var refreshRequests = new Dictionary<BrowserRefreshServer, List<string>>();
            var projectsWithoutRefreshServer = new HashSet<ProjectGraphNode>();
 
            for (int i = 0; i < files.Count; i++)
            {
                var file = files[i].Item;
 
                if (file.StaticWebAssetPath is null)
                {
                    allFilesHandled = false;
                    continue;
                }
 
                logger.LogDebug("Handling file change event for static content {FilePath}.", file.FilePath);
 
                foreach (var containingProjectPath in file.ContainingProjectPaths)
                {
                    if (!projectMap.Map.TryGetValue(containingProjectPath, out var projectNodes))
                    {
                        // Shouldn't happen.
                        logger.LogWarning("Project '{Path}' not found in the project graph.", containingProjectPath);
                        return allFilesHandled;
                    }
 
                    foreach (var projectNode in projectNodes)
                    {
                        if (browserConnector.TryGetRefreshServer(projectNode, out var refreshServer))
                        {
                            if (!refreshRequests.TryGetValue(refreshServer, out var filesPerServer))
                            {
                                logger.LogDebug("[{ProjectName}] Refreshing browser.", projectNode.GetDisplayName());
                                refreshRequests.Add(refreshServer, filesPerServer = []);
                            }
 
                            filesPerServer.Add(file.StaticWebAssetPath);
                        }
                        else if (projectsWithoutRefreshServer.Add(projectNode))
                        {
                            logger.LogDebug("[{ProjectName}] No refresh server.", projectNode.GetDisplayName());
                        }
                    }
                }
            }
 
            if (refreshRequests.Count == 0)
            {
                return allFilesHandled;
            }
 
            var tasks = refreshRequests.Select(request => request.Key.UpdateStaticAssetsAsync(request.Value, cancellationToken).AsTask());
 
            await Task.WhenAll(tasks).WaitAsync(cancellationToken);
 
            logger.Log(MessageDescriptor.HotReloadOfStaticAssetsSucceeded);
 
            return allFilesHandled;
        }
 
        private readonly struct UpdateStaticFileMessage
        {
            public string Type => "UpdateStaticFile";
            public string Path { get; init; }
        }
    }
}