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;
95 static string VersionKey(Version version,
bool allowPatch) => (allowPatch && version.Build > 0
96 ?
new Version(version.Major, version.Minor, version.Build)
97 : new Version(version.Major, version.Minor)).ToString();
108 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
109 this.byondInstaller = byondInstaller ??
throw new ArgumentNullException(nameof(byondInstaller));
110 this.eventConsumer = eventConsumer ??
throw new ArgumentNullException(nameof(eventConsumer));
111 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
113 installedVersions =
new Dictionary<string, Task>();
114 semaphore =
new SemaphoreSlim(1);
118 public void Dispose() => semaphore.Dispose();
127 async Task<string>
InstallVersion(Version version, byte[] versionZipBytes, CancellationToken cancellationToken)
129 var ourTcs =
new TaskCompletionSource<object>();
133 lock (installedVersions)
135 if (versionZipBytes != null)
137 int customInstallationNumber = 1;
140 versionKey = $
"{VersionKey(version, false)}.{customInstallationNumber++}";
142 while (installedVersions.ContainsKey(versionKey));
145 versionKey = VersionKey(version,
true);
147 installed = installedVersions.TryGetValue(versionKey, out inProgressTask);
149 installedVersions.Add(versionKey, ourTcs.Task);
153 using (cancellationToken.Register(() => ourTcs.SetCanceled()))
155 await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(
false);
156 cancellationToken.ThrowIfCancellationRequested();
160 if (versionZipBytes != null)
161 logger.LogInformation(
"Installing custom BYOND version as {0}...", versionKey);
162 else if (version.Build > 0)
165 logger.LogDebug(
"Requested BYOND version {0} not currently installed. Doing so now...");
170 await eventConsumer.HandleEvent(
EventType.ByondInstallStart,
new List<string> { versionKey }, cancellationToken).ConfigureAwait(
false);
171 var zipFileBytesTask = versionZipBytes == null
172 ? byondInstaller.DownloadVersion(version, cancellationToken)
173 : Task.FromResult(versionZipBytes);
175 await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(
false);
179 versionZipBytes = await zipFileBytesTask.ConfigureAwait(
false);
180 await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(
false);
182 var extractPath = ioManager.ResolvePath(versionKey);
183 logger.LogTrace(
"Extracting downloaded BYOND zip to {0}...", extractPath);
184 await ioManager.ZipToDirectory(extractPath, versionZipBytes, cancellationToken).ConfigureAwait(
false);
185 versionZipBytes = null;
187 await byondInstaller.InstallByond(extractPath, version, cancellationToken).ConfigureAwait(
false);
190 await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(versionKey), cancellationToken).ConfigureAwait(
false);
192 catch (WebException e)
197 catch (OperationCanceledException)
203 await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(
false);
207 ourTcs.SetResult(null);
211 if (!(e is OperationCanceledException))
212 await eventConsumer.HandleEvent(
EventType.ByondInstallFail,
new List<string> { e.Message }, cancellationToken).ConfigureAwait(
false);
213 lock (installedVersions)
214 installedVersions.Remove(versionKey);
215 ourTcs.SetException(e);
223 public async Task
ChangeVersion(Version version, byte[] customVersionBytes, CancellationToken cancellationToken)
226 throw new ArgumentNullException(nameof(version));
228 var versionKey = await InstallVersion(version, customVersionBytes, cancellationToken).ConfigureAwait(
false);
231 await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(versionKey), cancellationToken).ConfigureAwait(
false);
232 await eventConsumer.HandleEvent(
236 ActiveVersion != null
237 ? VersionKey(ActiveVersion, true)
242 .ConfigureAwait(
false);
245 ActiveVersion = Version.Parse(versionKey);
250 public async Task<IByondExecutableLock>
UseExecutables(Version requiredVersion, CancellationToken cancellationToken)
252 var versionToUse = requiredVersion ?? ActiveVersion;
253 if (versionToUse == null)
255 await InstallVersion(versionToUse, null, cancellationToken).ConfigureAwait(
false);
257 var versionKey = VersionKey(versionToUse,
true);
258 var binPathForVersion = ioManager.ConcatPath(versionKey, BinPath);
260 logger.LogTrace(
"Creating ByondExecutableLock lock for version {0}", requiredVersion);
265 ioManager.ResolvePath(
266 ioManager.ConcatPath(
268 byondInstaller.DreamDaemonName)),
269 ioManager.ResolvePath(
270 ioManager.ConcatPath(
272 byondInstaller.DreamMakerName)),
273 ioManager.ResolvePath(
274 ioManager.ConcatPath(
275 byondInstaller.PathToUserByondFolder,
277 TrustedDmbFileName)));
281 public async Task
StartAsync(CancellationToken cancellationToken)
283 async Task<byte[]> GetActiveVersion()
285 var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(
false);
286 return !activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(
false);
289 var activeVersionBytesTask = GetActiveVersion();
292 var localCfgDirectory = ioManager.ConcatPath(
293 byondInstaller.PathToUserByondFolder,
295 await ioManager.CreateDirectory(
297 cancellationToken).ConfigureAwait(
false);
300 var trustedFilePath =
301 ioManager.ConcatPath(
304 logger.LogTrace(
"Deleting trusted .dmbs file {0}", trustedFilePath);
305 await ioManager.DeleteFile(
307 cancellationToken).ConfigureAwait(
false);
309 var byondDirectory = ioManager.ResolvePath();
310 await ioManager.CreateDirectory(byondDirectory, cancellationToken).ConfigureAwait(
false);
311 var directories = await ioManager.GetDirectories(byondDirectory, cancellationToken).ConfigureAwait(
false);
313 async Task ReadVersion(
string path)
315 var versionFile = ioManager.ConcatPath(path, VersionFileName);
316 if (!await ioManager.FileExists(versionFile, cancellationToken).ConfigureAwait(
false))
318 logger.LogInformation(
"Cleaning unparsable version path: {0}", ioManager.ResolvePath(path));
319 await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(
false);
323 var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(
false);
324 var text = Encoding.UTF8.GetString(bytes);
325 if (Version.TryParse(text, out var version))
327 var key = VersionKey(version,
true);
328 lock (installedVersions)
329 if (!installedVersions.ContainsKey(key))
331 logger.LogDebug(
"Adding detected BYOND version {0}...", key);
332 installedVersions.Add(key, Task.CompletedTask);
337 await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(
false);
340 await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(
false);
342 var activeVersionBytes = await activeVersionBytesTask.ConfigureAwait(
false);
343 if (activeVersionBytes != null)
345 var activeVersionString = Encoding.UTF8.GetString(activeVersionBytes);
346 bool hasRequestedActiveVersion;
347 lock (installedVersions)
348 hasRequestedActiveVersion = installedVersions.ContainsKey(activeVersionString);
349 if (hasRequestedActiveVersion && Version.TryParse(activeVersionString, out var activeVersion))
350 ActiveVersion = activeVersion.Semver();
353 logger.LogWarning(
"Failed to load saved active version {0}!", activeVersionString);
354 await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(
false);
360 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< string > InstallVersion(Version version, byte[] versionZipBytes, CancellationToken cancellationToken)
Installs a BYOND version if it isn't already
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 ChangeVersion(Version version, byte[] customVersionBytes, CancellationToken cancellationToken)
Change the active BYOND version
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. RawData.Content is used to upload custom BYOND version zip files...
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