File Transfers

Use client.Transfers to upload and download files, folders and streams. The SDK adjusts parallel transfers where supported, retries temporary failures within a limit, and reports progress. Start with the paths; tuning is optional.

Upload a File

UploadFileAsync(localPath, destinationPath) uploads a local file. Set MkdirParents to create missing remote folders. The returned UploadResult describes the completed file, its parts and the bytes sent over the network.

Keep the source unchanged until the task finishes. The SDK opens it once and reads through that handle on every attempt, so replacing or renaming the path does not change the source. Windows prevents other programs from changing the file while the handle is open.

A successful task means Files.com completed the upload. If the final request's outcome is unknown, check the remote file before uploading again; see Cancellation and Failures.

Uploading a File

using FilesCom;
using FilesCom.Transfers;

FilesClient client = FilesClient.Create(new FilesConfiguration { ApiKey = "YOUR_API_KEY" });

UploadResult result = await client.Transfers.UploadFileAsync(
    @"C:\exports\report.csv",
    "reports/report.csv",
    new UploadOptions { MkdirParents = true });

Console.WriteLine($"Uploaded {result.Size} bytes in {result.Parts.Count} parts");

Downloads

DownloadFileAsync(remotePath, localPath) downloads into a staging file beside the destination. It puts the file in place only after all bytes have arrived and passed validation. By default, it replaces an existing local file. Choose PublicationPolicy = DownloadPublicationPolicy.CreateNew to keep anything already at that path. See Putting Downloaded Files in Place for the policies and platform limits.

The SDK uses parallel parts where storage supports both ranges and reliable version checks, and fetches the whole file in one response when needed. A version-bound download fails with DownloadSourceChangedException if the source changes, instead of combining different versions. DownloadResult.VersionBound reports version protection, not whether parts were used: a whole-response fallback can remain bound to a version. Unbound downloads cannot reuse partial bytes on a later run.

Temporary failures are retried within MaxNetworkRetries. A whole-response download into a local file can restart from its first byte after a failed attempt. Cancellation or a failure before publication leaves the destination file unchanged. A report of the last byte written is not completion: await the task.

Use DownloadStreamAsync(remotePath, stream) to write into your own stream, from its current position, in order. The SDK leaves it open and never writes the same bytes into it twice, so it can restart only before writing any bytes. Parts fetched ahead are buffered within the transfer limiter's memory bound.

Downloading a File

DownloadResult result = await client.Transfers.DownloadFileAsync(
    "reports/report.csv",
    "report.csv",
    new DownloadOptions
    {
        PublicationPolicy = DownloadPublicationPolicy.CreateNew,
    });

Console.WriteLine($"Downloaded {result.Size} bytes; bound to a version: {result.VersionBound}");

Downloading Into a Stream

using (Stream destination = System.IO.File.Create("report.csv"))
{
    await client.Transfers.DownloadStreamAsync("reports/report.csv", destination);
}

The stream example opens the local file itself, so it truncates an existing file immediately. Use DownloadFileAsync when you need staging and overwrite protection.

Folders

UploadDirectoryAsync(localDirectory, remoteDirectory) and DownloadDirectoryAsync(remoteDirectory, localDirectory) transfer whole folder trees, preserving relative paths and empty folders. Both list the source first, then start files in path order. FileOptions supplies each file's settings.

Automatic mode shares a concurrency target among the folder's files, with separate targets when upload destinations need different tuning. It adjusts both active files and parts, up to MaxConcurrentFiles if set, or 128 otherwise. In static mode, MaxConcurrentFiles defaults to 4, and each file has its own part concurrency. DirectoryTransferProgress.FileLimit shows the current limit. All files share the folder transfer's limiter.

Uploads exclude symbolic links and other entries that are neither regular files nor directories, and list them in DirectoryTransferResult.Excluded. Downloads refuse symbolic links in local folder paths and remote names that cannot be local names, such as .. or a name containing : on Windows, before writing anything.

The first failure stops active files and prevents new ones from starting. The task throws that failure after all active files stop. Files already completed stay completed; a folder download does not replace the tree as one operation. A Progress.Report exception is also a failure. Exceptions from cancellation callbacks registered on SDK-provided stream tokens are logged during cleanup and do not replace an existing transfer failure.

