1 using Microsoft.Extensions.Logging;
3 using System.Collections.Generic;
9 using System.Threading.Tasks;
21 public const string BinPath =
"byond/bin";
26 const string VersionFileName =
"Version.txt";
30 const string ActiveVersionFileName =
"ActiveVersion.txt";
33 public Version ActiveVersion {
get;
private set; }
36 public IReadOnlyList<Version> InstalledVersions
40 lock (installedVersions)
41 return installedVersions.Select(x => Version.Parse(x.Key)).ToList();
63 readonly ILogger<ByondManager>
logger;
80 static string VersionKey(Version version) =>
new Version(version.Major, version.Minor).ToString();
91 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
92 this.byondInstaller = byondInstaller ??
throw new ArgumentNullException(nameof(byondInstaller));
93 this.eventConsumer = eventConsumer ??
throw new ArgumentNullException(nameof(eventConsumer));
94 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
96 installedVersions =
new Dictionary<string, Task>();
97 semaphore =
new SemaphoreSlim(1);
101 public void Dispose() => semaphore.Dispose();
111 var ourTcs =
new TaskCompletionSource<object>();
114 var versionKey = VersionKey(version);
116 lock (installedVersions)
118 installed = installedVersions.TryGetValue(versionKey, out inProgressTask);
120 installedVersions.Add(versionKey, ourTcs.Task);
123 using (cancellationToken.Register(() => ourTcs.SetCanceled()))
125 await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(
false);
126 cancellationToken.ThrowIfCancellationRequested();
132 await eventConsumer.HandleEvent(
EventType.ByondInstallStart,
new List<string> { versionKey }, cancellationToken).ConfigureAwait(
false);
133 var downloadTask = byondInstaller.DownloadVersion(version, cancellationToken);
135 await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(
false);
136 await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(
false);
140 var download = await downloadTask.ConfigureAwait(
false);
141 await ioManager.ZipToDirectory(versionKey, download, cancellationToken).ConfigureAwait(
false);
142 await byondInstaller.InstallByond(ioManager.ResolvePath(versionKey), version, cancellationToken).ConfigureAwait(
false);
145 await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(
false);
147 catch (WebException e)
150 throw new JobException(String.Format(CultureInfo.InvariantCulture,
"Error downloading BYOND version: {0}", e.Message));
152 catch (OperationCanceledException)
158 await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(
false);
162 ourTcs.SetResult(null);
166 if (!(e is OperationCanceledException))
167 await eventConsumer.HandleEvent(
EventType.ByondInstallFail,
new List<string> { e.Message }, cancellationToken).ConfigureAwait(
false);
168 lock (installedVersions)
169 installedVersions.Remove(versionKey);
170 ourTcs.SetException(e);
176 public async Task
ChangeVersion(Version version, CancellationToken cancellationToken)
179 throw new ArgumentNullException(nameof(version));
180 var versionKey = VersionKey(version);
181 await InstallVersion(version, cancellationToken).ConfigureAwait(
false);
184 await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(versionKey), cancellationToken).ConfigureAwait(
false);
185 await eventConsumer.HandleEvent(
EventType.ByondActiveVersionChange,
new List<string> { ActiveVersion != null ? VersionKey(ActiveVersion) : null, versionKey }, cancellationToken).ConfigureAwait(
false);
186 ActiveVersion = version;
191 public async Task<IByondExecutableLock>
UseExecutables(Version requiredVersion, CancellationToken cancellationToken)
193 var versionToUse = requiredVersion ?? ActiveVersion;
194 if (versionToUse == null)
196 await InstallVersion(versionToUse, cancellationToken).ConfigureAwait(
false);
198 var versionKey = VersionKey(versionToUse);
200 return new ByondExecutableLock(versionToUse, ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamDaemonName)), ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamMakerName)));
204 public async Task
StartAsync(CancellationToken cancellationToken)
206 async Task<byte[]> GetActiveVersion()
208 if (!await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(
false))
210 return await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(
false);
213 var activeVersionBytesTask = GetActiveVersion();
215 await ioManager.CreateDirectory(
".", cancellationToken).ConfigureAwait(
false);
216 var directories = await ioManager.GetDirectories(
".", cancellationToken).ConfigureAwait(
false);
218 async Task ReadVersion(
string path)
220 var versionFile = ioManager.ConcatPath(path, VersionFileName);
221 if (!await ioManager.FileExists(versionFile, cancellationToken).ConfigureAwait(
false))
223 logger.LogInformation(
"Cleaning unparsable version path: {0}", ioManager.ResolvePath(path));
224 await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(
false);
227 var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(
false);
228 var text = Encoding.UTF8.GetString(bytes);
229 if (Version.TryParse(text, out var version))
231 var key = VersionKey(version);
232 lock (installedVersions)
233 if (!installedVersions.ContainsKey(key))
235 installedVersions.Add(key, Task.CompletedTask);
239 await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(
false);
242 await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(
false);
244 var activeVersionBytes = await activeVersionBytesTask.ConfigureAwait(
false);
245 if (activeVersionBytes != null)
247 var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
248 if (Version.TryParse(activeVersionString, out var activeVersion))
249 ActiveVersion = activeVersion;
251 await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(
false);
256 public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
readonly IIOManager ioManager
The IIOManager for the ByondManager
Consumes EventTypes and takes the appropriate actions
Use server authentication
EventType
Types of events. Mirror in tgs.dm
async Task StartAsync(CancellationToken cancellationToken)
async Task< IByondExecutableLock > UseExecutables(Version requiredVersion, CancellationToken cancellationToken)
Lock the current installation's location and return a IByondExecutableLock
For downloading and installing BYOND extractions for a given system
async Task InstallVersion(Version version, CancellationToken cancellationToken)
Installs a BYOND version if it isn't already
For managing the BYOND installation
readonly Dictionary< string, Task > installedVersions
Map of byond Versions to Tasks that complete when they are installed
ByondManager(IIOManager ioManager, IByondInstaller byondInstaller, IEventConsumer eventConsumer, ILogger< ByondManager > logger)
Construct a ByondManager
readonly ILogger< ByondManager > logger
The ILogger for the ByondManager
async Task ChangeVersion(Version version, CancellationToken cancellationToken)
Change the active BYOND version
readonly IByondInstaller byondInstaller
The IByondInstaller for the ByondManager
Interface for using filesystems
readonly SemaphoreSlim semaphore
The SemaphoreSlim for the ByondManager
static async Task< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken)
Asyncronously locks a semaphore
Operation exceptions thrown from the context of a Models.Job
readonly IEventConsumer eventConsumer
The IEventConsumer for the ByondManager
Async lock context helper