FileVault.Sftp 0.1.2-alpha

This is a prerelease version of FileVault.Sftp.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package FileVault.Sftp --version 0.1.2-alpha
                    
NuGet\Install-Package FileVault.Sftp -Version 0.1.2-alpha
                    
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="FileVault.Sftp" Version="0.1.2-alpha" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FileVault.Sftp" Version="0.1.2-alpha" />
                    
Directory.Packages.props
<PackageReference Include="FileVault.Sftp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FileVault.Sftp --version 0.1.2-alpha
                    
#r "nuget: FileVault.Sftp, 0.1.2-alpha"
                    
#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.
#:package FileVault.Sftp@0.1.2-alpha
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FileVault.Sftp&version=0.1.2-alpha&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FileVault.Sftp&version=0.1.2-alpha&prerelease
                    
Install as a Cake Tool

FileVault

CI NuGet License: MIT

A platform-independent .NET library for working with file systems. The single IFileProvider interface abstracts away the differences between the local file system, FTP, SFTP, and cloud storage. Extracted from the file subsystem of X-Filer Desktop.

Supported backends: Local FS · FTP/FTPS · SFTP · Yandex Disk · Google Drive · WSL (Windows)

Packages

Package NuGet Description
FileVault.Core NuGet Contracts and helpers. Depends only on BCL
FileVault.Local NuGet Local file system
FileVault.Ftp NuGet FTP/FTPS via FluentFTP
FileVault.Sftp NuGet SFTP via SSH.NET
FileVault.YandexDisk NuGet Yandex Disk via REST API
FileVault.GoogleDrive NuGet Google Drive via Drive API v3
FileVault.Wsl NuGet WSL distros via \\wsl$\ UNC paths (Windows 10 1903+)

Installation

dotnet add package FileVault.Core
dotnet add package FileVault.Local
dotnet add package FileVault.Ftp
dotnet add package FileVault.Sftp
dotnet add package FileVault.YandexDisk
dotnet add package FileVault.GoogleDrive
dotnet add package FileVault.Wsl        # Windows only

Quick Start

Local file system

using FileVault.Core;
using FileVault.Local;

var resolver = new LocalFileProviderResolver();
var provider = await resolver.ResolveAsync("/home/user/Documents");

await foreach (var item in provider!.GetItemsAsync(FileProviderFilter.Default))
{
    Console.WriteLine($"{item.Name}  {item.Size?.ToString() ?? "<dir>"}");
}

FTP

using FileVault.Ftp;

var resolver = new FtpFileProviderResolver(new FtpConnection
{
    Host     = "ftp.example.com",
    Username = "user",
    Password = "secret",
});

var provider = await resolver.ResolveAsync("ftp://ftp.example.com/public");

SFTP

using FileVault.Sftp;

var resolver = new SftpFileProviderResolver(new SftpConnection
{
    Host           = "ssh.example.com",
    Username       = "deploy",
    PrivateKeyPath = "~/.ssh/id_rsa",   // or use Password
});

var provider = await resolver.ResolveAsync("sftp://ssh.example.com/var/data");

Yandex Disk

using FileVault.YandexDisk;

var resolver = new YandexDiskFileProviderResolver(oauthToken: "y0_AgAAAA...");

// Disk root
var provider = await resolver.ResolveAsync("x-filevault:yandex-disk");

// Specific folder
var photos = await resolver.ResolveAsync("disk:/Photos");

Google Drive

using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Services;
using FileVault.GoogleDrive;

// Build an authenticated DriveService (OAuth2 user credentials example)
var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
    GoogleClientSecrets.FromFile("client_secrets.json").Secrets,
    [DriveService.Scope.Drive],
    user: "user",
    CancellationToken.None);

var driveService = new DriveService(new BaseClientService.Initializer
{
    HttpClientInitializer = credential,
    ApplicationName = "MyApp",
});

// FileVault takes the pre-authenticated service — no OAuth logic inside the provider
var resolver = new GoogleDriveFileProviderResolver(driveService);

// My Drive root
var root = await resolver.ResolveAsync("x-filevault:google-drive");

// Specific folder by Drive file ID
var folder = await resolver.ResolveAsync("gdrive:1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs");

Authentication is the caller's responsibility. Pass any DriveService — OAuth2 user, service account, or impersonation. See Google Auth Library docs.

WSL (Windows Subsystem for Linux)

Requires Windows 10 1903+ with WSL installed. No-op on macOS/Linux.

using FileVault.Wsl;

var resolver = new WslFileProviderResolver();

// List installed distros
var drives = await resolver.GetDrivesAsync();
foreach (var drive in drives)
    Console.WriteLine(drive.Name); // "Ubuntu", "Debian", ...

// Access a path inside a distro
// Route format: wsl://<distro>/<linux-path>
var provider = await resolver.ResolveAsync("wsl://Ubuntu/home/user/projects");

Multiple providers at once

CompositeFileProviderResolver queries a list of resolvers and returns the first match. Use it in integration entry points (UI, CLI).

using FileVault.Core;
using FileVault.Ftp;
using FileVault.Local;

