All Projects → tagcode → Lexical.FileSystem

tagcode / Lexical.FileSystem

Licence: Apache-2.0 license
Virtual IFileSystem interfaces, and implementations.

Programming Languages

C#
18002 projects

Projects that are alternatives of or similar to Lexical.FileSystem

Enchilada
Enchilada is a filesystem abstraction layer written in C#
Stars: ✭ 29 (+20.83%)
Mutual labels:  filesystem, abstraction
Go Billy
The missing interface filesystem abstraction for Go
Stars: ✭ 184 (+666.67%)
Mutual labels:  filesystem, abstraction
SimpleOS
Operating System Coded in Assembly and C
Stars: ✭ 72 (+200%)
Mutual labels:  filesystem, vfs
Dazinator.Extensions.FileProviders
No description or website provided.
Stars: ✭ 34 (+41.67%)
Mutual labels:  filesystem, fileprovider
fu
Unix's Find, Unleashed.
Stars: ✭ 32 (+33.33%)
Mutual labels:  filesystem
athena-sqlite
A SQLite driver for S3 and Amazon Athena 😳
Stars: ✭ 82 (+241.67%)
Mutual labels:  vfs
litefilesystem.js
Library with client (js) and serverside (php) to have a filesystem with previews, quotas, metadata, and multiple users with privileges.
Stars: ✭ 131 (+445.83%)
Mutual labels:  filesystem
rxnode
Rxnode - a small and fast wrapper around the nodejs API using RxJS.
Stars: ✭ 24 (+0%)
Mutual labels:  filesystem
StudApp
Stud.IP to Go
Stars: ✭ 16 (-33.33%)
Mutual labels:  fileprovider
fusell-seed
FUSE (the low-level interface) file system boilerplate 📂 🔌 💾
Stars: ✭ 13 (-45.83%)
Mutual labels:  filesystem
tabfs-specs
Specifications for the tabfs filesystem (osdev) | Mirror of https://codeark.it/Chalk-OS/tabfs-specs
Stars: ✭ 15 (-37.5%)
Mutual labels:  filesystem
ucz-dfs
A distributed file system written in Rust.
Stars: ✭ 25 (+4.17%)
Mutual labels:  filesystem
ltfs
Reference implementation of the LTFS format Spec for stand alone tape drive
Stars: ✭ 130 (+441.67%)
Mutual labels:  filesystem
JSON-API-Client
Abstract client-side php implementation of the json api specification (jsonapi.org)
Stars: ✭ 17 (-29.17%)
Mutual labels:  abstraction
xplr
A hackable, minimal, fast TUI file explorer
Stars: ✭ 2,271 (+9362.5%)
Mutual labels:  filesystem
FileSystemTest
FileSystem Api Test For Windows
Stars: ✭ 17 (-29.17%)
Mutual labels:  filesystem
fsify
Convert an array of objects into a persistent or temporary directory structure.
Stars: ✭ 24 (+0%)
Mutual labels:  filesystem
SSFS
Simple & Stupid Filesystem (Using FUSE)
Stars: ✭ 64 (+166.67%)
Mutual labels:  filesystem
tinyos
An UNIX-like toy operating system runs on x86 CPU
Stars: ✭ 47 (+95.83%)
Mutual labels:  filesystem
luufs
Lazy man's, user-mode union file system
Stars: ✭ 28 (+16.67%)
Mutual labels:  filesystem

Introduction

Lexical.FileSystem is a virtual filesystem class libraries for .NET.

NuGet Packages:

Please leave a comment or feedback on github, or kindle a star if you like the library.

Contents:

FileSystem

new FileSystem(path) creates an instance of filesystem at path.

