Linger.FileSystem.Sftp 1.4.0

dotnet add package Linger.FileSystem.Sftp --version 1.4.0
                    
NuGet\Install-Package Linger.FileSystem.Sftp -Version 1.4.0
                    
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="Linger.FileSystem.Sftp" Version="1.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Linger.FileSystem.Sftp" Version="1.4.0" />
                    
Directory.Packages.props
<PackageReference Include="Linger.FileSystem.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 Linger.FileSystem.Sftp --version 1.4.0
                    
#r "nuget: Linger.FileSystem.Sftp, 1.4.0"
                    
#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 Linger.FileSystem.Sftp@1.4.0
                    
#: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=Linger.FileSystem.Sftp&version=1.4.0
                    
Install as a Cake Addin
#tool nuget:?package=Linger.FileSystem.Sftp&version=1.4.0
                    
Install as a Cake Tool

Linger.FileSystem.Sftp

Overview

Linger.FileSystem.Sftp is an implementation of the Linger FileSystem abstraction that provides SFTP (SSH File Transfer Protocol) file operations support. It utilizes the SSH.NET library to offer a secure and reliable SFTP client for file operations with support for both password and certificate-based authentication.

Installation

dotnet add package Linger.FileSystem.Sftp

Features

  • Secure file operations over SFTP (upload, download, list, delete)
  • Support for both password and certificate-based authentication
  • Configurable retry policies for unstable networks
  • Timeout configurations
  • Integration with the Linger.FileSystem abstraction
  • Supports multiple .NET frameworks (net9.0, net8.0, netstandard2.0)
  • Unified batch operations and concurrency control (MaxDegreeOfParallelism)

Basic Usage

Creating an SFTP File System Instance with Password Authentication

// Create settings for remote SFTP system with password authentication
var settings = new RemoteSystemSetting
{
    Host = "sftp.example.com",
    Port = 22,
    UserName = "username",
    Password = "password",
    ConnectionTimeout = 15000, // 15 seconds
    OperationTimeout = 60000   // 60 seconds
};

// Configure retry options
var retryOptions = new RetryOptions
{
    MaxRetryAttempts = 3,
    DelayMilliseconds = 1000,
    MaxDelayMilliseconds = 5000
};

// Create SFTP file system
using var sftpSystem = new SftpFileSystem(settings, retryOptions);

// Connect to the server
await sftpSystem.ConnectAsync();

// Upload a file
await using var stream = File.OpenRead("./local/file.txt");
var result = await sftpSystem.UploadAsync(stream, "/remote/path/file.txt", overwrite: true);

if (result.Success)
{
    Console.WriteLine($"Upload successful: {result.FilePath}");
}

// Download a file
var downloadResult = await sftpSystem.DownloadFileAsync("/remote/path/file.txt", "C:/Downloads/file.txt");

if (downloadResult.Success)
{
    Console.WriteLine($"Downloaded {downloadResult.FileSize} bytes");
}

// Disconnect when done
await sftpSystem.DisconnectAsync();

File Upload Methods

// Method 1: Upload from stream to complete file path
await using var stream = File.OpenRead("local.txt");
var result = await sftpSystem.UploadAsync(stream, "/remote/path/file.txt", overwrite: true);

// Method 2: Upload local file to complete remote path
result = await sftpSystem.UploadFileAsync("C:/local/file.txt", "/remote/path/file.txt", overwrite: true);

// Method 3: Upload with separate directory and filename (convenient for dynamic naming)
result = await sftpSystem.UploadFileAsync(
    "C:/local/file.txt",           // Local file path
    "/remote/directory",            // Remote directory
    "custom-name.txt",              // Custom filename
    overwrite: true
);

Using Certificate-based Authentication

