2using System.Collections.Generic;
3using System.Diagnostics;
4using System.Globalization;
6using System.IO.Abstractions;
7using System.Runtime.Versioning;
9using System.Threading.Tasks;
11using Microsoft.Extensions.Logging;
24 [UnsupportedOSPlatform(
"windows")]
63 this.logger =
logger ??
throw new ArgumentNullException(nameof(
logger));
82 if (mirroredDir !=
null && !
Swapped)
84 logger.LogDebug(
"Cancelled mirroring task, we must cleanup!");
88 async
void AsyncCleanup()
93 logger.LogTrace(
"Completed async cleanup of unused mirror directory: {mirroredDir}", mirroredDir);
97 logger.LogError(ex,
"Error cleaning up mirrored directory {mirroredDir}!", mirroredDir);
104 await base.DisposeAsync();
111 logger.LogTrace(
"Waiting for mirroring to complete...");
117 protected override async ValueTask
DoSwap(CancellationToken cancellationToken)
119 logger.LogTrace(
"Begin DoSwap, mirroring task complete: {complete}...",
mirroringTask.IsCompleted);
120 var mirroredDir = await
mirroringTask.WaitAsync(cancellationToken);
121 if (mirroredDir ==
null)
124 cancellationToken.ThrowIfCancellationRequested();
125 throw new InvalidOperationException(
"mirroringTask was cancelled without us being cancelled?");
128 var goAheadTcs =
new TaskCompletionSource();
131 async
void DisposeOfOldDirectory()
133 var directoryMoved =
false;
134 var disposeGuid = Guid.NewGuid();
135 var disposePath = disposeGuid.ToString();
136 logger.LogTrace(
"Moving Live directory to {path} for deletion...", disposeGuid);
140 directoryMoved =
true;
141 goAheadTcs.SetResult();
142 logger.LogTrace(
"Deleting old Live directory {path}...", disposePath);
144 logger.LogTrace(
"Completed async cleanup of old Live directory: {disposePath}", disposePath);
146 catch (DirectoryNotFoundException ex)
148 logger.LogDebug(ex,
"Live directory appears to not exist");
150 goAheadTcs.SetResult();
154 logger.LogWarning(ex,
"Failed to delete hard linked directory: {disposePath}", disposePath);
156 goAheadTcs.SetException(ex);
160 DisposeOfOldDirectory();
161 await goAheadTcs.Task;
162 logger.LogTrace(
"Moving mirror directory {path} to Live...", mirroredDir);
164 logger.LogTrace(
"Swap complete!");
176 if (taskThrottle.HasValue && taskThrottle < 1)
177 throw new ArgumentOutOfRangeException(nameof(taskThrottle), taskThrottle,
"taskThrottle must be at least 1!");
182 var stopwatch = Stopwatch.StartNew();
183 var mirrorGuid = Guid.NewGuid();
185 logger.LogDebug(
"Starting to mirror {sourceDir} as hard links to {mirrorGuid}...",
CompileJob.DirectoryName, mirrorGuid);
190 using var semaphore = taskThrottle.HasValue ?
new SemaphoreSlim(taskThrottle.Value) :
null;
202 "Finished mirror of {sourceDir} to {mirrorGuid} in {seconds}s...",
205 stopwatch.Elapsed.TotalSeconds.ToString(
"0.##", CultureInfo.InvariantCulture));
207 catch (OperationCanceledException ex)
209 logger.LogDebug(ex,
"Cancelled while mirroring");
213 logger.LogError(ex,
"Could not mirror!");
218 logger.LogDebug(
"Cleaning up mirror attempt: {dest}", dest);
221 catch (OperationCanceledException ex2)
223 logger.LogDebug(ex2,
"Errored cleanup cancellation edge case!");
245 Task? subdirCreationTask =
null;
246 var dreamDaemonWillAcceptOutOfDirectorySymlinks = securityLevel ==
DreamDaemonSecurity.Trusted;
247 foreach (var subDirectory
in src.EnumerateDirectories())
252 if (subDirectory.Attributes.HasFlag(FileAttributes.ReparsePoint))
253 if (dreamDaemonWillAcceptOutOfDirectorySymlinks)
255 var target = subDirectory.ResolveLinkTarget(
false)
256 ??
throw new InvalidOperationException($
"\"{subDirectory.FullName}\" was incorrectly identified as a symlinked directory!");
257 logger.LogDebug(
"Recreating directory {name} as symlink to {target}", subDirectory.Name, target);
258 if (subdirCreationTask ==
null)
261 yield
return subdirCreationTask;
264 async Task CopyLink()
266 await subdirCreationTask.WaitAsync(cancellationToken);
267 using var lockContext = semaphore !=
null
273 yield
return CopyLink();
277 logger.LogDebug(
"Recreating symlinked directory {name} as hard links...", subDirectory.Name);
279 var checkingSubdirCreationTask =
true;
280 foreach (var copyTask
in MirrorDirectoryImpl(subDirectory, mirroredName, semaphore, securityLevel, cancellationToken))
282 if (subdirCreationTask ==
null)
284 subdirCreationTask = copyTask;
285 yield
return subdirCreationTask;
287 else if (!checkingSubdirCreationTask)
288 yield
return copyTask;
290 checkingSubdirCreationTask =
false;
294 foreach (var fileInfo
in src.EnumerateFiles())
296 if (subdirCreationTask ==
null)
299 yield
return subdirCreationTask;
302 var sourceFile = fileInfo.FullName;
305 async Task LinkThisFile()
307 await subdirCreationTask.WaitAsync(cancellationToken);
308 using var lockContext = semaphore !=
null
312 if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
315 var target = fileInfo.ResolveLinkTarget(!dreamDaemonWillAcceptOutOfDirectorySymlinks)
316 ??
throw new InvalidOperationException($
"\"{fileInfo.FullName}\" was incorrectly identified as a symlinked file!");
318 if (dreamDaemonWillAcceptOutOfDirectorySymlinks)
320 logger.LogDebug(
"Recreating symlinked file {name} as symlink to {target}", fileInfo.Name, target.FullName);
325 logger.LogDebug(
"Recreating symlinked file {name} as hard link to {target}", fileInfo.Name, target.FullName);
333 yield
return LinkThisFile();
A IDmbProvider that uses hard links.
readonly Task< string?> mirroringTask
The Task representing the base provider mirroring operation.
override async ValueTask DoSwap(CancellationToken cancellationToken)
readonly ILogger logger
The ILogger for the HardLinkDmbProvider.
HardLinkDmbProvider(IDmbProvider baseProvider, IIOManager ioManager, IFilesystemLinkFactory linkFactory, ILogger logger, GeneralConfiguration generalConfiguration, DreamDaemonSecurity securityLevel)
Initializes a new instance of the HardLinkDmbProvider class.
override async ValueTask DisposeAsync()
IEnumerable< Task > MirrorDirectoryImpl(IDirectoryInfo src, string dest, SemaphoreSlim? semaphore, DreamDaemonSecurity securityLevel, CancellationToken cancellationToken)
Recursively create tasks to create a hard link directory mirror of src to dest .
override Task FinishActivationPreparation(CancellationToken cancellationToken)
readonly CancellationTokenSource cancellationTokenSource
The CancellationTokenSource for mirroringTask.
async Task< string?> MirrorSourceDirectory(int? taskThrottle, DreamDaemonSecurity securityLevel, CancellationToken cancellationToken)
Mirror the Models.CompileJob.
A IDmbProvider that uses filesystem links to change directory structure underneath the server process...
Models.CompileJob CompileJob
The CompileJob of the .dmb.
IIOManager IOManager
The IIOManager to use.
bool Swapped
If MakeActive(CancellationToken) has been run.
const string LiveGameDirectory
The directory where the BaseProvider is symlinked to.
IFilesystemLinkFactory LinkFactory
The IFilesystemLinkFactory to use.
General configuration options.
Async lock context helper.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken, ILogger? logger=null)
Asyncronously locks a semaphore .
Provides absolute paths to the latest compiled .dmbs.
For creating filesystem symbolic links.
Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken)
Create a symbolic link.
Task CreateHardLink(string targetPath, string linkPath, CancellationToken cancellationToken)
Creates a hard link.
Interface for using filesystems.
string ResolvePath()
Retrieve the full path of the current working directory.
Task< IDirectoryInfo > DirectoryInfo(string path, CancellationToken cancellationToken)
Gets a IDirectoryInfo for the given path .
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path .
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
Task MoveDirectory(string source, string destination, CancellationToken cancellationToken)
Moves a directory at source to destination .
DreamDaemonSecurity
DreamDaemon's security level.