tgstation-server 5.12.7
The /tg/station 13 server suite
Loading...
Searching...
No Matches
Configuration.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.IO;
5using System.Linq;
6using System.Security.Cryptography;
7using System.Text;
8using System.Threading;
9using System.Threading.Tasks;
10
11using Microsoft.Extensions.Logging;
12
24
26{
29 {
33 const string CodeModificationsSubdirectory = "CodeModifications";
34
38 const string EventScriptsSubdirectory = "EventScripts";
39
43 const string GameStaticFilesSubdirectory = "GameStaticFiles";
44
48 const string StaticIgnoreFile = ".tgsignore";
49
53 const string CodeModificationsHeadFile = "HeadInclude.dm";
54
58 const string CodeModificationsTailFile = "TailInclude.dm";
59
63 static readonly string DefaultHeadInclude = @$"// TGS AUTO GENERATED HeadInclude.dm{Environment.NewLine}// This file will be included BEFORE all code in your .dme IF a replacement .dme does not exist in this directory{Environment.NewLine}// Please note that changes need to be made available if you are hosting an AGPL licensed codebase{Environment.NewLine}// The presence file in its default state does not constitute a code change that needs to be published by licensing standards{Environment.NewLine}";
64
68 static readonly string DefaultTailInclude = @$"// TGS AUTO GENERATED TailInclude.dm{Environment.NewLine}// This file will be included AFTER all code in your .dme IF a replacement .dme does not exist in this directory{Environment.NewLine}// Please note that changes need to be made available if you are hosting an AGPL licensed codebase{Environment.NewLine}// The presence file in its default state does not constitute a code change that needs to be published by licensing standards{Environment.NewLine}";
69
73 static readonly IReadOnlyDictionary<EventType, string> EventTypeScriptFileNameMap = new Dictionary<EventType, string>(
74 Enum.GetValues(typeof(EventType))
75 .OfType<EventType>()
76 .Select(
77 eventType => new KeyValuePair<EventType, string>(
78 eventType,
79 typeof(EventType)
80 .GetField(eventType.ToString())
81 .GetCustomAttributes(false)
82 .OfType<EventScriptAttribute>()
83 .First()
84 .ScriptName)));
85
90
95
100
105
110
115
120
124 readonly ILogger<Configuration> logger;
125
130
135
139 readonly SemaphoreSlim semaphore;
140
144 readonly CancellationTokenSource disposeCts;
145
150
172 ILogger<Configuration> logger,
175 {
176 this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
177 this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager));
178 this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
179 this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
180 this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
181 this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
182 this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
183 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
184 this.generalConfiguration = generalConfiguration ?? throw new ArgumentNullException(nameof(generalConfiguration));
185 this.sessionConfiguration = sessionConfiguration ?? throw new ArgumentNullException(nameof(sessionConfiguration));
186
187 semaphore = new SemaphoreSlim(1);
188 disposeCts = new CancellationTokenSource();
189 uploadTasks = Task.CompletedTask;
190 }
191
193 public void Dispose()
194 {
195 semaphore.Dispose();
196 disposeCts.Cancel();
197 disposeCts.Dispose();
198 }
199
201 public async Task<ServerSideModifications> CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken)
202 {
203 using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
204 {
205 await EnsureDirectories(cancellationToken);
206
207 // just assume no other fs race conditions here
208 var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken);
211 var copyTask = ioManager.CopyDirectory(
212 null,
213 null,
215 destination,
216 generalConfiguration.GetCopyDirectoryTaskThrottle(),
217 cancellationToken);
218
219 await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask, copyTask);
220
221 if (!dmeExistsTask.Result && !headFileExistsTask.Result && !tailFileExistsTask.Result)
222 return null;
223
224 if (dmeExistsTask.Result)
225 return new ServerSideModifications(null, null, true);
226
227 if (!headFileExistsTask.Result && !tailFileExistsTask.Result)
228 return null;
229
230 static string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath);
231
232 return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false);
233 }
234 }
235
237 public async Task<IReadOnlyList<ConfigurationFileResponse>> ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
238 {
239 await EnsureDirectories(cancellationToken);
240 var path = ValidateConfigRelativePath(configurationRelativePath);
241
242 configurationRelativePath ??= "/";
243
244 var result = new List<ConfigurationFileResponse>();
245
246 void ListImpl()
247 {
248 var enumerator = synchronousIOManager.GetDirectories(path, cancellationToken);
249 result.AddRange(enumerator.Select(x => new ConfigurationFileResponse
250 {
251 IsDirectory = true,
252 Path = ioManager.ConcatPath(configurationRelativePath, x),
253 }).OrderBy(file => file.Path));
254
255 enumerator = synchronousIOManager.GetFiles(path, cancellationToken);
256 result.AddRange(enumerator.Select(x => new ConfigurationFileResponse
257 {
258 IsDirectory = false,
259 Path = ioManager.ConcatPath(configurationRelativePath, x),
260 }).OrderBy(file => file.Path));
261 }
262
263 using (SemaphoreSlimContext.TryLock(semaphore, out var locked))
264 {
265 if (!locked)
266 {
267 logger.LogDebug("Contention when attempting to enumerate directory!");
268 return null;
269 }
270
271 if (systemIdentity == null)
272 ListImpl();
273 else
274 await systemIdentity.RunImpersonated(ListImpl, cancellationToken);
275 }
276
277 return result;
278 }
279
281 public async Task<ConfigurationFileResponse> Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
282 {
283 await EnsureDirectories(cancellationToken);
284 var path = ValidateConfigRelativePath(configurationRelativePath);
285
286 ConfigurationFileResponse result = null;
287
288 void ReadImpl()
289 {
290 try
291 {
292 string GetFileSha()
293 {
294 var content = synchronousIOManager.ReadFile(path);
295 using var sha1 = SHA1.Create();
296 return String.Join(String.Empty, sha1.ComputeHash(content).Select(b => b.ToString("x2", CultureInfo.InvariantCulture)));
297 }
298
299 var originalSha = GetFileSha();
300
301 var disposeToken = disposeCts.Token;
302 var fileTicket = fileTransferService.CreateDownload(
304 () =>
305 {
306 if (disposeToken.IsCancellationRequested)
307 return ErrorCode.InstanceOffline;
308
309 var newSha = GetFileSha();
310 if (newSha != originalSha)
311 return ErrorCode.ConfigurationFileUpdated;
312
313 return null;
314 },
315 async cancellationToken =>
316 {
317 FileStream result = null;
318 void GetFileStream()
319 {
320 result = ioManager.GetFileStream(path, false);
321 }
322
323 using (SemaphoreSlimContext.TryLock(semaphore, out var locked))
324 {
325 if (!locked)
326 return null;
327
328 if (systemIdentity == null)
329 await Task.Factory.StartNew(GetFileStream, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current);
330 else
331 await systemIdentity.RunImpersonated(GetFileStream, cancellationToken);
332 }
333
334 return result;
335 },
336 path,
337 false));
338
339 result = new ConfigurationFileResponse
340 {
341 FileTicket = fileTicket.FileTicket,
342 IsDirectory = false,
343 LastReadHash = originalSha,
344 AccessDenied = false,
345 Path = configurationRelativePath,
346 };
347 }
348 catch (UnauthorizedAccessException)
349 {
350 // this happens on windows, dunno about linux
351 bool isDirectory;
352 try
353 {
354 isDirectory = synchronousIOManager.IsDirectory(path);
355 }
356 catch (Exception ex)
357 {
358 logger.LogDebug(ex, "IsDirectory exception!");
359 isDirectory = false;
360 }
361
362 result = new ConfigurationFileResponse
363 {
364 Path = configurationRelativePath,
365 };
366 if (!isDirectory)
367 result.AccessDenied = true;
368
369 result.IsDirectory = isDirectory;
370 }
371 }
372
373 using (SemaphoreSlimContext.TryLock(semaphore, out var locked))
374 {
375 if (!locked)
376 {
377 logger.LogDebug("Contention when attempting to read file!");
378 return null;
379 }
380
381 if (systemIdentity == null)
382 await Task.Factory.StartNew(ReadImpl, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current);
383 else
384 await systemIdentity.RunImpersonated(ReadImpl, cancellationToken);
385 }
386
387 return result;
388 }
389
391 public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken)
392 {
393 async Task<IReadOnlyList<string>> GetIgnoreFiles()
394 {
395 var ignoreFileBytes = await ioManager.ReadAllBytes(StaticIgnorePath(), cancellationToken);
396 var ignoreFileText = Encoding.UTF8.GetString(ignoreFileBytes);
397
398 var results = new List<string> { StaticIgnoreFile };
399
400 // we don't want to lose trailing whitespace on linux
401 using (var reader = new StringReader(ignoreFileText))
402 {
403 cancellationToken.ThrowIfCancellationRequested();
404 var line = await reader.ReadLineAsync();
405 if (!String.IsNullOrEmpty(line))
406 results.Add(line);
407 }
408
409 return results;
410 }
411
412 IReadOnlyList<string> ignoreFiles;
413
414 async Task SymlinkBase(bool files)
415 {
416 Task<IReadOnlyList<string>> task;
417 if (files)
418 task = ioManager.GetFiles(GameStaticFilesSubdirectory, cancellationToken);
419 else
420 task = ioManager.GetDirectories(GameStaticFilesSubdirectory, cancellationToken);
421 var entries = await task;
422
423 await Task.WhenAll(entries.Select(async file =>
424 {
425 var fileName = ioManager.GetFileName(file);
426
427 // need to normalize
428 bool ignored;
429 if (platformIdentifier.IsWindows)
430 ignored = ignoreFiles.Any(y => fileName.ToUpperInvariant() == y.ToUpperInvariant());
431 else
432 ignored = ignoreFiles.Any(y => fileName == y);
433
434 if (ignored)
435 {
436 logger.LogTrace("Ignoring static file {fileName}...", fileName);
437 return;
438 }
439
440 var destPath = ioManager.ConcatPath(destination, fileName);
441 logger.LogTrace("Symlinking {filePath} to {destPath}...", file, destPath);
442 var fileExistsTask = ioManager.FileExists(destPath, cancellationToken);
443 if (await ioManager.DirectoryExists(destPath, cancellationToken))
444 await ioManager.DeleteDirectory(destPath, cancellationToken);
445 var fileExists = await fileExistsTask;
446 if (fileExists)
447 await ioManager.DeleteFile(destPath, cancellationToken);
448 await symlinkFactory.CreateSymbolicLink(ioManager.ResolvePath(file), ioManager.ResolvePath(destPath), cancellationToken);
449 }));
450 }
451
452 using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
453 {
454 await EnsureDirectories(cancellationToken);
455 ignoreFiles = await GetIgnoreFiles();
456 await Task.WhenAll(SymlinkBase(true), SymlinkBase(false));
457 }
458 }
459
461 public async Task<ConfigurationFileResponse> Write(string configurationRelativePath, ISystemIdentity systemIdentity, string previousHash, CancellationToken cancellationToken)
462 {
463 await EnsureDirectories(cancellationToken);
464 var path = ValidateConfigRelativePath(configurationRelativePath);
465
466 ConfigurationFileResponse result = null;
467
468 void WriteImpl()
469 {
470 try
471 {
472 var fileTicket = fileTransferService.CreateUpload(FileUploadStreamKind.ForSynchronousIO);
473 var uploadCancellationToken = disposeCts.Token;
474 async Task UploadHandler()
475 {
476 await using (fileTicket)
477 {
478 var fileHash = previousHash;
479 var uploadStream = await fileTicket.GetResult(uploadCancellationToken);
480 if (uploadStream == null)
481 return; // expired
482
483 bool success = false;
484 void WriteCallback()
485 {
486 success = synchronousIOManager.WriteFileChecked(path, uploadStream, ref fileHash, cancellationToken);
487 }
488
489 if (fileTicket == null)
490 {
491 logger.LogDebug("File upload ticket for {path} expired!", path);
492 return;
493 }
494
495 using (SemaphoreSlimContext.TryLock(semaphore, out var locked))
496 {
497 if (!locked)
498 {
499 fileTicket.SetError(ErrorCode.ConfigurationContendedAccess, null);
500 return;
501 }
502
503 if (systemIdentity == null)
504 await Task.Factory.StartNew(WriteCallback, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current);
505 else
506 await systemIdentity.RunImpersonated(WriteCallback, cancellationToken);
507 }
508
509 if (!success)
510 fileTicket.SetError(ErrorCode.ConfigurationFileUpdated, fileHash);
511 else if (uploadStream.Length > 0)
512 postWriteHandler.HandleWrite(path);
513 }
514 }
515
516 result = new ConfigurationFileResponse
517 {
518 FileTicket = fileTicket.Ticket.FileTicket,
519 LastReadHash = previousHash,
520 IsDirectory = false,
521 AccessDenied = false,
522 Path = configurationRelativePath,
523 };
524
525 lock (disposeCts)
526 uploadTasks = Task.WhenAll(uploadTasks, UploadHandler());
527 }
528 catch (UnauthorizedAccessException)
529 {
530 // this happens on windows, dunno about linux
531 bool isDirectory;
532 try
533 {
534 isDirectory = synchronousIOManager.IsDirectory(path);
535 }
536 catch (Exception ex)
537 {
538 logger.LogDebug(ex, "IsDirectory exception!");
539 isDirectory = false;
540 }
541
542 result = new ConfigurationFileResponse
543 {
544 Path = configurationRelativePath,
545 };
546 if (!isDirectory)
547 result.AccessDenied = true;
548
549 result.IsDirectory = isDirectory;
550 }
551 }
552
553 using (SemaphoreSlimContext.TryLock(semaphore, out var locked))
554 {
555 if (!locked)
556 {
557 logger.LogDebug("Contention when attempting to write file!");
558 return null;
559 }
560
561 if (systemIdentity == null)
562 await Task.Factory.StartNew(WriteImpl, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current);
563 else
564 await systemIdentity.RunImpersonated(WriteImpl, cancellationToken);
565 }
566
567 return result;
568 }
569
571 public async Task<bool?> CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
572 {
573 await EnsureDirectories(cancellationToken);
574 var path = ValidateConfigRelativePath(configurationRelativePath);
575
576 bool? result = null;
577 void DoCreate() => result = synchronousIOManager.CreateDirectory(path, cancellationToken);
578
579 using (SemaphoreSlimContext.TryLock(semaphore, out var locked))
580 {
581 if (!locked)
582 {
583 logger.LogDebug("Contention when attempting to create directory!");
584 return null;
585 }
586
587 if (systemIdentity == null)
588 await Task.Factory.StartNew(DoCreate, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current);
589 else
590 await systemIdentity.RunImpersonated(DoCreate, cancellationToken);
591 }
592
593 return result.Value;
594 }
595
597 public Task StartAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken);
598
600 public Task StopAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken);
601
603 public async Task HandleEvent(EventType eventType, IEnumerable<string> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
604 {
605 ArgumentNullException.ThrowIfNull(parameters);
606
607 await EnsureDirectories(cancellationToken);
608
609 if (!EventTypeScriptFileNameMap.TryGetValue(eventType, out var scriptName))
610 return;
611
612 // always execute in serial
613 using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
614 {
615 var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, platformIdentifier.ScriptFileExtension, false, cancellationToken);
616 var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory);
617
618 var scriptFiles = files
619 .Select(x => ioManager.GetFileName(x))
620 .Where(x => x.StartsWith(scriptName, StringComparison.Ordinal))
621 .ToList();
622
623 if (!scriptFiles.Any())
624 {
625 logger.LogTrace("No event scripts starting with \"{scriptName}\" detected", scriptName);
626 return;
627 }
628
629 foreach (var scriptFile in scriptFiles)
630 {
631 logger.LogTrace("Running event script {scriptFile}...", scriptFile);
632 await using (var script = await processExecutor.LaunchProcess(
633 ioManager.ConcatPath(resolvedScriptsDir, scriptFile),
634 resolvedScriptsDir,
635 String.Join(
636 ' ',
637 parameters.Select(arg =>
638 {
639 if (!arg.Contains(' ', StringComparison.Ordinal))
640 return arg;
641
642 arg = arg.Replace("\"", "\\\"", StringComparison.Ordinal);
643
644 return $"\"{arg}\"";
645 })),
646 readStandardHandles: true,
647 noShellExecute: true))
648 using (cancellationToken.Register(() => script.Terminate()))
649 {
650 if (sessionConfiguration.LowPriorityDeploymentProcesses)
651 script.AdjustPriority(false);
652
653 var exitCode = await script.Lifetime;
654 cancellationToken.ThrowIfCancellationRequested();
655 var scriptOutput = await script.GetCombinedOutput(cancellationToken);
656 if (exitCode != 0)
657 throw new JobException($"Script {scriptFile} exited with code {exitCode}:{Environment.NewLine}{scriptOutput}");
658 else
659 logger.LogDebug("Script output:{newLine}{scriptOutput}", Environment.NewLine, scriptOutput);
660 }
661 }
662 }
663 }
664
666 public async Task<bool?> DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
667 {
668 await EnsureDirectories(cancellationToken);
669 var path = ValidateConfigRelativePath(configurationRelativePath);
670
671 var result = false;
672 using (SemaphoreSlimContext.TryLock(semaphore, out var locked))
673 {
674 if (!locked)
675 {
676 logger.LogDebug("Contention when attempting to enumerate directory!");
677 return null;
678 }
679
680 void CheckDeleteImpl() => result = synchronousIOManager.DeleteDirectory(path);
681
682 if (systemIdentity != null)
683 await systemIdentity.RunImpersonated(CheckDeleteImpl, cancellationToken);
684 else
685 CheckDeleteImpl();
686 }
687
688 return result;
689 }
690
695 string StaticIgnorePath() => ioManager.ConcatPath(GameStaticFilesSubdirectory, StaticIgnoreFile);
696
702 async Task EnsureDirectories(CancellationToken cancellationToken)
703 {
704 async Task ValidateStaticFolder()
705 {
706 await ioManager.CreateDirectory(GameStaticFilesSubdirectory, cancellationToken);
707 var staticIgnorePath = StaticIgnorePath();
708 if (!await ioManager.FileExists(staticIgnorePath, cancellationToken))
709 await ioManager.WriteAllBytes(staticIgnorePath, Array.Empty<byte>(), cancellationToken);
710 }
711
712 async Task ValidateCodeModsFolder()
713 {
714 if (await ioManager.DirectoryExists(CodeModificationsSubdirectory, cancellationToken))
715 return;
716
717 await ioManager.CreateDirectory(CodeModificationsSubdirectory, cancellationToken);
718 await Task.WhenAll(
719 ioManager.WriteAllBytes(
720 ioManager.ConcatPath(
721 CodeModificationsSubdirectory,
722 CodeModificationsHeadFile),
723 Encoding.UTF8.GetBytes(DefaultHeadInclude),
724 cancellationToken),
725 ioManager.WriteAllBytes(
726 ioManager.ConcatPath(
727 CodeModificationsSubdirectory,
728 CodeModificationsTailFile),
729 Encoding.UTF8.GetBytes(DefaultTailInclude),
730 cancellationToken));
731 }
732
733 await Task.WhenAll(
734 ValidateCodeModsFolder(),
735 ioManager.CreateDirectory(EventScriptsSubdirectory, cancellationToken),
736 ValidateStaticFolder());
737 }
738
744 string ValidateConfigRelativePath(string configurationRelativePath)
745 {
746 var nullOrEmptyCheck = String.IsNullOrEmpty(configurationRelativePath);
747 if (nullOrEmptyCheck)
748 configurationRelativePath = DefaultIOManager.CurrentDirectory;
749 if (configurationRelativePath[0] == Path.DirectorySeparatorChar || configurationRelativePath[0] == Path.AltDirectorySeparatorChar)
750 configurationRelativePath = DefaultIOManager.CurrentDirectory + configurationRelativePath;
751 var resolved = ioManager.ResolvePath(configurationRelativePath);
752 var local = !nullOrEmptyCheck ? ioManager.ResolvePath() : null;
753 if (!nullOrEmptyCheck && resolved.Length < local.Length) // .. fuccbois
754 throw new InvalidOperationException("Attempted to access file outside of configuration manager!");
755 return resolved;
756 }
757 }
758}
virtual ? string FileTicket
The ticket to use to access the Routes.Transfer controller.
Attribute for indicating the script that a given EventType runs.
string ScriptName
The name of the script the event script the EventType runs.
readonly IProcessExecutor processExecutor
The IProcessExecutor for Configuration.
async Task< bool?> DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
Attempt to delete an empty directory at configurationRelativePath . true if the directory was empty a...
readonly IFileTransferTicketProvider fileTransferService
The IFileTransferTicketProvider for Configuration.
static readonly string DefaultTailInclude
Default contents of CodeModificationsHeadFile.
readonly ISynchronousIOManager synchronousIOManager
The ISynchronousIOManager for Configuration.
async Task< IReadOnlyList< ConfigurationFileResponse > > ListDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
Get ConfigurationFileResponses for all items in a given configurationRelativePath ....
Task StopAsync(CancellationToken cancellationToken)
string ValidateConfigRelativePath(string configurationRelativePath)
Resolve a given configurationRelativePath to it's full path or throw an InvalidOperationException if...
async Task< ServerSideModifications > CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken)
Copies all files in the CodeModifications directory to destination . A Task<TResult> resulting in the...
const string StaticIgnoreFile
Name of the ignore file in GameStaticFilesSubdirectory.
readonly SessionConfiguration sessionConfiguration
The SessionConfiguration for Configuration.
string StaticIgnorePath()
Get the proper path to StaticIgnoreFile.
Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IPlatformIdentifier platformIdentifier, IFileTransferTicketProvider fileTransferService, ILogger< Configuration > logger, GeneralConfiguration generalConfiguration, SessionConfiguration sessionConfiguration)
Initializes a new instance of the Configuration class.
async Task< ConfigurationFileResponse > Write(string configurationRelativePath, ISystemIdentity systemIdentity, string previousHash, CancellationToken cancellationToken)
Writes to a given configurationRelativePath . A Task<TResult> resulting in the updated ConfigurationF...
static readonly IReadOnlyDictionary< EventType, string > EventTypeScriptFileNameMap
Map of EventTypes to the filename of the event scripts they trigger.
async Task EnsureDirectories(CancellationToken cancellationToken)
Ensures standard configuration directories exist.
readonly ISymlinkFactory symlinkFactory
The ISymlinkFactory for Configuration.
readonly CancellationTokenSource disposeCts
The CancellationTokenSource that is triggered when IDisposable.Dispose is called.
Task uploadTasks
The culmination of all upload file transfer callbacks.
const string CodeModificationsSubdirectory
The CodeModifications directory name.
readonly IPostWriteHandler postWriteHandler
The IPostWriteHandler for Configuration.
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for Configuration.
async Task< ConfigurationFileResponse > Read(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
Reads a given configurationRelativePath . A Task<TResult> resulting in the ConfigurationFileResponse ...
const string CodeModificationsHeadFile
The HeadInclude.dm filename.
async Task HandleEvent(EventType eventType, IEnumerable< string > parameters, bool deploymentPipeline, CancellationToken cancellationToken)
Handle a given eventType . A Task representing the running operation.
readonly ILogger< Configuration > logger
The ILogger for Configuration.
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for Configuration.
const string CodeModificationsTailFile
The TailInclude.dm filename.
async Task< bool?> CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
Create an empty directory at configurationRelativePath . A Task<TResult> resulting in true if the dir...
static readonly string DefaultHeadInclude
Default contents of CodeModificationsHeadFile.
const string GameStaticFilesSubdirectory
The GameStaticFiles directory name.
Task StartAsync(CancellationToken cancellationToken)
const string EventScriptsSubdirectory
The EventScripts directory name.
readonly SemaphoreSlim semaphore
The SemaphoreSlim for Configuration. Also used as a lock object.
async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken)
Symlinks all directories in the GameData directory to destination . A Task representing the running o...
readonly IIOManager ioManager
The IIOManager for Configuration.
Configuration options for the game sessions.
IIOManager that resolves paths to Environment.CurrentDirectory.
const string CurrentDirectory
Path to the current working directory for the IIOManager.
const TaskCreationOptions BlockingTaskCreationOptions
The TaskCreationOptions used to spawn Tasks for potentially long running, blocking operations.
Operation exceptions thrown from the context of a Models.Job.
Definition: JobException.cs:11
Represents a file on disk to be downloaded.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken)
Asyncronously locks a semaphore .
static SemaphoreSlimContext TryLock(SemaphoreSlim semaphore, out bool locked)
Asyncronously attempts to lock a semaphore .
For managing the Configuration directory.
Interface for using filesystems.
Definition: IIOManager.cs:13
Task< IReadOnlyList< string > > GetFiles(string path, CancellationToken cancellationToken)
Returns file names in a given path .
string ResolvePath()
Retrieve the full path of the current working directory.
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task< IReadOnlyList< string > > GetDirectories(string path, CancellationToken cancellationToken)
Returns directory names in a given path .
Task DeleteFile(string path, CancellationToken cancellationToken)
Deletes a file at path .
Task CopyDirectory(IEnumerable< string > ignore, Func< string, string, Task > postCopyCallback, string src, string dest, int? taskThrottle, CancellationToken cancellationToken)
Copies a directory from src to dest .
FileStream GetFileStream(string path, bool shareWrite)
Gets the Stream for a given file path .
Task< byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
Returns all the contents of a file at path as a byte array.
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
Task< bool > FileExists(string path, CancellationToken cancellationToken)
Check that the file at path exists.
Task< bool > DirectoryExists(string path, CancellationToken cancellationToken)
Check that the directory at path exists.
Handles changing file modes/permissions after writing.
For accessing the disk in a synchronous manner.
byte[] ReadFile(string path)
Read the bytes of a file at a given path .
bool IsDirectory(string path)
Checks if a given path is a directory.
IEnumerable< string > GetFiles(string path, CancellationToken cancellationToken)
Enumerate files in a given path .
IEnumerable< string > GetDirectories(string path, CancellationToken cancellationToken)
Enumerate directories in a given path .
Represents a user on the current global::System.Runtime.InteropServices.OSPlatform.
Task RunImpersonated(Action action, CancellationToken cancellationToken)
Runs a given action in the context of the ISystemIdentity.
For identifying the current platform.
Service for temporarily storing files to be downloaded or uploaded.
FileTicketResponse CreateDownload(FileDownloadProvider fileDownloadProvider)
Create a FileTicketResponse for a download.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition: ErrorCode.cs:11
EventType
Types of events. Mirror in tgs.dm.
Definition: EventType.cs:7
FileUploadStreamKind
Determines the type of global::System.IO.Stream returned from IFileUploadTicket's created from IFileT...