// Create settings for remote SFTP system with certificate authentication
var settings = new RemoteSystemSetting
{
    Host = "sftp.example.com",
    Port = 22,
    UserName = "username",
    CertificatePath = "/path/to/private/key.pem",
    CertificatePassphrase = "optional-passphrase", // If the private key is protected with a passphrase
    ConnectionTimeout = 15000, // 15 seconds
    OperationTimeout = 60000   // 60 seconds
};

// Create SFTP file system with certificate authentication
using var sftpSystem = new SftpFileSystem(settings);

// Connect and use as normal
sftpSystem.Connect();
// ... perform operations ...
sftpSystem.Disconnect();

Asynchronous Operations

The library also provides asynchronous methods for all operations:

// Connect asynchronously
await sftpSystem.ConnectAsync();

// Check if file exists asynchronously
if (await sftpSystem.FileExistsAsync("/remote/path/file.txt"))
{
    // Download file asynchronously
    var fileContent = await sftpSystem.ReadAllTextAsync("/remote/path/file.txt");
    
    // Process file content
    Console.WriteLine(fileContent);
}

// Disconnect asynchronously when done
await sftpSystem.DisconnectAsync();

Advanced Features

Working Directory Management

// Get current working directory
var currentDir = sftpSystem.GetCurrentDirectory();
Console.WriteLine($"Current directory: {currentDir}");

// Change working directory
sftpSystem.ChangeDirectory("/home/user/documents");

// Get directory listing with details
var files = sftpSystem.GetFiles("/remote/path", "*", SearchOption.TopDirectoryOnly);
foreach (var file in files)
{
    Console.WriteLine($"File: {file}");
}

// Get directories
var directories = sftpSystem.GetDirectories("/remote/path");
foreach (var dir in directories)
{
    Console.WriteLine($"Directory: {dir}");
}

Batch Operations

// Use the unified batch operations interface
// Combine with concurrency to improve throughput
var settings = new RemoteSystemSetting
{
    Host = "sftp.example.com",
    Port = 22,
    UserName = "username",
    Password = "password",
    ConnectionTimeout = 15000,
    OperationTimeout = 60000,
    MaxDegreeOfParallelism = 4 // 1 = serial, >1 = parallel (per-task connection)
};

var sftp = new SftpFileSystem(settings);
await sftp.ConnectAsync();

// Batch upload
var uploadResult = await sftp.UploadFilesAsync(new[]
{
    "C:/local/file1.txt",
    "C:/local/file2.txt"
}, "/remote/uploads", overwrite: true);
Console.WriteLine($"Uploaded: {uploadResult.SucceededFiles.Count}, Failed: {uploadResult.FailedFiles.Count}");

// Batch download
var downloadResult = await sftp.DownloadFilesAsync(new[]
{
    "/remote/uploads/file1.txt",
    "/remote/uploads/file2.txt"
}, "C:/downloads", overwrite: true);
Console.WriteLine($"Downloaded: {downloadResult.SucceededFiles.Count}, Failed: {downloadResult.FailedFiles.Count}");

// Batch delete
var deleteResult = await sftp.DeleteFilesAsync(new[]
{
    "/remote/uploads/file1.txt",
    "/remote/uploads/file2.txt"
});
Console.WriteLine($"Deleted: {deleteResult.SucceededFiles.Count}, Failed: {deleteResult.FailedFiles.Count}");

await sftp.DisconnectAsync();

// Failed items are available in FailedFiles with path, message, and exception

Batch Operation Progress Reporting

Monitor batch operation progress using the IProgress<BatchProgress> parameter:

// Create a progress handler
var progress = new Progress<BatchProgress>(p =>
{
    Console.WriteLine($"Progress: {p.Completed}/{p.Total} ({p.PercentComplete:F1}%)");
    Console.WriteLine($"Current file: {p.CurrentFile}");
    Console.WriteLine($"Succeeded: {p.Succeeded}, Failed: {p.Failed}");
});

// Batch upload with progress
var result = await sftp.UploadFilesAsync(files, "/remote/uploads", overwrite: true, progress);

