<commit_before>﻿//-----------------------------------------------------------------------
// <copyright file="FileSystemExtensions.cs" company="Fubar Development Junker">
//     Copyright (c) Fubar Development Junker. All rights reserved.
// </copyright>
// <author><NAME></author>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using JetBrains.Annotations;

namespace FubarDev.FtpServer.FileSystem
{
    /// <summary>
    /// Extensions for the file system stuff
    /// </summary>
    public static class FileSystemExtensions
    {
        /// <summary>
        /// Is this directory a root directory?
        /// </summary>
        /// <param name="directoryEntry">The directory entry to test</param>
        /// <returns><code>true</code> if the <paramref name="directoryEntry"/> is a root directory</returns>
        public static bool IsRoot([NotNull] this IUnixDirectoryEntry directoryEntry)
        {
            return string.IsNullOrEmpty(directoryEntry.Name);
        }

        /// <summary>
        /// Clone the stack of directory entries
        /// </summary>
        /// <param name="path">The stack of directory entries to clone</param>
        /// <returns>the cloned <paramref name="path"/></returns>
        [NotNull, ItemNotNull]
        public static Stack<IUnixDirectoryEntry> Clone([NotNull, ItemNotNull] this Stack<IUnixDirectoryEntry> path)
        {
            return new Stack<IUnixDirectoryEntry>(path.Reverse());
        }

        /// <summary>
        /// Is the <paramref name="pathToTestAsChild"/> a child or the same path as <paramref name="pathToTestAsParent"/>?
        /// </summary>
        /// <param name="pathToTestAsChild">The path to test as child</param>
        /// <param name="pathToTestAsParent">The path to test as parent</param>
        /// <param name="fileSystem">The file system to use to compare the file names</param>
        /// <returns><code>true</code> if the <paramref name="pathToTestAsChild"/> is a child or the same path as <paramref name="pathToTestAsParent"/></returns>
        public static bool IsChildOfOrSameAs([NotNull, ItemNotNull] this Stack<IUnixDirectoryEntry> pathToTestAsChild, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> pathToTestAsParent, [NotNull] IUnixFileSystem fileSystem)
        {
            var fullPathOfParent = pathToTestAsParent.GetFullPath();
            var fullPathOfChild = pathToTestAsChild.GetFullPath();
            var testPathOfChild = fullPathOfChild.Substring(0, Math.Min(fullPathOfChild.Length, fullPathOfChild.Length));
            return fileSystem.FileSystemEntryComparer.Equals(testPathOfChild, fullPathOfParent) && fullPathOfParent.Length <= fullPathOfChild.Length;
        }

        /// <summary>
        /// Get the directory for the given <paramref name="path"/>
        /// </summary>
        /// <param name="fileSystem">The file system to get the directory for</param>
        /// <param name="currentPath">The current path</param>
        /// <param name="path">The (absolute or relative) path to get the directory for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/> or <code>null</code></returns>
        [NotNull, ItemCanBeNull]
        public static Task<IUnixDirectoryEntry> GetDirectoryAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [CanBeNull] string path, CancellationToken cancellationToken)
        {
            var pathElements = GetPathElements(path);
            return GetDirectoryAsync(fileSystem, currentPath, pathElements, cancellationToken);
        }