Set CheckpointDirectory to continue stopped files and recognize completed files on a later run. Keep it outside the local tree, including through links. The SDK verifies the source before skipping a file: accepted upload bytes must still match; a download needs the same storage version and the local file it wrote. Size and modification time alone do not establish that the source is unchanged. Unbound downloads are downloaded again. See Continuing Stopped Transfers for checkpoint requirements.

Settings, including the client's settings and listing request options, are captured when the transfer starts. Changing them during a run has no effect. The complete source plan stays in memory until the task ends; MaxConcurrentFiles limits active transfers, not the memory needed to list a very large tree.

Uploading a Folder

DirectoryTransferResult uploaded = await client.Transfers.UploadDirectoryAsync(
    @"C:\exports",
    "exports",
    new DirectoryUploadOptions
    {
        MaxConcurrentFiles = 4,
        CheckpointDirectory = @"C:\transfer-state\exports",
    });

Console.WriteLine($"{uploaded.Files.Count} files uploaded, {uploaded.Excluded.Count} excluded");

Downloading a Folder

await client.Transfers.DownloadDirectoryAsync("reports", "reports", new DirectoryDownloadOptions
{
    CheckpointDirectory = "transfer-state/reports",
    FileOptions = new DownloadOptions
    {
        PublicationPolicy = DownloadPublicationPolicy.CreateNew,
    },
});

Files and Streams

UploadStreamAsync(stream, length, destinationPath) reads exactly length bytes from the current position. The overload without length reads until the stream ends. Neither seeks nor closes your stream. After failure or cancellation, its position is undefined. A file or stream shorter than its declared length fails with EndOfStreamException, without completing the upload.

Stream parts must be kept until accepted so retries send the same bytes. Choose where to keep them:

OptionBehavior
StreamPartStorage.Memory (default)Holds parts within the limiter's buffer bound. A part larger than the bound fails before it is read.
StreamPartStorage.TemporaryFilesKeeps each part in a temporary file, deleting it when accepted or when the upload ends. TemporaryDirectory chooses the folder.
SpoolMaxBytesCopies the entire unknown-length stream to a temporary file first, up to this bound, then uploads it as a known-length file. Nothing is sent if the copy exceeds the bound, fails or is cancelled.

Temporary files inherit the Windows temporary folder's permissions. Choose a private TemporaryDirectory for sensitive data. The same applies to a forward-only FileAttachment that must be copied.

Streams of Unknown Length

Set MaxStreamLength when you know an upper bound. A destination requiring a known size can refuse an unspooled stream before sending anything. An upload that exceeds its length or part-count bound stops without sending the excess, but parts already sent are not undone. Use SpoolMaxBytes if nothing should be sent before the full stream is read.

Where the SDK chooses part sizes, an unknown-length upload starts at 8 MiB (AutomaticPartSizing.UnknownLengthStartSize) and may grow. It sizes parts to fit the upper bound into the remaining parts. If in-memory parts cannot fit that bound, it uses server-planned sizes instead (PartSizing.ServerPlannedReason = MemoryLimit). With the default 128 MiB buffer limit, the maximum 5 TiB stream cannot fit in 10,000 memory-backed parts; set a smaller MaxStreamLength or use temporary files.

Where Files.com plans each part, unknown-length streams are sent serially in the planned sizes. Reads are always forward and serial, even when buffered parts can be sent in parallel. An exact part-boundary end sends no empty part. Read-ahead defaults to MaxReadyParts = 2 and MaxReadyBytes = 64 MiB; one larger part may be read ahead when no other part is waiting. Spooling reports SpooledBytes; TotalBytesKnown stays false until the size is known.

Streams do not support checkpoints. Use a local file when you need to resume a transfer across runs.

Uploading Streams

using (Stream source = await httpClient.GetStreamAsync(exportUrl))
{
    await client.Transfers.UploadStreamAsync(source, exportLength, "exports/export.zip");
}

// Read an unknown-length stream, bounded at 10 GiB, keeping parts on disk.
using (Stream source = await httpClient.GetStreamAsync(liveExportUrl))
{
    UploadResult result = await client.Transfers.UploadStreamAsync(source, "exports/live.zip", new UploadOptions
    {
        MaxStreamLength = 10L * 1024 * 1024 * 1024,
        StreamPartStorage = StreamPartStorage.TemporaryFiles,
    });
    Console.WriteLine($"Uploaded {result.Size} bytes");
}

