CL.Storage
Safe mounted storage across local filesystems, S3, FTP/FTPS, SFTP, WebDAV, Azure Blob, Google Cloud Storage, and OpenStack Swift — with the connection options, transfer features, and diagnostics of a desktop file-transfer client.
| Package | CodeLogic.Storage |
| Library class | CL.Storage.StorageLibrary |
| Config files | config.storage.json · config.storage.<provider>.json |
| Target | .NET 10 / CodeLogic 4 |
| Result model | Result and Result<T>; cancellation throws OperationCanceledException |
This overview covers loading, the mount model, configuration, and the everyday API. The rest lives on four sub-pages:
- Connections — SFTP authentication, host keys, and jump hosts; FTP/FTPS and WebDAV options; proxies; session pools, retries, and keep-alive; runtime connections; testing settings and diagnosing a live connection.
- Transfers & Sync — safe copies and moves between connections, conflict policies, resume and append, progress and speed limits, the transfer queue, compare and sync, watching for changes, and links in transfers.
- Files & Attributes — permissions, ownership, timestamps, links, server-side checksums, metadata/tags/versions/signed URLs, raw commands, and free space.
- Errors & Events — the
storage.*error codes, transient-failure helpers, and every event the library publishes.
Install and start
dotnet add package CodeLogic.Storage
using CL.Storage;
await Libraries.LoadAsync<StorageLibrary>();
await CodeLogic.ConfigureAsync();
await CodeLogic.StartAsync();
var storage = Libraries.Get<StorageLibrary>();
IStorageService files = storage.DefaultStorage;
IStorageService archive = storage.GetStorage("archive");
Throughout these pages storage is the StorageLibrary (connections, cross-connection transfers,
diagnostics) and files is one connection's IStorageService (file operations on that mount).
GetStorage returns a stable proxy. Replacing a connection does not invalidate the proxy, and the
old backend stays alive until active operations and returned download/native-session leases drain.
Mount model and paths
Each connection mounts one security boundary:
- a local directory or UNC root;
- an S3/GCS bucket and optional prefix;
- an Azure/Swift container and optional prefix;
- an FTP, SFTP, or WebDAV directory.
API paths are relative to that mount. Backslashes are normalized to /, redundant . segments are
removed, and absolute or parent-escaping paths fail with storage.invalid_path. The empty path means
the mounted root for info/listing and idempotent root directory creation. Link targets, raw-command
paths, and every other path the library resolves stay inside the mount; only raw commands (off by
default) can reach outside it.
Configuration
The main storage section contains the master enable switch, default connection ID, bounded byte
download limit, health-check settings, and library-wide speed limits. Providers register these
sections:
| Section | Model | Important settings |
|---|---|---|
storage.local |
LocalConnectionConfig |
RootPath, FollowLinks |
storage.s3 |
S3ConnectionConfig |
Bucket, Prefix, Region, endpoint/auth, multipart bounds |
storage.ftp |
FtpConnectionConfig |
host/root/auth, encryption/data mode, TLS pins and versions, encoding, listing parser |
storage.sftp |
SftpConnectionConfig |
host/root, auth methods and keys, host-key pins or known_hosts, algorithms, jump host |
storage.webdav |
WebDavConnectionConfig |
endpoint/root, Basic/Digest/NTLM/Negotiate/bearer auth, TLS pins, client certificate |
storage.azure |
AzureBlobConnectionConfig |
container URI/prefix and credential mode |
storage.gcs |
GoogleCloudConnectionConfig |
bucket/prefix, project/credential, upload chunk size, emulator URL |
storage.swift |
SwiftConnectionConfig |
auth URL (Keystone or TempAuth), region, account/container/prefix |
Every section contains a case-insensitive Connections dictionary. IDs are globally unique, even
when the connections use different providers. Every remote connection accepts Proxy and
TransferLimits; FTP, SFTP, and WebDAV add Retry, and FTP and SFTP add Session. See
Connections.
Security defaults
- S3, WebDAV, GCS, and Swift custom endpoints require HTTPS unless
AllowInsecureHttpis enabled. - Endpoint user info, query strings, and fragments are rejected so secrets do not become configuration URLs.
- WebDAV custom headers cannot replace authorization/host/framing headers or contain line breaks.
- FTPS and WebDAV validate the certificate chain by default. Certificate (
TrustedCertificateSha256) or public-key (TrustedPublicKeySha256) pins deliberately trust a specific server; there is no accept-any switch. - SFTP needs a trusted host key: SHA-256
HostKeyFingerprints, an OpenSSHKnownHostsPath, orAutoAcceptHostKey = truefor trusted development environments only. - Raw FTP/SSH commands are disabled unless
AllowRawCommandsis set, because they are not confined to the mount. - Diagnostics, events, and error details never contain credentials, provider bodies, or signed URLs.
Everyday operations
await using var input = File.OpenRead("asset.bin");
var put = await files.UploadAsync("assets/asset.bin", input, new StorageUploadOptions
{
ConflictPolicy = StorageConflictPolicy.OverwriteIfNewer,
CreateParents = true,
ContentType = "application/octet-stream"
});
var info = await files.GetInfoAsync("assets/asset.bin");
var exists = await files.ExistsAsync("assets/asset.bin");
var page = await files.ListAsync("assets", new StorageListOptions
{
Recursive = true,
PageSize = 500,
NamePattern = "*.bin"
});
var download = await files.DownloadAsync("assets/asset.bin");
if (download.IsSuccess)
{
await using var owned = download.Value!;
await owned.CopyToAsync(output);
}
Upload streams are caller-owned and remain open. A successful DownloadAsync value owns its provider
response, session, and registry lease until disposed. DownloadBytesAsync is bounded by the global
limit or StorageDownloadOptions.MaxBufferedBytes.
Listings hide the library's own staging and backup items (.cl-storage-*, .clstorage-*); set
IncludeInternal to see them. IncludeHidden = false drops dot-files, and NamePattern filters
names with * and ? wildcards (case-insensitive).
EnumeratePagesAsync and EnumerateItemsAsync walk provider tokens lazily and fail safely if a
provider repeats a token. Batch helpers preserve input order, cap item count/concurrency, and retain
one result per item rather than stopping at the first expected provider failure.
StorageServiceExtensions adds UploadFileAsync and atomic DownloadToFileAsync, bounded
ReadTextAsync/WriteTextAsync and ReadJsonAsync<T>/WriteJsonAsync<T>, and streaming checksums
(see Files & Attributes).
Capabilities
Capabilities are granular flags plus provider limits. Check them at runtime rather than inferring behavior from a provider name:
if (files.Capabilities.Supports(StorageFeature.Permissions))
await files.SetPermissionsAsync("deploy.sh", "750");
Optional features return storage.unsupported on connections that lack them. Flags include
MetadataWrite, Tags, Versioning, SignedReadUrls, SignedWriteUrls, Permissions, Ownership, SetTimestamps,
CreateLinks, ReadLinks, Checksums, Append, ResumableUpload, AtomicMove, RawCommands,
SpaceInfo, and ChangeNotifications.
Migration
The legacy CodeLogic.StorageS3 package accepted a bucket on every operation. CodeLogic.Storage
mounts a bucket/prefix per connection and uses relative paths. This release also renames several
error codes, adds ConflictPolicy, and extends StorageItem and StorageConnectionInfo. See the
package MIGRATION.md
for every mapping.