        /// <summary>
        /// Get the directory for the given <paramref name="pathElements"/>
        /// </summary>
        /// <param name="fileSystem">The file system to get the directory for</param>
        /// <param name="currentPath">The current path</param>
        /// <param name="pathElements">The (absolute or relative) path to get the directory for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/> or <code>null</code></returns>
        [NotNull, ItemCanBeNull]
        public static async Task<IUnixDirectoryEntry> GetDirectoryAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [NotNull, ItemNotNull] IReadOnlyList<string> pathElements, CancellationToken cancellationToken)
        {
            IUnixDirectoryEntry currentDir = currentPath.Count == 0 ? fileSystem.Root : currentPath.Peek();
            foreach (var pathElement in pathElements)
            {
                if (pathElement == string.Empty)
                {
                    currentDir = fileSystem.Root;
                    currentPath.Clear();
                    continue;
                }
                if (pathElement == ".")
                    continue;
                if (pathElement == "..")
                {
                    if (currentPath.Count != 0)
                        currentPath.Pop();
                    continue;
                }
                var foundEntry = (IUnixDirectoryEntry)await fileSystem.GetEntryByNameAsync(currentDir, pathElement, cancellationToken);
                if (foundEntry == null)
                    return null;
                currentPath.Push(foundEntry);
                currentDir = foundEntry;
            }
            return currentDir;
        }

        /// <summary>
        /// Searches for a <see cref="IUnixDirectoryEntry"/> by using the <paramref name="currentPath"/> and <paramref name="path"/>
        /// </summary>
        /// <param name="fileSystem">The underlying <see cref="IUnixFileSystem"/></param>
        /// <param name="currentPath">The current path</param>
        /// <param name="path">The relative path to search for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/></returns>
        [NotNull, ItemCanBeNull]
        public static Task<SearchResult<IUnixDirectoryEntry>> SearchDirectoryAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [CanBeNull] string path, CancellationToken cancellationToken)
        {
            var pathElements = GetPathElements(path);
            return SearchDirectoryAsync(fileSystem, currentPath, pathElements, cancellationToken);
        }

        /// <summary>
        /// Searches for a <see cref="IUnixDirectoryEntry"/> by using the <paramref name="currentPath"/> and <paramref name="pathElements"/>
        /// </summary>
        /// <param name="fileSystem">The underlying <see cref="IUnixFileSystem"/></param>
        /// <param name="currentPath">The current path</param>
        /// <param name="pathElements">The relative path elements to search for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/></returns>
        [NotNull, ItemCanBeNull]
        public static async Task<SearchResult<IUnixDirectoryEntry>> SearchDirectoryAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [NotNull, ItemNotNull] IReadOnlyList<string> pathElements, CancellationToken cancellationToken)
        {
            var sourceDir = await GetDirectoryAsync(fileSystem, currentPath, new ListSegment<string>(pathElements, 0, pathElements.Count - 1), cancellationToken);
            if (sourceDir == null)
                return null;
            var fileName = pathElements[pathElements.Count - 1];
            var foundEntry = (IUnixDirectoryEntry)await fileSystem.GetEntryByNameAsync(sourceDir, fileName, cancellationToken);
            return new SearchResult<IUnixDirectoryEntry>(sourceDir, foundEntry, fileName);
        }

        /// <summary>
        /// Searches for a <see cref="IUnixFileEntry"/> by using the <paramref name="currentPath"/> and <paramref name="path"/>
        /// </summary>
        /// <param name="fileSystem">The underlying <see cref="IUnixFileSystem"/></param>
        /// <param name="currentPath">The current path</param>
        /// <param name="path">The relative path to search for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/></returns>
        [NotNull, ItemCanBeNull]
        public static Task<SearchResult<IUnixFileEntry>> SearchFileAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [CanBeNull] string path, CancellationToken cancellationToken)
        {
            var pathElements = GetPathElements(path);
            return SearchFileAsync(fileSystem, currentPath, pathElements, cancellationToken);
        }

        /// <summary>
        /// Searches for a <see cref="IUnixFileEntry"/> by using the <paramref name="currentPath"/> and <paramref name="pathElements"/>
        /// </summary>
        /// <param name="fileSystem">The underlying <see cref="IUnixFileSystem"/></param>
        /// <param name="currentPath">The current path</param>
        /// <param name="pathElements">The relative path elements to search for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/></returns>
        [NotNull, ItemCanBeNull]
        public static async Task<SearchResult<IUnixFileEntry>> SearchFileAsync(this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [NotNull, ItemNotNull] IReadOnlyList<string> pathElements, CancellationToken cancellationToken)
        {
            var sourceDir = await GetDirectoryAsync(fileSystem, currentPath, new ListSegment<string>(pathElements, 0, pathElements.Count - 1), cancellationToken);
            if (sourceDir == null)
                return null;
            var fileName = pathElements[pathElements.Count - 1];
            var foundEntry = (IUnixFileEntry)await fileSystem.GetEntryByNameAsync(sourceDir, fileName, cancellationToken);
            return new SearchResult<IUnixFileEntry>(sourceDir, foundEntry, fileName);
        }

        /// <summary>
        /// Returns the <paramref name="path"/> as string
        /// </summary>
        /// <param name="path">The path to convert to string</param>
        /// <returns>The <paramref name="path"/> as string</returns>
        [NotNull]<commit_msg>Return null when we find a file, but a directory is expected - and vice versa<commit_after>﻿//-----------------------------------------------------------------------
// <copyright file="FileSystemExtensions.cs" company="Fubar Development Junker">
//     Copyright (c) Fubar Development Junker. All rights reserved.
// </copyright>
// <author><NAME></author>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using JetBrains.Annotations;