Remote Files and Folders

client.FileSystem provides familiar file, folder and stream operations:

OperationWhat it does
OpenReadAsync(path)Opens a forward-only download stream.
OpenWriteAsync(path)Opens a forward-only upload stream; disposing it completes the upload.
ReadAllBytesAsync(path), WriteAllBytesAsync(path, bytes)Reads or writes the whole file.
FileExistsAsync(path), DirectoryExistsAsync(path)Checks whether the file or folder exists.
EnumerateAsync(path, recursive)Lists entries as enumeration advances, optionally including subfolders.
CreateDirectoryAsync(path)Creates the folder and missing parents; does nothing if it exists.
DeleteAsync(path, recursive), CopyAsync(source, destination), MoveAsync(source, destination)Waits for Files.com to finish the operation.

Remote streams cannot seek, append or change an existing file's contents. Seek, Length and Position throw NotSupportedException. A read stream delivers buffered bytes before raising a download failure. Disposing a write stream raises any upload failure; to abandon the upload instead of completing it, cancel the token passed to OpenWriteAsync before disposing the stream.

Reading and Writing Remote Files

using (Stream remote = await client.FileSystem.OpenReadAsync("reports/report.csv"))
using (var reader = new StreamReader(remote))
{
    Console.WriteLine(await reader.ReadLineAsync());
}

using (Stream remote = await client.FileSystem.OpenWriteAsync("reports/summary.txt"))
using (var writer = new StreamWriter(remote))
{
    await writer.WriteLineAsync("Done");
}

When using client.RemoteFiles.CopyAsync or MoveAsync directly, Files.com can return a pending FileAction. Call FileAction.WaitAsync() to wait for its migration; it throws FileMigrationFailedException if the migration fails or is cancelled. Details are on Migration and in DEBUG logs, and may include service-supplied addresses. client.RemoteFiles.WaitUntilGoneAsync(path) waits for a path to disappear, such as after recursive deletion. Use a cancellation token to bound either wait. Each operation uses the client settings, address and request options captured when it starts, including requests made while waiting.

Waiting for a Copy

FileAction copy = await client.RemoteFiles.CopyAsync("reports",
    new Dictionary<string, object> { { "destination", "archive/reports" } });
await copy.WaitAsync(cancellationToken: cancellationToken);

Progress

Set UploadOptions.Progress, DownloadOptions.Progress or the folder options' Progress to update your UI or job status. Uploads report when sending starts and when a part is accepted, rather than on a timer. Mark a job complete only after its task succeeds; a progress callback may run after task completion when reports are posted to a UI.

Reporting Progress

var options = new UploadOptions
{
    Progress = new Progress<TransferProgress>(progress =>
        Console.WriteLine($"{progress.AcknowledgedBytes} bytes accepted; {progress.Elapsed}")),
};

await client.Transfers.UploadFileAsync(localPath, destinationPath, options);
Console.WriteLine("Upload complete");
FieldUse
Upload AcknowledgedBytes; download WrittenBytesBytes accepted or checked and written, counted once.
Upload SentBytes; download ReceivedBytesNetwork bytes, including retries.
TotalBytes, TotalBytesKnownTotal size when known; otherwise bytes read or written so far. Check the flag before calculating a percentage.
Elapsed; folder BytesPerSecondDuration; folder speed based on this run's bytes only.
ActiveParts, upload PartRetries, download RetriesParts in progress and retry counts.
ConcurrencyTarget and limits, requests in flight, throughput, latency, throttles, cooldown and decisions. Null for downloads arriving in one response, including after a fallback.
Upload PartSizingPart-size source, limits, next size and growth decisions.
RequestWait, BufferWait; upload AdmissionWait, ReadyPartsTime waiting for shared resources or admission, and upload read-ahead.
ProcessWhole-process CPU and memory, sampled at most once a second.
Folder FilesDone, FilesTotal, FilesActive, FileLimitCompleted, planned and active files, and the current active-file limit.

UploadResult.FinalProgress and DownloadResult.FinalProgress retain the final snapshot. For automatic folder transfers, Concurrency.Shared means the target and decisions cover files together. An upload still reports its own Limit = 1 and SerialReason when its destination requires serial parts. Download concurrency also records time at the target, waiting for the limiter, without a ready part, and writing the destination.

