tgstation-server 6.19.1
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,
171 username,
172 password,
173 cancellationToken),
174 cancellationToken);
175
177 url,
178 cloneOptions,
179 repositoryPath,
180 cancellationToken);
181 }
182 catch
183 {
184 try
185 {
186 logger.LogTrace("Deleting partially cloned repository...");
187
188 // DCT: Cancellation token is for job, operation must run regardless
189 await ioManager.DeleteDirectory(repositoryPath, CancellationToken.None);
190 }
191 catch (Exception innerException)
192 {
193 logger.LogError(innerException, "Error deleting partially cloned repository!");
194 }
195
196 throw;
197 }
198 else
199 {
200 logger.LogDebug("Repository exists, clone aborted!");
201 return null;
202 }
203 }
204 }
205 finally
206 {
207 logger.LogTrace("Semaphore released after clone");
208 CloneInProgress = false;
209 }
210
211 logger.LogInformation("Clone complete!");
212 return await LoadRepository(cancellationToken);
213 }
214
216 public async ValueTask<IRepository?> LoadRepository(CancellationToken cancellationToken)
217 {
218 logger.LogTrace("Begin LoadRepository...");
219 lock (semaphore)
220 if (CloneInProgress)
221 throw new JobException(ErrorCode.RepoCloning);
222
223 try
224 {
225 await semaphore.WaitAsync(cancellationToken);
226 try
227 {
228 logger.LogTrace("Semaphore acquired for load");
229 var libGit2Repo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken);
230
231 return new Repository(
232 libGit2Repo,
233 commands,
234 ioManager,
242 () =>
243 {
244 logger.LogTrace("Releasing semaphore due to Repository disposal...");
245 semaphore.Release();
246 });
247 }
248 catch
249 {
250 logger.LogTrace("Releasing semaphore as load failed");
251 semaphore.Release();
252 throw;
253 }
254 }
255 catch (RepositoryNotFoundException e)
256 {
257 logger.LogTrace(e, "Repository not found!");
258 return null;
259 }
260 }
261
263 public async ValueTask DeleteRepository(CancellationToken cancellationToken)
264 {
265 logger.LogInformation("Deleting repository...");
266 try
267 {
268 using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
269 {
270 logger.LogTrace("Semaphore acquired, deleting Repository directory...");
271 await ioManager.DeleteDirectory(ioManager.ResolvePath(), cancellationToken);
272 }
273 }
274 finally
275 {
276 logger.LogTrace("Semaphore released after delete attempt");
277 }
278 }
279 }
280}
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.
ValueTask< CredentialsHandler > GenerateCredentialsHandler(IGitRemoteFeatures remoteFeatures, string? username, string? password, CancellationToken cancellationToken)
Generate a CredentialsHandler from a given username and password .
IGitRemoteFeatures CreateGitRemoteFeatures(IRepository repository)
Create the IGitRemoteFeatures for a given repository .
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