TagBites.IO turns any storage into the same thing: a FileSystem browsed with FileLink and DirectoryLink. The local disk, an in-memory tree and a composed virtual tree are built in, and separate packages add FTP, SFTP, SMB, WebDAV, HTTP, ZIP, Dropbox, OneDrive, Google Cloud Storage, Azure Blob Storage and S3. An operation is written once - copy, move, sync, watch, restore a version - and runs wherever the files live, without a provider-specific SDK.
Restore yesterday's FTP backup to disk:
using var ftp = FtpFileSystem.Create("ftp.example.com", "user", "password");
var backup = ftp.GetDirectory("/backups/2026-07-04");
var restore = FileSystem.Local.GetDirectory("C:/Restore");
backup.CopyTo(restore);Turn an S3 bucket, a Dropbox account and a local folder into one Windows drive:
var s3 = S3FileSystem.Create(serviceUrl, accessKey, secretKey, "bucket");
var dropbox = DropboxFileSystem.Create(dropboxToken);
var backups = s3.GetDirectory("backups");
var photos = dropbox.GetDirectory("Photos");
var files = FileSystem.Local.GetDirectory("C:/Files");
var merged = new VirtualDirectory
{
Entries =
{
new VirtualDirectoryEntry(backups, "S3"),
new VirtualDirectoryEntry(photos, "Dropbox"),
new VirtualDirectoryEntry(files, "Local")
}
};
using var drive = new WinDrive(merged.ToDirectory()) { Name = "Merged Drive" };
drive.Mount(); // "V:\S3", "V:\Dropbox", "V:\Local"dotnet add package TagBites.IO
Targets netstandard2.0, netstandard2.1 and net5.0. No dependencies.
Local disk, an in-memory file system and a virtual composed tree are built in. Remote storages ship as separate packages that plug into the same API:
| File system | Package | Storage |
|---|---|---|
FileSystem.Local |
TagBites.IO |
Local disk (System.IO) |
MemoryFileSystem.CreateMemory |
TagBites.IO |
In-memory tree, held for the lifetime of the instance |
VirtualFileSystem |
TagBites.IO |
Several file systems composed into one tree |
FtpFileSystem |
TagBites.IO.Ftp |
FTP / FTPS |
SftpFileSystem |
TagBites.IO.Sftp |
SFTP |
SmbFileSystem |
TagBites.IO.Smb |
SMB/CIFS network shares |
WebDavFileSystem |
TagBites.IO.WebDav |
WebDAV |
HttpFileSystem |
TagBites.IO.Http |
HTTP, read-only |
ZipFileSystem |
TagBites.IO.Zip |
ZIP archives |
DropboxFileSystem |
TagBites.IO.Dropbox |
Dropbox |
OneDriveFileSystem |
TagBites.IO.OneDrive |
OneDrive (Microsoft Graph) |
GoogleFileSystem |
TagBites.IO.Google |
Google Cloud Storage |
AzureBlobFileSystem |
TagBites.IO.AzureBlob |
Azure Blob Storage |
S3FileSystem |
TagBites.IO.S3 |
Amazon S3 and S3-compatible storage (e.g. Cloudflare R2) |
and also allows to create custom file system.
TagBites.IO.WinDrive lets you mount any DirectoryLink - regardless of the underlying file system - as a real Windows drive letter.
var fs = FileSystem.Local;
var directory = fs.GetDirectory("/some/folder");
directory.Create();
var file = fs.GetFile("/some/folder/file.txt");
file.WriteAllText("Hello world!");
var content = file.ReadAllText(); // "Hello world!"
foreach (var f in directory.GetFiles(recursive: true))
Console.WriteLine(f.FullName);Every provider package (TagBites.IO.Ftp, TagBites.IO.S3, ...) exposes the same shape - a static Create(...) method returning a FileSystem - so the code above works unchanged regardless of the storage:
using var fs = FtpFileSystem.Create("ftp.example.com", "user", "password");
var file = fs.GetFile("/reports/2024/summary.csv");Source and destination can live in different storages:
using var ftp = FtpFileSystem.Create("ftp.example.com", "user", "password");
using var s3 = S3FileSystem.Create(serviceUrl, accessKey, secretKey, "archive");
var reports = ftp.GetDirectory("/reports");
var archive = s3.GetDirectory("/reports");
reports.Copy(archive, overwrite: true);
reports.Move(archive);
reports.SyncWith(archive, FileSystemSynchronizeMode.OneWayWithRemoveNotExisting);What happens when the destination exists, and what happens when one file in a tree fails, is decided per call - see conflicts and errors.
SyncWith runs once. FileSystemLinkSynchronizer keeps a pair in step for as long as it is enabled, across two different storages:
using var ftp = FtpFileSystem.Create("ftp.example.com", "user", "password");
var local = FileSystem.Local.GetDirectory("C:/Reports");
var remote = ftp.GetDirectory("/reports");
using var synchronizer = new FileSystemLinkSynchronizer();
synchronizer.Add(local, remote, FileSystemSynchronizeMode.BothWaysUsingLastWriteTime);
synchronizer.SyncFailed += (_, e) =>
Console.WriteLine($"{e.Source.FullName}: {e.Exception.Message}, retry: {e.WillRetry}");
synchronizer.Enabled = true;Change detection uses the same watcher the provider reports through FileSystemFeatures.Watcher. A storage without one notices only the changes made through the registered FileSystem instances, so call ForceSyncAsync() on a timer to catch what another client wrote:
await synchronizer.ForceSyncAsync();See synchronization for what each mode does and what a watcher on a remote storage does not see.
A file system whose operations implement IFileSystemHistoryOperations exposes previous versions of a file:
var versions = file.GetHistoryVersions();
foreach (var version in versions)
Console.WriteLine($"{version.ModifyTime} {version.Length}");
var previous = versions[0];
using var stream = previous.Read(); // the content of that version, without restoring it
previous.Restore();No shipped provider implements file history yet. The API is there for a custom file system built over a storage that keeps versions - see file history and versioning.