tgstation-server 6.19.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
RepositoryManager.cs
Go to the documentation of this file.
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5using LibGit2Sharp;
6
7using Microsoft.Extensions.Logging;
8using Microsoft.Extensions.Options;
9
17
19{
22 {
24 public bool InUse => semaphore.CurrentCount == 0;
25
27 public bool CloneInProgress { get; private set; }
28
33
38
43
48
53
58
62 readonly IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions;
63
67 readonly ILogger<Repository> repositoryLogger;
68
72 readonly ILogger<RepositoryManager> logger;
73
77 readonly SemaphoreSlim semaphore;
78
98 IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions,
99 ILogger<Repository> repositoryLogger,
100 ILogger<RepositoryManager> logger)
101 {
102 this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
103 this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
104 this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
105 this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
106 this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
107 this.gitRemoteFeaturesFactory = gitRemoteFeaturesFactory ?? throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory));
108 this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
109 this.generalConfigurationOptions = generalConfigurationOptions ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
110 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
111 semaphore = new SemaphoreSlim(1);
112 }
113
115 public void Dispose()
116 {
117 logger.LogTrace("Disposing...");
118 semaphore.Dispose();
119 }
120
122 public async ValueTask<IRepository?> CloneRepository(
123 Uri url,
124 string? initialBranch,
125 string? username,
126 string? password,
127 JobProgressReporter progressReporter,
128 bool recurseSubmodules,
129 CancellationToken cancellationToken)
130 {
131 ArgumentNullException.ThrowIfNull(url);
132 lock (semaphore)
133 {
134 if (CloneInProgress)
135 throw new JobException(ErrorCode.RepoCloning);
136 CloneInProgress = true;
137 }
138
139 var repositoryPath = ioManager.ResolvePath();
140 logger.LogInformation("Begin clone {url} to {path} (Branch: {initialBranch})", url, repositoryPath, initialBranch);
141
142 try
143 {
144 using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
145 {
146 logger.LogTrace("Semaphore acquired for clone");
147 if (!await ioManager.DirectoryExists(repositoryPath, cancellationToken))
148 try
149 {
150 using var cloneProgressReporter = progressReporter.CreateSection(null, 0.75f);
151 using var checkoutProgressReporter = progressReporter.CreateSection(null, 0.25f);
152 var cloneOptions = new CloneOptions
153 {
154 RecurseSubmodules = recurseSubmodules,
155 OnCheckoutProgress = (path, completed, remaining) =>
156 {
157 if (checkoutProgressReporter == null)
158 return;
159
160 var percentage = (double)completed / remaining;
161 checkoutProgressReporter.ReportProgress(percentage);
162 },
163 BranchName = initialBranch,
164 };
165
166 cloneOptions.FetchOptions.Hydrate(
167 logger,
168 cloneProgressReporter,
170 cancellationToken);
171
173 url,
174 cloneOptions,
175 repositoryPath,
176 cancellationToken);
177 }
178 catch
179 {
180 try
181 {
182 logger.LogTrace("Deleting partially cloned repository...");
183
184 // DCT: Cancellation token is for job, operation must run regardless
185 await ioManager.DeleteDirectory(repositoryPath, CancellationToken.None);
186 }
187 catch (Exception innerException)
188 {
189 logger.LogError(innerException, "Error deleting partially cloned repository!");
190 }
191
192 throw;
193 }
194 else
195 {
196 logger.LogDebug("Repository exists, clone aborted!");
197 return null;
198 }
199 }
200 }
201 finally
202 {
203 logger.LogTrace("Semaphore released after clone");
204 CloneInProgress = false;
205 }
206
207 logger.LogInformation("Clone complete!");
208 return await LoadRepository(cancellationToken);
209 }
210
212 public async ValueTask<IRepository?> LoadRepository(CancellationToken cancellationToken)
213 {
214 logger.LogTrace("Begin LoadRepository...");
215 lock (semaphore)
216 if (CloneInProgress)
217 throw new JobException(ErrorCode.RepoCloning);
218
219 try
220 {
221 await semaphore.WaitAsync(cancellationToken);
222 try
223 {
224 logger.LogTrace("Semaphore acquired for load");
225 var libGit2Repo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken);
226
227 return new Repository(
228 libGit2Repo,
229 commands,
230 ioManager,
238 () =>
239 {
240 logger.LogTrace("Releasing semaphore due to Repository disposal...");
241 semaphore.Release();
242 });
243 }
244 catch
245 {
246 logger.LogTrace("Releasing semaphore as load failed");
247 semaphore.Release();
248 throw;
249 }
250 }
251 catch (RepositoryNotFoundException e)
252 {
253 logger.LogTrace(e, "Repository not found!");
254 return null;
255 }
256 }
257
259 public async ValueTask DeleteRepository(CancellationToken cancellationToken)
260 {
261 logger.LogInformation("Deleting repository...");
262 try
263 {
264 using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
265 {
266 logger.LogTrace("Semaphore acquired, deleting Repository directory...");
267 await ioManager.DeleteDirectory(ioManager.ResolvePath(), cancellationToken);
268 }
269 }
270 finally
271 {
272 logger.LogTrace("Semaphore released after delete attempt");
273 }
274 }
275 }
276}
readonly ILogger< Repository > repositoryLogger
The ILogger created Repositorys.
RepositoryManager(ILibGit2RepositoryFactory repositoryFactory, ILibGit2Commands commands, IIOManager ioManager, IEventConsumer eventConsumer, IPostWriteHandler postWriteHandler, IGitRemoteFeaturesFactory gitRemoteFeaturesFactory, IOptionsMonitor< GeneralConfiguration > generalConfigurationOptions, ILogger< Repository > repositoryLogger, ILogger< RepositoryManager > logger)
Initializes a new instance of the RepositoryManager class.
async ValueTask DeleteRepository(CancellationToken cancellationToken)
Delete the current repository.A ValueTask representing the running operation.
readonly ILibGit2RepositoryFactory repositoryFactory
The ILibGit2RepositoryFactory for the RepositoryManager.
readonly SemaphoreSlim semaphore
Used for controlling single access to the IRepository.
async ValueTask< IRepository?> LoadRepository(CancellationToken cancellationToken)
Attempt to load the IRepository from the default location.A ValueTask<TResult> resulting in the loade...
readonly ILibGit2Commands commands
The ILibGit2Commands for the RepositoryManager.
bool InUse
If something is holding a lock on the repository.
readonly IPostWriteHandler postWriteHandler
The IPostWriteHandler for the RepositoryManager.
readonly IOptionsMonitor< GeneralConfiguration > generalConfigurationOptions
The IOptionsMonitor<TOptions> of GeneralConfiguration for the RepositoryManager.
readonly IIOManager ioManager
The IIOManager for the RepositoryManager.
async ValueTask< IRepository?> CloneRepository(Uri url, string? initialBranch, string? username, string? password, JobProgressReporter progressReporter, bool recurseSubmodules, CancellationToken cancellationToken)
Clone the repository at url .A ValueTask<TResult> resulting i the newly cloned IRepository,...
readonly ILogger< RepositoryManager > logger
The ILogger for the RepositoryManager.
readonly IEventConsumer eventConsumer
The IEventConsumer for the RepositoryManager.
bool CloneInProgress
If a CloneRepository(Uri, string, string, string, JobProgressReporter, bool, CancellationToken) opera...
readonly IGitRemoteFeaturesFactory gitRemoteFeaturesFactory
The IGitRemoteFeaturesFactory for the RepositoryManager.
Operation exceptions thrown from the context of a Models.Job.
JobProgressReporter CreateSection(string? newStageName, double percentage)
Create a subsection of the JobProgressReporter with its optional own stage name.
void ReportProgress(double? progress)
Report progress.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken, ILogger? logger=null)
Asyncronously locks a semaphore .
Consumes EventTypes and takes the appropriate actions.
CredentialsHandler GenerateCredentialsHandler(string? username, string? password)
Generate a CredentialsHandler from a given username and password .
For low level interactions with a LibGit2Sharp.IRepository.
Task Clone(Uri url, CloneOptions cloneOptions, string path, CancellationToken cancellationToken)
Clone a remote LibGit2Sharp.IRepository.
ValueTask< LibGit2Sharp.IRepository > CreateFromPath(string path, CancellationToken cancellationToken)
Load a LibGit2Sharp.IRepository from a given path .
Interface for using filesystems.
Definition IIOManager.cs:14
string ResolvePath()
Retrieve the full path of the current working directory.
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
Task< bool > DirectoryExists(string path, CancellationToken cancellationToken)
Check that the directory at path exists.
Handles changing file modes/permissions after writing.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12