IFileSystem fs = new FileSystem(@"C:\Temp\");

.Browse(path) returns a snapshot of directory contents.

IDirectoryContent contents = fs.Browse("C:/Windows/");

IDirectoryContent is enumerable IEnumerable<IEntry>.

foreach (IEntry entry in fs.Browse("C:/Windows/"))
    Console.WriteLine(entry.Path);

.AssertExists() asserts that directory exists. It throws DirectoryNotFound if not found.

foreach (var entry in fs.Browse("C:/Windows/").AssertExists())
    Console.WriteLine(entry.Path);

.GetEntry(path) reads a single file or directory entry. Returns null if entry is not found.

IEntry e = FileSystem.OS.GetEntry("C:/Windows/win.ini");
Console.WriteLine(e.Path);

.AssertExists() asserts that null is not returned. Throws FileNotFoundException if entry was not found.

IEntry e = FileSystem.OS.GetEntry("C:/Windows/win.ini").AssertExists();

Files can be opened for reading.

using (Stream s = fs.Open("file.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
{
    Console.WriteLine(s.Length);
}

And for for writing.

using (Stream s = fs.Open("somefile.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
{
    s.WriteByte(32);
}

Directories can be created.

fs.CreateDirectory("dir/");

Directories can be deleted.

fs.Delete("dir/", recurse: true);

Files and directories can be renamed and moved.

fs.CreateDirectory("dir/");
fs.Move("dir/", "new-name/");

And file attributes changed.

fs.SetFileAttribute("myfile", FileAttributes.ReadOnly);

Singleton

The singleton instance FileSystem.OS refers to a filesystem at the OS root.

IFileSystem fs = FileSystem.OS;

Extension method .VisitTree() visits filesystem. On root path "" FileSystem.OS returns drive letters.

foreach (var line in FileSystem.OS.VisitTree(depth: 2))
    Console.WriteLine(line);
""
├──"C:"
│  ├── "hiberfil.sys"
│  ├── "pagefile.sys"
│  ├── "swapfile.sys"
│  ├── "Documents and Settings"
│  ├── "Program Files"
│  ├── "Program Files (x86)"
│  ├── "System Volume Information"
│  ├── "Users"
│  └── "Windows10"
└──"D:"

[!NOTE] The separator character is always forward slash '/'. For example "C:/Windows/win.ini".

Extension method .PrintTo() appends the visited filesystem to text output.

FileSystem.OS.PrintTo(Console.Out, depth: 2, format: PrintTree.Format.DefaultPath);
├── C:/
│  ├── C:/hiberfil.sys
│  ├── C:/pagefile.sys
│  ├── C:/swapfile.sys
│  ├── C:/Documents and Settings/
│  ├── C:/Program Files/
│  ├── C:/Program Files (x86)/
│  ├── C:/System Volume Information/
│  ├── C:/Users/
│  └── C:/Windows/
└── D:/

On linux FileSystem.OS returns slash '/' root.

FileSystem.OS.PrintTo(Console.Out, depth: 3, format: PrintTree.Format.DefaultPath);
└──/
   ├──/bin/
   ├──/boot/
   ├──/dev/
   ├──/etc/
   ├──/lib/
   ├──/media/
   ├──/mnt/
   ├──/root/
   ├──/sys/
   ├──/usr/
   └──/var/

FileSystem.Application refers to the application's root directory.

FileSystem.Application.PrintTo(Console.Out);
""
├── "Application.dll"
├── "Application.runtimeconfig.json"
├── "Lexical.FileSystem.Abstractions.dll"
└── "Lexical.FileSystem.dll"

FileSystem.Temp refers to the running user's temp directory.

FileSystem.Temp.PrintTo(Console.Out, depth: 1);
""
├── "dmk55ohj.jjp"
├── "wrz4cms5.r2f"
└── "18e1904137f065db88dfbd23609eb877"

Singleton instances:

Name Description On Windows On Linux
FileSystem.OS Operating system root. "" ""
FileSystem.Application Running application's base directory.
FileSystem.UserProfile The user's profile folder. "C:\Users\<user>" "/home/<user>"
FileSystem.MyDocuments The My Documents folder. "C:\Users\<user>\Documents" "/home/<user>"
FileSystem.Personal A common repository for documents. "C:\Users\<user>\Documents" "/home/<user>"
FileSystem.Temp Running user's temp directory. "C:\Users\<user>\AppData\Local\Temp" "/tmp
FileSystem.Config User's cloud-sync program configuration (roaming data). "C:\Users\<user>\AppData\Roaming" "/home/<user>/.config"
FileSystem.Data User's local program data. "C:\Users\<user>\AppData\Local" "/home/<user>/.local/share"
FileSystem.ProgramData Program data that is shared with every user. "C:\ProgramData" "/usr/share"
FileSystem.Desktop User's desktop. "C:\Users\<user>\Desktop" "/home/user/Desktop"
FileSystem.MyPictures User's pictures. "C:\Users\<user>\Pictures" "/home/user/Pictures"
FileSystem.MyVideos User's videos. "C:\Users\<user>\Videos" "/home/user/Videos"
FileSystem.MyMusic User's music. "C:\Users\<user>\Music" "/home/user/Music"
FileSystem.Templates Templates. "C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Templates" "/home/user/Templates"

IFileEntry.PhysicalPath() returns physical path of file entry.

foreach(var line in FileSystem.Temp.VisitTree(depth:2))
    Console.WriteLine(line.Entry.PhysicalPath());

TreeVisitor prints physical path with PrintTree.Format.PhysicalPath flag.

FileSystem.Temp.PrintTo(
    output: Console.Out, 
    depth: 2, 
    format: PrintTree.Format.Default | PrintTree.Format.PhysicalPath);
"" [C:\Users\\user\\AppData\Local\Temp\]
├── "dmk55ohj.jjp" [C:\Users\\user\\AppData\Local\Temp\dmk55ohj.jjp]
├── "wrz4cms5.r2f" [C:\Users\\user\\AppData\Local\Temp\wrz4cms5.r2f]
└── "18e1904137f065db88dfbd23609eb877" [C:\Users\\user\\AppData\Local\Temp\18e1904137f065db88dfbd23609eb877]

Observing

Files and directories can be observed for changes.

IObserver<IEvent> observer = new Observer();
IFileSystemObserver handle = FileSystem.OS.Observe("C:/**", observer);

Observer can be used in a using scope.

using (var handle = FileSystem.Temp.Observe("*.dat", new PrintObserver()))
{
    FileSystem.Temp.CreateFile("file.dat", new byte[] { 32, 32, 32, 32 });
    FileSystem.Temp.Delete("file.dat");

    Thread.Sleep(1000);
}
class PrintObserver : IObserver<IEvent>
{
    public void OnCompleted() => Console.WriteLine("OnCompleted");
    public void OnError(Exception error) => Console.WriteLine(error);
    public void OnNext(IEvent @event) => Console.WriteLine(@event);
}
StartEvent(C:\Users\\<i>&lt;user&gt;</i>\\AppData\Local\Temp\, 23.10.2019 16.27.01 +00:00)
CreateEvent(C:\Users\\<i>&lt;user&gt;</i>\\AppData\Local\Temp\, 23.10.2019 16.27.01 +00:00, file.dat)
ChangeEvent(C:\Users\\<i>&lt;user&gt;</i>\\AppData\Local\Temp\, 23.10.2019 16.27.01 +00:00, file.dat)
DeleteEvent(C:\Users\\<i>&lt;user&gt;</i>\\AppData\Local\Temp\, 23.10.2019 16.27.01 +00:00, file.dat)
OnCompleted

Disposing

Disposable objects can be attached to be disposed along with FileSystem.

// Init
object obj = new ReaderWriterLockSlim();
IFileSystemDisposable fs = new FileSystem("").AddDisposable(obj);

// ... do work ...

// Dispose both
fs.Dispose();

Delegates can be attached to be executed at dispose of FileSystem.

IFileSystemDisposable fs = new FileSystem("")
    .AddDisposeAction(f => Console.WriteLine("Disposed"));

.BelateDispose() creates a handle that postpones dispose on .Dispose(). Actual dispose will proceed once .Dispose() is called and all belate handles are disposed. This can be used for passing the IFileSystem to a worker thread.

FileSystem fs = new FileSystem("");
fs.Browse("");

// Postpone dispose
IDisposable belateDisposeHandle = fs.BelateDispose();
// Start concurrent work
Task.Run(() =>
{
    // Do work
    Thread.Sleep(1000);
    fs.GetEntry("");
    // Release belate handle. Disposes here or below, depending which thread runs last.
    belateDisposeHandle.Dispose();
});

// Start dispose, but postpone it until belatehandle is disposed in another thread.
fs.Dispose();

VirtualFileSystem

new VirtualFileSystem() creates virtual filesystem. Other filesystems can be mounted as part of it.

IFileSystem vfs = new VirtualFileSystem();

.Mount(path, filesystem) assigns a filesystem to a mountpoint.

IFileSystem vfs = new VirtualFileSystem()
    .Mount("", FileSystem.OS);

File systems can be assigned to multiple points.

IFileSystem urls = new VirtualFileSystem()
    .Mount("tmp/", FileSystem.Temp)
    .Mount("ram/", MemoryFileSystem.Instance);

File system can be assigned as a child of an earlier assignment. Child assignment has higher evaluation priority than parent. In the following example, "/tmp/" is evaluated from MemoryFileSystem first, and then concatenated with potential directory "/tmp/" from the FileSystem.OS.

IFileSystem vfs = new VirtualFileSystem()
    .Mount("", FileSystem.OS)
    .Mount("/tmp/", new MemoryFileSystem());

vfs.Browse("/tmp/");

.Unmount(path) removes filesystem assignments.

IFileSystem vfs = new VirtualFileSystem();
vfs.Mount("/tmp/", FileSystem.Temp);
vfs.Unmount("/tmp/");

Previously assigned filesystem can be replaced.

IFileSystem vfs = new VirtualFileSystem();
vfs.Mount("/tmp/", FileSystem.Temp);
vfs.Mount("/tmp/", new MemoryFileSystem());

.Mount(path, params IFilesystem, IOption) assigns filesystem with mount option.

IFileSystem vfs = new VirtualFileSystem();
vfs.Mount("/app/", FileSystem.Application, Option.ReadOnly);

Option such as Option.SubPath().

IFileSystem vfs = new VirtualFileSystem();
string appDir = AppDomain.CurrentDomain.BaseDirectory.Replace('\\', '/');
vfs.Mount("/app/", FileSystem.OS, Option.SubPath(appDir));

.Mount(path, params IFilesystem[]) assigns multiple filesystems into one mountpoint.

IFileSystem vfs = new VirtualFileSystem();
IFileSystem overrides = new MemoryFileSystem();
overrides.CreateFile("important.dat", new byte[] { 12, 23, 45, 67, 89 });
vfs.Mount("/app/", overrides, FileSystem.Application);

.Mount(path, params (IFilesystem, IOption)[]) assigns multiple filesystems with mount options.

IFileSystem overrides = new MemoryFileSystem();
IFileSystem vfs = new VirtualFileSystem();

vfs.Mount("/app/", 
    (overrides, Option.ReadOnly), 
    (FileSystem.Application, Option.ReadOnly)
);

If virtual filesystem is assigned with null filesystem, then empty mountpoint is created. Mountpoint cannot be deleted with .Delete() method, only remounted or unmounted. null assignment doesn't have any interface capabilities, such as .Browse().

IFileSystem vfs = new VirtualFileSystem();
vfs.Mount("/tmp/", filesystem: null);
└──/
   └── /tmp/ NotSupportedException: Browse

Observing

Observer can be placed before and after mounting. If observer is placed before, then mounting will notify the observer with ICreateEvent event for all the added files.

IFileSystem vfs = new VirtualFileSystem();
vfs.Observe("**", new PrintObserver());

IFileSystem ram = new MemoryFileSystem();
ram.CreateDirectory("/dir/");
ram.CreateFile("/dir/file.txt", new byte[] { 32, 65, 66 });

vfs.Mount("", ram);
class PrintObserver : IObserver<IEvent>
{
    public void OnCompleted() => Console.WriteLine("OnCompleted");
    public void OnError(Exception error) => Console.WriteLine(error);
    public void OnNext(IEvent @event) => Console.WriteLine(@event);
}
StartEvent(VirtualFileSystem, 19.10.2019 11.34.08 +00:00)
MountEvent(VirtualFileSystem, 19.10.2019 11.34.08 +00:00, , MemoryFileSystem, )
CreateEvent(VirtualFileSystem, 19.10.2019 11.34.08 +00:00, /dir/file.txt)

Observer filter can be an intersection of the mounted filesystem's contents.

IFileSystem vfs = new VirtualFileSystem();
vfs.Observe("/dir/*.txt", new PrintObserver());

IFileSystem ram = new MemoryFileSystem();
ram.CreateDirectory("/dir/");
ram.CreateFile("/dir/file.txt", new byte[] { 32, 65, 66 });
ram.CreateFile("/dir/file.dat", new byte[] { 255, 255, 255 });

vfs.Mount("", ram);
StartEvent(VirtualFileSystem, 19.10.2019 11.34.23 +00:00)
CreateEvent(VirtualFileSystem, 19.10.2019 11.34.23 +00:00, /dir/file.txt)

.Unmount() dispatches events of unmounted files as if they were deleted.

vfs.Unmount("");
Delete(VirtualFileSystem, 19.10.2019 11.34.39 +00:00, /dir/file.txt)

If filesystem is mounted with Option.NoObserve, then the assigned filesystem cannot be observed, and it won't dispatch events of added files on mount.

vfs.Mount("", ram, Option.NoObserve);

Observer isn't closed by unmounting. It can be closed by disposing its handle.

IDisposable observerHandle = vfs.Observe("**", new PrintObserver());
observerHandle.Dispose();
OnCompleted

... or by disposing the virtual filesystem.

VirtualFileSystem vfs = new VirtualFileSystem();
IDisposable observerHandle = vfs.Observe("**", new PrintObserver());
vfs.Dispose();
OnCompleted

Singleton

VirtualFileSystem.Url is a singleton instance that has the following filesystems mounted as urls.

new VirtualFileSystem.NonDisposable()
    .Mount("file://", FileSystem.OS)                  // All files
    .Mount("tmp://", FileSystem.Temp)                 // Temp files
    .Mount("ram://", MemoryFileSystem.Instance)       // Application's internal ram drive
    .Mount("home://", FileSystem.Personal)            // User's home directory
    .Mount("document://", FileSystem.MyDocuments)     // User's documents
    .Mount("desktop://", FileSystem.Desktop)          // User's desktop
    .Mount("picture://", FileSystem.MyPictures)       // User's pictures
    .Mount("video://", FileSystem.MyVideos)           // User's videos
    .Mount("music://", FileSystem.MyMusic)            // User's music
    .Mount("config://", FileSystem.Config)            // User's cloud-sync program configuration (roaming data).
    .Mount("data://", FileSystem.Data)                // User's local program data.
    .Mount("program-data://", FileSystem.ProgramData) // Program data that is shared with every user.
    .Mount("application://", FileSystem.Application)  // Application's install directory
    .Mount("http://", HttpFileSystem.Instance, Option.SubPath("http://"))
    .Mount("https://", HttpFileSystem.Instance, Option.SubPath("https://"))

VirtualFileSystem.Url can be read, browsed and written to with its different url schemes.

VirtualFileSystem.Url.PrintTo(Console.Out, "config://", 2, PrintTree.Format.DefaultPath);
VirtualFileSystem.Url.PrintTo(Console.Out, "data://", 1, PrintTree.Format.DefaultPath);
VirtualFileSystem.Url.PrintTo(Console.Out, "program-data://", 1, PrintTree.Format.DefaultPath);
VirtualFileSystem.Url.PrintTo(Console.Out, "home://", 1, PrintTree.Format.DefaultPath);
VirtualFileSystem.Url.PrintTo(Console.Out, "https://github.com/tagcode/Lexical.FileSystem/tree/master/");

Application configuration can be placed in "config://ApplicationName/config.ini".

string config = "[Config]\nUser=ExampleUser\n";
VirtualFileSystem.Url.CreateDirectory("config://ApplicationName/");
VirtualFileSystem.Url.CreateFile("config://ApplicationName/config.ini", UTF8Encoding.UTF8.GetBytes(config));

Application's user specific local data can be placed in "data://ApplicationName/data.db".

byte[] cacheData = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
VirtualFileSystem.Url.CreateDirectory("data://ApplicationName/");
VirtualFileSystem.Url.CreateFile("data://ApplicationName/cache.db", cacheData);

Application's user documents can be placed in "document://ApplicationName/document".

string saveGame = "[Save]\nLocation=12.32N 43.43W\n";
VirtualFileSystem.Url.CreateDirectory("document://ApplicationName/");
VirtualFileSystem.Url.CreateFile("document://ApplicationName/save1.txt", UTF8Encoding.UTF8.GetBytes(saveGame));

Program data that is shared with every user can be placed in "program-data://ApplicationName/datafile". These are typically modifiable files.

byte[] programData = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
VirtualFileSystem.Url.CreateDirectory("program-data://ApplicationName/");
VirtualFileSystem.Url.CreateFile("program-data://ApplicationName/index.db", programData);

Application's installed files and binaries are located at "application://". These are typically read-only files.

VirtualFileSystem.Url.PrintTo(Console.Out, "application://", format: PrintTree.Format.DefaultPath);

Disposing

Disposable objects can be attached to be disposed along with VirtualFileSystem.

// Init
object obj = new ReaderWriterLockSlim();
IFileSystemDisposable vfs = new VirtualFileSystem().AddDisposable(obj);

// ... do work ...

// Dispose both
vfs.Dispose();

Delegates can be attached to be executed at dispose of VirtualFileSystem.

IFileSystemDisposable vfs = new VirtualFileSystem()
    .AddDisposeAction(f => Console.WriteLine("Disposed"));

.BelateDispose() creates a handle that postpones dispose on .Dispose(). Actual dispose will proceed once .Dispose() is called and all belate handles are disposed. This can be used for passing the IFileSystem to a worker thread.

VirtualFileSystem vfs = new VirtualFileSystem().Mount("", FileSystem.OS);
vfs.Browse("");

// Postpone dispose
IDisposable belateDisposeHandle = vfs.BelateDispose();
// Start concurrent work
Task.Run(() =>
{
    // Do work
    Thread.Sleep(1000);
    vfs.GetEntry("");
    // Release belate handle. Disposes here or below, depending which thread runs last.
    belateDisposeHandle.Dispose();
});

// Start dispose, but postpone it until belatehandle is disposed in another thread.
vfs.Dispose();

.AddMountsToBeDisposed() Disposes mounted filesystems at the dispose of the VirtualFileSystem.

IFileSystemDisposable vfs =
    new VirtualFileSystem()
    .Mount("", new FileSystem(""))
    .Mount("/tmp/", new MemoryFileSystem())
    .AddMountsToBeDisposed();

Decoration

The IFileSystems.Decorate(IOption) extension method decorates a filesystem with new decorated options. Decoration options is an intersection of filesystem's options and the options in the parameters, so decoration reduces features.

IFileSystem ram = new MemoryFileSystem();
IFileSystem rom = ram.Decorate(Option.ReadOnly);

IFileSystems.AsReadOnly() is same as IFileSystems.Decorate(Option.ReadOnly).

IFileSystem rom = ram.AsReadOnly();

Option.NoBrowse prevents browsing, hiding files.

IFileSystem invisible = ram.Decorate(Option.NoBrowse);

Option.SubPath(subpath) option exposes only a subtree of the decorated filesystem. The subpath argument must end with slash "/", or else it mounts filename prefix, e.g. "tmp-".

IFileSystem ram = new MemoryFileSystem();
ram.CreateDirectory("tmp/dir/");
ram.CreateFile("tmp/dir/file.txt", new byte[] { 32,32,32,32,32,32,32,32,32 });

IFileSystem tmp = ram.Decorate(Option.SubPath("tmp/"));
tmp.PrintTo(Console.Out, format: PrintTree.Format.DefaultPath);
└── dir/
   └── dir/file.txt

.AddSourceToBeDisposed() adds source objects to be disposed along with the decoration.

MemoryFileSystem ram = new MemoryFileSystem();
IFileSystemDisposable rom = ram.Decorate(Option.ReadOnly).AddSourceToBeDisposed();
// Do work ...
rom.Dispose();

Decorations implement IDisposeList and IBelatableDispose which allows to attach disposable objects.

MemoryFileSystem ram = new MemoryFileSystem();
ram.CreateDirectory("tmp/dir/");
ram.CreateFile("tmp/dir/file.txt", new byte[] { 32, 32, 32, 32, 32, 32, 32, 32, 32 });
IFileSystemDisposable rom = ram.Decorate(Option.ReadOnly).AddDisposable(ram);
// Do work ...
rom.Dispose();

If multiple decorations are used, the source reference can be 'forgotten' after construction if belate dispose handles are passed over to decorations.

// Create ram filesystem
MemoryFileSystem ram = new MemoryFileSystem();
ram.CreateDirectory("tmp/dir/");
ram.CreateFile("tmp/dir/file.txt", new byte[] { 32, 32, 32, 32, 32, 32, 32, 32, 32 });

// Create decorations
IFileSystemDisposable rom = ram.Decorate(Option.ReadOnly).AddDisposable(ram.BelateDispose());
IFileSystemDisposable tmp = ram.Decorate(Option.SubPath("tmp/")).AddDisposable(ram.BelateDispose());
ram.Dispose(); // <- is actually postponed

// Do work ...

// Dispose rom1 and tmp, disposes ram as well
rom.Dispose();
tmp.Dispose();

Concat

FileSystems.Concat(IFileSystem[]) method composes IFileSystem instances into one.

IFileSystem ram = new MemoryFileSystem();
IFileSystem os = FileSystem.OS;
IFileSystem fp = new PhysicalFileProvider(AppDomain.CurrentDomain.BaseDirectory).ToFileSystem()
    .AddDisposeAction(fs=>fs.FileProviderDisposable?.Dispose());
IFileSystem embedded = new EmbeddedFileSystem(typeof(Composition_Examples).Assembly);

IFileSystem composition = FileSystemExtensions.Concat(ram, os, fp, embedded)
    .AddDisposable(embedded)
    .AddDisposable(fp)
    .AddDisposable(os);

Composed set of files can be browsed.

foreach (var entry in composition.VisitTree(depth: 1))
    Console.WriteLine(entry);

Files can be read from the composed set.

using (Stream s = composition.Open("docs.example-file.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
{
    Console.WriteLine(s.Length);
}

If two files have same name and path, the file in the first IFileSystem overshadows files from later IFileSystems.

IFileSystem ram1 = new MemoryFileSystem();
IFileSystem ram2 = new MemoryFileSystem();
IFileSystem composition = FileSystemExtensions.Concat(ram1, ram2);

// Create file of 1024 bytes
ram1.CreateFile("file.txt", new byte[1024]);

// Create file of 10 bytes
ram2.CreateFile("file.txt", new byte[10]);

// Get only one entry size of 1024 bytes.
composition.PrintTo(Console.Out, format: PrintTree.Format.Default | PrintTree.Format.Length);
""
└── "file.txt" 1024

FileSystems.Concat((IFileSystem, IOption)[]) applies options to the filesystems.

IFileSystem filesystem = FileSystem.Application;
IFileSystem overrides = new MemoryFileSystem();
IFileSystem composition = FileSystemExtensions.Concat(
    (filesystem, null), 
    (overrides, Option.ReadOnly)
);

IFileProvider

There are decorating adapters to and from IFileProvider instances.

To use IFileProvider decorations, the calling assembly must import the Microsoft.Extensions.FileProviders.Abstractions assembly.

To IFileSystem

The extension method IFileProvider.ToFileSystem() adapts IFileProvider into IFileSystem.

IFileSystem fs = new PhysicalFileProvider(@"C:\Users").ToFileSystem();

Parameters .ToFileSystem(bool canBrowse, bool canObserve, bool canOpen) can be used for limiting the capabilities of the adapted IFileSystem.

IFileProvider fp = new PhysicalFileProvider(@"C:\");
IFileSystem fs = fp.ToFileSystem(
    canBrowse: true,
    canObserve: true,
    canOpen: true);

.AddDisposable(object) attaches a disposable to be disposed along with the IFileSystem adapter.

IFileProvider fp = new PhysicalFileProvider(@"C:\Users");
IFileSystemDisposable filesystem = fp.ToFileSystem().AddDisposable(fp);

.AddDisposeAction() attaches a delegate to be ran at dispose. It can be used for disposing the source IFileProvider.

IFileSystemDisposable filesystem = new PhysicalFileProvider(@"C:\Users")
    .ToFileSystem()
    .AddDisposeAction(fs => fs.FileProviderDisposable?.Dispose());

.BelateDispose() creates a handle that postpones dispose on .Dispose(). Actual dispose will proceed once .Dispose() is called and all belate handles are disposed. This can be used for passing the IFileSystem to a worker thread.

using (var fs = new PhysicalFileProvider(@"C:\Users")
    .ToFileSystem()
    .AddDisposeAction(f => f.FileProviderDisposable?.Dispose()))
{
    fs.Browse("");

    // Post pone dispose at end of using()
    IDisposable belateDisposeHandle = fs.BelateDispose();
    // Start concurrent work
    Task.Run(() =>
    {
        // Do work
        Thread.Sleep(100);
        fs.GetEntry("");

        // Release the belate dispose handle
        // FileSystem is actually disposed here
        // provided that the using block has exited
        // in the main thread.
        belateDisposeHandle.Dispose();
    });

    // using() exists here and starts the dispose fs
}

The adapted IFileSystem can be used as any filesystem that has Open(), Browse() and Observe() features.

IFileSystem fs = new PhysicalFileProvider(@"C:\Users").ToFileSystem();
foreach (var line in fs.VisitTree(depth: 2))
    Console.WriteLine(line);
""
├──"Public"
│  ├──"Shared Files"
│  ├──"Documents"
│  ├──"Downloads"
│  ├──"Music"
│  ├──"Pictures"
│  ├──"Roaming"
│  └──"Videos"
└──"user"
   ├──"Contacts"
   ├──"Desktop"
   ├──"Documents"
   ├──"Downloads"
   ├──"Favorites"
   ├──"Links"
   ├──"Music"
   ├──"OneDrive"
   ├──"Pictures"
   └──"Videos"

.Observe() attaches a watcher to the source IFileProvider and adapts incoming events.

IFileSystem fs = new PhysicalFileProvider(@"C:\Users").ToFileSystem();
IObserver<IEvent> observer = new Observer();
using (IDisposable handle = fs.Observe("**", observer))
{
}

[!WARNING] Note that, observing a IFileProvider through IFileSystem adapter browses the subtree of the source IFileProvider and compares snapshots in order to produce change events. If observer uses "**" pattern, it will browse through the whole IFileProvider.

To IFileProvider

IFileSystem.ToFileProvider() adapts IFileProvider into IFileSystem.

IFileProvider fp = FileSystem.OS.ToFileProvider();

.AddDisposable(object) attaches a disposable to be disposed along with the IFileProvider adapter.

IFileSystem fs = new FileSystem("");
IFileProviderDisposable fp = fs.ToFileProvider().AddDisposable(fs);

.AddDisposeAction() attaches a delegate to be ran at dispose. It can be used for disposing the source IFileSystem.

IFileProviderDisposable fp = new FileSystem("")
    .ToFileProvider()
    .AddDisposeAction(fs => fs.FileSystemDisposable?.Dispose());

.BelateDispose() creates a handle that postpones dispose on .Dispose(). Actual dispose will proceed once .Dispose() is called and all belate handles are disposed. This can be used for passing the IFileProvider to a worker thread.

using (var fp = new FileSystem("").ToFileProvider()
        .AddDisposeAction(fs => fs.FileSystemDisposable?.Dispose()))
{
    fp.GetDirectoryContents("");

    // Post pone dispose at end of using()
    IDisposable belateDisposeHandle = fp.BelateDispose();
    // Start concurrent work
    Task.Run(() =>
    {
        // Do work
        Thread.Sleep(100);
        fp.GetDirectoryContents("");

        // Release the belate dispose handle
        // FileSystem is actually disposed here
        // provided that the using block has exited
        // in the main thread.
        belateDisposeHandle.Dispose();
    });

    // using() exists here and starts the dispose fs
}

The adapted IFileProvider can be used as any fileprovider that can GetDirectoryContents(), GetFileInfo(), and Watch().

IFileProvider fp = FileSystem.OS.ToFileProvider();
foreach (var fi in fp.GetDirectoryContents(""))
    Console.WriteLine(fi.Name);
C:
D:
E:

.Watch() attaches a watcher.

IChangeToken token = new FileSystem(@"c:").ToFileProvider().Watch("**");
token.RegisterChangeCallback(o => Console.WriteLine("Changed"), null);

MemoryFileSystem

MemoryFileSystem is a memory based filesystem.

IFileSystem filesystem = new MemoryFileSystem();

Files are based on blocks. Maximum number of blocks is 2^31-1. The blockSize can be set in constructor. The default blocksize is 1024.

IFileSystem filesystem = new MemoryFileSystem(blockSize: 4096);

Files can be browsed.

foreach (var entry in filesystem.Browse(""))
    Console.WriteLine(entry.Path);

Files can be opened for reading.

using (Stream s = filesystem.Open("file.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
{
    Console.WriteLine(s.Length);
}

And for writing.

using (Stream s = filesystem.Open("file.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
{
    s.WriteByte(32);
}

Files and directories can be observed for changes.

IObserver<IEvent> observer = new Observer();
using (IDisposable handle = filesystem.Observe("**", observer))
{
}

Directories can be created.

filesystem.CreateDirectory("dir/");

Directories can be created recursively.

filesystem.CreateDirectory("dir1/dir2/dir3/");
filesystem.PrintTo(Console.Out);

The root is "".

""
└──"dir1"
   └──"dir2"
      └──"dir3"

MemoryFileSystem can create empty directory names. For example, a slash '/' at the start of a path refers to an empty directory right under the root.

filesystem.CreateDirectory("/tmp/dir/");
""
└──""
   └──"tmp"
      └──"dir"

Path "file://" refers to three directories; the root, "file:" and a empty-named directory between two slashes "//".

filesystem.CreateDirectory("file://");
""
└──"file:"
   └──""

Directories can be deleted.

filesystem.Delete("dir/", recurse: true);

Files and directories can be renamed and moved.

filesystem.CreateDirectory("dir/");
filesystem.Move("dir/", "new-name/");

Disposing

Disposable objects can be attached to be disposed along with FileSystem.

// Init
object obj = new ReaderWriterLockSlim();
IFileSystemDisposable filesystem = new FileSystem("").AddDisposable(obj);

// ... do work ...

// Dispose both
filesystem.Dispose();

Delegates can be attached to be executed at dispose of FileSystem.

IFileSystemDisposable filesystem = new FileSystem("")
    .AddDisposeAction(f => Console.WriteLine("Disposed"));

.BelateDispose() creates a handle that postpones dispose on .Dispose(). Actual dispose proceeds once .Dispose() is called and all belate handles are disposed. This can be used for passing the IFileSystem to worker threads.

MemoryFileSystem filesystem = new MemoryFileSystem();
filesystem.CreateDirectory("/tmp/dir/");

// Postpone dispose
IDisposable belateDisposeHandle = filesystem.BelateDispose();
// Start concurrent work
Task.Run(() =>
{
    // Do work
    Thread.Sleep(1000);
    filesystem.GetEntry("");
    // Release belate handle. Disposes here or below, depending which thread runs last.
    belateDisposeHandle.Dispose();
});

// Start dispose, but postpone it until belatehandle is disposed in another thread.
filesystem.Dispose();

Size Limit

Constructor new MemoryFileSystem(blockSize, maxSpace) creates size limited filesystem. Memory limitation applies to files only, not to directory structure.

IFileSystem ms = new MemoryFileSystem(blockSize: 1024, maxSpace: 1L << 34);

Printing with PrintTree.Format.DriveFreespace | PrintTree.Format.DriveSize flags show drive size.

IFileSystem ms = new MemoryFileSystem(blockSize: 1024, maxSpace: 1L << 34);
ms.CreateFile("file", new byte[1 << 30]);
ms.PrintTo(Console.Out, format: PrintTree.Format.AllWithName);
"" [Freespace: 15G, Size: 1G/16G, Ram]
└── "file" [1073741824]

If filesystem runs out of space, it throws FileSystemExceptionOutOfDiskSpace.

IFileSystem ms = new MemoryFileSystem(blockSize: 1024, maxSpace: 2048);
ms.CreateFile("file1", new byte[1024]);
ms.CreateFile("file2", new byte[1024]);

// throws FileSystemExceptionOutOfDiskSpace
ms.CreateFile("file3", new byte[1024]);

Available space can be shared between MemoryFileSystem instances with IBlockPool.

IBlockPool pool = new BlockPool(blockSize: 1024, maxBlockCount: 3, maxRecycleQueue: 3);
IFileSystem ms1 = new MemoryFileSystem(pool);
IFileSystem ms2 = new MemoryFileSystem(pool);

// Reserve 2048 from shared pool
ms1.CreateFile("file1", new byte[2048]);

// Not enough for another 3072, throws FileSystemExceptionOutOfDiskSpace
ms2.CreateFile("file2", new byte[2048]);

Deleted file is returned back to pool once all open streams are closed.

IBlockPool pool = new BlockPool(blockSize: 1024, maxBlockCount: 3, maxRecycleQueue: 3);
IFileSystem ms = new MemoryFileSystem(pool);
Stream s = ms.Open("file", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
s.Write(new byte[3072], 0, 3072);
ms.Delete("file");

Console.WriteLine(pool.BytesAvailable); // Prints 0
s.Dispose();
Console.WriteLine(pool.BytesAvailable); // Prints 3072

HttpFileSystem

new HttpFileSystem(HttpClient, IOption) creates a new http based filesystem.

IFileSystem fs = new HttpFileSystem(httpClient: default, option: default);

HttpFileSystem.Instance is the default singleton instance.

IFileSystem fs = HttpFileSystem.Instance;

Opening a resource with FileMode.Open and FileAccess.Read parameters makes a GET request.

using (var s = HttpFileSystem.Instance.Open("http://lexical.fi/", FileMode.Open, FileAccess.Read, FileShare.None))
{
    byte[] data = StreamUtils.ReadFully(s);
    String str = UTF8Encoding.UTF8.GetString(data);
    Console.WriteLine(str);
}

Web resources can be used with generic extension methods such as .CopyFile().

MemoryFileSystem ram = new MemoryFileSystem();
HttpFileSystem.Instance.CopyFile("http://lexical.fi", ram, "document.txt");
ram.PrintTo(Console.Out);
""
└── "document.txt"

Opening a resource with FileMode.Create and FileAccess.Write makes a PUT request.

byte[] data = new byte[1024];
using (var s = HttpFileSystem.Instance.Open("http://lexical.fi/", FileMode.Create, FileAccess.Write, FileShare.None))
    s.Write(data);

HttpFileSystem can be constructed with various options, such as SubPath and custom http header.

// Create options
List<KeyValuePair<string, IEnumerable<string>>> headers = new List<KeyValuePair<string, IEnumerable<string>>>();
headers.Add(new KeyValuePair<string, IEnumerable<string>>("User-Agent", new String[] { "MyUserAgent" }));
IOption option1 = new Token(headers, typeof(System.Net.Http.Headers.HttpHeaders).FullName);
IOption option2 = Option.SubPath("http://lexical.fi/");
IOption options = Option.Union(option1, option2);

// Create FileSystem
IFileSystem fs = new HttpFileSystem(option: options);

// Read resource
using (var s = fs.Open("index.html", FileMode.Open, FileAccess.Read, FileShare.None))
{
    byte[] data = StreamUtils.ReadFully(s);
    String str = UTF8Encoding.UTF8.GetString(data);
    Console.WriteLine(str);
}

User authentication header AuthenticationHeaderValue can be wrapped in Token and passed to Open() method.

AuthenticationHeaderValue authentication = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes($"webuser:webpassword")));
IToken token = new Token(authentication, typeof(AuthenticationHeaderValue).FullName);
using (var s = HttpFileSystem.Instance.Open("https://lexical.fi/public/Lexical.FileSystem/private/document.txt", FileMode.Open, FileAccess.Read, FileShare.None, token))
{
    byte[] data = new byte[4096];
    int c = s.Read(data, 0, 1024);
    String str = UTF8Encoding.UTF8.GetString(data, 0, c);
    Console.WriteLine(str);
}

Another way is to pass user authentication token at construction of HttpFileSystem. The token must be given glob patterns where the token applies, for example "http://lexical.fi/FileSystem/private/**".

// Authentication header
AuthenticationHeaderValue authentication = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes($"webuser:webpassword")));

// Token
IToken token = new Token(
    authentication, 
    typeof(AuthenticationHeaderValue).FullName,
    "https://lexical.fi/public/Lexical.FileSystem/private/**",
    "https://www.lexical.fi/public/Lexical.FileSystem/private/**"
);

// Create FileSystem
IFileSystem fs = new HttpFileSystem(default, token);

// Open
using (var s = fs.Open("https://lexical.fi/public/Lexical.FileSystem/private/document.txt", FileMode.Open, FileAccess.Read, FileShare.None))
{
    byte[] data = new byte[4096];
    int c = s.Read(data, 0, 1024);
    String str = UTF8Encoding.UTF8.GetString(data, 0, c);
    Console.WriteLine(str);
}

Third way is to pass authentication token into a decoration.

// Authentication header
AuthenticationHeaderValue authentication = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes($"webuser:webpassword")));

// Create token
IToken token = new Token(authentication, typeof(AuthenticationHeaderValue).FullName, "https://lexical.fi/public/Lexical.FileSystem/private/**");

// Pass token into decorator
IFileSystem decoration = HttpFileSystem.Instance.Decorate(token);

// Open
using (var s = decoration.Open("https://lexical.fi/public/Lexical.FileSystem/private/document.txt", FileMode.Open, FileAccess.Read, FileShare.None))
{
    byte[] data = new byte[4096];
    int c = s.Read(data, 0, 1024);
    String str = UTF8Encoding.UTF8.GetString(data, 0, c);
    Console.WriteLine(str);
}

.Delete(uri) sends DELETE http request.

HttpFileSystem.Instance.Delete("https://lexical.fi/public/Lexical.FileSystem/private/document.txt");

.Browse(uri) reads html document and parses links that refer to immediate child files and directories.

var authBlob = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes($"webuser:webpassword"));
var authentication = new AuthenticationHeaderValue("Basic", authBlob);
var token = new Token(authentication, typeof(AuthenticationHeaderValue).FullName, "https://lexical.fi/public/Lexical.FileSystem/private/**");

IEnumerable<IEntry> entries = HttpFileSystem.Instance.Browse("https://lexical.fi/public/Lexical.FileSystem/private/", token);

.GetEntry(uri) reads resource headers and returns entry.

var authBlob = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes($"webuser:webpassword"));
var authentication = new AuthenticationHeaderValue("Basic", authBlob);
var token = new Token(authentication, typeof(AuthenticationHeaderValue).FullName, "https://lexical.fi/public/Lexical.FileSystem/private/**");

IEntry entry = HttpFileSystem.Instance.GetEntry("https://lexical.fi/public/Lexical.FileSystem/private/document.txt", token);

File system can be scanned with .VisitTree() and .PrintTo() extension methods.

var authBlob = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes($"webuser:webpassword"));
var authentication = new AuthenticationHeaderValue("Basic", authBlob);
var token = new Token(authentication, typeof(AuthenticationHeaderValue).FullName, "https://lexical.fi/public/Lexical.FileSystem/private/**");

HttpFileSystem.Instance.PrintTo(Console.Out, "https://lexical.fi/public/Lexical.FileSystem/private/", option: token);
"private"
├── "Directory"
│  └── "file.txt"
├── "Folder"
│  └── "file.txt"
└── "document.txt"

On github too. Notice that, only directories are returned from "/tree/", as files are on different url branch "/blob/".

HttpFileSystem.Instance.PrintTo(Console.Out, "https://github.com/tagcode/Lexical.FileSystem/tree/master/");
"master"
├── "Lexical.FileSystem"
│  ├── "Decoration"
│  ├── "Extensions"
│  ├── "Internal"
│  ├── "Package"
│  └── "Utility"
└── "Lexical.FileSystem.Abstractions"
   ├── "Extensions"
   ├── "FileProvider"
   ├── "Internal"
   ├── "Option"
   ├── "Package"
   └── "Utility"

CancellationToken can be passed to break-up operation.

// Cancel token
CancellationTokenSource cancelSrc = new CancellationTokenSource();
IToken token = new Token(cancelSrc.Token, typeof(CancellationToken).FullName);

// Set canceled
cancelSrc.Cancel();

// Read
HttpFileSystem.Instance.Open("http://lexical.fi/", FileMode.Open, FileAccess.Read, FileShare.None, token);

[!NOTE] HttpFileSystem doesn't support stream seeking. It can only stream content.

FileScanner

FileScanner scans the tree structure of a filesystem for files that match its configured criteria. It uses concurrent threads.

IFileSystem fs = new MemoryFileSystem();
fs.CreateDirectory("myfile.zip/folder");
fs.CreateFile("myfile.zip/folder/somefile.txt");

FileScanner filescanner = new FileScanner(fs);

FileScanner needs to be populated with at least one filter, such as wildcard pattern, .AddWildcard(string).

filescanner.AddWildcard("*.zip");

Or regular expression.

filescanner.AddRegex(path: "", pattern: new Regex(@".*\.zip"));

Or glob pattern.

filescanner.AddGlobPattern("**.zip/**.txt");

The initial start path is extracted from the pattern.

filescanner.AddGlobPattern("myfile.zip/**.txt");

Search is started when IEnumerator<IEntry> is enumerated from the scanner.

foreach (IEntry entry in filescanner)
{
    Console.WriteLine(entry.Path);
}

Exceptions that occur at real-time can be captured into concurrent collection.

// Collect errors
filescanner.errors = new ConcurrentBag<Exception>();
// Run scan
IEntry[] entries = filescanner.ToArray();
// View errors
foreach (Exception e in filescanner.errors) Console.WriteLine(e);

The property .ReturnDirectories determines whether scanner returns directories.

filescanner.ReturnDirectories = true;

The property .SetDirectoryEvaluator(Func<IEntry, bool> func) sets a criteria that determines whether scanner enters a directory.

filescanner.SetDirectoryEvaluator(e => e.Name != "tmp");

VisitTree

The extension method IFileSystem.VisitTree(string path, int depth) visits a tree structure of a filesystem.

IFileSystem ram = new MemoryFileSystem();
ram.CreateDirectory("/tmp/");
ram.CreateDirectory("/mnt/");
ram.CreateDirectory("/usr/lex/");
ram.CreateDirectory("c:/dir/dir/");
ram.CreateFile("/tmp/helloworld.txt", Encoding.UTF8.GetBytes("Hello World!\r\n"));
ram.CreateDirectory("file://c:/temp");

foreach (TreeVisit.Line line in ram.VisitTree())
{
    Console.WriteLine(line);
}
""
├── ""
│  ├── "mnt"
│  ├── "tmp"
│  │  └── "helloworld.txt"
│  └── "usr"
│     └── "lex"
├── "c:"
│  └── "dir"
│     └── "dir"
└── "file:"
   └── ""
      └── "c:"
         └── "temp"

Parameter depth determines visit depth.

foreach (TreeVisit.Line line in ram.VisitTree(depth: 1))
{
    Console.WriteLine(line);
}
""
├── ""
├── "c:"
└── "file:"

Parameter path determines start location.

foreach (TreeVisit.Line line in ram.VisitTree(path: "/tmp/"))
{
    Console.WriteLine(line);
}
"tmp"
└── "helloworld.txt"

PrintTree

The extension method IFileSystem.PrintTo(TextWriter output, string path, int depth, Format format) prints a tree structure to a TextWriter such as Console.Out (stdout).

IFileSystem ram = new MemoryFileSystem();
ram.CreateDirectory("/tmp/");
ram.CreateDirectory("/mnt/");
ram.CreateDirectory("/usr/lex/");
ram.CreateDirectory("c:/dir/dir/");
ram.CreateFile("/tmp/helloworld.txt", Encoding.UTF8.GetBytes("Hello World!\r\n"));
ram.CreateDirectory("file://c:/temp/");

ram.PrintTo(Console.Out);
""
├── ""
│  ├── "mnt"
│  ├── "tmp"
│  │  └── "helloworld.txt"
│  └── "usr"
│     └── "lex"
├── "c:"
│  └── "dir"
│     └── "dir"
└── "file:"
   └── ""
      └── "c:"
         └── "temp"

IFileSystem.PrintTo(TextWriter output, string path, int depth, Format format) appends to a StringBuilder.

StringBuilder sb = new StringBuilder();
ram.PrintTo(sb);

IFileSystem.Print(string path, int depth, Format format) prints out to as string.

Console.WriteLine(ram.Print());

Parameter depth determines visit depth.

Console.WriteLine(ram.Print(depth:1));
""
├── ""
├── "c:"
└── "file:"

Parameter path determines start location.

Console.WriteLine(ram.Print(path:"/tmp/"));
"tmp"
└── "helloworld.txt"

Parameter format determines which infos are printed out. For example PrintTree.Format.Path prints out full path instead of name.

string tree =  ram.Print(format: PrintTree.Format.Tree | PrintTree.Format.Path | 
                                 PrintTree.Format.Length | PrintTree.Format.Error);
├── /
│  ├── /mnt/
│  ├── /tmp/
│  │  └── /tmp/helloworld.txt 14
│  └── /usr/
│     └── /usr/lex/
├── c:/
│  └── c:/dir/
│     └── c:/dir/dir/
└── file:/
   └── file://
      └── file://c:/
         └── file://c:/temp/

PrintTree.Format flags:

Flag Description
PrintTree.Format.Tree The tree structure.
PrintTree.Format.Name Entry name.
PrintTree.Format.Path Entry path.
PrintTree.Format.Length Entry length for file entires.
PrintTree.Format.Error Traverse error.
PrintTree.Format.LineFeed Next line.
PrintTree.Format.Mount Mounted filesystem.
PrintTree.Format.DriveLabel Label of filesystem drive or volume.
PrintTree.Format.DriveFreespace Available free space on the drive or volume.
PrintTree.Format.DriveSize Total size of drive or volume.
PrintTree.Format.DriveType Drive or volume type, such as: Fixed, Removeable, Network, Ram
PrintTree.Format.DriveFormat FileSystem format, such as: NTFS, FAT32, EXT4
PrintTree.Format.FileAttributes File attributes, such as: ReadOnly, Hidden, System, Directory, Archive
PrintTree.Format.PhysicalPath Physical path
PrintTree.Format.Default PrintTree.Format.Tree | PrintTree.Format.Name | PrintTree.Format.Error
PrintTree.Format.DefaultPath PrintTree.Format.Tree | PrintTree.Format.Path | PrintTree.Format.Error
PrintTree.Format.All All flags.
PrintTree.Format.AllWithName All flags with name printing (excludes path printing).
PrintTree.Format.AllWithPath All flags with path printing (excludes name printing).

Operation

The namespace Lexical.FileSystem.Operation contains file operation classes.

IOperation is ran inside a context of IOperationSession.

IFileSystem ms = new MemoryFileSystem();
ms.CreateFile("file", new byte[1024*1024]);

using(var s = new OperationSession())
{
    new CopyFile(s, ms, "file", ms, "file.copy")
        .Estimate()
        .AssertCanRollback()
        .Run()
        .AssertSuccessful();
}
""
├── "file" [1048576]
└── "file.copy" [1048576]

IOperation.Estimate() makes checks to test if the operation can be completed. It throws exception if there is a visible reason why the operation would not complete.

var s = new OperationSession();
var op = new CopyFile(s, ms, "file", ms, "file.copy");
op.Estimate();

After .Estimate() is called, the property .CanRollback is set. It determines if the operation can be reverted.

Console.WriteLine(op.CanRollback);

.Estimate().AssertCanRollback() asserts rollback is possible.

op.Estimate().AssertCanRollback();

.Run() executes the operation. It throws an exception on unexpected error.

op.Run();

The caller should test the .CurrentState property after .Run() to see what the state is.

if (op.CurrentState == OperationState.Completed) 
    Console.WriteLine("ok");

... or use .Run().AssertSuccessful() to assert it.

op.Run().AssertSuccessful();

Caller can try rollback upon failure (or anyway).

try
{
    op.Run();
}
catch (Exception)
{
    // Rollback
    op.CreateRollback()?.Run();
}

Executing .Run(rollbackOnError) rollbacks automatically on failure if rollback is possible.

op.Run(rollbackOnError: true);

CopyTree copies a directory tree from one place to another.

IFileSystem ms = new MemoryFileSystem();
ms.CreateDirectory("dir/dir/dir/");
ms.CreateFile("dir/dir/dir/file", new byte[1024 * 1024]);

using (var s = new OperationSession())
{
    var op = new CopyTree(s, ms, "dir", ms, "dir.copy");

    op.Estimate().AssertCanRollback().Run().AssertSuccessful();
}
""
├── "dir"
│  └── "dir"
│     └── "dir"
│        └── "file"
└── "dir.copy"
   └── "dir"
      └── "dir"
         └── "file"

Batch is a list of operations to run as one operation.

IFileSystem ms = new MemoryFileSystem();                
using (var s = new OperationSession(policy: OperationPolicy.EstimateOnRun/*important*/))
{
    Batch batch = new Batch(s, OperationPolicy.Unset);

    batch.Ops.Add(new CreateDirectory(s, ms, "dir/dir/dir"));
    batch.Ops.Add(new CopyTree(s, ms, "dir", ms, "dir.copy"));
    batch.Ops.Add(new Delete(s, ms, "dir/dir", true, policy: OperationPolicy.EstimateOnRun));
    batch.Estimate().Run().AssertSuccessful();
}

TransferTree moves files from one location to another by copy-and-delete.

IFileSystem ms_src = new MemoryFileSystem();
ms_src.CreateDirectory("dir/dir/dir/");
ms_src.CreateFile("dir/dir/dir/file", new byte[1024 * 1024]);

IFileSystem ms_dst = new MemoryFileSystem();

using (var s = new OperationSession())
{
    var op = new TransferTree(s, ms_src, "dir", ms_dst, "dir.elsewhere");
    op.Estimate().AssertCanRollback().Run().AssertSuccessful();
}

Session gathers events automatically. Gathered evants can be read from the session object.

IFileSystem ms = new MemoryFileSystem();
ms.CreateFile("file", new byte[1024 * 1024]);

using (var s = new OperationSession())
{
    new CopyFile(s, ms, "file", ms, "file.copy")
        .Estimate()
        .AssertCanRollback()
        .Run()
        .AssertSuccessful();

    foreach (var @event in s.Events)
        Console.WriteLine(@event);
}
ms.PrintTo(Console.Out);
CopyFile(Src=file, Dst=file.copy, State=Completed) = Estimating
CopyFile(Src=file, Dst=file.copy, State=Completed) = Estimated
CopyFile(Src=file, Dst=file.copy, State=Completed) = Running
CopyFile(Src=file, Dst=file.copy, State=Completed) = Completed

Events can subscribed. Copy operation can be configured to send regular progress events. .SetProgressInterval(long) sets the interval on how often Operation.Event.Progress is sent in terms of bytes.

IFileSystem ms = new MemoryFileSystem();
ms.CreateFile("file", new byte[1024 * 20]);

using (var s = new OperationSession().SetProgressInterval(1024))
{
    s.Subscribe(new OpEventPrinter());

    new CopyFile(s, ms, "file", ms, "file.copy")
        .Estimate()
        .AssertCanRollback()
        .Run()
        .AssertSuccessful();
}
ms.PrintTo(Console.Out);
class OpEventPrinter : IObserver<IOperationEvent>
{
    public void OnCompleted() => Console.WriteLine("OnCompleted");
    public void OnError(Exception error) => Console.WriteLine(error);
    public void OnNext(IOperationEvent @event) => Console.WriteLine(@event);
}
CopyFile(Src=file, Dst=file.copy, State=Estimating) = Estimating
CopyFile(Src=file, Dst=file.copy, State=Estimated) = Estimated
CopyFile(Src=file, Dst=file.copy, State=Running) = Running
Progress(CopyFile(Src=file, Dst=file.copy, State=Running), 20%)
Progress(CopyFile(Src=file, Dst=file.copy, State=Running), 40%)
Progress(CopyFile(Src=file, Dst=file.copy, State=Running), 60%)
Progress(CopyFile(Src=file, Dst=file.copy, State=Running), 80%)
Progress(CopyFile(Src=file, Dst=file.copy, State=Running), 100%)
CopyFile(Src=file, Dst=file.copy, State=Completed) = Completed
OnCompleted
""
├── "file"
└── "file.copy"

If cancellation token is canceled, the operation does not proceed.

IFileSystem ms = new MemoryFileSystem();
ms.CreateFile("file", new byte[1024 * 10]);
CancellationTokenSource cancelSrc = new CancellationTokenSource();

using (var s = new OperationSession(cancelSrc: cancelSrc))
{
    cancelSrc.Cancel();
    new Move(s, ms, "file", ms, "file.moved")
        .Estimate()
        .AssertCanRollback()
        .Run()
        .AssertSuccessful();
}
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].