PowerShellUtils/Commands/PrintTree/RemoveBranchesExceptFilteredImpl.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using PowerShellStandardModule1.Lib.Extensions;
 
 
namespace PowerShellStandardModule1.Commands.PrintTree;
 
public class RemoveBranchesExceptFilteredImpl(
    Func<FileSystemInfo, bool> filter,
    CancellationToken cancellationToken)
{
    public void Invoke(IList<FileSystemInfoTreeNode> result)
    {
        var branches = GetBranchesSatisfyingFilter(result);
        foreach (var node in result)
        {
            node.Children = node
               .Children
               .Where(branches.Contains)
               .TapEach(_ => cancellationToken.ThrowIfCancellationRequested())
               .ToList();
        }
    }
 
    private HashSet<FileSystemInfoTreeNode> GetBranchesSatisfyingFilter(FileSystemInfoTreeNodeEnumerable nodes)
    {
        var visited = new HashSet<FileSystemInfoTreeNode>();
 
        nodes
           .Where(x => filter(x.Value))
           .TapEach(_ => cancellationToken.ThrowIfCancellationRequested())
           .ForEach(MarkAncestorsAndNode);
 
        return visited;
 
        void MarkAncestorsAndNode(FileSystemInfoTreeNode? node)
        {
            while (node is not null && !visited.Contains(node))
            {
                cancellationToken.ThrowIfCancellationRequested();
                visited.Add(node);
                node = node.Parent;
            }
        }
    }
}
 
public class PreserveTerminalNodeChildrenImpl(
    Func<FileSystemInfo, bool> filter,
    CancellationToken cancellationToken,
    int parallelThreshold = int.MaxValue)
{
    private bool ShouldInvokeParallel(int count) => count >= parallelThreshold;
 
    public void Invoke(IList<FileSystemInfoTreeNode> result)
    {
        var (dependencyNodes, terminalNodes) = GetDependencyAndTerminalNodes(result);
 
 
        if (ShouldInvokeParallel(result.Count))
        {
            dependencyNodes
               .AsParallel()
               .ForAll(KeepDependencyAndTerminalNodes);
        }
        else
        {
            dependencyNodes.ForEach(KeepDependencyAndTerminalNodes);
        }
 
 
        return;
 
        void KeepDependencyAndTerminalNodes(FileSystemInfoTreeNode node)
        {
            node.Children = node
               .Children.Where(IsInEitherSet)
               .TapEach(_ => cancellationToken.ThrowIfCancellationRequested())
               .ToList();
        }
 
        bool IsInEitherSet(FileSystemInfoTreeNode node) =>
            dependencyNodes.Contains(node) || terminalNodes.Contains(node);
    }
 
    private (HashSet<FileSystemInfoTreeNode> dependencyNodes, HashSet<FileSystemInfoTreeNode> terminalNodes)
        GetDependencyAndTerminalNodes(IList<FileSystemInfoTreeNode> nodes)
    {
        var dependencyNodes = new HashSet<FileSystemInfoTreeNode>();
        var terminalNodes = new HashSet<FileSystemInfoTreeNode>();
 
        if (ShouldInvokeParallel(nodes.Count))
        {
            // synchronize before foreach block
            foreach (var node in nodes
                        .AsParallel()
                        .Where(x => filter(x.Value)))
            {
                DoMarkingOperations(node);
            }
        }
        else
        {
            nodes
               .Where(x => filter(x.Value))
               .ForEach(DoMarkingOperations);
        }
 
        return (dependencyNodes, terminalNodes);
 
        void MarkAncestors(FileSystemInfoTreeNode node)
        {
            var parent = node.Parent;
            while (parent is not null && !dependencyNodes.Contains(parent))
            {
                dependencyNodes.Add(parent);
                parent = parent.Parent;
            }
        }
 
        void DoMarkingOperations(FileSystemInfoTreeNode node)
        {
            cancellationToken.ThrowIfCancellationRequested();
            MarkAncestors(node);
            terminalNodes.Add(node);
        }
    }
}