Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ if (!fs.IsReadOnly)

## Supported Versions

| | Version |
|------|------------|
| .NET | 6, 7, 8, 9 |
| | Version |
|------|----------------|
| .NET | 6, 7, 8, 9, 10 |

## Contributions

Expand Down
10 changes: 6 additions & 4 deletions src/Ramstack.FileSystem.Abstractions/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -158,9 +160,9 @@ if (!fs.IsReadOnly)

## Supported Versions

| | Version |
|------|------------|
| .NET | 6, 7, 8, 9 |
| | Version |
|------|----------------|
| .NET | 6, 7, 8, 9, 10 |

## Contributions

Expand Down
10 changes: 10 additions & 0 deletions src/Ramstack.FileSystem.Abstractions/VirtualPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace Ramstack.FileSystem;
/// For compatibility across different implementations of <see cref="IVirtualFileSystem"/>
/// and operating systems, directory separators are unified to use both
/// backslashes and forward slashes ("/" and "\").
/// </para>
/// <para>
/// <strong>This approach will be reviewed once a better solution is found.</strong>
/// </para>
/// <para>
Expand Down Expand Up @@ -221,6 +223,14 @@ public static bool IsNormalized(ReadOnlySpan<char> path)
/// </remarks>
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;

Expand Down
8 changes: 5 additions & 3 deletions src/Ramstack.FileSystem.Adapters/README.md
Original file line number Diff line number Diff line change
@@ -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`.

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/Ramstack.FileSystem.Amazon/AccessControl.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Ramstack.FileSystem.Amazon;
namespace Ramstack.FileSystem.Amazon;

/// <summary>
/// An enumeration of all possible CannedACLs that can be used
Expand Down
8 changes: 5 additions & 3 deletions src/Ramstack.FileSystem.Amazon/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -57,9 +59,9 @@ AmazonS3FileSystem fs = new AmazonS3FileSystem(

## Supported versions

| | Version |
|------|------------|
| .NET | 6, 7, 8, 9 |
| | Version |
|------|----------------|
| .NET | 6, 7, 8, 9, 10 |

## Contributions

Expand Down
25 changes: 15 additions & 10 deletions src/Ramstack.FileSystem.Amazon/S3Directory.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;

using Amazon.S3.Model;
Expand Down Expand Up @@ -180,8 +181,8 @@ protected override async IAsyncEnumerable<VirtualNode> 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))
{
Expand All @@ -193,13 +194,13 @@ protected override async IAsyncEnumerable<VirtualNode> 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;
Expand Down Expand Up @@ -276,7 +277,7 @@ protected override async IAsyncEnumerable<VirtualDirectory> 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))
{
Expand All @@ -288,7 +289,7 @@ protected override async IAsyncEnumerable<VirtualDirectory> 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);
}
Expand All @@ -303,10 +304,11 @@ protected override async IAsyncEnumerable<VirtualDirectory> GetDirectoriesCoreAs
/// Creates a <see cref="S3File"/> instance based on the specified object.
/// </summary>
/// <param name="obj">The <see cref="S3Object"/> representing the file.</param>
/// <param name="normalizedName">The normalized name of the object.</param>
/// <returns>
/// A new <see cref="S3File"/> instance representing the file.
/// </returns>
private S3File CreateVirtualFile(S3Object obj)
private S3File CreateVirtualFile(S3Object obj, string? normalizedName = null)
{
var properties = VirtualNodeProperties
.CreateFileProperties(
Expand All @@ -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);
}

Expand Down Expand Up @@ -363,6 +365,9 @@ private static bool IsMatched(scoped ReadOnlySpan<char> path, string[] patterns,
/// GetPrefix("/sub/folder") // returns "sub/folder/"
/// </code>
/// </example>
private static string GetPrefix(string path) =>
path == "/" ? "" : $"{path[1..]}/";
private static string GetPrefix(string path)
{
Debug.Assert(VirtualPath.IsNormalized(path));
return path == "/" ? "" : $"{path[1..]}/";
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Ramstack.FileSystem.Amazon.Utilities;
namespace Ramstack.FileSystem.Amazon.Utilities;

/// <summary>
/// Provides a mechanism to temporarily set the <see cref="SynchronizationContext"/> to <see langword="null"/>.
Expand Down
25 changes: 15 additions & 10 deletions src/Ramstack.FileSystem.Azure/AzureDirectory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;

using Azure;
Expand Down Expand Up @@ -168,8 +169,8 @@ protected override async IAsyncEnumerable<VirtualNode> 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))
{
Expand All @@ -181,13 +182,13 @@ protected override async IAsyncEnumerable<VirtualNode> 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);
}
}
}
Expand Down Expand Up @@ -246,7 +247,7 @@ protected override async IAsyncEnumerable<VirtualDirectory> 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))
{
Expand All @@ -258,7 +259,7 @@ protected override async IAsyncEnumerable<VirtualDirectory> 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);
}
Expand All @@ -270,10 +271,11 @@ protected override async IAsyncEnumerable<VirtualDirectory> GetDirectoriesCoreAs
/// Creates a <see cref="AzureFile"/> instance based on the specified blob item.
/// </summary>
/// <param name="blob">The <see cref="BlobItem"/> representing the file.</param>
/// <param name="normalizedPath">The normalized name of the blob.</param>
/// <returns>
/// A new <see cref="AzureFile"/> instance representing the file.
/// </returns>
private AzureFile CreateVirtualFile(BlobItem blob)
private AzureFile CreateVirtualFile(BlobItem blob, string? normalizedPath = null)
{
var info = blob.Properties;
var properties = VirtualNodeProperties.CreateFileProperties(
Expand All @@ -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);
}

Expand Down Expand Up @@ -329,6 +331,9 @@ private static bool IsMatched(scoped ReadOnlySpan<char> path, string[] patterns,
/// GetPrefix("/sub/folder") // returns "sub/folder/"
/// </code>
/// </example>
private static string GetPrefix(string path) =>
path == "/" ? "" : $"{path[1..]}/";
private static string GetPrefix(string path)
{
Debug.Assert(VirtualPath.IsNormalized(path));
return path == "/" ? "" : $"{path[1..]}/";
}
}
2 changes: 1 addition & 1 deletion src/Ramstack.FileSystem.Azure/AzureFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ void IDisposable.Dispose()
/// </returns>
internal BlobClient CreateBlobClient(string path)
{
Debug.Assert(path == VirtualPath.Normalize(path));
Debug.Assert(VirtualPath.IsNormalized(path));
return AzureClient.GetBlobClient(path[1..]);
}
}
8 changes: 5 additions & 3 deletions src/Ramstack.FileSystem.Azure/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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

Expand Down
42 changes: 38 additions & 4 deletions src/Ramstack.FileSystem.Composite/CompositeDirectory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,46 @@ protected override async IAsyncEnumerable<VirtualNode> 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);
}
}
}

/// <inheritdoc />
protected override async IAsyncEnumerable<VirtualFile> GetFilesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var set = new HashSet<string>();

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);
}
}
}

/// <inheritdoc />
protected override async IAsyncEnumerable<VirtualDirectory> GetDirectoriesCoreAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
var set = new HashSet<string>();

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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Ramstack.FileSystem.Null;
using Ramstack.FileSystem.Null;

namespace Ramstack.FileSystem.Composite;

Expand Down
8 changes: 5 additions & 3 deletions src/Ramstack.FileSystem.Composite/README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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

Expand Down
Loading
Loading