namespace FubarDev.FtpServer.FileSystem
{
    /// <summary>
    /// Extensions for the file system stuff
    /// </summary>
    public static class FileSystemExtensions
    {
        /// <summary>
        /// Is this directory a root directory?
        /// </summary>
        /// <param name="directoryEntry">The directory entry to test</param>
        /// <returns><code>true</code> if the <paramref name="directoryEntry"/> is a root directory</returns>
        public static bool IsRoot([NotNull] this IUnixDirectoryEntry directoryEntry)
        {
            return string.IsNullOrEmpty(directoryEntry.Name);
        }

        /// <summary>
        /// Clone the stack of directory entries
        /// </summary>
        /// <param name="path">The stack of directory entries to clone</param>
        /// <returns>the cloned <paramref name="path"/></returns>
        [NotNull, ItemNotNull]
        public static Stack<IUnixDirectoryEntry> Clone([NotNull, ItemNotNull] this Stack<IUnixDirectoryEntry> path)
        {
            return new Stack<IUnixDirectoryEntry>(path.Reverse());
        }

        /// <summary>
        /// Is the <paramref name="pathToTestAsChild"/> a child or the same path as <paramref name="pathToTestAsParent"/>?
        /// </summary>
        /// <param name="pathToTestAsChild">The path to test as child</param>
        /// <param name="pathToTestAsParent">The path to test as parent</param>
        /// <param name="fileSystem">The file system to use to compare the file names</param>
        /// <returns><code>true</code> if the <paramref name="pathToTestAsChild"/> is a child or the same path as <paramref name="pathToTestAsParent"/></returns>
        public static bool IsChildOfOrSameAs([NotNull, ItemNotNull] this Stack<IUnixDirectoryEntry> pathToTestAsChild, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> pathToTestAsParent, [NotNull] IUnixFileSystem fileSystem)
        {
            var fullPathOfParent = pathToTestAsParent.GetFullPath();
            var fullPathOfChild = pathToTestAsChild.GetFullPath();
            var testPathOfChild = fullPathOfChild.Substring(0, Math.Min(fullPathOfChild.Length, fullPathOfChild.Length));
            return fileSystem.FileSystemEntryComparer.Equals(testPathOfChild, fullPathOfParent) && fullPathOfParent.Length <= fullPathOfChild.Length;
        }

        /// <summary>
        /// Get the directory for the given <paramref name="path"/>
        /// </summary>
        /// <param name="fileSystem">The file system to get the directory for</param>
        /// <param name="currentPath">The current path</param>
        /// <param name="path">The (absolute or relative) path to get the directory for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/> or <code>null</code></returns>
        [NotNull, ItemCanBeNull]
        public static Task<IUnixDirectoryEntry> GetDirectoryAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [CanBeNull] string path, CancellationToken cancellationToken)
        {
            var pathElements = GetPathElements(path);
            return GetDirectoryAsync(fileSystem, currentPath, pathElements, cancellationToken);
        }

