tgstation-server
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;
8 using Tgstation.Server.Host.IO;
9 
10 namespace Tgstation.Server.Host.Components.Repository
11 {
14  {
16  public bool InUse => semaphore.CurrentCount == 0;
17 
19  public bool CloneInProgress { get; private set; }
20 
25 
30 
35 
39  readonly ILogger<Repository> repositoryLogger;
40 
44  readonly ILogger<RepositoryManager> logger;
45 
50 
54  readonly SemaphoreSlim semaphore;
55 
65  public RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, ILogger<Repository> repositoryLogger, ILogger<RepositoryManager> logger)
66  {
67  this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings));
68  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
69  this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
70  this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
71  this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
72  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
73  semaphore = new SemaphoreSlim(1);
74  }
75 
77  public void Dispose()
78  {
79  logger.LogTrace("Disposing...");
80  semaphore.Dispose();
81  }
82 
84  public async Task<IRepository> CloneRepository(Uri url, string initialBranch, string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
85  {
86  if (url == null)
87  throw new ArgumentNullException(nameof(url));
88  if (progressReporter == null)
89  throw new ArgumentNullException(nameof(progressReporter));
90 
91  logger.LogInformation("Begin clone {0} (Branch: {1})", url, initialBranch);
92  lock (this)
93  {
94  if (CloneInProgress)
95  throw new InvalidOperationException("The repository is already being cloned!");
96  CloneInProgress = true;
97  }
98  try
99  {
100  using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
101  {
102  logger.LogTrace("Semaphore acquired");
103  if (!await ioManager.DirectoryExists(".", cancellationToken).ConfigureAwait(false))
104  try
105  {
106  await Task.Factory.StartNew(() =>
107  {
108  string path = null;
109  try
110  {
111  path = LibGit2Sharp.Repository.Clone(url.ToString(), ioManager.ResolvePath("."), new CloneOptions
112  {
113  OnProgress = (a) => !cancellationToken.IsCancellationRequested,
114  OnTransferProgress = (a) =>
115  {
116  var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
117  progressReporter((int)percentage);
118  return !cancellationToken.IsCancellationRequested;
119  },
120  RecurseSubmodules = true,
121  OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
122  RepositoryOperationStarting = (a) => !cancellationToken.IsCancellationRequested,
123  BranchName = initialBranch,
124  CredentialsProvider = credentialsProvider.GenerateHandler(username, password)
125  });
126  }
127  catch (UserCancelledException) { }
128  cancellationToken.ThrowIfCancellationRequested();
129  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
130  }
131  catch
132  {
133  try
134  {
135  logger.LogTrace("Deleting partially cloned repository...");
136  await ioManager.DeleteDirectory(".", default).ConfigureAwait(false);
137  }
138  catch (Exception e)
139  {
140  logger.LogDebug("Error deleting partially cloned repository! Exception: {0}", e);
141  }
142  throw;
143  }
144  else
145  {
146  logger.LogDebug("Repository exists, clone aborted!");
147  return null;
148  }
149  }
150  logger.LogInformation("Clone complete!");
151  }
152  finally
153  {
154  CloneInProgress = false;
155  }
156  return await LoadRepository(cancellationToken).ConfigureAwait(false);
157  }
158 
160  public async Task<IRepository> LoadRepository(CancellationToken cancellationToken)
161  {
162  logger.LogTrace("Begin LoadRepository...");
163  lock (this)
164  if (CloneInProgress)
165  throw new InvalidOperationException("The repository is being cloned!");
166  await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
167  LibGit2Sharp.Repository repo = null;
168  await Task.Factory.StartNew(() =>
169  {
170  try
171  {
172  logger.LogTrace("Creating LibGit2Sharp.Repository...");
173  repo = new LibGit2Sharp.Repository(ioManager.ResolvePath("."));
174  }
175  catch (RepositoryNotFoundException e)
176  {
177  logger.LogDebug("Repository not found!");
178  logger.LogTrace("Exception: {0}", e);
179  }
180  catch
181  {
182  semaphore.Release();
183  throw;
184  }
185  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
186  if (repo == null)
187  {
188  semaphore.Release();
189  return null;
190  }
191  return new Repository(repo, ioManager, eventConsumer, credentialsProvider, repositoryLogger, () =>
192  {
193  logger.LogTrace("Releasing semaphore due to Repository disposal...");
194  semaphore.Release();
195  });
196  }
197 
199  public async Task DeleteRepository(CancellationToken cancellationToken)
200  {
201  logger.LogInformation("Deleting repository...");
202  using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
203  {
204  logger.LogTrace("Semaphore acquired, deleting Repository directory...");
205  await ioManager.DeleteDirectory(".", cancellationToken).ConfigureAwait(false);
206  }
207  }
208  }
209 }
Represents configurable settings for a Repository
Consumes EventTypes and takes the appropriate actions
RepositoryManager(RepositorySettings repositorySettings, IIOManager ioManager, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, ILogger< Repository > repositoryLogger, ILogger< RepositoryManager > logger)
Construct a RepositoryManager
CredentialsHandler GenerateHandler(string username, string password)
Generate a CredentialsHandler from a given username and password
readonly ILogger< RepositoryManager > logger
The ILogger for the RepositoryManager
readonly ILogger< Repository > repositoryLogger
The ILogger created Repositorys
readonly SemaphoreSlim semaphore
Used for controlling single access to the IRepository
readonly ICredentialsProvider credentialsProvider
The ICredentialsProvider for the RepositoryManager
readonly IIOManager ioManager
The IIOManager for the RepositoryManager
Factory for creating and loading IRepositorys
readonly IEventConsumer eventConsumer
The IEventConsumer for the RepositoryManager
async Task< IRepository > CloneRepository(Uri url, string initialBranch, string username, string password, Action< int > progressReporter, CancellationToken cancellationToken)
Clone the repository at url
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
readonly RepositorySettings repositorySettings
The RepositorySettings for the RepositoryManager