tgstation-server  4.4.0
The /tg/station 13 server suite
RepositoryManager.cs
Go to the documentation of this file.
1 using LibGit2Sharp;
2 using Microsoft.Extensions.Logging;
3 using System;
4 using System.Threading;
5 using System.Threading.Tasks;
9 using Tgstation.Server.Host.IO;
11 
12 namespace Tgstation.Server.Host.Components.Repository
13 {
16  {
18  public bool InUse => semaphore.CurrentCount == 0;
19 
21  public bool CloneInProgress { get; private set; }
22 
27 
32 
37 
42 
46  readonly ILogger<Repository> repositoryLogger;
47 
51  readonly ILogger<RepositoryManager> logger;
52 
56  readonly SemaphoreSlim semaphore;
57 
68  ILibGit2RepositoryFactory repositoryFactory,
69  ILibGit2Commands commands,
70  IIOManager ioManager,
71  IEventConsumer eventConsumer,
72  ILogger<Repository> repositoryLogger,
73  ILogger<RepositoryManager> logger)
74  {
75  this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
76  this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
77  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
78  this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
79  this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
80  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
81  semaphore = new SemaphoreSlim(1);
82  }
83 
85  public void Dispose()
86  {
87  logger.LogTrace("Disposing...");
88  semaphore.Dispose();
89  }
90 
92  public async Task<IRepository> CloneRepository(
93  Uri url,
94  string initialBranch,
95  string username,
96  string password,
97  Action<int> progressReporter,
98  bool recurseSubmodules,
99  CancellationToken cancellationToken)
100  {
101  if (url == null)
102  throw new ArgumentNullException(nameof(url));
103  if (progressReporter == null)
104  throw new ArgumentNullException(nameof(progressReporter));
105 
106  logger.LogInformation("Begin clone {0} (Branch: {1})", url, initialBranch);
107  lock (semaphore)
108  {
109  if (CloneInProgress)
110  throw new JobException(ErrorCode.RepoCloning);
111  CloneInProgress = true;
112  }
113 
114  try
115  {
116  using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
117  {
118  logger.LogTrace("Semaphore acquired");
119  var repositoryPath = ioManager.ResolvePath();
120  if (!await ioManager.DirectoryExists(repositoryPath, cancellationToken).ConfigureAwait(false))
121  try
122  {
123  var cloneOptions = new CloneOptions
124  {
125  OnProgress = (a) => !cancellationToken.IsCancellationRequested,
126  OnTransferProgress = (a) =>
127  {
128  var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
129  progressReporter((int)percentage);
130  return !cancellationToken.IsCancellationRequested;
131  },
132  RecurseSubmodules = recurseSubmodules,
133  OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
134  RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
135  BranchName = initialBranch,
136  CredentialsProvider = repositoryFactory.GenerateCredentialsHandler(username, password)
137  };
138 
139  await repositoryFactory.Clone(
140  url,
141  cloneOptions,
142  repositoryPath,
143  cancellationToken)
144  .ConfigureAwait(false);
145  }
146  catch
147  {
148  try
149  {
150  logger.LogTrace("Deleting partially cloned repository...");
151  await ioManager.DeleteDirectory(repositoryPath, default).ConfigureAwait(false);
152  }
153  catch (Exception e)
154  {
155  logger.LogDebug("Error deleting partially cloned repository! Exception: {0}", e);
156  }
157 
158  throw;
159  }
160  else
161  {
162  logger.LogDebug("Repository exists, clone aborted!");
163  return null;
164  }
165  }
166 
167  logger.LogInformation("Clone complete!");
168  }
169  finally
170  {
171  CloneInProgress = false;
172  }
173 
174  return await LoadRepository(cancellationToken).ConfigureAwait(false);
175  }
176 
178  public async Task<IRepository> LoadRepository(CancellationToken cancellationToken)
179  {
180  logger.LogTrace("Begin LoadRepository...");
181  lock (semaphore)
182  if (CloneInProgress)
183  throw new JobException(ErrorCode.RepoCloning);
184  await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
185  try
186  {
187  try
188  {
189  var libGitRepo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken).ConfigureAwait(false);
190  return new Repository(
191  libGitRepo,
192  commands,
193  ioManager,
194  eventConsumer,
195  repositoryFactory,
196  repositoryLogger, () =>
197  {
198  logger.LogTrace("Releasing semaphore due to Repository disposal...");
199  semaphore.Release();
200  });
201  }
202  catch
203  {
204  semaphore.Release();
205  throw;
206  }
207  }
208  catch (RepositoryNotFoundException e)
209  {
210  logger.LogDebug("Repository not found!");
211  logger.LogTrace("Exception: {0}", e);
212  return null;
213  }
214  }
215 
217  public async Task DeleteRepository(CancellationToken cancellationToken)
218  {
219  logger.LogInformation("Deleting repository...");
220  using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
221  {
222  logger.LogTrace("Semaphore acquired, deleting Repository directory...");
223  await ioManager.DeleteDirectory(ioManager.ResolvePath(), cancellationToken).ConfigureAwait(false);
224  }
225  }
226  }
227 }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
readonly ILogger< RepositoryManager > logger
The ILogger for the RepositoryManager
For low level interactions with a LibGit2Sharp.IRepository.
RepositoryManager(ILibGit2RepositoryFactory repositoryFactory, ILibGit2Commands commands, IIOManager ioManager, IEventConsumer eventConsumer, ILogger< Repository > repositoryLogger, ILogger< RepositoryManager > logger)
Construct a RepositoryManager
readonly ILogger< Repository > repositoryLogger
The ILogger created Repositorys
readonly SemaphoreSlim semaphore
Used for controlling single access to the IRepository
async Task< IRepository > CloneRepository(Uri url, string initialBranch, string username, string password, Action< int > progressReporter, bool recurseSubmodules, CancellationToken cancellationToken)
Clone the repository at url
Operation exceptions thrown from the context of a Models.Job
Definition: JobException.cs:9
Represents a git repository
Definition: Repository.cs:9
readonly IIOManager ioManager
The IIOManager for the RepositoryManager
Consumes EventTypes and takes the appropriate actions
readonly ILibGit2RepositoryFactory repositoryFactory
The ILibGit2RepositoryFactory for the RepositoryManager
Factory for creating and loading IRepositorys
readonly ILibGit2Commands commands
The ILibGit2Commands for the RepositoryManager.
readonly IEventConsumer eventConsumer
The IEventConsumer for the RepositoryManager
Interface for using filesystems
Definition: IIOManager.cs:11
async Task< IRepository > LoadRepository(CancellationToken cancellationToken)
Attempt to load the IRepository from the default location
async Task DeleteRepository(CancellationToken cancellationToken)
Delete the current repository
static async Task< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken)
Asyncronously locks a semaphore