Callback Behavior

Upload progress and trace handlers are called serially on SDK threads. Accepted-part reports happen after that part releases its limiter resources; other parts may still hold resources. Return promptly. If your IProgress<T>.Report implementation throws, the transfer fails with that exception.

Progress<T> posts to the context where it was created and returns immediately. Its handler exceptions occur in that context, rather than failing the transfer, and posted callbacks may arrive after the task ends.

How Parts Are Sent

Files.com supplies each upload part's address, size and permission to send parts in parallel or retry them. The SDK respects those instructions. Accepted parts are never sent again. After all parts are accepted, it makes one final completion request, which is never automatically retried.

MaxNetworkRetries defaults to 3; 0 disables the SDK's retries. Retryable failures are connection failures, timeouts, HTTP 408, HTTP 429, and 5xx other than 501. Upload parts are retried only when Files.com allows it, with the same bytes. Version-bound download parts are retried individually. Whole-response file downloads may restart; downloads into your stream retry only before any bytes have been written.

For storage requests, HTTP 429 or 503 waits for the server's Retry-After, without shortening it to MaxNetworkRetryDelay; without that header there is no retry wait. Other transient failures wait 100 milliseconds. If the platform cannot schedule the requested wait, the failure is thrown instead of retrying early. Automatic mode also pauses new part requests after throttling.

An address about to expire is renewed before use. An address refused as expired is renewed for the same part or storage object. Other 403 responses fail the transfer. Files.com API retry settings and their replay-safety rules are described in Configuration.

Addresses and Credentials

Upload parts go to the exact storage addresses Files.com provides, with the part's exact Content-Length, without an API key, session ID or Workspace selection. Upload storage redirects must preserve the method and bytes and never downgrade HTTPS to HTTP. Negotiated headers stay within their origin; credential headers such as Authorization are not forwarded to another origin. Each transfer uses the client settings captured at its start.

Cancellation and Failures

Pass a CancellationToken to stop a transfer or give it a total deadline. Disposing the client also stops its transfers. Cancellation and failure stop reads, requests, limiter waits, cooldowns and retry waits. The task ends after active parts stop and release handles and limiter resources, with OperationCanceledException or the first failure.

ReadTimeout and WriteTimeout detect stalled requests, not total transfer duration. Both default to 80 seconds. For uploads, reading your source does not count toward these network timeouts. Use a token with a deadline to limit the entire operation.

Upload Completion Can Be Unknown

Before the final request, a stopped upload is not completed, and accepted parts are not undone. Once the final request may have reached Files.com, a connection failure, unreadable response, error response or cancellation can leave its outcome unknown, even if the response reports an error. The SDK throws UploadCompletionUnknownException with the underlying failure in InnerException. Check the remote file before sending another upload.

A checkpoint recording a final request also refuses automatic replay on a later run, even with StartOver.

Other Failures

  • UploadLimitException derives from InvalidOperationException. Its Limit identifies StreamLength, PartCount, BufferSpace, MemoryPart, SpoolLength or FixedPartSize; its message explains what to change. Known limits fail before any part is sent. Unknown-length streams can hit limits later, without sending the excess.
  • Download connection failures throw ApiConnectionException; stalled requests throw NetworkTimeoutException, a subtype. These read failures have RequestMayHaveBeenProcessed = false. This flag does not undo bytes already written into your stream or a file already published.
  • Exceptions from your source or destination stream are propagated with their original type.
  • Local download publication can fail before or after the file is in place. Inspect DownloadPublicationException.Placed, as described below, before deciding how to recover.

Setting a Transfer Deadline

using (var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(30)))
{
    try
    {
        await client.Transfers.UploadFileAsync(localPath, destinationPath,
            cancellationToken: cancellation.Token);
    }
    catch (UploadCompletionUnknownException e)
    {
        Console.WriteLine($"Check {e.Path}: upload completion is unknown.");
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("The upload was cancelled.");
    }
}

Continuing Stopped Transfers

Set CheckpointPath for a local file transfer, or CheckpointDirectory for a folder transfer. After a failure, cancellation or process interruption, run the same transfer with the same site, workspace, local and remote paths, and checkpoint. Streams cannot use checkpoints.

