Linger.FileSystem.Ftp 1.3.3-preview

This is a prerelease version of Linger.FileSystem.Ftp.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Linger.FileSystem.Ftp --version 1.3.3-preview
                    
NuGet\Install-Package Linger.FileSystem.Ftp -Version 1.3.3-preview
                    
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.Ftp" Version="1.3.3-preview" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Linger.FileSystem.Ftp" Version="1.3.3-preview" />
                    
Directory.Packages.props
<PackageReference Include="Linger.FileSystem.Ftp" />
                    
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.Ftp --version 1.3.3-preview
                    
#r "nuget: Linger.FileSystem.Ftp, 1.3.3-preview"
                    
#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.Ftp@1.3.3-preview
                    
#: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.Ftp&version=1.3.3-preview&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Linger.FileSystem.Ftp&version=1.3.3-preview&prerelease
                    
Install as a Cake Tool

Linger.FileSystem.Ftp

Overview

Linger.FileSystem.Ftp is an implementation of the Linger FileSystem abstraction that provides FTP file operations support. It uses the FluentFTP library to offer a robust and retry-capable FTP client for common file operations such as uploading, downloading, listing, and deleting files.

Installation

dotnet add package Linger.FileSystem.Ftp

Features

  • File operations over FTP (upload, download, list, delete)
  • Configurable retry policies for unstable networks
  • Timeout configurations
  • Seamless integration with other Linger.FileSystem components
  • Supports multiple .NET frameworks (net9.0, net8.0, netstandard2.0)

Basic Usage

Creating an FTP File System Instance

