FileSystem 0.5.1

A FileSystem abstraction, allowing decoration and testing of the physical file system.

Install-Package FileSystem -Version 0.5.1
dotnet add package FileSystem --version 0.5.1
<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
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: FileSystem, 0.5.1"
#r directive can be used in F# Interactive, C# scripting and .NET Interactive. 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
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

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

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

  • .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 History

Version Downloads Last updated
0.5.1 6,206 7/12/2018
0.5.0 4,712 7/10/2018
0.4.0 5,155 10/19/2017
0.3.0 4,955 9/25/2017
0.2.2 4,730 4/28/2017
0.2.1 4,719 4/26/2017
0.2.0 4,674 4/15/2017
0.1.0 4,718 4/7/2017