1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.Extensions.Logging;
4 using System.Collections.Generic;
7 using System.Threading.Tasks;
20 public Task OnNewerDmb
25 return newerDmbTcs.Task;
30 public bool DmbAvailable => nextDmbProvider != null;
78 this.databaseContextFactory = databaseContextFactory ??
throw new ArgumentNullException(nameof(databaseContextFactory));
79 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
80 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
81 this.instance = instance ??
throw new ArgumentNullException(nameof(instance));
83 cleanupTask = Task.CompletedTask;
84 newerDmbTcs =
new TaskCompletionSource<object>();
85 cleanupCts =
new CancellationTokenSource();
86 jobLockCounts =
new Dictionary<long, int>();
90 public void Dispose() => cleanupCts.Dispose();
98 async Task HandleCleanup()
100 var deleteJob = ioManager.DeleteDirectory(job.
DirectoryName.ToString(), cleanupCts.Token);
103 otherTask = cleanupTask;
104 await Task.WhenAll(otherTask, deleteJob).ConfigureAwait(
false);
108 if (!jobLockCounts.TryGetValue(job.
Id, out var currentVal) || currentVal == 1)
110 jobLockCounts.Remove(job.
Id);
111 logger.LogDebug(
"Cleaning compile job {0} => {1}", job.
Id, job.
DirectoryName);
112 cleanupTask = HandleCleanup();
116 var decremented = --jobLockCounts[job.
Id];
117 logger.LogTrace(
"Compile job {0} lock count now: {1}", job.
Id, decremented);
126 throw new ArgumentNullException(nameof(job));
128 var newProvider = await FromCompileJob(job, cancellationToken).ConfigureAwait(
false);
129 if (newProvider == null)
133 nextDmbProvider?.Dispose();
134 nextDmbProvider = newProvider;
135 newerDmbTcs.SetResult(nextDmbProvider);
136 newerDmbTcs =
new TaskCompletionSource<object>();
144 throw new InvalidOperationException(
"No .dmb available!");
146 throw new ArgumentOutOfRangeException(nameof(lockCount), lockCount,
"lockCount must be greater than or equal to 0!");
150 var incremented = jobLockCounts[jobId] += lockCount;
151 logger.LogTrace(
"Compile job {0} lock count now: {1}", jobId, incremented);
152 return nextDmbProvider;
157 public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async (db) =>
160 var cj = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id)
161 .OrderByDescending(x => x.Job.StoppedAt).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(
false);
164 await LoadCompileJob(cj, cancellationToken).ConfigureAwait(
false);
169 public async Task
StopAsync(CancellationToken cancellationToken)
171 using (cancellationToken.Register(() => cleanupCts.Cancel()))
172 await cleanupTask.ConfigureAwait(
false);
178 if (compileJob == null)
179 throw new ArgumentNullException(nameof(compileJob));
182 await databaseContextFactory.UseContext(async db => compileJob = await db.CompileJobs.Where(x => x.Id == compileJob.
Id)
183 .Include(x => x.Job).ThenInclude(x => x.StartedBy)
184 .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
185 .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
186 .FirstAsync(cancellationToken).ConfigureAwait(
false)).ConfigureAwait(
false);
188 logger.LogTrace(
"Loading compile job {0}...", compileJob.
Id);
189 var providerSubmitted =
false;
190 var newProvider =
new DmbProvider(compileJob, ioManager, () =>
192 if (providerSubmitted)
193 CleanJob(compileJob);
198 var primaryCheckTask = ioManager.FileExists(ioManager.ConcatPath(newProvider.PrimaryDirectory, newProvider.DmbName), cancellationToken);
199 var secondaryCheckTask = ioManager.FileExists(ioManager.ConcatPath(newProvider.PrimaryDirectory, newProvider.DmbName), cancellationToken);
201 if (!(await primaryCheckTask.ConfigureAwait(
false) && await secondaryCheckTask.ConfigureAwait(
false)))
203 logger.LogWarning(
"Error loading compile job, .dmb missing!");
209 if (!jobLockCounts.TryGetValue(compileJob.
Id, out
int value))
212 jobLockCounts.Add(compileJob.
Id, 1);
215 jobLockCounts[compileJob.
Id] = ++value;
217 logger.LogTrace(
"Compile job {0} lock count now: {1}", compileJob.
Id, value);
219 providerSubmitted =
true;
225 if (!providerSubmitted)
233 List<long> jobIdsToSkip;
236 jobIdsToSkip = jobLockCounts.Select(x => x.Key).ToList();
238 List<string> jobUidsToNotErase = null;
241 await databaseContextFactory.UseContext(async db =>
243 jobUidsToNotErase = await db.CompileJobs.Where(x => x.Job.Instance.Id == instance.Id && jobIdsToSkip.Contains(x.Id)).Select(x => x.DirectoryName.Value.ToString().ToUpperInvariant()).ToListAsync(cancellationToken).ConfigureAwait(
false);
244 }).ConfigureAwait(
false);
247 if (exceptThisOne != null)
248 jobUidsToNotErase.Add(exceptThisOne.
DirectoryName.Value.ToString().ToUpperInvariant());
251 await ioManager.CreateDirectory(
".", cancellationToken).ConfigureAwait(
false);
252 var directories = await ioManager.GetDirectories(
".", cancellationToken).ConfigureAwait(
false);
254 var tasks = directories.Select(async x =>
256 var nameOnly = ioManager.GetFileName(x);
257 if (jobUidsToNotErase.Contains(nameOnly.ToUpperInvariant()))
262 await ioManager.DeleteDirectory(x, cancellationToken).ConfigureAwait(
false);
264 catch (OperationCanceledException)
270 logger.LogWarning(
"Error deleting directory {0}! Exception: {1}", x, e);
275 logger.LogDebug(
"Cleaning {0} unused game folders...", deleting);
276 await Task.WhenAll().ConfigureAwait(
false);
IDmbProvider nextDmbProvider
The latest DmbProvider
Task cleanupTask
Task representing calls to CleanJob(CompileJob)
CompileJob CompileJob
The CompileJob of the .dmb
Factory for scoping usage of IDatabaseContexts. Meant for use by Components
async Task StopAsync(CancellationToken cancellationToken)
Dictionary< long, int > jobLockCounts
readonly IIOManager ioManager
The IIOManager for the DmbFactory
Factory for IDmbProviders
async Task< IDmbProvider > FromCompileJob(CompileJob compileJob, CancellationToken cancellationToken)
Gets a IDmbProvider for a given CompileJob
DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager, ILogger< DmbFactory > logger, Api.Models.Instance instance)
Construct a DmbFactory
async Task CleanUnusedCompileJobs(CompileJob exceptThisOne, CancellationToken cancellationToken)
Deletes all compile jobs that are inactive in the Game folder exceptThisOne
readonly CancellationTokenSource cleanupCts
The CancellationTokenSource for cleanupTask
readonly ILogger< DmbFactory > logger
The ILogger for the DmbFactory
readonly Api.Models.Instance instance
The Api.Models.Instance for the DmbFactory
void CleanJob(CompileJob job)
Delete the Api.Models.Internal.CompileJob.DirectoryName of job
IDmbProvider LockNextDmb(int lockCount)
Gets the next IDmbProvider
Guid DirectoryName
The Game folder the results were compiled into
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the DmbFactory
TaskCompletionSource< object > newerDmbTcs
TaskCompletionSource<TResult> resulting in the latest DmbProvider yet to exist
async Task LoadCompileJob(CompileJob job, CancellationToken cancellationToken)
Load a new job into the ICompileJobConsumer
Provides absolute paths to the latest compiled .dmbs
Interface for using filesystems