// Batch download with progress
var downloadResult = await sftp.DownloadFilesAsync(remoteFiles, "C:/Downloads", overwrite: true, progress);

// Batch delete with progress
var deleteResult = await sftp.DeleteFilesAsync(filesToDelete, progress);

The BatchProgress struct provides:

  • Completed: Number of files processed (reported after each file completes)
  • Total: Total number of files
  • CurrentFile: Path of the file that was just processed
  • Succeeded: Number of successful operations
  • Failed: Number of failed operations
  • PercentComplete: Completion percentage (0-100)

Note: Progress is reported after each file operation completes, ensuring Completed always reflects the accurate count.

Concurrency

Control parallelism for batch operations via RemoteSystemSetting.MaxDegreeOfParallelism:

  • 1: single connection, serial execution (lower resource usage).
  • >1: independent SftpClient connection per task for thread safety and improved throughput.

Custom Connection Settings

// Advanced SFTP settings with custom configurations
var settings = new RemoteSystemSetting
{
    Host = "sftp.example.com",
    Port = 2222, // Custom port
    UserName = "username",
    Password = "password",
    
    // Connection settings
    ConnectionTimeout = 30000,    // 30 seconds
    OperationTimeout = 120000,    // 2 minutes
    
    // Concurrency for batch operations
    MaxDegreeOfParallelism = 4,
    
    // Connection pool idle timeout (optional)
    // Connections idle longer than this will be discarded and recreated
    ConnectionPoolIdleTimeout = TimeSpan.FromMinutes(5),
    
    // Batch operation retry settings
    BatchRetryOptions = new RetryOptions
    {
        MaxRetryAttempts = 3,
        DelayMilliseconds = 1000
    },
    
    // Encoding settings (if needed for special characters)
    Encoding = Encoding.UTF8
};

// Enhanced retry configuration
var retryOptions = new RetryOptions
{
    MaxRetryAttempts = 5,
    DelayMilliseconds = 2000,
    MaxDelayMilliseconds = 10000,
    UseExponentialBackoff = true // Exponential backoff
};

using var sftpSystem = new SftpFileSystem(settings, retryOptions);

Error Handling and Connection Management

try
{
    sftpSystem.Connect();
    
    // Perform operations with automatic retry
    if (sftpSystem.FileExists("/remote/important-file.txt"))
    {
        var content = sftpSystem.ReadAllText("/remote/important-file.txt");
        
        // Process content safely
        if (!string.IsNullOrEmpty(content))
        {
            // Save backup
            sftpSystem.WriteAllText("/remote/backup/important-file.bak", content);
        }
    }
}
catch (SftpException ex)
{
    Console.WriteLine($"SFTP Error: {ex.Message}");
    // Handle SFTP-specific errors
}
catch (SshException ex)
{
    Console.WriteLine($"SSH Error: {ex.Message}");
    // Handle SSH connection errors
}
catch (Exception ex)
{
    Console.WriteLine($"General Error: {ex.Message}");
    // Handle other errors
}
finally
{
    // Ensure disconnection
    if (sftpSystem.IsConnected)
    {
        sftpSystem.Disconnect();
    }
}

File Permissions and Attributes

// Check file attributes
var fileInfo = sftpSystem.GetFileInfo("/remote/path/file.txt");
Console.WriteLine($"File size: {fileInfo.Length} bytes");
Console.WriteLine($"Last modified: {fileInfo.LastWriteTime}");

// Create directory with specific permissions (Unix-like systems)
sftpSystem.CreateDirectory("/remote/path/new-directory");

// Note: File permissions are typically handled at the SSH server level
// Check if file is readable/writable
if (sftpSystem.FileExists("/remote/path/file.txt"))
{
    try
    {
        var testContent = sftpSystem.ReadAllText("/remote/path/file.txt");
        Console.WriteLine("File is readable");
    }
    catch (UnauthorizedAccessException)
    {
        Console.WriteLine("File is not readable");
    }
}