Uploads verify accepted parts against the source bytes, then send only parts not yet accepted. UploadResult.ResumedParts counts the reused parts. Downloads confirm the stored version first, then fetch missing parts where storage honors ranges. If ranges are ignored, they fetch the same version from the beginning; DownloadResult.ResumedBytes counts reused bytes. Successful completion deletes the checkpoint.

Keep checkpoints on a private local disk, outside a folder transfer's local tree. They contain no credentials or signed addresses, but their integrity determines which bytes the SDK reuses. The SDK writes complete replacement checkpoints, at most every two seconds during a run and when it stops, flushing data before relying on it. The SDK flushes checkpoint contents but does not flush the directory rename on Windows, so a system crash or power loss can undo the latest checkpoint update.

Choosing Whether to Start Over

SituationBehavior
Upload source changed, upload expired, or granted limits changedTransferCheckpointException with SourceChanged, UploadExpired or GrantChanged; no part is sent. Delete the checkpoint or choose CheckpointRestartPolicy.StartOver to start a new upload.
Download's stored version is gone or its address now names another objectStarts from byte zero in a new staging file.
Version confirmation is cancelled, fails or is refused accessLeaves the download checkpoint and stage unchanged for another attempt.
Download stage is shorter than recordedFails by default; StartOver discards only a stage proven to belong to the transfer and starts again.
Checkpoint belongs to another site, workspace or paths (OtherTransfer), is untrusted, or is from a newer unsupported formatRefuses it regardless of restart policy.
Upload checkpoint records the final requestSends nothing and throws UploadCompletionUnknownException, regardless of restart policy. Check the remote outcome first.

The final-upload record is flushed before sending the completion request. If that flush fails, the request is not sent; a record left behind still makes later runs refuse replay. Only when the failure explicitly establishes that the request was never sent is it safe to delete that record and upload again. If recording a stopped transfer also fails, the SDK logs the checkpoint failure and keeps the original transfer exception.

Checkpoint Trust and Platform Support

The SDK rejects unsafe checkpoints with TransferCheckpointException (Untrusted) before reading them. A file transfer sends no request first; a folder download lists the source first. Existing stages and destinations stay unchanged, though empty directories created for the transfer may remain. Restore the required privacy to retry.

  • The checkpoint directory must belong to you, SYSTEM or Administrators. Its ACL must prevent others from adding, removing or renaming entries, deleting the directory or changing its access.
  • Directories on the resolved path are held during checkpoint access; links and junctions are refused. Impersonated calls use the impersonated user's identity.
  • The checkpoint must be a regular file with one name, owned by you, SYSTEM or Administrators, that no other user can write. The SDK creates checkpoint files and missing checkpoint directories with private access.

Checkpoints also refuse recorded permissions beyond those of a new file. Restore the required directory and file access before retrying an untrusted checkpoint.

Download Recovery and Older Checkpoints

A version-bound, complete stage retained after a publication refusal can be published on a later run without fetching again, once storage confirms its version. DownloadResult.CompletedEarlier is true when a later run recognizes the file already published and finishes the remaining bookkeeping. Recognition uses file identity and length, not a content hash: an in-place edit preserving both may be treated as the original, and filesystems can reuse deleted file identities. Avoid concurrent changes to transfer files and directories.

A checkpoint format that an older SDK cannot read is refused without changing it, regardless of StartOver. Finish with an SDK that reads that format or use another checkpoint path. A partial 2.0-preview checkpoint bound by Files.com cannot be confirmed against storage; it is refused unless StartOver is selected. Upload checkpoint formats are unchanged.

Folder checkpoints recognize completed downloads only after the current storage version and local file identity and length match. Older folder records use length and modification time for local recognition; the default policy can still skip them after storage confirmation, but CreateNew and ReplaceRegularFile treat them as existing files. Every resumed-byte or completed-file verification uses buffers within the limiter's bound.

Continuing a File Transfer

var options = new UploadOptions { CheckpointPath = "backup.iso.checkpoint" };

// Run this same call again after interruption to reuse accepted parts.
UploadResult uploaded = await client.Transfers.UploadFileAsync(
    "backup.iso", "backups/backup.iso", options, cancellationToken);

DownloadResult downloaded = await client.Transfers.DownloadFileAsync(
    "backups/backup.iso", "restored.iso",
    new DownloadOptions { CheckpointPath = "restored.iso.checkpoint" },
    cancellationToken);

