1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.Extensions.Logging;
4 using System.Collections.Generic;
8 using System.Threading.Tasks;
21 public Task OnNewerDmb
26 return newerDmbTcs.Task;
31 public bool DmbAvailable => nextDmbProvider != null;
87 this.databaseContextFactory = databaseContextFactory ??
throw new ArgumentNullException(nameof(databaseContextFactory));
88 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
89 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
90 this.instance = instance ??
throw new ArgumentNullException(nameof(instance));
92 cleanupTask = Task.CompletedTask;
93 newerDmbTcs =
new TaskCompletionSource<object>();
94 cleanupCts =
new CancellationTokenSource();
95 jobLockCounts =
new Dictionary<long, int>();
99 public void Dispose() => cleanupCts.Dispose();
107 async Task HandleCleanup()
109 var deleteJob = ioManager.DeleteDirectory(job.
DirectoryName.ToString(), cleanupCts.Token);
113 otherTask = cleanupTask;
114 await Task.WhenAll(otherTask, deleteJob).ConfigureAwait(
false);
118 if (!jobLockCounts.TryGetValue(job.
Id, out var currentVal) || currentVal == 1)
120 jobLockCounts.Remove(job.
Id);
121 logger.LogDebug(
"Cleaning lock-free compile job {0} => {1}", job.
Id, job.
DirectoryName);
122 cleanupTask = HandleCleanup();
126 var decremented = --jobLockCounts[job.
Id];
127 logger.LogTrace(
"Compile job {0} lock count now: {1}", job.
Id, decremented);
135 throw new ArgumentNullException(nameof(job));
137 var newProvider = await FromCompileJob(job, cancellationToken).ConfigureAwait(
false);
138 if (newProvider == null)
142 nextDmbProvider?.Dispose();
143 nextDmbProvider = newProvider;
146 var temp = newerDmbTcs;
147 newerDmbTcs =
new TaskCompletionSource<object>();
148 temp.SetResult(nextDmbProvider);
156 throw new InvalidOperationException(
"No .dmb available!");
158 throw new ArgumentOutOfRangeException(nameof(lockCount), lockCount,
"lockCount must be greater than or equal to 0!");
162 var incremented = jobLockCounts[jobId] += lockCount;
163 logger.LogTrace(
"Compile job {0} lock count now: {1}", jobId, incremented);
164 return nextDmbProvider;
169 public async Task
StartAsync(CancellationToken cancellationToken)
172 await databaseContextFactory.UseContext(async (db) =>
177 .Where(x => x.Job.Instance.Id == instance.Id)
178 .OrderByDescending(x => x.Job.StoppedAt)
179 .FirstOrDefaultAsync(cancellationToken)
180 .ConfigureAwait(
false);
182 .ConfigureAwait(
false);
186 await LoadCompileJob(cj, cancellationToken).ConfigureAwait(
false);
192 public async Task
StopAsync(CancellationToken cancellationToken)
194 using (cancellationToken.Register(() => cleanupCts.Cancel()))
195 await cleanupTask.ConfigureAwait(
false);
199 #pragma warning disable CA1506 // TODO: Decomplexify 202 if (compileJob == null)
203 throw new ArgumentNullException(nameof(compileJob));
206 logger.LogTrace(
"Loading compile job {0}...", compileJob.
Id);
207 await databaseContextFactory.UseContext(
208 async db => compileJob = await db
211 .Where(x => x.Id == compileJob.
Id)
212 .Include(x => x.Job).ThenInclude(x => x.StartedBy)
213 .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
214 .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
215 .FirstAsync(cancellationToken)
216 .ConfigureAwait(
false))
217 .ConfigureAwait(
false);
224 logger.LogTrace(
"Setting missing StoppedAt for CompileJob job...");
228 var providerSubmitted =
false;
232 if (providerSubmitted)
233 CleanJob(compileJob);
236 var newProvider =
new DmbProvider(compileJob, ioManager, CleanupAction);
239 const string LegacyADirectoryName =
"A";
240 const string LegacyBDirectoryName =
"B";
242 var dmbExistsAtRoot = await ioManager.FileExists(
243 ioManager.ConcatPath(
244 newProvider.Directory,
245 newProvider.DmbName),
247 .ConfigureAwait(
false);
249 if (!dmbExistsAtRoot)
251 var primaryCheckTask = ioManager.FileExists(
252 ioManager.ConcatPath(
253 newProvider.Directory,
254 LegacyADirectoryName,
255 newProvider.DmbName),
257 var secondaryCheckTask = ioManager.FileExists(
258 ioManager.ConcatPath(
259 newProvider.Directory,
260 LegacyBDirectoryName,
261 newProvider.DmbName),
264 if (!(await primaryCheckTask.ConfigureAwait(
false) && await secondaryCheckTask.ConfigureAwait(
false)))
266 logger.LogWarning(
"Error loading compile job, .dmb missing!");
272 logger.LogDebug(
"Creating legacy two folder .dmb provider targeting {0} directory...", LegacyADirectoryName);
273 newProvider =
new DmbProvider(compileJob, ioManager, CleanupAction, Path.DirectorySeparatorChar + LegacyADirectoryName);
278 if (!jobLockCounts.TryGetValue(compileJob.
Id, out
int value))
281 jobLockCounts.Add(compileJob.
Id, 1);
284 jobLockCounts[compileJob.
Id] = ++value;
286 logger.LogTrace(
"Compile job {0} lock count now: {1}", compileJob.
Id, value);
288 providerSubmitted =
true;
294 if (!providerSubmitted)
295 newProvider.Dispose();
298 #pragma warning restore CA1506 301 #pragma warning disable CA1506 // TODO: Decomplexify 304 List<long> jobIdsToSkip;
308 jobIdsToSkip = jobLockCounts.Select(x => x.Key).ToList();
310 List<string> jobUidsToNotErase = null;
313 await databaseContextFactory.UseContext(async db =>
315 jobUidsToNotErase = (await db
319 x => x.Job.Instance.Id == instance.Id
320 && jobIdsToSkip.Contains(x.Id))
321 .Select(x => x.DirectoryName.Value)
322 .ToListAsync(cancellationToken)
323 .ConfigureAwait(
false))
324 .Select(x => x.ToString())
326 }).ConfigureAwait(
false);
330 logger.LogTrace(
"We will not clean the following directories: {0}", String.Join(
", ", jobUidsToNotErase));
333 var gameDirectory = ioManager.ResolvePath();
334 await ioManager.CreateDirectory(gameDirectory, cancellationToken).ConfigureAwait(
false);
335 var directories = await ioManager.GetDirectories(gameDirectory, cancellationToken).ConfigureAwait(
false);
337 var tasks = directories.Select(async x =>
339 var nameOnly = ioManager.GetFileName(x);
340 if (jobUidsToNotErase.Contains(nameOnly))
342 logger.LogDebug(
"Cleaning unused game folder: {0}...", nameOnly);
346 await ioManager.DeleteDirectory(x, cancellationToken).ConfigureAwait(
false);
348 catch (OperationCanceledException)
354 logger.LogWarning(
"Error deleting directory {0}! Exception: {1}", x, e);
358 await Task.WhenAll(tasks).ConfigureAwait(
false);
360 #pragma warning restore CA1506 367 return LockNextDmb(0)?.CompileJob;
CompileJob CompileJob
The CompileJob of the .dmb
async Task StartAsync(CancellationToken cancellationToken)
IDmbProvider nextDmbProvider
The latest DmbProvider
long Id
The ID of the entity.
async Task StopAsync(CancellationToken cancellationToken)
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the DmbFactory
DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager, ILogger< DmbFactory > logger, Api.Models.Instance instance)
Construct a DmbFactory
Factory for scoping usage of IDatabaseContexts. Meant for use by Components
async Task CleanUnusedCompileJobs(CancellationToken cancellationToken)
Deletes all compile jobs that are inactive in the Game folder.
DateTimeOffset StoppedAt
When the Job stopped
void CleanJob(CompileJob job)
Delete the Api.Models.Internal.CompileJob.DirectoryName of job
Job Job
See Api.Models.CompileJob.Job
TaskCompletionSource< object > newerDmbTcs
TaskCompletionSource<TResult> resulting in the latest DmbProvider yet to exist
readonly IDictionary< long, int > jobLockCounts
Map of CompileJob.JobIds to locks on them.
readonly IIOManager ioManager
The IIOManager for the DmbFactory
readonly CancellationTokenSource cleanupCts
The CancellationTokenSource for cleanupTask
CompileJob LatestCompileJob()
Gets the latest CompileJob.
A IDmbProvider that uses symlinks.
Factory for IDmbProviders
Guid DirectoryName
The Game folder the results were compiled into
async Task< IDmbProvider > FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken)
Gets a IDmbProvider for a given CompileJob
Provides absolute paths to the latest compiled .dmbs
Interface for using filesystems
async Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken)
Load a new job into the ICompileJobSink
const string LiveGameDirectory
The directory where the baseProvider is symlinked to.
IDmbProvider LockNextDmb(int lockCount)
Gets the next IDmbProvider
readonly ILogger< DmbFactory > logger
The ILogger for the DmbFactory
readonly Api.Models.Instance instance
The Api.Models.Instance for the DmbFactory
Task cleanupTask
Task representing calls to CleanJob(CompileJob)