Streaming Operations for Large Files

// Stream large file download
using var remoteStream = sftpSystem.OpenRead("/remote/large-file.zip");
using var localStream = File.Create(@"C:\local\large-file.zip");

var buffer = new byte[8192]; // 8KB buffer
int bytesRead;
long totalBytes = 0;

while ((bytesRead = await remoteStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
    await localStream.WriteAsync(buffer, 0, bytesRead);
    totalBytes += bytesRead;
    
    // Report progress
    Console.WriteLine($"Downloaded: {totalBytes:N0} bytes");
}

Console.WriteLine($"Download completed: {totalBytes:N0} bytes total");

Configuration Examples

Production Configuration
var productionSettings = new RemoteSystemSetting
{
    Host = "prod-sftp.company.com",
    Port = 22,
    UserName = "prod-user",
    CertificatePath = "/secure/certs/prod-key.pem",
    ConnectionTimeout = 15000,
    OperationTimeout = 300000, // 5 minutes for large files
    Encoding = Encoding.UTF8
};

var productionRetry = new RetryOptions
{
    MaxRetryAttempts = 3,
    DelayMilliseconds = 5000,
    MaxDelayMilliseconds = 30000,
    UseExponentialBackoff = true
};
Development Configuration
var devSettings = new RemoteSystemSetting
{
    Host = "dev-sftp.company.com",
    Port = 22,
    UserName = "dev-user",
    Password = "dev-password",
    ConnectionTimeout = 10000,
    OperationTimeout = 60000,
    Encoding = Encoding.UTF8
};

var devRetry = new RetryOptions
{
    MaxRetryAttempts = 1,
    DelayMilliseconds = 1000,
    MaxDelayMilliseconds = 5000
};

Integration with Dependency Injection

// In your startup class
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IFileSystemOperations>(provider => {
        var settings = new RemoteSystemSetting
        {
            Host = "sftp.example.com",
            Port = 22,
            UserName = "username",
            Password = "password",
            ConnectionTimeout = 15000,
            OperationTimeout = 60000
        };
        
        var retryOptions = new RetryOptions
        {
            MaxRetryAttempts = 3,
            DelayMilliseconds = 1000,
            MaxDelayMilliseconds = 5000
        };
        
        return new SftpFileSystem(settings, retryOptions);
    });
}

Best Practices

  1. Connection Management: Always use using statements or ensure proper disposal of SFTP connections
  2. Error Handling: Implement specific exception handling for SftpException and SshException
  3. Authentication: Prefer certificate-based authentication over passwords for production environments
  4. Timeouts: Configure appropriate timeouts based on your network conditions and file sizes
  5. Retry Logic: Use exponential backoff for retry attempts to avoid overwhelming the server
  6. Large Files: Use streaming operations for files larger than available memory
  7. Security: Store connection credentials securely using configuration management or key vaults

Dependencies

License

This project is licensed under the terms of the license provided with the Linger project.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

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 is compatible.  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.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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. 
.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 net461 was computed.  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.

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
1.4.0 30 5/6/2026
1.3.3-preview 32 5/5/2026
1.3.2-preview 95 4/29/2026
1.3.1-preview 92 4/28/2026
1.3.0-preview 87 4/27/2026
1.2.0-preview 100 3/29/2026
1.1.0 117 2/4/2026
1.0.3-preview 111 1/9/2026
1.0.2-preview 111 1/8/2026
1.0.0 321 11/12/2025
1.0.0-preview2 217 11/6/2025
1.0.0-preview1 217 11/5/2025
0.9.9 201 10/16/2025
0.9.8 219 10/14/2025
0.9.7-preview 199 10/13/2025
0.9.6-preview 187 10/12/2025
0.9.5 197 9/28/2025
0.9.4-preview 207 9/25/2025
0.9.3-preview 228 9/22/2025
0.9.1-preview 336 9/16/2025
Loading failed