![]() |
VOOZH | about |
dotnet add package Linger.FileSystem --version 1.4.2
NuGet\Install-Package Linger.FileSystem -Version 1.4.2
<PackageReference Include="Linger.FileSystem" Version="1.4.2" />
<PackageVersion Include="Linger.FileSystem" Version="1.4.2" />Directory.Packages.props
<PackageReference Include="Linger.FileSystem" />Project file
paket add Linger.FileSystem --version 1.4.2
#r "nuget: Linger.FileSystem, 1.4.2"
#:package Linger.FileSystem@1.4.2
#addin nuget:?package=Linger.FileSystem&version=1.4.2Install as a Cake Addin
#tool nuget:?package=Linger.FileSystem&version=1.4.2Install as a Cake Tool
A unified file system abstraction library providing a consistent interface for accessing different file systems, including local file system, FTP, and SFTP. With this library, you can use the same API to operate on different types of file systems, simplifying the development process and improving code reusability.
The Linger.FileSystem solution includes the following NuGet packages:
# Install core library
dotnet add package Linger.FileSystem
# Install FTP support
dotnet add package Linger.FileSystem.Ftp
# Install SFTP support
dotnet add package Linger.FileSystem.Sftp
IFileSystemOperations interface to operate different types of file systemsIncrementalHash and ArrayPool<byte> for memory-efficient large file processing, supporting files of any sizeIProgress<BatchProgress> for batch upload, download, and delete operationsConnectionPoolIdleTimeoutBatchRetryOptions settings// Create local file system instance
var localFs = new LocalFileSystem("C:/Storage");
// Upload file
using var fileStream = File.OpenRead("test.txt");
var result = await localFs.UploadAsync(fileStream, "uploads/destination-file.txt", true);
// Check upload result
if (result.Success)
{
Console.WriteLine($"File uploaded successfully: {result.FilePath}");
}
Local batch operations support configurable parallelism via LocalFileSystemOptions.MaxDegreeOfParallelism:
1: serial execution (default), lower resource usage>1: parallel execution (internally throttled), ideal for large batchesExample:
// Configure parallelism and use unified batch operations
var options = new LocalFileSystemOptions
{
RootDirectoryPath = "C:/Storage",
MaxDegreeOfParallelism = 4 // 1 = serial, >1 = parallel
};
var localFs = new LocalFileSystem(options);
// Batch upload into a directory under the root (copies into C:/Storage/uploads)
var uploadResult = await localFs.UploadFilesAsync(new[]
{
"C:/in/a.txt",
"C:/in/b.txt"
}, "uploads", overwrite: true);
Console.WriteLine($"Uploaded: {uploadResult.SucceededFiles.Count}, Failed: {uploadResult.FailedFiles.Count}");
// Batch download: copy from root-relative paths into a local directory
var downloadResult = await localFs.DownloadFilesAsync(new[]
{
"uploads/a.txt",
"uploads/b.txt"
}, "C:/downloads", overwrite: true);
Console.WriteLine($"Downloaded: {downloadResult.SucceededFiles.Count}, Failed: {downloadResult.FailedFiles.Count}");
// Batch delete: pass root-relative paths
var deleteResult = await localFs.DeleteFilesAsync(new[]
{
"uploads/a.txt",
"uploads/b.txt"
});
Console.WriteLine($"Deleted: {deleteResult.SucceededFiles.Count}, Failed: {deleteResult.FailedFiles.Count}");
These batch APIs return BatchOperationResult with SucceededFiles and FailedFiles (failed items include error message and exception).
Use IProgress<BatchProgress> to monitor the progress of batch operations:
// 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 uploadResult = await fileSystem.UploadFilesAsync(files, "/uploads", overwrite: true, progress);
// Batch download with progress
var downloadResult = await fileSystem.DownloadFilesAsync(remoteFiles, "C:/Downloads", overwrite: true, progress);
// Batch delete with progress
var deleteResult = await fileSystem.DeleteFilesAsync(filesToDelete, progress);
BatchProgress structure contains:
Completed: Number of files processed (reported after each file completes)Total: Total number of filesCurrentFile: Path of the file that was just processedSucceeded: Number of successful operationsFailed: Number of failed operationsPercentComplete: Completion percentage (0-100)Note: Progress is reported after each file operation completes, ensuring Completed always reflects the accurate count.
For FTP file system operations, install the FTP package:
Install-Package Linger.FileSystem.Ftp
Basic usage example:
using Linger.FileSystem.Ftp;
var ftpSetting = new RemoteSystemSetting
{
Host = "ftp.example.com",
Port = 21,
UserName = "username",
Password = "password",
Type = "FTP"
};
var ftpFs = new FtpFileSystem(ftpSetting);
var result = await ftpFs.UploadAsync(fileStream, "/public_html/test.txt", true);
📖 For detailed FTP documentation and advanced features, see:
For SFTP file system operations, install the SFTP package:
Install-Package Linger.FileSystem.Sftp
Basic usage example:
using Linger.FileSystem.Sftp;
var sftpSetting = new RemoteSystemSetting
{
Host = "sftp.example.com",
Port = 22,
UserName = "username",
Password = "password",
Type = "SFTP"
};
var sftpFs = new SftpFileSystem(sftpSetting);
var result = await sftpFs.UploadAsync(fileStream, "/home/user/test.txt", true);
📖 For detailed SFTP documentation and advanced features, see:
// Upload stream
using var stream = File.OpenRead("local-file.txt");
var result = await fileSystem.UploadAsync(stream, "uploads/destination-file.txt", true);
// Upload local file
result = await fileSystem.UploadFileAsync("local-file.txt", "uploads", true);
// Upload with cancellation token
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
result = await fileSystem.UploadAsync(
stream,
"uploads/destination-file.txt",
true,
cts.Token);
// Download to stream
using var outputStream = new MemoryStream();
var result = await fileSystem.DownloadToStreamAsync("uploads/file.txt", outputStream);
// Download to local file
result = await fileSystem.DownloadFileAsync("uploads/file.txt", "C:/Downloads/downloaded-file.txt", true);
// Download with cancellation token
using var cts = new CancellationTokenSource();
result = await fileSystem.DownloadFileAsync(
"uploads/large-file.zip",
"C:/Downloads/large-file.zip",
true,
cts.Token);
var result = await fileSystem.DeleteAsync("uploads/file-to-delete.txt");
// Check if directory exists
bool exists = await fileSystem.DirectoryExistsAsync("uploads/images");
// Create directory
await fileSystem.CreateDirectoryIfNotExistsAsync("uploads/documents");
// Check if path is a directory
if (await fileSystem.IsDirectoryAsync("uploads/images"))
{
Console.WriteLine("Path is a directory");
}
For efficient streaming operations without loading entire files into memory:
// Open file for reading (returns raw Stream)
await using var readStream = await fileSystem.OpenReadAsync("data/large-file.bin", cancellationToken);
await ProcessLargeFileAsync(readStream);
// Open file for writing
await using var writeStream = await fileSystem.OpenWriteAsync("output/result.bin", overwrite: true, cancellationToken);
await writeStream.WriteAsync(data, cancellationToken);
// Text file reading with StreamReader
using var reader = await fileSystem.GetReaderAsync("logs/app.log", Encoding.UTF8, cancellationToken);
while (await reader.ReadLineAsync() is { } line)
{
ProcessLine(line);
}
// Text file writing with StreamWriter
await using var writer = await fileSystem.GetWriterAsync("output/report.csv", overwrite: true, Encoding.UTF8, cancellationToken);
await writer.WriteLineAsync("Name,Value");
await writer.WriteLineAsync("Item1,100");
// Get file size (returns null if file doesn't exist)
var fileSize = await fileSystem.GetFileSizeAsync("uploads/document.pdf", cancellationToken);
if (fileSize.HasValue)
{
Console.WriteLine($"File size: {fileSize.Value} bytes");
}
else
{
Console.WriteLine("File not found");
}
var options = new LocalFileSystemOptions
{
RootDirectoryPath = "C:/Storage", // Root directory path
DefaultNamingRule = NamingRule.Md5, // Default naming rule: Md5, Uuid, Normal
DefaultOverwrite = false, // Whether to overwrite files with same name by default
DefaultUseSequencedName = true, // Whether to use sequence naming on file name conflicts
ValidationLevel = FileValidationLevel.Full, // Validation level: None, SizeOnly, Full
CleanupOnValidationFailure = true, // Whether to cleanup files on validation failure
UploadBufferSize = 81920, // Upload buffer size
DownloadBufferSize = 81920, // Download buffer size
RetryOptions = new RetryOptions
{
MaxRetryAttempts = 3,
DelayMilliseconds = 1000
}
};
var localFs = new LocalFileSystem(options);
var remoteSetting = new RemoteSystemSetting
{
Host = "example.com", // Host address
Port = 21, // Port (FTP default 21, SFTP default 22)
UserName = "username", // Username
Password = "password", // Password
Type = "FTP", // Type: "FTP" or "SFTP"
ConnectionTimeout = 30000, // Connection timeout (milliseconds)
OperationTimeout = 60000, // Operation timeout (milliseconds)
MaxDegreeOfParallelism = 4, // Batch operation concurrency
// Connection pool idle timeout (connections idle longer than this will be recreated)
ConnectionPoolIdleTimeout = TimeSpan.FromMinutes(5),
// Batch operation retry settings
BatchRetryOptions = new RetryOptions
{
MaxRetryAttempts = 3,
DelayMilliseconds = 1000
},
// SFTP specific settings
CertificatePath = "", // Certificate path
CertificatePassphrase = "" // Certificate passphrase
};
The local file system supports three file naming rules:
// Use advanced upload functionality (with naming rule)
var uploadedInfo = await localFs.UploadAsync(
stream,
"source-file.txt", // Source file name
"container1", // Container name
"images", // Target path
NamingRule.Md5, // Naming rule: Normal, Md5, Uuid
false, // Whether to overwrite
true // Whether to use sequence naming
);
// Access uploaded information
Console.WriteLine($"File hash: {uploadedInfo.HashData}");
Console.WriteLine($"Relative path: {uploadedInfo.FilePath}");
Console.WriteLine($"Full path: {uploadedInfo.FullFilePath}");
For advanced FTP and SFTP features such as:
Please refer to the dedicated documentation:
// Method 1: Use using statement for automatic connection management
using (var ftpFs = new FtpFileSystem(remoteSetting))
{
// Operations automatically handle connection and disconnection
await ftpFs.UploadFileAsync("local.txt", "/remote/path");
}
// Method 2: Manual connection management
try
{
ftpFs.Connect();
// Execute multiple operations...
await ftpFs.UploadFileAsync("file1.txt", "/remote");
await ftpFs.UploadFileAsync("file2.txt", "/remote");
}
finally
{
ftpFs.Disconnect();
}
All file system operations support CancellationToken for graceful cancellation:
public class FileUploadService
{
private readonly IFileSystemOperations _fileSystem;
public FileUploadService(IFileSystemOperations fileSystem)
{
_fileSystem = fileSystem;
}
// Upload with timeout
public async Task<FileOperationResult> UploadWithTimeoutAsync(
Stream stream,
string destinationPath,
int timeoutSeconds = 300)
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds));
try
{
return await _fileSystem.UploadAsync(
stream,
destinationPath,
overwrite: true,
cancellationToken: cts.Token);
}
catch (OperationCanceledException)
{
return FileOperationResult.Failure("Upload cancelled due to timeout");
}
}
// Batch upload with cancellation
public async Task<List<FileOperationResult>> UploadMultipleFilesAsync(
Dictionary<Stream, string> files,
CancellationToken cancellationToken)
{
var results = new List<FileOperationResult>();
foreach (var (stream, path) in files)
{
cancellationToken.ThrowIfCancellationRequested();
var result = await _fileSystem.UploadAsync(
stream,
path,
overwrite: true,
cancellationToken);
results.Add(result);
}
return results;
}
}
[ApiController]
[Route("api/[controller]")]
public class FileController : ControllerBase
{
private readonly IFileSystemOperations _fileSystem;
public FileController(IFileSystemOperations fileSystem)
{
_fileSystem = fileSystem;
}
[HttpPost("upload")]
public async Task<IActionResult> UploadFile(
IFormFile file,
CancellationToken cancellationToken)
{
try
{
using var stream = file.OpenReadStream();
var result = await _fileSystem.UploadAsync(
stream,
$"uploads/{file.FileName}",
overwrite: true,
cancellationToken);
if (result.Success)
{
return Ok(new { path = result.FilePath });
}
return BadRequest(result.ErrorMessage);
}
catch (OperationCanceledException)
{
return StatusCode(499, "Upload cancelled by client");
}
}
}
try
{
var result = await ftpFs.UploadFileAsync("local.txt", "/remote");
if (result.Success)
{
Console.WriteLine($"Upload successful: {result.FilePath}");
}
else
{
Console.WriteLine($"Upload failed: {result.ErrorMessage}");
}
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled");
}
catch (FileSystemException ex)
{
Console.WriteLine($"File system operation exception: {ex.Message}");
Console.WriteLine($"Operation: {ex.Operation}, Path: {ex.Path}");
if (ex.InnerException != null)
{
Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
}
}
var retryOptions = new RetryOptions
{
MaxRetryAttempts = 5, // Maximum 5 retries
DelayMilliseconds = 1000, // Initial delay 1 second
MaxDelayMilliseconds = 30000, // Maximum delay 30 seconds
UseExponentialBackoff = true // Use exponential backoff algorithm
};
// Configure retry options for remote file system
var ftpFs = new FtpFileSystem(remoteSetting, retryOptions);
Default buffer size is 81920 bytes (80KB). For large file processing scenarios, you can increase the buffer size to improve throughput:
var options = new LocalFileSystemOptions
{
RootDirectoryPath = "C:/Storage",
UploadBufferSize = 262144, // 256KB - suitable for large file uploads
DownloadBufferSize = 262144 // 256KB - suitable for large file downloads
};
💡 Larger buffers improve performance for large files but increase memory usage per concurrent operation.
For scenarios requiring processing of large numbers of files, use batch processing APIs to reduce connection overhead:
// FTP system batch operation example - more efficient than individual operations
string[] localFiles = Directory.GetFiles("local/directory", "*.txt");
await ftpFs.UploadFilesAsync(localFiles, "/remote/path");
IFileSystem
│
IFileSystemOperations
/ \
ILocalFileSystem IRemoteFileSystem
FileSystemBase
/ \
LocalFileSystem RemoteFileSystemBase
/ \
FtpFileSystem SftpFileSystem
This library uses the following design patterns:
File Upload Workflow:
Remote System Connection Management:
EnsureConnectedAsync() for automatic connection managementWe welcome Pull Requests and Issues to help us improve this library.
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 2 NuGet packages that depend on Linger.FileSystem:
| Package | Downloads |
|---|---|
|
Linger.FileSystem.Ftp
An FTP implementation of the Linger FileSystem abstraction. Provides a robust and retry-capable FTP client for file operations using FluentFTP. Supports common file system operations such as uploading, downloading, listing, and deleting files. |
|
|
Linger.FileSystem.Sftp
An SFTP implementation of the Linger FileSystem abstraction. Provides a secure file transfer protocol client using SSH.NET for file operations. Supports both password and certificate-based authentication with configurable retry capabilities. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.4.4-preview | 83 | 6/16/2026 |
| 1.4.3-preview | 167 | 6/15/2026 |
| 1.4.2 | 192 | 5/20/2026 |
| 1.4.1-preview | 176 | 5/12/2026 |
| 1.4.0 | 175 | 5/6/2026 |
| 1.3.3-preview | 169 | 5/5/2026 |
| 1.3.2-preview | 174 | 4/29/2026 |
| 1.3.1-preview | 178 | 4/28/2026 |
| 1.3.0-preview | 174 | 4/27/2026 |
| 1.2.0-preview | 184 | 3/29/2026 |
| 1.1.0 | 206 | 2/4/2026 |
| 1.0.3-preview | 199 | 1/9/2026 |
| 1.0.2-preview | 192 | 1/8/2026 |
| 1.0.0 | 400 | 11/12/2025 |
| 1.0.0-preview2 | 313 | 11/6/2025 |
| 1.0.0-preview1 | 299 | 11/5/2025 |
| 0.9.9 | 287 | 10/16/2025 |
| 0.9.8 | 294 | 10/14/2025 |
| 0.9.7-preview | 273 | 10/13/2025 |
| 0.9.6-preview | 264 | 10/12/2025 |