Putting Downloaded Files in Place

DownloadOptions.PublicationPolicy controls the final local name. The policy of the current run applies even when resuming an older checkpoint. DownloadStreamAsync and OpenReadAsync accept only the default policy, because they have no local file to publish.

PolicyExisting destination
ReplaceExisting (default)Replaces it using the Windows access behavior below.
CreateNewRefuses anything already at the name, including folders and dangling links.
ReplaceRegularFileCreates a file or replaces a regular file, preserving the access described below. Refuses folders, links and special files, without following them. This is PowerShell's -Force behavior.

A refused destination fails before downloading when already present, and is checked again at publication. CreateNew never replaces an existing entry. It holds the destination directory and its ancestors while the file is downloaded, preventing directory renames. ReplaceRegularFile checks the file type and replaces it in separate steps; another program changing directory entries can change the entry between those steps.

Recovering From Publication Failure

DownloadPublicationException is an IOException. Inspect its fields:

FieldMeaning
ProblemWhy publication failed: for example, DestinationExists, DestinationNotRegularFile, AccessNotKept, NotSupported, StageReplaced or DestinationMoved.
PlacedWhether the downloaded file was already put in place. It stays there after a subsequent failure.
RetainedWhether a private, version-bound stage was kept with a checkpoint for a later attempt.

With a checkpoint, a complete stage is flushed and recorded before publication. A refusal can retain it privately for another run after the destination issue is resolved. Unbound downloads cannot retain a confirmable version. StageReplaced retains nothing and removes the checkpoint. A moved destination directory can retain the stage in the originally opened directory; put that directory back at the original path before retrying.

After publication, PlacedNotRecorded means a folder transfer could not record the completed file. DestinationMoved can also have Placed = true: the file is in the original directory, which is now elsewhere. These errors do not roll back the file. A later run recognizes only the same file, in the expected directory, once its storage version is confirmed.

A folder transfer records each completed file before removing its individual checkpoint. An interrupted record can be recovered from that checkpoint, including with CreateNew. If the remote version changed, the current publication policy applies again; CreateNew keeps and refuses the existing local file.

Handling an Existing Destination

try
{
    await client.Transfers.DownloadFileAsync("reports/report.csv", "report.csv", new DownloadOptions
    {
        PublicationPolicy = DownloadPublicationPolicy.CreateNew,
        CheckpointPath = "report.csv.checkpoint",
    });
}
catch (DownloadPublicationException e) when (e.Problem == DownloadPublicationProblem.DestinationExists)
{
    Console.WriteLine($"{e.LocalPath} exists; download retained for another attempt: {e.Retained}");
}

Local Access and Durability

The staging file has a protected ACL allowing only you, SYSTEM and Administrators, and remains open without sharing until publication. Its folder is held while used and removed only if still the same empty folder. CreateNew and ReplaceRegularFile hold the destination directory and its ancestors from transfer start, preventing renames. Unsupported paths, such as a volume without a drive letter, fail with NotSupported before fetching. ReplaceRegularFile preserves the replaced file's ACL and hidden attribute; a new file gets the directory's normal new-file access.

The default ReplaceExisting uses the directory by path. net8.0 and net10.0 builds give the replacement new-file access; netstandard2.0 uses Windows ReplaceFile, retaining the replaced file's ACL and attributes. Only the file is flushed. Resume requires a retained stage whose owner and ACL are verified private and which has only its own name. Stages without verifiable ACLs, including those retained by earlier Windows SDK versions, restart instead. They can still be published with the required final access; inability to apply it fails with AccessNotKept.

The SDK flushes file contents before publication but does not flush the directory rename on Windows. This does not promise that the newest filename or checkpoint update survives a system crash or power loss. A failure after the file was put in place does not roll it back; inspect Placed before deciding how to recover.

Automatic Concurrency

The default mode adjusts the number of simultaneous parts based on measured throughput, latency and temporary failures. Upload tuning is selected for the destination from Files.com's instructions. Downloads tune concurrency only when using version-bound parts. Other downloads remain whole-response requests.

MaxConcurrency caps the target; the limiter's slots and buffers can reduce actual concurrency further. Static mode keeps the target at MaxConcurrency when parallel parts are supported. Downloads accept 1–1024 and default to 4 in static mode. Server-required serial uploads remain serial in either mode.

