diff --git a/README.md b/README.md index e473966..73dcd07 100644 --- a/README.md +++ b/README.md @@ -152,9 +152,9 @@ if (!fs.IsReadOnly) ## Supported Versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Abstractions/README.md b/src/Ramstack.FileSystem.Abstractions/README.md index fd7558e..ddf2a0f 100644 --- a/src/Ramstack.FileSystem.Abstractions/README.md +++ b/src/Ramstack.FileSystem.Abstractions/README.md @@ -1,4 +1,6 @@ -# Ramstack.FileSystem +# Ramstack.FileSystem.Abstractions +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Abstractions.svg)](https://nuget.org/packages/Ramstack.FileSystem.Abstractions) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides a virtual file system abstraction. @@ -158,9 +160,9 @@ if (!fs.IsReadOnly) ## Supported Versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Abstractions/VirtualPath.cs b/src/Ramstack.FileSystem.Abstractions/VirtualPath.cs index ef964e0..5a938aa 100644 --- a/src/Ramstack.FileSystem.Abstractions/VirtualPath.cs +++ b/src/Ramstack.FileSystem.Abstractions/VirtualPath.cs @@ -12,6 +12,8 @@ namespace Ramstack.FileSystem; /// For compatibility across different implementations of /// and operating systems, directory separators are unified to use both /// backslashes and forward slashes ("/" and "\"). +/// +/// /// This approach will be reviewed once a better solution is found. /// /// @@ -221,6 +223,14 @@ public static bool IsNormalized(ReadOnlySpan path) /// public static string Normalize(string path) { + // Short-circuit optimization: + // Many paths are already normalized except for a missing leading slash. + // It's faster and more memory-efficient to first check/add the leading slash + // before performing full normalization. + + if (!path.StartsWith('/')) + path = $"/{path}"; + if (IsNormalized(path)) return path; diff --git a/src/Ramstack.FileSystem.Adapters/README.md b/src/Ramstack.FileSystem.Adapters/README.md index 83c0818..3a5cd9a 100644 --- a/src/Ramstack.FileSystem.Adapters/README.md +++ b/src/Ramstack.FileSystem.Adapters/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Adapters +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Adapters.svg)](https://nuget.org/packages/Ramstack.FileSystem.Adapters) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` for integrating with `Microsoft.Extensions.FileProviders`. @@ -46,9 +48,9 @@ await foreach (VirtualFile file in fs.GetFilesAsync("/")) ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Amazon/AccessControl.cs b/src/Ramstack.FileSystem.Amazon/AccessControl.cs index cfdb0ff..aa830a9 100644 --- a/src/Ramstack.FileSystem.Amazon/AccessControl.cs +++ b/src/Ramstack.FileSystem.Amazon/AccessControl.cs @@ -1,4 +1,4 @@ -namespace Ramstack.FileSystem.Amazon; +namespace Ramstack.FileSystem.Amazon; /// /// An enumeration of all possible CannedACLs that can be used diff --git a/src/Ramstack.FileSystem.Amazon/README.md b/src/Ramstack.FileSystem.Amazon/README.md index 1238550..637e7cc 100644 --- a/src/Ramstack.FileSystem.Amazon/README.md +++ b/src/Ramstack.FileSystem.Amazon/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Amazon +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Amazon.svg)](https://nuget.org/packages/Ramstack.FileSystem.Amazon) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` using Amazon S3 storage. @@ -57,9 +59,9 @@ AmazonS3FileSystem fs = new AmazonS3FileSystem( ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Amazon/S3Directory.cs b/src/Ramstack.FileSystem.Amazon/S3Directory.cs index 7d4aae9..517e6a5 100644 --- a/src/Ramstack.FileSystem.Amazon/S3Directory.cs +++ b/src/Ramstack.FileSystem.Amazon/S3Directory.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Runtime.CompilerServices; using Amazon.S3.Model; @@ -180,8 +181,8 @@ protected override async IAsyncEnumerable GetFileNodesCoreAsync(str foreach (var obj in response.S3Objects) { - var directoryPath = VirtualPath.GetDirectoryName( - VirtualPath.Join("/", obj.Key)); + var path = VirtualPath.Normalize(obj.Key); + var directoryPath = VirtualPath.GetDirectoryName(path); while (directoryPath.Length != 0 && directories.Add(directoryPath)) { @@ -193,13 +194,13 @@ protected override async IAsyncEnumerable GetFileNodesCoreAsync(str // unnecessary memory allocation, we process them directly. // if (IsMatched(directoryPath.AsSpan(FullName.Length), patterns, excludes)) - yield return new S3Directory(_fs, VirtualPath.Normalize(directoryPath)); + yield return new S3Directory(_fs, directoryPath); directoryPath = VirtualPath.GetDirectoryName(directoryPath); } if (IsMatched(obj.Key.AsSpan(request.Prefix.Length), patterns, excludes)) - yield return CreateVirtualFile(obj); + yield return CreateVirtualFile(obj, path); } request.ContinuationToken = response.NextContinuationToken; @@ -276,7 +277,7 @@ protected override async IAsyncEnumerable GetDirectoriesCoreAs foreach (var obj in response.S3Objects) { var directoryPath = VirtualPath.GetDirectoryName( - VirtualPath.Join("/", obj.Key)); + VirtualPath.Normalize(obj.Key)); while (directoryPath.Length != 0 && directories.Add(directoryPath)) { @@ -288,7 +289,7 @@ protected override async IAsyncEnumerable GetDirectoriesCoreAs // unnecessary memory allocation, we process them directly. // if (IsMatched(directoryPath.AsSpan(FullName.Length), patterns, excludes)) - yield return new S3Directory(_fs, VirtualPath.Normalize(directoryPath)); + yield return new S3Directory(_fs, directoryPath); directoryPath = VirtualPath.GetDirectoryName(directoryPath); } @@ -303,10 +304,11 @@ protected override async IAsyncEnumerable GetDirectoriesCoreAs /// Creates a instance based on the specified object. /// /// The representing the file. + /// The normalized name of the object. /// /// A new instance representing the file. /// - private S3File CreateVirtualFile(S3Object obj) + private S3File CreateVirtualFile(S3Object obj, string? normalizedName = null) { var properties = VirtualNodeProperties .CreateFileProperties( @@ -315,7 +317,7 @@ private S3File CreateVirtualFile(S3Object obj) lastWriteTime: obj.LastModified, length: obj.Size); - var path = VirtualPath.Normalize(obj.Key); + var path = normalizedName ?? VirtualPath.Normalize(obj.Key); return new S3File(_fs, path, properties); } @@ -363,6 +365,9 @@ private static bool IsMatched(scoped ReadOnlySpan path, string[] patterns, /// GetPrefix("/sub/folder") // returns "sub/folder/" /// /// - private static string GetPrefix(string path) => - path == "/" ? "" : $"{path[1..]}/"; + private static string GetPrefix(string path) + { + Debug.Assert(VirtualPath.IsNormalized(path)); + return path == "/" ? "" : $"{path[1..]}/"; + } } diff --git a/src/Ramstack.FileSystem.Amazon/Utilities/NullSynchronizationContext.cs b/src/Ramstack.FileSystem.Amazon/Utilities/NullSynchronizationContext.cs index adedfa2..b7404bc 100644 --- a/src/Ramstack.FileSystem.Amazon/Utilities/NullSynchronizationContext.cs +++ b/src/Ramstack.FileSystem.Amazon/Utilities/NullSynchronizationContext.cs @@ -1,4 +1,4 @@ -namespace Ramstack.FileSystem.Amazon.Utilities; +namespace Ramstack.FileSystem.Amazon.Utilities; /// /// Provides a mechanism to temporarily set the to . diff --git a/src/Ramstack.FileSystem.Azure/AzureDirectory.cs b/src/Ramstack.FileSystem.Azure/AzureDirectory.cs index db9d180..f14a92c 100644 --- a/src/Ramstack.FileSystem.Azure/AzureDirectory.cs +++ b/src/Ramstack.FileSystem.Azure/AzureDirectory.cs @@ -1,4 +1,5 @@ using System.Collections.ObjectModel; +using System.Diagnostics; using System.Runtime.CompilerServices; using Azure; @@ -168,8 +169,8 @@ protected override async IAsyncEnumerable GetFileNodesCoreAsync(str { foreach (var blob in page.Values) { - var directoryPath = VirtualPath.GetDirectoryName( - VirtualPath.Join("/", blob.Name)); + var path = VirtualPath.Normalize(blob.Name); + var directoryPath = VirtualPath.GetDirectoryName(path); while (directoryPath.Length != 0 && directories.Add(directoryPath)) { @@ -181,13 +182,13 @@ protected override async IAsyncEnumerable GetFileNodesCoreAsync(str // unnecessary memory allocation, we process them directly. // if (IsMatched(directoryPath.AsSpan(FullName.Length), patterns, excludes)) - yield return new AzureDirectory(_fs, VirtualPath.Normalize(directoryPath)); + yield return new AzureDirectory(_fs, directoryPath); directoryPath = VirtualPath.GetDirectoryName(directoryPath); } if (IsMatched(blob.Name.AsSpan(prefix.Length), patterns, excludes)) - yield return CreateVirtualFile(blob); + yield return CreateVirtualFile(blob, path); } } } @@ -246,7 +247,7 @@ protected override async IAsyncEnumerable GetDirectoriesCoreAs foreach (var blob in page.Values) { var directoryPath = VirtualPath.GetDirectoryName( - VirtualPath.Join("/", blob.Name)); + VirtualPath.Normalize(blob.Name)); while (directoryPath.Length != 0 && directories.Add(directoryPath)) { @@ -258,7 +259,7 @@ protected override async IAsyncEnumerable GetDirectoriesCoreAs // unnecessary memory allocation, we process them directly. // if (IsMatched(directoryPath.AsSpan(FullName.Length), patterns, excludes)) - yield return new AzureDirectory(_fs, VirtualPath.Normalize(directoryPath)); + yield return new AzureDirectory(_fs, directoryPath); directoryPath = VirtualPath.GetDirectoryName(directoryPath); } @@ -270,10 +271,11 @@ protected override async IAsyncEnumerable GetDirectoriesCoreAs /// Creates a instance based on the specified blob item. /// /// The representing the file. + /// The normalized name of the blob. /// /// A new instance representing the file. /// - private AzureFile CreateVirtualFile(BlobItem blob) + private AzureFile CreateVirtualFile(BlobItem blob, string? normalizedPath = null) { var info = blob.Properties; var properties = VirtualNodeProperties.CreateFileProperties( @@ -282,7 +284,7 @@ private AzureFile CreateVirtualFile(BlobItem blob) lastWriteTime: info.LastModified.GetValueOrDefault(), length: info.ContentLength.GetValueOrDefault(defaultValue: -1)); - var path = VirtualPath.Normalize(blob.Name); + var path = normalizedPath ?? VirtualPath.Normalize(blob.Name); return new AzureFile(_fs, path, properties); } @@ -329,6 +331,9 @@ private static bool IsMatched(scoped ReadOnlySpan path, string[] patterns, /// GetPrefix("/sub/folder") // returns "sub/folder/" /// /// - private static string GetPrefix(string path) => - path == "/" ? "" : $"{path[1..]}/"; + private static string GetPrefix(string path) + { + Debug.Assert(VirtualPath.IsNormalized(path)); + return path == "/" ? "" : $"{path[1..]}/"; + } } diff --git a/src/Ramstack.FileSystem.Azure/AzureFileSystem.cs b/src/Ramstack.FileSystem.Azure/AzureFileSystem.cs index 2860e38..a756947 100644 --- a/src/Ramstack.FileSystem.Azure/AzureFileSystem.cs +++ b/src/Ramstack.FileSystem.Azure/AzureFileSystem.cs @@ -158,7 +158,7 @@ void IDisposable.Dispose() /// internal BlobClient CreateBlobClient(string path) { - Debug.Assert(path == VirtualPath.Normalize(path)); + Debug.Assert(VirtualPath.IsNormalized(path)); return AzureClient.GetBlobClient(path[1..]); } } diff --git a/src/Ramstack.FileSystem.Azure/README.md b/src/Ramstack.FileSystem.Azure/README.md index 1601788..727d9a6 100644 --- a/src/Ramstack.FileSystem.Azure/README.md +++ b/src/Ramstack.FileSystem.Azure/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Azure +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Azure.svg)](https://nuget.org/packages/Ramstack.FileSystem.Azure) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` based on Azure Blob Storage. @@ -49,9 +51,9 @@ AzureFileSystem fs = new AzureFileSystem(connectionString, containerName: "stora ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Composite/CompositeDirectory.cs b/src/Ramstack.FileSystem.Composite/CompositeDirectory.cs index 5331169..4622791 100644 --- a/src/Ramstack.FileSystem.Composite/CompositeDirectory.cs +++ b/src/Ramstack.FileSystem.Composite/CompositeDirectory.cs @@ -46,12 +46,46 @@ protected override async IAsyncEnumerable GetFileNodesCoreAsync([En if (node is NotFoundFile or NotFoundDirectory) continue; - if (!set.Add(node.FullName)) + if (set.Add(node.FullName)) + yield return node is VirtualFile file + ? new CompositeFile(_fs, node.FullName, file) + : new CompositeDirectory(_fs, node.FullName); + } + } + } + + /// + protected override async IAsyncEnumerable GetFilesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var set = new HashSet(); + + foreach (var fs in _fs.InternalFileSystems) + { + await foreach (var file in fs.GetFilesAsync(FullName, cancellationToken).ConfigureAwait(false)) + { + if (file is NotFoundFile) + continue; + + if (set.Add(file.FullName)) + yield return new CompositeFile(_fs, file.FullName, file); + } + } + } + + /// + protected override async IAsyncEnumerable GetDirectoriesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var set = new HashSet(); + + foreach (var fs in _fs.InternalFileSystems) + { + await foreach (var directory in fs.GetDirectoriesAsync(FullName, cancellationToken).ConfigureAwait(false)) + { + if (directory is NotFoundDirectory) continue; - yield return node is VirtualFile file - ? new CompositeFile(_fs, node.FullName, file) - : new CompositeDirectory(_fs, node.FullName); + if (set.Add(directory.FullName)) + yield return new CompositeDirectory(_fs, directory.FullName); } } } diff --git a/src/Ramstack.FileSystem.Composite/CompositeFileSystem.Helpers.cs b/src/Ramstack.FileSystem.Composite/CompositeFileSystem.Helpers.cs index 9b4bc16..2bc0a52 100644 --- a/src/Ramstack.FileSystem.Composite/CompositeFileSystem.Helpers.cs +++ b/src/Ramstack.FileSystem.Composite/CompositeFileSystem.Helpers.cs @@ -1,4 +1,4 @@ -using Ramstack.FileSystem.Null; +using Ramstack.FileSystem.Null; namespace Ramstack.FileSystem.Composite; diff --git a/src/Ramstack.FileSystem.Composite/README.md b/src/Ramstack.FileSystem.Composite/README.md index 24d7f06..371cfdf 100644 --- a/src/Ramstack.FileSystem.Composite/README.md +++ b/src/Ramstack.FileSystem.Composite/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Composite +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Composite.svg)](https://nuget.org/packages/Ramstack.FileSystem.Composite) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` that combines multiple file systems into a single composite file system. @@ -40,9 +42,9 @@ await foreach (VirtualFile file in fs.GetFilesAsync("/")) ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Globbing/README.md b/src/Ramstack.FileSystem.Globbing/README.md index e161265..9d30996 100644 --- a/src/Ramstack.FileSystem.Globbing/README.md +++ b/src/Ramstack.FileSystem.Globbing/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Globbing +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Globbing.svg)](https://nuget.org/packages/Ramstack.FileSystem.Globbing) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` that applies glob-based filtering rules to determine which files and directories of the underlying file system to include or exclude. @@ -45,9 +47,9 @@ await foreach (VirtualFile file in fs.GetFilesAsync("/")) ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Google/GcsDirectory.cs b/src/Ramstack.FileSystem.Google/GcsDirectory.cs index 80e8866..a66c20f 100644 --- a/src/Ramstack.FileSystem.Google/GcsDirectory.cs +++ b/src/Ramstack.FileSystem.Google/GcsDirectory.cs @@ -1,4 +1,5 @@ -using System.Net; +using System.Diagnostics; +using System.Net; using System.Runtime.CompilerServices; using Google; @@ -306,6 +307,9 @@ private static bool IsMatched(scoped ReadOnlySpan path, string[] patterns, /// GetPrefix("/sub/folder") // returns "sub/folder/" /// /// - private static string GetPrefix(string path) => - path == "/" ? "" : $"{path[1..]}/"; + private static string GetPrefix(string path) + { + Debug.Assert(VirtualPath.IsNormalized(path)); + return path == "/" ? "" : $"{path[1..]}/"; + } } diff --git a/src/Ramstack.FileSystem.Google/GcsFile.cs b/src/Ramstack.FileSystem.Google/GcsFile.cs index 3c5ce08..fd69cdd 100644 --- a/src/Ramstack.FileSystem.Google/GcsFile.cs +++ b/src/Ramstack.FileSystem.Google/GcsFile.cs @@ -1,4 +1,4 @@ -using System.Net; +using System.Net; using Google; using Google.Cloud.Storage.V1; diff --git a/src/Ramstack.FileSystem.Google/GcsWriteStream.cs b/src/Ramstack.FileSystem.Google/GcsWriteStream.cs index 2d4d064..99c2591 100644 --- a/src/Ramstack.FileSystem.Google/GcsWriteStream.cs +++ b/src/Ramstack.FileSystem.Google/GcsWriteStream.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; namespace Ramstack.FileSystem.Google; @@ -56,8 +56,11 @@ public GcsWriteStream(GoogleFileSystem fs, string objectName) } /// - public override int Read(byte[] array, int offset, int count) => - _stream.Read(array, offset, count); + public override int Read(byte[] array, int offset, int count) + { + Error_NotSupported(); + return 0; + } /// public override int Read(Span buffer) @@ -80,6 +83,8 @@ public override void Write(ReadOnlySpan buffer) catch { _disposed = true; + _stream.Close(); + throw; } } @@ -89,15 +94,17 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask(); /// - public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) { try { - return _stream.WriteAsync(buffer, cancellationToken); + await _stream.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); } catch { _disposed = true; + _stream.Close(); + throw; } } @@ -114,12 +121,13 @@ public override void SetLength(long value) => Error_NotSupported(); /// - public override void Flush() => - _stream.Flush(); + public override void Flush() + { + } /// public override Task FlushAsync(CancellationToken cancellationToken) => - _stream.FlushAsync(cancellationToken); + Task.CompletedTask; /// protected override void Dispose(bool disposing) diff --git a/src/Ramstack.FileSystem.Google/GoogleFileSystem.cs b/src/Ramstack.FileSystem.Google/GoogleFileSystem.cs index 6530197..bb69257 100644 --- a/src/Ramstack.FileSystem.Google/GoogleFileSystem.cs +++ b/src/Ramstack.FileSystem.Google/GoogleFileSystem.cs @@ -1,4 +1,4 @@ -using Google; +using Google; using Google.Cloud.Storage.V1; using Google.Apis.Auth.OAuth2; diff --git a/src/Ramstack.FileSystem.Google/README.md b/src/Ramstack.FileSystem.Google/README.md index c6bd6a0..a7aff67 100644 --- a/src/Ramstack.FileSystem.Google/README.md +++ b/src/Ramstack.FileSystem.Google/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Google +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Google.svg)](https://nuget.org/packages/Ramstack.FileSystem.Google) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` using Google Cloud Storage. @@ -52,9 +54,9 @@ GoogleFileSystem fs = new GoogleFileSystem(client, bucketName: "my-bucket") ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Physical/PhysicalFileSystem.cs b/src/Ramstack.FileSystem.Physical/PhysicalFileSystem.cs index 0bf02c0..cadf419 100644 --- a/src/Ramstack.FileSystem.Physical/PhysicalFileSystem.cs +++ b/src/Ramstack.FileSystem.Physical/PhysicalFileSystem.cs @@ -63,7 +63,7 @@ void IDisposable.Dispose() /// private string GetPhysicalPath(string path) { - Debug.Assert(path == VirtualPath.Normalize(path)); + Debug.Assert(VirtualPath.IsNormalized(path)); return Path.Join(_root, path); } } diff --git a/src/Ramstack.FileSystem.Physical/README.md b/src/Ramstack.FileSystem.Physical/README.md index 3c4f4c7..4f56c65 100644 --- a/src/Ramstack.FileSystem.Physical/README.md +++ b/src/Ramstack.FileSystem.Physical/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Physical +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Physical.svg)](https://nuget.org/packages/Ramstack.FileSystem.Physical) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` based on local file system. @@ -47,9 +49,9 @@ PhysicalFileSystem fs = new PhysicalFileSystem(@"C:\path\to\directory") ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Prefixed/README.md b/src/Ramstack.FileSystem.Prefixed/README.md index 64fbcf5..cdb5b11 100644 --- a/src/Ramstack.FileSystem.Prefixed/README.md +++ b/src/Ramstack.FileSystem.Prefixed/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Prefixed +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Prefixed.svg)](https://nuget.org/packages/Ramstack.FileSystem.Prefixed) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` that adds a specified prefix to the file paths within the underlying file system. @@ -44,9 +46,9 @@ await foreach (VirtualFile file in fs.GetFilesAsync("/public/assets")) ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Readonly/README.md b/src/Ramstack.FileSystem.Readonly/README.md index 8da8a9b..475a6bc 100644 --- a/src/Ramstack.FileSystem.Readonly/README.md +++ b/src/Ramstack.FileSystem.Readonly/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Readonly +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Readonly.svg)](https://nuget.org/packages/Ramstack.FileSystem.Readonly) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` that wraps the underlying file system, preventing any destructive operations. @@ -41,9 +43,9 @@ await fs.DeleteFileAsync("/hello.txt"); ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Sub/README.md b/src/Ramstack.FileSystem.Sub/README.md index 079af32..35cbff2 100644 --- a/src/Ramstack.FileSystem.Sub/README.md +++ b/src/Ramstack.FileSystem.Sub/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Sub +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Sub.svg)](https://nuget.org/packages/Ramstack.FileSystem.Sub) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` that wraps an underlying file system for managing files under a specific subpath. @@ -41,9 +43,9 @@ await foreach (VirtualFile file in fs.GetFilesAsync("/")) ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/src/Ramstack.FileSystem.Zip/README.md b/src/Ramstack.FileSystem.Zip/README.md index fcbb6d5..44c96fd 100644 --- a/src/Ramstack.FileSystem.Zip/README.md +++ b/src/Ramstack.FileSystem.Zip/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Zip +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Zip.svg)](https://nuget.org/packages/Ramstack.FileSystem.Zip) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides an implementation of `Ramstack.FileSystem` based on ZIP archives. @@ -40,9 +42,9 @@ await foreach (VirtualFile file in fs.GetFilesAsync("/")) ## Supported versions -| | Version | -|------|------------| -| .NET | 6, 7, 8, 9 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions diff --git a/tests/Ramstack.FileSystem.Abstractions.Tests/VirtualPathTests.cs b/tests/Ramstack.FileSystem.Abstractions.Tests/VirtualPathTests.cs index b02ea29..8ccae68 100644 --- a/tests/Ramstack.FileSystem.Abstractions.Tests/VirtualPathTests.cs +++ b/tests/Ramstack.FileSystem.Abstractions.Tests/VirtualPathTests.cs @@ -116,9 +116,33 @@ public bool IsNormalized(string path) => public void Normalize(string path, string expected) { foreach (var p in GetPathVariations(path)) - Assert.That(VirtualPath.Normalize(p),Is.EqualTo(expected)); + Assert.That(VirtualPath.Normalize(p), Is.EqualTo(expected)); } + [TestCase("/", "/", "//")] + [TestCase("", "/", "/")] + [TestCase("/", "", "/")] + [TestCase("a", "b", "a/b")] + [TestCase("a/", "b", "a/b")] + [TestCase("a", "/b", "a/b")] + [TestCase("a/", "/b", "a//b")] + [TestCase("a", "", "a")] + [TestCase("", "a", "a")] + [TestCase("/a", "/b", "/a/b")] + [TestCase("a", "/b", "a/b")] + [TestCase("/a/", "/b/", "/a//b/")] + + [TestCase("a\\", "b", "a\\b")] + [TestCase("a", "\\b", "a\\b")] + [TestCase("a\\", "\\b", "a\\\\b")] + [TestCase("a\\", "/b", "a\\/b")] + [TestCase("a/", "\\b", "a/\\b")] + + [TestCase("a/", "/", "a//")] + [TestCase("/", "/a", "//a")] + public void Join(string a, string b, string expected) => + Assert.That(VirtualPath.Join(a, b), Is.EqualTo(expected)); + private static string[] GetPathVariations(string path) => [path, path.Replace('/', '\\')]; } diff --git a/tests/Ramstack.FileSystem.Amazon.Tests/ReadonlyAmazonFileSystemTests.cs b/tests/Ramstack.FileSystem.Amazon.Tests/ReadonlyAmazonFileSystemTests.cs index efade71..32e2b58 100644 --- a/tests/Ramstack.FileSystem.Amazon.Tests/ReadonlyAmazonFileSystemTests.cs +++ b/tests/Ramstack.FileSystem.Amazon.Tests/ReadonlyAmazonFileSystemTests.cs @@ -1,4 +1,4 @@ -using Amazon; +using Amazon; using Amazon.Runtime; using Amazon.S3; diff --git a/tests/Ramstack.FileSystem.Amazon.Tests/WritableAmazonFileSystemTests.cs b/tests/Ramstack.FileSystem.Amazon.Tests/WritableAmazonFileSystemTests.cs index f009143..6d4d567 100644 --- a/tests/Ramstack.FileSystem.Amazon.Tests/WritableAmazonFileSystemTests.cs +++ b/tests/Ramstack.FileSystem.Amazon.Tests/WritableAmazonFileSystemTests.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Amazon; using Amazon.Runtime; diff --git a/tests/Ramstack.FileSystem.Composite.Tests/CompositionHelperTests.cs b/tests/Ramstack.FileSystem.Composite.Tests/CompositionHelperTests.cs index b883e4e..905cb75 100644 --- a/tests/Ramstack.FileSystem.Composite.Tests/CompositionHelperTests.cs +++ b/tests/Ramstack.FileSystem.Composite.Tests/CompositionHelperTests.cs @@ -1,4 +1,4 @@ -using Ramstack.FileSystem.Null; +using Ramstack.FileSystem.Null; namespace Ramstack.FileSystem.Composite; diff --git a/tests/Ramstack.FileSystem.Composite.Tests/TestFileSystem.cs b/tests/Ramstack.FileSystem.Composite.Tests/TestFileSystem.cs index 0d9ca98..5ff2180 100644 --- a/tests/Ramstack.FileSystem.Composite.Tests/TestFileSystem.cs +++ b/tests/Ramstack.FileSystem.Composite.Tests/TestFileSystem.cs @@ -1,4 +1,4 @@ -namespace Ramstack.FileSystem.Composite; +namespace Ramstack.FileSystem.Composite; internal sealed class TestFileSystem : IVirtualFileSystem { diff --git a/tests/Ramstack.FileSystem.Google.Tests/ReadonlyGoogleFileSystemTests.cs b/tests/Ramstack.FileSystem.Google.Tests/ReadonlyGoogleFileSystemTests.cs index fc6c802..261e911 100644 --- a/tests/Ramstack.FileSystem.Google.Tests/ReadonlyGoogleFileSystemTests.cs +++ b/tests/Ramstack.FileSystem.Google.Tests/ReadonlyGoogleFileSystemTests.cs @@ -1,4 +1,4 @@ -using Google.Apis.Auth.OAuth2; +using Google.Apis.Auth.OAuth2; using Google.Cloud.Storage.V1; using Ramstack.FileSystem.Specification.Tests; diff --git a/tests/Ramstack.FileSystem.Google.Tests/WritableGoogleFileSystemTests.cs b/tests/Ramstack.FileSystem.Google.Tests/WritableGoogleFileSystemTests.cs index 11f08cf..00280ea 100644 --- a/tests/Ramstack.FileSystem.Google.Tests/WritableGoogleFileSystemTests.cs +++ b/tests/Ramstack.FileSystem.Google.Tests/WritableGoogleFileSystemTests.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using Google.Apis.Auth.OAuth2; using Google.Cloud.Storage.V1; diff --git a/tests/Ramstack.FileSystem.Prefixed.Tests/PrefixedFileSystemTests.cs b/tests/Ramstack.FileSystem.Prefixed.Tests/PrefixedFileSystemTests.cs index 3b240b7..d60adb0 100644 --- a/tests/Ramstack.FileSystem.Prefixed.Tests/PrefixedFileSystemTests.cs +++ b/tests/Ramstack.FileSystem.Prefixed.Tests/PrefixedFileSystemTests.cs @@ -27,7 +27,7 @@ await fs.FileExistsAsync("/ea5fd219.txt"), public async Task Directory_Delete_ArtificialDirectory_ThrowsException() { using var storage = new TempFileStorage(); - using var fs = new PrefixedFileSystem("/bin/apps/myapp1", new PhysicalFileSystem(storage.Root));; + using var fs = new PrefixedFileSystem("/bin/apps/myapp1", new PhysicalFileSystem(storage.Root)); await Assert.ThatAsync( async () => await fs.DeleteDirectoryAsync("/"), diff --git a/tests/Ramstack.FileSystem.Specification.Tests/README.md b/tests/Ramstack.FileSystem.Specification.Tests/README.md index c0869c6..f84b3c8 100644 --- a/tests/Ramstack.FileSystem.Specification.Tests/README.md +++ b/tests/Ramstack.FileSystem.Specification.Tests/README.md @@ -1,4 +1,6 @@ # Ramstack.FileSystem.Specification.Tests +[![NuGet](https://img.shields.io/nuget/v/Ramstack.FileSystem.Specification.Tests.svg)](https://nuget.org/packages/Ramstack.FileSystem.Specification.Tests) +[![MIT](https://img.shields.io/github/license/rameel/ramstack.filesystem)](https://github.com/rameel/ramstack.filesystem/blob/main/LICENSE) Provides a suite of `NUnit` tests to validate specifications for `Ramstack.FileSystem`. @@ -43,9 +45,9 @@ public class PhysicalFileSystemSpecificationTests : VirtualFileSystemSpecificati ## Supported Versions -| | Version | -|------|---------| -| .NET | 6, 7, 8 | +| | Version | +|------|----------------| +| .NET | 6, 7, 8, 9, 10 | ## Contributions