1 using Microsoft.Extensions.Logging;
3 using System.Collections.Generic;
8 using System.Threading.Tasks;
24 public const string BinPath =
"byond/bin";
29 const string CfgDirectoryName =
"cfg";
34 const string TrustedDmbFileName =
"trusted.txt";
39 const string VersionFileName =
"Version.txt";
44 const string ActiveVersionFileName =
"ActiveVersion.txt";
47 public Version ActiveVersion {
get;
private set; }
50 public IReadOnlyList<Version> InstalledVersions
54 lock (installedVersions)
55 return installedVersions.Select(x => Version.Parse(x.Key).Semver()).ToList();
77 readonly ILogger<ByondManager>
logger;
94 static string VersionKey(Version version) =>
new Version(version.Major, version.Minor).ToString();
105 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
106 this.byondInstaller = byondInstaller ??
throw new ArgumentNullException(nameof(byondInstaller));
107 this.eventConsumer = eventConsumer ??
throw new ArgumentNullException(nameof(eventConsumer));
108 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
110 installedVersions =
new Dictionary<string, Task>();
111 semaphore =
new SemaphoreSlim(1);
115 public void Dispose() => semaphore.Dispose();
125 var ourTcs =
new TaskCompletionSource<object>();
128 var versionKey = VersionKey(version);
130 lock (installedVersions)
132 installed = installedVersions.TryGetValue(versionKey, out inProgressTask);
134 installedVersions.Add(versionKey, ourTcs.Task);
138 using (cancellationToken.Register(() => ourTcs.SetCanceled()))
140 await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(
false);
141 cancellationToken.ThrowIfCancellationRequested();
145 logger.LogDebug(
"Requested BYOND version {0} not currently installed. Doing so now...");
150 await eventConsumer.HandleEvent(
EventType.ByondInstallStart,
new List<string> { versionKey }, cancellationToken).ConfigureAwait(
false);
151 var downloadTask = byondInstaller.DownloadVersion(version, cancellationToken);
153 await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(
false);
157 var download = await downloadTask.ConfigureAwait(
false);
158 await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(
false);
160 var extractPath = ioManager.ResolvePath(versionKey);
161 logger.LogTrace(
"Extracting downloaded BYOND zip to {0}...", extractPath);
162 await ioManager.ZipToDirectory(extractPath, download, cancellationToken).ConfigureAwait(
false);
163 await byondInstaller.InstallByond(extractPath, version, cancellationToken).ConfigureAwait(
false);
166 await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(
false);
168 catch (WebException e)
173 catch (OperationCanceledException)
179 await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(
false);
183 ourTcs.SetResult(null);
187 if (!(e is OperationCanceledException))
188 await eventConsumer.HandleEvent(
EventType.ByondInstallFail,
new List<string> { e.Message }, cancellationToken).ConfigureAwait(
false);
189 lock (installedVersions)
190 installedVersions.Remove(versionKey);
191 ourTcs.SetException(e);
197 public async Task
ChangeVersion(Version version, CancellationToken cancellationToken)
200 throw new ArgumentNullException(nameof(version));
201 var versionKey = VersionKey(version);
202 await InstallVersion(version, cancellationToken).ConfigureAwait(
false);
205 await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(versionKey), cancellationToken).ConfigureAwait(
false);
206 await eventConsumer.HandleEvent(
EventType.ByondActiveVersionChange,
new List<string> { ActiveVersion != null ? VersionKey(ActiveVersion) : null, versionKey }, cancellationToken).ConfigureAwait(
false);
207 ActiveVersion = version;
212 public async Task<IByondExecutableLock>
UseExecutables(Version requiredVersion, CancellationToken cancellationToken)
214 var versionToUse = requiredVersion ?? ActiveVersion;
215 if (versionToUse == null)
217 await InstallVersion(versionToUse, cancellationToken).ConfigureAwait(
false);
219 var versionKey = VersionKey(versionToUse);
220 var binPathForVersion = ioManager.ConcatPath(versionKey, BinPath);
222 logger.LogTrace(
"Creating ByondExecutableLock lock for version {0}", requiredVersion);
227 ioManager.ResolvePath(
228 ioManager.ConcatPath(
230 byondInstaller.DreamDaemonName)),
231 ioManager.ResolvePath(
232 ioManager.ConcatPath(
234 byondInstaller.DreamMakerName)),
235 ioManager.ResolvePath(
236 ioManager.ConcatPath(
237 byondInstaller.PathToUserByondFolder,
239 TrustedDmbFileName)));
243 public async Task
StartAsync(CancellationToken cancellationToken)
245 async Task<byte[]> GetActiveVersion()
247 var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(
false);
248 return !activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(
false);
251 var activeVersionBytesTask = GetActiveVersion();
254 var localCfgDirectory = ioManager.ConcatPath(
255 byondInstaller.PathToUserByondFolder,
257 await ioManager.CreateDirectory(
259 cancellationToken).ConfigureAwait(
false);
262 var trustedFilePath =
263 ioManager.ConcatPath(
266 logger.LogTrace(
"Deleting trusted .dmbs file {0}", trustedFilePath);
267 await ioManager.DeleteFile(
269 cancellationToken).ConfigureAwait(
false);
271 var byondDirectory = ioManager.ResolvePath();
272 await ioManager.CreateDirectory(byondDirectory, cancellationToken).ConfigureAwait(
false);
273 var directories = await ioManager.GetDirectories(byondDirectory, cancellationToken).ConfigureAwait(
false);
275 async Task ReadVersion(
string path)
277 var versionFile = ioManager.ConcatPath(path, VersionFileName);
278 if (!await ioManager.FileExists(versionFile, cancellationToken).ConfigureAwait(
false))
280 logger.LogInformation(
"Cleaning unparsable version path: {0}", ioManager.ResolvePath(path));
281 await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(
false);
285 var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(
false);
286 var text = Encoding.UTF8.GetString(bytes);
287 if (Version.TryParse(text, out var version))
289 var key = VersionKey(version);
290 lock (installedVersions)
291 if (!installedVersions.ContainsKey(key))
293 logger.LogDebug(
"Adding detected BYOND version {0}...", key);
294 installedVersions.Add(key, Task.CompletedTask);
299 await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(
false);
302 await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(
false);
304 var activeVersionBytes = await activeVersionBytesTask.ConfigureAwait(
false);
305 if (activeVersionBytes != null)
307 var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
308 bool hasRequestedActiveVersion;
309 lock (installedVersions)
310 hasRequestedActiveVersion = installedVersions.ContainsKey(activeVersionString);
311 if (hasRequestedActiveVersion && Version.TryParse(activeVersionString, out var activeVersion))
312 ActiveVersion = activeVersion.Semver();
315 logger.LogWarning(
"Failed to load saved active version {0}!", activeVersionString);
316 await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(
false);
322 public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
readonly IIOManager ioManager
The IIOManager for the ByondManager
ErrorCode
Types of ErrorMessages that the API may return.
Use server authentication
async Task StartAsync(CancellationToken cancellationToken)
EventType
Types of events. Mirror in tgs.dm
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
Operation exceptions thrown from the context of a Models.Job
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
Consumes EventTypes and takes the appropriate actions
readonly ILogger< ByondManager > logger
The ILogger for the ByondManager
Represents a BYOND installation
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
readonly IEventConsumer eventConsumer
The IEventConsumer for the ByondManager
Async lock context helper