2using System.Collections.Generic;
8using System.Threading.Tasks;
10using Microsoft.Extensions.Logging;
27 public const string BinPath =
"byond/bin";
85 readonly ILogger<ByondManager>
logger;
90 readonly Dictionary<Version, ReferenceCountingContainer<ByondInstallation, ByondExecutableLock>>
installedVersions;
108 ArgumentNullException.ThrowIfNull(version);
110 if (version.Build == 0)
111 throw new ArgumentException(
"version.Build cannot be 0!", nameof(version));
126 this.logger =
logger ??
throw new ArgumentNullException(nameof(
logger));
128 installedVersions =
new Dictionary<Version, ReferenceCountingContainer<ByondInstallation, ByondExecutableLock>>();
140 Stream customVersionStream,
141 bool allowInstallation,
142 CancellationToken cancellationToken)
157 version = installLock.Version;
159 var stringVersion = version.ToString();
165 ActiveVersion?.ToString(),
176 logger.LogInformation(
"Active version changed to {version}", version);
180 public async Task<IByondExecutableLock>
UseExecutables(Version requiredVersion,
string trustDmbFullPath, CancellationToken cancellationToken)
183 "Acquiring lock on BYOND version {version}...",
184 requiredVersion?.ToString() ?? $
"{ActiveVersion} (active)");
190 requiredVersion !=
null,
195 if (trustDmbFullPath !=
null)
196 await
TrustDmbPath(trustDmbFullPath, cancellationToken);
202 installLock.Dispose();
210 ArgumentNullException.ThrowIfNull(progressReporter);
214 logger.LogTrace(
"DeleteVersion {version}", version);
224 logger.LogInformation(
"Deleting BYOND version {version}...", version);
225 progressReporter.StageName =
"Waiting for version to not be in use...";
231 Task activeVersionUpdate;
238 .WithToken(cancellationToken);
240 if (containerTask.IsCompleted)
241 logger.LogTrace(
"All BYOND locks for {version} are gone", version);
255 logger.LogWarning(
"Unable to remove BYOND installation {version} from list! Is there a duplicate job running?", version);
258 if (container != newerContainer)
263 logger.LogDebug(
"Extreme race condition encountered, applying concentrated copium...");
264 container = newerContainer;
275 progressReporter.StageName =
"Deleting installation...";
278 var installPath = version.ToString();
286 if (containerTask.IsCompleted)
288 "Another lock was acquired before we could remove version {version} from the list. We will have to wait again.",
295 public async Task
StartAsync(CancellationToken cancellationToken)
297 async Task<byte[]> GetActiveVersion()
303 var activeVersionBytesTask = GetActiveVersion();
316 var trustedFilePath =
320 logger.LogTrace(
"Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
329 var installedVersionPaths =
new Dictionary<string, Version>();
331 async Task ReadVersion(
string path)
342 var text = Encoding.UTF8.GetString(bytes);
343 if (!Version.TryParse(text, out var version))
353 logger.LogDebug(
"Added detected BYOND version {versionKey}...", version);
359 "It seems that there are multiple directories that say they contain BYOND version {version}. We're ignoring and cleaning the duplicate: {duplicatePath}",
366 lock (installedVersionPaths)
370 await Task.WhenAll(directories.Select(ReadVersion));
372 logger.LogTrace(
"Upgrading BYOND installations...");
375 var activeVersionBytes = await activeVersionBytesTask;
376 if (activeVersionBytes !=
null)
378 var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
380 Version activeVersion;
381 bool hasRequestedActiveVersion;
383 hasRequestedActiveVersion = Version.TryParse(activeVersionString, out activeVersion)
386 if (hasRequestedActiveVersion)
390 logger.LogWarning(
"Failed to load saved active version {activeVersion}!", activeVersionString);
397 public Task
StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
412 Stream customVersionStream,
414 bool allowInstallation,
415 CancellationToken cancellationToken)
417 var ourTcs =
new TaskCompletionSource();
420 bool installedOrInstalling;
423 if (customVersionStream !=
null)
425 var customInstallationNumber = 1;
428 version =
new Version(version.Major, version.Minor, customInstallationNumber++);
433 installedOrInstalling =
installedVersions.TryGetValue(version, out var installationContainer);
434 if (!installedOrInstalling)
436 if (!allowInstallation)
437 throw new InvalidOperationException($
"BYOND version {version} not installed!");
442 installation = installationContainer.Instance;
443 installLock = installationContainer.AddReference();
448 if (installedOrInstalling)
450 if (progressReporter !=
null)
451 progressReporter.StageName =
"Waiting for existing installation job...";
454 logger.LogWarning(
"The required BYOND version ({version}) is not readily available! We will have to wait for it to install.", version);
463 if (customVersionStream !=
null)
464 logger.LogInformation(
"Installing custom BYOND version as {version}...", version);
465 else if (neededForLock)
467 if (version.Build > 0)
470 logger.LogWarning(
"The required BYOND version ({version}) is not readily available! We will have to install it.", version);
473 logger.LogDebug(
"Requested BYOND version {version} not currently installed. Doing so now...", version);
475 if (progressReporter !=
null)
476 progressReporter.StageName =
"Running event";
478 var versionString = version.ToString();
481 await
InstallVersionFiles(progressReporter, version, customVersionStream, cancellationToken);
487 if (ex is not OperationCanceledException)
493 ourTcs.SetException(ex);
517 async Task DirectoryCleanup()
523 var directoryCleanupTask = DirectoryCleanup();
527 if (customVersionStream ==
null)
529 if (progressReporter !=
null)
530 progressReporter.StageName =
"Downloading version";
535 versionZipStream = customVersionStream;
537 await
using (versionZipStream)
539 if (progressReporter !=
null)
540 progressReporter.StageName =
"Cleaning target directory";
542 await directoryCleanupTask;
544 if (progressReporter !=
null)
545 progressReporter.StageName =
"Extracting zip";
547 logger.LogTrace(
"Extracting downloaded BYOND zip to {extractPath}...", installFullPath);
551 if (progressReporter !=
null)
552 progressReporter.StageName =
"Running installation actions";
556 if (progressReporter !=
null)
557 progressReporter.StageName =
"Writing version file";
562 Encoding.UTF8.GetBytes(version.ToString()),
565 catch (HttpRequestException e)
570 catch (OperationCanceledException)
608 return installationContainer;
617 async Task
TrustDmbPath(
string fullDmbPath, CancellationToken cancellationToken)
624 logger.LogDebug(
"Adding .dmb ({dmbPath}) to {trustedFilePath}", fullDmbPath, trustedFilePath);
628 string trustedFileText;
632 trustedFileText = Encoding.UTF8.GetString(trustedFileBytes);
633 trustedFileText = $
"{trustedFileText.Trim()}{Environment.NewLine}";
637 trustedFileText = String.Empty;
640 if (trustedFileText.Contains(fullDmbPath, StringComparison.Ordinal))
643 trustedFileText = $
"{trustedFileText}{fullDmbPath}{Environment.NewLine}";
645 var newTrustedFileBytes = Encoding.UTF8.GetBytes(trustedFileText);
Task InstallationTask
The Task that completes when the BYOND version finished installing.
async Task< ByondExecutableLock > AssertAndLockVersion(JobProgressReporter progressReporter, Version version, Stream customVersionStream, bool neededForLock, bool allowInstallation, CancellationToken cancellationToken)
Ensures a BYOND version is installed if it isn't already.
async Task TrustDmbPath(string fullDmbPath, CancellationToken cancellationToken)
Add a given fullDmbPath to the trusted DMBs list in BYOND's config.
readonly IEventConsumer eventConsumer
The IEventConsumer for the ByondManager.
readonly Dictionary< Version, ReferenceCountingContainer< ByondInstallation, ByondExecutableLock > > installedVersions
Map of byond Versions to Tasks that complete when they are installed.
static readonly SemaphoreSlim UserFilesSemaphore
SemaphoreSlim for writing to files in the user's BYOND directory.
async Task InstallVersionFiles(JobProgressReporter progressReporter, Version version, Stream customVersionStream, CancellationToken cancellationToken)
Installs the files for a given BYOND version .
readonly ILogger< ByondManager > logger
The ILogger for the ByondManager.
readonly IIOManager ioManager
The IIOManager for the ByondManager.
readonly IByondInstaller byondInstaller
The IByondInstaller for the ByondManager.
Version ActiveVersion
The currently active BYOND version.
static void CheckVersionParameter(Version version)
Validates a given version parameter.
ByondManager(IIOManager ioManager, IByondInstaller byondInstaller, IEventConsumer eventConsumer, ILogger< ByondManager > logger)
Initializes a new instance of the ByondManager class.
const string ActiveVersionFileName
The file in which we store the ActiveVersion.
const string TrustedDmbFileName
The name of the list of trusted .dmb files in the user's BYOND cfg directory.
readonly SemaphoreSlim changeDeleteSemaphore
The SemaphoreSlim for changing or deleting the active BYOND version.
Task StopAsync(CancellationToken cancellationToken)
async Task< IByondExecutableLock > UseExecutables(Version requiredVersion, string trustDmbFullPath, CancellationToken cancellationToken)
Lock the current installation's location and return a IByondExecutableLock. A Task<TResult> resulting...
async Task ChangeVersion(JobProgressReporter progressReporter, Version version, Stream customVersionStream, bool allowInstallation, CancellationToken cancellationToken)
Change the active BYOND version. A Task representing the running operation.
async Task StartAsync(CancellationToken cancellationToken)
async Task DeleteVersion(JobProgressReporter progressReporter, Version version, CancellationToken cancellationToken)
Deletes a given BYOND version from the disk. A Task representing the running operation.
const string BinPath
The path to the BYOND bin folder.
IReadOnlyList< Version > InstalledVersions
The installed BYOND versions.
const string VersionFileName
The file in which we store the Version for installations.
const string CfgDirectoryName
The path to the cfg directory.
ReferenceCountingContainer< ByondInstallation, ByondExecutableLock > AddInstallationContainer(Version version, Task installationTask)
Create and add a new ByondInstallation to installedVersions.
TaskCompletionSource activeVersionChanged
TaskCompletionSource that notifes when the ActiveVersion changes.
IIOManager that resolves paths to Environment.CurrentDirectory.
const string CurrentDirectory
Path to the current working directory for the IIOManager.
Operation exceptions thrown from the context of a Models.Job.
Progress reporter for a Job.
Wrapper for managing some TWrapped .
Task OnZeroReferences
A Task that completes when there are no TReference s active for the Instance.
Async lock context helper.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken)
Asyncronously locks a semaphore .
For downloading and installing BYOND extractions for a given system.
string PathToUserByondFolder
The path to the BYOND folder for the user.
Task< MemoryStream > DownloadVersion(Version version, CancellationToken cancellationToken)
Download a given BYOND version .
string DreamMakerName
Get the file name of the DreamMaker executable.
string GetDreamDaemonName(Version version, out bool supportsCli)
Get the file name of the DreamDaemon executable.
Task InstallByond(Version version, string path, CancellationToken cancellationToken)
Does actions necessary to get an extracted BYOND installation working.
Task UpgradeInstallation(Version version, string path, CancellationToken cancellationToken)
Does actions necessary to get upgrade a BYOND version installed by a previous version of TGS.
For managing the BYOND installation.
Consumes EventTypes and takes the appropriate actions.
Task HandleEvent(EventType eventType, IEnumerable< string > parameters, bool deploymentPipeline, CancellationToken cancellationToken)
Handle a given eventType .
Interface for using filesystems.
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 CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path .
Task DeleteFile(string path, CancellationToken cancellationToken)
Deletes a file at path .
Task ZipToDirectory(string path, Stream zipFile, CancellationToken cancellationToken)
Extract a set of zipFile to a given 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 WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
EventType
Types of events. Mirror in tgs.dm.