File: Model\VolumePathLauncher.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
 
namespace Aspire.Dashboard.Model;
 
/// <summary>
/// Reveals a bind-mount volume's host path in the local OS file explorer. This only works when the dashboard
/// process is running on the same machine as the browser and the mounted path (the common local-development
/// case), so callers must treat a false return as an expected, user-facing outcome rather than an error to log.
/// </summary>
public interface IVolumePathLauncher
{
    /// <summary>Attempts to open <paramref name="path"/> in the OS file explorer.</summary>
    /// <param name="path">The host path to reveal.</param>
    /// <returns><see langword="true"/> if an explorer process was launched; otherwise <see langword="false"/>.</returns>
    bool TryReveal(string? path);
}
 
internal sealed class VolumePathLauncher : IVolumePathLauncher
{
    public bool TryReveal([NotNullWhen(true)] string? path)
    {
        // A named Docker volume's "source" is an opaque volume name, not a filesystem path, and a bind mount
        // created elsewhere (e.g. inside WSL, or on a remote Docker host) won't resolve on this machine. Checking
        // existence first turns both cases into an expected "can't open" outcome instead of a confusing OS error
        // or an explorer window that silently opens to nothing.
        if (string.IsNullOrWhiteSpace(path) || (!Directory.Exists(path) && !File.Exists(path)))
        {
            return false;
        }
 
        try
        {
            var fullPath = Path.GetFullPath(path);
            var startInfo = CreateStartInfo(fullPath, Directory.Exists(fullPath), GetCurrentPlatform());
            if (startInfo is null)
            {
                return false;
            }
 
            using var process = Process.Start(startInfo);
            return process is not null;
        }
        catch (Exception)
        {
            // The target platform may not have a file explorer available (e.g. a minimal container image), or the
            // process couldn't be launched for some other OS-specific reason. Either way, this is a best-effort
            // convenience feature, so report it back to the caller as a normal failure instead of throwing.
            return false;
        }
    }
 
    /// <summary>
    /// Creates the command that shows the path in the platform's file explorer without opening it. Opening a mounted
    /// file with its default handler could run it, for example a <c>.command</c> file on macOS or a <c>.desktop</c>
    /// file on Linux, so a file is selected in its folder instead:
    /// <list type="bullet">
    /// <item>Windows: <c>explorer.exe /select,"C:\src\init.sql"</c>, or <c>explorer.exe "C:\src\data"</c> for a folder.</item>
    /// <item>macOS: <c>open -R /src/init.sql</c> selects the file or folder in Finder.</item>
    /// <item>Linux: <c>xdg-open</c> can't select a file, so it opens the file's folder, or the folder itself.</item>
    /// </list>
    /// </summary>
    internal static ProcessStartInfo? CreateStartInfo(string fullPath, bool isDirectory, VolumePathPlatform platform)
    {
        ProcessStartInfo startInfo;
        switch (platform)
        {
            case VolumePathPlatform.Windows:
                // explorer.exe parses its own command line, and only accepts the path quoted after "/select,", so
                // ArgumentList's quoting can't be used. Windows paths can't contain '"', so reject one rather than
                // let it end the quoted path early.
                if (fullPath.Contains('"'))
                {
                    return null;
                }
 
                startInfo = new ProcessStartInfo("explorer.exe", isDirectory ? $"\"{fullPath}\"" : $"/select,\"{fullPath}\"");
                break;
            case VolumePathPlatform.MacOS:
                startInfo = new ProcessStartInfo("open") { ArgumentList = { "-R", fullPath } };
                break;
            default:
                var folder = isDirectory ? fullPath : Path.GetDirectoryName(fullPath);
                if (string.IsNullOrEmpty(folder))
                {
                    return null;
                }
 
                startInfo = new ProcessStartInfo("xdg-open") { ArgumentList = { folder } };
                break;
        }
 
        startInfo.UseShellExecute = false;
        return startInfo;
    }
 
    private static VolumePathPlatform GetCurrentPlatform() =>
        OperatingSystem.IsWindows() ? VolumePathPlatform.Windows
        : OperatingSystem.IsMacOS() ? VolumePathPlatform.MacOS
        : VolumePathPlatform.Linux;
}
 
internal enum VolumePathPlatform
{
    Windows,
    MacOS,
    Linux
}