Class AtomicDownload
- Namespace
- McServerLauncher.Services
- Assembly
- McServerLauncher.dll
Downloads a file so that a failure can never damage what was already on disk.
Writing straight to the destination truncates it the moment the stream opens, long before the download is known to be good. A connection dropped halfway then leaves a valid path holding a half a file — and for the Fabric server jar it was worse than that: the structural check that runs afterwards deletes what it rejects, so an interrupted loader change turned a working server into one with no jar to start at all.
So the bytes go to "<dest>.part", verification runs against that, and only a file that arrived complete and passed its check replaces the real one — in a single filesystem move. This is the same guarantee AtomicJsonFile gives the app's JSON, applied to binaries.
public static class AtomicDownload
- Inheritance
-
AtomicDownload
- Inherited Members
Methods
DisplayName(string)
The name to show the user for a file being downloaded to path.
public static string DisplayName(string path)
Parameters
pathstring
Returns
Remarks
Verification runs against the ".part" copy, so the raw file name would tell the user their download of "server.jar.part" failed. This gives back the name they actually recognise.
PathIn(string, string)
Where a file named by a remote server is allowed to land: inside
folder, always.
public static string PathIn(string folder, string remoteFileName)
Parameters
Returns
Remarks
Path.Combine(folder, name) is not a containment check and does not pretend to be one.
Handed "../../evil.jar" it walks up; handed an absolute path it discards the folder
entirely and returns that path. The name comes from Modrinth or from GeyserMC's downloads
API, so nothing hostile is expected there — which is exactly the reasoning that makes a
boundary get skipped once and then five more times.
This exists so the join happens in one place. Every download the app performs goes through it, and a name with no usable last segment is refused rather than guessed at: it can only mean the API said something the app does not understand.
ToFileAsync(HttpContent, string, Func<string, CancellationToken, Task>?, IProgress<double>?, CancellationToken)
Streams content into destPath atomically.
verifyAsync is handed the path of the *temporary* file, so anything it
rejects (a checksum mismatch, a jar with the wrong structure) never reaches the real one.
It runs with the write handle already closed, because Create(string)
opens with FileShare.None and a reader would otherwise hit a sharing violation on
Windows. Throwing from it aborts the download and leaves the destination untouched.
public static Task ToFileAsync(HttpContent content, string destPath, Func<string, CancellationToken, Task>? verifyAsync = null, IProgress<double>? progress = null, CancellationToken ct = default)
Parameters
contentHttpContentdestPathstringverifyAsyncFunc<string, CancellationToken, Task>progressIProgress<double>ctCancellationToken