From 65a49fc403ed723287cda97d487fdcee0380c8af Mon Sep 17 00:00:00 2001 From: Anatoly Kulakov Date: Thu, 10 Jun 2021 23:55:32 +0300 Subject: [PATCH 1/4] Add async FileSystem abstration --- Auditor.sln | 14 ++++ src/Storage/AsyncEnumerable.cs | 25 ++++++ src/Storage/FileSystem/FileSystemEntry.cs | 34 ++++++++ src/Storage/FileSystem/IDirectory.cs | 16 ++++ src/Storage/FileSystem/IFile.cs | 11 +++ src/Storage/FileSystem/IFileSystem.cs | 6 ++ src/Storage/FileSystem/IFileSystemEntry.cs | 11 +++ src/Storage/FileSystem/NotFoundDirectory.cs | 23 +++++ src/Storage/FileSystem/NotFoundFile.cs | 22 +++++ src/Storage/FileSystem/NullFileSystem.cs | 14 ++++ src/Storage/Storage.csproj | 13 +++ .../Storage/FileSystem/FileSystemEntryTest.cs | 84 +++++++++++++++++++ src/test/Tests/Tests.csproj | 1 + 13 files changed, 274 insertions(+) create mode 100644 src/Storage/AsyncEnumerable.cs create mode 100644 src/Storage/FileSystem/FileSystemEntry.cs create mode 100644 src/Storage/FileSystem/IDirectory.cs create mode 100644 src/Storage/FileSystem/IFile.cs create mode 100644 src/Storage/FileSystem/IFileSystem.cs create mode 100644 src/Storage/FileSystem/IFileSystemEntry.cs create mode 100644 src/Storage/FileSystem/NotFoundDirectory.cs create mode 100644 src/Storage/FileSystem/NotFoundFile.cs create mode 100644 src/Storage/FileSystem/NullFileSystem.cs create mode 100644 src/Storage/Storage.csproj create mode 100644 src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs diff --git a/Auditor.sln b/Auditor.sln index 4698de2..9f5e593 100644 --- a/Auditor.sln +++ b/Auditor.sln @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D09148E2-D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "src\test\Tests\Tests.csproj", "{896C7174-7249-4CD8-A7F1-58F27D637BF1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Storage", "src\Storage\Storage.csproj", "{61852A44-BC38-4CF4-8BA9-AF5335784ACB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,6 +50,18 @@ Global {896C7174-7249-4CD8-A7F1-58F27D637BF1}.Release|x64.Build.0 = Release|Any CPU {896C7174-7249-4CD8-A7F1-58F27D637BF1}.Release|x86.ActiveCfg = Release|Any CPU {896C7174-7249-4CD8-A7F1-58F27D637BF1}.Release|x86.Build.0 = Release|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Debug|x64.ActiveCfg = Debug|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Debug|x64.Build.0 = Debug|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Debug|x86.ActiveCfg = Debug|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Debug|x86.Build.0 = Debug|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Release|Any CPU.Build.0 = Release|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Release|x64.ActiveCfg = Release|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Release|x64.Build.0 = Release|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Release|x86.ActiveCfg = Release|Any CPU + {61852A44-BC38-4CF4-8BA9-AF5335784ACB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {1D5C0BDD-9D0A-4849-86E0-FE904F9A1F3E} = {2D90C96D-0481-42E2-9526-6342B17FB37C} diff --git a/src/Storage/AsyncEnumerable.cs b/src/Storage/AsyncEnumerable.cs new file mode 100644 index 0000000..17273b3 --- /dev/null +++ b/src/Storage/AsyncEnumerable.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace DotNetRu.Auditor.Storage +{ + internal static class AsyncEnumerable + { + public static IAsyncEnumerable Empty() + { + return EmptyAsyncEnumerable.Instance; + } + + private class EmptyAsyncEnumerable : IAsyncEnumerable + { + public static readonly IAsyncEnumerable Instance = new EmptyAsyncEnumerable(); + + public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new()) + { + await Task.Yield(); + yield break; + } + } + } +} diff --git a/src/Storage/FileSystem/FileSystemEntry.cs b/src/Storage/FileSystem/FileSystemEntry.cs new file mode 100644 index 0000000..2d115e3 --- /dev/null +++ b/src/Storage/FileSystem/FileSystemEntry.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; + +namespace DotNetRu.Auditor.Storage.FileSystem +{ + public abstract class FileSystemEntry : IFileSystemEntry + { + protected FileSystemEntry(string fullName, bool exists) + { + FullName = Path.GetFullPath(fullName); + Name = Path.GetFileName(FullName); + Exists = exists; + } + + public string Name { get; } + + public string FullName { get; } + + public bool Exists { get; } + + protected string GetFullPath(string subPath) + { + var childrenPath = Path.Combine(FullName, subPath); + var childrenFullPath = Path.GetFullPath(childrenPath); + + if (!childrenFullPath.StartsWith(FullName, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException($"Children path «{childrenFullPath}» should be under the root «{FullName}»"); + } + + return childrenFullPath; + } + } +} diff --git a/src/Storage/FileSystem/IDirectory.cs b/src/Storage/FileSystem/IDirectory.cs new file mode 100644 index 0000000..df9de71 --- /dev/null +++ b/src/Storage/FileSystem/IDirectory.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace DotNetRu.Auditor.Storage.FileSystem +{ + public interface IDirectory : IFileSystemEntry + { + Task GetDirectoryInfoAsync(string subPath); + + Task GetFileInfoAsync(string subPath); + + IAsyncEnumerable EnumerateDirectoriesAsync(); + + IAsyncEnumerable EnumerateFilesAsync(); + } +} diff --git a/src/Storage/FileSystem/IFile.cs b/src/Storage/FileSystem/IFile.cs new file mode 100644 index 0000000..1734051 --- /dev/null +++ b/src/Storage/FileSystem/IFile.cs @@ -0,0 +1,11 @@ +using System.IO; + +namespace DotNetRu.Auditor.Storage.FileSystem +{ + public interface IFile : IFileSystemEntry + { + Stream OpenForRead(); + + Stream OpenForWrite(); + } +} \ No newline at end of file diff --git a/src/Storage/FileSystem/IFileSystem.cs b/src/Storage/FileSystem/IFileSystem.cs new file mode 100644 index 0000000..2cc2bf6 --- /dev/null +++ b/src/Storage/FileSystem/IFileSystem.cs @@ -0,0 +1,6 @@ +namespace DotNetRu.Auditor.Storage.FileSystem +{ + public interface IFileSystem : IDirectory + { + } +} diff --git a/src/Storage/FileSystem/IFileSystemEntry.cs b/src/Storage/FileSystem/IFileSystemEntry.cs new file mode 100644 index 0000000..f99b7c1 --- /dev/null +++ b/src/Storage/FileSystem/IFileSystemEntry.cs @@ -0,0 +1,11 @@ +namespace DotNetRu.Auditor.Storage.FileSystem +{ + public interface IFileSystemEntry + { + string Name { get; } + + string FullName { get; } + + bool Exists { get; } + } +} \ No newline at end of file diff --git a/src/Storage/FileSystem/NotFoundDirectory.cs b/src/Storage/FileSystem/NotFoundDirectory.cs new file mode 100644 index 0000000..e3ea2ac --- /dev/null +++ b/src/Storage/FileSystem/NotFoundDirectory.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace DotNetRu.Auditor.Storage.FileSystem +{ + public class NotFoundDirectory : FileSystemEntry, IDirectory + { + public NotFoundDirectory(string fullName) + : base(fullName, false) + { + } + + public static Task ToTask(string fullName) => Task.FromResult(new NotFoundDirectory(fullName)); + + public Task GetDirectoryInfoAsync(string subPath) => ToTask(subPath); + + public Task GetFileInfoAsync(string subPath) => NotFoundFile.ToTask(subPath); + + public IAsyncEnumerable EnumerateDirectoriesAsync() => AsyncEnumerable.Empty(); + + public IAsyncEnumerable EnumerateFilesAsync() => AsyncEnumerable.Empty(); + } +} diff --git a/src/Storage/FileSystem/NotFoundFile.cs b/src/Storage/FileSystem/NotFoundFile.cs new file mode 100644 index 0000000..0d07854 --- /dev/null +++ b/src/Storage/FileSystem/NotFoundFile.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace DotNetRu.Auditor.Storage.FileSystem +{ + public class NotFoundFile : FileSystemEntry, IFile + { + public NotFoundFile(string fullName) + : base(fullName, false) + { + } + + public static Task ToTask(string fullName) => Task.FromResult(new NotFoundFile(fullName)); + + public static Exception ToException(string fullName) => new FileNotFoundException($"Could not find file: {fullName}", fullName); + + public Stream OpenForRead() => throw ToException(FullName); + + public Stream OpenForWrite() => throw ToException(FullName); + } +} diff --git a/src/Storage/FileSystem/NullFileSystem.cs b/src/Storage/FileSystem/NullFileSystem.cs new file mode 100644 index 0000000..3facb96 --- /dev/null +++ b/src/Storage/FileSystem/NullFileSystem.cs @@ -0,0 +1,14 @@ +using System; + +namespace DotNetRu.Auditor.Storage.FileSystem +{ + public class NullFileSystem : NotFoundDirectory, IFileSystem + { + public static readonly NullFileSystem Instance = new NullFileSystem(); + + public NullFileSystem() + : base(String.Empty) + { + } + } +} diff --git a/src/Storage/Storage.csproj b/src/Storage/Storage.csproj new file mode 100644 index 0000000..1fd7ba4 --- /dev/null +++ b/src/Storage/Storage.csproj @@ -0,0 +1,13 @@ + + + + net5.0 + DotNetRu.Auditor.Storage + DotNetRu.Auditor.Storage + + + + + + + diff --git a/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs b/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs new file mode 100644 index 0000000..fb862c3 --- /dev/null +++ b/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs @@ -0,0 +1,84 @@ +using System; +using System.IO; +using DotNetRu.Auditor.Storage.FileSystem; +using Xunit; + +namespace DotNetRu.Auditor.Tests.Storage.FileSystem +{ + public sealed class FileSystemEntryTest + { + [Fact] + public void ShouldResolveFullName() + { + // Arrange + const string name = "Test"; + + // Act + var entry = new TestEntry(name); + + // Assert + var fullName = entry.FullName; + Assert.EndsWith(name, fullName); + + var hasRoot = Path.IsPathRooted(fullName); + Assert.True(hasRoot); + } + + [Fact] + public void ShouldResolveName() + { + // Arrange + const string expectedName = "Test"; + var fullName = Path.Combine("A", "B", expectedName); + + // Act + var entry = new TestEntry(fullName); + + // Assert + Assert.Equal(expectedName, entry.Name); + } + + [Theory] + [InlineData(@"C:\", @"Abc", @"C:\Abc")] + [InlineData(@"C:\1", @".\Abc", @"C:\1\Abc")] + public void ShouldResolveFullPath(string root, string subPath, string expectedFullPath) + { + // Act + var fullPath = GetFullPath(root, subPath); + + // Assert + Assert.Equal(expectedFullPath, fullPath); + } + + [Fact] + public void ShouldRaiseErrorWhenHackingRoot() + { + // Arrange + const string root = @"C:\A\B"; + const string subPath = @"..\..\etc\passwd"; + + // Act + void HackRoot() => GetFullPath(root, subPath); + + // Assert + Assert.Throws(HackRoot); + } + + private static string GetFullPath(string root, string subPath) + { + // var root = AssertEx.NotNull(Path.GetPathRoot(typeof(FileSystemEntry).Assembly.Location)); + var entry = new TestEntry(root); + return entry.GetFullSubPath(subPath); + } + + private sealed class TestEntry : FileSystemEntry + { + public TestEntry(string fullName) + : base(fullName, true) + { + } + + public string GetFullSubPath(string subPath) => base.GetFullPath(subPath); + } + } +} diff --git a/src/test/Tests/Tests.csproj b/src/test/Tests/Tests.csproj index eb95259..43c1b02 100644 --- a/src/test/Tests/Tests.csproj +++ b/src/test/Tests/Tests.csproj @@ -22,6 +22,7 @@ + From a7eb439998eacfdd7108984395bc74a274e73010 Mon Sep 17 00:00:00 2001 From: Anatoly Kulakov Date: Thu, 10 Jun 2021 23:57:22 +0300 Subject: [PATCH 2/4] Add physical file system --- .../FileSystem/Physical/PhysicalDirectory.cs | 68 +++++++++++++++++++ .../FileSystem/Physical/PhysicalFile.cs | 32 +++++++++ .../FileSystem/Physical/PhysicalFileSystem.cs | 19 ++++++ 3 files changed, 119 insertions(+) create mode 100644 src/Storage/FileSystem/Physical/PhysicalDirectory.cs create mode 100644 src/Storage/FileSystem/Physical/PhysicalFile.cs create mode 100644 src/Storage/FileSystem/Physical/PhysicalFileSystem.cs diff --git a/src/Storage/FileSystem/Physical/PhysicalDirectory.cs b/src/Storage/FileSystem/Physical/PhysicalDirectory.cs new file mode 100644 index 0000000..5f74cef --- /dev/null +++ b/src/Storage/FileSystem/Physical/PhysicalDirectory.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace DotNetRu.Auditor.Storage.FileSystem.Physical +{ + public class PhysicalDirectory : FileSystemEntry, IDirectory + { + public PhysicalDirectory(string fullName) + : base(fullName, Directory.Exists(fullName)) + { + } + + public Task GetDirectoryInfoAsync(string subPath) + { + if (!Exists) + { + return NotFoundDirectory.ToTask(subPath); + } + + var fullDirectoryName = GetFullPath(subPath); + var directory = new PhysicalDirectory(fullDirectoryName); + return Task.FromResult(directory); + } + + public Task GetFileInfoAsync(string subPath) + { + if (!Exists) + { + return NotFoundFile.ToTask(subPath); + } + + var fullFileName = GetFullPath(subPath); + var file = new PhysicalFile(fullFileName); + return Task.FromResult(file); + } + + public async IAsyncEnumerable EnumerateDirectoriesAsync() + { + if (!Exists) + { + yield break; + } + + await Task.Yield(); + + foreach (var directoryFullName in Directory.EnumerateDirectories(FullName)) + { + yield return new PhysicalDirectory(directoryFullName); + } + } + + public async IAsyncEnumerable EnumerateFilesAsync() + { + if (!Exists) + { + yield break; + } + + await Task.Yield(); + + foreach (var fileFullName in Directory.EnumerateFiles(FullName)) + { + yield return new PhysicalFile(fileFullName); + } + } + } +} diff --git a/src/Storage/FileSystem/Physical/PhysicalFile.cs b/src/Storage/FileSystem/Physical/PhysicalFile.cs new file mode 100644 index 0000000..881530f --- /dev/null +++ b/src/Storage/FileSystem/Physical/PhysicalFile.cs @@ -0,0 +1,32 @@ +using System.IO; + +namespace DotNetRu.Auditor.Storage.FileSystem.Physical +{ + public sealed class PhysicalFile : FileSystemEntry, IFile + { + public PhysicalFile(string fullName) + : base(fullName, File.Exists(fullName)) + { + } + + public Stream OpenForRead() + { + if (!Exists) + { + throw NotFoundFile.ToException(FullName); + } + + return File.OpenRead(FullName); + } + + public Stream OpenForWrite() + { + if (!Exists) + { + throw NotFoundFile.ToException(FullName); + } + + return File.OpenWrite(FullName); + } + } +} diff --git a/src/Storage/FileSystem/Physical/PhysicalFileSystem.cs b/src/Storage/FileSystem/Physical/PhysicalFileSystem.cs new file mode 100644 index 0000000..b35dec3 --- /dev/null +++ b/src/Storage/FileSystem/Physical/PhysicalFileSystem.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace DotNetRu.Auditor.Storage.FileSystem.Physical +{ + public sealed class PhysicalFileSystem : PhysicalDirectory, IFileSystem + { + public PhysicalFileSystem(string root) + : base(root) + { + if (!Path.IsPathRooted(root)) + { + throw new ArgumentException($"The path «{root}» must be absolute", nameof(root)); + } + } + } +} + + From c4aa06b474e2ebd5c0e16982f2ce08e4d9775de6 Mon Sep 17 00:00:00 2001 From: zetroot Date: Fri, 11 Jun 2021 22:42:29 +0300 Subject: [PATCH 3/4] Refactored test data to meet crossplatform path requirements --- .../FileSystem/CrossPlatformDataGenerator.cs | 23 +++++++++++++++++++ .../Storage/FileSystem/FileSystemEntryTest.cs | 8 +++---- 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 src/test/Tests/Storage/FileSystem/CrossPlatformDataGenerator.cs diff --git a/src/test/Tests/Storage/FileSystem/CrossPlatformDataGenerator.cs b/src/test/Tests/Storage/FileSystem/CrossPlatformDataGenerator.cs new file mode 100644 index 0000000..7f6c312 --- /dev/null +++ b/src/test/Tests/Storage/FileSystem/CrossPlatformDataGenerator.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.IO; + +namespace DotNetRu.Auditor.Tests.Storage.FileSystem +{ + public class CrossPlatformDataGenerator + { + private static readonly string filesystemRoot; + + static CrossPlatformDataGenerator() + { + filesystemRoot = Path.GetPathRoot(Directory.GetCurrentDirectory()) ?? string.Empty; + } + + public static IEnumerable GetDataForFullPathTest() + { + // "C:\" + "Abc" => "C:\Abc" + yield return new object[] {filesystemRoot, "Abc", Path.Combine(filesystemRoot, "Abc")}; + // "C:\1" + ".\Abc" => "C:\1\Abc" + yield return new object[] {Path.Combine(filesystemRoot, "1"), Path.Combine(".","Abc"), Path.Combine(filesystemRoot, "1", "Abc")}; + } + } +} diff --git a/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs b/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs index fb862c3..83a221c 100644 --- a/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs +++ b/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs @@ -39,8 +39,7 @@ public void ShouldResolveName() } [Theory] - [InlineData(@"C:\", @"Abc", @"C:\Abc")] - [InlineData(@"C:\1", @".\Abc", @"C:\1\Abc")] + [MemberData(nameof(CrossPlatformDataGenerator.GetDataForFullPathTest), MemberType = typeof(CrossPlatformDataGenerator))] public void ShouldResolveFullPath(string root, string subPath, string expectedFullPath) { // Act @@ -54,8 +53,9 @@ public void ShouldResolveFullPath(string root, string subPath, string expectedFu public void ShouldRaiseErrorWhenHackingRoot() { // Arrange - const string root = @"C:\A\B"; - const string subPath = @"..\..\etc\passwd"; + var fsRoot= Path.GetPathRoot(Directory.GetCurrentDirectory()) ?? string.Empty; + string root = Path.Combine(fsRoot, "A", "B"); + string subPath = Path.Combine("..", "..", "etc","passwd"); // Act void HackRoot() => GetFullPath(root, subPath); From f5f7b3c3ee9eee62d62e6ec6ed4114b161f0859d Mon Sep 17 00:00:00 2001 From: Anatoly Kulakov Date: Sat, 12 Jun 2021 00:21:26 +0300 Subject: [PATCH 4/4] More async and value tasks for file system --- src/Storage/FileSystem/IDirectory.cs | 13 +++++++++-- src/Storage/FileSystem/IFile.cs | 7 +++--- src/Storage/FileSystem/IFileSystemEntry.cs | 2 +- src/Storage/FileSystem/NotFoundDirectory.cs | 6 ++--- src/Storage/FileSystem/NotFoundFile.cs | 6 ++--- .../FileSystem/Physical/PhysicalDirectory.cs | 8 +++---- .../FileSystem/Physical/PhysicalFile.cs | 11 +++++---- .../FileSystem/Physical/PhysicalFileSystem.cs | 2 -- .../FileSystem/CrossPlatformDataGenerator.cs | 23 ------------------- .../Storage/FileSystem/FileSystemEntryTest.cs | 19 +++++++++++---- 10 files changed, 47 insertions(+), 50 deletions(-) delete mode 100644 src/test/Tests/Storage/FileSystem/CrossPlatformDataGenerator.cs diff --git a/src/Storage/FileSystem/IDirectory.cs b/src/Storage/FileSystem/IDirectory.cs index df9de71..2a20bde 100644 --- a/src/Storage/FileSystem/IDirectory.cs +++ b/src/Storage/FileSystem/IDirectory.cs @@ -3,11 +3,20 @@ namespace DotNetRu.Auditor.Storage.FileSystem { + /// + /// In fact, most operating systems do not support asynchronous enumeration of file system contents. + /// This is why there is no asynchronous API in the official BCL. But we have a Blazor consumer that + /// throws an exception when using synchronization operations, and a GutHub HTTP API implementation + /// that can perform real asynchronous operations. + /// + /// To respect I/O operations, we decided to use asynchronous abstractions. But we assume that real + /// asynchronous operations will be rare, so we decided to use ValueTask. + /// > public interface IDirectory : IFileSystemEntry { - Task GetDirectoryInfoAsync(string subPath); + ValueTask GetDirectoryInfoAsync(string subPath); - Task GetFileInfoAsync(string subPath); + ValueTask GetFileInfoAsync(string subPath); IAsyncEnumerable EnumerateDirectoriesAsync(); diff --git a/src/Storage/FileSystem/IFile.cs b/src/Storage/FileSystem/IFile.cs index 1734051..7d2efc5 100644 --- a/src/Storage/FileSystem/IFile.cs +++ b/src/Storage/FileSystem/IFile.cs @@ -1,11 +1,12 @@ using System.IO; +using System.Threading.Tasks; namespace DotNetRu.Auditor.Storage.FileSystem { public interface IFile : IFileSystemEntry { - Stream OpenForRead(); + ValueTask OpenForReadAsync(); - Stream OpenForWrite(); + ValueTask OpenForWriteAsync(); } -} \ No newline at end of file +} diff --git a/src/Storage/FileSystem/IFileSystemEntry.cs b/src/Storage/FileSystem/IFileSystemEntry.cs index f99b7c1..ba9c584 100644 --- a/src/Storage/FileSystem/IFileSystemEntry.cs +++ b/src/Storage/FileSystem/IFileSystemEntry.cs @@ -8,4 +8,4 @@ public interface IFileSystemEntry bool Exists { get; } } -} \ No newline at end of file +} diff --git a/src/Storage/FileSystem/NotFoundDirectory.cs b/src/Storage/FileSystem/NotFoundDirectory.cs index e3ea2ac..0798bd3 100644 --- a/src/Storage/FileSystem/NotFoundDirectory.cs +++ b/src/Storage/FileSystem/NotFoundDirectory.cs @@ -10,11 +10,11 @@ public NotFoundDirectory(string fullName) { } - public static Task ToTask(string fullName) => Task.FromResult(new NotFoundDirectory(fullName)); + public static ValueTask ToTask(string fullName) => ValueTask.FromResult(new NotFoundDirectory(fullName)); - public Task GetDirectoryInfoAsync(string subPath) => ToTask(subPath); + public ValueTask GetDirectoryInfoAsync(string subPath) => ToTask(subPath); - public Task GetFileInfoAsync(string subPath) => NotFoundFile.ToTask(subPath); + public ValueTask GetFileInfoAsync(string subPath) => NotFoundFile.ToTask(subPath); public IAsyncEnumerable EnumerateDirectoriesAsync() => AsyncEnumerable.Empty(); diff --git a/src/Storage/FileSystem/NotFoundFile.cs b/src/Storage/FileSystem/NotFoundFile.cs index 0d07854..b69d33c 100644 --- a/src/Storage/FileSystem/NotFoundFile.cs +++ b/src/Storage/FileSystem/NotFoundFile.cs @@ -11,12 +11,12 @@ public NotFoundFile(string fullName) { } - public static Task ToTask(string fullName) => Task.FromResult(new NotFoundFile(fullName)); + public static ValueTask ToTask(string fullName) => ValueTask.FromResult(new NotFoundFile(fullName)); public static Exception ToException(string fullName) => new FileNotFoundException($"Could not find file: {fullName}", fullName); - public Stream OpenForRead() => throw ToException(FullName); + public ValueTask OpenForReadAsync() => throw ToException(FullName); - public Stream OpenForWrite() => throw ToException(FullName); + public ValueTask OpenForWriteAsync() => throw ToException(FullName); } } diff --git a/src/Storage/FileSystem/Physical/PhysicalDirectory.cs b/src/Storage/FileSystem/Physical/PhysicalDirectory.cs index 5f74cef..0f07a83 100644 --- a/src/Storage/FileSystem/Physical/PhysicalDirectory.cs +++ b/src/Storage/FileSystem/Physical/PhysicalDirectory.cs @@ -11,7 +11,7 @@ public PhysicalDirectory(string fullName) { } - public Task GetDirectoryInfoAsync(string subPath) + public ValueTask GetDirectoryInfoAsync(string subPath) { if (!Exists) { @@ -20,10 +20,10 @@ public Task GetDirectoryInfoAsync(string subPath) var fullDirectoryName = GetFullPath(subPath); var directory = new PhysicalDirectory(fullDirectoryName); - return Task.FromResult(directory); + return ValueTask.FromResult(directory); } - public Task GetFileInfoAsync(string subPath) + public ValueTask GetFileInfoAsync(string subPath) { if (!Exists) { @@ -32,7 +32,7 @@ public Task GetFileInfoAsync(string subPath) var fullFileName = GetFullPath(subPath); var file = new PhysicalFile(fullFileName); - return Task.FromResult(file); + return ValueTask.FromResult(file); } public async IAsyncEnumerable EnumerateDirectoriesAsync() diff --git a/src/Storage/FileSystem/Physical/PhysicalFile.cs b/src/Storage/FileSystem/Physical/PhysicalFile.cs index 881530f..ed66421 100644 --- a/src/Storage/FileSystem/Physical/PhysicalFile.cs +++ b/src/Storage/FileSystem/Physical/PhysicalFile.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Threading.Tasks; namespace DotNetRu.Auditor.Storage.FileSystem.Physical { @@ -9,24 +10,26 @@ public PhysicalFile(string fullName) { } - public Stream OpenForRead() + public ValueTask OpenForReadAsync() { if (!Exists) { throw NotFoundFile.ToException(FullName); } - return File.OpenRead(FullName); + var inputStream = File.OpenRead(FullName); + return ValueTask.FromResult(inputStream); } - public Stream OpenForWrite() + public ValueTask OpenForWriteAsync() { if (!Exists) { throw NotFoundFile.ToException(FullName); } - return File.OpenWrite(FullName); + var outputStream = File.OpenWrite(FullName); + return ValueTask.FromResult(outputStream); } } } diff --git a/src/Storage/FileSystem/Physical/PhysicalFileSystem.cs b/src/Storage/FileSystem/Physical/PhysicalFileSystem.cs index b35dec3..1313a2e 100644 --- a/src/Storage/FileSystem/Physical/PhysicalFileSystem.cs +++ b/src/Storage/FileSystem/Physical/PhysicalFileSystem.cs @@ -15,5 +15,3 @@ public PhysicalFileSystem(string root) } } } - - diff --git a/src/test/Tests/Storage/FileSystem/CrossPlatformDataGenerator.cs b/src/test/Tests/Storage/FileSystem/CrossPlatformDataGenerator.cs deleted file mode 100644 index 7f6c312..0000000 --- a/src/test/Tests/Storage/FileSystem/CrossPlatformDataGenerator.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.IO; - -namespace DotNetRu.Auditor.Tests.Storage.FileSystem -{ - public class CrossPlatformDataGenerator - { - private static readonly string filesystemRoot; - - static CrossPlatformDataGenerator() - { - filesystemRoot = Path.GetPathRoot(Directory.GetCurrentDirectory()) ?? string.Empty; - } - - public static IEnumerable GetDataForFullPathTest() - { - // "C:\" + "Abc" => "C:\Abc" - yield return new object[] {filesystemRoot, "Abc", Path.Combine(filesystemRoot, "Abc")}; - // "C:\1" + ".\Abc" => "C:\1\Abc" - yield return new object[] {Path.Combine(filesystemRoot, "1"), Path.Combine(".","Abc"), Path.Combine(filesystemRoot, "1", "Abc")}; - } - } -} diff --git a/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs b/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs index 83a221c..6039d2d 100644 --- a/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs +++ b/src/test/Tests/Storage/FileSystem/FileSystemEntryTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using DotNetRu.Auditor.Storage.FileSystem; using Xunit; @@ -7,6 +8,8 @@ namespace DotNetRu.Auditor.Tests.Storage.FileSystem { public sealed class FileSystemEntryTest { + private static readonly string PathRoot = Path.GetPathRoot(Directory.GetCurrentDirectory()) ?? String.Empty; + [Fact] public void ShouldResolveFullName() { @@ -38,8 +41,16 @@ public void ShouldResolveName() Assert.Equal(expectedName, entry.Name); } + public static IEnumerable GetDataForFullPathTest() + { + // "C:\" + "Abc" => "C:\Abc" + yield return new object[] { PathRoot, "Abc", Path.Combine(PathRoot, "Abc") }; + // "C:\1" + ".\Abc" => "C:\1\Abc" + yield return new object[] { Path.Combine(PathRoot, "1"), Path.Combine(".", "Abc"), Path.Combine(PathRoot, "1", "Abc") }; + } + [Theory] - [MemberData(nameof(CrossPlatformDataGenerator.GetDataForFullPathTest), MemberType = typeof(CrossPlatformDataGenerator))] + [MemberData(nameof(GetDataForFullPathTest))] public void ShouldResolveFullPath(string root, string subPath, string expectedFullPath) { // Act @@ -53,9 +64,8 @@ public void ShouldResolveFullPath(string root, string subPath, string expectedFu public void ShouldRaiseErrorWhenHackingRoot() { // Arrange - var fsRoot= Path.GetPathRoot(Directory.GetCurrentDirectory()) ?? string.Empty; - string root = Path.Combine(fsRoot, "A", "B"); - string subPath = Path.Combine("..", "..", "etc","passwd"); + var root = Path.Combine(PathRoot, "A", "B"); + var subPath = Path.Combine("..", "..", "etc", "passwd"); // Act void HackRoot() => GetFullPath(root, subPath); @@ -66,7 +76,6 @@ public void ShouldRaiseErrorWhenHackingRoot() private static string GetFullPath(string root, string subPath) { - // var root = AssertEx.NotNull(Path.GetPathRoot(typeof(FileSystemEntry).Assembly.Location)); var entry = new TestEntry(root); return entry.GetFullSubPath(subPath); }