var resolver = new CompositeFileProviderResolver([
    new LocalFileProviderResolver(),
    new FtpFileProviderResolver(new FtpConnection { Host = "ftp.example.com", ... }),
]);

// Works for any path — local or FTP
var provider = await resolver.ResolveAsync(path);

Core Operations

All operations return FileOperationResult<T> — exceptions never propagate to the caller.

// Create folder (appends " (2)", " (3)", etc. on name conflict)
var result = await provider.CreateFolderAsync("NewFolder");
if (result.IsSuccess)
    Console.WriteLine(result.Result!.FullName);

// Copy a file INTO this provider from any other provider
var copyResult = await destProvider.CopyFileInAsync(
    sourceFileItem,
    "/backups/report.pdf",
    new Progress<double>(v => Console.Write($"\r{v:P0}"))
);

// Move a file (same-drive: native move; cross-provider: copy + delete)
await destProvider.MoveFileInAsync(sourceFileItem, destPath, progress);

// Delete
await provider.DeleteAsync([item], toRecycleBin: false);

// Rename
await provider.RenameAsync(item, "new-name.txt");

// Resolve a unique destination path (file is NOT created on disk)
var dest = await provider.ResolveDestinationFileAsync(sourceFile, overwrite: false);
// "report.pdf" → "report (2).pdf" if the name is already taken

Copying folders across providers

CopyFolderInAsync returns null in Result — not an error — when the provider does not support native folder copying (FTP, SFTP, or cross-provider scenarios). The orchestrator is expected to recurse manually:

async Task RecursiveCopyAsync(IFolderItem source, IFileProvider dest, string destPath)
{
    // Attempt native copy (Yandex Disk: server-side; others: returns null)
    var result = await dest.CopyFolderInAsync(source, destPath, progress);
    if (result.IsSuccess && result.Result is not null)
        return; // done

    // No native support — recurse manually
    await dest.CreateFolderAsync(source.Name);
    var subProvider = source.CreateProvider();

    await foreach (var item in subProvider.GetItemsAsync(FileProviderFilter.Default))
    {
        if (item is IFileItem file)
            await dest.CopyFileInAsync(file, $"{destPath}/{file.Name}", progress);
        else if (item is IFolderItem folder)
            await RecursiveCopyAsync(folder, dest, $"{destPath}/{folder.Name}");
    }
}

Microsoft.Extensions.DependencyInjection

Each package registers its own IFileProviderResolver. CompositeFileProviderResolver aggregates them.

services.AddSingleton<IFileProviderResolver, LocalFileProviderResolver>();

services.AddSingleton<IFileProviderResolver>(_ =>
    new FtpFileProviderResolver(new FtpConnection
    {
        Host     = config["Ftp:Host"]!,
        Username = config["Ftp:Username"],
        Password = config["Ftp:Password"],
    }));

services.AddSingleton<IFileProviderResolver>(_ =>
    new GoogleDriveFileProviderResolver(/* your DriveService */));

// Windows only — silently returns empty on other platforms
services.AddSingleton<IFileProviderResolver, WslFileProviderResolver>();

services.AddSingleton<IFileProviderResolver>(sp =>
    new CompositeFileProviderResolver(sp.GetServices<IFileProviderResolver>()));

Adding a Custom Provider

  1. Implement IFileProvider (and IFileItem, IFolderItem for your items).
  2. Implement IFileProviderResolver.
  3. Write tests by inheriting FileProviderContractTests from the FileVault.Contract.Tests package — all 17 contract tests run automatically.
// In your MyProvider.Tests project:
// <PackageReference Include="FileVault.Contract.Tests" Version="..." />

public class MyProviderContractTests : FileProviderContractTests
{
    protected override Task<IFileProvider> CreateProviderAsync() { ... }
    protected override Task SeedFileAsync(string name, byte[] content) { ... }
    protected override Task SeedFolderAsync(string name) { ... }
    protected override Task<byte[]> ReadFileAsync(string name) { ... }
    protected override Task<bool> FileExistsAsync(string name) { ... }
    protected override string GetDestPath(string name) { ... }
    public override Task TearDown() { ... }
}

Development

Prerequisites

  • .NET 10 SDK
  • Docker (for FTP integration tests via TestContainers)

Build & Test

dotnet build
dotnet test tests/FileVault.Core.Tests
dotnet test tests/FileVault.Local.Tests

# FTP integration tests (requires Docker)
dotnet test tests/FileVault.Ftp.Tests --filter "Category=Integration"

# Google Drive integration tests (requires a service account JSON)
export GOOGLE_DRIVE_SERVICE_ACCOUNT_JSON=/path/to/sa.json
dotnet test tests/FileVault.GoogleDrive.Tests --filter "Category=Integration"

Releasing

Versions are driven by git tags:

git tag v1.2.0
git push origin v1.2.0

GitHub Actions will pack and publish all packages to NuGet.org automatically.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.1.3-alpha 56 5/17/2026
0.1.2-alpha 64 5/10/2026
0.0.0-alpha.0.12 61 5/10/2026
0.0.0-alpha.0.11 54 5/10/2026