For special cases, set UploadOptions.AutomaticConcurrency or DownloadOptions.AutomaticConcurrency to custom AutomaticConcurrencyOptions. These apply throughout the transfer, do not change part sizes and cannot override a server requirement for serial uploads. Invalid tuning fails before sending anything; static downloads refuse AutomaticConcurrency.

Upload Startup

For the SDK-selected scaled S3 upload policy, UploadOptions.Startup chooses how the transfer begins:

  • UploadStartup.Default probes from one part and raises concurrency as responses show that it helps. This is the default and can reach the same concurrency ceiling as Aggressive.
  • UploadStartup.Aggressive skips those initial probes and starts at the policy's target of 150, within MaxConcurrency and the limiter. Use it for a known high-throughput network.

Both remain adaptive, with the same throttling response, backoff, retries and cancellation. This choice affects only automatic uploads using the SDK-selected scaled S3 policy. Custom AutomaticConcurrency, static mode, Agents and other destination profiles use their own starting settings. It does not change part sizes. A folder upload shares one startup across its files.

Downloads fetching version-bound parts use the scaled S3 policy without an upload startup choice or initial upload probes. Their initial target is 150, within explicit caps and the limiter; custom tuning uses its own start.

Automatic folder transfers learn a shared target rather than assigning that target to every file. They admit files with some headroom for growth (target plus one quarter plus one, at least 4), within the explicit file cap or 128. Reducing the target limits new starts without stopping files already active. Static folder transfers use an independent part target for each file.

On .NET Framework, ServicePointManager.DefaultConnectionLimit also limits connections per server, usually to 2 in desktop and console applications. Raise it if you need more simultaneous parts.

When Downloads Use Parts

Parallel downloads currently require HTTPS Amazon S3 general-purpose endpoints. The first ranged response must establish a strong ETag, size and storage object; later requests use If-Match and validate each range, length and tag. Renewed addresses must identify the same object, even if another object's tag matches. Other storage and specialized S3 endpoints, including FIPS, accelerate and access points, use whole responses.

Before a version is established, a whole-file 200, 416 (such as an empty file), redirect or missing version information also leads to a whole response. If storage ignores ranges after version binding, the fallback keeps the version check: VersionBound remains true while Concurrency becomes null. A response containing an entire small file is accepted only after all bytes arrive; if cut short, the next attempt determines the mode again.

The SDK rejects web-app responses (X-Files-Frontend-App), compressed bodies, incorrect ranges or lengths, and whole-file responses other than 200. Bound part requests follow no redirects; other storage redirects must not leave HTTPS. These checks run before publication.

Capping or Fixing Concurrency

// Automatic, with a cap of eight simultaneous parts.
var capped = new UploadOptions { MaxConcurrency = 8 };

// A fixed target of four, where Files.com allows parallel parts.
var fixedFour = new UploadOptions
{
    ConcurrencyMode = UploadConcurrencyMode.Static,
    MaxConcurrency = 4,
};

await client.Transfers.UploadFileAsync(localPath, destinationPath, capped);

// Skip initial probes on a known high-throughput network, where the scaled S3 policy applies.
var aggressive = new UploadOptions { Startup = UploadStartup.Aggressive };

Part Sizes

The SDK chooses upload part sizes only for ordinary direct Amazon S3 multipart uploads. Elsewhere—including remote servers, Agents, S3-compatible storage and append uploads—it follows the sizes Files.com plans. Downloads use DownloadOptions.PartSize, 8 MiB by default, when fetching version-bound parts.

Upload PartSizingModeBehavior where the SDK can choose sizes
Automatic (default)Starts from the file size; tries larger parts after concurrency growth stops improving throughput, and keeps them only if faster. Customize with AutomaticPartSizing.
FixedUses PartSize for every part except the last. Invalid sizes fail before sending a part.
ServerPlannedUses Files.com's planned sizes.

Chosen upload sizes stay within 5 MiB minimum except the last part, 1 GiB maximum, at most 10,000 parts (or fewer if Files.com grants fewer), and a 5 TiB file limit. Larger files use server-planned sizes. Files.com and the destination may impose further limits. If the file cannot fit the allowed parts, automatic mode falls back to server-planned sizes; fixed sizes or insufficient memory-backed part capacity fail before sending.

