File: TransientIO.cs
Web Access
Project: ..\..\..\src\Build.OM.UnitTests\Microsoft.Build.Engine.OM.UnitTests.csproj (Microsoft.Build.Engine.OM.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
namespace Microsoft.Build.UnitTests
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using Microsoft.Build.Shared;
 
    public class TransientIO : IDisposable
    {
        private DirectoryInfo root;
        private TransientIO Parent { get; }
        private string SubFolder { get; }
        private Dictionary<string, TransientIO> Children = new Dictionary<string, TransientIO>(StringComparer.OrdinalIgnoreCase);
 
        private DirectoryInfo EnsureTempRoot()
        {
            if (this.root == null)
            {
                this.root = new DirectoryInfo(
                    this.Parent != null ?
                          this.Parent.GetAbsolutePath(this.SubFolder)
                        : FileUtilities.GetTemporaryDirectory(true));
            }
 
            return this.root;
        }
 
        private static bool IsDirSlash(char c) => c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
 
        public TransientIO()
        {
        }
 
        private TransientIO(TransientIO parent, string subFolder)
        {
            this.SubFolder = subFolder;
            this.Parent = parent;
        }
 
        public bool IsControled(string path)
        {
            if (this.root == null || path == null)
            {
                return false;
            }
 
            var tempRoot = this.RootFolder;
            path = Path.GetFullPath(path);
            return path != null && tempRoot != null
                && path.Length > tempRoot.Length
                && IsDirSlash(path[tempRoot.Length])
                && path.StartsWith(tempRoot, StringComparison.OrdinalIgnoreCase);
        }
 
        public string RootFolder => EnsureTempRoot().FullName;
 
        public void EnsureFileLocation(string path)
        {
            var absolute = this.GetAbsolutePath(path);
            var parent = Path.GetDirectoryName(absolute);
            if (!Directory.Exists(parent))
            {
                Directory.CreateDirectory(parent);
            }
        }
 
        public string GetRelativePath(string path)
        {
            var absolute = GetAbsolutePath(path);
 
            return absolute.Substring(this.RootFolder.Length + 1);
        }
        public string GetAbsolutePath(string relative)
        {
            var tempRoot = this.RootFolder;
 
            var absolute = Path.GetFullPath(Path.IsPathRooted(relative) ? relative : Path.Combine(tempRoot, relative));
            if (!IsControled(absolute))
            {
                throw new ArgumentException(nameof(relative));
            }
 
            return absolute;
        }
 
        public TransientIO GetSubFolder(string path)
        {
            var subFolder = this.GetRelativePath(path);
            if (!this.Children.TryGetValue(subFolder, out var result))
            {
                result = new TransientIO(this, subFolder);
                this.Children.Add(subFolder, result);
            }
 
            return result;
        }
 
        public string WriteProjectFile(string path, string content)
        {
            var absolute = this.GetAbsolutePath(path);
            content = ObjectModelHelpers.CleanupFileContents(content);
            this.EnsureFileLocation(absolute);
            File.WriteAllText(absolute, content);
            return absolute;
        }
 
        public void Clear()
        {
            if (this.root != null && Directory.Exists(this.root.FullName))
            {
                // Note: FileUtilities.DeleteDirectoryNoThrow will be very slow if the directory does not exists. (it will retry with timeout in this case for ~0.5 sec).
                // not sure if that was intentional, so have not fixed it there but instead we check exist here.
                FileUtilities.DeleteDirectoryNoThrow(this.root.FullName, true);
            }
 
            Reset();
        }
 
        private void Reset()
        {
            this.root = null;
            foreach (var child in this.Children.Values)
            {
                child.Reset();
            }
        }
 
        public void Dispose()
        {
            this.Clear();
            // this object still can be used ...
        }
    }
}