1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.Extensions.Logging;
4 using System.Collections.Generic;
7 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) =>
175 .MostRecentCompletedCompileJobOrDefault(instance, cancellationToken)
176 .ConfigureAwait(
false);
178 .ConfigureAwait(
false);
182 await LoadCompileJob(cj, cancellationToken).ConfigureAwait(
false);
188 public async Task
StopAsync(CancellationToken cancellationToken)
190 using (cancellationToken.Register(() => cleanupCts.Cancel()))
191 await cleanupTask.ConfigureAwait(
false);
195 #pragma warning disable CA1506 // TODO: Decomplexify 198 if (compileJob == null)
199 throw new ArgumentNullException(nameof(compileJob));
202 logger.LogTrace(
"Loading compile job {0}...", compileJob.
Id);
203 await databaseContextFactory.UseContext(
204 async db => compileJob = await db
207 .Where(x => x.Id == compileJob.
Id)
208 .Include(x => x.Job).ThenInclude(x => x.StartedBy)
209 .Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
210 .Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
211 .FirstAsync(cancellationToken)
212 .ConfigureAwait(
false))
213 .ConfigureAwait(
false);
220 logger.LogTrace(
"Setting missing StoppedAt for CompileJob job...");
224 var providerSubmitted =
false;
225 var newProvider =
new DmbProvider(compileJob, ioManager, () =>
227 if (providerSubmitted)
228 CleanJob(compileJob);
233 var primaryCheckTask = ioManager.FileExists(ioManager.ConcatPath(newProvider.PrimaryDirectory, newProvider.DmbName), cancellationToken);
234 var secondaryCheckTask = ioManager.FileExists(ioManager.ConcatPath(newProvider.PrimaryDirectory, newProvider.DmbName), cancellationToken);
236 if (!(await primaryCheckTask.ConfigureAwait(
false) && await secondaryCheckTask.ConfigureAwait(
false)))
238 logger.LogWarning(
"Error loading compile job, .dmb missing!");
244 if (!jobLockCounts.TryGetValue(compileJob.
Id, out
int value))
247 jobLockCounts.Add(compileJob.
Id, 1);
250 jobLockCounts[compileJob.
Id] = ++value;
252 logger.LogTrace(
"Compile job {0} lock count now: {1}", compileJob.
Id, value);
254 providerSubmitted =
true;
260 if (!providerSubmitted)
264 #pragma warning restore CA1506 267 #pragma warning disable CA1506 // TODO: Decomplexify 270 List<long> jobIdsToSkip;
274 jobIdsToSkip = jobLockCounts.Select(x => x.Key).ToList();
276 List<string> jobUidsToNotErase = null;
279 await databaseContextFactory.UseContext(async db =>
281 jobUidsToNotErase = (await db
285 x => x.Job.Instance.Id == instance.Id
286 && jobIdsToSkip.Contains(x.Id))
287 .Select(x => x.DirectoryName.Value)
288 .ToListAsync(cancellationToken)
289 .ConfigureAwait(
false))
290 .Select(x => x.ToString())
292 }).ConfigureAwait(
false);
296 logger.LogTrace(
"We will not clean the following directories: {0}", String.Join(
", ", jobUidsToNotErase));
299 var gameDirectory = ioManager.ResolvePath();
300 await ioManager.CreateDirectory(gameDirectory, cancellationToken).ConfigureAwait(
false);
301 var directories = await ioManager.GetDirectories(gameDirectory, cancellationToken).ConfigureAwait(
false);
303 var tasks = directories.Select(async x =>
305 var nameOnly = ioManager.GetFileName(x);
306 if (jobUidsToNotErase.Contains(nameOnly))
308 logger.LogDebug(
"Cleaning unused game folder: {0}...", nameOnly);
312 await ioManager.DeleteDirectory(x, cancellationToken).ConfigureAwait(
false);
314 catch (OperationCanceledException)
320 logger.LogWarning(
"Error deleting directory {0}! Exception: {1}", x, e);
324 await Task.WhenAll(tasks).ConfigureAwait(
false);
326 #pragma warning restore CA1506 333 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
A windows IDmbProvider that uses symlinks.
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.
Factory for IDmbProviders
Guid DirectoryName
The Game folder the results were compiled into
const string LiveGameDirectory
The directory where the baseProvider is symlinked to.
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
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)