FileSystem 0.5.1

dotnet add package FileSystem --version 0.5.1                
NuGet\Install-Package FileSystem -Version 0.5.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FileSystem" Version="0.5.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add FileSystem --version 0.5.1                
#r "nuget: FileSystem, 0.5.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install FileSystem as a Cake Addin
#addin nuget:?package=FileSystem&version=0.5.1

// Install FileSystem as a Cake Tool
#tool nuget:?package=FileSystem&version=0.5.1                

FileSystem

An async FileSystem abstraction, with decoration and in-memory/virtual support.

Build status NuGet

Installation

This package is available on NuGet.

PM> install-package FileSystem

Usage

public class Configuration
{
    private readonly IFileSystem _fileSystem;

    public Configuration(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    public async Task Load()
    {
        using (var stream = await _fileSystem.ReadFile("config.json"))
        {
            //...
        }
    }

    public async Task Save()
    {
        await _fileSystem.WriteFile("config.json", async stream => {
            await stream.Write(/* ... */);
        });
    }
}

//usage:
var config = new Configuration(new PhysicalFileSystem());

Logging

As I didn't want to take a dependency on any particular logging library, there is no out of the box implementation. However, implementing your own only takes a few lines of code, making use of the EventingFileSystem class. For example, logging everything as Debug with Serilog:

public class LoggingFileSystem : EventingFileSystem
{
    public LoggingFileSystem(IFileSystem inner) : base(inner)
    {
        HandleEvent = message =>
        {
            Log.Debug($"{message}: {{@event}}", message);
            return Task.CompletedTask;
        };
    }
}

Each event emitted by the EventingFileSystem has a reasonable .ToString implementation, so you can just write Console.WriteLine(message.ToString()) if you wish.

Decoration

For ease of implementing, FileSystem supplies a FileSystemDecorator class, which implements all IFileSystem methods as virtual calls to an inner IFileSystem.

For example, an encrypting filesystem could be implemented by just overriding a few methods:

public class EncryptingFileSystem : FileSystemDecorator
{
    private readonly ICrypto _crypto;

    public EncryptingFileSystem(IFileSystem inner, ICrypto crypto) : base(inner)
    {
        _crypto = crypto;
    }

    public override  async Task<Stream> ReadFile(string path)
    {
        return await _crypto.DecryptStream(await base.ReadFile(path));
    }

    public override async Task WriteFile(string path, Func<Stream, Task> write)
    {
        await base.WriteFile(path, async stream =>
        {
            using (var encrypted = await _crypto.Encrypt(async cryptoStream => await write(cryptoStream)))
            {
                await encrypted.CopyToAsync(stream);
            }
        });
    }

    public override Task AppendFile(string path, Func<Stream, Task> write)
    {
        throw new NotSupportedException("You cannot append to an encrypted file.  Try reading, and the writing the whole file.");
    }
}

Testing

The easiest way of testing code using an IFileSystem dependency is to use the InMemoryFileSystem, which will behave the same as the physical file system.

var fileSystem = new InMemoryFileSystem();

var sut = new TestClass(fileSystem);
sut.Execute();

fileSystem
    .ReadLines("./the/file.txt")
    .ShouldBe(new[] { "first", "second", "third" });

Alternatly, if you want to assert on something written to a stream, e.g. on a .AppendFile() call, you can do it manually (this example using NSubstitute):

var ms = new MemoryStream();
var fileSystem = Substitute.For<IFileSystem>();
fileSystem
    .AppendFile("wat", Arg.Do<Func<Stream, Task>>(func => func(ms).Wait()))
    .Returns(Task.CompletedTask);

fileSystem.AppendFile("wat", async stream => {
    await stream.WriteAsync(new byte[] { 1, 2, 3 }, 0, 3);
});

ms.ToArray().ShouldBe(new byte[] { 1, 2, 3});

Or if you want to capture many streams, you can use the provided StreamCapture class:

var streams = new StreamCapture();
var fileSystem = Substitute.For<IFileSystem>();
fileSystem
    .AppendFile("wat", Arg.Do<Func<Stream, Task>>(streams.Capture))
    .Returns(Task.CompletedTask);

fileSystem.AppendFile("wat", async stream => {
    await stream.WriteAsync(new byte[] { 1, 2, 3 }, 0, 3);
});

streams.Last.ToArray().ShouldBe(new byte[] { 1, 2, 3});

To do

  • Caching FileSystem
    • Read caching I guess
    • pluggable caching strategies
  • Commitable FileSystem (call .Commit() to flush writes to disk.)
  • Case(In)Sensitive FileSystem?
    • Not sure how this would work
    • ReadFile would ListFiles first, then find the right mapping perhaps?
  • S3FileSystem
    • Separate package I guess
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net451 is compatible.  net452 was computed.  net46 was computed.  net461 is compatible.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 4.5.1

    • No dependencies.
  • .NETFramework 4.6.1

    • No dependencies.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.5.1 7,600 7/12/2018
0.5.0 5,158 7/10/2018
0.4.0 5,589 10/19/2017
0.3.0 5,342 9/25/2017
0.2.2 5,126 4/28/2017
0.2.1 5,123 4/26/2017
0.2.0 5,072 4/15/2017
0.1.0 5,149 4/7/2017