tgstation-server 6.19.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
ByondInstallerBase.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.Linq;
5using System.Threading;
6using System.Threading.Tasks;
7
8using Microsoft.Extensions.Logging;
9using Microsoft.Extensions.Options;
10
15
17{
22 {
26 protected const string ByondBinPath = "byond/bin";
27
31 protected const string CfgDirectoryName = "cfg";
32
36 const string CacheDirectoryName = "cache";
37
41 static readonly Version MapThreadsVersion = new(515, 1609);
42
44 protected override EngineType TargetEngineType => EngineType.Byond;
45
49 protected IOptionsMonitor<GeneralConfiguration> GeneralConfigurationOptions { get; }
50
54 protected abstract string PathToUserFolder { get; }
55
59 protected abstract string DreamMakerName { get; }
60
64 protected abstract string OSMarkerTemplate { get; }
65
70
79 internal static Uri GetDownloadZipUrl(Version semver, string byondZipDownloadTemplate, string osMarkerTemplate)
80 {
81 // god forbid
82 var guardGuid = Guid.NewGuid();
83
84 var url = byondZipDownloadTemplate
85 .Replace("$$", guardGuid.ToString(), StringComparison.Ordinal)
86 .Replace("${Major}", semver.Major.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)
87 .Replace("${Minor}", semver.Minor.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal);
88
89 var osMarkerPrefix = $"${{{osMarkerTemplate}:";
90 var osMarkerIndex = url.IndexOf(osMarkerPrefix);
91 while (osMarkerIndex != -1)
92 {
93 var start = osMarkerIndex + osMarkerPrefix.Length;
94 var end = url.IndexOf('}', start);
95 if (end == -1)
96 break;
97
98 var substitution = url.Substring(start, end - start);
99 url = url.Replace($"{osMarkerPrefix}{substitution}}}", substitution, StringComparison.Ordinal);
100
101 osMarkerIndex = url.IndexOf(osMarkerPrefix);
102 }
103
104 // at this point, any other substitution attempts should be removed
105 var otherMarkerPrefix = "${";
106 var otherMarkerIndex = url.IndexOf(otherMarkerPrefix);
107 while (otherMarkerIndex != -1)
108 {
109 var start = otherMarkerIndex + otherMarkerPrefix.Length;
110 var end = url.IndexOf('}', start);
111 if (end == -1)
112 break;
113
114 var substitution = url.Substring(start, end - start);
115 url = url.Replace($"{otherMarkerPrefix}{substitution}}}", String.Empty, StringComparison.Ordinal);
116
117 otherMarkerIndex = url.IndexOf(otherMarkerPrefix);
118 }
119
120 url = url.Replace(guardGuid.ToString(), "$", StringComparison.Ordinal);
121
122 return new Uri(url);
123 }
124
133 IIOManager ioManager,
134 ILogger<ByondInstallerBase> logger,
136 IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions)
137 : base(ioManager, logger)
138 {
139 this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
140 GeneralConfigurationOptions = generalConfigurationOptions ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
141 }
142
144 public sealed override ValueTask<IEngineInstallation> GetInstallation(EngineVersion version, string path, Task installationTask, CancellationToken cancellationToken)
145 {
146 CheckVersionValidity(version);
147
148 var installationIOManager = IOManager.CreateResolverForSubdirectory(path);
149 var supportsMapThreads = version.Version >= MapThreadsVersion;
150
151 var dreamDaemonName = GetDreamDaemonName(
152 version.Version!,
153 out var supportsCli);
154 var dreamDaemonPath = installationIOManager.ResolvePath(
155 installationIOManager.ConcatPath(
157 dreamDaemonName));
158 var dreamMakerPath = installationIOManager.ResolvePath(
159 installationIOManager.ConcatPath(
162
163 return ValueTask.FromResult<IEngineInstallation>(
165 installationIOManager,
166 installationTask,
167 version,
168 dreamDaemonPath,
169 dreamMakerPath,
170 supportsCli,
171 supportsMapThreads));
172 }
173
175 public sealed override async Task CleanCache(CancellationToken cancellationToken)
176 {
177 try
178 {
179 var byondDir = PathToUserFolder;
180
181 Logger.LogDebug("Cleaning BYOND cache...");
182 async Task CleanDirectorySafe()
183 {
184 try
185 {
188 byondDir,
190 cancellationToken);
191 }
192 catch (Exception ex)
193 {
194 Logger.LogWarning(ex, "Failed to clean BYOND cache!");
195 }
196 }
197
198 var cacheCleanTask = CleanDirectorySafe();
199
200 // Create local cfg directory in case it doesn't exist
201 var localCfgDirectory = IOManager.ConcatPath(
202 byondDir,
204
205 var cfgCreateTask = IOManager.CreateDirectory(
206 localCfgDirectory,
207 cancellationToken);
208
209 var additionalCleanTasks = AdditionalCacheCleanFilePaths(localCfgDirectory)
210 .Select(path => IOManager.DeleteFile(path, cancellationToken));
211
212 await Task.WhenAll(cacheCleanTask, cfgCreateTask, Task.WhenAll(additionalCleanTasks));
213 }
214 catch (Exception ex) when (ex is not OperationCanceledException)
215 {
216 Logger.LogWarning(ex, "Error cleaning BYOND cache!");
217 }
218 }
219
221 public sealed override async ValueTask<IEngineInstallationData> DownloadVersion(EngineVersion version, JobProgressReporter progressReporter, CancellationToken cancellationToken)
222 {
223 CheckVersionValidity(version);
224
225 var url = GetDownloadZipUrl(version);
226 Logger.LogTrace("Downloading {engineType} version {version} from {url}...", TargetEngineType, version, url);
227
228 await using var download = fileDownloader.DownloadFile(url, null);
229 await using var buffer = new BufferedFileStreamProvider(
230 await download.GetResult(cancellationToken));
231
232 var stream = await buffer.GetOwnedResult(cancellationToken);
233 try
234 {
236 IOManager,
237 stream);
238 }
239 catch
240 {
241 await stream.DisposeAsync();
242 throw;
243 }
244 }
245
252 protected abstract string GetDreamDaemonName(Version byondVersion, out bool supportsCli);
253
259 protected virtual IEnumerable<string> AdditionalCacheCleanFilePaths(string configDirectory) => Enumerable.Empty<string>();
260
267 {
268 CheckVersionValidity(version);
269
270 var guardGuid = Guid.NewGuid();
271
272 var semver = version.Version!;
273 var template = GeneralConfigurationOptions.CurrentValue.ByondZipDownloadTemplate;
274 return GetDownloadZipUrl(semver, template, OSMarkerTemplate);
275 }
276 }
277}
Information about an engine installation.
Version? Version
The System.Version of the engine. Currently only valid when Engine is EngineType.Byond.
Implementation of IEngineInstallation for EngineType.Byond.
Base implementation of IEngineInstaller for EngineType.Byond.
readonly IFileDownloader fileDownloader
The IFileDownloader for the ByondInstallerBase.
string GetDreamDaemonName(Version byondVersion, out bool supportsCli)
Get the file name of the DreamDaemon executable.
const string CacheDirectoryName
The name of BYOND's cache directory.
const string CfgDirectoryName
The path to the cfg directory.
string OSMarkerTemplate
Template to do ${Marker:xxx} replacements in GeneralConfiguration.ByondZipDownloadTemplate.
static readonly Version MapThreadsVersion
The first Version of BYOND that supports the '-map-threads' parameter on DreamDaemon.
const string ByondBinPath
The path to the BYOND bin folder.
override ValueTask< IEngineInstallation > GetInstallation(EngineVersion version, string path, Task installationTask, CancellationToken cancellationToken)
Creates an IEngineInstallation for a given version .A ValueTask<TResult> resulting in a new IEngineIn...
virtual IEnumerable< string > AdditionalCacheCleanFilePaths(string configDirectory)
List off additional file paths in the configDirectory to delete.
ByondInstallerBase(IIOManager ioManager, ILogger< ByondInstallerBase > logger, IFileDownloader fileDownloader, IOptionsMonitor< GeneralConfiguration > generalConfigurationOptions)
Initializes a new instance of the ByondInstallerBase class.
override async Task CleanCache(CancellationToken cancellationToken)
Attempts to cleans the engine's cache folder for the system.A Task representing the running operation...
Uri GetDownloadZipUrl(EngineVersion version)
Create a Uri pointing to the location of the download for a given version .
string PathToUserFolder
Path to the system user's local BYOND folder.
override async ValueTask< IEngineInstallationData > DownloadVersion(EngineVersion version, JobProgressReporter progressReporter, CancellationToken cancellationToken)
Download a given engine version .A ValueTask<TResult> resulting in the IEngineInstallationData for th...
string DreamMakerName
Path to the DreamMaker executable.
IOptionsMonitor< GeneralConfiguration > GeneralConfigurationOptions
The GeneralConfiguration IOptionsMonitor<TOptions> for the ByondInstallerBase.
void CheckVersionValidity(EngineVersion version)
Check that a given version is of type EngineType.Byond.
IIOManager IOManager
Gets the IIOManager for the EngineInstallerBase.
ILogger< EngineInstallerBase > Logger
Gets the ILogger for the EngineInstallerBase.
Implementation of IEngineInstallationData for a zip file in a Stream.
IFileStreamProvider that provides a ISeekableFileStreamProvider from an input Stream.
IFileStreamProvider DownloadFile(Uri url, string? bearerToken)
Downloads a file from a given url .
Interface for using filesystems.
Definition IIOManager.cs:14
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path .
Task DeleteFile(string path, CancellationToken cancellationToken)
Deletes a file at path .
IIOManager CreateResolverForSubdirectory(string subdirectoryPath)
Create a new IIOManager that resolves paths to the specified subdirectoryPath .
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
EngineType
The type of engine the codebase is using.
Definition EngineType.cs:7