// Create settings for remote FTP system
var settings = new RemoteSystemSetting
{
    Host = "ftp.example.com",
    Port = 21,
    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 FTP file system
using var ftpSystem = new FtpFileSystem(settings, retryOptions);

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

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

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

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

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

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

File Upload Methods

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

// Method 2: Upload local file to complete remote path
result = await ftpSystem.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 ftpSystem.UploadFileAsync(
    "C:/local/file.txt",           // Local file path
    "/remote/directory",            // Remote directory
    "custom-name.txt",              // Custom filename
    overwrite: true
);

Integration with Dependency Injection

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

Advanced Features

Working Directory Management

// Set working directory
await ftpSystem.SetWorkingDirectoryAsync("/public_html");

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

Directory Listing and Manipulation

// List directory contents
var files = await ftpSystem.ListDirectoryAsync("/public_html");
foreach (var file in files)
{
    Console.WriteLine($"File: {file.Name}, Size: {file.Size}, Modified: {file.Modified}");
}

// Create directory
await ftpSystem.CreateDirectoryAsync("/public_html/uploads");

// Check if directory exists
bool exists = await ftpSystem.DirectoryExistsAsync("/public_html/uploads");

Batch File Operations

// Batch upload to a remote directory
var localFiles = new[] { "C:/data/a.txt", "C:/data/b.txt", "C:/data/c.txt" };
var uploadResult = await ftpSystem.UploadFilesAsync(localFiles, "/remote/uploads", overwrite: true);
Console.WriteLine($"Uploaded: {uploadResult.SucceededFiles.Count}, Failed: {uploadResult.FailedFiles.Count}");

// Batch download into a local directory
var remoteFiles = new[] { "/remote/uploads/a.txt", "/remote/uploads/b.txt" };
var downloadResult = await ftpSystem.DownloadFilesAsync(remoteFiles, "C:/Downloads", overwrite: true);
Console.WriteLine($"Downloaded: {downloadResult.SucceededFiles.Count}, Failed: {downloadResult.FailedFiles.Count}");

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

Each batch call returns a BatchOperationResult containing SucceededFiles and FailedFiles with detailed error information.

Batch Operation Progress Reporting

You can 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 reporting
var localFiles = new[] { "C:/data/a.txt", "C:/data/b.txt", "C:/data/c.txt" };
var result = await ftpSystem.UploadFilesAsync(localFiles, "/remote/uploads", overwrite: true, progress);

// Batch download with progress
var remoteFiles = new[] { "/remote/uploads/a.txt", "/remote/uploads/b.txt" };
var downloadResult = await ftpSystem.DownloadFilesAsync(remoteFiles, "C:/Downloads", overwrite: true, progress);

// Batch delete with progress
var deleteResult = await ftpSystem.DeleteFilesAsync(new[] { "/remote/old.txt" }, 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.

Custom Connection Settings

var settings = new RemoteSystemSetting
{
    Host = "ftp.example.com",
    Port = 21,
    UserName = "username",
    Password = "password",
    ConnectionTimeout = 30000,           // 30 seconds connection timeout
    OperationTimeout = 120000,           // 2 minutes operation timeout
    Type = "FTP",
    
    // Concurrency for batch operations: 1 = serial, >1 = parallel
    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
    }
};

// Advanced retry configuration
var retryOptions = new RetryOptions
{
    MaxRetryAttempts = 5,
    DelayMilliseconds = 2000,
    MaxDelayMilliseconds = 30000,
    UseExponentialBackoff = true    // Use exponential backoff for retries
};

var ftpSystem = new FtpFileSystem(settings, retryOptions);

### Concurrency for Batch Operations

You can control parallelism for batch upload/download/delete via `RemoteSystemSetting.MaxDegreeOfParallelism`.

Behavior:
- `MaxDegreeOfParallelism = 1`: single connection, serial execution.
- `MaxDegreeOfParallelism > 1`: per-task independent `AsyncFtpClient` connections for thread safety and throughput.

Example:

```csharp
var settings = new RemoteSystemSetting
{
    Host = "ftp.example.com",
    Port = 21,
    UserName = "username",
    Password = "password",
    ConnectionTimeout = 15000,
    OperationTimeout = 60000,
    MaxDegreeOfParallelism = 4
};

var ftp = new FtpFileSystem(settings);
await ftp.ConnectAsync();

var files = new[] { "C:/data/a.txt", "C:/data/b.txt", "C:/data/c.txt" };
var result = await ftp.UploadFilesAsync(files, "/remote/uploads", overwrite: true);
Console.WriteLine($"Uploaded: {result.SucceededFiles.Count}, Failed: {result.FailedFiles.Count}");

### File Information and Metadata

```csharp
// Get file size
long fileSize = await ftpSystem.GetFileSizeAsync("/remote/file.txt");

// Get file last modified time
DateTime modTime = await ftpSystem.GetModifiedTimeAsync("/remote/file.txt");

// Check if file exists
bool exists = await ftpSystem.FileExistsAsync("/remote/file.txt");

Connection Management Best Practices

// Method 1: Automatic connection management with using statement
using (var ftpSystem = new FtpFileSystem(settings))
{
    // Connection is automatically established and closed
    await ftpSystem.UploadFileAsync("local.txt", "/remote/path");
    await ftpSystem.DownloadFileAsync("/remote/file.txt", "downloaded.txt");
}

// Method 2: Manual connection management for multiple operations
var ftpSystem = new FtpFileSystem(settings);
try
{
    await ftpSystem.ConnectAsync();
    
    // Perform multiple operations efficiently
    for (int i = 0; i < 10; i++)
    {
        await ftpSystem.UploadFileAsync($"file{i}.txt", $"/remote/file{i}.txt");
    }
}
finally
{
    await ftpSystem.DisconnectAsync();
}

Error Handling and Troubleshooting

Common FTP Exceptions

try
{
    await ftpSystem.UploadFileAsync("local.txt", "/remote/path");
}
catch (FileSystemException ex)
{
    switch (ex.Operation)
    {
        case "Upload":
            Console.WriteLine($"Upload failed: {ex.Message}");
            break;
        case "Connect":
            Console.WriteLine($"Connection failed: {ex.Message}");
            break;
    }
}
catch (TimeoutException ex)
{
    Console.WriteLine($"Operation timed out: {ex.Message}");
}

Retry Configuration for Unstable Networks

var retryOptions = new RetryOptions
{
    MaxRetryAttempts = 10,             // Retry up to 10 times
    DelayMilliseconds = 1000,       // Start with 1 second delay
    MaxDelayMilliseconds = 60000,   // Maximum 60 seconds delay
    UseExponentialBackoff = true    // Increase delay exponentially
};

var ftpSystem = new FtpFileSystem(settings, retryOptions);

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.1-preview 43 5/12/2026
1.4.0 67 5/6/2026
1.3.3-preview 80 5/5/2026
1.3.2-preview 91 4/29/2026
1.3.1-preview 91 4/28/2026
1.3.0-preview 88 4/27/2026
1.2.0-preview 103 3/29/2026
1.1.0 121 2/4/2026
1.0.3-preview 117 1/9/2026
1.0.2-preview 113 1/8/2026
1.0.0 313 11/12/2025
1.0.0-preview2 217 11/6/2025
1.0.0-preview1 217 11/5/2025
0.9.9 211 10/16/2025
0.9.8 200 10/14/2025
0.9.7-preview 190 10/13/2025
0.9.6-preview 182 10/12/2025
0.9.5 184 9/28/2025
0.9.4-preview 213 9/25/2025
0.9.3-preview 233 9/22/2025
Loading failed