Folder uploads test automatic size increases one file at a time, using that file's throughput at a stable shared concurrency target. The target holds during a size trial. Throttling or a temporary failure from any file ends the trial and restores the earlier size. Serial files do not force the other files to become serial.

Inspect FinalProgress.PartSizing for the source, fallback reason, limits, next size, largest part and growth count.

Choosing Fixed Upload Parts

var options = new UploadOptions
{
    PartSizingMode = UploadPartSizingMode.Fixed,
    PartSize = 32 * 1024 * 1024,
    ConcurrencyMode = UploadConcurrencyMode.Static,
    MaxConcurrency = 4,
};

UploadResult result = await client.Transfers.UploadFileAsync(localPath, destinationPath, options);
Console.WriteLine($"Part-size source: {result.FinalProgress.PartSizing.Source}");

Sharing Limits Between Uploads

Use a TransferLimiter to cap simultaneous part requests and buffered file bytes across uploads and downloads, including transfers from different clients. Waiting transfers take turns for resources. A request slot is held while sending, not during a retry wait, and failures or cancellation release reserved resources.

A limiter bounds requests and buffers; it does not make separate automatic transfers learn one target. Only files within one automatic folder transfer share a learned target.

Without an explicit limiter, transfers use 128 MiB of buffer space. Single-file request slots follow the configured maximum target; a static download defaults to 4, and an automatic download without an explicit cap or custom tuning allows up to 1024. Automatic folder transfers allow at least 8 slots for requests outside their shared target, such as version checks and whole-response downloads; static folder transfers use 8 slots.

File uploads read through small copy buffers rather than keeping whole parts in memory. File downloads reserve a copy buffer of at most 80 KiB; stream downloads reserve the part itself, since ahead-of-order bytes must wait in memory. Both reserve buffer space before a request slot. Stream uploads retain memory-backed parts until accepted, or use temporary files. Inspect ActiveRequests, BufferedBytes, QueuedRequests and QueuedBufferReservations on the limiter to see current usage.

Sharing a Transfer Limit

var limiter = new TransferLimiter(maxConcurrentRequests: 8, maxBufferedBytes: 64 * 1024 * 1024);
var options = new UploadOptions { Limiter = limiter };

await Task.WhenAll(localPaths.Select(localPath =>
    client.Transfers.UploadFileAsync(localPath, "backups/" + Path.GetFileName(localPath), options)));

Diagnostics

Set UploadOptions.Trace for detailed upload records. Keep the handler quick; reports follow the same callback rules as progress.

RecordContents
UploadAttemptTraceEach answered or failed part request before retry: status, throttling, network time, admission wait and sent bytes. Cancelled requests and source-read failures are not recorded.
UploadDecisionTraceConcurrency changes and the throughput behind them.
UploadSizingTracePart-size decisions and the throughput behind them.

In a folder upload, shared-target decisions are reported once through the deciding file's FileOptions.Trace, with the remote folder as Path. Attempts and size decisions keep their file's path. Downloads use DownloadProgress.Concurrency and have no trace stream. Traces, snapshots and ordinary SDK logs omit part addresses, headers and credentials.

Tracing Concurrency Decisions

var options = new UploadOptions
{
    Trace = new Progress<UploadTraceEvent>(record =>
    {
        if (record is UploadDecisionTrace decision)
        {
            Console.WriteLine($"{decision.Elapsed}: {decision.Decision} " +
                $"{decision.PreviousTarget} -> {decision.Target}");
        }
    }),
};

Earlier Upload and Download Methods

Existing RemoteFile methods and client.RemoteFiles transfer methods remain available:

Method familyBehavior
RemoteFile.UploadFile, client.RemoteFiles.UploadFileAsyncSends parts serially and returns true. Stream overloads dispose the supplied stream. Expiring addresses are renewed for the same part and retries resend the bytes already read. The final request is not retried.
RemoteFile.DownloadFile, client.RemoteFiles.DownloadFileAsyncFetches the whole file directly into the local file or stream, without staging. Rejects web-app and partial responses before writing. An address refused as expired can be renewed at most twice before any bytes are written. Downloads are not retried after writing bytes.

Use client.Transfers for automatic tuning, progress, staged local downloads, shared limits and file checkpoints.