        /// <summary>
        /// Get the directory for the given <paramref name="pathElements"/>
        /// </summary>
        /// <param name="fileSystem">The file system to get the directory for</param>
        /// <param name="currentPath">The current path</param>
        /// <param name="pathElements">The (absolute or relative) path to get the directory for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/> or <code>null</code></returns>
        [NotNull, ItemCanBeNull]
        public static async Task<IUnixDirectoryEntry> GetDirectoryAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [NotNull, ItemNotNull] IReadOnlyList<string> pathElements, CancellationToken cancellationToken)
        {
            IUnixDirectoryEntry currentDir = currentPath.Count == 0 ? fileSystem.Root : currentPath.Peek();
            foreach (var pathElement in pathElements)
            {
                if (pathElement == string.Empty)
                {
                    currentDir = fileSystem.Root;
                    currentPath.Clear();
                    continue;
                }
                if (pathElement == ".")
                    continue;
                if (pathElement == "..")
                {
                    if (currentPath.Count != 0)
                        currentPath.Pop();
                    continue;
                }
                var foundEntry = (IUnixDirectoryEntry)await fileSystem.GetEntryByNameAsync(currentDir, pathElement, cancellationToken);
                if (foundEntry == null)
                    return null;
                currentPath.Push(foundEntry);
                currentDir = foundEntry;
            }
            return currentDir;
        }

        /// <summary>
        /// Searches for a <see cref="IUnixDirectoryEntry"/> by using the <paramref name="currentPath"/> and <paramref name="path"/>
        /// </summary>
        /// <param name="fileSystem">The underlying <see cref="IUnixFileSystem"/></param>
        /// <param name="currentPath">The current path</param>
        /// <param name="path">The relative path to search for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/></returns>
        [NotNull, ItemCanBeNull]
        public static Task<SearchResult<IUnixDirectoryEntry>> SearchDirectoryAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [CanBeNull] string path, CancellationToken cancellationToken)
        {
            var pathElements = GetPathElements(path);
            return SearchDirectoryAsync(fileSystem, currentPath, pathElements, cancellationToken);
        }

        /// <summary>
        /// Searches for a <see cref="IUnixDirectoryEntry"/> by using the <paramref name="currentPath"/> and <paramref name="pathElements"/>
        /// </summary>
        /// <param name="fileSystem">The underlying <see cref="IUnixFileSystem"/></param>
        /// <param name="currentPath">The current path</param>
        /// <param name="pathElements">The relative path elements to search for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/></returns>
        [NotNull, ItemCanBeNull]
        public static async Task<SearchResult<IUnixDirectoryEntry>> SearchDirectoryAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [NotNull, ItemNotNull] IReadOnlyList<string> pathElements, CancellationToken cancellationToken)
        {
            var sourceDir = await GetDirectoryAsync(fileSystem, currentPath, new ListSegment<string>(pathElements, 0, pathElements.Count - 1), cancellationToken);
            if (sourceDir == null)
                return null;
            var fileName = pathElements[pathElements.Count - 1];
            var foundEntry = await fileSystem.GetEntryByNameAsync(sourceDir, fileName, cancellationToken);
            var foundDirEntry = foundEntry as IUnixDirectoryEntry;
            if (foundDirEntry == null && foundEntry != null)
                return null;
            return new SearchResult<IUnixDirectoryEntry>(sourceDir, foundDirEntry, fileName);
        }

        /// <summary>
        /// Searches for a <see cref="IUnixFileEntry"/> by using the <paramref name="currentPath"/> and <paramref name="path"/>
        /// </summary>
        /// <param name="fileSystem">The underlying <see cref="IUnixFileSystem"/></param>
        /// <param name="currentPath">The current path</param>
        /// <param name="path">The relative path to search for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/></returns>
        [NotNull, ItemCanBeNull]
        public static Task<SearchResult<IUnixFileEntry>> SearchFileAsync([NotNull] this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [CanBeNull] string path, CancellationToken cancellationToken)
        {
            var pathElements = GetPathElements(path);
            return SearchFileAsync(fileSystem, currentPath, pathElements, cancellationToken);
        }

        /// <summary>
        /// Searches for a <see cref="IUnixFileEntry"/> by using the <paramref name="currentPath"/> and <paramref name="pathElements"/>
        /// </summary>
        /// <param name="fileSystem">The underlying <see cref="IUnixFileSystem"/></param>
        /// <param name="currentPath">The current path</param>
        /// <param name="pathElements">The relative path elements to search for</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The found <see cref="IUnixDirectoryEntry"/></returns>
        [NotNull, ItemCanBeNull]
        public static async Task<SearchResult<IUnixFileEntry>> SearchFileAsync(this IUnixFileSystem fileSystem, [NotNull, ItemNotNull] Stack<IUnixDirectoryEntry> currentPath, [NotNull, ItemNotNull] IReadOnlyList<string> pathElements, CancellationToken cancellationToken)
        {
            var sourceDir = await GetDirectoryAsync(fileSystem, currentPath, new ListSegment<string>(pathElements, 0, pathElements.Count - 1), cancellationToken);
            if (sourceDir == null)
                return null;
            var fileName = pathElements[pathElements.Count - 1];
            var foundEntry = await fileSystem.GetEntryByNameAsync(sourceDir, fileName, cancellationToken);
            var foundFileEntry = foundEntry as IUnixFileEntry;
            if (foundEntry != null && foundFileEntry == null)
                return null;
            return new SearchResult<IUnixFileEntry>(sourceDir, foundFileEntry, fileName);
        }

        /// <summary>
        /// Returns the <paramref name="path"/> as string
        /// </summary>
        /// <param name="path">The path to convert to string</param>
        /// <returns>The <paramref name="path"/> as string</returns>
        [NotNull]
{'fraction_non_alphanumeric': 0.12883519489400502, 'fraction_numerical': 0.0008206063369044905, 'mean_word_length': 2.6688409433015554, 'pattern_counts': {'":': 0, '<': 355, '<?xml version=': 0, '>': 353, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7386801', 'n_tokens_mistral': 5485, 'n_tokens_neox': 5475, 'n_words': 1447}