From 8d1bbf7559e7f91589de4f66d16c4c2d05139fb1 Mon Sep 17 00:00:00 2001 From: tgstation-server Date: Sun, 24 May 2020 22:31:13 +0000 Subject: [PATCH] Deploy code docs to GitHub Pages for Travis build 3348 Commit: 22ecd78f325fc45fa195894a29b3eea633907d6f --- _administration_8cs_source.html | 2 +- _api_client_8cs_source.html | 15 +- _api_client_factory_8cs_source.html | 2 +- _api_conflict_exception_8cs_source.html | 4 +- _api_exception_8cs_source.html | 4 +- _api_headers_8cs_source.html | 4 +- _application_8cs_source.html | 2 +- _byond_8cs_source.html | 2 +- _client_exception_8cs_source.html | 2 +- _configuration_client_8cs_source.html | 2 +- _configuration_file_8cs_source.html | 2 +- _conflict_exception_8cs_source.html | 4 +- ..._connection_string_builder_8cs_source.html | 2 +- _dream_daemon_8cs_source.html | 2 +- _error_code_extensions_8cs_source.html | 6 +- _error_message_8cs_source.html | 5 +- _i_api_client_factory_8cs_source.html | 2 +- _i_server_client_factory_8cs_source.html | 4 +- _instance_manager_8cs_source.html | 55 ++--- _instance_user_client_8cs_source.html | 2 +- ...ernal_2_server_information_8cs_source.html | 2 +- ..._connection_string_builder_8cs_source.html | 2 +- ...od_not_supported_exception_8cs_source.html | 4 +- _r_e_a_d_m_e_8md_source.html | 2 +- _rate_limit_exception_8cs_source.html | 4 +- _repository_controller_8cs_source.html | 2 +- _server_client_8cs_source.html | 2 +- _server_client_factory_8cs_source.html | 18 +- _server_error_exception_8cs_source.html | 4 +- _server_information_8cs_source.html | 2 +- _setup_wizard_8cs_source.html | 14 +- _test_merge_base_8cs_source.html | 2 +- _test_merge_parameters_8cs_source.html | 2 +- ..._8_api_2_models_2_chat_bot_8cs_source.html | 2 +- ...pi_2_models_2_chat_channel_8cs_source.html | 2 +- ...api_2_models_2_compile_job_8cs_source.html | 2 +- ...api_2_models_2_dream_maker_8cs_source.html | 2 +- ..._8_api_2_models_2_instance_8cs_source.html | 2 +- ...dels_2_internal_2_chat_bot_8cs_source.html | 4 +- ...s_2_internal_2_compile_job_8cs_source.html | 2 +- ..._2_models_2_internal_2_job_8cs_source.html | 2 +- ...rnal_2_repository_settings_8cs_source.html | 2 +- ...nal_2_revision_information_8cs_source.html | 2 +- ...2_models_2_internal_2_user_8cs_source.html | 2 +- ...erver_8_api_2_models_2_job_8cs_source.html | 2 +- ..._api_2_models_2_repository_8cs_source.html | 2 +- ...els_2_revision_information_8cs_source.html | 2 +- ..._api_2_models_2_test_merge_8cs_source.html | 2 +- ...rver_8_api_2_models_2_user_8cs_source.html | 2 +- ..._2_repository_2_repository_8cs_source.html | 2 +- _token_8cs_source.html | 2 +- _unauthorized_exception_8cs_source.html | 4 +- _user_update_8cs_source.html | 2 +- _version_mismatch_exception_8cs_source.html | 4 +- api.html | 2 +- ...erver_1_1_api_1_1_api_headers-members.html | 2 +- ...on_1_1_server_1_1_api_1_1_api_headers.html | 24 +-- ...f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.map | 2 +- ...f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.md5 | 2 +- ...erver_1_1_api_1_1_models_1_1_chat_bot.html | 2 +- ..._1_1_models_1_1_error_code_extensions.html | 6 +- ..._1_1_models_1_1_error_message-members.html | 7 +- ..._1_1_api_1_1_models_1_1_error_message.html | 26 ++- ..._1_1_models_1_1_internal_1_1_chat_bot.html | 6 +- ...al_1_1_chat_connection_string_builder.html | 2 +- ...api_1_1_models_1_1_repository-members.html | 2 +- ...ver_1_1_api_1_1_models_1_1_repository.html | 10 +- ...1_server_1_1_api_1_1_models_1_1_token.html | 2 +- ...r_1_1_api_1_1_models_1_1_user-members.html | 4 +- ..._1_server_1_1_api_1_1_models_1_1_user.html | 42 ++-- ...pi_1_1_models_1_1_user_update-members.html | 4 +- ...er_1_1_api_1_1_models_1_1_user_update.html | 14 +- ...station_1_1_server_1_1_api_1_1_routes.html | 2 +- ...ver_1_1_client_1_1_api_client-members.html | 23 +- ..._1_1_server_1_1_client_1_1_api_client.html | 157 ++++++++++++-- ...1_1_client_1_1_api_client__coll__graph.md5 | 2 +- ...1_1_client_1_1_api_client__coll__graph.png | Bin 50296 -> 55315 bytes ...ff438a63f0eaf323c7cf6adc8532cba_cgraph.map | 3 + ...ff438a63f0eaf323c7cf6adc8532cba_cgraph.md5 | 1 + ...ff438a63f0eaf323c7cf6adc8532cba_cgraph.png | Bin 0 -> 6480 bytes ...client_1_1_api_client_factory-members.html | 2 +- ...ver_1_1_client_1_1_api_client_factory.html | 18 +- ...nt_1_1_api_conflict_exception-members.html | 4 +- ...1_1_client_1_1_api_conflict_exception.html | 18 +- ..._1_1_client_1_1_api_exception-members.html | 2 +- ...1_server_1_1_client_1_1_api_exception.html | 15 +- ...1_components_1_1_configuration_client.html | 2 +- ...client_1_1_conflict_exception-members.html | 4 +- ...ver_1_1_client_1_1_conflict_exception.html | 18 +- ...ficient_permissions_exception-members.html | 2 +- ..._1_insufficient_permissions_exception.html | 6 +- ...ethod_not_supported_exception-members.html | 4 +- ...nt_1_1_method_not_supported_exception.html | 18 +- ...ient_1_1_rate_limit_exception-members.html | 4 +- ...r_1_1_client_1_1_rate_limit_exception.html | 18 +- ...1_server_1_1_client_1_1_server_client.html | 2 - ...ent_1_1_server_client_factory-members.html | 4 +- ..._1_1_client_1_1_server_client_factory.html | 84 ++++---- ...nt_1_1_server_error_exception-members.html | 4 +- ...1_1_client_1_1_server_error_exception.html | 18 +- ...nt_1_1_unauthorized_exception-members.html | 4 +- ...1_1_client_1_1_unauthorized_exception.html | 18 +- ..._1_version_mismatch_exception-members.html | 4 +- ...client_1_1_version_mismatch_exception.html | 18 +- ...mponents_1_1_instance_manager-members.html | 41 ++-- ...t_1_1_components_1_1_instance_manager.html | 199 +++++++++++------- ...ents_1_1_instance_manager__coll__graph.map | 30 +-- ...ents_1_1_instance_manager__coll__graph.md5 | 2 +- ...ents_1_1_instance_manager__coll__graph.png | Bin 172532 -> 206426 bytes ...ce33ffacb174f0ac669e29058854318_cgraph.map | 3 + ...ce33ffacb174f0ac669e29058854318_cgraph.md5 | 1 + ...ce33ffacb174f0ac669e29058854318_cgraph.png | Bin 0 -> 6599 bytes ...ts_1_1_interop_1_1_d_m_api_parameters.html | 2 +- ...ponents_1_1_repository_1_1_repository.html | 2 +- ...ation_1_1_control_panel_configuration.html | 4 +- ...figuration_1_1_database_configuration.html | 2 +- ...ration_1_1_file_logging_configuration.html | 2 +- ...nfiguration_1_1_general_configuration.html | 2 +- ...controllers_1_1_repository_controller.html | 4 +- ...ver_1_1_host_1_1_core_1_1_application.html | 2 +- ...r_1_1_host_1_1_setup_1_1_setup_wizard.html | 37 ++-- functions.html | 6 +- functions_c.html | 16 +- functions_d.html | 2 +- functions_e.html | 2 +- functions_func.html | 6 +- functions_func_c.html | 18 +- functions_func_e.html | 2 +- functions_func_i.html | 2 +- functions_func_m.html | 2 +- functions_func_r.html | 13 +- functions_func_u.html | 2 +- functions_func_v.html | 2 +- functions_g.html | 3 + functions_i.html | 2 +- functions_m.html | 2 +- functions_n.html | 6 +- functions_prop_n.html | 2 +- functions_r.html | 17 +- functions_s.html | 3 + functions_t.html | 7 +- functions_u.html | 2 +- functions_vars.html | 2 +- functions_vars_d.html | 2 +- functions_vars_g.html | 3 +- functions_vars_s.html | 3 + functions_vars_t.html | 3 + index.html | 1 + ...ient_1_1_i_api_client_factory-members.html | 2 +- ...r_1_1_client_1_1_i_api_client_factory.html | 19 +- ...t_1_1_i_server_client_factory-members.html | 4 +- ..._1_client_1_1_i_server_client_factory.html | 57 ++--- ...op_1_1_bridge_1_1_i_bridge_dispatcher.html | 2 +- ...terop_1_1_bridge_1_1_i_bridge_handler.html | 2 +- ..._1_host_1_1_core_1_1_i_server_control.html | 4 +- ...728d66030addfd5551351b8c6db942_icgraph.map | 2 +- ...728d66030addfd5551351b8c6db942_icgraph.md5 | 2 +- ...station_1_1_server_1_1_api_1_1_models.html | 2 +- search/all_1.js | 12 +- search/all_10.js | 8 +- search/all_12.js | 21 +- search/all_13.js | 9 +- search/all_14.js | 5 +- search/all_15.js | 2 +- search/all_16.js | 2 +- search/all_3.js | 13 +- search/all_4.js | 2 +- search/all_5.js | 2 +- search/all_7.js | 2 +- search/all_9.js | 2 +- search/all_d.js | 2 +- search/all_e.js | 4 +- search/functions_0.js | 8 +- search/functions_10.js | 5 +- search/functions_11.js | 2 +- search/functions_13.js | 2 +- search/functions_14.js | 2 +- search/functions_2.js | 7 +- search/functions_4.js | 2 +- search/functions_8.js | 2 +- search/functions_c.js | 2 +- search/properties_c.js | 2 +- search/variables_0.js | 4 +- search/variables_12.js | 1 + search/variables_13.js | 3 +- search/variables_3.js | 2 +- search/variables_6.js | 2 +- 187 files changed, 920 insertions(+), 641 deletions(-) create mode 100644 class_tgstation_1_1_server_1_1_client_1_1_api_client_a5ff438a63f0eaf323c7cf6adc8532cba_cgraph.map create mode 100644 class_tgstation_1_1_server_1_1_client_1_1_api_client_a5ff438a63f0eaf323c7cf6adc8532cba_cgraph.md5 create mode 100644 class_tgstation_1_1_server_1_1_client_1_1_api_client_a5ff438a63f0eaf323c7cf6adc8532cba_cgraph.png create mode 100644 class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager_a4ce33ffacb174f0ac669e29058854318_cgraph.map create mode 100644 class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager_a4ce33ffacb174f0ac669e29058854318_cgraph.md5 create mode 100644 class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager_a4ce33ffacb174f0ac669e29058854318_cgraph.png diff --git a/_administration_8cs_source.html b/_administration_8cs_source.html index 312ddedbe8..5154f66ecb 100644 --- a/_administration_8cs_source.html +++ b/_administration_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
Administration.cs
-Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class Administration
9  {
13  public bool WindowsHost { get; set; }
14 
18  public Uri TrackedRepositoryUrl { get; set; }
19 
23  public Version LatestVersion { get; set; }
24 
28  public Version NewVersion { get; set; }
29  }
30 }
+Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class Administration
9  {
13  public bool WindowsHost { get; set; }
14 
18  public Uri? TrackedRepositoryUrl { get; set; }
19 
23  public Version? LatestVersion { get; set; }
24 
28  public Version? NewVersion { get; set; }
29  }
30 }
Represents administrative server information
diff --git a/_api_client_8cs_source.html b/_api_client_8cs_source.html index feef0be38d..36e077c343 100644 --- a/_api_client_8cs_source.html +++ b/_api_client_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ApiClient.cs
-Go to the documentation of this file.
1 using Newtonsoft.Json;
2 using Newtonsoft.Json.Converters;
3 using Newtonsoft.Json.Serialization;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Net;
8 using System.Net.Http;
9 using System.Text;
10 using System.Threading;
11 using System.Threading.Tasks;
12 using Tgstation.Server.Api;
14 
15 namespace Tgstation.Server.Client
16 {
18  sealed class ApiClient : IApiClient
19  {
21  public Uri Url { get; }
22 
24  public ApiHeaders Headers
25  {
26  get => headers;
27  set => headers = value ?? throw new InvalidOperationException("Cannot set null headers!");
28  }
29 
31  public TimeSpan Timeout
32  {
33  get => httpClient.Timeout;
34  set => httpClient.Timeout = value;
35  }
36 
41 
45  readonly List<IRequestLogger> requestLoggers;
46 
51 
52  static JsonSerializerSettings GetSerializerSettings() => new JsonSerializerSettings
53  {
54  ContractResolver = new CamelCasePropertyNamesContractResolver(),
55  Converters = new[] { new VersionConverter() }
56  };
57 
58  static void HandleBadResponse(HttpResponseMessage response, string json)
59  {
60  ErrorMessage errorMessage = null;
61  try
62  {
63  // check if json serializes to an error message
64  errorMessage = JsonConvert.DeserializeObject<ErrorMessage>(json, GetSerializerSettings());
65  }
66  catch (JsonException) { }
67 
68 #pragma warning disable IDE0010 // Add missing cases
69  switch (response.StatusCode)
70 #pragma warning restore IDE0010 // Add missing cases
71  {
72  case HttpStatusCode.UpgradeRequired:
73  throw new VersionMismatchException(errorMessage, response);
74  case HttpStatusCode.Unauthorized:
75  throw new UnauthorizedException(errorMessage, response);
76  case HttpStatusCode.InternalServerError:
77  throw new ServerErrorException(errorMessage, response);
78  case HttpStatusCode.NotImplemented:
79  // unprocessable entity
80  case (HttpStatusCode)422:
81  throw new MethodNotSupportedException(errorMessage, response);
82  case HttpStatusCode.NotFound:
83  case HttpStatusCode.Gone:
84  case HttpStatusCode.Conflict:
85  throw new ConflictException(errorMessage, response);
86  case HttpStatusCode.Forbidden:
87  throw new InsufficientPermissionsException(response);
88  case HttpStatusCode.ServiceUnavailable:
89  throw new ServiceUnavailableException(response);
90  case HttpStatusCode.RequestTimeout:
91  throw new RequestTimeoutException(response);
92  case (HttpStatusCode)429:
93  throw new RateLimitException(errorMessage, response);
94  default:
95  throw new ApiConflictException(errorMessage, response);
96  }
97  }
98 
105  public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders)
106  {
107  this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
108  Url = url ?? throw new ArgumentNullException(nameof(url));
109  headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
110 
111  requestLoggers = new List<IRequestLogger>();
112  }
113 
115  public void Dispose() => httpClient.Dispose();
116 
127  async Task<TResult> RunRequest<TResult>(string route, object body, HttpMethod method, long? instanceId, CancellationToken cancellationToken)
128  {
129  if (route == null)
130  throw new ArgumentNullException(nameof(route));
131  if (method == null)
132  throw new ArgumentNullException(nameof(method));
133  if (body == null && (method == HttpMethod.Post || method == HttpMethod.Put))
134  throw new InvalidOperationException("Body cannot be null for POST or PUT!");
135 
136  HttpResponseMessage response;
137  var fullUri = new Uri(Url, route);
138  var serializerSettings = GetSerializerSettings();
139  using (var request = new HttpRequestMessage(method, fullUri))
140  {
141  if (body != null)
142  request.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
143 
144  headers.SetRequestHeaders(request.Headers, instanceId);
145 
146  await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
147 
148  response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
149  }
150 
151  using (response)
152  {
153  await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
154 
155  var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
156 
157  if (!response.IsSuccessStatusCode)
158  HandleBadResponse(response, json);
159 
160  if (String.IsNullOrWhiteSpace(json))
161  json = JsonConvert.SerializeObject(new object());
162 
163  try
164  {
165  return JsonConvert.DeserializeObject<TResult>(json, serializerSettings);
166  }
167  catch (JsonException)
168  {
169  throw new UnrecognizedResponseException(response);
170  }
171  }
172  }
173 
175  public Task<TResult> Create<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, null, cancellationToken);
176 
178  public Task<TResult> Read<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, null, cancellationToken);
179 
181  public Task<TResult> Update<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Post, null, cancellationToken);
182 
184  public Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, null, cancellationToken);
185 
187  public Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Post, null, cancellationToken);
188 
190  public Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, null, cancellationToken);
191 
193  public Task Delete(string route, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, null, cancellationToken);
194 
196  public Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, instanceId, cancellationToken);
197 
199  public Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, instanceId, cancellationToken);
200 
202  public Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, instanceId, cancellationToken);
203 
205  public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, instanceId, cancellationToken);
206 
208  public Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Delete, instanceId, cancellationToken);
209 
211  public Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Delete, instanceId, cancellationToken);
212 
214  public Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, instanceId, cancellationToken);
215 
217  public Task<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), new HttpMethod("PATCH"), instanceId, cancellationToken);
218 
220  public void AddRequestLogger(IRequestLogger requestLogger) => requestLoggers.Add(requestLogger ?? throw new ArgumentNullException(nameof(requestLogger)));
221  }
222 }
Occurs when a response is received that did not deserialize to one of the expected Api...
+Go to the documentation of this file.
1 using Newtonsoft.Json;
2 using Newtonsoft.Json.Converters;
3 using Newtonsoft.Json.Serialization;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Net;
8 using System.Net.Http;
9 using System.Text;
10 using System.Threading;
11 using System.Threading.Tasks;
12 using Tgstation.Server.Api;
14 
15 namespace Tgstation.Server.Client
16 {
18  sealed class ApiClient : IApiClient
19  {
21  public Uri Url { get; }
22 
24  public ApiHeaders Headers
25  {
26  get => headers;
27  set => headers = value ?? throw new InvalidOperationException("Cannot set null headers!");
28  }
29 
31  public TimeSpan Timeout
32  {
33  get => httpClient.Timeout;
34  set => httpClient.Timeout = value;
35  }
36 
41 
45  readonly List<IRequestLogger> requestLoggers;
46 
51 
55  readonly SemaphoreSlim semaphoreSlim;
56 
61 
62  static JsonSerializerSettings GetSerializerSettings() => new JsonSerializerSettings
63  {
64  ContractResolver = new CamelCasePropertyNamesContractResolver(),
65  Converters = new[] { new VersionConverter() }
66  };
67 
68  static void HandleBadResponse(HttpResponseMessage response, string json)
69  {
70  ErrorMessage? errorMessage = null;
71  try
72  {
73  // check if json serializes to an error message
74  errorMessage = JsonConvert.DeserializeObject<ErrorMessage>(json, GetSerializerSettings());
75  }
76  catch (JsonException) { }
77 
78 #pragma warning disable IDE0010 // Add missing cases
79  switch (response.StatusCode)
80 #pragma warning restore IDE0010 // Add missing cases
81  {
82  case HttpStatusCode.UpgradeRequired:
83  throw new VersionMismatchException(errorMessage, response);
84  case HttpStatusCode.Unauthorized:
85  throw new UnauthorizedException(errorMessage, response);
86  case HttpStatusCode.InternalServerError:
87  throw new ServerErrorException(errorMessage, response);
88  case HttpStatusCode.NotImplemented:
89  // unprocessable entity
90  case (HttpStatusCode)422:
91  throw new MethodNotSupportedException(errorMessage, response);
92  case HttpStatusCode.NotFound:
93  case HttpStatusCode.Gone:
94  case HttpStatusCode.Conflict:
95  throw new ConflictException(errorMessage, response);
96  case HttpStatusCode.Forbidden:
97  throw new InsufficientPermissionsException(response);
98  case HttpStatusCode.ServiceUnavailable:
99  throw new ServiceUnavailableException(response);
100  case HttpStatusCode.RequestTimeout:
101  throw new RequestTimeoutException(response);
102  case (HttpStatusCode)429:
103  throw new RateLimitException(errorMessage, response);
104  default:
105  throw new ApiConflictException(errorMessage, response);
106  }
107  }
108 
116  public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders)
117  {
118  this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
119  Url = url ?? throw new ArgumentNullException(nameof(url));
120  headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
121  this.tokenRefreshHeaders = tokenRefreshHeaders;
122 
123  requestLoggers = new List<IRequestLogger>();
124  semaphoreSlim = new SemaphoreSlim(1);
125  }
126 
128  public void Dispose()
129  {
130  httpClient.Dispose();
131  semaphoreSlim.Dispose();
132  }
133 
145  async Task<TResult> RunRequest<TResult>(string route, object? body, HttpMethod method, long? instanceId, bool tokenRefresh, CancellationToken cancellationToken)
146  {
147  if (route == null)
148  throw new ArgumentNullException(nameof(route));
149  if (method == null)
150  throw new ArgumentNullException(nameof(method));
151  if (body == null && (method == HttpMethod.Post || method == HttpMethod.Put))
152  throw new InvalidOperationException("Body cannot be null for POST or PUT!");
153 
154  HttpResponseMessage response;
155  var fullUri = new Uri(Url, route);
156  var serializerSettings = GetSerializerSettings();
157  using (var request = new HttpRequestMessage(method, fullUri))
158  {
159  if (body != null)
160  request.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
161 
162  var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers;
163  headersToUse.SetRequestHeaders(request.Headers, instanceId);
164 
165  // This is meant to be a gate against token refresh operations
166  await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
167  if(!tokenRefresh)
168  semaphoreSlim.Release();
169 
170  try
171  {
172  await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
173 
174  response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
175  }
176  finally
177  {
178  if (tokenRefresh)
179  semaphoreSlim.Release();
180  }
181  }
182 
183  using (response)
184  {
185  await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
186 
187  var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
188 
189  if (!response.IsSuccessStatusCode)
190  {
191  if (!tokenRefresh
192  && response.StatusCode == HttpStatusCode.Unauthorized
193  && await RefreshToken(cancellationToken).ConfigureAwait(false))
194  return await RunRequest<TResult>(route, body, method, instanceId, false, cancellationToken).ConfigureAwait(false);
195  HandleBadResponse(response, json);
196  }
197 
198  if (String.IsNullOrWhiteSpace(json))
199  json = JsonConvert.SerializeObject(new object());
200 
201  try
202  {
203  return JsonConvert.DeserializeObject<TResult>(json, serializerSettings) !;
204  }
205  catch (JsonException)
206  {
207  throw new UnrecognizedResponseException(response);
208  }
209  }
210  }
211 
212  async Task<bool> RefreshToken(CancellationToken cancellationToken)
213  {
214  if (tokenRefreshHeaders == null)
215  return false;
216 
217  try
218  {
219  var token = await RunRequest<Token>(Routes.Root, null, HttpMethod.Post, null, true, cancellationToken);
220  headers = new ApiHeaders(headers.UserAgent!, token.Bearer!);
221  }
222  catch (ClientException)
223  {
224  return false;
225  }
226 
227  return true;
228  }
229 
231  public Task<TResult> Create<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, null, false, cancellationToken);
232 
234  public Task<TResult> Read<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, null, false, cancellationToken);
235 
237  public Task<TResult> Update<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Post, null, false, cancellationToken);
238 
240  public Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, null, false, cancellationToken);
241 
243  public Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Post, null, false, cancellationToken);
244 
246  public Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, null, false, cancellationToken);
247 
249  public Task Delete(string route, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, null, false, cancellationToken);
250 
252  public Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, instanceId, false, cancellationToken);
253 
255  public Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, instanceId, false, cancellationToken);
256 
258  public Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, instanceId, false, cancellationToken);
259 
261  public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, instanceId, false, cancellationToken);
262 
264  public Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Delete, instanceId, false, cancellationToken);
265 
267  public Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Delete, instanceId, false, cancellationToken);
268 
270  public Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, instanceId, false, cancellationToken);
271 
273  public Task<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Patch, instanceId, false, cancellationToken);
274 
276  public void AddRequestLogger(IRequestLogger requestLogger) => requestLoggers.Add(requestLogger ?? throw new ArgumentNullException(nameof(requestLogger)));
277  }
278 }
Occurs when a response is received that did not deserialize to one of the expected Api...
@@ -105,25 +105,32 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
Occurs when an error occurs in the server
void SetRequestHeaders(HttpRequestHeaders headers, long?instanceId=null)
Set HttpRequestHeaders using the ApiHeaders. This initially clears headers
Definition: ApiHeaders.cs:259
Occurs when the server returns a bad request response if the ApiException.ErrorCode is present...
-
ApiHeaders headers
Backing field for Headers
Definition: ApiClient.cs:50
+
ApiHeaders headers
Backing field for Headers
Definition: ApiClient.cs:60
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
+
Routes to a server actions
Definition: Routes.cs:9
Occurs when the client makes a request while the server is starting or stopping
+
readonly ApiHeaders tokenRefreshHeaders
Backing field for Headers
Definition: ApiClient.cs:50
For sending HTTP requests
Definition: IHttpClient.cs:11
+ +
async Task< bool > RefreshToken(CancellationToken cancellationToken)
Definition: ApiClient.cs:212
+
const string Root
The root controller
Definition: Routes.cs:14
readonly List< IRequestLogger > requestLoggers
The IRequestLoggers used by the ApiClient
Definition: ApiClient.cs:45
-
static void HandleBadResponse(HttpResponseMessage response, string json)
Definition: ApiClient.cs:58
+
readonly SemaphoreSlim semaphoreSlim
The SemaphoreSlim for Token refreshes.
Definition: ApiClient.cs:55
+
static void HandleBadResponse(HttpResponseMessage response, string json)
Definition: ApiClient.cs:68
Occurs when the client provides invalid credentials
-
ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders)
Construct an ApiClient
Definition: ApiClient.cs:105
Occurs when the client performs an action that would result in data conflict
Occurs when the client attempts to perform an action they do not have the rights for ...
For logging HTTP requests and responses
Occurs when the client provides invalid credentials
Web interface for the API
Definition: IApiClient.cs:11
+
ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders)
Construct an ApiClient
Definition: ApiClient.cs:116
Occurs when the client tries to use a currently unsupported API
readonly IHttpClient httpClient
The HttpClient for the ApiClient
Definition: ApiClient.cs:40
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
+
Exceptions thrown by IServerClients
-Go to the documentation of this file.
1 using System;
2 using Tgstation.Server.Api;
3 
4 namespace Tgstation.Server.Client
5 {
8  {
10  public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders) => new ApiClient(new HttpClient(), url, apiHeaders);
11  }
12 }
+Go to the documentation of this file.
1 using System;
2 using Tgstation.Server.Api;
3 
4 namespace Tgstation.Server.Client
5 {
8  {
10  public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders) => new ApiClient(new HttpClient(), url, apiHeaders, tokenRefreshHeaders);
11  }
12 }
diff --git a/_api_conflict_exception_8cs_source.html b/_api_conflict_exception_8cs_source.html index 4596615a63..e96df7d14d 100644 --- a/_api_conflict_exception_8cs_source.html +++ b/_api_conflict_exception_8cs_source.html @@ -94,15 +94,15 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ApiConflictException.cs
-Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class ApiConflictException : ApiException
11  {
17  public ApiConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  { }
19 
23  public ApiConflictException() { }
24 
29  public ApiConflictException(string message) : base(message) { }
30 
36  public ApiConflictException(string message, Exception innerException) : base(message, innerException) { }
37  }
38 }
+Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class ApiConflictException : ApiException
11  {
17  public ApiConflictException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  { }
19 
23  public ApiConflictException() { }
24 
29  public ApiConflictException(string message) : base(message) { }
30 
36  public ApiConflictException(string message, Exception innerException) : base(message, innerException) { }
37  }
38 }
+
ApiConflictException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
Construct an ApiConflictException using an errorMessage
Occurs when the server returns a bad request response if the ApiException.ErrorCode is present...
ApiConflictException(string message)
Construct an ApiConflictException with a message
ApiConflictException(string message, Exception innerException)
Construct an ApiConflictException with a message and innerException
API related Exceptions.
Definition: ApiException.cs:10
-
ApiConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)
Construct an ApiConflictException using an errorMessage
ApiConflictException()
Construct an ApiConflictException
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
diff --git a/_api_exception_8cs_source.html b/_api_exception_8cs_source.html index c0d10df13d..44f27f28a9 100644 --- a/_api_exception_8cs_source.html +++ b/_api_exception_8cs_source.html @@ -94,10 +94,10 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ApiException.cs
-Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public abstract class ApiException : ClientException
11  {
15  public Version ServerApiVersion { get; }
16 
20  public string AdditionalServerData { get; }
21 
25  public ErrorCode? ErrorCode { get; }
26 
32  protected ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(
33  responseMessage,
34  errorMessage?.Message ?? $"HTTP {responseMessage.StatusCode}. Unknown API error, ErrorMessage payload not present!")
35  {
36  ServerApiVersion = errorMessage?.ServerApiVersion;
37  AdditionalServerData = errorMessage?.AdditionalData;
38  ErrorCode = errorMessage?.ErrorCode;
39  }
40 
46  protected ApiException(HttpResponseMessage responseMessage, string message) : base(
47  responseMessage,
48  message)
49  {
50  }
51 
55  protected ApiException()
56  {
57  }
58 
63  protected ApiException(string message) : base(message)
64  {
65  }
66 
72  protected ApiException(string message, Exception innerException) : base(message, innerException)
73  {
74  }
75  }
76 }
ApiException()
Initialize a new instance of the ApiException .
Definition: ApiException.cs:55
+Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public abstract class ApiException : ClientException
11  {
15  public Version? ServerApiVersion { get; }
16 
20  public string? AdditionalServerData { get; }
21 
25  public ErrorCode? ErrorCode { get; }
26 
32  protected ApiException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(
33  responseMessage,
34  errorMessage?.Message ?? $"HTTP {responseMessage.StatusCode}. Unknown API error, ErrorMessage payload not present!")
35  {
36  ServerApiVersion = errorMessage?.ServerApiVersion;
37  AdditionalServerData = errorMessage?.AdditionalData;
38  ErrorCode = errorMessage?.ErrorCode;
39  }
40 
46  protected ApiException(HttpResponseMessage responseMessage, string message) : base(
47  responseMessage,
48  message)
49  {
50  }
51 
55  protected ApiException()
56  {
57  }
58 
63  protected ApiException(string message) : base(message)
64  {
65  }
66 
72  protected ApiException(string message, Exception innerException) : base(message, innerException)
73  {
74  }
75  }
76 }
ApiException()
Initialize a new instance of the ApiException .
Definition: ApiException.cs:55
ApiException(string message)
Initialize a new instance of the ApiException .
Definition: ApiException.cs:63
+
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the ApiException .
Definition: ApiException.cs:32
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
-
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the ApiException .
Definition: ApiException.cs:32
string AdditionalData
Additional data associated with the error message.
Definition: ErrorMessage.cs:26
diff --git a/_api_headers_8cs_source.html b/_api_headers_8cs_source.html index 9d01b7a2fc..3466a14e16 100644 --- a/_api_headers_8cs_source.html +++ b/_api_headers_8cs_source.html @@ -94,12 +94,12 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ApiHeaders.cs
-Go to the documentation of this file.
1 using Microsoft.AspNetCore.Http.Headers;
2 using Microsoft.Extensions.Primitives;
3 using Microsoft.Net.Http.Headers;
4 using System;
5 using System.Collections.Generic;
6 using System.Globalization;
7 using System.Linq;
8 using System.Net.Http.Headers;
9 using System.Reflection;
10 using System.Text;
11 
12 namespace Tgstation.Server.Api
13 {
17  public sealed class ApiHeaders
18  {
22  public const string ApplicationJson = "application/json";
23 
27  public const string ApiVersionHeader = "api";
28 
32  public const string InstanceIdHeader = "instance";
33 
37  public const string JwtAuthenticationScheme = "bearer";
38 
42  public const string BasicAuthenticationScheme = "basic";
43 
47  const string UsernameHeader = "username";
48 
52  const string PasswordAuthenticationScheme = "password";
53 
57  static readonly AssemblyName AssemblyName = Assembly.GetExecutingAssembly().GetName();
58 
62  public static readonly Version Version = AssemblyName.Version.Semver();
63 
67  public long? InstanceId { get; set; }
68 
72  public ProductHeaderValue UserAgent => ProductInfoHeaderValue.TryParse(RawUserAgent, out var userAgent) ? userAgent.Product : null;
73 
77  public string RawUserAgent { get; }
78 
82  public Version ApiVersion { get; }
83 
87  public string Token { get; }
88 
92  public string Username { get; }
93 
97  public string Password { get; }
98 
102  public bool IsTokenAuthentication => Token != null;
103 
109  public static bool CheckCompatibility(Version otherVersion) => Version.Major == (otherVersion?.Major ?? throw new ArgumentNullException(nameof(otherVersion)));
110 
116  public ApiHeaders(ProductHeaderValue userAgent, string token) : this(userAgent, token, null, null)
117  {
118  if (userAgent == null)
119  throw new ArgumentNullException(nameof(userAgent));
120  if (token == null)
121  throw new ArgumentNullException(nameof(token));
122  }
123 
130  public ApiHeaders(ProductHeaderValue userAgent, string username, string password) : this(userAgent, null, username, password)
131  {
132  if (userAgent == null)
133  throw new ArgumentNullException(nameof(userAgent));
134  if (username == null)
135  throw new ArgumentNullException(nameof(username));
136  if (password == null)
137  throw new ArgumentNullException(nameof(password));
138  }
139 
144  public ApiHeaders(RequestHeaders requestHeaders)
145  {
146  if (requestHeaders == null)
147  throw new ArgumentNullException(nameof(requestHeaders));
148 
149  var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(ApplicationJson);
150  if (!requestHeaders.Accept.Any(x => x.MediaType == jsonAccept.MediaType))
151  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", ApplicationJson));
152 
153  if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgentValues) || userAgentValues.Count == 0)
154  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} headers!", HeaderNames.UserAgent));
155 
156  RawUserAgent = userAgentValues.First();
157  if (String.IsNullOrWhiteSpace(RawUserAgent))
158  throw new InvalidOperationException("Malformed client User-Agent!");
159 
160  // make sure the api header matches ours
161  if (!requestHeaders.Headers.TryGetValue(ApiVersionHeader, out var apiUserAgentHeaderValues) || !ProductInfoHeaderValue.TryParse(apiUserAgentHeaderValues.FirstOrDefault(), out var apiUserAgent) || apiUserAgent.Product.Name != AssemblyName.Name)
162  throw new InvalidOperationException("Missing API version!");
163 
164  if (!Version.TryParse(apiUserAgent.Product.Version, out var apiVersion))
165  throw new InvalidOperationException("Malformed API version!");
166 
167  ApiVersion = apiVersion.Semver();
168 
169  if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization))
170  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} header!", HeaderNames.Authorization));
171  var auth = authorization.First();
172  var splits = new List<string>(auth.Split(' '));
173  var scheme = splits.First();
174  if (String.IsNullOrWhiteSpace(scheme))
175  throw new InvalidOperationException("Missing authentication scheme!");
176 
177  splits.RemoveAt(0);
178  var parameter = String.Concat(splits);
179  if (String.IsNullOrEmpty(parameter))
180  throw new InvalidOperationException("Missing authentication parameter!");
181 
182  if (requestHeaders.Headers.TryGetValue(InstanceIdHeader, out var instanceIdValues))
183  {
184  var instanceIdString = instanceIdValues.FirstOrDefault();
185  if (instanceIdString != default && Int64.TryParse(instanceIdString, out var instanceId))
186  InstanceId = instanceId;
187  }
188 
189 #pragma warning disable CA1308 // Normalize strings to uppercase
190  switch (scheme.ToLowerInvariant())
191 #pragma warning restore CA1308 // Normalize strings to uppercase
192  {
193  case JwtAuthenticationScheme:
194  Token = parameter;
195  break;
196  case PasswordAuthenticationScheme:
197  Password = parameter;
198  var fail = !requestHeaders.Headers.TryGetValue(UsernameHeader, out var values);
199  if (!fail)
200  {
201  Username = values.FirstOrDefault();
202  fail = String.IsNullOrWhiteSpace(Username);
203  }
204 
205  if (fail)
206  throw new InvalidOperationException("Missing Username header!");
207  break;
208  case BasicAuthenticationScheme:
209  string joinedString;
210  try
211  {
212  var base64Bytes = Convert.FromBase64String(parameter);
213  joinedString = Encoding.UTF8.GetString(base64Bytes);
214  }
215  catch
216  {
217  throw new InvalidOperationException("Invalid basic Authorization header!");
218  }
219 
220  var basicAuthSplits = joinedString.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
221  if (basicAuthSplits.Length < 2)
222  throw new InvalidOperationException("Invalid basic Authorization header!");
223 
224  Username = basicAuthSplits.First();
225  Password = String.Concat(basicAuthSplits.Skip(1));
226  break;
227  default:
228  throw new InvalidOperationException("Invalid authentication scheme!");
229  }
230  }
231 
239  ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)
240  {
241  RawUserAgent = userAgent?.ToString();
242  Token = token;
243  Username = username;
244  Password = password;
245  ApiVersion = Version;
246  }
247 
252  public bool Compatible() => CheckCompatibility(ApiVersion);
253 
259  public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null)
260  {
261  if (headers == null)
262  throw new ArgumentNullException(nameof(headers));
263  if (instanceId.HasValue && InstanceId.HasValue && instanceId != InstanceId)
264  throw new InvalidOperationException("Specified instance ID in constructor and SetRequestHeaders!");
265 
266  headers.Clear();
267  headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson));
268  if (IsTokenAuthentication)
269  headers.Authorization = new AuthenticationHeaderValue(JwtAuthenticationScheme, Token);
270  else
271  headers.Authorization = new AuthenticationHeaderValue(
272  BasicAuthenticationScheme,
273  Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}")));
274 
275  headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
276  headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
277  instanceId = instanceId ?? InstanceId;
278  if (instanceId.HasValue)
279  headers.Add(InstanceIdHeader, instanceId.ToString());
280  }
281  }
282 }
+Go to the documentation of this file.
1 using Microsoft.AspNetCore.Http.Headers;
2 using Microsoft.Extensions.Primitives;
3 using Microsoft.Net.Http.Headers;
4 using System;
5 using System.Collections.Generic;
6 using System.Globalization;
7 using System.Linq;
8 using System.Net.Http.Headers;
9 using System.Reflection;
10 using System.Text;
11 
12 namespace Tgstation.Server.Api
13 {
17  public sealed class ApiHeaders
18  {
22  public const string ApplicationJson = "application/json";
23 
27  public const string ApiVersionHeader = "api";
28 
32  public const string InstanceIdHeader = "instance";
33 
37  public const string JwtAuthenticationScheme = "bearer";
38 
42  public const string BasicAuthenticationScheme = "basic";
43 
47  const string UsernameHeader = "username";
48 
52  const string PasswordAuthenticationScheme = "password";
53 
57  static readonly AssemblyName AssemblyName = Assembly.GetExecutingAssembly().GetName();
58 
62  public static readonly Version Version = AssemblyName.Version.Semver();
63 
67  public long? InstanceId { get; set; }
68 
72  public ProductHeaderValue? UserAgent => ProductInfoHeaderValue.TryParse(RawUserAgent, out var userAgent) ? userAgent.Product : null;
73 
77  public string? RawUserAgent { get; }
78 
82  public Version ApiVersion { get; }
83 
87  public string? Token { get; }
88 
92  public string? Username { get; }
93 
97  public string? Password { get; }
98 
102  public bool IsTokenAuthentication => Token != null;
103 
109  public static bool CheckCompatibility(Version otherVersion) => Version.Major == (otherVersion?.Major ?? throw new ArgumentNullException(nameof(otherVersion)));
110 
116  public ApiHeaders(ProductHeaderValue userAgent, string token) : this(userAgent, token, null, null)
117  {
118  if (userAgent == null)
119  throw new ArgumentNullException(nameof(userAgent));
120  if (token == null)
121  throw new ArgumentNullException(nameof(token));
122  }
123 
130  public ApiHeaders(ProductHeaderValue userAgent, string username, string password) : this(userAgent, null, username, password)
131  {
132  if (userAgent == null)
133  throw new ArgumentNullException(nameof(userAgent));
134  if (username == null)
135  throw new ArgumentNullException(nameof(username));
136  if (password == null)
137  throw new ArgumentNullException(nameof(password));
138  }
139 
144  public ApiHeaders(RequestHeaders requestHeaders)
145  {
146  if (requestHeaders == null)
147  throw new ArgumentNullException(nameof(requestHeaders));
148 
149  var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(ApplicationJson);
150  if (!requestHeaders.Accept.Any(x => x.MediaType == jsonAccept.MediaType))
151  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", ApplicationJson));
152 
153  if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out var userAgentValues) || userAgentValues.Count == 0)
154  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} headers!", HeaderNames.UserAgent));
155 
156  RawUserAgent = userAgentValues.First();
157  if (String.IsNullOrWhiteSpace(RawUserAgent))
158  throw new InvalidOperationException("Malformed client User-Agent!");
159 
160  // make sure the api header matches ours
161  if (!requestHeaders.Headers.TryGetValue(ApiVersionHeader, out var apiUserAgentHeaderValues) || !ProductInfoHeaderValue.TryParse(apiUserAgentHeaderValues.FirstOrDefault(), out var apiUserAgent) || apiUserAgent.Product.Name != AssemblyName.Name)
162  throw new InvalidOperationException("Missing API version!");
163 
164  if (!Version.TryParse(apiUserAgent.Product.Version, out var apiVersion))
165  throw new InvalidOperationException("Malformed API version!");
166 
167  ApiVersion = apiVersion.Semver();
168 
169  if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization))
170  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} header!", HeaderNames.Authorization));
171  var auth = authorization.First();
172  var splits = new List<string>(auth.Split(' '));
173  var scheme = splits.First();
174  if (String.IsNullOrWhiteSpace(scheme))
175  throw new InvalidOperationException("Missing authentication scheme!");
176 
177  splits.RemoveAt(0);
178  var parameter = String.Concat(splits);
179  if (String.IsNullOrEmpty(parameter))
180  throw new InvalidOperationException("Missing authentication parameter!");
181 
182  if (requestHeaders.Headers.TryGetValue(InstanceIdHeader, out var instanceIdValues))
183  {
184  var instanceIdString = instanceIdValues.FirstOrDefault();
185  if (instanceIdString != default && Int64.TryParse(instanceIdString, out var instanceId))
186  InstanceId = instanceId;
187  }
188 
189 #pragma warning disable CA1308 // Normalize strings to uppercase
190  switch (scheme.ToLowerInvariant())
191 #pragma warning restore CA1308 // Normalize strings to uppercase
192  {
193  case JwtAuthenticationScheme:
194  Token = parameter;
195  break;
196  case PasswordAuthenticationScheme:
197  Password = parameter;
198  var fail = !requestHeaders.Headers.TryGetValue(UsernameHeader, out var values);
199  if (!fail)
200  {
201  Username = values.FirstOrDefault();
202  fail = String.IsNullOrWhiteSpace(Username);
203  }
204 
205  if (fail)
206  throw new InvalidOperationException("Missing Username header!");
207  break;
208  case BasicAuthenticationScheme:
209  string joinedString;
210  try
211  {
212  var base64Bytes = Convert.FromBase64String(parameter);
213  joinedString = Encoding.UTF8.GetString(base64Bytes);
214  }
215  catch
216  {
217  throw new InvalidOperationException("Invalid basic Authorization header!");
218  }
219 
220  var basicAuthSplits = joinedString.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
221  if (basicAuthSplits.Length < 2)
222  throw new InvalidOperationException("Invalid basic Authorization header!");
223 
224  Username = basicAuthSplits.First();
225  Password = String.Concat(basicAuthSplits.Skip(1));
226  break;
227  default:
228  throw new InvalidOperationException("Invalid authentication scheme!");
229  }
230  }
231 
239  ApiHeaders(ProductHeaderValue userAgent, string? token, string? username, string? password)
240  {
241  RawUserAgent = userAgent?.ToString();
242  Token = token;
243  Username = username;
244  Password = password;
245  ApiVersion = Version;
246  }
247 
252  public bool Compatible() => CheckCompatibility(ApiVersion);
253 
259  public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null)
260  {
261  if (headers == null)
262  throw new ArgumentNullException(nameof(headers));
263  if (instanceId.HasValue && InstanceId.HasValue && instanceId != InstanceId)
264  throw new InvalidOperationException("Specified instance ID in constructor and SetRequestHeaders!");
265 
266  headers.Clear();
267  headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson));
268  if (IsTokenAuthentication)
269  headers.Authorization = new AuthenticationHeaderValue(JwtAuthenticationScheme, Token);
270  else
271  headers.Authorization = new AuthenticationHeaderValue(
272  BasicAuthenticationScheme,
273  Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}")));
274 
275  headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
276  headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
277  instanceId ??= InstanceId;
278  if (instanceId.HasValue)
279  headers.Add(InstanceIdHeader, instanceId.ToString());
280  }
281  }
282 }
ApiHeaders(ProductHeaderValue userAgent, string?token, string?username, string?password)
Construct ApiHeaders
Definition: ApiHeaders.cs:239
+
void SetRequestHeaders(HttpRequestHeaders headers, long?instanceId=null)
Set HttpRequestHeaders using the ApiHeaders. This initially clears headers
Definition: ApiHeaders.cs:259
ApiHeaders(ProductHeaderValue userAgent, string username, string password)
Construct ApiHeaders for password authentication
Definition: ApiHeaders.cs:130
ApiHeaders(ProductHeaderValue userAgent, string token)
Construct ApiHeaders for JWT authentication
Definition: ApiHeaders.cs:116
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
-
ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)
Construct ApiHeaders
Definition: ApiHeaders.cs:239
static readonly Version Version
Get the version of the Api the caller is using
Definition: ApiHeaders.cs:62
ApiHeaders(RequestHeaders requestHeaders)
Construct and validates ApiHeaders from a set of requestHeaders
Definition: ApiHeaders.cs:144
diff --git a/_application_8cs_source.html b/_application_8cs_source.html index 86670930f5..61545ab30a 100644 --- a/_application_8cs_source.html +++ b/_application_8cs_source.html @@ -176,7 +176,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
JsonConverter for serializing Versions for BYOND.
static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
Configure the swagger settings.
- + diff --git a/_byond_8cs_source.html b/_byond_8cs_source.html index 236547590d..f4ab174b30 100644 --- a/_byond_8cs_source.html +++ b/_byond_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
Byond.cs
-Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class Byond
9  {
13  public Version Version { get; set; }
14 
18  public Job InstallJob { get; set; }
19  }
20 }
+Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class Byond
9  {
13  public Version? Version { get; set; }
14 
18  public Job? InstallJob { get; set; }
19  }
20 }
Represents a BYOND installation
Definition: Byond.cs:8
diff --git a/_client_exception_8cs_source.html b/_client_exception_8cs_source.html index 997ed597ba..931f871ee9 100644 --- a/_client_exception_8cs_source.html +++ b/_client_exception_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ClientException.cs
-Go to the documentation of this file.
1 using System;
2 using System.Net;
3 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public abstract class ClientException : Exception
11  {
15  public HttpResponseMessage ResponseMessage { get; }
16 
22  protected ClientException(HttpResponseMessage responseMessage, string message) : base(message)
23  {
24  ResponseMessage = responseMessage ?? throw new ArgumentNullException(nameof(responseMessage));
25  }
26 
30  protected ClientException() { }
31 
36  protected ClientException(string message) : base(message) { }
37 
43  protected ClientException(string message, Exception innerException) : base(message, innerException) { }
44  }
45 }
ClientException()
Construct a ClientException
+Go to the documentation of this file.
1 using System;
2 using System.Net;
3 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public abstract class ClientException : Exception
11  {
15  public HttpResponseMessage? ResponseMessage { get; }
16 
22  protected ClientException(HttpResponseMessage responseMessage, string message) : base(message)
23  {
24  ResponseMessage = responseMessage ?? throw new ArgumentNullException(nameof(responseMessage));
25  }
26 
30  protected ClientException() { }
31 
36  protected ClientException(string message) : base(message) { }
37 
43  protected ClientException(string message, Exception innerException) : base(message, innerException) { }
44  }
45 }
ClientException()
Construct a ClientException
ClientException(string message, Exception innerException)
Construct a ClientException with a message and innerException
diff --git a/_configuration_client_8cs_source.html b/_configuration_client_8cs_source.html index 6ecf048106..f1d7019209 100644 --- a/_configuration_client_8cs_source.html +++ b/_configuration_client_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ConfigurationClient.cs
-Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Threading;
4 using System.Threading.Tasks;
5 using Tgstation.Server.Api;
7 
8 namespace Tgstation.Server.Client.Components
9 {
12  {
17 
21  readonly Instance instance;
22 
28  static string SanitizeGetPath(string path)
29  {
30  if (path == null)
31  path = String.Empty;
32  if (path.Length == 0 || path[0] != '/')
33  path = '/' + path;
34  return path;
35  }
36 
42  public ConfigurationClient(IApiClient apiClient, Instance instance)
43  {
44  this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
45  this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
46  }
47 
49  public Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration, directory, instance.Id, cancellationToken);
50 
52  public Task<ConfigurationFile> CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Create<ConfigurationFile, ConfigurationFile>(Routes.Configuration, directory, instance.Id, cancellationToken);
53 
55  public Task<IReadOnlyList<ConfigurationFile>> List(string directory, CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<ConfigurationFile>>(Routes.ListRoute(Routes.Configuration) + SanitizeGetPath(directory), instance.Id, cancellationToken);
56 
58  public Task<ConfigurationFile> Read(ConfigurationFile file, CancellationToken cancellationToken)
59  {
60  if (file == null)
61  throw new ArgumentNullException(nameof(file));
62  return apiClient.Read<ConfigurationFile>(Routes.ConfigurationFile + SanitizeGetPath(file.Path), instance.Id, cancellationToken);
63  }
64 
66  public Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken) => apiClient.Update<ConfigurationFile, ConfigurationFile>(Routes.Configuration, file ?? throw new ArgumentNullException(nameof(file)), instance.Id, cancellationToken);
67  }
68 }
const string Configuration
The Models.ConfigurationFile controller
Definition: Routes.cs:49
+Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Threading;
4 using System.Threading.Tasks;
5 using Tgstation.Server.Api;
7 
8 namespace Tgstation.Server.Client.Components
9 {
12  {
17 
21  readonly Instance instance;
22 
28  static string SanitizeGetPath(string path)
29  {
30  if (path == null)
31  path = String.Empty;
32  if (path.Length == 0 || path[0] != '/')
33  path = '/' + path;
34  return path;
35  }
36 
42  public ConfigurationClient(IApiClient apiClient, Instance instance)
43  {
44  this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
45  this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
46  }
47 
49  public Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration, directory, instance.Id, cancellationToken);
50 
52  public Task<ConfigurationFile> CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Create<ConfigurationFile, ConfigurationFile>(Routes.Configuration, directory, instance.Id, cancellationToken);
53 
55  public Task<IReadOnlyList<ConfigurationFile>> List(string directory, CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<ConfigurationFile>>(Routes.ListRoute(Routes.Configuration) + SanitizeGetPath(directory), instance.Id, cancellationToken);
56 
58  public Task<ConfigurationFile> Read(ConfigurationFile file, CancellationToken cancellationToken)
59  {
60  if (file == null)
61  throw new ArgumentNullException(nameof(file));
62  return apiClient.Read<ConfigurationFile>(
63  Routes.ConfigurationFile + SanitizeGetPath(file.Path ?? throw new ArgumentException("file.Path should not be null!", nameof(file))),
64  instance.Id,
65  cancellationToken);
66  }
67 
69  public Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken) => apiClient.Update<ConfigurationFile, ConfigurationFile>(Routes.Configuration, file ?? throw new ArgumentNullException(nameof(file)), instance.Id, cancellationToken);
70  }
71 }
const string Configuration
The Models.ConfigurationFile controller
Definition: Routes.cs:49
diff --git a/_configuration_file_8cs_source.html b/_configuration_file_8cs_source.html index 4a8b30f7c4..ec8365c63b 100644 --- a/_configuration_file_8cs_source.html +++ b/_configuration_file_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ConfigurationFile.cs
-Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class ConfigurationFile
9  {
13  [StringLength(Limits.MaximumStringLength)]
14  public string Path { get; set; }
15 
19  public bool? AccessDenied { get; set; }
20 
24  public bool? IsDirectory { get; set; }
25 
29  public string LastReadHash { get; set; }
30 
34 #pragma warning disable CA1819 // Properties should not return arrays
35  public byte[] Content { get; set; }
36 #pragma warning restore CA1819 // Properties should not return arrays
37  }
38 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
+Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class ConfigurationFile
9  {
13  [StringLength(Limits.MaximumStringLength)]
14  public string? Path { get; set; }
15 
19  public bool? AccessDenied { get; set; }
20 
24  public bool? IsDirectory { get; set; }
25 
29  public string? LastReadHash { get; set; }
30 
34 #pragma warning disable CA1819, SA1011 // Properties should not return arrays, Closing square bracket should be followed by a space
35  public byte[]? Content { get; set; }
36 #pragma warning restore CA1819, SA1011 // Properties should not return arrays, Closing square bracket should be followed by a space
37  }
38 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
Sanity limits to prevent users from overloading
Definition: Limits.cs:6
diff --git a/_conflict_exception_8cs_source.html b/_conflict_exception_8cs_source.html index 1a928831ef..c6a89c849a 100644 --- a/_conflict_exception_8cs_source.html +++ b/_conflict_exception_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ConflictException.cs
-Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class ConflictException : ApiException
11  {
17  public ConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  { }
19 
23  public ConflictException() { }
24 
29  public ConflictException(string message) : base(message) { }
30 
36  public ConflictException(string message, Exception innerException) : base(message, innerException) { }
37  }
38 }
ConflictException()
Construct a ConflictException
+Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class ConflictException : ApiException
11  {
17  public ConflictException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  { }
19 
23  public ConflictException() { }
24 
29  public ConflictException(string message) : base(message) { }
30 
36  public ConflictException(string message, Exception innerException) : base(message, innerException) { }
37  }
38 }
ConflictException()
Construct a ConflictException
ConflictException(string message, Exception innerException)
Construct an ConflictException with a message and innerException
ConflictException(string message)
Construct an ConflictException with a message
@@ -102,9 +102,9 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
API related Exceptions.
Definition: ApiException.cs:10
+
ConflictException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the ConflictException .
Occurs when the client performs an action that would result in data conflict
-
ConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the ConflictException .
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
diff --git a/_discord_connection_string_builder_8cs_source.html b/_discord_connection_string_builder_8cs_source.html index 776fc34942..49c57a9ab9 100644 --- a/_discord_connection_string_builder_8cs_source.html +++ b/_discord_connection_string_builder_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
DiscordConnectionStringBuilder.cs
-Go to the documentation of this file.
1 using System;
3 
4 namespace Tgstation.Server.Api.Models
5 {
10  {
12  public override bool Valid => !String.IsNullOrEmpty(BotToken);
13 
18  public string BotToken { get; set; }
19 
24 
29  public DiscordConnectionStringBuilder(string connectionString)
30  {
31  BotToken = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
32  }
33 
35  public override string ToString() => BotToken;
36  }
37 }
+Go to the documentation of this file.
1 using System;
3 
4 namespace Tgstation.Server.Api.Models
5 {
10  {
12  public override bool Valid => !String.IsNullOrEmpty(BotToken);
13 
18  public string? BotToken { get; set; }
19 
24 
29  public DiscordConnectionStringBuilder(string connectionString)
30  {
31  BotToken = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
32  }
33 
35  public override string ToString() => BotToken ?? "(null)";
36  }
37 }
ChatConnectionStringBuilder for ChatProvider.Discord
diff --git a/_dream_daemon_8cs_source.html b/_dream_daemon_8cs_source.html index 88deecb0dc..fe7f88092e 100644 --- a/_dream_daemon_8cs_source.html +++ b/_dream_daemon_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
DreamDaemon.cs
-Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
3 
4 namespace Tgstation.Server.Api.Models
5 {
9  public sealed class DreamDaemon : DreamDaemonSettings
10  {
14  public CompileJob ActiveCompileJob { get; set; }
15 
19  public CompileJob StagedCompileJob { get; set; }
20 
24  public bool? Running { get; set; }
25 
29  [EnumDataType(typeof(DreamDaemonSecurity))]
30  public DreamDaemonSecurity? CurrentSecurity { get; set; }
31 
35  public ushort? CurrentPort { get; set; }
36 
40  public bool? CurrentAllowWebclient { get; set; }
41 
45  public bool? SoftRestart { get; set; }
46 
50  public bool? SoftShutdown { get; set; }
51  }
52 }
+Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
3 
4 namespace Tgstation.Server.Api.Models
5 {
9  public sealed class DreamDaemon : DreamDaemonSettings
10  {
14  public CompileJob? ActiveCompileJob { get; set; }
15 
19  public CompileJob? StagedCompileJob { get; set; }
20 
24  public bool? Running { get; set; }
25 
29  [EnumDataType(typeof(DreamDaemonSecurity))]
30  public DreamDaemonSecurity? CurrentSecurity { get; set; }
31 
35  public ushort? CurrentPort { get; set; }
36 
40  public bool? CurrentAllowWebclient { get; set; }
41 
45  public bool? SoftRestart { get; set; }
46 
50  public bool? SoftShutdown { get; set; }
51  }
52 }
diff --git a/_error_code_extensions_8cs_source.html b/_error_code_extensions_8cs_source.html index e94d09a174..476639eaeb 100644 --- a/_error_code_extensions_8cs_source.html +++ b/_error_code_extensions_8cs_source.html @@ -94,11 +94,11 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ErrorCodeExtensions.cs
-Go to the documentation of this file.
1 using System.ComponentModel;
2 using System.Linq;
3 
4 namespace Tgstation.Server.Api.Models
5 {
9  public static class ErrorCodeExtensions
10  {
16  public static string Describe(this ErrorCode errorCode)
17  {
18  var attributes = (DescriptionAttribute[])typeof(ErrorCode)
19  .GetField(errorCode.ToString())
20  ?.GetCustomAttributes(typeof(DescriptionAttribute), false);
21 
22  return attributes?.FirstOrDefault()?.Description;
23  }
24  }
25 }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
+Go to the documentation of this file.
1 using System.Collections.Generic;
2 using System.ComponentModel;
3 using System.Linq;
4 
5 namespace Tgstation.Server.Api.Models
6 {
10  public static class ErrorCodeExtensions
11  {
17  public static string? Describe(this ErrorCode errorCode)
18  {
19  var attributes = (IEnumerable<DescriptionAttribute>?)typeof(ErrorCode)
20  .GetField(errorCode.ToString())
21  ?.GetCustomAttributes(typeof(DescriptionAttribute), false);
22 
23  return attributes?.FirstOrDefault()?.Description;
24  }
25  }
26 }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
-
Extension methods for the ErrorCode .
-
static string Describe(this ErrorCode errorCode)
Describe a given errorCode .
+
Extension methods for the ErrorCode .
+
static string Describe(this ErrorCode errorCode)
Describe a given errorCode .
diff --git a/_error_message_8cs_source.html b/_error_message_8cs_source.html index cb5588e17a..ceb7ade27f 100644 --- a/_error_message_8cs_source.html +++ b/_error_message_8cs_source.html @@ -94,12 +94,13 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ErrorMessage.cs
-Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
3 
4 namespace Tgstation.Server.Api.Models
5 {
9  public sealed class ErrorMessage
10  {
14  [Required]
15  public Version ServerApiVersion { get; set; }
16 
20  [Required]
21  public string Message { get; set; }
22 
26  public string AdditionalData { get; set; }
27 
31  [EnumDataType(typeof(ErrorCode))]
32  public ErrorCode ErrorCode { get; set; }
33 
38  public ErrorMessage(ErrorCode errorCode)
39  {
40  ErrorCode = errorCode;
41  Message = errorCode.Describe();
42  ServerApiVersion = ApiHeaders.Version;
43  }
44  }
45 }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
+Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
3 
4 namespace Tgstation.Server.Api.Models
5 {
9  public sealed class ErrorMessage
10  {
14  [Required]
15  public Version? ServerApiVersion { get; set; }
16 
20  [Required]
21  public string? Message { get; set; }
22 
26  public string? AdditionalData { get; set; }
27 
31  [EnumDataType(typeof(ErrorCode))]
32  public ErrorCode ErrorCode { get; set; }
33 
37  public ErrorMessage() { }
38 
43  public ErrorMessage(ErrorCode errorCode)
44  {
45  ErrorCode = errorCode;
46  Message = errorCode.Describe();
47  ServerApiVersion = ApiHeaders.Version;
48  }
49  }
50 }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
+
ErrorMessage()
Initializes a new instance of the ErrorMessage .
Definition: ErrorMessage.cs:37
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
static readonly Version Version
Get the version of the Api the caller is using
Definition: ApiHeaders.cs:62
-
ErrorMessage(ErrorCode errorCode)
Initializes a new instance of the ErrorMessage .
Definition: ErrorMessage.cs:38
+
ErrorMessage(ErrorCode errorCode)
Initializes a new instance of the ErrorMessage .
Definition: ErrorMessage.cs:43
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
diff --git a/_i_api_client_factory_8cs_source.html b/_i_api_client_factory_8cs_source.html index 63aabf8dd9..0973c25b87 100644 --- a/_i_api_client_factory_8cs_source.html +++ b/_i_api_client_factory_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
IApiClientFactory.cs
-Go to the documentation of this file.
1 using System;
2 using Tgstation.Server.Api;
3 
4 namespace Tgstation.Server.Client
5 {
9  interface IApiClientFactory
10  {
17  IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders);
18  }
19 }
+Go to the documentation of this file.
1 using System;
2 using Tgstation.Server.Api;
3 
4 namespace Tgstation.Server.Client
5 {
9  interface IApiClientFactory
10  {
18  IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders);
19  }
20 }
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
diff --git a/_i_server_client_factory_8cs_source.html b/_i_server_client_factory_8cs_source.html index c867de39ed..411218c39e 100644 --- a/_i_server_client_factory_8cs_source.html +++ b/_i_server_client_factory_8cs_source.html @@ -94,13 +94,13 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
IServerClientFactory.cs
-Go to the documentation of this file.
1 using System;
2 using System.Threading;
3 using System.Threading.Tasks;
5 
6 namespace Tgstation.Server.Client
7 {
11  public interface IServerClientFactory
12  {
22  Task<IServerClient> CreateServerClient(Uri host, string username, string password, TimeSpan timeout = default, CancellationToken cancellationToken = default);
23 
31  IServerClient CreateServerClient(Uri host, Token token, TimeSpan timeout = default);
32  }
33 }
+Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Threading;
4 using System.Threading.Tasks;
6 
7 namespace Tgstation.Server.Client
8 {
12  public interface IServerClientFactory
13  {
25  Task<IServerClient> CreateFromLogin(
26  Uri host,
27  string username,
28  string password,
29  IEnumerable<IRequestLogger>? requestLoggers = null,
30  TimeSpan? timeout = null,
31  bool attemptLoginRefresh = true,
32  CancellationToken cancellationToken = default);
33 
40  IServerClient CreateFromToken(
41  Uri host,
42  Token token);
43  }
44 }
Main client for communicating with a server
Represents a JWT returned by the API
Definition: Token.cs:8
-
Factory for creating IServerClients
+
Factory for creating IServerClients
diff --git a/_instance_manager_8cs_source.html b/_instance_manager_8cs_source.html index a9c56a87ff..2ba6903c27 100644 --- a/_instance_manager_8cs_source.html +++ b/_instance_manager_8cs_source.html @@ -94,59 +94,62 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
InstanceManager.cs
-Go to the documentation of this file.
1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.Extensions.Hosting;
3 using Microsoft.Extensions.Logging;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Threading;
8 using System.Threading.Tasks;
13 using Tgstation.Server.Host.IO;
17 
18 namespace Tgstation.Server.Host.Components
19 {
22  {
24  public Task Ready => readyTcs.Task;
25 
30 
35 
40 
45 
50 
55 
60 
65 
69  readonly ILogger<InstanceManager> logger;
70 
74  readonly IDictionary<long, IInstance> instances;
75 
79  readonly IDictionary<string, IBridgeHandler> bridgeHandlers;
80 
84  readonly TaskCompletionSource<object> readyTcs;
85 
90 
94  bool disposed;
95 
109  IInstanceFactory instanceFactory,
110  IIOManager ioManager,
111  IDatabaseContextFactory databaseContextFactory,
112  IAssemblyInformationProvider assemblyInformationProvider,
113  IJobManager jobManager,
114  IServerControl serverControl,
115  ISystemIdentityFactory systemIdentityFactory,
116  IAsyncDelayer asyncDelayer,
117  ILogger<InstanceManager> logger)
118  {
119  this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
120  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
121  this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
122  this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
123  this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
124  this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
125  this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
126  this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
127  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
128 
129  serverControl.RegisterForRestart(this);
130 
131  instances = new Dictionary<long, IInstance>();
132  bridgeHandlers = new Dictionary<string, IBridgeHandler>();
133  readyTcs = new TaskCompletionSource<object>();
134  }
135 
137  public void Dispose()
138  {
139  lock (instances)
140  {
141  if (disposed)
142  return;
143  disposed = true;
144  }
145 
146  foreach (var I in instances)
147  I.Value.Dispose();
148  }
149 
151  public IInstance GetInstance(Models.Instance metadata)
152  {
153  if (metadata == null)
154  throw new ArgumentNullException(nameof(metadata));
155  lock (instances)
156  {
157  if (!instances.TryGetValue(metadata.Id, out IInstance instance))
158  throw new InvalidOperationException("Instance not online!");
159  return instance;
160  }
161  }
162 
164  public async Task MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken)
165  {
166  if (newPath == null)
167  throw new ArgumentNullException(nameof(newPath));
168  if (instance.Online.Value)
169  throw new InvalidOperationException("Cannot move an online instance!");
170  var oldPath = instance.Path;
171  await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false);
172  await databaseContextFactory.UseContext(db =>
173  {
174  var targetInstance = new Models.Instance
175  {
176  Id = instance.Id
177  };
178  db.Instances.Attach(targetInstance);
179  targetInstance.Path = newPath;
180  return db.Save(cancellationToken);
181  }).ConfigureAwait(false);
182  await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false);
183  }
184 
186  public async Task OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
187  {
188  if (metadata == null)
189  throw new ArgumentNullException(nameof(metadata));
190  logger.LogInformation("Offlining instance ID {0}", metadata.Id);
191  IInstance instance;
192  lock (instances)
193  {
194  if (!instances.TryGetValue(metadata.Id, out instance))
195  throw new InvalidOperationException("Instance not online!");
196  instances.Remove(metadata.Id);
197  }
198 
199  try
200  {
201  // we are the one responsible for cancelling his jobs
202  var tasks = new List<Task>();
203  await databaseContextFactory.UseContext(async db =>
204  {
205  var jobs = db.Jobs.Where(x => x.Instance.Id == metadata.Id).Select(x => new Models.Job
206  {
207  Id = x.Id
208  }).ToAsyncEnumerable();
209  await jobs.ForEachAsync(job =>
210  {
211  lock (tasks)
212  tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken));
213  }, cancellationToken).ConfigureAwait(false);
214  }).ConfigureAwait(false);
215 
216  await Task.WhenAll(tasks).ConfigureAwait(false);
217 
218  await instance.StopAsync(cancellationToken).ConfigureAwait(false);
219  }
220  finally
221  {
222  instance.Dispose();
223  }
224  }
225 
227  public async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
228  {
229  if (metadata == null)
230  throw new ArgumentNullException(nameof(metadata));
231  logger.LogInformation("Onlining instance ID {0} ({1}) at {2}", metadata.Id, metadata.Name, metadata.Path);
232  var instance = instanceFactory.CreateInstance(this, metadata);
233  try
234  {
235  lock (instances)
236  {
237  if (instances.ContainsKey(metadata.Id))
238  throw new InvalidOperationException("Instance already online!");
239  instances.Add(metadata.Id, instance);
240  }
241  }
242  catch
243  {
244  instance.Dispose();
245  throw;
246  }
247 
248  await instance.StartAsync(cancellationToken).ConfigureAwait(false);
249  }
250 
252  #pragma warning disable CA1506 // TODO: Decomplexify
253  public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext =>
254  {
255  logger.LogInformation(assemblyInformationProvider.VersionString);
256 
257  try
258  {
259  CheckSystemCompatibility();
260  var factoryStartup = instanceFactory.StartAsync(cancellationToken);
261  await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
262  await jobManager.StartAsync(cancellationToken).ConfigureAwait(false);
263  var dbInstances = databaseContext.Instances.Where(x => x.Online.Value)
264  .Include(x => x.RepositorySettings)
265  .Include(x => x.ChatSettings)
266  .ThenInclude(x => x.Channels)
267  .Include(x => x.DreamDaemonSettings)
268  .ToAsyncEnumerable();
269  var tasks = new List<Task>();
270  await factoryStartup.ConfigureAwait(false);
271  await dbInstances.ForEachAsync(metadata => tasks.Add(metadata.Online.Value ? OnlineInstance(metadata, cancellationToken) : Task.CompletedTask), cancellationToken).ConfigureAwait(false);
272  await Task.WhenAll(tasks).ConfigureAwait(false);
273  logger.LogInformation("Server ready!");
274  readyTcs.SetResult(null);
275  }
276  catch (OperationCanceledException)
277  {
278  logger.LogInformation("Cancelled instance manager initialization!");
279  }
280  catch (Exception e)
281  {
282  logger.LogCritical("Instance manager startup error! Exception: {0}", e);
283  try
284  {
285  await serverControl.Die(e).ConfigureAwait(false);
286  return;
287  }
288  catch (Exception e2)
289  {
290  logger.LogCritical("Failed to kill server! Exception: {0}", e2);
291  }
292 
293  throw;
294  }
295  });
296  #pragma warning restore CA1506 // TODO: Decomplexify
297 
299  public async Task StopAsync(CancellationToken cancellationToken)
300  {
301  await jobManager.StopAsync(cancellationToken).ConfigureAwait(false);
302  await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false);
303  await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false);
304 
305  // downgrade the db if necessary
306  if (downgradeVersion != null)
307  await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false);
308  }
309 
311  public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
312  {
313  downgradeVersion = updateVersion != null && updateVersion < assemblyInformationProvider.Version ? updateVersion : null;
314  return Task.CompletedTask;
315  }
316 
321  {
322  using var systemIdentity = systemIdentityFactory.GetCurrent();
323  if (!systemIdentity.CanCreateSymlinks)
324  throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
325  }
326 
328  public async Task<BridgeResponse> ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
329  {
330  if (parameters == null)
331  throw new ArgumentNullException(nameof(parameters));
332 
333  IBridgeHandler bridgeHandler = null;
334  for (var i = 0; bridgeHandler == null && i < 30; ++i)
335  {
336  // There's a miniscule time period where we could potentially receive a bridge request and not have the registration ready when we launch DD
337  // This is a stopgap
338  Task delayTask = Task.CompletedTask;
339  lock (bridgeHandlers)
340  if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
341  delayTask = asyncDelayer.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
342 
343  await delayTask.ConfigureAwait(false);
344  }
345 
346  if (bridgeHandler == null)
347  lock (bridgeHandlers)
348  if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
349  {
350  logger.LogWarning("Recieved invalid bridge request with accees identifier: {0}", parameters.AccessIdentifier);
351  return null;
352  }
353 
354  return await bridgeHandler.ProcessBridgeRequest(parameters, cancellationToken).ConfigureAwait(false);
355  }
356 
359  {
360  if (bridgeHandler == null)
361  throw new ArgumentNullException(nameof(bridgeHandler));
362 
363  var accessIdentifier = bridgeHandler.DMApiParameters.AccessIdentifier;
364  lock (bridgeHandlers)
365  {
366  bridgeHandlers.Add(accessIdentifier, bridgeHandler);
367  logger.LogTrace("Registered bridge handler: {0}", accessIdentifier);
368  }
369 
370  return new BridgeRegistration(() =>
371  {
372  lock (bridgeHandlers)
373  {
374  bridgeHandlers.Remove(accessIdentifier);
375  logger.LogTrace("Unregistered bridge handler: {0}", accessIdentifier);
376  }
377  });
378  }
379  }
380 }
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the InstanceManager
-
readonly IServerControl serverControl
The IServerControl for the InstanceManager
-
IInstance GetInstance(Models.Instance metadata)
Get the IInstance associated with given metadata
+Go to the documentation of this file.
1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.Extensions.Hosting;
3 using Microsoft.Extensions.Logging;
4 using Microsoft.Extensions.Options;
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Linq;
9 using System.Threading;
10 using System.Threading.Tasks;
16 using Tgstation.Server.Host.IO;
20 
21 namespace Tgstation.Server.Host.Components
22 {
25  {
27  public Task Ready => readyTcs.Task;
28 
33 
38 
43 
48 
53 
58 
63 
68 
72  readonly ILogger<InstanceManager> logger;
73 
77  readonly IDictionary<long, IInstance> instances;
78 
82  readonly IDictionary<string, IBridgeHandler> bridgeHandlers;
83 
88 
92  readonly TaskCompletionSource<object> readyTcs;
93 
98 
102  bool disposed;
103 
118  IInstanceFactory instanceFactory,
119  IIOManager ioManager,
120  IDatabaseContextFactory databaseContextFactory,
121  IAssemblyInformationProvider assemblyInformationProvider,
122  IJobManager jobManager,
123  IServerControl serverControl,
124  ISystemIdentityFactory systemIdentityFactory,
125  IAsyncDelayer asyncDelayer,
126  IOptions<GeneralConfiguration> generalConfigurationOptions,
127  ILogger<InstanceManager> logger)
128  {
129  this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
130  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
131  this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
132  this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
133  this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
134  this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
135  this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
136  this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
137  generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
138  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
139 
140  serverControl.RegisterForRestart(this);
141 
142  instances = new Dictionary<long, IInstance>();
143  bridgeHandlers = new Dictionary<string, IBridgeHandler>();
144  readyTcs = new TaskCompletionSource<object>();
145  }
146 
148  public void Dispose()
149  {
150  lock (instances)
151  {
152  if (disposed)
153  return;
154  disposed = true;
155  }
156 
157  foreach (var I in instances)
158  I.Value.Dispose();
159  }
160 
162  public IInstance GetInstance(Models.Instance metadata)
163  {
164  if (metadata == null)
165  throw new ArgumentNullException(nameof(metadata));
166  lock (instances)
167  {
168  if (!instances.TryGetValue(metadata.Id, out IInstance instance))
169  throw new InvalidOperationException("Instance not online!");
170  return instance;
171  }
172  }
173 
175  public async Task MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken)
176  {
177  if (newPath == null)
178  throw new ArgumentNullException(nameof(newPath));
179  if (instance.Online.Value)
180  throw new InvalidOperationException("Cannot move an online instance!");
181  var oldPath = instance.Path;
182  await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false);
183  await databaseContextFactory.UseContext(db =>
184  {
185  var targetInstance = new Models.Instance
186  {
187  Id = instance.Id
188  };
189  db.Instances.Attach(targetInstance);
190  targetInstance.Path = newPath;
191  return db.Save(cancellationToken);
192  }).ConfigureAwait(false);
193  await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false);
194  }
195 
197  public async Task OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
198  {
199  if (metadata == null)
200  throw new ArgumentNullException(nameof(metadata));
201  logger.LogInformation("Offlining instance ID {0}", metadata.Id);
202  IInstance instance;
203  lock (instances)
204  {
205  if (!instances.TryGetValue(metadata.Id, out instance))
206  throw new InvalidOperationException("Instance not online!");
207  instances.Remove(metadata.Id);
208  }
209 
210  try
211  {
212  // we are the one responsible for cancelling his jobs
213  var tasks = new List<Task>();
214  await databaseContextFactory.UseContext(async db =>
215  {
216  var jobs = db.Jobs.Where(x => x.Instance.Id == metadata.Id).Select(x => new Models.Job
217  {
218  Id = x.Id
219  }).ToAsyncEnumerable();
220  await jobs.ForEachAsync(job =>
221  {
222  lock (tasks)
223  tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken));
224  }, cancellationToken).ConfigureAwait(false);
225  }).ConfigureAwait(false);
226 
227  await Task.WhenAll(tasks).ConfigureAwait(false);
228 
229  await instance.StopAsync(cancellationToken).ConfigureAwait(false);
230  }
231  finally
232  {
233  instance.Dispose();
234  }
235  }
236 
238  public async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
239  {
240  if (metadata == null)
241  throw new ArgumentNullException(nameof(metadata));
242  logger.LogInformation("Onlining instance ID {0} ({1}) at {2}", metadata.Id, metadata.Name, metadata.Path);
243  var instance = instanceFactory.CreateInstance(this, metadata);
244  try
245  {
246  lock (instances)
247  {
248  if (instances.ContainsKey(metadata.Id))
249  throw new InvalidOperationException("Instance already online!");
250  instances.Add(metadata.Id, instance);
251  }
252  }
253  catch
254  {
255  instance.Dispose();
256  throw;
257  }
258 
259  await instance.StartAsync(cancellationToken).ConfigureAwait(false);
260  }
261 
263  #pragma warning disable CA1506 // TODO: Decomplexify
264  public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext =>
265  {
266  logger.LogInformation(assemblyInformationProvider.VersionString);
267 
268  try
269  {
270  CheckSystemCompatibility();
271  var factoryStartup = instanceFactory.StartAsync(cancellationToken);
272  await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
273  await jobManager.StartAsync(cancellationToken).ConfigureAwait(false);
274  var dbInstances = databaseContext.Instances.Where(x => x.Online.Value)
275  .Include(x => x.RepositorySettings)
276  .Include(x => x.ChatSettings)
277  .ThenInclude(x => x.Channels)
278  .Include(x => x.DreamDaemonSettings)
279  .ToAsyncEnumerable();
280  var tasks = new List<Task>();
281  await factoryStartup.ConfigureAwait(false);
282  await dbInstances.ForEachAsync(metadata => tasks.Add(metadata.Online.Value ? OnlineInstance(metadata, cancellationToken) : Task.CompletedTask), cancellationToken).ConfigureAwait(false);
283  await Task.WhenAll(tasks).ConfigureAwait(false);
284  logger.LogInformation("Server ready!");
285  readyTcs.SetResult(null);
286  }
287  catch (OperationCanceledException)
288  {
289  logger.LogInformation("Cancelled instance manager initialization!");
290  }
291  catch (Exception e)
292  {
293  logger.LogCritical("Instance manager startup error! Exception: {0}", e);
294  try
295  {
296  await serverControl.Die(e).ConfigureAwait(false);
297  return;
298  }
299  catch (Exception e2)
300  {
301  logger.LogCritical("Failed to kill server! Exception: {0}", e2);
302  }
303 
304  throw;
305  }
306  });
307  #pragma warning restore CA1506 // TODO: Decomplexify
308 
310  public async Task StopAsync(CancellationToken cancellationToken)
311  {
312  await jobManager.StopAsync(cancellationToken).ConfigureAwait(false);
313  await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false);
314  await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false);
315 
316  // downgrade the db if necessary
317  if (downgradeVersion != null)
318  await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false);
319  }
320 
322  public Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
323  {
324  downgradeVersion = updateVersion != null && updateVersion < assemblyInformationProvider.Version ? updateVersion : null;
325  return Task.CompletedTask;
326  }
327 
332  {
333  if (generalConfiguration.UseExperimentalWatchdog && !Debugger.IsAttached)
334  throw new InvalidOperationException(
335  "The experimental watchdog is currently non-functional! Please disable the '{nameof(generalConfiguration.UseExperimentalWatchdog)}' option in your configuration!");
336 
337  using var systemIdentity = systemIdentityFactory.GetCurrent();
338  if (!systemIdentity.CanCreateSymlinks)
339  throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
340  }
341 
343  public async Task<BridgeResponse> ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
344  {
345  if (parameters == null)
346  throw new ArgumentNullException(nameof(parameters));
347 
348  IBridgeHandler bridgeHandler = null;
349  for (var i = 0; bridgeHandler == null && i < 30; ++i)
350  {
351  // There's a miniscule time period where we could potentially receive a bridge request and not have the registration ready when we launch DD
352  // This is a stopgap
353  Task delayTask = Task.CompletedTask;
354  lock (bridgeHandlers)
355  if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
356  delayTask = asyncDelayer.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
357 
358  await delayTask.ConfigureAwait(false);
359  }
360 
361  if (bridgeHandler == null)
362  lock (bridgeHandlers)
363  if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
364  {
365  logger.LogWarning("Recieved invalid bridge request with accees identifier: {0}", parameters.AccessIdentifier);
366  return null;
367  }
368 
369  return await bridgeHandler.ProcessBridgeRequest(parameters, cancellationToken).ConfigureAwait(false);
370  }
371 
374  {
375  if (bridgeHandler == null)
376  throw new ArgumentNullException(nameof(bridgeHandler));
377 
378  var accessIdentifier = bridgeHandler.DMApiParameters.AccessIdentifier;
379  lock (bridgeHandlers)
380  {
381  bridgeHandlers.Add(accessIdentifier, bridgeHandler);
382  logger.LogTrace("Registered bridge handler: {0}", accessIdentifier);
383  }
384 
385  return new BridgeRegistration(() =>
386  {
387  lock (bridgeHandlers)
388  {
389  bridgeHandlers.Remove(accessIdentifier);
390  logger.LogTrace("Unregistered bridge handler: {0}", accessIdentifier);
391  }
392  });
393  }
394  }
395 }
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the InstanceManager
+
readonly IServerControl serverControl
The IServerControl for the InstanceManager
+
IInstance GetInstance(Models.Instance metadata)
Get the IInstance associated with given metadata
-
readonly IDictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
+
readonly IDictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
Factory for scoping usage of IDatabaseContexts. Meant for use by Components
-
async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
Online an IInstance
+
async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
Online an IInstance
-
readonly ISystemIdentityFactory systemIdentityFactory
The ISystemIdentityFactory for the InstanceManager
-
bool disposed
If the InstanceManager has been Disposed
-
Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
Handle a restart of the server
-
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the InstanceManager
+
readonly ISystemIdentityFactory systemIdentityFactory
The ISystemIdentityFactory for the InstanceManager
+
bool disposed
If the InstanceManager has been Disposed
+
Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
Handle a restart of the server
+
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the InstanceManager
-
async Task OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
-
IBridgeRegistration RegisterHandler(IBridgeHandler bridgeHandler)
Register a given bridgeHandler .
+
async Task OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
+
IBridgeRegistration RegisterHandler(IBridgeHandler bridgeHandler)
Register a given bridgeHandler .
-
async Task< BridgeResponse > ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .
+
async Task< BridgeResponse > ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .
+
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the InstanceManager.
-
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
-
readonly IJobManager jobManager
The IJobManager for the InstanceManager
+
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
+
readonly IJobManager jobManager
The IJobManager for the InstanceManager
-
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
-
InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IAssemblyInformationProvider assemblyInformationProvider, IJobManager jobManager, IServerControl serverControl, ISystemIdentityFactory systemIdentityFactory, IAsyncDelayer asyncDelayer, ILogger< InstanceManager > logger)
Construct an InstanceManager
+
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
+ -
readonly TaskCompletionSource< object > readyTcs
The TaskCompletionSource<TResult> for Ready.
-
Version downgradeVersion
Used in StopAsync(CancellationToken) to determine if database downgrades must be made ...
+
readonly TaskCompletionSource< object > readyTcs
The TaskCompletionSource<TResult> for Ready.
+
Version downgradeVersion
Used in StopAsync(CancellationToken) to determine if database downgrades must be made ...
For interacting with the instance services
Definition: IInstance.cs:16
- -
async Task StopAsync(CancellationToken cancellationToken)
+ +
async Task StopAsync(CancellationToken cancellationToken)
string AccessIdentifier
Used to identify and authenticate the DreamDaemon instance
- -
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
+ +
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
-
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
+
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
Task< BridgeResponse > ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .
+
Manages the runtime of Jobs
Definition: IJobManager.cs:13
-
readonly IIOManager ioManager
The IIOManager for the InstanceManager
+
readonly IIOManager ioManager
The IIOManager for the InstanceManager
Interface for using filesystems
Definition: IIOManager.cs:11
-
void CheckSystemCompatibility()
Check we have a valid system identity.
-
async Task MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken)
Move an IInstance
+
void CheckSystemCompatibility()
Check we have a valid system identity.
+
InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IAssemblyInformationProvider assemblyInformationProvider, IJobManager jobManager, IServerControl serverControl, ISystemIdentityFactory systemIdentityFactory, IAsyncDelayer asyncDelayer, IOptions< GeneralConfiguration > generalConfigurationOptions, ILogger< InstanceManager > logger)
Construct an InstanceManager
+
async Task MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken)
Move an IInstance
IRestartRegistration RegisterForRestart(IRestartHandler handler)
Register a given handler to run before stopping the server for a restart
Represents a registration of an interop session.
diff --git a/_instance_user_client_8cs_source.html b/_instance_user_client_8cs_source.html index f84c5d9d8a..f064d03a33 100644 --- a/_instance_user_client_8cs_source.html +++ b/_instance_user_client_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
InstanceUserClient.cs
-Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Threading;
4 using System.Threading.Tasks;
5 using Tgstation.Server.Api;
7 
8 namespace Tgstation.Server.Client.Components
9 {
12  {
17 
21  readonly Instance instance;
22 
28  public InstanceUserClient(IApiClient apiClient, Instance instance)
29  {
30  this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
31  this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
32  }
33 
35  public Task<InstanceUser> Create(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Create<InstanceUser, InstanceUser>(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken);
36 
38  public Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceUser, instanceUser.UserId.Value), instance.Id, cancellationToken);
39 
41  public Task<InstanceUser> Read(CancellationToken cancellationToken) => apiClient.Read<InstanceUser>(Routes.InstanceUser, instance.Id, cancellationToken);
42 
44  public Task<InstanceUser> Update(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Update<InstanceUser, InstanceUser>(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken);
45 
47  public Task<IReadOnlyList<InstanceUser>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<InstanceUser>>(Routes.ListRoute(Routes.InstanceUser), instance.Id, cancellationToken);
48 
50  public Task<InstanceUser> GetId(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Read<InstanceUser>(Routes.SetID(Routes.InstanceUser, instanceUser?.UserId ?? throw new ArgumentNullException(nameof(instanceUser))), instance.Id, cancellationToken);
51  }
52 }
readonly IApiClient apiClient
The IApiClient for the InstanceUserClient
+Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Threading;
4 using System.Threading.Tasks;
5 using Tgstation.Server.Api;
7 
8 namespace Tgstation.Server.Client.Components
9 {
12  {
17 
21  readonly Instance instance;
22 
28  public InstanceUserClient(IApiClient apiClient, Instance instance)
29  {
30  this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
31  this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
32  }
33 
35  public Task<InstanceUser> Create(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Create<InstanceUser, InstanceUser>(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken);
36 
38  public Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Delete(
39  Routes.SetID(
41  instanceUser.UserId ?? throw new ArgumentException("Missing instanceUser.UserId!", nameof(instanceUser))),
42  instance.Id,
43  cancellationToken);
44 
46  public Task<InstanceUser> Read(CancellationToken cancellationToken) => apiClient.Read<InstanceUser>(Routes.InstanceUser, instance.Id, cancellationToken);
47 
49  public Task<InstanceUser> Update(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Update<InstanceUser, InstanceUser>(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken);
50 
52  public Task<IReadOnlyList<InstanceUser>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<InstanceUser>>(Routes.ListRoute(Routes.InstanceUser), instance.Id, cancellationToken);
53 
55  public Task<InstanceUser> GetId(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Read<InstanceUser>(Routes.SetID(Routes.InstanceUser, instanceUser?.UserId ?? throw new ArgumentNullException(nameof(instanceUser))), instance.Id, cancellationToken);
56  }
57 }
readonly IApiClient apiClient
The IApiClient for the InstanceUserClient
readonly Instance instance
The Instance for the InstanceUserClient
diff --git a/_internal_2_server_information_8cs_source.html b/_internal_2_server_information_8cs_source.html index 15085fc51e..b6f7e29a18 100644 --- a/_internal_2_server_information_8cs_source.html +++ b/_internal_2_server_information_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ServerInformation.cs
-Go to the documentation of this file.
1 using System.Collections.Generic;
2 
3 namespace Tgstation.Server.Api.Models.Internal
4 {
8  public abstract class ServerInformation
9  {
13  public uint MinimumPasswordLength { get; set; }
14 
18  public uint InstanceLimit { get; set; }
19 
23  public uint UserLimit { get; set; }
24 
28  public ICollection<string> ValidInstancePaths { get; set; }
29  }
30 }
Base class for Models.ServerInformation.
+Go to the documentation of this file.
1 using System.Collections.Generic;
2 
3 namespace Tgstation.Server.Api.Models.Internal
4 {
8  public abstract class ServerInformation
9  {
13  public uint MinimumPasswordLength { get; set; }
14 
18  public uint InstanceLimit { get; set; }
19 
23  public uint UserLimit { get; set; }
24 
28  public ICollection<string>? ValidInstancePaths { get; set; }
29  }
30 }
Base class for Models.ServerInformation.
diff --git a/_irc_connection_string_builder_8cs_source.html b/_irc_connection_string_builder_8cs_source.html index a165d6b946..015e7102ef 100644 --- a/_irc_connection_string_builder_8cs_source.html +++ b/_irc_connection_string_builder_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
IrcConnectionStringBuilder.cs
-Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
5 
6 namespace Tgstation.Server.Api.Models
7 {
12  {
14  public override bool Valid => Address != null && Port.HasValue && Port != 0 && UseSsl.HasValue && (PasswordType.HasValue ^ Password == null);
15 
19  public string Address { get; set; }
20 
24  public ushort? Port { get; set; }
25 
29  public string Nickname { get; set; }
30 
34  public bool? UseSsl { get; set; }
35 
39  public IrcPasswordType? PasswordType { get; set; }
40 
44  public string Password { get; set; }
45 
50 
55  public IrcConnectionStringBuilder(string connectionString)
56  {
57  if (connectionString == null)
58  throw new ArgumentNullException(nameof(connectionString));
59  var splits = connectionString.Split(';');
60 
61  Address = splits[0];
62 
63  if (splits.Length < 2)
64  return;
65 
66  if (UInt16.TryParse(splits[1], out var port))
67  Port = port;
68 
69  if (splits.Length < 3)
70  return;
71 
72  Nickname = splits[2];
73 
74  if (splits.Length < 4)
75  return;
76 
77  if (Int32.TryParse(splits[3], out var intSsl))
78  UseSsl = Convert.ToBoolean(intSsl);
79 
80  if (splits.Length < 5)
81  return;
82  if (Enum.TryParse<IrcPasswordType>(splits[4], out var passwordType))
83  switch (passwordType)
84  {
85  case IrcPasswordType.NickServ:
86  case IrcPasswordType.Sasl:
87  case IrcPasswordType.Server:
88  PasswordType = passwordType;
89  break;
90  default:
91  break;
92  }
93 
94  if (splits.Length < 6)
95  return;
96 
97  var rest = new List<string>(splits);
98  rest.RemoveRange(0, 5);
99  Password = String.Join(";", rest);
100  }
101 
103  public override string ToString()
104  {
105  var sb = new StringBuilder();
106  sb.Append(Address);
107  sb.Append(';');
108  sb.Append(Port);
109  sb.Append(';');
110  sb.Append(Nickname);
111  sb.Append(';');
112  if(UseSsl.HasValue)
113  sb.Append(Convert.ToInt32(UseSsl.Value));
114  if (PasswordType.HasValue)
115  {
116  sb.Append(';');
117  sb.Append((int)PasswordType);
118  sb.Append(';');
119  sb.Append(Password);
120  }
121 
122  return sb.ToString();
123  }
124  }
125 }
+Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
5 
6 namespace Tgstation.Server.Api.Models
7 {
12  {
14  public override bool Valid => Address != null && Port.HasValue && Port != 0 && UseSsl.HasValue && (PasswordType.HasValue ^ Password == null);
15 
19  public string? Address { get; set; }
20 
24  public ushort? Port { get; set; }
25 
29  public string? Nickname { get; set; }
30 
34  public bool? UseSsl { get; set; }
35 
39  public IrcPasswordType? PasswordType { get; set; }
40 
44  public string? Password { get; set; }
45 
50 
55  public IrcConnectionStringBuilder(string connectionString)
56  {
57  if (connectionString == null)
58  throw new ArgumentNullException(nameof(connectionString));
59  var splits = connectionString.Split(';');
60 
61  Address = splits[0];
62 
63  if (splits.Length < 2)
64  return;
65 
66  if (UInt16.TryParse(splits[1], out var port))
67  Port = port;
68 
69  if (splits.Length < 3)
70  return;
71 
72  Nickname = splits[2];
73 
74  if (splits.Length < 4)
75  return;
76 
77  if (Int32.TryParse(splits[3], out var intSsl))
78  UseSsl = Convert.ToBoolean(intSsl);
79 
80  if (splits.Length < 5)
81  return;
82  if (Enum.TryParse<IrcPasswordType>(splits[4], out var passwordType))
83  switch (passwordType)
84  {
85  case IrcPasswordType.NickServ:
86  case IrcPasswordType.Sasl:
87  case IrcPasswordType.Server:
88  PasswordType = passwordType;
89  break;
90  default:
91  break;
92  }
93 
94  if (splits.Length < 6)
95  return;
96 
97  var rest = new List<string>(splits);
98  rest.RemoveRange(0, 5);
99  Password = String.Join(";", rest);
100  }
101 
103  public override string ToString()
104  {
105  var sb = new StringBuilder();
106  sb.Append(Address);
107  sb.Append(';');
108  sb.Append(Port);
109  sb.Append(';');
110  sb.Append(Nickname);
111  sb.Append(';');
112  if(UseSsl.HasValue)
113  sb.Append(Convert.ToInt32(UseSsl.Value));
114  if (PasswordType.HasValue)
115  {
116  sb.Append(';');
117  sb.Append((int)PasswordType);
118  sb.Append(';');
119  sb.Append(Password);
120  }
121 
122  return sb.ToString();
123  }
124  }
125 }
diff --git a/_method_not_supported_exception_8cs_source.html b/_method_not_supported_exception_8cs_source.html index ec267e8445..01df6f421b 100644 --- a/_method_not_supported_exception_8cs_source.html +++ b/_method_not_supported_exception_8cs_source.html @@ -94,14 +94,14 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
MethodNotSupportedException.cs
-Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
11  {
17  public MethodNotSupportedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  { }
19 
24  public MethodNotSupportedException(string message) : base(message) { }
25 
31  public MethodNotSupportedException(string message, Exception innerException) : base(message, innerException) { }
32  }
33 }
+Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
11  {
17  public MethodNotSupportedException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  { }
19 
24  public MethodNotSupportedException(string message) : base(message) { }
25 
31  public MethodNotSupportedException(string message, Exception innerException) : base(message, innerException) { }
32  }
33 }
+
MethodNotSupportedException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the MethodNotSupportedException .
MethodNotSupportedException(string message, Exception innerException)
Construct an MethodNotSupportedException with a message and innerException
MethodNotSupportedException(string message)
Construct an MethodNotSupportedException with a message
API related Exceptions.
Definition: ApiException.cs:10
-
MethodNotSupportedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the MethodNotSupportedException .
Occurs when the client tries to use a currently unsupported API
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
diff --git a/_r_e_a_d_m_e_8md_source.html b/_r_e_a_d_m_e_8md_source.html index 3c120c9c0d..85b5bee7cc 100644 --- a/_r_e_a_d_m_e_8md_source.html +++ b/_r_e_a_d_m_e_8md_source.html @@ -90,7 +90,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
README.md
-Go to the documentation of this file.
1 # tgstation-server v4:
2 
3 [![Build status](https://ci.appveyor.com/api/projects/status/7t1h7bvuha0p9j5f/branch/master?svg=true)](https://ci.appveyor.com/project/Cyberboss/tgstation-server-tools/branch/master) [![Build Status](https://travis-ci.org/tgstation/tgstation-server.svg?branch=master)](https://travis-ci.org/tgstation/tgstation-server) [![codecov](https://codecov.io/gh/tgstation/tgstation-server/branch/master/graph/badge.svg)](https://codecov.io/gh/tgstation/tgstation-server)
4 
5 [![GitHub license](https://img.shields.io/github/license/tgstation/tgstation-server.svg)](LICENSE) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tgstation/tgstation-server.svg)](http://isitmaintained.com/project/tgstation/tgstation-server "Average time to resolve an issue") [![NuGet version](https://img.shields.io/nuget/v/Tgstation.Server.Api.svg)](https://www.nuget.org/packages/Tgstation.Server.Api) [![NuGet version](https://img.shields.io/nuget/v/Tgstation.Server.Client.svg)](https://www.nuget.org/packages/Tgstation.Server.Client)
6 
7 [![forthebadge](http://forthebadge.com/images/badges/made-with-c-sharp.svg)](http://forthebadge.com) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a)
8 
9 [![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg)](http://forthebadge.com)
10 
11 This is a toolset to manage production BYOND servers. It includes the ability to update the server without having to stop or shutdown the server (the update will take effect on a "reboot" of the server) the ability start the server and restart it if it crashes, as well as systems for managing code and game files, and merging GitHub Pull Requests for test deployments.
12 
13 ### Legacy Servers
14 
15 Older server versions can be found in the V# branches of this repository. Note that V4 is nearly fully incompatible with existing installations. Only some static files may be copied over: https://github.com/tgstation/tgstation-server#static-files
16 
17 ## Setup
18 
19 ### Pre-Requisites
20 
21 - [ASP .NET Core Runtime (>= v3.1)](https://dotnet.microsoft.com/download/dotnet-core/current/runtime) (Choose the option to `Run Server Apps` for your system) If you plan to install tgstation-server as a Windows service, you should also ensure that your .NET Framework runtime version is >= v4.7.2 (Download can be found on same page). Ensure that the `dotnet` executable file is in your system's `PATH` variable (or that of the user's that will be running the server).
22 - A [MariaDB](https://downloads.mariadb.org/), MySQL, or [Microsoft SQL Server](https://www.microsoft.com/en-us/download/details.aspx?id=55994) database engine is required
23 
24 ### Installation
25 
26 1. [Download the latest V4 release .zip](https://github.com/tgstation/tgstation-server/releases/latest). The `ServerService` package will only work on Windows. Choose `ServerConsole` if that is not your target OS or you prefer not to use the Windows service.
27 2. Extract the .zip file to where you want the server to run from. Note the account running the server must have write and delete access to the `lib` subdirectory.
28 
29 #### Windows
30 
31 If you wish to install the TGS as a service, run `Tgstation.Server.Host.Service.exe`. It should prompt you to install it. Click `Yes` and accept a potential UAC elevation prompt and the setup wizard should run.
32 
33 #### Linux (Native)
34 
35 We recommend using Docker for Linux installations, see below. The content of this parent section may be skipped if you choose to do so.
36 
37 The following dependencies are required to run tgstation-server on Linux alongside the .NET Core runtime
38 
39 - libc6-i386
40 - libstdc++6:i386
41 - gcc-multilib (Only on 64-bit systems)
42 
43 Note that tgstation-server has only ever been tested on Linux via it's [docker environment](build/Dockerfile#L22). If you are having trouble with something in a native installation, or figure out a required workaround, please contact project maintainers so this documentation may be better updated.
44 
45 #### Docker (Linux)
46 
47 tgstation-server supports running in a docker container and is the recommended deployment method for Linux systems. The official image repository is located at https://hub.docker.com/r/tgstation/server. It can also be built locally by running `docker build . -f build/Dockerfile -t <your tag name>` in the repository root.
48 
49 To create a container run
50 ```sh
51 docker run \
52  -ti \ #start interactive for manual configuration
53  --restart=always \ #if you want maximum uptime
54  --network="host" \ #if your sql server is on the same machine
55  --name="tgs" \ #or whatever else you wanna call it
56  --cap-add=sys_nice \ #allows tgs to schedule DreamDaemon as a higher priority process
57  --init \ #reaps potential zombie processes
58  -p <tgs port>:80 \
59  -p 0.0.0.0:<public game port>:<public game port> \
60  -v /path/to/your/configfile/directory:/config_data \ #only if you want to use manual configuration
61  -v /path/to/store/instances:/tgs4_instances \
62  -v /path/to/your/log/folder:/tgs_logs \
63  tgstation/server:<release version> #replace this with <your tag name> if you built the image locally
64 ```
65 with any additional options you desire (i.e. You'll have to expose more game ports in order to host more than one instance).
66 
67 Note although `/app/lib` is specified as a volume mount point in the `Dockerfile`, unless you REALLY know what you're doing. Do not mount any volumes over this for fear of breaking your container.
68 
69 The configuration option `General:ValidInstancePaths` will be preconfigured to point to `/tgs4_instances`. It is recommended you don't change this.
70 
71 Note that this container is meant to be long running. Updates are handled internally as opposed to at the container level.
72 
73 If using manual configuration, before starting your container make sure the aforementioned `appsettings.Production.json` is setup properly. See below
74 
75 ### Configuring
76 
77 The first time you run TGS4 you should be prompted with a configuration wizard which will guide you through setting up your appsettings.Production.json
78 
79 This wizard will, generally, run whenever the server is launched without detecting the config json. Follow the instructions below to perform this process manually.
80 
81 #### Manual Configuration
82 
83 Create an `appsettings.Production.json` file next to `appsettings.json`. This will override the default settings in appsettings.json with your production settings. There are a few keys meant to be changed by hosts. Modifying any config files while the server is running will trigger a safe restart (Keeps DreamDaemon's running). Note these are all case-sensitive:
84 
85 - `General:MinimumPasswordLength`: Minimum password length requirement for database users
86 
87 - `General:ValidInstancePaths`: Array meant to limit the directories in which instances may be created.
88 
89 - `General:UserLimit`: Maximum number of users that may be created
90 
91 - `General:InstanceLimit`: Maximum number of instances that may be created
92 
93 - `General:GitHubAccessToken`: Specify a GitHub personal access token with no scopes here to highly mitigate the possiblity of 429 response codes from GitHub requests
94 
95 - `FileLogging:Directory`: Override the default directory where server logs are stored. Default is C:/ProgramData/tgstation-server/logs on Windows, /usr/share/tgstation-server/logs otherwise
96 
97 - `FileLogging:LogLevel`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting.
98 
99 - `Kestrel:Endpoints:Http:Url`: The URL (i.e. interface and ports) your application should listen on. General use case should be `http://localhost:<port>` for restricted local connections. See the Remote Access section for configuring public access to the World Wide Web. This doesn't need to be changed using the docker setup and should be mapped with the `-p` option instead
100 
101 - `Database:DatabaseType`: Can be one of `SqlServer`, `MariaDB`, `MySql`, or `Sqlite`.
102 
103 - `Database:MySqlServerVersion`: The version of MySql/MariaDB the database resides on, can be left as null for attempted auto detection. Used by the MySQL/MariaDB provider for selection of [certain features](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/blob/2.2.6/src/EFCore.MySql/Storage/Internal/ServerVersion.cs) ignore at your own risk. A string in the form `<major>.<minor>.<patch>`
104 
105 - `Database:ConnectionString`: Connection string for your database. Click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide ([You should probably use '127.0.0.1' instead of 'localhost'](https://stackoverflow.com/questions/19712307/mysql-localhost-127-0-0-1)).
106 
107 - `ControlPanel:Enable`: Enable the javascript based control panel to be served from the server via /index.html
108 
109 - `ControlPanel:AllowAnyOrigin`: Set the Access-Control-Allow-Origin header to * for all responses (also enables all headers and methods)
110 
111 - `ControlPanel:AllowedOrigins`: Set the Access-Control-Allow-Origin headers to this list of origins for all responses (also enables all headers and methods). This is overridden by `ControlPanel:AllowAnyOrigin`
112 
113 ### Database Configuration
114 
115 If using a MariaDB/MySQL server, our client library [recommends you set 'utf8mb4' as your default charset](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1-recommended-server-charset) disregard at your own risk.
116 
117 The user created for the application will need the privilege to create databases on the first run, do not create the database for it. Once the initial set of migrations is run, the create right may be revoked. The user should maintain DDL rights though for applying future migrations
118 
119 Note that the ratio of application installations to databases is 1:1. Do not attempt to share a database amongst multiple TGS installations.
120 
121 ### Starting
122 
123 For the Windows service version start the `tgstation-server-4` service. If it fails to start, check the Windows event log under Windows/Application for entries from tgstation-server-4 for errors.
124 
125 For the console version run `dotnet Tgstation.Server.Host.Console.dll` in the installation directory. The `tgs.bat` and `tgs.sh` shell scripts are shortcuts for this. If on Windows, you must do this as admin to give the server permission to install the required DirectX dependency for certain 512 BYOND versions as well as create symlinks.
126 
127 For the docker version run `docker start <your container name>`
128 
129 Test your server is running by visiting the local port in your browser. You should receive a 401 Unauthorized response (You may need to view the developer console). Otherwise an error page will be present.
130 
131 ### Stopping
132 
133 Note that the live detach for DreamDaemon servers is only supported for updates or restarts via the API at this time. Stopping tgstation-server will TERMINATE ALL CHILD DREAMDAEMON SERVERS.
134 
135 For the Windows service version stop the `tgstation-server-4` service
136 
137 For the console version press `Ctrl+C` or send a SIGQUIT to the ORIGINAL dotnet process
138 
139 For the docker version run `docker stop <your container name>`
140 
141 ## Integrating
142 
143 tgstation-server 4 currently REQUIRES the DMAPI to be integrated into any BYOND codebase which plans on being used by it. The integration process is a fairly simple set of code changes.
144 
145 1. Copy the [latest release of the DMAPI](https://github.com/tgstation/tgstation-server/releases) anywhere in your code base. `tgs.dm` can be seperated from the `tgs` folder, but do not modify or move the contents of the `tgs` folder
146 2. Modify your `.dme`(s) to include the `tgs.dm` and `tgs/includes.dm` files (ORDER OF APPEARANCE IS MANDATORY)
147 3. Follow the instructions in `tgs.dm` to integrate the API with your codebase.
148 
149 The DMAPI is fully backwards compatible and should function with any tgstation-server version to date. Updates can be performed in the same manner. Using the `TGS_EXTERNAL_CONFIGURATION` is recommended in order to make the process as easy as replacing `tgs.dm` and the `tgs` folder with a newer version
150 
151 ### Example
152 
153 Here is a bare minimum example project that implements the essential code changes for integrating the DMAPI
154 
155 Before `tgs.dm`:
156 ```dm
157 //Remember, every codebase is different, you probably have better methods for these defines than the ones given here
158 #define TGS_EXTERNAL_CONFIGURATION
159 #define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) var/global/##Name = ##Value
160 #define TGS_READ_GLOBAL(Name) global.##Name
161 #define TGS_WRITE_GLOBAL(Name, Value) global.##Name = ##Value
162 #define TGS_WORLD_ANNOUNCE(message) world << ##message
163 #define TGS_INFO_LOG(message) world.log << "TGS Info: [##message]"
164 #define TGS_ERROR_LOG(message) world.log << "TGS Error: [##message]"
165 #define TGS_NOTIFY_ADMINS(event) world.log << "TGS Admin Message: [##event]"
166 #define TGS_CLIENT_COUNT global.client_cout
167 #define TGS_PROTECT_DATUM(Path) // Leave blank if your codebase doesn't give administrators code reflection capabilities
168 ```
169 
170 Anywhere else:
171 ```dm
172 var/global/client_count = 0
173 
174 /world/New()
175  ..()
176  TgsNew()
177  TgsInitializationsComplete()
178 
179 /world/Reboot()
180  TgsReboot()
181  ..()
182 
183 /world/Topic()
184  TGS_TOPIC
185  ..()
186 
187 /client/New()
188  ..()
189  ++global.client_count
190 
191 /client/Del()
192  ..()
193  --global.client_count
194 ```
195 
196 ## Remote Access
197 
198 tgstation-server is an [ASP.Net Core](https://docs.microsoft.com/en-us/aspnet/core/) app based on the Kestrel web server. This section is meant to serve as a general use case overview, but the entire Kestrel configuration can be modified to your liking with the configuration JSON. See [the official documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel) for details.
199 
200 Exposing the builtin Kestrel server to the internet directly over HTTP is highly not reccommended due to the lack of security. The recommended way to expose tgstation-server to the internet is to host it through a reverse proxy with HTTPS support. Here are some step by step examples to achieve this for major web servers.
201 
202 System administrators will most likely have their own configuration plans, but here are some basic guides for beginners.
203 
204 Once complete, test that your configuration worked by visiting your proxy site from a browser on a different computer. You should recieve a 401 Unauthorized response.
205 
206 ### IIS (Reccommended for Windows)
207 
208 1. Acquire an HTTPS certificate. The easiet free way for Windows is [win-acme](https://github.com/PKISharp/win-acme) (requires you to set up the website first)
209 2. Install the [Web Platform Installer](https://www.microsoft.com/web/downloads/platform.aspx)
210 3. Open the web platform installer in the IIS Manager and install the Application Request Routing 3.0 module
211 4. Create a new website, bind it to HTTPS only with your chosen certificate and exposed port. The physical path won't matter since it won't be used. Use `Require Server Name Indication` if you want to limit requests to a specific URL prefix.
212 5. Close and reopen the IIS Manager
213 5. Open the site and navigate to the `URL Rewrite` module
214 6. In the `Actions` Pane on the right click `Add Rule(s)...`
215 7. For the rule template, select `Reverse Proxy` under `Inbound and Outbound Rules` and click `OK`
216 8. You may get a prompt about enabling proxy functionality. Click `OK`
217 9. In the window that appears set the `Inbound Rules` textbox to the URL of your tgstation-server i.e. `http://localhost:5000`. Ensure `Enable SSL Offloading` is checked, then click `OK`
218 
219 ### Caddy (Reccommended for Linux, or those unfamilar with configuring NGINX or Apache)
220 
221 1. Setup a basic website configuration. Instructions on how to do so are out of scope.
222 2. In your Caddyfile, under a server entry, add the following (replace 8080 with the port TGS is hosted on):
223 ```
224 proxy /tgs localhost:8080 {
225  transparent
226 }
227 ```
228 
229 See https://caddyserver.com/docs/proxy
230 
231 ### NGINX (Reccommended for Linux)
232 
233 1. Setup a basic website configuration. Instructions on how to do so are out of scope.
234 2. Acquire an HTTPS certificate, likely via Let's Encrypt, and configure NGINX to use it.
235 3. Setup a path under a server like the following (replace 8080 with the port TGS is hosted on):
236 ```
237 location /tgs {
238  proxy_pass http://127.0.0.1:8080;
239  break;
240 }
241 ```
242 
243 See https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
244 
245 ### Apache
246 
247 1. Ensure the `mod_proxy` extension is installed.
248 2. Setup a basic website configuration. Instructions on how to do so are out of scope.
249 3. Acquire an HTTPS certificate, likely via Let's Encrypt, and configure Apache to use it.
250 4. Under a VirtualHost entry, setup the following (replace 8080 with the port TGS is hosted on):
251 ```
252 ProxyPass / http://127.0.0.1:8080
253 ProxyPassReverse / http://127.0.0.1:8080
254 ```
255 
256 See https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
257 
258 Example VirtualHost Entry
259 ```
260 <IfModule mod_ssl.c>
261 <VirtualHost *:443>
262  ServerName tgs_subdomain.example.com
263 
264  SSLEngine on
265  SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
266  SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
267 
268  ProxyPass / http://127.0.0.1:8080/
269  ProxyPassReverse / http://127.0.0.1:8080/
270 </VirtualHost>
271 </IfModule>
272 ```
273 
274 ## Usage
275 
276 tgstation-server v4 is controlled via a RESTful HTTP json API. Documentation on this API can be found [here](https://tgstation.github.io/tgstation-server/api.html). This section serves to document the concepts of the server. The API is versioned separately from the release version. A specification for it can be found in the api-vX.X.X git releases/tags.
277 
278 ### Users
279 
280 All actions apart from logging in must be taken by a user. TGS installs with one default user whose credentials can be found [here](src/Tgstation.Server.Api/Models/User.cs). It is recommended to disable this user ASAP as it is used to create Jobs that are started by the server itself. If access to all users is lost, the default user can be reset using the `Database:ResetAdminPassword` configuration setting.
281 
282 Users can be enabled/disabled and have a very granular set of rights associated to them that determine the actions they are allowed to take (i.e. Modify the user list or create instances). Users can be _database based_ or _system based_. Database users are your standard web users with a username and password. System users, on the otherhand, are authenticated with the host OS. These users cannot have their password or names changed by TGS as they are managed by the system (and in reverse, login tokens don't expire when their password changes). The benefit to having these users is it allows the use of system ACLs for static file control. More on that later.
283 
284 ### Instances
285 
286 A TGS deployment is made up with a set of instances, which each represent a production BYOND server. As many instances as desired can be created. Be aware, however, due to the nature of BYOND, this will quickly result in system resource exhaustion.
287 
288 An instance is stored in a single folder anywhere on a system and is made up of several components: The source code git repository, the BYOND installations, the compiler, the watchdog, chat bots, and static file management systems.
289 
290 ##### Instance Users
291 
292 All users with access to an instance have an InstanceUser object associated with the two that defines more rights specific to that instance (i.e. Deploy code, modify bots, edit other InstanceUsers).
293 
294 #### Repository
295 
296 The `Repository` folder is a git repository containing the code of the game you wish to host. It can be cloned from any public or private remote repository and has capabilities to affect changes back to it. All the standard benefits of git are utilized (i.e. check out any revision or reference).
297 
298 Additional features become available if the remote repository is hosted on https://github.com/. Namely the Test Merge feature, which allows you to take a pull request opened on the repository and compile it into a game deployment for testing. Information about test merges is available in game via the DMAPI and via the main API as well.
299 
300 Manual operations on the repository while an instance is running may lead to git data corruption. Thankfully, it's simple enough to delete and reclone the repository via the API.
301 
302 #### Byond
303 
304 The `Byond` folder contains installations of [BYOND](https://secure.byond.com/) versions. The version which is used by your game code can be changed on a whim (Note that only versions >= 511.1385 have been thouroughly tested. Lower versions should work but if one doesn't function, please open an issue report) and the server will take care of installing it.
305 
306 #### Compiler
307 
308 The compiler deploys code from the `Repository` folder to the `Game` folder and compiles it either by autodetecting the `.dme` or having it set by configuration. Several other step are also run such as validating the DMAPI version and creating symlinks for static files are done at this point. The compiler also applies server side code modifications and duplicates compiled code for the watchdog as well (See following section).
309 
310 #### Watchdog
311 
312 The watchdog is responsible for starting and keeping your server running. It functions by launching two servers which are hot-swapped on `/world/Reboot`s and during crashes to prevent downtime. This hot swapping feature is also what allows TGS to deploy updates to live servers without bringing them down.
313 
314 DreamDaemon can be finicky and will crash with several high load games or bad DM code. The watchdog has several failure prevention methods to keep at least one server running while these issues are sorted out.
315 
316 #### Chat Bots
317 
318 TGS supports creating infinite chat bots for notifying staff or players of things like code deployments and uptime in. Currently the following providers are supported
319 
320 - Internet Relay Chat (IRC)
321 - Discord
322 
323 More can be added by providing a new implementation of the [IProvider](src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs) interface
324 
325 Bots have a set of built-in commands that can be triggered via `!tgs`, mentioning, or private messaging them. Along with these, custom commands can be defined using the DMAPI by creating a subtype of the `/datum/tgs_chat_command` type (See `tgs.dm` for details). Invocation for custom commands can be restricted to certain channels.
326 
327 #### Static Files
328 
329 All files in game code deployments are considered transient by default, meaning when new code is deployed, changes will be lost. Static files allow you to specify which files and folders stick around throughout all deployments.
330 
331 The `StaticFiles` folder contains 3 root folders which cannot be deleted and operate under special rules
332  - `CodeModifications`
333  - `EventScripts`
334  - `GameStaticFiles`
335 
336 These files can be modified either in host mode or system user mode. In host mode, TGS itself is responsible for reading and writing the files. In system user mode read and write actions are performed using the system account of the logged on User, enabling the use of ACLs to control access to files. Database users will not be able to use the static file system if this mode is configured for an instance.
337 
338 This folder may be freely modified manually just beware this may cause in-progress deployments to error if done on Windows systems.
339 
340 #### CodeModifications
341 
342 When a deployment is made by the compiler, all the contents of this folder are copied over the repository contents. Then one of two code change modes are selected based on the prescense of certain files.
343 
344 If `<target dme>.dm` is present, that .dme will be used instead of the repository's `.dme`
345 
346 Otherwise the files `HeadInclude.dm` and `TailInclude.dm` are searched for and added as include lines to the top and bottom of the target `.dme` repsectively if they exist. These files can contain any valid DreamMaker code (Including `#include`ing other `.dm` files!) allowing you to modify the a repository's code on a per instance basis
347 
348 #### EventScripts
349 
350 This folder can contain anything. But, when certain events occur in the instance, TGS will look here for `.bat` or `.sh` files with the same name and run those with corresponding arguments. List of supported events can be found [here](src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs#L28) (subject to expansion) list of event parameters can be found [here](src/Tgstation.Server.Host/Components/EventType.cs)
351 
352 #### GameStaticFiles
353 
354 Any files and folders contained in this root level of this folder will be symbolically linked to all deployments at the time they are created. This allows persistent game data (BYOND `.sav`s or code configuration files for example) to persist across all deployments. This folder contains a .tgsignore file which can be used to prevent symlinks from being generated by entering the names of files and folders (1 per line)
355 
356 ### Updating
357 
358 TGS 4 can self update without stopping your DreamDaemon servers. Any V4 release made to this repository is bound by a contract that allows changes of the runtime assemblies without stopping your servers. Database migrations are automatically applied as well. Because of this REVERTING TO LOWER VERSIONS IS NOT OFFICIALLY SUPPORTED, do so at your own risk (check changes made to `/src/Tgstation.Server.Host/Models/Migrations`).
359 
360 ### Clients
361 
362 Here are tools for interacting with the TGS 4 web API
363 
364 - [tgstation-server-control-panel]: Official client and included with the server. A react web app for using tgstation-server.
365 - [Tgstation.Server.ControlPanel](https://github.com/tgstation/Tgstation.Server.ControlPanel): Official client. A cross platform GUI for using tgstation-server
366 - [Tgstation.Server.Client](https://www.nuget.org/packages/Tgstation.Server.Client): A nuget .NET Standard 2.0 TAP based library for communicating with tgstation-server
367 - [Tgstation.Server.Api](https://www.nuget.org/packages/Tgstation.Server.Api): A nuget .NET Standard 2.0 library containing API definitions for tgstation-server
368 - [Postman](https://www.getpostman.com/): This repository contains [TGS.postman_collection.json](tools/TGS.postman_collection.json) which is used during development for testing. Contains example requests for all endpoints but takes some knowledge to use (Note that the pre-request script is configured to login the default admin user for every request)
369 
370 Contact project maintainers to get your client added to this list
371 
372 ## Backup/Restore
373 
374 Note that tgstation-server is NOT a backup solution, the onus is on the server runners.
375 
376 The `Repository` folder should be backed up on the remote server, do not rely on the instance copy to store changes.
377 
378 The `BYOND` and `Game` folders should never be backed up due to being intertwined with instance data.
379 
380 The `Configuration` folder should be fully backed up.
381 
382 The database should be fully backed up.
383 
384 To restore an installation from backups, first restore the instance `Configuration` folder in its new home. Then restore the database, modifying the `Path` column in the `Instances` table where necessary to point to the new instances. Then start the server pointed at the new database.
385 
386 Should you end up with a lost database for some reason or want to reattach a detached instance you can reattach an existing folder by creating an empty file named `TGS4_ALLOW_INSTANCE_ATTACH` inside it (This is automatically created when detaching instances). Then create a new instance with that path, this will bypass the empty folder check. Note that this will not restore things such as user permissions, server config options, or deployment metadata. Those must be reconfigured manually.
387 
388 ## Troubleshooting
389 
390 Feel free to ask for help at the coderbus discord in \#hosting-questions: https://discord.gg/Vh8TJp9. Cyberboss#8246 can answer most questions.
391 
392 ## Contributing
393 
394 * See [CONTRIBUTING.md](.github/CONTRIBUTING.md)
395 
396 ## Licensing
397 
398 * The DMAPI for the project is licensed under the MIT license.
399 * The /tg/station 13 icon is licensed under [Creative Commons 3.0 BY-SA](http://creativecommons.org/licenses/by-sa/3.0/).
400 * The remainder of the project is licensed under [GNU AGPL v3](http://www.gnu.org/licenses/agpl-3.0.html)
401 
402 See the files in the `/src/DMAPI` tree for the MIT license
+Go to the documentation of this file.
1 # tgstation-server v4:
2 
3 [![Build status](https://ci.appveyor.com/api/projects/status/7t1h7bvuha0p9j5f/branch/master?svg=true)](https://ci.appveyor.com/project/Cyberboss/tgstation-server-tools/branch/master) [![Build Status](https://travis-ci.org/tgstation/tgstation-server.svg?branch=master)](https://travis-ci.org/tgstation/tgstation-server) [![codecov](https://codecov.io/gh/tgstation/tgstation-server/branch/master/graph/badge.svg)](https://codecov.io/gh/tgstation/tgstation-server)
4 
5 [![GitHub license](https://img.shields.io/github/license/tgstation/tgstation-server.svg)](LICENSE) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tgstation/tgstation-server.svg)](http://isitmaintained.com/project/tgstation/tgstation-server "Average time to resolve an issue") [![NuGet version](https://img.shields.io/nuget/v/Tgstation.Server.Api.svg)](https://www.nuget.org/packages/Tgstation.Server.Api) [![NuGet version](https://img.shields.io/nuget/v/Tgstation.Server.Client.svg)](https://www.nuget.org/packages/Tgstation.Server.Client)
6 
7 [![forthebadge](http://forthebadge.com/images/badges/made-with-c-sharp.svg)](http://forthebadge.com) [![forinfinityandbyond](https://user-images.githubusercontent.com/5211576/29499758-4efff304-85e6-11e7-8267-62919c3688a9.gif)](https://www.reddit.com/r/SS13/comments/5oplxp/what_is_the_main_problem_with_byond_as_an_engine/dclbu1a)
8 
9 [![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://forthebadge.com) [![forthebadge](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg)](http://forthebadge.com)
10 
11 This is a toolset to manage production BYOND servers. It includes the ability to update the server without having to stop or shutdown the server (the update will take effect on a "reboot" of the server) the ability start the server and restart it if it crashes, as well as systems for managing code and game files, and merging GitHub Pull Requests for test deployments.
12 
13 ### Legacy Servers
14 
15 Older server versions can be found in the V# branches of this repository. Note that V4 is nearly fully incompatible with existing installations. Only some static files may be copied over: https://github.com/tgstation/tgstation-server#static-files
16 
17 ## Setup
18 
19 ### Pre-Requisites
20 
21 - [ASP .NET Core Runtime (>= v3.1)](https://dotnet.microsoft.com/download/dotnet-core/current/runtime) (Choose the option to `Run Server Apps` for your system) If you plan to install tgstation-server as a Windows service, you should also ensure that your .NET Framework runtime version is >= v4.7.2 (Download can be found on same page). Ensure that the `dotnet` executable file is in your system's `PATH` variable (or that of the user's that will be running the server).
22 - A [MariaDB](https://downloads.mariadb.org/), MySQL, or [Microsoft SQL Server](https://www.microsoft.com/en-us/download/details.aspx?id=55994) database engine is required
23 
24 ### Installation
25 
26 1. [Download the latest V4 release .zip](https://github.com/tgstation/tgstation-server/releases/latest). The `ServerService` package will only work on Windows. Choose `ServerConsole` if that is not your target OS or you prefer not to use the Windows service.
27 2. Extract the .zip file to where you want the server to run from. Note the account running the server must have write and delete access to the `lib` subdirectory.
28 
29 #### Windows
30 
31 If you wish to install the TGS as a service, run `Tgstation.Server.Host.Service.exe`. It should prompt you to install it. Click `Yes` and accept a potential UAC elevation prompt and the setup wizard should run.
32 
33 #### Linux (Native)
34 
35 We recommend using Docker for Linux installations, see below. The content of this parent section may be skipped if you choose to do so.
36 
37 The following dependencies are required to run tgstation-server on Linux alongside the .NET Core runtime
38 
39 - libc6-i386
40 - libstdc++6:i386
41 - libssl1.0.0
42 - gcc-multilib (Only on 64-bit systems)
43 
44 Note that tgstation-server has only ever been tested on Linux via it's [docker environment](build/Dockerfile#L22). If you are having trouble with something in a native installation, or figure out a required workaround, please contact project maintainers so this documentation may be better updated.
45 
46 #### Docker (Linux)
47 
48 tgstation-server supports running in a docker container and is the recommended deployment method for Linux systems. The official image repository is located at https://hub.docker.com/r/tgstation/server. It can also be built locally by running `docker build . -f build/Dockerfile -t <your tag name>` in the repository root.
49 
50 To create a container run
51 ```sh
52 docker run \
53  -ti \ #start interactive for manual configuration
54  --restart=always \ #if you want maximum uptime
55  --network="host" \ #if your sql server is on the same machine
56  --name="tgs" \ #or whatever else you wanna call it
57  --cap-add=sys_nice \ #allows tgs to schedule DreamDaemon as a higher priority process
58  --init \ #reaps potential zombie processes
59  -p <tgs port>:80 \
60  -p 0.0.0.0:<public game port>:<public game port> \
61  -v /path/to/your/configfile/directory:/config_data \ #only if you want to use manual configuration
62  -v /path/to/store/instances:/tgs4_instances \
63  -v /path/to/your/log/folder:/tgs_logs \
64  tgstation/server:<release version> #replace this with <your tag name> if you built the image locally
65 ```
66 with any additional options you desire (i.e. You'll have to expose more game ports in order to host more than one instance).
67 
68 Note although `/app/lib` is specified as a volume mount point in the `Dockerfile`, unless you REALLY know what you're doing. Do not mount any volumes over this for fear of breaking your container.
69 
70 The configuration option `General:ValidInstancePaths` will be preconfigured to point to `/tgs4_instances`. It is recommended you don't change this.
71 
72 Note that this container is meant to be long running. Updates are handled internally as opposed to at the container level.
73 
74 If using manual configuration, before starting your container make sure the aforementioned `appsettings.Production.json` is setup properly. See below
75 
76 ### Configuring
77 
78 The first time you run TGS4 you should be prompted with a configuration wizard which will guide you through setting up your appsettings.Production.json
79 
80 This wizard will, generally, run whenever the server is launched without detecting the config json. Follow the instructions below to perform this process manually.
81 
82 #### Manual Configuration
83 
84 Create an `appsettings.Production.json` file next to `appsettings.json`. This will override the default settings in appsettings.json with your production settings. There are a few keys meant to be changed by hosts. Modifying any config files while the server is running will trigger a safe restart (Keeps DreamDaemon's running). Note these are all case-sensitive:
85 
86 - `General:MinimumPasswordLength`: Minimum password length requirement for database users
87 
88 - `General:ValidInstancePaths`: Array meant to limit the directories in which instances may be created.
89 
90 - `General:UserLimit`: Maximum number of users that may be created
91 
92 - `General:InstanceLimit`: Maximum number of instances that may be created
93 
94 - `General:GitHubAccessToken`: Specify a GitHub personal access token with no scopes here to highly mitigate the possiblity of 429 response codes from GitHub requests
95 
96 - `FileLogging:Directory`: Override the default directory where server logs are stored. Default is C:/ProgramData/tgstation-server/logs on Windows, /usr/share/tgstation-server/logs otherwise
97 
98 - `FileLogging:LogLevel`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting.
99 
100 - `Kestrel:Endpoints:Http:Url`: The URL (i.e. interface and ports) your application should listen on. General use case should be `http://localhost:<port>` for restricted local connections. See the Remote Access section for configuring public access to the World Wide Web. This doesn't need to be changed using the docker setup and should be mapped with the `-p` option instead
101 
102 - `Database:DatabaseType`: Can be one of `SqlServer`, `MariaDB`, `MySql`, or `Sqlite`.
103 
104 - `Database:MySqlServerVersion`: The version of MySql/MariaDB the database resides on, can be left as null for attempted auto detection. Used by the MySQL/MariaDB provider for selection of [certain features](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/blob/2.2.6/src/EFCore.MySql/Storage/Internal/ServerVersion.cs) ignore at your own risk. A string in the form `<major>.<minor>.<patch>`
105 
106 - `Database:ConnectionString`: Connection string for your database. Click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide ([You should probably use '127.0.0.1' instead of 'localhost'](https://stackoverflow.com/questions/19712307/mysql-localhost-127-0-0-1)).
107 
108 - `ControlPanel:Enable`: Enable the javascript based control panel to be served from the server via /index.html
109 
110 - `ControlPanel:AllowAnyOrigin`: Set the Access-Control-Allow-Origin header to * for all responses (also enables all headers and methods)
111 
112 - `ControlPanel:AllowedOrigins`: Set the Access-Control-Allow-Origin headers to this list of origins for all responses (also enables all headers and methods). This is overridden by `ControlPanel:AllowAnyOrigin`
113 
114 ### Database Configuration
115 
116 If using a MariaDB/MySQL server, our client library [recommends you set 'utf8mb4' as your default charset](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1-recommended-server-charset) disregard at your own risk.
117 
118 The user created for the application will need the privilege to create databases on the first run, do not create the database for it. Once the initial set of migrations is run, the create right may be revoked. The user should maintain DDL rights though for applying future migrations
119 
120 Note that the ratio of application installations to databases is 1:1. Do not attempt to share a database amongst multiple TGS installations.
121 
122 ### Starting
123 
124 For the Windows service version start the `tgstation-server-4` service. If it fails to start, check the Windows event log under Windows/Application for entries from tgstation-server-4 for errors.
125 
126 For the console version run `dotnet Tgstation.Server.Host.Console.dll` in the installation directory. The `tgs.bat` and `tgs.sh` shell scripts are shortcuts for this. If on Windows, you must do this as admin to give the server permission to install the required DirectX dependency for certain 512 BYOND versions as well as create symlinks.
127 
128 For the docker version run `docker start <your container name>`
129 
130 Test your server is running by visiting the local port in your browser. You should receive a 401 Unauthorized response (You may need to view the developer console). Otherwise an error page will be present.
131 
132 ### Stopping
133 
134 Note that the live detach for DreamDaemon servers is only supported for updates or restarts via the API at this time. Stopping tgstation-server will TERMINATE ALL CHILD DREAMDAEMON SERVERS.
135 
136 For the Windows service version stop the `tgstation-server-4` service
137 
138 For the console version press `Ctrl+C` or send a SIGQUIT to the ORIGINAL dotnet process
139 
140 For the docker version run `docker stop <your container name>`
141 
142 ## Integrating
143 
144 tgstation-server 4 currently REQUIRES the DMAPI to be integrated into any BYOND codebase which plans on being used by it. The integration process is a fairly simple set of code changes.
145 
146 1. Copy the [latest release of the DMAPI](https://github.com/tgstation/tgstation-server/releases) anywhere in your code base. `tgs.dm` can be seperated from the `tgs` folder, but do not modify or move the contents of the `tgs` folder
147 2. Modify your `.dme`(s) to include the `tgs.dm` and `tgs/includes.dm` files (ORDER OF APPEARANCE IS MANDATORY)
148 3. Follow the instructions in `tgs.dm` to integrate the API with your codebase.
149 
150 The DMAPI is fully backwards compatible and should function with any tgstation-server version to date. Updates can be performed in the same manner. Using the `TGS_EXTERNAL_CONFIGURATION` is recommended in order to make the process as easy as replacing `tgs.dm` and the `tgs` folder with a newer version
151 
152 ### Example
153 
154 Here is a bare minimum example project that implements the essential code changes for integrating the DMAPI
155 
156 Before `tgs.dm`:
157 ```dm
158 //Remember, every codebase is different, you probably have better methods for these defines than the ones given here
159 #define TGS_EXTERNAL_CONFIGURATION
160 #define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) var/global/##Name = ##Value
161 #define TGS_READ_GLOBAL(Name) global.##Name
162 #define TGS_WRITE_GLOBAL(Name, Value) global.##Name = ##Value
163 #define TGS_WORLD_ANNOUNCE(message) world << ##message
164 #define TGS_INFO_LOG(message) world.log << "TGS Info: [##message]"
165 #define TGS_ERROR_LOG(message) world.log << "TGS Error: [##message]"
166 #define TGS_NOTIFY_ADMINS(event) world.log << "TGS Admin Message: [##event]"
167 #define TGS_CLIENT_COUNT global.client_cout
168 #define TGS_PROTECT_DATUM(Path) // Leave blank if your codebase doesn't give administrators code reflection capabilities
169 ```
170 
171 Anywhere else:
172 ```dm
173 var/global/client_count = 0
174 
175 /world/New()
176  ..()
177  TgsNew()
178  TgsInitializationsComplete()
179 
180 /world/Reboot()
181  TgsReboot()
182  ..()
183 
184 /world/Topic()
185  TGS_TOPIC
186  ..()
187 
188 /client/New()
189  ..()
190  ++global.client_count
191 
192 /client/Del()
193  ..()
194  --global.client_count
195 ```
196 
197 ## Remote Access
198 
199 tgstation-server is an [ASP.Net Core](https://docs.microsoft.com/en-us/aspnet/core/) app based on the Kestrel web server. This section is meant to serve as a general use case overview, but the entire Kestrel configuration can be modified to your liking with the configuration JSON. See [the official documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel) for details.
200 
201 Exposing the builtin Kestrel server to the internet directly over HTTP is highly not reccommended due to the lack of security. The recommended way to expose tgstation-server to the internet is to host it through a reverse proxy with HTTPS support. Here are some step by step examples to achieve this for major web servers.
202 
203 System administrators will most likely have their own configuration plans, but here are some basic guides for beginners.
204 
205 Once complete, test that your configuration worked by visiting your proxy site from a browser on a different computer. You should recieve a 401 Unauthorized response.
206 
207 ### IIS (Reccommended for Windows)
208 
209 1. Acquire an HTTPS certificate. The easiet free way for Windows is [win-acme](https://github.com/PKISharp/win-acme) (requires you to set up the website first)
210 2. Install the [Web Platform Installer](https://www.microsoft.com/web/downloads/platform.aspx)
211 3. Open the web platform installer in the IIS Manager and install the Application Request Routing 3.0 module
212 4. Create a new website, bind it to HTTPS only with your chosen certificate and exposed port. The physical path won't matter since it won't be used. Use `Require Server Name Indication` if you want to limit requests to a specific URL prefix.
213 5. Close and reopen the IIS Manager
214 5. Open the site and navigate to the `URL Rewrite` module
215 6. In the `Actions` Pane on the right click `Add Rule(s)...`
216 7. For the rule template, select `Reverse Proxy` under `Inbound and Outbound Rules` and click `OK`
217 8. You may get a prompt about enabling proxy functionality. Click `OK`
218 9. In the window that appears set the `Inbound Rules` textbox to the URL of your tgstation-server i.e. `http://localhost:5000`. Ensure `Enable SSL Offloading` is checked, then click `OK`
219 
220 ### Caddy (Reccommended for Linux, or those unfamilar with configuring NGINX or Apache)
221 
222 1. Setup a basic website configuration. Instructions on how to do so are out of scope.
223 2. In your Caddyfile, under a server entry, add the following (replace 8080 with the port TGS is hosted on):
224 ```
225 proxy /tgs localhost:8080 {
226  transparent
227 }
228 ```
229 
230 See https://caddyserver.com/docs/proxy
231 
232 ### NGINX (Reccommended for Linux)
233 
234 1. Setup a basic website configuration. Instructions on how to do so are out of scope.
235 2. Acquire an HTTPS certificate, likely via Let's Encrypt, and configure NGINX to use it.
236 3. Setup a path under a server like the following (replace 8080 with the port TGS is hosted on):
237 ```
238 location /tgs {
239  proxy_pass http://127.0.0.1:8080;
240  break;
241 }
242 ```
243 
244 See https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
245 
246 ### Apache
247 
248 1. Ensure the `mod_proxy` extension is installed.
249 2. Setup a basic website configuration. Instructions on how to do so are out of scope.
250 3. Acquire an HTTPS certificate, likely via Let's Encrypt, and configure Apache to use it.
251 4. Under a VirtualHost entry, setup the following (replace 8080 with the port TGS is hosted on):
252 ```
253 ProxyPass / http://127.0.0.1:8080
254 ProxyPassReverse / http://127.0.0.1:8080
255 ```
256 
257 See https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html
258 
259 Example VirtualHost Entry
260 ```
261 <IfModule mod_ssl.c>
262 <VirtualHost *:443>
263  ServerName tgs_subdomain.example.com
264 
265  SSLEngine on
266  SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
267  SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
268 
269  ProxyPass / http://127.0.0.1:8080/
270  ProxyPassReverse / http://127.0.0.1:8080/
271 </VirtualHost>
272 </IfModule>
273 ```
274 
275 ## Usage
276 
277 tgstation-server v4 is controlled via a RESTful HTTP json API. Documentation on this API can be found [here](https://tgstation.github.io/tgstation-server/api.html). This section serves to document the concepts of the server. The API is versioned separately from the release version. A specification for it can be found in the api-vX.X.X git releases/tags.
278 
279 ### Users
280 
281 All actions apart from logging in must be taken by a user. TGS installs with one default user whose credentials can be found [here](src/Tgstation.Server.Api/Models/User.cs). It is recommended to disable this user ASAP as it is used to create Jobs that are started by the server itself. If access to all users is lost, the default user can be reset using the `Database:ResetAdminPassword` configuration setting.
282 
283 Users can be enabled/disabled and have a very granular set of rights associated to them that determine the actions they are allowed to take (i.e. Modify the user list or create instances). Users can be _database based_ or _system based_. Database users are your standard web users with a username and password. System users, on the otherhand, are authenticated with the host OS. These users cannot have their password or names changed by TGS as they are managed by the system (and in reverse, login tokens don't expire when their password changes). The benefit to having these users is it allows the use of system ACLs for static file control. More on that later.
284 
285 ### Instances
286 
287 A TGS deployment is made up with a set of instances, which each represent a production BYOND server. As many instances as desired can be created. Be aware, however, due to the nature of BYOND, this will quickly result in system resource exhaustion.
288 
289 An instance is stored in a single folder anywhere on a system and is made up of several components: The source code git repository, the BYOND installations, the compiler, the watchdog, chat bots, and static file management systems.
290 
291 ##### Instance Users
292 
293 All users with access to an instance have an InstanceUser object associated with the two that defines more rights specific to that instance (i.e. Deploy code, modify bots, edit other InstanceUsers).
294 
295 #### Repository
296 
297 The `Repository` folder is a git repository containing the code of the game you wish to host. It can be cloned from any public or private remote repository and has capabilities to affect changes back to it. All the standard benefits of git are utilized (i.e. check out any revision or reference).
298 
299 Additional features become available if the remote repository is hosted on https://github.com/. Namely the Test Merge feature, which allows you to take a pull request opened on the repository and compile it into a game deployment for testing. Information about test merges is available in game via the DMAPI and via the main API as well.
300 
301 Manual operations on the repository while an instance is running may lead to git data corruption. Thankfully, it's simple enough to delete and reclone the repository via the API.
302 
303 #### Byond
304 
305 The `Byond` folder contains installations of [BYOND](https://secure.byond.com/) versions. The version which is used by your game code can be changed on a whim (Note that only versions >= 511.1385 have been thouroughly tested. Lower versions should work but if one doesn't function, please open an issue report) and the server will take care of installing it.
306 
307 #### Compiler
308 
309 The compiler deploys code from the `Repository` folder to the `Game` folder and compiles it either by autodetecting the `.dme` or having it set by configuration. Several other step are also run such as validating the DMAPI version and creating symlinks for static files are done at this point. The compiler also applies server side code modifications and duplicates compiled code for the watchdog as well (See following section).
310 
311 #### Watchdog
312 
313 The watchdog is responsible for starting and keeping your server running. It functions by launching two servers which are hot-swapped on `/world/Reboot`s and during crashes to prevent downtime. This hot swapping feature is also what allows TGS to deploy updates to live servers without bringing them down.
314 
315 DreamDaemon can be finicky and will crash with several high load games or bad DM code. The watchdog has several failure prevention methods to keep at least one server running while these issues are sorted out.
316 
317 #### Chat Bots
318 
319 TGS supports creating infinite chat bots for notifying staff or players of things like code deployments and uptime in. Currently the following providers are supported
320 
321 - Internet Relay Chat (IRC)
322 - Discord
323 
324 More can be added by providing a new implementation of the [IProvider](src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs) interface
325 
326 Bots have a set of built-in commands that can be triggered via `!tgs`, mentioning, or private messaging them. Along with these, custom commands can be defined using the DMAPI by creating a subtype of the `/datum/tgs_chat_command` type (See `tgs.dm` for details). Invocation for custom commands can be restricted to certain channels.
327 
328 #### Static Files
329 
330 All files in game code deployments are considered transient by default, meaning when new code is deployed, changes will be lost. Static files allow you to specify which files and folders stick around throughout all deployments.
331 
332 The `StaticFiles` folder contains 3 root folders which cannot be deleted and operate under special rules
333  - `CodeModifications`
334  - `EventScripts`
335  - `GameStaticFiles`
336 
337 These files can be modified either in host mode or system user mode. In host mode, TGS itself is responsible for reading and writing the files. In system user mode read and write actions are performed using the system account of the logged on User, enabling the use of ACLs to control access to files. Database users will not be able to use the static file system if this mode is configured for an instance.
338 
339 This folder may be freely modified manually just beware this may cause in-progress deployments to error if done on Windows systems.
340 
341 #### CodeModifications
342 
343 When a deployment is made by the compiler, all the contents of this folder are copied over the repository contents. Then one of two code change modes are selected based on the prescense of certain files.
344 
345 If `<target dme>.dm` is present, that .dme will be used instead of the repository's `.dme`
346 
347 Otherwise the files `HeadInclude.dm` and `TailInclude.dm` are searched for and added as include lines to the top and bottom of the target `.dme` repsectively if they exist. These files can contain any valid DreamMaker code (Including `#include`ing other `.dm` files!) allowing you to modify the a repository's code on a per instance basis
348 
349 #### EventScripts
350 
351 This folder can contain anything. But, when certain events occur in the instance, TGS will look here for `.bat` or `.sh` files with the same name and run those with corresponding arguments. List of supported events can be found [here](src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs#L28) (subject to expansion) list of event parameters can be found [here](src/Tgstation.Server.Host/Components/EventType.cs)
352 
353 #### GameStaticFiles
354 
355 Any files and folders contained in this root level of this folder will be symbolically linked to all deployments at the time they are created. This allows persistent game data (BYOND `.sav`s or code configuration files for example) to persist across all deployments. This folder contains a .tgsignore file which can be used to prevent symlinks from being generated by entering the names of files and folders (1 per line)
356 
357 ### Updating
358 
359 TGS 4 can self update without stopping your DreamDaemon servers. Any V4 release made to this repository is bound by a contract that allows changes of the runtime assemblies without stopping your servers. Database migrations are automatically applied as well. Because of this REVERTING TO LOWER VERSIONS IS NOT OFFICIALLY SUPPORTED, do so at your own risk (check changes made to `/src/Tgstation.Server.Host/Models/Migrations`).
360 
361 ### Clients
362 
363 Here are tools for interacting with the TGS 4 web API
364 
365 - [tgstation-server-control-panel]: Official client and included with the server. A react web app for using tgstation-server.
366 - [Tgstation.Server.ControlPanel](https://github.com/tgstation/Tgstation.Server.ControlPanel): Official client. A cross platform GUI for using tgstation-server
367 - [Tgstation.Server.Client](https://www.nuget.org/packages/Tgstation.Server.Client): A nuget .NET Standard 2.0 TAP based library for communicating with tgstation-server
368 - [Tgstation.Server.Api](https://www.nuget.org/packages/Tgstation.Server.Api): A nuget .NET Standard 2.0 library containing API definitions for tgstation-server
369 - [Postman](https://www.getpostman.com/): This repository contains [TGS.postman_collection.json](tools/TGS.postman_collection.json) which is used during development for testing. Contains example requests for all endpoints but takes some knowledge to use (Note that the pre-request script is configured to login the default admin user for every request)
370 
371 Contact project maintainers to get your client added to this list
372 
373 ## Backup/Restore
374 
375 Note that tgstation-server is NOT a backup solution, the onus is on the server runners.
376 
377 The `Repository` folder should be backed up on the remote server, do not rely on the instance copy to store changes.
378 
379 The `BYOND` and `Game` folders should never be backed up due to being intertwined with instance data.
380 
381 The `Configuration` folder should be fully backed up.
382 
383 The database should be fully backed up.
384 
385 To restore an installation from backups, first restore the instance `Configuration` folder in its new home. Then restore the database, modifying the `Path` column in the `Instances` table where necessary to point to the new instances. Then start the server pointed at the new database.
386 
387 Should you end up with a lost database for some reason or want to reattach a detached instance you can reattach an existing folder by creating an empty file named `TGS4_ALLOW_INSTANCE_ATTACH` inside it (This is automatically created when detaching instances). Then create a new instance with that path, this will bypass the empty folder check. Note that this will not restore things such as user permissions, server config options, or deployment metadata. Those must be reconfigured manually.
388 
389 ## Troubleshooting
390 
391 Feel free to ask for help at the coderbus discord in \#hosting-questions: https://discord.gg/Vh8TJp9. Cyberboss#8246 can answer most questions.
392 
393 ## Contributing
394 
395 * See [CONTRIBUTING.md](.github/CONTRIBUTING.md)
396 
397 ## Licensing
398 
399 * The DMAPI for the project is licensed under the MIT license.
400 * The /tg/station 13 icon is licensed under [Creative Commons 3.0 BY-SA](http://creativecommons.org/licenses/by-sa/3.0/).
401 * The remainder of the project is licensed under [GNU AGPL v3](http://www.gnu.org/licenses/agpl-3.0.html)
402 
403 See the files in the `/src/DMAPI` tree for the MIT license
-Go to the documentation of this file.
1 using Microsoft.Net.Http.Headers;
2 using System;
3 using System.Linq;
4 using System.Net.Http;
6 
7 namespace Tgstation.Server.Client
8 {
12  public sealed class RateLimitException : ApiException
13  {
17  public DateTimeOffset? RetryAfter { get; }
18 
24  public RateLimitException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
25  {
26  if (!responseMessage.Headers.TryGetValues(HeaderNames.RetryAfter, out var values))
27  return;
28 
29  var secondsString = values.FirstOrDefault();
30  if (UInt32.TryParse(secondsString, out var seconds))
31  RetryAfter = DateTimeOffset.Now.AddSeconds(seconds);
32  }
33 
37  public RateLimitException() { }
38 
44  public RateLimitException(string message, Exception innerException) : base(message, innerException) { }
45  }
46 }
+Go to the documentation of this file.
1 using Microsoft.Net.Http.Headers;
2 using System;
3 using System.Linq;
4 using System.Net.Http;
6 
7 namespace Tgstation.Server.Client
8 {
12  public sealed class RateLimitException : ApiException
13  {
17  public DateTimeOffset? RetryAfter { get; }
18 
24  public RateLimitException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
25  {
26  if (!responseMessage.Headers.TryGetValues(HeaderNames.RetryAfter, out var values))
27  return;
28 
29  var secondsString = values.FirstOrDefault();
30  if (UInt32.TryParse(secondsString, out var seconds))
31  RetryAfter = DateTimeOffset.Now.AddSeconds(seconds);
32  }
33 
37  public RateLimitException() { }
38 
44  public RateLimitException(string message, Exception innerException) : base(message, innerException) { }
45  }
46 }
Occurs when a GitHub rate limit occurs
API related Exceptions.
Definition: ApiException.cs:10
-
RateLimitException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the MethodNotSupportedException .
+
RateLimitException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the MethodNotSupportedException .
RateLimitException()
Construct an RateLimitException
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
diff --git a/_repository_controller_8cs_source.html b/_repository_controller_8cs_source.html index c9a8b43abf..21b7ef1fde 100644 --- a/_repository_controller_8cs_source.html +++ b/_repository_controller_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
RepositoryController.cs
-Go to the documentation of this file.
1 using Microsoft.AspNetCore.Mvc;
2 using Microsoft.EntityFrameworkCore;
3 using Microsoft.Extensions.Logging;
4 using Microsoft.Extensions.Options;
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using System.Linq.Expressions;
10 using System.Net;
11 using System.Reflection;
12 using System.Threading;
13 using System.Threading.Tasks;
14 using Tgstation.Server.Api;
24 
25 namespace Tgstation.Server.Host.Controllers
26 {
30  [Route(Routes.Repository)]
31  #pragma warning disable CA1506 // TODO: Decomplexify
32  public sealed class RepositoryController : ApiController
33  {
38 
43 
48 
53 
64  public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IGitHubClientFactory gitHubClientFactory, IJobManager jobManager, ILogger<RepositoryController> logger, IOptions<GeneralConfiguration> generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, true, true)
65  {
66  this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
67  this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
68  this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
69  generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
70  }
71 
72  async Task<bool> LoadRevisionInformation(Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action<Models.RevisionInformation> revInfoSink, CancellationToken cancellationToken)
73  {
74  var repoSha = repository.Head;
75 
76  IQueryable<Models.RevisionInformation> ApplyQuery(IQueryable<Models.RevisionInformation> query) => query
77  .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
78  .Include(x => x.CompileJobs)
79  .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy);
80 
81  var revisionInfo = await ApplyQuery(databaseContext.RevisionInformations).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
82 
83  // If the DB doesn't have it, check the local set
84  if (revisionInfo == default)
85  revisionInfo = databaseContext
87  .Local
88  .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
89  .FirstOrDefault();
90 
91  var needsDbUpdate = revisionInfo == default;
92  if (needsDbUpdate)
93  {
94  // needs insertion
95  revisionInfo = new Models.RevisionInformation
96  {
97  Instance = instance,
98  CommitSha = repoSha,
99  CompileJobs = new List<Models.CompileJob>(),
100  ActiveTestMerges = new List<RevInfoTestMerge>() // non null vals for api returns
101  };
102 
103  lock (databaseContext) // cleaner this way
104  databaseContext.RevisionInformations.Add(revisionInfo);
105  }
106 
107  revisionInfo.OriginCommitSha ??= lastOriginCommitSha;
108  if (revisionInfo.OriginCommitSha == null)
109  {
110  revisionInfo.OriginCommitSha = repoSha;
111  Logger.LogWarning(Components.Repository.Repository.OriginTrackingErrorTemplate, repoSha);
112  }
113 
114  revInfoSink?.Invoke(revisionInfo);
115  return needsDbUpdate;
116  }
117 
118  async Task<bool> PopulateApi(Repository model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, CancellationToken cancellationToken)
119  {
120  if (repository.IsGitHubRepository)
121  {
122  model.GitHubOwner = repository.GitHubOwner;
123  model.GitHubName = repository.GitHubRepoName;
124  }
125 
126  model.Origin = repository.Origin;
127  model.Reference = repository.Reference;
128 
129  // rev info stuff
130  Models.RevisionInformation revisionInfo = null;
131  var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, null, x => revisionInfo = x, cancellationToken).ConfigureAwait(false);
132  model.RevisionInformation = revisionInfo.ToApi();
133  return needsDbUpdate;
134  }
135 
144  [HttpPut]
145  [TgsAuthorize(RepositoryRights.SetOrigin)]
146  [ProducesResponseType(typeof(Repository), 201)]
147  [ProducesResponseType(410)]
148  public async Task<IActionResult> Create([FromBody] Repository model, CancellationToken cancellationToken)
149  {
150  if (model == null)
151  throw new ArgumentNullException(nameof(model));
152 
153  if (model.Origin == null)
154  return BadRequest(ErrorCode.RepoMissingOrigin);
155 
156  if (model.AccessUser == null ^ model.AccessToken == null)
157  return BadRequest(ErrorCode.RepoMismatchUserAndAccessToken);
158 
159  var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
160 
161  if (currentModel == default)
162  return StatusCode((int)HttpStatusCode.Gone);
163 
164  // normalize github urls
165  const string BadGitHubUrl = "://www.github.com/";
166  var uiOrigin = model.Origin.ToUpperInvariant();
167  var uiBad = BadGitHubUrl.ToUpperInvariant();
168  var uiGitHub = Components.Repository.Repository.GitHubUrl.ToUpperInvariant();
169  if (uiOrigin.Contains(uiBad, StringComparison.Ordinal))
170  model.Origin = uiOrigin.Replace(uiBad, uiGitHub, StringComparison.Ordinal);
171 
172  currentModel.AccessToken = model.AccessToken;
173  currentModel.AccessUser = model.AccessUser; // intentionally only these fields, user not allowed to change anything else atm
174  var cloneBranch = model.Reference;
175  var origin = model.Origin;
176 
177  var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
178 
179  if (repoManager.CloneInProgress)
180  return Conflict(new ErrorMessage(ErrorCode.RepoCloning));
181 
182  if(repoManager.InUse)
183  return Conflict(new ErrorMessage(ErrorCode.RepoBusy));
184 
185  using var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false);
186 
187  // clone conflict
188  if (repo != null)
189  return Conflict(new ErrorMessage(ErrorCode.RepoExists));
190 
191  var job = new Models.Job
192  {
193  Description = String.Format(CultureInfo.InvariantCulture, "Clone branch {1} of repository {0}", origin, cloneBranch ?? "master"),
194  StartedBy = AuthenticationContext.User,
195  CancelRightsType = RightsType.Repository,
196  CancelRight = (ulong)RepositoryRights.CancelClone,
198  };
199  var api = currentModel.ToApi();
200  await jobManager.RegisterOperation(job, async (paramJob, databaseContextFactory, progressReporter, ct) =>
201  {
202  using var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, currentModel.AccessUser, currentModel.AccessToken, progressReporter, ct).ConfigureAwait(false);
203  if (repos == null)
204  throw new JobException(ErrorCode.RepoExists);
205  var instance = new Models.Instance
206  {
207  Id = Instance.Id
208  };
209  await databaseContextFactory.UseContext(
210  async databaseContext =>
211  {
212  databaseContext.Instances.Attach(instance);
213  if (await PopulateApi(api, repos, databaseContext, instance, ct).ConfigureAwait(false))
214  await databaseContext.Save(ct).ConfigureAwait(false);
215  })
216  .ConfigureAwait(false);
217  }, cancellationToken).ConfigureAwait(false);
218 
219  api.Origin = model.Origin;
220  api.Reference = model.Reference;
221  api.ActiveJob = job.ToApi();
222 
223  return StatusCode((int)HttpStatusCode.Created, api);
224  }
225 
233  [HttpDelete]
234  [TgsAuthorize(RepositoryRights.Delete)]
235  [ProducesResponseType(typeof(Repository), 202)]
236  [ProducesResponseType(410)]
237  public async Task<IActionResult> Delete(CancellationToken cancellationToken)
238  {
239  var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
240 
241  if (currentModel == default)
242  return StatusCode((int)HttpStatusCode.Gone);
243 
244  currentModel.AccessToken = null;
245  currentModel.AccessUser = null;
246 
247  await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
248 
249  Logger.LogInformation("Instance {0} repository delete initiated by user {1}", Instance.Id, AuthenticationContext.User.Id);
250 
251  var job = new Models.Job
252  {
253  Description = "Delete repository",
254  StartedBy = AuthenticationContext.User,
256  };
257  var api = currentModel.ToApi();
258  await jobManager.RegisterOperation(job, (paramJob, databaseContextFactory, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false);
259  api.ActiveJob = job.ToApi();
260  return Accepted(api);
261  }
262 
271  [HttpGet]
272  [TgsAuthorize(RepositoryRights.Read)]
273  [ProducesResponseType(typeof(Repository), 200)]
274  [ProducesResponseType(typeof(Repository), 201)]
275  [ProducesResponseType(410)]
276  public async Task<IActionResult> Read(CancellationToken cancellationToken)
277  {
278  var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
279 
280  if (currentModel == default)
281  return StatusCode((int)HttpStatusCode.Gone, new ErrorMessage(ErrorCode.RepoMissing));
282 
283  var api = currentModel.ToApi();
284  var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
285 
286  if (repoManager.CloneInProgress)
287  return Conflict(new ErrorMessage(ErrorCode.RepoCloning));
288 
289  if (repoManager.InUse)
290  return Conflict(new ErrorMessage(ErrorCode.RepoBusy));
291 
292  using var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false);
293  if (repo != null && await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false))
294  {
295  // user may have fucked with the repo manually, do what we can
296  await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
297  return StatusCode((int)HttpStatusCode.Created, api);
298  }
299 
300  return Json(api);
301  }
302 
312  [HttpPost]
313  [TgsAuthorize(RepositoryRights.ChangeAutoUpdateSettings | RepositoryRights.ChangeCommitter | RepositoryRights.ChangeCredentials | RepositoryRights.ChangeTestMergeCommits | RepositoryRights.MergePullRequest | RepositoryRights.SetReference | RepositoryRights.SetSha | RepositoryRights.UpdateBranch)]
314  [ProducesResponseType(typeof(Repository), 200)]
315  [ProducesResponseType(typeof(Repository), 202)]
316  [ProducesResponseType(410)]
317  #pragma warning disable CA1502 // TODO: Decomplexify
318  #pragma warning disable CA1505
319  public async Task<IActionResult> Update([FromBody]Repository model, CancellationToken cancellationToken)
320  {
321  if (model == null)
322  throw new ArgumentNullException(nameof(model));
323 
324  if (model.AccessUser == null ^ model.AccessToken == null)
325  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchUserAndAccessToken));
326 
327  if (model.CheckoutSha != null && model.Reference != null)
328  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchShaAndReference));
329 
330  if (model.CheckoutSha != null && model.UpdateFromOrigin == true)
331  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchShaAndUpdate));
332 
333  if (model.NewTestMerges?.Any(x => model.NewTestMerges.Any(y => x != y && x.Number == y.Number)) == true)
334  return BadRequest(new ErrorMessage(ErrorCode.RepoDuplicateTestMerge));
335 
336  if (model.CommitterName?.Length == 0)
337  return BadRequest(new ErrorMessage(ErrorCode.RepoWhitespaceCommitterName));
338 
339  if (model.CommitterEmail?.Length == 0)
340  return BadRequest(new ErrorMessage(ErrorCode.RepoWhitespaceCommitterEmail));
341 
342  var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0;
343  var userRights = (RepositoryRights)AuthenticationContext.GetRight(RightsType.Repository);
344  if (newTestMerges && !userRights.HasFlag(RepositoryRights.MergePullRequest))
345  return Forbid();
346 
347  var currentModel = await DatabaseContext
349  .Where(x => x.InstanceId == Instance.Id)
350  .FirstOrDefaultAsync(cancellationToken)
351  .ConfigureAwait(false);
352 
353  if (currentModel == default)
354  return StatusCode((int)HttpStatusCode.Gone);
355 
356  bool CheckModified<T>(Expression<Func<Api.Models.Internal.RepositorySettings, T>> expression, RepositoryRights requiredRight)
357  {
358  var memberSelectorExpression = (MemberExpression)expression.Body;
359  var property = (PropertyInfo)memberSelectorExpression.Member;
360 
361  var newVal = property.GetValue(model);
362  if (newVal == null)
363  return false;
364  if (!userRights.HasFlag(requiredRight) && property.GetValue(currentModel) != newVal)
365  return true;
366 
367  property.SetValue(currentModel, newVal);
368  return false;
369  }
370 
371  if (CheckModified(x => x.AccessToken, RepositoryRights.ChangeCredentials)
372  || CheckModified(x => x.AccessUser, RepositoryRights.ChangeCredentials)
373  || CheckModified(x => x.AutoUpdatesKeepTestMerges, RepositoryRights.ChangeAutoUpdateSettings)
374  || CheckModified(x => x.AutoUpdatesSynchronize, RepositoryRights.ChangeAutoUpdateSettings)
375  || CheckModified(x => x.CommitterEmail, RepositoryRights.ChangeCommitter)
376  || CheckModified(x => x.CommitterName, RepositoryRights.ChangeCommitter)
377  || CheckModified(x => x.PushTestMergeCommits, RepositoryRights.ChangeTestMergeCommits)
378  || CheckModified(x => x.ShowTestMergeCommitters, RepositoryRights.ChangeTestMergeCommits)
379  || CheckModified(x => x.PostTestMergeComment, RepositoryRights.ChangeTestMergeCommits)
380  || (model.UpdateFromOrigin == true && !userRights.HasFlag(RepositoryRights.UpdateBranch)))
381  return Forbid();
382 
383  if (currentModel.AccessToken?.Length == 0 && currentModel.AccessUser?.Length == 0)
384  {
385  // setting an empty string clears everything
386  currentModel.AccessUser = null;
387  currentModel.AccessToken = null;
388  }
389 
390  var canRead = userRights.HasFlag(RepositoryRights.Read);
391 
392  var api = canRead ? currentModel.ToApi() : new Repository();
393  var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
394 
395  if (canRead)
396  {
397  if (repoManager.CloneInProgress)
398  return Conflict(new ErrorMessage(ErrorCode.RepoCloning));
399 
400  if (repoManager.InUse)
401  return Conflict(new ErrorMessage(ErrorCode.RepoBusy));
402 
403  using var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false);
404  if (repo == null)
405  return Conflict(new ErrorMessage(ErrorCode.RepoMissing));
406  await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false);
407 
408  if (model.Origin != null && model.Origin != repo.Origin)
409  return BadRequest(new ErrorMessage(ErrorCode.RepoCantChangeOrigin));
410  }
411 
412  // this is just db stuf so stow it away
413  await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
414 
415  // format the job description
416  string description = null;
417  if (model.UpdateFromOrigin == true)
418  if (model.Reference != null)
419  description = String.Format(CultureInfo.InvariantCulture, "Fetch and hard reset repository to origin/{0}", model.Reference);
420  else if (model.CheckoutSha != null)
421  description = String.Format(CultureInfo.InvariantCulture, "Fetch and checkout {0} in repository", model.CheckoutSha);
422  else
423  description = "Pull current repository reference";
424  else if (model.Reference != null || model.CheckoutSha != null)
425  description = String.Format(CultureInfo.InvariantCulture, "Checkout repository {0} {1}", model.Reference != null ? "reference" : "SHA", model.Reference ?? model.CheckoutSha);
426 
427  if (newTestMerges)
428  description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}",
429  description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T",
430  String.Join(", ", model.NewTestMerges.Select(x =>
431  String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number,
432  x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))),
433  description != null ? String.Empty : " in repository");
434 
435  if (description == null)
436  return Json(api); // no git changes
437 
438  var job = new Models.Job
439  {
440  Description = description,
441  StartedBy = AuthenticationContext.User,
442  Instance = Instance,
443  CancelRightsType = RightsType.Repository,
444  CancelRight = (ulong)RepositoryRights.CancelPendingChanges,
445  };
446 
447  // Time to access git, do it in a job
448  await jobManager.RegisterOperation(job, async (paramJob, databaseContextFactory, progressReporter, ct) =>
449  {
450  using var repo = await repoManager.LoadRepository(ct).ConfigureAwait(false);
451  if (repo == null)
452  throw new JobException(ErrorCode.RepoMissing);
453 
454  var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null;
455 
456  var startReference = repo.Reference;
457  var startSha = repo.Head;
458  string postUpdateSha = null;
459 
460  if (newTestMerges && !repo.IsGitHubRepository)
461  throw new JobException(ErrorCode.RepoUnsupportedTestMergeRemote);
462 
463  var committerName = currentModel.ShowTestMergeCommitters.Value
465  : currentModel.CommitterName;
466 
467  var hardResettingToOriginReference = model.UpdateFromOrigin == true && model.Reference != null;
468 
469  var numSteps = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0) + (!modelHasShaOrReference ? 2 : (hardResettingToOriginReference ? 3 : 1));
470  var doneSteps = 0;
471 
472  Action<int> NextProgressReporter()
473  {
474  var tmpDoneSteps = doneSteps;
475  ++doneSteps;
476  return progress => progressReporter((progress + (100 * tmpDoneSteps)) / numSteps);
477  }
478 
479  progressReporter(0);
480 
481  // get a base line for where we are
482  Models.RevisionInformation lastRevisionInfo = null;
483 
484  var attachedInstance = new Models.Instance
485  {
486  Id = Instance.Id
487  };
488 
489  Task CallLoadRevInfo(Models.TestMerge testMergeToAdd = null, string lastOriginCommitSha = null) => databaseContextFactory
490  .UseContext(
491  async databaseContext =>
492  {
493  databaseContext.Instances.Attach(attachedInstance);
494  var needsUpdate = await LoadRevisionInformation(
495  repo,
496  databaseContext,
497  attachedInstance,
498  lastOriginCommitSha,
499  x => lastRevisionInfo = x,
500  ct)
501  .ConfigureAwait(false);
502 
503  if (testMergeToAdd != null)
504  {
505  // rev info may have already loaded the user
506  var mergedBy = databaseContext.Users.Local.FirstOrDefault(x => x.Id == AuthenticationContext.User.Id);
507  if (mergedBy == default)
508  {
509  mergedBy = new Models.User
510  {
512  };
513 
514  databaseContext.Users.Attach(mergedBy);
515  }
516 
517  testMergeToAdd.MergedBy = mergedBy;
518 
519  lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
520  {
521  TestMerge = testMergeToAdd
522  });
523  lastRevisionInfo.PrimaryTestMerge = testMergeToAdd;
524 
525  databaseContext.Users.Attach(testMergeToAdd.MergedBy);
526  }
527 
528  if (needsUpdate)
529  await databaseContext.Save(cancellationToken).ConfigureAwait(false);
530  });
531 
532  await CallLoadRevInfo().ConfigureAwait(false);
533 
534  // apply new rev info, tracking applied test merges
535  async Task UpdateRevInfo(Models.TestMerge testMergeToAdd = null)
536  {
537  var last = lastRevisionInfo;
538  await CallLoadRevInfo(testMergeToAdd, last.OriginCommitSha).ConfigureAwait(false);
539  lastRevisionInfo.ActiveTestMerges.AddRange(last.ActiveTestMerges);
540  }
541 
542  try
543  {
544  // fetch/pull
545  if (model.UpdateFromOrigin == true)
546  {
547  if (!repo.Tracking)
548  throw new JobException(ErrorCode.RepoReferenceRequired);
549  await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false);
550  doneSteps = 1;
551  if (!modelHasShaOrReference)
552  {
553  var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter(), ct).ConfigureAwait(false);
554  if (!fastForward.HasValue)
555  throw new JobException(ErrorCode.RepoMergeConflict);
556  await UpdateRevInfo().ConfigureAwait(false);
557  if (fastForward.Value)
558  {
559  lastRevisionInfo.OriginCommitSha = repo.Head;
560  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
561  postUpdateSha = repo.Head;
562  }
563  else
564  NextProgressReporter()(100);
565  }
566  }
567 
568  // checkout/hard reset
569  if (modelHasShaOrReference)
570  {
571  var validCheckoutSha =
572  model.CheckoutSha != null
573  && !repo.Head.StartsWith(model.CheckoutSha, StringComparison.OrdinalIgnoreCase);
574  var validCheckoutReference =
575  model.Reference != null
576  && !repo.Reference.Equals(model.Reference, StringComparison.OrdinalIgnoreCase);
577  if (validCheckoutSha || validCheckoutReference)
578  {
579  var committish = model.CheckoutSha ?? model.Reference;
580  var isSha = await repo.IsSha(committish, cancellationToken).ConfigureAwait(false);
581 
582  if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null))
583  throw new JobException(ErrorCode.RepoSwappedShaOrReference);
584 
585  await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false);
586  await CallLoadRevInfo().ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
587  }
588  else
589  NextProgressReporter()(100);
590 
591  if (hardResettingToOriginReference)
592  {
593  if (!repo.Tracking)
594  throw new JobException(ErrorCode.RepoReferenceNotTracking);
595  await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false);
596  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
597  await CallLoadRevInfo().ConfigureAwait(false);
598 
599  // repo head is on origin so force this
600  // will update the db if necessary
601  lastRevisionInfo.OriginCommitSha = repo.Head;
602  }
603  }
604 
605  // test merging
606  Dictionary<int, Octokit.PullRequest> prMap = null;
607  if (newTestMerges)
608  {
609  // bit of sanitization
610  foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision)))
611  I.PullRequestRevision = null;
612 
613  var gitHubClient = currentModel.AccessToken != null
614  ? gitHubClientFactory.CreateClient(currentModel.AccessToken)
615  : (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken)
616  ? gitHubClientFactory.CreateClient()
617  : gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken));
618 
619  var repoOwner = repo.GitHubOwner;
620  var repoName = repo.GitHubRepoName;
621 
622  // optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
623  Models.RevisionInformation revInfoWereLookingFor = null;
624  bool needToApplyRemainingPrs = true;
625  if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
626  {
627  // In order for this to work though we need the shas of all the commits
628  if (model.NewTestMerges.Any(x => x.PullRequestRevision == null))
629  prMap = new Dictionary<int, Octokit.PullRequest>();
630 
631  bool cantSearch = false;
632  foreach (var I in model.NewTestMerges)
633  {
634  if (I.PullRequestRevision != null)
635 #pragma warning disable CA1308 // Normalize strings to uppercase
636  I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant(); // ala libgit2
637 #pragma warning restore CA1308 // Normalize strings to uppercase
638  else
639  try
640  {
641  // retrieve the latest sha
642  var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
643  prMap.Add(I.Number, pr);
644  I.PullRequestRevision = pr.Head.Sha;
645  }
646  catch
647  {
648  cantSearch = true;
649  break;
650  }
651  }
652 
653  if (!cantSearch)
654  {
655  List<Models.RevisionInformation> dbPull = null;
656 
657  await databaseContextFactory.UseContext(
658  async databaseContext =>
659  dbPull = await databaseContext.RevisionInformations
660  .Where(x => x.Instance.Id == Instance.Id
661  && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
662  && x.ActiveTestMerges.Count <= model.NewTestMerges.Count
663  && x.ActiveTestMerges.Count > 0)
664  .Include(x => x.ActiveTestMerges)
665  .ThenInclude(x => x.TestMerge)
666  .ToListAsync(cancellationToken)
667  .ConfigureAwait(false))
668  .ConfigureAwait(false);
669 
670  // split here cause this bit has to be done locally
671  revInfoWereLookingFor = dbPull
672  .Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count
673  && x.ActiveTestMerges.Select(y => y.TestMerge)
674  .All(y => model.NewTestMerges.Any(z =>
675  y.Number == z.Number
676  && y.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal)
677  && (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))))
678  .FirstOrDefault();
679 
680  if (revInfoWereLookingFor == default && model.NewTestMerges.Count > 1)
681  {
682  // okay try to add at least SOME prs we've seen before
683  var search = model.NewTestMerges.ToList();
684 
685  var appliedTestMergeIds = new List<long>();
686 
687  Models.RevisionInformation lastGoodRevInfo = null;
688  do
689  {
690  foreach (var I in search)
691  {
692  revInfoWereLookingFor = dbPull
693  .Where(x => model.NewTestMerges.Any(z =>
694  x.PrimaryTestMerge.Number == z.Number
695  && x.PrimaryTestMerge.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal)
696  && (x.PrimaryTestMerge.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))
697  && x.ActiveTestMerges.Select(y => y.TestMerge).All(y => appliedTestMergeIds.Contains(y.Id)))
698  .FirstOrDefault();
699 
700  if (revInfoWereLookingFor != null)
701  {
702  lastGoodRevInfo = revInfoWereLookingFor;
703  appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge.Id);
704  search.Remove(I);
705  break;
706  }
707  }
708  }
709  while (revInfoWereLookingFor != null && search.Count > 0);
710 
711  revInfoWereLookingFor = lastGoodRevInfo;
712  needToApplyRemainingPrs = search.Count != 0;
713  if (needToApplyRemainingPrs)
714  model.NewTestMerges = search;
715  }
716  else if (revInfoWereLookingFor != null)
717  needToApplyRemainingPrs = false;
718  }
719  }
720 
721  if (revInfoWereLookingFor != null)
722  {
723  // goteem
724  await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken).ConfigureAwait(false);
725  lastRevisionInfo = revInfoWereLookingFor;
726  }
727 
728  if (needToApplyRemainingPrs)
729  {
730  foreach (var I in model.NewTestMerges)
731  {
732  Octokit.PullRequest pr = null;
733  string errorMessage = null;
734 
735  if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == I.Number))
736  throw new JobException(ErrorCode.RepoDuplicateTestMerge);
737 
738  Exception exception = null;
739  try
740  {
741  // load from cache if possible
742  if (prMap == null || !prMap.TryGetValue(I.Number, out pr))
743  pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
744  }
745  catch (Octokit.RateLimitExceededException ex)
746  {
747  // you look at your anonymous access and sigh
748  errorMessage = "REMOTE API ERROR: RATE LIMITED";
749  exception = ex;
750  }
751  catch (Octokit.AuthorizationException ex)
752  {
753  errorMessage = "REMOTE API ERROR: BAD CREDENTIALS";
754  exception = ex;
755  }
756  catch (Octokit.NotFoundException ex)
757  {
758  // you look at your shithub and sigh
759  errorMessage = "REMOTE API ERROR: PULL REQUEST NOT FOUND";
760  exception = ex;
761  }
762 
763  if (exception != null)
764  Logger.LogWarning("Error retrieving pull request metadata: {0}", exception);
765 
766  // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
767  if (I.PullRequestRevision == null && pr != null)
768  I.PullRequestRevision = pr.Head.Sha;
769 
770  var mergeResult = await repo.AddTestMerge(
771  I,
772  committerName,
773  currentModel.CommitterEmail,
774  currentModel.AccessUser,
775  currentModel.AccessToken,
776  NextProgressReporter(),
777  ct).ConfigureAwait(false);
778 
779  if (!mergeResult.HasValue)
780  throw new JobException(
781  ErrorCode.RepoTestMergeConflict,
782  new JobException(
783  $"Merge of PR #{I.Number} at {I.PullRequestRevision.Substring(0, 7)} conflicted!"));
784 
785  ++doneSteps;
786 
787  // MergedBy will be set later
788  var tm = new Models.TestMerge
789  {
790  Author = pr?.User.Login ?? errorMessage,
791  BodyAtMerge = pr?.Body ?? errorMessage ?? String.Empty,
792  MergedAt = DateTimeOffset.Now,
793  TitleAtMerge = pr?.Title ?? errorMessage ?? String.Empty,
794  Comment = I.Comment,
795  Number = I.Number,
796  PullRequestRevision = I.PullRequestRevision,
797  Url = pr?.HtmlUrl ?? errorMessage
798  };
799 
800  await UpdateRevInfo(tm).ConfigureAwait(false);
801  }
802  }
803  }
804 
805  var currentHead = repo.Head;
806  if (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead))
807  {
808  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false);
809  await UpdateRevInfo().ConfigureAwait(false);
810  }
811  }
812  catch
813  {
814  doneSteps = 0;
815  numSteps = 2;
816 
817  // the stuff didn't make it into the db, forget what we've done and abort
818  await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false);
819  if (startReference != null && repo.Head != startSha)
820  await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
821  else
822  progressReporter(100);
823  throw;
824  }
825  }, cancellationToken).ConfigureAwait(false);
826 
827  api.ActiveJob = job.ToApi();
828  return Accepted(api);
829  }
830  #pragma warning restore CA1502
831  #pragma warning restore CA1505
832  }
833 }
string Name
The name of the User
Definition: User.cs:41
+Go to the documentation of this file.
1 using Microsoft.AspNetCore.Mvc;
2 using Microsoft.EntityFrameworkCore;
3 using Microsoft.Extensions.Logging;
4 using Microsoft.Extensions.Options;
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using System.Linq.Expressions;
10 using System.Net;
11 using System.Reflection;
12 using System.Threading;
13 using System.Threading.Tasks;
14 using Tgstation.Server.Api;
24 
25 namespace Tgstation.Server.Host.Controllers
26 {
30  [Route(Routes.Repository)]
31  #pragma warning disable CA1506 // TODO: Decomplexify
32  public sealed class RepositoryController : ApiController
33  {
38 
43 
48 
53 
64  public RepositoryController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IGitHubClientFactory gitHubClientFactory, IJobManager jobManager, ILogger<RepositoryController> logger, IOptions<GeneralConfiguration> generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, true, true)
65  {
66  this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
67  this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
68  this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
69  generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
70  }
71 
72  async Task<bool> LoadRevisionInformation(Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action<Models.RevisionInformation> revInfoSink, CancellationToken cancellationToken)
73  {
74  var repoSha = repository.Head;
75 
76  IQueryable<Models.RevisionInformation> ApplyQuery(IQueryable<Models.RevisionInformation> query) => query
77  .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
78  .Include(x => x.CompileJobs)
79  .Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy);
80 
81  var revisionInfo = await ApplyQuery(databaseContext.RevisionInformations).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
82 
83  // If the DB doesn't have it, check the local set
84  if (revisionInfo == default)
85  revisionInfo = databaseContext
87  .Local
88  .Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
89  .FirstOrDefault();
90 
91  var needsDbUpdate = revisionInfo == default;
92  if (needsDbUpdate)
93  {
94  // needs insertion
95  revisionInfo = new Models.RevisionInformation
96  {
97  Instance = instance,
98  CommitSha = repoSha,
99  CompileJobs = new List<Models.CompileJob>(),
100  ActiveTestMerges = new List<RevInfoTestMerge>() // non null vals for api returns
101  };
102 
103  lock (databaseContext) // cleaner this way
104  databaseContext.RevisionInformations.Add(revisionInfo);
105  }
106 
107  revisionInfo.OriginCommitSha ??= lastOriginCommitSha;
108  if (revisionInfo.OriginCommitSha == null)
109  {
110  revisionInfo.OriginCommitSha = repoSha;
111  Logger.LogWarning(Components.Repository.Repository.OriginTrackingErrorTemplate, repoSha);
112  }
113 
114  revInfoSink?.Invoke(revisionInfo);
115  return needsDbUpdate;
116  }
117 
118  async Task<bool> PopulateApi(Repository model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, CancellationToken cancellationToken)
119  {
120  if (repository.IsGitHubRepository)
121  {
122  model.GitHubOwner = repository.GitHubOwner;
123  model.GitHubName = repository.GitHubRepoName;
124  }
125 
126  model.Origin = repository.Origin;
127  model.Reference = repository.Reference;
128 
129  // rev info stuff
130  Models.RevisionInformation revisionInfo = null;
131  var needsDbUpdate = await LoadRevisionInformation(repository, databaseContext, instance, null, x => revisionInfo = x, cancellationToken).ConfigureAwait(false);
132  model.RevisionInformation = revisionInfo.ToApi();
133  return needsDbUpdate;
134  }
135 
144  [HttpPut]
145  [TgsAuthorize(RepositoryRights.SetOrigin)]
146  [ProducesResponseType(typeof(Repository), 201)]
147  [ProducesResponseType(410)]
148  public async Task<IActionResult> Create([FromBody] Repository model, CancellationToken cancellationToken)
149  {
150  if (model == null)
151  throw new ArgumentNullException(nameof(model));
152 
153  if (model.Origin == null)
154  return BadRequest(ErrorCode.RepoMissingOrigin);
155 
156  if (model.AccessUser == null ^ model.AccessToken == null)
157  return BadRequest(ErrorCode.RepoMismatchUserAndAccessToken);
158 
159  var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
160 
161  if (currentModel == default)
162  return StatusCode((int)HttpStatusCode.Gone);
163 
164  // normalize github urls
165  const string BadGitHubUrl = "://www.github.com/";
166  var uiOrigin = model.Origin.ToUpperInvariant();
167  var uiBad = BadGitHubUrl.ToUpperInvariant();
168  var uiGitHub = Components.Repository.Repository.GitHubUrl.ToUpperInvariant();
169  if (uiOrigin.Contains(uiBad, StringComparison.Ordinal))
170  model.Origin = uiOrigin.Replace(uiBad, uiGitHub, StringComparison.Ordinal);
171 
172  currentModel.AccessToken = model.AccessToken;
173  currentModel.AccessUser = model.AccessUser; // intentionally only these fields, user not allowed to change anything else atm
174  var cloneBranch = model.Reference;
175  var origin = model.Origin;
176 
177  var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
178 
179  if (repoManager.CloneInProgress)
180  return Conflict(new ErrorMessage(ErrorCode.RepoCloning));
181 
182  if(repoManager.InUse)
183  return Conflict(new ErrorMessage(ErrorCode.RepoBusy));
184 
185  using var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false);
186 
187  // clone conflict
188  if (repo != null)
189  return Conflict(new ErrorMessage(ErrorCode.RepoExists));
190 
191  var job = new Models.Job
192  {
193  Description = String.Format(CultureInfo.InvariantCulture, "Clone branch {1} of repository {0}", origin, cloneBranch ?? "master"),
194  StartedBy = AuthenticationContext.User,
195  CancelRightsType = RightsType.Repository,
196  CancelRight = (ulong)RepositoryRights.CancelClone,
198  };
199  var api = currentModel.ToApi();
200  await jobManager.RegisterOperation(job, async (paramJob, databaseContextFactory, progressReporter, ct) =>
201  {
202  using var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, currentModel.AccessUser, currentModel.AccessToken, progressReporter, ct).ConfigureAwait(false);
203  if (repos == null)
204  throw new JobException(ErrorCode.RepoExists);
205  var instance = new Models.Instance
206  {
207  Id = Instance.Id
208  };
209  await databaseContextFactory.UseContext(
210  async databaseContext =>
211  {
212  databaseContext.Instances.Attach(instance);
213  if (await PopulateApi(api, repos, databaseContext, instance, ct).ConfigureAwait(false))
214  await databaseContext.Save(ct).ConfigureAwait(false);
215  })
216  .ConfigureAwait(false);
217  }, cancellationToken).ConfigureAwait(false);
218 
219  api.Origin = model.Origin;
220  api.Reference = model.Reference;
221  api.ActiveJob = job.ToApi();
222 
223  return StatusCode((int)HttpStatusCode.Created, api);
224  }
225 
233  [HttpDelete]
234  [TgsAuthorize(RepositoryRights.Delete)]
235  [ProducesResponseType(typeof(Repository), 202)]
236  [ProducesResponseType(410)]
237  public async Task<IActionResult> Delete(CancellationToken cancellationToken)
238  {
239  var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
240 
241  if (currentModel == default)
242  return StatusCode((int)HttpStatusCode.Gone);
243 
244  currentModel.AccessToken = null;
245  currentModel.AccessUser = null;
246 
247  await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
248 
249  Logger.LogInformation("Instance {0} repository delete initiated by user {1}", Instance.Id, AuthenticationContext.User.Id);
250 
251  var job = new Models.Job
252  {
253  Description = "Delete repository",
254  StartedBy = AuthenticationContext.User,
256  };
257  var api = currentModel.ToApi();
258  await jobManager.RegisterOperation(job, (paramJob, databaseContextFactory, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false);
259  api.ActiveJob = job.ToApi();
260  return Accepted(api);
261  }
262 
271  [HttpGet]
272  [TgsAuthorize(RepositoryRights.Read)]
273  [ProducesResponseType(typeof(Repository), 200)]
274  [ProducesResponseType(typeof(Repository), 201)]
275  [ProducesResponseType(410)]
276  public async Task<IActionResult> Read(CancellationToken cancellationToken)
277  {
278  var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
279 
280  if (currentModel == default)
281  return StatusCode((int)HttpStatusCode.Gone, new ErrorMessage(ErrorCode.RepoMissing));
282 
283  var api = currentModel.ToApi();
284  var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
285 
286  if (repoManager.CloneInProgress)
287  return Conflict(new ErrorMessage(ErrorCode.RepoCloning));
288 
289  if (repoManager.InUse)
290  return Conflict(new ErrorMessage(ErrorCode.RepoBusy));
291 
292  using var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false);
293  if (repo != null && await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false))
294  {
295  // user may have fucked with the repo manually, do what we can
296  await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
297  return StatusCode((int)HttpStatusCode.Created, api);
298  }
299 
300  return Json(api);
301  }
302 
312  [HttpPost]
313  [TgsAuthorize(RepositoryRights.ChangeAutoUpdateSettings | RepositoryRights.ChangeCommitter | RepositoryRights.ChangeCredentials | RepositoryRights.ChangeTestMergeCommits | RepositoryRights.MergePullRequest | RepositoryRights.SetReference | RepositoryRights.SetSha | RepositoryRights.UpdateBranch)]
314  [ProducesResponseType(typeof(Repository), 200)]
315  [ProducesResponseType(typeof(Repository), 202)]
316  [ProducesResponseType(410)]
317  #pragma warning disable CA1502 // TODO: Decomplexify
318  #pragma warning disable CA1505
319  public async Task<IActionResult> Update([FromBody]Repository model, CancellationToken cancellationToken)
320  {
321  if (model == null)
322  throw new ArgumentNullException(nameof(model));
323 
324  if (model.AccessUser == null ^ model.AccessToken == null)
325  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchUserAndAccessToken));
326 
327  if (model.CheckoutSha != null && model.Reference != null)
328  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchShaAndReference));
329 
330  if (model.CheckoutSha != null && model.UpdateFromOrigin == true)
331  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchShaAndUpdate));
332 
333  if (model.NewTestMerges?.Any(x => model.NewTestMerges.Any(y => x != y && x.Number == y.Number)) == true)
334  return BadRequest(new ErrorMessage(ErrorCode.RepoDuplicateTestMerge));
335 
336  if (model.CommitterName?.Length == 0)
337  return BadRequest(new ErrorMessage(ErrorCode.RepoWhitespaceCommitterName));
338 
339  if (model.CommitterEmail?.Length == 0)
340  return BadRequest(new ErrorMessage(ErrorCode.RepoWhitespaceCommitterEmail));
341 
342  var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0;
343  var userRights = (RepositoryRights)AuthenticationContext.GetRight(RightsType.Repository);
344  if (newTestMerges && !userRights.HasFlag(RepositoryRights.MergePullRequest))
345  return Forbid();
346 
347  var currentModel = await DatabaseContext
349  .Where(x => x.InstanceId == Instance.Id)
350  .FirstOrDefaultAsync(cancellationToken)
351  .ConfigureAwait(false);
352 
353  if (currentModel == default)
354  return StatusCode((int)HttpStatusCode.Gone);
355 
356  bool CheckModified<T>(Expression<Func<Api.Models.Internal.RepositorySettings, T>> expression, RepositoryRights requiredRight)
357  {
358  var memberSelectorExpression = (MemberExpression)expression.Body;
359  var property = (PropertyInfo)memberSelectorExpression.Member;
360 
361  var newVal = property.GetValue(model);
362  if (newVal == null)
363  return false;
364  if (!userRights.HasFlag(requiredRight) && property.GetValue(currentModel) != newVal)
365  return true;
366 
367  property.SetValue(currentModel, newVal);
368  return false;
369  }
370 
371  if (CheckModified(x => x.AccessToken, RepositoryRights.ChangeCredentials)
372  || CheckModified(x => x.AccessUser, RepositoryRights.ChangeCredentials)
373  || CheckModified(x => x.AutoUpdatesKeepTestMerges, RepositoryRights.ChangeAutoUpdateSettings)
374  || CheckModified(x => x.AutoUpdatesSynchronize, RepositoryRights.ChangeAutoUpdateSettings)
375  || CheckModified(x => x.CommitterEmail, RepositoryRights.ChangeCommitter)
376  || CheckModified(x => x.CommitterName, RepositoryRights.ChangeCommitter)
377  || CheckModified(x => x.PushTestMergeCommits, RepositoryRights.ChangeTestMergeCommits)
378  || CheckModified(x => x.ShowTestMergeCommitters, RepositoryRights.ChangeTestMergeCommits)
379  || CheckModified(x => x.PostTestMergeComment, RepositoryRights.ChangeTestMergeCommits)
380  || (model.UpdateFromOrigin == true && !userRights.HasFlag(RepositoryRights.UpdateBranch)))
381  return Forbid();
382 
383  if (currentModel.AccessToken?.Length == 0 && currentModel.AccessUser?.Length == 0)
384  {
385  // setting an empty string clears everything
386  currentModel.AccessUser = null;
387  currentModel.AccessToken = null;
388  }
389 
390  var canRead = userRights.HasFlag(RepositoryRights.Read);
391 
392  var api = canRead ? currentModel.ToApi() : new Repository();
393  var repoManager = instanceManager.GetInstance(Instance).RepositoryManager;
394 
395  if (canRead)
396  {
397  if (repoManager.CloneInProgress)
398  return Conflict(new ErrorMessage(ErrorCode.RepoCloning));
399 
400  if (repoManager.InUse)
401  return Conflict(new ErrorMessage(ErrorCode.RepoBusy));
402 
403  using var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false);
404  if (repo == null)
405  return Conflict(new ErrorMessage(ErrorCode.RepoMissing));
406  await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false);
407 
408  if (model.Origin != null && model.Origin != repo.Origin)
409  return BadRequest(new ErrorMessage(ErrorCode.RepoCantChangeOrigin));
410  }
411 
412  // this is just db stuf so stow it away
413  await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
414 
415  // format the job description
416  string description = null;
417  if (model.UpdateFromOrigin == true)
418  if (model.Reference != null)
419  description = String.Format(CultureInfo.InvariantCulture, "Fetch and hard reset repository to origin/{0}", model.Reference);
420  else if (model.CheckoutSha != null)
421  description = String.Format(CultureInfo.InvariantCulture, "Fetch and checkout {0} in repository", model.CheckoutSha);
422  else
423  description = "Pull current repository reference";
424  else if (model.Reference != null || model.CheckoutSha != null)
425  description = String.Format(CultureInfo.InvariantCulture, "Checkout repository {0} {1}", model.Reference != null ? "reference" : "SHA", model.Reference ?? model.CheckoutSha);
426 
427  if (newTestMerges)
428  description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}",
429  description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T",
430  String.Join(", ", model.NewTestMerges.Select(x =>
431  String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number,
432  x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))),
433  description != null ? String.Empty : " in repository");
434 
435  if (description == null)
436  return Json(api); // no git changes
437 
438  var job = new Models.Job
439  {
440  Description = description,
441  StartedBy = AuthenticationContext.User,
442  Instance = Instance,
443  CancelRightsType = RightsType.Repository,
444  CancelRight = (ulong)RepositoryRights.CancelPendingChanges,
445  };
446 
447  // Time to access git, do it in a job
448  await jobManager.RegisterOperation(job, async (paramJob, databaseContextFactory, progressReporter, ct) =>
449  {
450  using var repo = await repoManager.LoadRepository(ct).ConfigureAwait(false);
451  if (repo == null)
452  throw new JobException(ErrorCode.RepoMissing);
453 
454  var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null;
455 
456  var startReference = repo.Reference;
457  var startSha = repo.Head;
458  string postUpdateSha = null;
459 
460  if (newTestMerges && !repo.IsGitHubRepository)
461  throw new JobException(ErrorCode.RepoUnsupportedTestMergeRemote);
462 
463  var committerName = currentModel.ShowTestMergeCommitters.Value
465  : currentModel.CommitterName;
466 
467  var hardResettingToOriginReference = model.UpdateFromOrigin == true && model.Reference != null;
468 
469  var numSteps = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0) + (!modelHasShaOrReference ? 2 : (hardResettingToOriginReference ? 3 : 1));
470  var doneSteps = 0;
471 
472  Action<int> NextProgressReporter()
473  {
474  var tmpDoneSteps = doneSteps;
475  ++doneSteps;
476  return progress => progressReporter((progress + (100 * tmpDoneSteps)) / numSteps);
477  }
478 
479  progressReporter(0);
480 
481  // get a base line for where we are
482  Models.RevisionInformation lastRevisionInfo = null;
483 
484  var attachedInstance = new Models.Instance
485  {
486  Id = Instance.Id
487  };
488 
489  Task CallLoadRevInfo(Models.TestMerge testMergeToAdd = null, string lastOriginCommitSha = null) => databaseContextFactory
490  .UseContext(
491  async databaseContext =>
492  {
493  databaseContext.Instances.Attach(attachedInstance);
494  var previousRevInfo = lastRevisionInfo;
495  var needsUpdate = await LoadRevisionInformation(
496  repo,
497  databaseContext,
498  attachedInstance,
499  lastOriginCommitSha,
500  x => lastRevisionInfo = x,
501  ct)
502  .ConfigureAwait(false);
503 
504  if (testMergeToAdd != null)
505  {
506  // rev info may have already loaded the user
507  var mergedBy = databaseContext.Users.Local.FirstOrDefault(x => x.Id == AuthenticationContext.User.Id);
508  if (mergedBy == default)
509  {
510  mergedBy = new Models.User
511  {
513  };
514 
515  databaseContext.Users.Attach(mergedBy);
516  }
517 
518  testMergeToAdd.MergedBy = mergedBy;
519 
520  lastRevisionInfo.ActiveTestMerges.AddRange(previousRevInfo.ActiveTestMerges);
521  lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
522  {
523  TestMerge = testMergeToAdd
524  });
525  lastRevisionInfo.PrimaryTestMerge = testMergeToAdd;
526 
527  needsUpdate = true;
528  }
529 
530  if (needsUpdate)
531  await databaseContext.Save(cancellationToken).ConfigureAwait(false);
532  });
533 
534  await CallLoadRevInfo().ConfigureAwait(false);
535 
536  // apply new rev info, tracking applied test merges
537  Task UpdateRevInfo(Models.TestMerge testMergeToAdd = null) => CallLoadRevInfo(testMergeToAdd, lastRevisionInfo.OriginCommitSha);
538 
539  try
540  {
541  // fetch/pull
542  if (model.UpdateFromOrigin == true)
543  {
544  if (!repo.Tracking)
545  throw new JobException(ErrorCode.RepoReferenceRequired);
546  await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false);
547  doneSteps = 1;
548  if (!modelHasShaOrReference)
549  {
550  var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter(), ct).ConfigureAwait(false);
551  if (!fastForward.HasValue)
552  throw new JobException(ErrorCode.RepoMergeConflict);
553  await UpdateRevInfo().ConfigureAwait(false);
554  if (fastForward.Value)
555  {
556  lastRevisionInfo.OriginCommitSha = repo.Head;
557  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
558  postUpdateSha = repo.Head;
559  }
560  else
561  NextProgressReporter()(100);
562  }
563  }
564 
565  // checkout/hard reset
566  if (modelHasShaOrReference)
567  {
568  var validCheckoutSha =
569  model.CheckoutSha != null
570  && !repo.Head.StartsWith(model.CheckoutSha, StringComparison.OrdinalIgnoreCase);
571  var validCheckoutReference =
572  model.Reference != null
573  && !repo.Reference.Equals(model.Reference, StringComparison.OrdinalIgnoreCase);
574  if (validCheckoutSha || validCheckoutReference)
575  {
576  var committish = model.CheckoutSha ?? model.Reference;
577  var isSha = await repo.IsSha(committish, cancellationToken).ConfigureAwait(false);
578 
579  if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null))
580  throw new JobException(ErrorCode.RepoSwappedShaOrReference);
581 
582  await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false);
583  await CallLoadRevInfo().ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
584  }
585  else
586  NextProgressReporter()(100);
587 
588  if (hardResettingToOriginReference)
589  {
590  if (!repo.Tracking)
591  throw new JobException(ErrorCode.RepoReferenceNotTracking);
592  await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false);
593  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
594  await CallLoadRevInfo().ConfigureAwait(false);
595 
596  // repo head is on origin so force this
597  // will update the db if necessary
598  lastRevisionInfo.OriginCommitSha = repo.Head;
599  }
600  }
601 
602  // test merging
603  Dictionary<int, Octokit.PullRequest> prMap = null;
604  if (newTestMerges)
605  {
606  // bit of sanitization
607  foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision)))
608  I.PullRequestRevision = null;
609 
610  var gitHubClient = currentModel.AccessToken != null
611  ? gitHubClientFactory.CreateClient(currentModel.AccessToken)
612  : (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken)
613  ? gitHubClientFactory.CreateClient()
614  : gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken));
615 
616  var repoOwner = repo.GitHubOwner;
617  var repoName = repo.GitHubRepoName;
618 
619  // optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
620  Models.RevisionInformation revInfoWereLookingFor = null;
621  bool needToApplyRemainingPrs = true;
622  if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
623  {
624  // In order for this to work though we need the shas of all the commits
625  if (model.NewTestMerges.Any(x => x.PullRequestRevision == null))
626  prMap = new Dictionary<int, Octokit.PullRequest>();
627 
628  bool cantSearch = false;
629  foreach (var I in model.NewTestMerges)
630  {
631  if (I.PullRequestRevision != null)
632 #pragma warning disable CA1308 // Normalize strings to uppercase
633  I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant(); // ala libgit2
634 #pragma warning restore CA1308 // Normalize strings to uppercase
635  else
636  try
637  {
638  // retrieve the latest sha
639  var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
640  prMap.Add(I.Number, pr);
641  I.PullRequestRevision = pr.Head.Sha;
642  }
643  catch
644  {
645  cantSearch = true;
646  break;
647  }
648  }
649 
650  if (!cantSearch)
651  {
652  List<Models.RevisionInformation> dbPull = null;
653 
654  await databaseContextFactory.UseContext(
655  async databaseContext =>
656  dbPull = await databaseContext.RevisionInformations
657  .Where(x => x.Instance.Id == Instance.Id
658  && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
659  && x.ActiveTestMerges.Count <= model.NewTestMerges.Count
660  && x.ActiveTestMerges.Count > 0)
661  .Include(x => x.ActiveTestMerges)
662  .ThenInclude(x => x.TestMerge)
663  .ToListAsync(cancellationToken)
664  .ConfigureAwait(false))
665  .ConfigureAwait(false);
666 
667  // split here cause this bit has to be done locally
668  revInfoWereLookingFor = dbPull
669  .Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count
670  && x.ActiveTestMerges.Select(y => y.TestMerge)
671  .All(y => model.NewTestMerges.Any(z =>
672  y.Number == z.Number
673  && y.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal)
674  && (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))))
675  .FirstOrDefault();
676 
677  if (revInfoWereLookingFor == default && model.NewTestMerges.Count > 1)
678  {
679  // okay try to add at least SOME prs we've seen before
680  var search = model.NewTestMerges.ToList();
681 
682  var appliedTestMergeIds = new List<long>();
683 
684  Models.RevisionInformation lastGoodRevInfo = null;
685  do
686  {
687  foreach (var I in search)
688  {
689  revInfoWereLookingFor = dbPull
690  .Where(x => model.NewTestMerges.Any(z =>
691  x.PrimaryTestMerge.Number == z.Number
692  && x.PrimaryTestMerge.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal)
693  && (x.PrimaryTestMerge.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))
694  && x.ActiveTestMerges.Select(y => y.TestMerge).All(y => appliedTestMergeIds.Contains(y.Id)))
695  .FirstOrDefault();
696 
697  if (revInfoWereLookingFor != null)
698  {
699  lastGoodRevInfo = revInfoWereLookingFor;
700  appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge.Id);
701  search.Remove(I);
702  break;
703  }
704  }
705  }
706  while (revInfoWereLookingFor != null && search.Count > 0);
707 
708  revInfoWereLookingFor = lastGoodRevInfo;
709  needToApplyRemainingPrs = search.Count != 0;
710  if (needToApplyRemainingPrs)
711  model.NewTestMerges = search;
712  }
713  else if (revInfoWereLookingFor != null)
714  needToApplyRemainingPrs = false;
715  }
716  }
717 
718  if (revInfoWereLookingFor != null)
719  {
720  // goteem
721  await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken).ConfigureAwait(false);
722  lastRevisionInfo = revInfoWereLookingFor;
723  }
724 
725  if (needToApplyRemainingPrs)
726  {
727  foreach (var I in model.NewTestMerges)
728  {
729  Octokit.PullRequest pr = null;
730  string errorMessage = null;
731 
732  if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == I.Number))
733  throw new JobException(ErrorCode.RepoDuplicateTestMerge);
734 
735  Exception exception = null;
736  try
737  {
738  // load from cache if possible
739  if (prMap == null || !prMap.TryGetValue(I.Number, out pr))
740  pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
741  }
742  catch (Octokit.RateLimitExceededException ex)
743  {
744  // you look at your anonymous access and sigh
745  errorMessage = "REMOTE API ERROR: RATE LIMITED";
746  exception = ex;
747  }
748  catch (Octokit.AuthorizationException ex)
749  {
750  errorMessage = "REMOTE API ERROR: BAD CREDENTIALS";
751  exception = ex;
752  }
753  catch (Octokit.NotFoundException ex)
754  {
755  // you look at your shithub and sigh
756  errorMessage = "REMOTE API ERROR: PULL REQUEST NOT FOUND";
757  exception = ex;
758  }
759 
760  if (exception != null)
761  Logger.LogWarning("Error retrieving pull request metadata: {0}", exception);
762 
763  // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
764  if (I.PullRequestRevision == null && pr != null)
765  I.PullRequestRevision = pr.Head.Sha;
766 
767  var mergeResult = await repo.AddTestMerge(
768  I,
769  committerName,
770  currentModel.CommitterEmail,
771  currentModel.AccessUser,
772  currentModel.AccessToken,
773  NextProgressReporter(),
774  ct).ConfigureAwait(false);
775 
776  if (!mergeResult.HasValue)
777  throw new JobException(
778  ErrorCode.RepoTestMergeConflict,
779  new JobException(
780  $"Merge of PR #{I.Number} at {I.PullRequestRevision.Substring(0, 7)} conflicted!"));
781 
782  ++doneSteps;
783 
784  // MergedBy will be set later
785  var tm = new Models.TestMerge
786  {
787  Author = pr?.User.Login ?? errorMessage,
788  BodyAtMerge = pr?.Body ?? errorMessage ?? String.Empty,
789  MergedAt = DateTimeOffset.Now,
790  TitleAtMerge = pr?.Title ?? errorMessage ?? String.Empty,
791  Comment = I.Comment,
792  Number = I.Number,
793  PullRequestRevision = I.PullRequestRevision,
794  Url = pr?.HtmlUrl ?? errorMessage
795  };
796 
797  await UpdateRevInfo(tm).ConfigureAwait(false);
798  }
799  }
800  }
801 
802  var currentHead = repo.Head;
803  if (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead))
804  {
805  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false);
806  await UpdateRevInfo().ConfigureAwait(false);
807  }
808  }
809  catch
810  {
811  doneSteps = 0;
812  numSteps = 2;
813 
814  // Forget what we've done and abort
815  await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false);
816  if (startReference != null && repo.Head != startSha)
817  await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
818  else
819  progressReporter(100);
820  throw;
821  }
822  }, cancellationToken).ConfigureAwait(false);
823 
824  api.ActiveJob = job.ToApi();
825  return Accepted(api);
826  }
827  #pragma warning restore CA1502
828  #pragma warning restore CA1505
829  }
830 }
string Name
The name of the User
Definition: User.cs:41
Task Save(CancellationToken cancellationToken)
Saves changes made to the IDatabaseContext
diff --git a/_server_client_8cs_source.html b/_server_client_8cs_source.html index 3af17abdb5..bf6b7de592 100644 --- a/_server_client_8cs_source.html +++ b/_server_client_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ServerClient.cs
-Go to the documentation of this file.
1 using System;
2 using System.Threading;
3 using System.Threading.Tasks;
4 using Tgstation.Server.Api;
6 
7 namespace Tgstation.Server.Client
8 {
10  sealed class ServerClient : IServerClient
11  {
13  public Uri Url => apiClient.Url;
14 
16  public Token Token
17  {
18  get => token;
19  set
20  {
21  token = value ?? throw new InvalidOperationException("Cannot set a null Token!");
22  apiClient.Headers = new ApiHeaders(apiClient.Headers.UserAgent, token.Bearer);
23  }
24  }
25 
27  public TimeSpan Timeout
28  {
29  get => apiClient.Timeout;
30  set => apiClient.Timeout = value;
31  }
32 
34  public IInstanceManagerClient Instances { get; }
35 
38 
40  public IUsersClient Users { get; }
41 
46 
51 
57  public ServerClient(IApiClient apiClient, Token token)
58  {
59  this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
60  this.token = token ?? throw new ArgumentNullException(nameof(token));
61 
62  if (Token.Bearer != apiClient.Headers.Token)
63  throw new ArgumentOutOfRangeException(nameof(token), token, "Provided token does not match apiClient headers!");
64 
65  Instances = new InstanceManagerClient(apiClient);
66  Users = new UsersClient(apiClient);
67  Administration = new AdministrationClient(apiClient);
68  }
69 
71  public void Dispose() => apiClient.Dispose();
72 
74  public Task<ServerInformation> Version(CancellationToken cancellationToken) => apiClient.Read<ServerInformation>(Routes.Root, cancellationToken);
75 
77  public void AddRequestLogger(IRequestLogger requestLogger) => apiClient.AddRequestLogger(requestLogger);
78  }
79 }
ServerClient(IApiClient apiClient, Token token)
Construct a ServerClient
Definition: ServerClient.cs:57
+Go to the documentation of this file.
1 using System;
2 using System.Threading;
3 using System.Threading.Tasks;
4 using Tgstation.Server.Api;
6 
7 namespace Tgstation.Server.Client
8 {
10  sealed class ServerClient : IServerClient
11  {
13  public Uri Url => apiClient.Url;
14 
16  public Token Token
17  {
18  get => token;
19  set
20  {
21  token = value ?? throw new InvalidOperationException("Cannot set a null Token!");
22  apiClient.Headers = new ApiHeaders(apiClient.Headers.UserAgent!, token.Bearer!);
23  }
24  }
25 
27  public TimeSpan Timeout
28  {
29  get => apiClient.Timeout;
30  set => apiClient.Timeout = value;
31  }
32 
34  public IInstanceManagerClient Instances { get; }
35 
38 
40  public IUsersClient Users { get; }
41 
46 
51 
57  public ServerClient(IApiClient apiClient, Token token)
58  {
59  this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
60  this.token = token ?? throw new ArgumentNullException(nameof(token));
61 
62  if (Token.Bearer != apiClient.Headers.Token)
63  throw new ArgumentOutOfRangeException(nameof(token), token, "Provided token does not match apiClient headers!");
64 
65  Instances = new InstanceManagerClient(apiClient);
66  Users = new UsersClient(apiClient);
67  Administration = new AdministrationClient(apiClient);
68  }
69 
71  public void Dispose() => apiClient.Dispose();
72 
74  public Task<ServerInformation> Version(CancellationToken cancellationToken) => apiClient.Read<ServerInformation>(Routes.Root, cancellationToken);
75 
77  public void AddRequestLogger(IRequestLogger requestLogger) => apiClient.AddRequestLogger(requestLogger);
78  }
79 }
ServerClient(IApiClient apiClient, Token token)
Construct a ServerClient
Definition: ServerClient.cs:57
ApiHeaders Headers
The ApiHeaders the IApiClient uses
Definition: IApiClient.cs:16
Token token
Backing field for Token
Definition: ServerClient.cs:50
diff --git a/_server_client_factory_8cs_source.html b/_server_client_factory_8cs_source.html index 20b278d9cb..1eb6cf3044 100644 --- a/_server_client_factory_8cs_source.html +++ b/_server_client_factory_8cs_source.html @@ -94,28 +94,28 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ServerClientFactory.cs
-Go to the documentation of this file.
1 using System;
2 using System.Net.Http.Headers;
3 using System.Threading;
4 using System.Threading.Tasks;
5 using Tgstation.Server.Api;
7 
8 namespace Tgstation.Server.Client
9 {
12  {
17 
21  readonly ProductHeaderValue productHeaderValue;
22 
27  public ServerClientFactory(ProductHeaderValue productHeaderValue)
28  {
29  this.productHeaderValue = productHeaderValue ?? throw new ArgumentNullException(nameof(productHeaderValue));
30  }
31 
33  public async Task<IServerClient> CreateServerClient(Uri host, string username, string password, TimeSpan timeout, CancellationToken cancellationToken)
34  {
35  if (host == null)
36  throw new ArgumentNullException(nameof(host));
37  if (username == null)
38  throw new ArgumentNullException(nameof(username));
39  if (password == null)
40  throw new ArgumentNullException(nameof(password));
41 
42  Token token;
43  using (var api = ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password)))
44  {
45  if (timeout != default)
46  api.Timeout = timeout;
47  token = await api.Update<Token>(Routes.Root, cancellationToken).ConfigureAwait(false);
48  }
49 
50  return CreateServerClient(host, token, timeout);
51  }
52 
54  public IServerClient CreateServerClient(Uri host, Token token, TimeSpan timeout)
55  {
56  if (host == null)
57  throw new ArgumentNullException(nameof(host));
58  if (token == null)
59  throw new ArgumentNullException(nameof(token));
60  var result = new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token);
61  if (timeout != default)
62  result.Timeout = timeout;
63  return result;
64  }
65  }
66 }
- +Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Net.Http.Headers;
5 using System.Threading;
6 using System.Threading.Tasks;
7 using Tgstation.Server.Api;
9 
10 namespace Tgstation.Server.Client
11 {
14  {
19 
23  readonly ProductHeaderValue productHeaderValue;
24 
29  public ServerClientFactory(ProductHeaderValue productHeaderValue)
30  {
31  this.productHeaderValue = productHeaderValue ?? throw new ArgumentNullException(nameof(productHeaderValue));
32  }
33 
35  public async Task<IServerClient> CreateFromLogin(
36  Uri host,
37  string username,
38  string password,
39  IEnumerable<IRequestLogger>? requestLoggers = null,
40  TimeSpan? timeout = null,
41  bool attemptRefreshLogin = true,
42  CancellationToken cancellationToken = default)
43  {
44  if (host == null)
45  throw new ArgumentNullException(nameof(host));
46  if (username == null)
47  throw new ArgumentNullException(nameof(username));
48  if (password == null)
49  throw new ArgumentNullException(nameof(password));
50 
51  requestLoggers ??= Enumerable.Empty<IRequestLogger>();
52 
53  Token token;
54  var loginHeaders = new ApiHeaders(productHeaderValue, username, password);
55  using (var api = ApiClientFactory.CreateApiClient(host, loginHeaders, null))
56  {
57  foreach (var requestLogger in requestLoggers)
58  api.AddRequestLogger(requestLogger);
59 
60  if (timeout.HasValue)
61  api.Timeout = timeout.Value;
62  token = await api.Update<Token>(Routes.Root, cancellationToken).ConfigureAwait(false);
63  }
64 
65  var client = CreateFromToken(host, token);
66  if (timeout.HasValue)
67  client.Timeout = timeout.Value;
68 
69  var apiHeaders = new ApiHeaders(productHeaderValue, token.Bearer!);
70 
71  return new ServerClient(ApiClientFactory.CreateApiClient(host, apiHeaders, loginHeaders), token);
72  }
73 
75  public IServerClient CreateFromToken(Uri host, Token token)
76  {
77  if (host == null)
78  throw new ArgumentNullException(nameof(host));
79  if (token == null)
80  throw new ArgumentNullException(nameof(token));
81  if (token.Bearer == null)
82  throw new ArgumentException("token.Bearer should not be null!", nameof(token));
83 
84  return new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer), null), token);
85  }
86  }
87 }
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
Routes to a server actions
Definition: Routes.cs:9
+
async Task< IServerClient > CreateFromLogin(Uri host, string username, string password, IEnumerable< IRequestLogger >?requestLoggers=null, TimeSpan?timeout=null, bool attemptRefreshLogin=true, CancellationToken cancellationToken=default)
Create a IServerClient
Main client for communicating with a server
const string Root
The root controller
Definition: Routes.cs:14
Represents a JWT returned by the API
Definition: Token.cs:8
-
Factory for creating IServerClients
- +
Factory for creating IServerClients
+ -
readonly ProductHeaderValue productHeaderValue
The ProductHeaderValue for the ServerClientFactory
-
static readonly IApiClientFactory ApiClientFactory
The IApiClientFactory for the ServerClientFactory
+
IServerClient CreateFromToken(Uri host, Token token)
Create a IServerClient
+
For logging HTTP requests and responses
+
readonly ProductHeaderValue productHeaderValue
The ProductHeaderValue for the ServerClientFactory
+
static readonly IApiClientFactory ApiClientFactory
The IApiClientFactory for the ServerClientFactory
-
async Task< IServerClient > CreateServerClient(Uri host, string username, string password, TimeSpan timeout, CancellationToken cancellationToken)
Create a IServerClient
-
IServerClient CreateServerClient(Uri host, Token token, TimeSpan timeout)
Create a IServerClient
string Bearer
The value of the JWT
Definition: Token.cs:13
-
ServerClientFactory(ProductHeaderValue productHeaderValue)
Construct a ServerClientFactory
+
ServerClientFactory(ProductHeaderValue productHeaderValue)
Construct a ServerClientFactory
diff --git a/_server_error_exception_8cs_source.html b/_server_error_exception_8cs_source.html index c1544b6d43..5104ab10dc 100644 --- a/_server_error_exception_8cs_source.html +++ b/_server_error_exception_8cs_source.html @@ -94,14 +94,14 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ServerErrorException.cs
-Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class ServerErrorException : ApiException
11  {
15  public ServerErrorException() { }
16 
22  public ServerErrorException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
23  {
24  }
25 
31  public ServerErrorException(string message, Exception innerException) : base(message, innerException) { }
32  }
33 }
ServerErrorException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)
Construct an ServerErrorException with a given errorMessage
- +Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class ServerErrorException : ApiException
11  {
15  public ServerErrorException() { }
16 
22  public ServerErrorException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
23  {
24  }
25 
31  public ServerErrorException(string message, Exception innerException) : base(message, innerException) { }
32  }
33 }
ServerErrorException(string message, Exception innerException)
Construct an ServerErrorException with a message and innerException
Occurs when an error occurs in the server
API related Exceptions.
Definition: ApiException.cs:10
+
ServerErrorException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
Construct an ServerErrorException with a given errorMessage
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
ServerErrorException()
Construct an ServerErrorException
diff --git a/_server_information_8cs_source.html b/_server_information_8cs_source.html index d274a63c6b..43025b8254 100644 --- a/_server_information_8cs_source.html +++ b/_server_information_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ServerInformation.cs
-Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class ServerInformation : Internal.ServerInformation
9  {
13  public Version Version { get; set; }
14 
18  public Version ApiVersion { get; set; }
19 
23  public Version DMApiVersion { get; set; }
24  }
25 }
+Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class ServerInformation : Internal.ServerInformation
9  {
13  public Version? Version { get; set; }
14 
18  public Version? ApiVersion { get; set; }
19 
23  public Version? DMApiVersion { get; set; }
24  }
25 }
Represents basic server information.
diff --git a/_setup_wizard_8cs_source.html b/_setup_wizard_8cs_source.html index 35960d89f4..55c2203f7f 100644 --- a/_setup_wizard_8cs_source.html +++ b/_setup_wizard_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
SetupWizard.cs
-Go to the documentation of this file.
1 using Microsoft.Data.Sqlite;
2 using Microsoft.Extensions.Hosting;
3 using Microsoft.Extensions.Logging;
4 using Microsoft.Extensions.Options;
5 using MySql.Data.MySqlClient;
6 using Newtonsoft.Json;
7 using System;
8 using System.Collections.Generic;
9 using System.Data.Common;
10 using System.Data.SqlClient;
11 using System.Globalization;
12 using System.IO;
13 using System.Linq;
14 using System.Text;
15 using System.Text.RegularExpressions;
16 using System.Threading;
17 using System.Threading.Tasks;
21 using Tgstation.Server.Host.IO;
23 
24 namespace Tgstation.Server.Host.Setup
25 {
27  sealed class SetupWizard : IHostedService
28  {
33 
37  readonly IConsole console;
38 
42  readonly IHostEnvironment hostingEnvironment;
43 
48 
53 
58 
63 
67  readonly IHostApplicationLifetime applicationLifetime;
68 
73 
86  public SetupWizard(
87  IIOManager ioManager,
88  IConsole console,
89  IHostEnvironment hostingEnvironment,
90  IAssemblyInformationProvider assemblyInformationProvider,
91  IDatabaseConnectionFactory dbConnectionFactory,
92  IPlatformIdentifier platformIdentifier,
93  IAsyncDelayer asyncDelayer,
94  IHostApplicationLifetime applicationLifetime,
95  IOptions<GeneralConfiguration> generalConfigurationOptions)
96  {
97  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
98  this.console = console ?? throw new ArgumentNullException(nameof(console));
99  this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
100  this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
101  this.dbConnectionFactory = dbConnectionFactory ?? throw new ArgumentNullException(nameof(dbConnectionFactory));
102  this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
103  this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
104  this.applicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
105  generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
106  }
107 
114  async Task<bool> PromptYesNo(string question, CancellationToken cancellationToken)
115  {
116  do
117  {
118  await console.WriteAsync(question, false, cancellationToken).ConfigureAwait(false);
119  var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
120  var upperResponse = responseString.ToUpperInvariant();
121  if (upperResponse == "Y" || upperResponse == "YES")
122  return true;
123  else if (upperResponse == "N" || upperResponse == "NO")
124  return false;
125  await console.WriteAsync("Invalid response!", true, cancellationToken).ConfigureAwait(false);
126  }
127  while (true);
128  }
129 
135  async Task<ushort?> PromptForHostingPort(CancellationToken cancellationToken)
136  {
137  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
138  await console.WriteAsync("What port would you like to connect to TGS on?", true, cancellationToken).ConfigureAwait(false);
139  await console.WriteAsync("Note: If this is a docker container with the default port already mapped, use the default.", true, cancellationToken).ConfigureAwait(false);
140 
141  do
142  {
143  await console.WriteAsync("API Port (leave blank for default): ", false, cancellationToken).ConfigureAwait(false);
144  var portString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
145  if (String.IsNullOrWhiteSpace(portString))
146  return null;
147  if (UInt16.TryParse(portString, out var port) && port != 0)
148  return port;
149  await console.WriteAsync("Invalid port! Please enter a value between 1 and 65535", true, cancellationToken).ConfigureAwait(false);
150  }
151  while (true);
152  }
153 
164  DbConnection testConnection,
165  DatabaseConfiguration databaseConfiguration,
166  string databaseName,
167  bool dbExists,
168  CancellationToken cancellationToken)
169  {
170  bool isSqliteDB = databaseConfiguration.DatabaseType == DatabaseType.Sqlite;
171  using (testConnection)
172  {
173  await console.WriteAsync("Testing connection...", true, cancellationToken).ConfigureAwait(false);
174  await testConnection.OpenAsync(cancellationToken).ConfigureAwait(false);
175  await console.WriteAsync("Connection successful!", true, cancellationToken).ConfigureAwait(false);
176 
177  if (databaseConfiguration.DatabaseType == DatabaseType.MariaDB
178  || databaseConfiguration.DatabaseType == DatabaseType.MySql)
179  {
180  await console.WriteAsync("Checking MySQL/MariaDB version...", true, cancellationToken).ConfigureAwait(false);
181  using var command = testConnection.CreateCommand();
182  command.CommandText = "SELECT VERSION()";
183  var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
184  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Found {0}", fullVersion), true, cancellationToken).ConfigureAwait(false);
185  var splits = fullVersion.Split('-');
186  databaseConfiguration.MySqlServerVersion = splits.First();
187  }
188 
189  if (!isSqliteDB && !dbExists)
190  {
191  await console.WriteAsync("Testing create DB permission...", true, cancellationToken).ConfigureAwait(false);
192  using (var command = testConnection.CreateCommand())
193  {
194  // I really don't care about user sanitization here, they want to fuck their own DB? so be it
195 #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
196  command.CommandText = $"CREATE DATABASE {databaseName}";
197 #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
198  await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
199  }
200 
201  await console.WriteAsync("Success!", true, cancellationToken).ConfigureAwait(false);
202  await console.WriteAsync("Dropping test database...", true, cancellationToken).ConfigureAwait(false);
203  using (var command = testConnection.CreateCommand())
204  {
205 #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
206  command.CommandText = $"DROP DATABASE {databaseName}";
207 #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
208  try
209  {
210  await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
211  }
212  catch (OperationCanceledException)
213  {
214  throw;
215  }
216  catch (Exception e)
217  {
218  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
219  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
220  await console.WriteAsync("This should be okay, but you may want to manually drop the database before continuing!", true, cancellationToken).ConfigureAwait(false);
221  await console.WriteAsync("Press any key to continue...", true, cancellationToken).ConfigureAwait(false);
222  await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false);
223  }
224  }
225  }
226  }
227 
228  if (isSqliteDB && !dbExists)
229  await Task.WhenAll(
230  console.WriteAsync("Deleting test database file...", true, cancellationToken),
231  ioManager.DeleteFile(databaseName, cancellationToken)).ConfigureAwait(false);
232  }
233 
234  async Task<string> ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken)
235  {
236  var resolvedPath = ioManager.ResolvePath(databaseName);
237  try
238  {
239  var directoryName = ioManager.GetDirectoryName(resolvedPath);
240  bool directoryExisted = await ioManager.DirectoryExists(directoryName, cancellationToken).ConfigureAwait(false);
241  await ioManager.CreateDirectory(directoryName, cancellationToken).ConfigureAwait(false);
242  try
243  {
244  await ioManager.WriteAllBytes(resolvedPath, Array.Empty<byte>(), cancellationToken).ConfigureAwait(false);
245  }
246  catch
247  {
248  if (!directoryExisted)
249  await ioManager.DeleteDirectory(directoryName, cancellationToken).ConfigureAwait(false);
250  throw;
251  }
252  }
253  catch (IOException)
254  {
255  return null;
256  }
257 
258  if (!Path.IsPathRooted(databaseName))
259  {
260  await console.WriteAsync("Note, this relative path (currently) resolves to the following:", true, cancellationToken).ConfigureAwait(false);
261  await console.WriteAsync(resolvedPath, true, cancellationToken).ConfigureAwait(false);
262  bool writeResolved = await PromptYesNo(
263  "Would you like to save the relative path in the configuration? If not, the full path will be saved. (y/n): ",
264  cancellationToken)
265  .ConfigureAwait(false);
266 
267  if (writeResolved)
268  databaseName = resolvedPath;
269  }
270 
271  await ioManager.DeleteFile(databaseName, cancellationToken).ConfigureAwait(false);
272  return databaseName;
273  }
274 
280  async Task<DatabaseType> PromptDatabaseType(CancellationToken cancellationToken)
281  {
282  await console.WriteAsync(String.Empty, true, cancellationToken).ConfigureAwait(false);
283  await console.WriteAsync(
284  "NOTE: It is HIGHLY reccommended that TGS runs on a complete relational database, specfically *NOT* Sqlite.",
285  true,
286  cancellationToken)
287  .ConfigureAwait(false);
288  await console.WriteAsync(
289  "Sqlite, by nature cannot perform several DDL operations. Because of this future compatiblility cannot be guaranteed.",
290  true,
291  cancellationToken)
292  .ConfigureAwait(false);
293  await console.WriteAsync(
294  "This means that you may not be able to update to the next minor version of TGS4 without a clean re-installation!",
295  true,
296  cancellationToken)
297  .ConfigureAwait(false);
298  await console.WriteAsync(
299  "Please consider taking the time to set up a relational database if this is meant to be a long-standing server.",
300  true,
301  cancellationToken)
302  .ConfigureAwait(false);
303  await console.WriteAsync(String.Empty, true, cancellationToken).ConfigureAwait(false);
304 
305  await asyncDelayer.Delay(TimeSpan.FromSeconds(3), cancellationToken).ConfigureAwait(false);
306 
307  await console.WriteAsync("What SQL database type will you be using?", true, cancellationToken).ConfigureAwait(false);
308  do
309  {
310  await console.WriteAsync(
311  String.Format(
312  CultureInfo.InvariantCulture,
313  "Please enter one of {0}, {1}, {2} or {3}: ",
314  DatabaseType.MariaDB,
315  DatabaseType.MySql,
316  DatabaseType.SqlServer,
317  DatabaseType.Sqlite),
318  false,
319  cancellationToken)
320  .ConfigureAwait(false);
321  var databaseTypeString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
322  if (Enum.TryParse<DatabaseType>(databaseTypeString, out var databaseType))
323  return databaseType;
324 
325  await console.WriteAsync("Invalid database type!", true, cancellationToken).ConfigureAwait(false);
326  }
327  while (true);
328  }
329 
335  async Task<DatabaseConfiguration> ConfigureDatabase(CancellationToken cancellationToken)
336  {
337  do
338  {
339  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
340 
341  var databaseConfiguration = new DatabaseConfiguration
342  {
343  DatabaseType = await PromptDatabaseType(cancellationToken).ConfigureAwait(false)
344  };
345 
346  string serverAddress = null;
347  uint? mySQLServerPort = null;
348 
349  bool isSqliteDB = databaseConfiguration.DatabaseType == DatabaseType.Sqlite;
350  if (!isSqliteDB)
351  do
352  {
353  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
354  await console.WriteAsync("Enter the server's address and port [<server>:<port> or <server>] (blank for local): ", false, cancellationToken).ConfigureAwait(false);
355  serverAddress = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
356  if (!String.IsNullOrWhiteSpace(serverAddress) && databaseConfiguration.DatabaseType == DatabaseType.SqlServer)
357  {
358  var match = Regex.Match(serverAddress, @"^(?<server>.+):(?<port>.+)$");
359  if (match.Success)
360  {
361  serverAddress = match.Groups["server"].Value;
362  var portString = match.Groups["port"].Value;
363  if (uint.TryParse(portString, out uint port))
364  mySQLServerPort = port;
365  else
366  {
367  await console.WriteAsync($"Failed to parse port \"{portString}\", please try again.", true, cancellationToken).ConfigureAwait(false);
368  continue;
369  }
370  }
371  }
372 
373  break;
374  }
375  while (true);
376 
377  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
378  await console.WriteAsync($"Enter the database {(isSqliteDB ? "file path" : "name")} (Can be from previous installation. Otherwise, should not exist): ", false, cancellationToken).ConfigureAwait(false);
379 
380  string databaseName;
381  bool dbExists = false;
382  do
383  {
384  databaseName = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
385  if (!String.IsNullOrWhiteSpace(databaseName))
386  {
387  if (isSqliteDB)
388  {
389  dbExists = await ioManager.FileExists(databaseName, cancellationToken).ConfigureAwait(false);
390  if (!dbExists)
391  databaseName = await ValidateNonExistantSqliteDBName(databaseName, cancellationToken).ConfigureAwait(false);
392  }
393  else
394  dbExists = await PromptYesNo("Does this database already exist? If not, we will attempt to CREATE it. (y/n): ", cancellationToken).ConfigureAwait(false);
395  }
396 
397  if (String.IsNullOrWhiteSpace(databaseName))
398  await console.WriteAsync("Invalid database name!", true, cancellationToken).ConfigureAwait(false);
399  else
400  break;
401  }
402  while (true);
403 
404  bool useWinAuth;
405  if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer && platformIdentifier.IsWindows)
406  useWinAuth = await PromptYesNo("Use Windows Authentication? (y/n): ", cancellationToken).ConfigureAwait(false);
407  else
408  useWinAuth = false;
409 
410  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
411 
412  string username = null;
413  string password = null;
414  if (!isSqliteDB)
415  if (!useWinAuth)
416  {
417  await console.WriteAsync("Enter username: ", false, cancellationToken).ConfigureAwait(false);
418  username = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
419  await console.WriteAsync("Enter password: ", false, cancellationToken).ConfigureAwait(false);
420  password = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false);
421  }
422  else
423  {
424  await console.WriteAsync("IMPORTANT: If using the service runner, ensure this computer's LocalSystem account has CREATE DATABASE permissions on the target server!", true, cancellationToken).ConfigureAwait(false);
425  await console.WriteAsync("The account it uses in MSSQL is usually \"NT AUTHORITY\\SYSTEM\" and the role it needs is usually \"dbcreator\".", true, cancellationToken).ConfigureAwait(false);
426  await console.WriteAsync("We'll run a sanity test here, but it won't be indicative of the service's permissions if that is the case", true, cancellationToken).ConfigureAwait(false);
427  }
428 
429  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
430 
431  DbConnection testConnection;
432  void CreateTestConnection(string connectionString) =>
433  testConnection = dbConnectionFactory.CreateConnection(
434  connectionString,
435  databaseConfiguration.DatabaseType);
436 
437  if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer)
438  {
439  var csb = new SqlConnectionStringBuilder
440  {
441  ApplicationName = assemblyInformationProvider.VersionPrefix,
442  DataSource = serverAddress ?? "(local)"
443  };
444 
445  if (useWinAuth)
446  csb.IntegratedSecurity = true;
447  else
448  {
449  csb.UserID = username;
450  csb.Password = password;
451  }
452 
453  CreateTestConnection(csb.ConnectionString);
454  csb.InitialCatalog = databaseName;
455  databaseConfiguration.ConnectionString = csb.ConnectionString;
456  }
457  else if(databaseConfiguration.DatabaseType == DatabaseType.Sqlite)
458  {
459  var csb = new SqliteConnectionStringBuilder
460  {
461  DataSource = databaseName,
462  Mode = dbExists ? SqliteOpenMode.ReadOnly : SqliteOpenMode.ReadWriteCreate
463  };
464 
465  CreateTestConnection(csb.ConnectionString);
466  databaseConfiguration.ConnectionString = csb.ConnectionString;
467  }
468  else
469  {
470  // MySQL/MariaDB
471  var csb = new MySqlConnectionStringBuilder
472  {
473  Server = serverAddress ?? "127.0.0.1",
474  UserID = username,
475  Password = password
476  };
477 
478  if (mySQLServerPort.HasValue)
479  csb.Port = mySQLServerPort.Value;
480 
481  CreateTestConnection(csb.ConnectionString);
482  csb.Database = databaseName;
483  databaseConfiguration.ConnectionString = csb.ConnectionString;
484  }
485 
486  try
487  {
488  await TestDatabaseConnection(testConnection, databaseConfiguration, databaseName, dbExists, cancellationToken).ConfigureAwait(false);
489 
490  return databaseConfiguration;
491  }
492  catch (OperationCanceledException)
493  {
494  throw;
495  }
496  catch (Exception e)
497  {
498  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
499  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
500  await console.WriteAsync("Retrying database configuration...", true, cancellationToken).ConfigureAwait(false);
501  }
502  }
503  while (true);
504  }
505 
511  async Task<GeneralConfiguration> ConfigureGeneral(CancellationToken cancellationToken)
512  {
513  var newGeneralConfiguration = new GeneralConfiguration
514  {
516  };
517 
518  do
519  {
520  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
521  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Minimum database user password length (leave blank for default of {0}): ", newGeneralConfiguration.MinimumPasswordLength), false, cancellationToken).ConfigureAwait(false);
522  var passwordLengthString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
523  if (String.IsNullOrWhiteSpace(passwordLengthString))
524  break;
525  if (UInt32.TryParse(passwordLengthString, out var passwordLength) && passwordLength >= 0)
526  {
527  newGeneralConfiguration.MinimumPasswordLength = passwordLength;
528  break;
529  }
530 
531  await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
532  }
533  while (true);
534 
535  do
536  {
537  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
538  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Timeout for sending and receiving BYOND topics (ms, 0 for infinite, leave blank for default of {0}): ", newGeneralConfiguration.ByondTopicTimeout), false, cancellationToken).ConfigureAwait(false);
539  var topicTimeoutString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
540  if (String.IsNullOrWhiteSpace(topicTimeoutString))
541  break;
542  if (Int32.TryParse(topicTimeoutString, out var topicTimeout) && topicTimeout >= 0)
543  {
544  newGeneralConfiguration.ByondTopicTimeout = topicTimeout;
545  break;
546  }
547 
548  await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
549  }
550  while (true);
551 
552  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
553  await console.WriteAsync("Enter a GitHub personal access token to bypass some rate limits (this is optional and does not require any scopes)", true, cancellationToken).ConfigureAwait(false);
554  await console.WriteAsync("GitHub personal access token: ", false, cancellationToken).ConfigureAwait(false);
555  newGeneralConfiguration.GitHubAccessToken = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false);
556  if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
557  newGeneralConfiguration.GitHubAccessToken = null;
558 
559  newGeneralConfiguration.UseExperimentalWatchdog = await PromptYesNo("Use the experimental watchdog (NOT RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false);
560 
561  newGeneralConfiguration.UseBasicWatchdogOnWindows = true;
562 
563  return newGeneralConfiguration;
564  }
565 
571  async Task<FileLoggingConfiguration> ConfigureLogging(CancellationToken cancellationToken)
572  {
573  var fileLoggingConfiguration = new FileLoggingConfiguration();
574  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
575  fileLoggingConfiguration.Disable = !await PromptYesNo("Enable file logging? (y/n): ", cancellationToken).ConfigureAwait(false);
576 
577  if (!fileLoggingConfiguration.Disable)
578  {
579  do
580  {
581  await console.WriteAsync("Log file directory path (leave blank for default): ", false, cancellationToken).ConfigureAwait(false);
582  fileLoggingConfiguration.Directory = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
583  if (String.IsNullOrWhiteSpace(fileLoggingConfiguration.Directory))
584  {
585  fileLoggingConfiguration.Directory = null;
586  break;
587  }
588 
589  // test a write of it
590  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
591  await console.WriteAsync("Testing directory access...", true, cancellationToken).ConfigureAwait(false);
592  try
593  {
594  await ioManager.CreateDirectory(fileLoggingConfiguration.Directory, cancellationToken).ConfigureAwait(false);
595  var testFile = ioManager.ConcatPath(fileLoggingConfiguration.Directory, String.Format(CultureInfo.InvariantCulture, "WizardAccesTest.{0}.deleteme", Guid.NewGuid()));
596  await ioManager.WriteAllBytes(testFile, Array.Empty<byte>(), cancellationToken).ConfigureAwait(false);
597  try
598  {
599  await ioManager.DeleteFile(testFile, cancellationToken).ConfigureAwait(false);
600  }
601  catch (OperationCanceledException)
602  {
603  throw;
604  }
605  catch (Exception e)
606  {
607  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Error deleting test log file: {0}", testFile), true, cancellationToken).ConfigureAwait(false);
608  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
609  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
610  }
611 
612  break;
613  }
614  catch (OperationCanceledException)
615  {
616  throw;
617  }
618  catch (Exception e)
619  {
620  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
621  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
622  await console.WriteAsync("Please verify the path is valid and you have access to it!", true, cancellationToken).ConfigureAwait(false);
623  }
624  }
625  while (true);
626 
627  async Task<LogLevel?> PromptLogLevel(string question)
628  {
629  do
630  {
631  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
632  await console.WriteAsync(question, true, cancellationToken).ConfigureAwait(false);
633  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ", nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false);
634  var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
635  if (String.IsNullOrWhiteSpace(responseString))
636  return null;
637  if (Enum.TryParse<LogLevel>(responseString, out var logLevel) && logLevel != LogLevel.None)
638  return logLevel;
639  await console.WriteAsync("Invalid log level!", true, cancellationToken).ConfigureAwait(false);
640  }
641  while (true);
642  }
643 
644  fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.LogLevel;
645  fileLoggingConfiguration.MicrosoftLogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for Microsoft logs (VERY verbose, default {0}).", fileLoggingConfiguration.MicrosoftLogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.MicrosoftLogLevel;
646  }
647 
648  return fileLoggingConfiguration;
649  }
650 
656  async Task<ControlPanelConfiguration> ConfigureControlPanel(CancellationToken cancellationToken)
657  {
658  var config = new ControlPanelConfiguration
659  {
660  Enable = await PromptYesNo("Enable the web control panel (Incomplete)? (y/n): ", cancellationToken).ConfigureAwait(false),
661  AllowAnyOrigin = await PromptYesNo("Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(false)
662  };
663 
664  if (!config.AllowAnyOrigin)
665  {
666  await console.WriteAsync("Enter a comma seperated list of CORS allowed origins (optional): ", false, cancellationToken).ConfigureAwait(false);
667  var commaSeperatedOrigins = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
668  if (!String.IsNullOrWhiteSpace(commaSeperatedOrigins))
669  {
670  var splits = commaSeperatedOrigins.Split(',');
671  config.AllowedOrigins = new List<string>(splits.Select(x => x.Trim()));
672  }
673  }
674 
675  return config;
676  }
677 
689  async Task SaveConfiguration(string userConfigFileName, ushort? hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, ControlPanelConfiguration controlPanelConfiguration, CancellationToken cancellationToken)
690  {
691  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Configuration complete! Saving to {0}", userConfigFileName), true, cancellationToken).ConfigureAwait(false);
692 
693  var map = new Dictionary<string, object>()
694  {
695  { DatabaseConfiguration.Section, databaseConfiguration },
696  { GeneralConfiguration.Section, newGeneralConfiguration },
697  { FileLoggingConfiguration.Section, fileLoggingConfiguration },
698  { ControlPanelConfiguration.Section, controlPanelConfiguration }
699  };
700 
701  if (hostingPort.HasValue)
702  map.Add("Kestrel", new
703  {
704  EndPoints = new
705  {
706  Http = new
707  {
708  Url = String.Format(CultureInfo.InvariantCulture, "http://0.0.0.0:{0}", hostingPort)
709  }
710  }
711  });
712 
713  var json = JsonConvert.SerializeObject(map, Formatting.Indented);
714  var configBytes = Encoding.UTF8.GetBytes(json);
715 
716  try
717  {
718  await ioManager.WriteAllBytes(userConfigFileName, configBytes, cancellationToken).ConfigureAwait(false);
719  }
720  catch (OperationCanceledException)
721  {
722  throw;
723  }
724  catch (Exception e)
725  {
726  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
727  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
728  await console.WriteAsync("For your convienence, here's the json we tried to write out:", true, cancellationToken).ConfigureAwait(false);
729  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
730  await console.WriteAsync(json, true, cancellationToken).ConfigureAwait(false);
731  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
732  await console.WriteAsync("Press any key to exit...", true, cancellationToken).ConfigureAwait(false);
733  await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false);
734  throw new OperationCanceledException();
735  }
736  }
737 
744  async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken)
745  {
746  // welcome message
747  await console.WriteAsync("Welcome to tgstation-server 4!", true, cancellationToken).ConfigureAwait(false);
748  await console.WriteAsync("This wizard will help you configure your server.", true, cancellationToken).ConfigureAwait(false);
749 
750  var hostingPort = await PromptForHostingPort(cancellationToken).ConfigureAwait(false);
751 
752  var databaseConfiguration = await ConfigureDatabase(cancellationToken).ConfigureAwait(false);
753 
754  var newGeneralConfiguration = await ConfigureGeneral(cancellationToken).ConfigureAwait(false);
755 
756  var fileLoggingConfiguration = await ConfigureLogging(cancellationToken).ConfigureAwait(false);
757 
758  var controlPanelConfiguration = await ConfigureControlPanel(cancellationToken).ConfigureAwait(false);
759 
760  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
761 
762  await SaveConfiguration(userConfigFileName, hostingPort, databaseConfiguration, newGeneralConfiguration, fileLoggingConfiguration, controlPanelConfiguration, cancellationToken).ConfigureAwait(false);
763  }
764 
770  async Task CheckRunWizard(CancellationToken cancellationToken)
771  {
772  var setupWizardMode = generalConfiguration.SetupWizardMode;
773  if (setupWizardMode == SetupWizardMode.Never)
774  return;
775 
776  var forceRun = setupWizardMode == SetupWizardMode.Force || setupWizardMode == SetupWizardMode.Only;
777  if (!console.Available)
778  {
779  if (forceRun)
780  throw new InvalidOperationException("Asked to run setup wizard with no console avaliable!");
781  return;
782  }
783 
784  var userConfigFileName = String.Format(CultureInfo.InvariantCulture, "appsettings.{0}.json", hostingEnvironment.EnvironmentName);
785 
786  async Task HandleSetupCancel()
787  {
788  await console.WriteAsync(String.Empty, true, default).ConfigureAwait(false);
789  await console.WriteAsync("Aborting setup!", true, default).ConfigureAwait(false);
790  }
791 
792  // Link passed cancellationToken with cancel key press
793  Task finalTask = Task.CompletedTask;
794  using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, console.CancelKeyPress))
795  using ((cancellationToken = cts.Token).Register(() => finalTask = HandleSetupCancel()))
796  try
797  {
798  var exists = await ioManager.FileExists(userConfigFileName, cancellationToken).ConfigureAwait(false);
799 
800  bool shouldRunBasedOnAutodetect;
801  if (exists)
802  {
803  var bytes = await ioManager.ReadAllBytes(userConfigFileName, cancellationToken).ConfigureAwait(false);
804  var contents = Encoding.UTF8.GetString(bytes);
805  var existingConfigIsEmpty = String.IsNullOrWhiteSpace(contents) || contents.Trim() == "{}";
806  shouldRunBasedOnAutodetect = existingConfigIsEmpty;
807  }
808  else
809  shouldRunBasedOnAutodetect = true;
810 
811  if (!shouldRunBasedOnAutodetect)
812  {
813  if (forceRun)
814  {
815  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "The configuration settings are requesting the setup wizard be run, but you already appear to have a configuration file ({0})!", userConfigFileName), true, cancellationToken).ConfigureAwait(false);
816 
817  forceRun = await PromptYesNo("Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(false);
818  }
819 
820  if (!forceRun)
821  return;
822  }
823 
824  // flush the logs to prevent console conflicts
825  await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
826 
827  await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(false);
828  }
829  finally
830  {
831  await finalTask.ConfigureAwait(false);
832  }
833  }
834 
836  public async Task StartAsync(CancellationToken cancellationToken)
837  {
838  await CheckRunWizard(cancellationToken).ConfigureAwait(false);
839  applicationLifetime.StopApplication();
840  }
841 
843  public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
844  }
845 }
+Go to the documentation of this file.
1 using Microsoft.Data.Sqlite;
2 using Microsoft.Extensions.Hosting;
3 using Microsoft.Extensions.Logging;
4 using Microsoft.Extensions.Options;
5 using MySql.Data.MySqlClient;
6 using Newtonsoft.Json;
7 using System;
8 using System.Collections.Generic;
9 using System.Data.Common;
10 using System.Data.SqlClient;
11 using System.Globalization;
12 using System.IO;
13 using System.Linq;
14 using System.Text;
15 using System.Text.RegularExpressions;
16 using System.Threading;
17 using System.Threading.Tasks;
21 using Tgstation.Server.Host.IO;
23 
24 namespace Tgstation.Server.Host.Setup
25 {
27  sealed class SetupWizard : IHostedService
28  {
33 
37  readonly IConsole console;
38 
42  readonly IHostEnvironment hostingEnvironment;
43 
48 
53 
58 
63 
67  readonly IHostApplicationLifetime applicationLifetime;
68 
73 
86  public SetupWizard(
87  IIOManager ioManager,
88  IConsole console,
89  IHostEnvironment hostingEnvironment,
90  IAssemblyInformationProvider assemblyInformationProvider,
91  IDatabaseConnectionFactory dbConnectionFactory,
92  IPlatformIdentifier platformIdentifier,
93  IAsyncDelayer asyncDelayer,
94  IHostApplicationLifetime applicationLifetime,
95  IOptions<GeneralConfiguration> generalConfigurationOptions)
96  {
97  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
98  this.console = console ?? throw new ArgumentNullException(nameof(console));
99  this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
100  this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
101  this.dbConnectionFactory = dbConnectionFactory ?? throw new ArgumentNullException(nameof(dbConnectionFactory));
102  this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
103  this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
104  this.applicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
105  generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
106  }
107 
114  async Task<bool> PromptYesNo(string question, CancellationToken cancellationToken)
115  {
116  do
117  {
118  await console.WriteAsync(question, false, cancellationToken).ConfigureAwait(false);
119  var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
120  var upperResponse = responseString.ToUpperInvariant();
121  if (upperResponse == "Y" || upperResponse == "YES")
122  return true;
123  else if (upperResponse == "N" || upperResponse == "NO")
124  return false;
125  await console.WriteAsync("Invalid response!", true, cancellationToken).ConfigureAwait(false);
126  }
127  while (true);
128  }
129 
135  async Task<ushort?> PromptForHostingPort(CancellationToken cancellationToken)
136  {
137  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
138  await console.WriteAsync("What port would you like to connect to TGS on?", true, cancellationToken).ConfigureAwait(false);
139  await console.WriteAsync("Note: If this is a docker container with the default port already mapped, use the default.", true, cancellationToken).ConfigureAwait(false);
140 
141  do
142  {
143  await console.WriteAsync("API Port (leave blank for default): ", false, cancellationToken).ConfigureAwait(false);
144  var portString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
145  if (String.IsNullOrWhiteSpace(portString))
146  return null;
147  if (UInt16.TryParse(portString, out var port) && port != 0)
148  return port;
149  await console.WriteAsync("Invalid port! Please enter a value between 1 and 65535", true, cancellationToken).ConfigureAwait(false);
150  }
151  while (true);
152  }
153 
164  DbConnection testConnection,
165  DatabaseConfiguration databaseConfiguration,
166  string databaseName,
167  bool dbExists,
168  CancellationToken cancellationToken)
169  {
170  bool isSqliteDB = databaseConfiguration.DatabaseType == DatabaseType.Sqlite;
171  using (testConnection)
172  {
173  await console.WriteAsync("Testing connection...", true, cancellationToken).ConfigureAwait(false);
174  await testConnection.OpenAsync(cancellationToken).ConfigureAwait(false);
175  await console.WriteAsync("Connection successful!", true, cancellationToken).ConfigureAwait(false);
176 
177  if (databaseConfiguration.DatabaseType == DatabaseType.MariaDB
178  || databaseConfiguration.DatabaseType == DatabaseType.MySql)
179  {
180  await console.WriteAsync("Checking MySQL/MariaDB version...", true, cancellationToken).ConfigureAwait(false);
181  using var command = testConnection.CreateCommand();
182  command.CommandText = "SELECT VERSION()";
183  var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
184  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Found {0}", fullVersion), true, cancellationToken).ConfigureAwait(false);
185  var splits = fullVersion.Split('-');
186  databaseConfiguration.MySqlServerVersion = splits.First();
187  }
188 
189  if (!isSqliteDB && !dbExists)
190  {
191  await console.WriteAsync("Testing create DB permission...", true, cancellationToken).ConfigureAwait(false);
192  using (var command = testConnection.CreateCommand())
193  {
194  // I really don't care about user sanitization here, they want to fuck their own DB? so be it
195 #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
196  command.CommandText = $"CREATE DATABASE {databaseName}";
197 #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
198  await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
199  }
200 
201  await console.WriteAsync("Success!", true, cancellationToken).ConfigureAwait(false);
202  await console.WriteAsync("Dropping test database...", true, cancellationToken).ConfigureAwait(false);
203  using (var command = testConnection.CreateCommand())
204  {
205 #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
206  command.CommandText = $"DROP DATABASE {databaseName}";
207 #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
208  try
209  {
210  await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
211  }
212  catch (OperationCanceledException)
213  {
214  throw;
215  }
216  catch (Exception e)
217  {
218  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
219  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
220  await console.WriteAsync("This should be okay, but you may want to manually drop the database before continuing!", true, cancellationToken).ConfigureAwait(false);
221  await console.WriteAsync("Press any key to continue...", true, cancellationToken).ConfigureAwait(false);
222  await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false);
223  }
224  }
225  }
226  }
227 
228  if (isSqliteDB && !dbExists)
229  await Task.WhenAll(
230  console.WriteAsync("Deleting test database file...", true, cancellationToken),
231  ioManager.DeleteFile(databaseName, cancellationToken)).ConfigureAwait(false);
232  }
233 
234  async Task<string> ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken)
235  {
236  var resolvedPath = ioManager.ResolvePath(databaseName);
237  try
238  {
239  var directoryName = ioManager.GetDirectoryName(resolvedPath);
240  bool directoryExisted = await ioManager.DirectoryExists(directoryName, cancellationToken).ConfigureAwait(false);
241  await ioManager.CreateDirectory(directoryName, cancellationToken).ConfigureAwait(false);
242  try
243  {
244  await ioManager.WriteAllBytes(resolvedPath, Array.Empty<byte>(), cancellationToken).ConfigureAwait(false);
245  }
246  catch
247  {
248  if (!directoryExisted)
249  await ioManager.DeleteDirectory(directoryName, cancellationToken).ConfigureAwait(false);
250  throw;
251  }
252  }
253  catch (IOException)
254  {
255  return null;
256  }
257 
258  if (!Path.IsPathRooted(databaseName))
259  {
260  await console.WriteAsync("Note, this relative path (currently) resolves to the following:", true, cancellationToken).ConfigureAwait(false);
261  await console.WriteAsync(resolvedPath, true, cancellationToken).ConfigureAwait(false);
262  bool writeResolved = await PromptYesNo(
263  "Would you like to save the relative path in the configuration? If not, the full path will be saved. (y/n): ",
264  cancellationToken)
265  .ConfigureAwait(false);
266 
267  if (writeResolved)
268  databaseName = resolvedPath;
269  }
270 
271  await ioManager.DeleteFile(databaseName, cancellationToken).ConfigureAwait(false);
272  return databaseName;
273  }
274 
280  async Task<DatabaseType> PromptDatabaseType(CancellationToken cancellationToken)
281  {
282  await console.WriteAsync(String.Empty, true, cancellationToken).ConfigureAwait(false);
283  await console.WriteAsync(
284  "NOTE: It is HIGHLY reccommended that TGS runs on a complete relational database, specfically *NOT* Sqlite.",
285  true,
286  cancellationToken)
287  .ConfigureAwait(false);
288  await console.WriteAsync(
289  "Sqlite, by nature cannot perform several DDL operations. Because of this future compatiblility cannot be guaranteed.",
290  true,
291  cancellationToken)
292  .ConfigureAwait(false);
293  await console.WriteAsync(
294  "This means that you may not be able to update to the next minor version of TGS4 without a clean re-installation!",
295  true,
296  cancellationToken)
297  .ConfigureAwait(false);
298  await console.WriteAsync(
299  "Please consider taking the time to set up a relational database if this is meant to be a long-standing server.",
300  true,
301  cancellationToken)
302  .ConfigureAwait(false);
303  await console.WriteAsync(String.Empty, true, cancellationToken).ConfigureAwait(false);
304 
305  await asyncDelayer.Delay(TimeSpan.FromSeconds(3), cancellationToken).ConfigureAwait(false);
306 
307  await console.WriteAsync("What SQL database type will you be using?", true, cancellationToken).ConfigureAwait(false);
308  do
309  {
310  await console.WriteAsync(
311  String.Format(
312  CultureInfo.InvariantCulture,
313  "Please enter one of {0}, {1}, {2} or {3}: ",
314  DatabaseType.MariaDB,
315  DatabaseType.MySql,
316  DatabaseType.SqlServer,
317  DatabaseType.Sqlite),
318  false,
319  cancellationToken)
320  .ConfigureAwait(false);
321  var databaseTypeString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
322  if (Enum.TryParse<DatabaseType>(databaseTypeString, out var databaseType))
323  return databaseType;
324 
325  await console.WriteAsync("Invalid database type!", true, cancellationToken).ConfigureAwait(false);
326  }
327  while (true);
328  }
329 
335  async Task<DatabaseConfiguration> ConfigureDatabase(CancellationToken cancellationToken)
336  {
337  do
338  {
339  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
340 
341  var databaseConfiguration = new DatabaseConfiguration
342  {
343  DatabaseType = await PromptDatabaseType(cancellationToken).ConfigureAwait(false)
344  };
345 
346  string serverAddress = null;
347  uint? mySQLServerPort = null;
348 
349  bool isSqliteDB = databaseConfiguration.DatabaseType == DatabaseType.Sqlite;
350  if (!isSqliteDB)
351  do
352  {
353  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
354  await console.WriteAsync("Enter the server's address and port [<server>:<port> or <server>] (blank for local): ", false, cancellationToken).ConfigureAwait(false);
355  serverAddress = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
356  if (!String.IsNullOrWhiteSpace(serverAddress) && databaseConfiguration.DatabaseType == DatabaseType.SqlServer)
357  {
358  var match = Regex.Match(serverAddress, @"^(?<server>.+):(?<port>.+)$");
359  if (match.Success)
360  {
361  serverAddress = match.Groups["server"].Value;
362  var portString = match.Groups["port"].Value;
363  if (uint.TryParse(portString, out uint port))
364  mySQLServerPort = port;
365  else
366  {
367  await console.WriteAsync($"Failed to parse port \"{portString}\", please try again.", true, cancellationToken).ConfigureAwait(false);
368  continue;
369  }
370  }
371  }
372 
373  break;
374  }
375  while (true);
376 
377  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
378  await console.WriteAsync($"Enter the database {(isSqliteDB ? "file path" : "name")} (Can be from previous installation. Otherwise, should not exist): ", false, cancellationToken).ConfigureAwait(false);
379 
380  string databaseName;
381  bool dbExists = false;
382  do
383  {
384  databaseName = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
385  if (!String.IsNullOrWhiteSpace(databaseName))
386  {
387  if (isSqliteDB)
388  {
389  dbExists = await ioManager.FileExists(databaseName, cancellationToken).ConfigureAwait(false);
390  if (!dbExists)
391  databaseName = await ValidateNonExistantSqliteDBName(databaseName, cancellationToken).ConfigureAwait(false);
392  }
393  else
394  dbExists = await PromptYesNo("Does this database already exist? If not, we will attempt to CREATE it. (y/n): ", cancellationToken).ConfigureAwait(false);
395  }
396 
397  if (String.IsNullOrWhiteSpace(databaseName))
398  await console.WriteAsync("Invalid database name!", true, cancellationToken).ConfigureAwait(false);
399  else
400  break;
401  }
402  while (true);
403 
404  bool useWinAuth;
405  if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer && platformIdentifier.IsWindows)
406  useWinAuth = await PromptYesNo("Use Windows Authentication? (y/n): ", cancellationToken).ConfigureAwait(false);
407  else
408  useWinAuth = false;
409 
410  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
411 
412  string username = null;
413  string password = null;
414  if (!isSqliteDB)
415  if (!useWinAuth)
416  {
417  await console.WriteAsync("Enter username: ", false, cancellationToken).ConfigureAwait(false);
418  username = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
419  await console.WriteAsync("Enter password: ", false, cancellationToken).ConfigureAwait(false);
420  password = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false);
421  }
422  else
423  {
424  await console.WriteAsync("IMPORTANT: If using the service runner, ensure this computer's LocalSystem account has CREATE DATABASE permissions on the target server!", true, cancellationToken).ConfigureAwait(false);
425  await console.WriteAsync("The account it uses in MSSQL is usually \"NT AUTHORITY\\SYSTEM\" and the role it needs is usually \"dbcreator\".", true, cancellationToken).ConfigureAwait(false);
426  await console.WriteAsync("We'll run a sanity test here, but it won't be indicative of the service's permissions if that is the case", true, cancellationToken).ConfigureAwait(false);
427  }
428 
429  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
430 
431  DbConnection testConnection;
432  void CreateTestConnection(string connectionString) =>
433  testConnection = dbConnectionFactory.CreateConnection(
434  connectionString,
435  databaseConfiguration.DatabaseType);
436 
437  if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer)
438  {
439  var csb = new SqlConnectionStringBuilder
440  {
441  ApplicationName = assemblyInformationProvider.VersionPrefix,
442  DataSource = serverAddress ?? "(local)"
443  };
444 
445  if (useWinAuth)
446  csb.IntegratedSecurity = true;
447  else
448  {
449  csb.UserID = username;
450  csb.Password = password;
451  }
452 
453  CreateTestConnection(csb.ConnectionString);
454  csb.InitialCatalog = databaseName;
455  databaseConfiguration.ConnectionString = csb.ConnectionString;
456  }
457  else if(databaseConfiguration.DatabaseType == DatabaseType.Sqlite)
458  {
459  var csb = new SqliteConnectionStringBuilder
460  {
461  DataSource = databaseName,
462  Mode = dbExists ? SqliteOpenMode.ReadOnly : SqliteOpenMode.ReadWriteCreate
463  };
464 
465  CreateTestConnection(csb.ConnectionString);
466  databaseConfiguration.ConnectionString = csb.ConnectionString;
467  }
468  else
469  {
470  // MySQL/MariaDB
471  var csb = new MySqlConnectionStringBuilder
472  {
473  Server = serverAddress ?? "127.0.0.1",
474  UserID = username,
475  Password = password
476  };
477 
478  if (mySQLServerPort.HasValue)
479  csb.Port = mySQLServerPort.Value;
480 
481  CreateTestConnection(csb.ConnectionString);
482  csb.Database = databaseName;
483  databaseConfiguration.ConnectionString = csb.ConnectionString;
484  }
485 
486  try
487  {
488  await TestDatabaseConnection(testConnection, databaseConfiguration, databaseName, dbExists, cancellationToken).ConfigureAwait(false);
489 
490  return databaseConfiguration;
491  }
492  catch (OperationCanceledException)
493  {
494  throw;
495  }
496  catch (Exception e)
497  {
498  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
499  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
500  await console.WriteAsync("Retrying database configuration...", true, cancellationToken).ConfigureAwait(false);
501  }
502  }
503  while (true);
504  }
505 
511  async Task<GeneralConfiguration> ConfigureGeneral(CancellationToken cancellationToken)
512  {
513  var newGeneralConfiguration = new GeneralConfiguration
514  {
516  };
517 
518  do
519  {
520  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
521  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Minimum database user password length (leave blank for default of {0}): ", newGeneralConfiguration.MinimumPasswordLength), false, cancellationToken).ConfigureAwait(false);
522  var passwordLengthString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
523  if (String.IsNullOrWhiteSpace(passwordLengthString))
524  break;
525  if (UInt32.TryParse(passwordLengthString, out var passwordLength) && passwordLength >= 0)
526  {
527  newGeneralConfiguration.MinimumPasswordLength = passwordLength;
528  break;
529  }
530 
531  await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
532  }
533  while (true);
534 
535  do
536  {
537  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
538  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Timeout for sending and receiving BYOND topics (ms, 0 for infinite, leave blank for default of {0}): ", newGeneralConfiguration.ByondTopicTimeout), false, cancellationToken).ConfigureAwait(false);
539  var topicTimeoutString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
540  if (String.IsNullOrWhiteSpace(topicTimeoutString))
541  break;
542  if (Int32.TryParse(topicTimeoutString, out var topicTimeout) && topicTimeout >= 0)
543  {
544  newGeneralConfiguration.ByondTopicTimeout = topicTimeout;
545  break;
546  }
547 
548  await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
549  }
550  while (true);
551 
552  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
553  await console.WriteAsync("Enter a GitHub personal access token to bypass some rate limits (this is optional and does not require any scopes)", true, cancellationToken).ConfigureAwait(false);
554  await console.WriteAsync("GitHub personal access token: ", false, cancellationToken).ConfigureAwait(false);
555  newGeneralConfiguration.GitHubAccessToken = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false);
556  if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
557  newGeneralConfiguration.GitHubAccessToken = null;
558 
559  // newGeneralConfiguration.UseExperimentalWatchdog = await PromptYesNo("Use the experimental watchdog (NOT RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false);
560  return newGeneralConfiguration;
561  }
562 
568  async Task<FileLoggingConfiguration> ConfigureLogging(CancellationToken cancellationToken)
569  {
570  var fileLoggingConfiguration = new FileLoggingConfiguration();
571  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
572  fileLoggingConfiguration.Disable = !await PromptYesNo("Enable file logging? (y/n): ", cancellationToken).ConfigureAwait(false);
573 
574  if (!fileLoggingConfiguration.Disable)
575  {
576  do
577  {
578  await console.WriteAsync("Log file directory path (leave blank for default): ", false, cancellationToken).ConfigureAwait(false);
579  fileLoggingConfiguration.Directory = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
580  if (String.IsNullOrWhiteSpace(fileLoggingConfiguration.Directory))
581  {
582  fileLoggingConfiguration.Directory = null;
583  break;
584  }
585 
586  // test a write of it
587  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
588  await console.WriteAsync("Testing directory access...", true, cancellationToken).ConfigureAwait(false);
589  try
590  {
591  await ioManager.CreateDirectory(fileLoggingConfiguration.Directory, cancellationToken).ConfigureAwait(false);
592  var testFile = ioManager.ConcatPath(fileLoggingConfiguration.Directory, String.Format(CultureInfo.InvariantCulture, "WizardAccesTest.{0}.deleteme", Guid.NewGuid()));
593  await ioManager.WriteAllBytes(testFile, Array.Empty<byte>(), cancellationToken).ConfigureAwait(false);
594  try
595  {
596  await ioManager.DeleteFile(testFile, cancellationToken).ConfigureAwait(false);
597  }
598  catch (OperationCanceledException)
599  {
600  throw;
601  }
602  catch (Exception e)
603  {
604  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Error deleting test log file: {0}", testFile), true, cancellationToken).ConfigureAwait(false);
605  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
606  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
607  }
608 
609  break;
610  }
611  catch (OperationCanceledException)
612  {
613  throw;
614  }
615  catch (Exception e)
616  {
617  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
618  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
619  await console.WriteAsync("Please verify the path is valid and you have access to it!", true, cancellationToken).ConfigureAwait(false);
620  }
621  }
622  while (true);
623 
624  async Task<LogLevel?> PromptLogLevel(string question)
625  {
626  do
627  {
628  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
629  await console.WriteAsync(question, true, cancellationToken).ConfigureAwait(false);
630  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ", nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false);
631  var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
632  if (String.IsNullOrWhiteSpace(responseString))
633  return null;
634  if (Enum.TryParse<LogLevel>(responseString, out var logLevel) && logLevel != LogLevel.None)
635  return logLevel;
636  await console.WriteAsync("Invalid log level!", true, cancellationToken).ConfigureAwait(false);
637  }
638  while (true);
639  }
640 
641  fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.LogLevel;
642  fileLoggingConfiguration.MicrosoftLogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for Microsoft logs (VERY verbose, default {0}).", fileLoggingConfiguration.MicrosoftLogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.MicrosoftLogLevel;
643  }
644 
645  return fileLoggingConfiguration;
646  }
647 
653  async Task<ControlPanelConfiguration> ConfigureControlPanel(CancellationToken cancellationToken)
654  {
655  var config = new ControlPanelConfiguration
656  {
657  Enable = await PromptYesNo("Enable the web control panel (Incomplete)? (y/n): ", cancellationToken).ConfigureAwait(false),
658  AllowAnyOrigin = await PromptYesNo("Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(false)
659  };
660 
661  if (!config.AllowAnyOrigin)
662  {
663  await console.WriteAsync("Enter a comma seperated list of CORS allowed origins (optional): ", false, cancellationToken).ConfigureAwait(false);
664  var commaSeperatedOrigins = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
665  if (!String.IsNullOrWhiteSpace(commaSeperatedOrigins))
666  {
667  var splits = commaSeperatedOrigins.Split(',');
668  config.AllowedOrigins = new List<string>(splits.Select(x => x.Trim()));
669  }
670  }
671 
672  return config;
673  }
674 
686  async Task SaveConfiguration(string userConfigFileName, ushort? hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, ControlPanelConfiguration controlPanelConfiguration, CancellationToken cancellationToken)
687  {
688  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Configuration complete! Saving to {0}", userConfigFileName), true, cancellationToken).ConfigureAwait(false);
689 
690  var map = new Dictionary<string, object>()
691  {
692  { DatabaseConfiguration.Section, databaseConfiguration },
693  { GeneralConfiguration.Section, newGeneralConfiguration },
694  { FileLoggingConfiguration.Section, fileLoggingConfiguration },
695  { ControlPanelConfiguration.Section, controlPanelConfiguration }
696  };
697 
698  if (hostingPort.HasValue)
699  map.Add("Kestrel", new
700  {
701  EndPoints = new
702  {
703  Http = new
704  {
705  Url = String.Format(CultureInfo.InvariantCulture, "http://0.0.0.0:{0}", hostingPort)
706  }
707  }
708  });
709 
710  var json = JsonConvert.SerializeObject(map, Formatting.Indented);
711  var configBytes = Encoding.UTF8.GetBytes(json);
712 
713  try
714  {
715  await ioManager.WriteAllBytes(userConfigFileName, configBytes, cancellationToken).ConfigureAwait(false);
716  }
717  catch (OperationCanceledException)
718  {
719  throw;
720  }
721  catch (Exception e)
722  {
723  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
724  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
725  await console.WriteAsync("For your convienence, here's the json we tried to write out:", true, cancellationToken).ConfigureAwait(false);
726  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
727  await console.WriteAsync(json, true, cancellationToken).ConfigureAwait(false);
728  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
729  await console.WriteAsync("Press any key to exit...", true, cancellationToken).ConfigureAwait(false);
730  await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false);
731  throw new OperationCanceledException();
732  }
733  }
734 
741  async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken)
742  {
743  // welcome message
744  await console.WriteAsync("Welcome to tgstation-server 4!", true, cancellationToken).ConfigureAwait(false);
745  await console.WriteAsync("This wizard will help you configure your server.", true, cancellationToken).ConfigureAwait(false);
746 
747  var hostingPort = await PromptForHostingPort(cancellationToken).ConfigureAwait(false);
748 
749  var databaseConfiguration = await ConfigureDatabase(cancellationToken).ConfigureAwait(false);
750 
751  var newGeneralConfiguration = await ConfigureGeneral(cancellationToken).ConfigureAwait(false);
752 
753  var fileLoggingConfiguration = await ConfigureLogging(cancellationToken).ConfigureAwait(false);
754 
755  var controlPanelConfiguration = await ConfigureControlPanel(cancellationToken).ConfigureAwait(false);
756 
757  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
758 
759  await SaveConfiguration(userConfigFileName, hostingPort, databaseConfiguration, newGeneralConfiguration, fileLoggingConfiguration, controlPanelConfiguration, cancellationToken).ConfigureAwait(false);
760  }
761 
767  async Task CheckRunWizard(CancellationToken cancellationToken)
768  {
769  var setupWizardMode = generalConfiguration.SetupWizardMode;
770  if (setupWizardMode == SetupWizardMode.Never)
771  return;
772 
773  var forceRun = setupWizardMode == SetupWizardMode.Force || setupWizardMode == SetupWizardMode.Only;
774  if (!console.Available)
775  {
776  if (forceRun)
777  throw new InvalidOperationException("Asked to run setup wizard with no console avaliable!");
778  return;
779  }
780 
781  var userConfigFileName = String.Format(CultureInfo.InvariantCulture, "appsettings.{0}.json", hostingEnvironment.EnvironmentName);
782 
783  async Task HandleSetupCancel()
784  {
785  await console.WriteAsync(String.Empty, true, default).ConfigureAwait(false);
786  await console.WriteAsync("Aborting setup!", true, default).ConfigureAwait(false);
787  }
788 
789  // Link passed cancellationToken with cancel key press
790  Task finalTask = Task.CompletedTask;
791  using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, console.CancelKeyPress))
792  using ((cancellationToken = cts.Token).Register(() => finalTask = HandleSetupCancel()))
793  try
794  {
795  var exists = await ioManager.FileExists(userConfigFileName, cancellationToken).ConfigureAwait(false);
796 
797  bool shouldRunBasedOnAutodetect;
798  if (exists)
799  {
800  var bytes = await ioManager.ReadAllBytes(userConfigFileName, cancellationToken).ConfigureAwait(false);
801  var contents = Encoding.UTF8.GetString(bytes);
802  var existingConfigIsEmpty = String.IsNullOrWhiteSpace(contents) || contents.Trim() == "{}";
803  shouldRunBasedOnAutodetect = existingConfigIsEmpty;
804  }
805  else
806  shouldRunBasedOnAutodetect = true;
807 
808  if (!shouldRunBasedOnAutodetect)
809  {
810  if (forceRun)
811  {
812  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "The configuration settings are requesting the setup wizard be run, but you already appear to have a configuration file ({0})!", userConfigFileName), true, cancellationToken).ConfigureAwait(false);
813 
814  forceRun = await PromptYesNo("Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(false);
815  }
816 
817  if (!forceRun)
818  return;
819  }
820 
821  // flush the logs to prevent console conflicts
822  await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
823 
824  await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(false);
825  }
826  finally
827  {
828  await finalTask.ConfigureAwait(false);
829  }
830  }
831 
833  public async Task StartAsync(CancellationToken cancellationToken)
834  {
835  await CheckRunWizard(cancellationToken).ConfigureAwait(false);
836  applicationLifetime.StopApplication();
837  }
838 
840  public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
841  }
842 }
List< string > AllowedOrigins
Origins allowed for CORS requests
async Task< DatabaseType > PromptDatabaseType(CancellationToken cancellationToken)
Prompt the user for the DatabaseType.
Definition: SetupWizard.cs:280
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the SetupWizard
Definition: SetupWizard.cs:62
@@ -107,14 +107,14 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the GeneralConfiguration res...
async Task< string > ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken)
Definition: SetupWizard.cs:234
-
async Task StartAsync(CancellationToken cancellationToken)
Definition: SetupWizard.cs:836
+
async Task StartAsync(CancellationToken cancellationToken)
Definition: SetupWizard.cs:833
async Task< GeneralConfiguration > ConfigureGeneral(CancellationToken cancellationToken)
Prompts the user to create a GeneralConfiguration
Definition: SetupWizard.cs:511
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the DatabaseConfiguration re...
DatabaseType DatabaseType
The Configuration.DatabaseType to create
-
async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken)
Runs the SetupWizard
Definition: SetupWizard.cs:744
+
async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken)
Runs the SetupWizard
Definition: SetupWizard.cs:741
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the ControlPanelConfiguratio...
async Task< ushort?> PromptForHostingPort(CancellationToken cancellationToken)
Prompts the user to enter the port to host TGS on
Definition: SetupWizard.cs:135
@@ -124,23 +124,23 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
uint MinimumPasswordLength
Minimum length of database user passwords.
readonly IHostApplicationLifetime applicationLifetime
The IHostApplicationLifetime for the SetupWizard.
Definition: SetupWizard.cs:67
-
async Task< ControlPanelConfiguration > ConfigureControlPanel(CancellationToken cancellationToken)
Prompts the user to create a ControlPanelConfiguration
Definition: SetupWizard.cs:656
+
async Task< ControlPanelConfiguration > ConfigureControlPanel(CancellationToken cancellationToken)
Prompts the user to create a ControlPanelConfiguration
Definition: SetupWizard.cs:653
SetupWizard(IIOManager ioManager, IConsole console, IHostEnvironment hostingEnvironment, IAssemblyInformationProvider assemblyInformationProvider, IDatabaseConnectionFactory dbConnectionFactory, IPlatformIdentifier platformIdentifier, IAsyncDelayer asyncDelayer, IHostApplicationLifetime applicationLifetime, IOptions< GeneralConfiguration > generalConfigurationOptions)
Construct a SetupWizard
Definition: SetupWizard.cs:86
async Task< bool > PromptYesNo(string question, CancellationToken cancellationToken)
A prompt for a yes or no value
Definition: SetupWizard.cs:114
readonly IConsole console
The IConsole for the SetupWizard
Definition: SetupWizard.cs:37
string MySqlServerVersion
The string form of the global::System.Version of a target MySQL/MariaDB server
-
async Task CheckRunWizard(CancellationToken cancellationToken)
Check if it should and run the SetupWizard if necessary.
Definition: SetupWizard.cs:770
+
async Task CheckRunWizard(CancellationToken cancellationToken)
Check if it should and run the SetupWizard if necessary.
Definition: SetupWizard.cs:767
Configuration options for the Database.DatabaseContext<TParentContext>
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the SetupWizard
Definition: SetupWizard.cs:57
-
async Task< FileLoggingConfiguration > ConfigureLogging(CancellationToken cancellationToken)
Prompts the user to create a FileLoggingConfiguration
Definition: SetupWizard.cs:571
+
async Task< FileLoggingConfiguration > ConfigureLogging(CancellationToken cancellationToken)
Prompts the user to create a FileLoggingConfiguration
Definition: SetupWizard.cs:568
DatabaseType
Type of database to user
Definition: DatabaseType.cs:6
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the SetupWizard
Definition: SetupWizard.cs:47
-
async Task SaveConfiguration(string userConfigFileName, ushort?hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, ControlPanelConfiguration controlPanelConfiguration, CancellationToken cancellationToken)
Saves a given Configuration set to userConfigFileName
Definition: SetupWizard.cs:689
+
async Task SaveConfiguration(string userConfigFileName, ushort?hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, ControlPanelConfiguration controlPanelConfiguration, CancellationToken cancellationToken)
Saves a given Configuration set to userConfigFileName
Definition: SetupWizard.cs:686
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the FileLoggingConfiguration...
Interface for using filesystems
Definition: IIOManager.cs:11
diff --git a/_test_merge_base_8cs_source.html b/_test_merge_base_8cs_source.html index f81f165553..8da15eed2d 100644 --- a/_test_merge_base_8cs_source.html +++ b/_test_merge_base_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
TestMergeBase.cs
-Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
3 
4 namespace Tgstation.Server.Api.Models.Internal
5 {
9  public abstract class TestMergeBase : TestMergeParameters
10  {
14  [Required]
15  public string TitleAtMerge { get; set; }
16 
20  [Required]
21  public string BodyAtMerge { get; set; }
22 
26  [Required]
27 #pragma warning disable CA1056 // Uri properties should not be strings
28  public string Url { get; set; }
29 #pragma warning restore CA1056 // Uri properties should not be strings
30 
34  [Required]
35  public string Author { get; set; }
36 
40  protected TestMergeBase() { }
41 
46  protected TestMergeBase(TestMergeBase copy)
47  {
48  if (copy == null)
49  throw new ArgumentNullException(nameof(copy));
50  Author = copy.Author;
51  BodyAtMerge = copy.BodyAtMerge;
52  Comment = copy.Comment;
53  Number = copy.Number;
54  PullRequestRevision = copy.PullRequestRevision;
55  TitleAtMerge = copy.TitleAtMerge;
56  Url = copy.Url;
57  }
58  }
59 }
+Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
3 
4 namespace Tgstation.Server.Api.Models.Internal
5 {
9  public abstract class TestMergeBase : TestMergeParameters
10  {
14  [Required]
15  public string? TitleAtMerge { get; set; }
16 
20  [Required]
21  public string? BodyAtMerge { get; set; }
22 
26  [Required]
27 #pragma warning disable CA1056 // Uri properties should not be strings
28  public string? Url { get; set; }
29 #pragma warning restore CA1056 // Uri properties should not be strings
30 
34  [Required]
35  public string? Author { get; set; }
36 
40  protected TestMergeBase() { }
41 
46  protected TestMergeBase(TestMergeBase copy)
47  {
48  if (copy == null)
49  throw new ArgumentNullException(nameof(copy));
50  Author = copy.Author;
51  BodyAtMerge = copy.BodyAtMerge;
52  Comment = copy.Comment;
53  Number = copy.Number;
54  PullRequestRevision = copy.PullRequestRevision;
55  TitleAtMerge = copy.TitleAtMerge;
56  Url = copy.Url;
57  }
58  }
59 }
string TitleAtMerge
The title of the pull request
string Author
The author of the pull request
diff --git a/_test_merge_parameters_8cs_source.html b/_test_merge_parameters_8cs_source.html index 4285b2cc91..1c08646187 100644 --- a/_test_merge_parameters_8cs_source.html +++ b/_test_merge_parameters_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
TestMergeParameters.cs
-Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public class TestMergeParameters
9  {
13  public int Number { get; set; }
14 
18  [Required]
19  [StringLength(40)]
20  public string PullRequestRevision { get; set; }
21 
25  [StringLength(Limits.MaximumStringLength)]
26  public string Comment { get; set; }
27  }
28 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
+Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public class TestMergeParameters
9  {
13  public int Number { get; set; }
14 
18  [Required]
19  [StringLength(40)]
20  public string? PullRequestRevision { get; set; }
21 
25  [StringLength(Limits.MaximumStringLength)]
26  public string? Comment { get; set; }
27  }
28 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
Sanity limits to prevent users from overloading
Definition: Limits.cs:6
diff --git a/_tgstation_8_server_8_api_2_models_2_chat_bot_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_chat_bot_8cs_source.html index 8f03fe3957..2e1d2627c3 100644 --- a/_tgstation_8_server_8_api_2_models_2_chat_bot_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_chat_bot_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ChatBot.cs
-Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 
5 namespace Tgstation.Server.Api.Models
6 {
8  public sealed class ChatBot : Internal.ChatBot
9  {
13  public ICollection<ChatChannel> Channels { get; set; }
14 
20  {
21  if (!Provider.HasValue)
22  return true;
23  switch (Provider.Value)
24  {
25  case ChatProvider.Discord:
26  return Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true;
27  case ChatProvider.Irc:
28  return Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true;
29  default:
30  throw new InvalidOperationException("Invalid provider type!");
31  }
32  }
33  }
34 }
+Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 
5 namespace Tgstation.Server.Api.Models
6 {
8  public sealed class ChatBot : Internal.ChatBot
9  {
13  public ICollection<ChatChannel>? Channels { get; set; }
14 
20  {
21  if (!Provider.HasValue)
22  return true;
23  return Provider.Value switch
24  {
25  ChatProvider.Discord => Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true,
26  ChatProvider.Irc => Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true,
27  _ => throw new InvalidOperationException("Invalid provider type!"),
28  };
29  }
30  }
31 }
bool ValidateProviderChannelTypes()
Validates Channels are correct for the Internal.ChatBot.Provider
Definition: ChatBot.cs:19
diff --git a/_tgstation_8_server_8_api_2_models_2_chat_channel_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_chat_channel_8cs_source.html index 3ce294a0a5..698bfd9916 100644 --- a/_tgstation_8_server_8_api_2_models_2_chat_channel_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_chat_channel_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ChatChannel.cs
-Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public class ChatChannel
9  {
14  [StringLength(Limits.MaximumIndexableStringLength)]
15  public string IrcChannel { get; set; }
16 
20  public ulong? DiscordChannelId { get; set; }
21 
25  [Required]
26  public bool? IsAdminChannel { get; set; }
27 
31  [Required]
32  public bool? IsWatchdogChannel { get; set; }
33 
37  [Required]
38  public bool? IsUpdatesChannel { get; set; }
39 
43  [StringLength(Limits.MaximumStringLength)]
44  public string Tag { get; set; }
45  }
46 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
+Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public class ChatChannel
9  {
14  [StringLength(Limits.MaximumIndexableStringLength)]
15  public string? IrcChannel { get; set; }
16 
20  public ulong? DiscordChannelId { get; set; }
21 
25  [Required]
26  public bool? IsAdminChannel { get; set; }
27 
31  [Required]
32  public bool? IsWatchdogChannel { get; set; }
33 
37  [Required]
38  public bool? IsUpdatesChannel { get; set; }
39 
43  [StringLength(Limits.MaximumStringLength)]
44  public string? Tag { get; set; }
45  }
46 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
Indicates a chat channel
Definition: ChatChannel.cs:8
diff --git a/_tgstation_8_server_8_api_2_models_2_compile_job_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_compile_job_8cs_source.html index 5736db1f99..12409c856f 100644 --- a/_tgstation_8_server_8_api_2_models_2_compile_job_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_compile_job_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
CompileJob.cs
-Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
6  public sealed class CompileJob : Internal.CompileJob
7  {
11  public Job Job { get; set; }
12 
17 
21  public Version ByondVersion { get; set; }
22  }
23 }
+Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
6  public sealed class CompileJob : Internal.CompileJob
7  {
11  public Job? Job { get; set; }
12 
17 
21  public Version? ByondVersion { get; set; }
22  }
23 }
diff --git a/_tgstation_8_server_8_api_2_models_2_dream_maker_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_dream_maker_8cs_source.html index ffb5281bfa..4da70a7e10 100644 --- a/_tgstation_8_server_8_api_2_models_2_dream_maker_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_dream_maker_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
DreamMaker.cs
-Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public class DreamMaker
9  {
13  [StringLength(Limits.MaximumStringLength)]
14  public string ProjectName { get; set; }
15 
19  [Required]
20  [Range(1, 65535)]
21  public ushort? ApiValidationPort { get; set; }
22 
26  [Required]
27  public DreamDaemonSecurity? ApiValidationSecurityLevel { get; set; }
28  }
29 }
Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile
Definition: DreamMaker.cs:8
+Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public class DreamMaker
9  {
13  [StringLength(Limits.MaximumStringLength)]
14  public string? ProjectName { get; set; }
15 
19  [Required]
20  [Range(1, 65535)]
21  public ushort? ApiValidationPort { get; set; }
22 
26  [Required]
27  public DreamDaemonSecurity? ApiValidationSecurityLevel { get; set; }
28  }
29 }
Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile
Definition: DreamMaker.cs:8
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
diff --git a/_tgstation_8_server_8_api_2_models_2_instance_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_instance_8cs_source.html index 6a94aa314d..dbfaa3f7a1 100644 --- a/_tgstation_8_server_8_api_2_models_2_instance_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_instance_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
Instance.cs
-Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 using System.ComponentModel.DataAnnotations.Schema;
3 
4 namespace Tgstation.Server.Api.Models
5 {
9  public class Instance
10  {
14  public long Id { get; set; }
15 
19  [Required]
20  [StringLength(Limits.MaximumStringLength)]
21  public string Name { get; set; }
22 
26  [Required]
27  public string Path { get; set; }
28 
32  [Required]
33  public bool? Online { get; set; }
34 
38  [Required]
39  [EnumDataType(typeof(ConfigurationType))]
40  public ConfigurationType? ConfigurationType { get; set; }
41 
45  [Required]
46  public uint? AutoUpdateInterval { get; set; }
47 
51  [Required]
52  public ushort? ChatBotLimit { get; set; }
53 
58  [NotMapped]
59  public Job MoveJob { get; set; }
60 
65  public Instance CloneMetadata() => new Instance
66  {
67  Id = Id,
68  Name = Name,
69  Path = Path,
70  Online = Online,
72  AutoUpdateInterval = AutoUpdateInterval
73  };
74  }
75 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
+Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 using System.ComponentModel.DataAnnotations.Schema;
3 
4 namespace Tgstation.Server.Api.Models
5 {
9  public class Instance
10  {
14  public long Id { get; set; }
15 
19  [Required]
20  [StringLength(Limits.MaximumStringLength)]
21  public string? Name { get; set; }
22 
26  [Required]
27  public string? Path { get; set; }
28 
32  [Required]
33  public bool? Online { get; set; }
34 
38  [Required]
39  [EnumDataType(typeof(ConfigurationType))]
40  public ConfigurationType? ConfigurationType { get; set; }
41 
45  [Required]
46  public uint? AutoUpdateInterval { get; set; }
47 
51  [Required]
52  public ushort? ChatBotLimit { get; set; }
53 
58  [NotMapped]
59  public Job? MoveJob { get; set; }
60 
65  public Instance CloneMetadata() => new Instance
66  {
67  Id = Id,
68  Name = Name,
69  Path = Path,
70  Online = Online,
72  AutoUpdateInterval = AutoUpdateInterval
73  };
74  }
75 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
Sanity limits to prevent users from overloading
Definition: Limits.cs:6
diff --git a/_tgstation_8_server_8_api_2_models_2_internal_2_chat_bot_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_internal_2_chat_bot_8cs_source.html index 6253332a66..1c27b82c90 100644 --- a/_tgstation_8_server_8_api_2_models_2_internal_2_chat_bot_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_internal_2_chat_bot_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ChatBot.cs
-Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
3 
4 namespace Tgstation.Server.Api.Models.Internal
5 {
9  public class ChatBot
10  {
14  public long Id { get; set; }
15 
19  [Required]
20  [StringLength(Limits.MaximumIndexableStringLength)]
21  public string Name { get; set; }
22 
26  public bool? Enabled { get; set; }
27 
31  [Required]
32  [Range(1, UInt32.MaxValue)]
33  public uint? ReconnectionInterval { get; set; }
34 
38  [Required]
39  public ushort? ChannelLimit { get; set; }
40 
44  [Required]
45  [EnumDataType(typeof(ChatProvider))]
46  public ChatProvider? Provider { get; set; }
47 
51  [Required]
52  [StringLength(Limits.MaximumStringLength)]
53  public string ConnectionString { get; set; }
54 
60  {
61  if (ConnectionString == null)
62  return null;
63  switch (Provider)
64  {
65  case ChatProvider.Discord:
66  return new DiscordConnectionStringBuilder(ConnectionString);
67  case ChatProvider.Irc:
68  return new IrcConnectionStringBuilder(ConnectionString);
69  default:
70  throw new InvalidOperationException("Invalid Provider!");
71  }
72  }
73 
79  {
80  ConnectionString = stringBuilder?.ToString();
81  }
82  }
83 }
abstract override string ToString()
Gets the ChatBot.ConnectionString associated with the ChatConnectionStringBuilder ...
+Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
3 
4 namespace Tgstation.Server.Api.Models.Internal
5 {
9  public class ChatBot
10  {
14  public long Id { get; set; }
15 
19  [Required]
20  [StringLength(Limits.MaximumIndexableStringLength)]
21  public string? Name { get; set; }
22 
26  public bool? Enabled { get; set; }
27 
31  [Required]
32  [Range(1, UInt32.MaxValue)]
33  public uint? ReconnectionInterval { get; set; }
34 
38  [Required]
39  public ushort? ChannelLimit { get; set; }
40 
44  [Required]
45  [EnumDataType(typeof(ChatProvider))]
46  public ChatProvider? Provider { get; set; }
47 
51  [Required]
52  [StringLength(Limits.MaximumStringLength)]
53  public string? ConnectionString { get; set; }
54 
60  {
61  if (ConnectionString == null)
62  return null;
63  return Provider switch
64  {
65  ChatProvider.Discord => new DiscordConnectionStringBuilder(ConnectionString),
66  ChatProvider.Irc => new IrcConnectionStringBuilder(ConnectionString),
67  _ => throw new InvalidOperationException("Invalid Provider!"),
68  };
69  }
70 
76  {
77  ConnectionString = stringBuilder?.ToString() ?? throw new ArgumentNullException(nameof(stringBuilder));
78  }
79  }
80 }
abstract override string ToString()
Gets the ChatBot.ConnectionString associated with the ChatConnectionStringBuilder ...
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
@@ -106,7 +106,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
ChatConnectionStringBuilder CreateConnectionStringBuilder()
Get the ChatConnectionStringBuilder which maps to the ConnectionString.
Definition: ChatBot.cs:59
ChatConnectionStringBuilder for ChatProvider.Irc
ChatProvider
Represents a chat service provider
Definition: ChatProvider.cs:6
-
void SetConnectionStringBuilder(ChatConnectionStringBuilder stringBuilder)
Set the ChatConnectionStringBuilder for the ChatBot. Also updates the ConnectionString.
Definition: ChatBot.cs:78
+
void SetConnectionStringBuilder(ChatConnectionStringBuilder stringBuilder)
Set the ChatConnectionStringBuilder for the ChatBot. Also updates the ConnectionString.
Definition: ChatBot.cs:75
diff --git a/_tgstation_8_server_8_api_2_models_2_internal_2_compile_job_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_internal_2_compile_job_8cs_source.html index 7206ddaf26..633d9d9fd7 100644 --- a/_tgstation_8_server_8_api_2_models_2_internal_2_compile_job_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_internal_2_compile_job_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
CompileJob.cs
-Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
3 using System.ComponentModel.DataAnnotations.Schema;
4 
5 namespace Tgstation.Server.Api.Models.Internal
6 {
10  public class CompileJob : EntityId
11  {
15  [Required]
16  public string DmeName { get; set; }
17 
21  [Required]
22  public string Output { get; set; }
23 
27  [Required]
28  public Guid? DirectoryName { get; set; }
29 
33  [Required]
34  public DreamDaemonSecurity? MinimumSecurityLevel { get; set; }
35 
39  [NotMapped]
40  public virtual Version DMApiVersion { get; set; }
41  }
42 }
+Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
3 using System.ComponentModel.DataAnnotations.Schema;
4 
5 namespace Tgstation.Server.Api.Models.Internal
6 {
10  public class CompileJob : EntityId
11  {
15  [Required]
16  public string? DmeName { get; set; }
17 
21  [Required]
22  public string? Output { get; set; }
23 
27  [Required]
28  public Guid? DirectoryName { get; set; }
29 
33  [Required]
34  public DreamDaemonSecurity? MinimumSecurityLevel { get; set; }
35 
39  [NotMapped]
40  public virtual Version? DMApiVersion { get; set; }
41  }
42 }
Represents a run of DreamMaker
Definition: CompileJob.cs:10
Common base of CompileJobs and Jobs.
Definition: EntityId.cs:6
diff --git a/_tgstation_8_server_8_api_2_models_2_internal_2_job_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_internal_2_job_8cs_source.html index 4ed0889c29..eb120bb666 100644 --- a/_tgstation_8_server_8_api_2_models_2_internal_2_job_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_internal_2_job_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
Job.cs
-Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
4 
5 namespace Tgstation.Server.Api.Models.Internal
6 {
10  public class Job : EntityId
11  {
15  [Required]
16  public string Description { get; set; }
17 
21  public ErrorCode? ErrorCode { get; set; }
22 
26  public string ExceptionDetails { get; set; }
27 
31  [Required]
32  public DateTimeOffset? StartedAt { get; set; }
33 
37  public DateTimeOffset? StoppedAt { get; set; }
38 
42  [Required]
43  public bool? Cancelled { get; set; }
44 
48  public RightsType? CancelRightsType { get; set; }
49 
53  public ulong? CancelRight { get; set; }
54  }
55 }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
+Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
4 
5 namespace Tgstation.Server.Api.Models.Internal
6 {
10  public class Job : EntityId
11  {
15  [Required]
16  public string? Description { get; set; }
17 
21  public ErrorCode? ErrorCode { get; set; }
22 
26  public string? ExceptionDetails { get; set; }
27 
31  [Required]
32  public DateTimeOffset? StartedAt { get; set; }
33 
37  public DateTimeOffset? StoppedAt { get; set; }
38 
42  [Required]
43  public bool? Cancelled { get; set; }
44 
48  public RightsType? CancelRightsType { get; set; }
49 
53  public ulong? CancelRight { get; set; }
54  }
55 }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
RightsType
The type of rights a model uses
Definition: RightsType.cs:6
diff --git a/_tgstation_8_server_8_api_2_models_2_internal_2_repository_settings_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_internal_2_repository_settings_8cs_source.html index f2028a89d3..9e5979ee8e 100644 --- a/_tgstation_8_server_8_api_2_models_2_internal_2_repository_settings_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_internal_2_repository_settings_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
RepositorySettings.cs
-Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models.Internal
4 {
8  public class RepositorySettings
9  {
13  [Required]
14  [StringLength(Limits.MaximumStringLength)]
15  public string CommitterName { get; set; }
16 
20  [Required]
21  [StringLength(Limits.MaximumStringLength)]
22  [EmailAddress]
23  public string CommitterEmail { get; set; }
24 
28  [StringLength(Limits.MaximumStringLength)]
29  public string AccessUser { get; set; }
30 
34  [StringLength(Limits.MaximumStringLength)]
35  public string AccessToken { get; set; }
36 
40  [Required]
41  public bool? PushTestMergeCommits { get; set; }
42 
46  [Required]
47  public bool? ShowTestMergeCommitters { get; set; }
48 
52  [Required]
53  public bool? AutoUpdatesKeepTestMerges { get; set; }
54 
58  [Required]
59  public bool? AutoUpdatesSynchronize { get; set; }
60 
64  [Required]
65  public bool? PostTestMergeComment { get; set; }
66  }
67 }
Represents configurable settings for a Repository
+Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models.Internal
4 {
8  public class RepositorySettings
9  {
13  [Required]
14  [StringLength(Limits.MaximumStringLength)]
15  public string? CommitterName { get; set; }
16 
20  [Required]
21  [StringLength(Limits.MaximumStringLength)]
22  [EmailAddress]
23  public string? CommitterEmail { get; set; }
24 
28  [StringLength(Limits.MaximumStringLength)]
29  public string? AccessUser { get; set; }
30 
34  [StringLength(Limits.MaximumStringLength)]
35  public string? AccessToken { get; set; }
36 
40  [Required]
41  public bool? PushTestMergeCommits { get; set; }
42 
46  [Required]
47  public bool? ShowTestMergeCommitters { get; set; }
48 
52  [Required]
53  public bool? AutoUpdatesKeepTestMerges { get; set; }
54 
58  [Required]
59  public bool? AutoUpdatesSynchronize { get; set; }
60 
64  [Required]
65  public bool? PostTestMergeComment { get; set; }
66  }
67 }
Represents configurable settings for a Repository
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
diff --git a/_tgstation_8_server_8_api_2_models_2_internal_2_revision_information_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_internal_2_revision_information_8cs_source.html index af3ffd6d1b..bf84640771 100644 --- a/_tgstation_8_server_8_api_2_models_2_internal_2_revision_information_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_internal_2_revision_information_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
RevisionInformation.cs
-Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models.Internal
4 {
8  public class RevisionInformation
9  {
13  [Required]
14  [StringLength(40)]
15  public string CommitSha { get; set; }
16 
20  [Required]
21  [StringLength(40)]
22  public string OriginCommitSha { get; set; }
23  }
24 }
+Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models.Internal
4 {
8  public class RevisionInformation
9  {
13  [Required]
14  [StringLength(40)]
15  public string? CommitSha { get; set; }
16 
20  [Required]
21  [StringLength(40)]
22  public string? OriginCommitSha { get; set; }
23  }
24 }
Represents information about a current git revison
diff --git a/_tgstation_8_server_8_api_2_models_2_internal_2_user_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_internal_2_user_8cs_source.html index d21ff2bd74..fd24dfdb46 100644 --- a/_tgstation_8_server_8_api_2_models_2_internal_2_user_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_internal_2_user_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
User.cs
-Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
4 
5 namespace Tgstation.Server.Api.Models.Internal
6 {
10  public class User
11  {
15  [Required]
16  public long? Id { get; set; }
17 
21  [Required]
22  public bool? Enabled { get; set; }
23 
27  [Required]
28  public DateTimeOffset? CreatedAt { get; set; }
29 
33  // No need for StringLength as the server MUST validate it.
34  public string SystemIdentifier { get; set; }
35 
39  [Required]
40  [StringLength(Limits.MaximumStringLength)]
41  public string Name { get; set; }
42 
46  [Required]
48 
52  [Required]
54  }
55 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
+Go to the documentation of this file.
1 using System;
2 using System.ComponentModel.DataAnnotations;
4 
5 namespace Tgstation.Server.Api.Models.Internal
6 {
10  public class User
11  {
15  [Required]
16  public long? Id { get; set; }
17 
21  [Required]
22  public bool? Enabled { get; set; }
23 
27  [Required]
28  public DateTimeOffset? CreatedAt { get; set; }
29 
33  // No need for StringLength as the server MUST validate it.
34  public string? SystemIdentifier { get; set; }
35 
39  [Required]
40  [StringLength(Limits.MaximumStringLength)]
41  public string? Name { get; set; }
42 
46  [Required]
48 
52  [Required]
54  }
55 }
const int MaximumStringLength
Length limit for strings in fields.
Definition: Limits.cs:11
Represents a server User
Definition: User.cs:10
diff --git a/_tgstation_8_server_8_api_2_models_2_job_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_job_8cs_source.html index a2480a150f..a1d908d261 100644 --- a/_tgstation_8_server_8_api_2_models_2_job_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_job_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
Job.cs
-Go to the documentation of this file.
1 namespace Tgstation.Server.Api.Models
2 {
6  public sealed class Job : Internal.Job
7  {
11  public User StartedBy { get; set; }
12 
16  public User CancelledBy { get; set; }
17 
21  public int? Progress { get; set; }
22  }
23 }
+Go to the documentation of this file.
1 namespace Tgstation.Server.Api.Models
2 {
6  public sealed class Job : Internal.Job
7  {
11  public User? StartedBy { get; set; }
12 
16  public User? CancelledBy { get; set; }
17 
21  public int? Progress { get; set; }
22  }
23 }
Represents a long running job on the server. Model is read-only, updates attempt to cancel the job ...
Definition: Job.cs:6
diff --git a/_tgstation_8_server_8_api_2_models_2_repository_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_repository_8cs_source.html index 6f05dbb38d..fc6df5ab5b 100644 --- a/_tgstation_8_server_8_api_2_models_2_repository_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_repository_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
Repository.cs
-Go to the documentation of this file.
1 using System.Collections.Generic;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class Repository : Internal.RepositorySettings
9  {
13  public string Origin { get; set; }
14 
18  public string CheckoutSha { get; set; }
19 
24 
28  public string GitHubOwner { get; set; }
29 
33  public string GitHubName { get; set; }
34 
38  public Job ActiveJob { get; set; }
39 
43  public bool? UpdateFromOrigin { get; set; }
44 
48  public string Reference { get; set; }
49 
53  public List<TestMergeParameters> NewTestMerges { get; set; }
54  }
55 }
+Go to the documentation of this file.
1 using System.Collections.Generic;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class Repository : Internal.RepositorySettings
9  {
13  public string? Origin { get; set; }
14 
18  public string? CheckoutSha { get; set; }
19 
24 
28  public string? GitHubOwner { get; set; }
29 
33  public string? GitHubName { get; set; }
34 
38  public Job? ActiveJob { get; set; }
39 
43  public bool? UpdateFromOrigin { get; set; }
44 
48  public string? Reference { get; set; }
49 
53  public ICollection<TestMergeParameters>? NewTestMerges { get; set; }
54  }
55 }
Represents a git repository
Definition: Repository.cs:8
diff --git a/_tgstation_8_server_8_api_2_models_2_revision_information_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_revision_information_8cs_source.html index 93a6c3934a..31a7bdb9dd 100644 --- a/_tgstation_8_server_8_api_2_models_2_revision_information_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_revision_information_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
RevisionInformation.cs
-Go to the documentation of this file.
1 using System.Collections.Generic;
2 
3 namespace Tgstation.Server.Api.Models
4 {
6  public sealed class RevisionInformation : Internal.RevisionInformation
7  {
11  public TestMerge PrimaryTestMerge { get; set; }
12 
16  public ICollection<TestMerge> ActiveTestMerges { get; set; }
17 
21  public ICollection<CompileJob> CompileJobs { get; set; }
22  }
23 }
+Go to the documentation of this file.
1 using System.Collections.Generic;
2 
3 namespace Tgstation.Server.Api.Models
4 {
6  public sealed class RevisionInformation : Internal.RevisionInformation
7  {
11  public TestMerge? PrimaryTestMerge { get; set; }
12 
16  public ICollection<TestMerge>? ActiveTestMerges { get; set; }
17 
21  public ICollection<CompileJob>? CompileJobs { get; set; }
22  }
23 }
diff --git a/_tgstation_8_server_8_api_2_models_2_test_merge_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_test_merge_8cs_source.html index 6b1b090eb4..93d862d225 100644 --- a/_tgstation_8_server_8_api_2_models_2_test_merge_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_test_merge_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
TestMerge.cs
-Go to the documentation of this file.
1 namespace Tgstation.Server.Api.Models
2 {
4  public sealed class TestMerge : Internal.TestMerge
5  {
9  public User MergedBy { get; set; }
10  }
11 }
+Go to the documentation of this file.
1 namespace Tgstation.Server.Api.Models
2 {
4  public sealed class TestMerge : Internal.TestMerge
5  {
9  public User? MergedBy { get; set; }
10  }
11 }
diff --git a/_tgstation_8_server_8_api_2_models_2_user_8cs_source.html b/_tgstation_8_server_8_api_2_models_2_user_8cs_source.html index cc0d061205..03ebbf3aa8 100644 --- a/_tgstation_8_server_8_api_2_models_2_user_8cs_source.html +++ b/_tgstation_8_server_8_api_2_models_2_user_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
User.cs
-Go to the documentation of this file.
1 namespace Tgstation.Server.Api.Models
2 {
4  public class User : Internal.User
5  {
9  public const string AdminName = "Admin";
10 
14  public const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory";
15 
19  public User CreatedBy { get; set; }
20  }
21 }
+Go to the documentation of this file.
1 namespace Tgstation.Server.Api.Models
2 {
4  public class User : Internal.User
5  {
9  public static readonly string AdminName = "Admin";
10 
14  public static readonly string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory";
15 
19  public User? CreatedBy { get; set; }
20  }
21 }
diff --git a/_tgstation_8_server_8_host_2_components_2_repository_2_repository_8cs_source.html b/_tgstation_8_server_8_host_2_components_2_repository_2_repository_8cs_source.html index 84399fe0a9..41f957db38 100644 --- a/_tgstation_8_server_8_host_2_components_2_repository_2_repository_8cs_source.html +++ b/_tgstation_8_server_8_host_2_components_2_repository_2_repository_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
Repository.cs
-Go to the documentation of this file.
1 using LibGit2Sharp;
2 using LibGit2Sharp.Handlers;
3 using Microsoft.Extensions.Logging;
4 using System;
5 using System.Collections.Generic;
6 using System.Globalization;
7 using System.Linq;
8 using System.Threading;
9 using System.Threading.Tasks;
11 using Tgstation.Server.Host.IO;
13 
14 namespace Tgstation.Server.Host.Components.Repository
15 {
17  sealed class Repository : IRepository
18  {
22  public const string GitHubUrl = "://github.com/";
23 
27  public const string OriginTrackingErrorTemplate = "Unable to determine most recent origin commit of {0}. Marking it as an origin commit. This may result in invalid git metadata until the next hard reset to an origin reference.";
28 
32  public const string RemoteTemporaryBranchName = "___TGSTempBranch";
33 
34  const string UnknownReference = "<UNKNOWN>";
35 
37  public bool IsGitHubRepository { get; }
38 
40  public string GitHubOwner { get; }
41 
43  public string GitHubRepoName { get; }
44 
46  public bool Tracking => Reference != null && libGitRepo.Head.IsTracking;
47 
49  public string Head => libGitRepo.Head.Tip.Sha;
50 
52  public string Reference => libGitRepo.Head.FriendlyName;
53 
55  public string Origin => libGitRepo.Network.Remotes.First().Url;
56 
60  readonly LibGit2Sharp.IRepository libGitRepo;
61 
66 
71 
76 
81 
85  readonly ILogger<Repository> logger;
86 
90  readonly Action onDispose;
91 
95  bool disposed;
96 
102  static CheckoutProgressHandler CheckoutProgressHandler(Action<int> progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)(((float)completedSteps) / totalSteps * 100));
103 
114  public Repository(
115  LibGit2Sharp.IRepository libGitRepo,
116  ILibGit2Commands commands,
117  IIOManager ioMananger,
118  IEventConsumer eventConsumer,
119  ICredentialsProvider credentialsProvider,
120  ILogger<Repository> logger,
121  Action onDispose)
122  {
123  this.libGitRepo = libGitRepo ?? throw new ArgumentNullException(nameof(libGitRepo));
124  this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
125  this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger));
126  this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
127  this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
128  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
129  this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
130 
131  IsGitHubRepository = Origin.Contains(GitHubUrl, StringComparison.InvariantCultureIgnoreCase);
132 
133  if (IsGitHubRepository)
134  {
135  GetRepositoryOwnerName(Origin, out var owner, out var name);
136  GitHubOwner = owner;
137  GitHubRepoName = name;
138  }
139  }
140 
142  public void Dispose()
143  {
144  lock (onDispose)
145  {
146  if (disposed)
147  return;
148 
149  disposed = true;
150  }
151 
152  logger.LogTrace("Disposing...");
153  libGitRepo.Dispose();
154  onDispose();
155  }
156 
157  void GetRepositoryOwnerName(string remote, out string owner, out string name)
158  {
159  // Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
160  // Yes use .git twice in case it was weird
161  var toRemove = new string[] { ".git", "/", ".git" };
162  foreach (string item in toRemove)
163  if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase))
164  remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase));
165  var splits = remote.Split('/');
166  name = splits.Last();
167  owner = splits[^2].Split('.').First();
168 
169  logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name);
170  }
171 
180  PushOptions GeneratePushOptions(Action<int> progressReporter, string username, string password, CancellationToken cancellationToken) => new PushOptions
181  {
182  OnPackBuilderProgress = (stage, current, total) =>
183  {
184  var baseProgress = stage == PackBuilderStage.Counting ? 0 : 25;
185  progressReporter(baseProgress + ((int)(25 * ((float)current) / total)));
186  return !cancellationToken.IsCancellationRequested;
187  },
188  OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
189  OnPushTransferProgress = (a, sentBytes, totalBytes) =>
190  {
191  progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
192  return !cancellationToken.IsCancellationRequested;
193  },
194  CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
195  };
196 
203  void RawCheckout(string committish, Action<int> progressReporter, CancellationToken cancellationToken)
204  {
205  logger.LogTrace("Checkout: {0}", committish);
206 
207  progressReporter(0);
208  cancellationToken.ThrowIfCancellationRequested();
209 
210  var checkoutOptions = new CheckoutOptions
211  {
212  CheckoutModifiers = CheckoutModifiers.Force,
213  OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
214  };
215 
216  void RunCheckout() => commands.Checkout(
217  libGitRepo,
218  checkoutOptions,
219  committish);
220 
221  try
222  {
223  RunCheckout();
224  }
225  catch (NotFoundException)
226  {
227  // Maybe (likely) a remote?
228  var remoteName = $"origin/{committish}";
229  var potentialBranch = libGitRepo.Branches.FirstOrDefault(
230  branch => branch.FriendlyName.Equals(remoteName, StringComparison.Ordinal));
231  cancellationToken.ThrowIfCancellationRequested();
232 
233  if (potentialBranch == default)
234  throw;
235 
236  logger.LogDebug("Creating local branch for {0}...", potentialBranch.FriendlyName);
237  libGitRepo.CreateBranch(committish, potentialBranch.Tip);
238  cancellationToken.ThrowIfCancellationRequested();
239 
240  RunCheckout();
241  }
242 
243  cancellationToken.ThrowIfCancellationRequested();
244 
245  libGitRepo.RemoveUntrackedFiles();
246  }
247 
249  #pragma warning disable CA1506 // TODO: Decomplexify
250  public async Task<bool?> AddTestMerge(
251  TestMergeParameters testMergeParameters,
252  string committerName,
253  string committerEmail,
254  string username,
255  string password,
256  Action<int> progressReporter,
257  CancellationToken cancellationToken)
258  {
259  if (testMergeParameters == null)
260  throw new ArgumentNullException(nameof(testMergeParameters));
261  if (committerName == null)
262  throw new ArgumentNullException(nameof(committerName));
263  if (committerEmail == null)
264  throw new ArgumentNullException(nameof(committerEmail));
265  if (progressReporter == null)
266  throw new ArgumentNullException(nameof(progressReporter));
267 
268  logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({4}) by <{2} ({3})>",
269  testMergeParameters.Number,
270  testMergeParameters.PullRequestRevision?.Substring(0, 7),
271  committerName,
272  committerEmail,
273  testMergeParameters.Comment);
274 
275  if (!IsGitHubRepository)
276  throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!");
277 
278  var commitMessage = String.Format(
279  CultureInfo.InvariantCulture,
280  "Test merge of pull request #{0}{1}{2}",
281  testMergeParameters.Number,
282  testMergeParameters.Comment != null
283  ? Environment.NewLine
284  : String.Empty,
285  testMergeParameters.Comment ?? String.Empty);
286 
287  var prBranchName = String.Format(CultureInfo.InvariantCulture, "pr-{0}", testMergeParameters.Number);
288  var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", testMergeParameters.Number, prBranchName);
289 
290  var refSpec = String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName);
291  var refSpecList = new List<string> { refSpec };
292  var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", testMergeParameters.Number);
293 
294  var originalCommit = libGitRepo.Head;
295 
296  MergeResult result = null;
297 
298  var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now);
299  await Task.Factory.StartNew(() =>
300  {
301  try
302  {
303  try
304  {
305  logger.LogTrace("Fetching refspec {0}...", refSpec);
306 
307  var remote = libGitRepo.Network.Remotes.First();
308  progressReporter(0);
309  commands.Fetch(
310  libGitRepo,
311  refSpecList,
312  remote,
313  new FetchOptions
314  {
315  Prune = true,
316  OnProgress = (a) => !cancellationToken.IsCancellationRequested,
317  OnTransferProgress = (a) =>
318  {
319  var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
320  progressReporter((int)percentage);
321  return !cancellationToken.IsCancellationRequested;
322  },
323  OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
324  CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
325  },
326  logMessage);
327  }
328  catch (UserCancelledException) { }
329 
330  cancellationToken.ThrowIfCancellationRequested();
331 
332  libGitRepo.RemoveUntrackedFiles();
333 
334  cancellationToken.ThrowIfCancellationRequested();
335 
336  testMergeParameters.PullRequestRevision = libGitRepo.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha;
337 
338  cancellationToken.ThrowIfCancellationRequested();
339 
340  logger.LogTrace("Merging {0} into {1}...", testMergeParameters.PullRequestRevision.Substring(0, 7), Reference);
341 
342  result = libGitRepo.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions
343  {
344  CommitOnSuccess = commitMessage == null,
345  FailOnConflict = true,
346  FastForwardStrategy = FastForwardStrategy.NoFastForward,
347  SkipReuc = true,
348  OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50)))
349  });
350  }
351  finally
352  {
353  libGitRepo.Branches.Remove(localBranchName);
354  }
355 
356  cancellationToken.ThrowIfCancellationRequested();
357 
358  if (result.Status == MergeStatus.Conflicts)
359  {
360  var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
361  logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo);
362  RawCheckout(revertTo, progressReporter, cancellationToken);
363  cancellationToken.ThrowIfCancellationRequested();
364  }
365 
366  libGitRepo.RemoveUntrackedFiles();
367  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
368 
369  if (result.Status == MergeStatus.Conflicts)
370  {
371  await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List<string> { originalCommit.Tip.Sha, testMergeParameters.PullRequestRevision, originalCommit.FriendlyName ?? UnknownReference, prBranchName }, cancellationToken).ConfigureAwait(false);
372  return null;
373  }
374 
375  if (commitMessage != null && result.Status != MergeStatus.UpToDate)
376  {
377  logger.LogTrace("Committing merge: \"{0}\"...", commitMessage);
378  await Task.Factory.StartNew(() => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions
379  {
380  PrettifyMessage = true
381  }), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
382  }
383 
384  await eventConsumer.HandleEvent(
385  EventType.RepoMergePullRequest,
386  new List<string>
387  {
388  testMergeParameters.Number.ToString(CultureInfo.InvariantCulture),
389  testMergeParameters.PullRequestRevision,
390  testMergeParameters.Comment
391  },
392  cancellationToken)
393  .ConfigureAwait(false);
394 
395  return result.Status != MergeStatus.NonFastForward;
396  }
397  #pragma warning restore CA1506
398 
400  public async Task CheckoutObject(string committish, Action<int> progressReporter, CancellationToken cancellationToken)
401  {
402  if (committish == null)
403  throw new ArgumentNullException(nameof(committish));
404  if (progressReporter == null)
405  throw new ArgumentNullException(nameof(progressReporter));
406  logger.LogDebug("Checkout object: {0}...", committish);
407  await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { committish }, cancellationToken).ConfigureAwait(false);
408  await Task.Factory.StartNew(() =>
409  {
410  libGitRepo.RemoveUntrackedFiles();
411  RawCheckout(committish, progressReporter, cancellationToken);
412  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
413  }
414 
416  public async Task FetchOrigin(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
417  {
418  if (progressReporter == null)
419  throw new ArgumentNullException(nameof(progressReporter));
420  logger.LogDebug("Fetch origin...");
421  await eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty<string>(), cancellationToken).ConfigureAwait(false);
422  await Task.Factory.StartNew(() =>
423  {
424  var remote = libGitRepo.Network.Remotes.First();
425  try
426  {
427  Commands.Fetch((LibGit2Sharp.Repository)libGitRepo, remote.Name, remote.FetchRefSpecs.Select(x => x.Specification), new FetchOptions
428  {
429  Prune = true,
430  OnProgress = (a) => !cancellationToken.IsCancellationRequested,
431  OnTransferProgress = (a) =>
432  {
433  var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
434  progressReporter((int)percentage);
435  return !cancellationToken.IsCancellationRequested;
436  },
437  OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
438  CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
439  }, "Fetch origin commits");
440  }
441  catch (UserCancelledException)
442  {
443  cancellationToken.ThrowIfCancellationRequested();
444  }
445  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
446  }
447 
456  Task PushHeadToTemporaryBranch(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
457  {
458  logger.LogInformation("Pushing changes to temporary remote branch...");
459  var branch = libGitRepo.CreateBranch(RemoteTemporaryBranchName);
460  try
461  {
462  cancellationToken.ThrowIfCancellationRequested();
463  var remote = libGitRepo.Network.Remotes.First();
464  try
465  {
466  var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName);
467  libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
468  var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName);
469  libGitRepo.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken));
470  }
471  catch (UserCancelledException)
472  {
473  cancellationToken.ThrowIfCancellationRequested();
474  }
475  catch(LibGit2SharpException e)
476  {
477  logger.LogWarning("Unable to push to temporary branch! Exception: {0}", e);
478  }
479  }
480  finally
481  {
482  libGitRepo.Branches.Remove(branch);
483  }
484  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
485 
487  public async Task ResetToOrigin(Action<int> progressReporter, CancellationToken cancellationToken)
488  {
489  if (progressReporter == null)
490  throw new ArgumentNullException(nameof(progressReporter));
491  if (!Tracking)
492  throw new JobException(ErrorCode.RepoReferenceRequired);
493  logger.LogTrace("Reset to origin...");
494  var trackedBranch = libGitRepo.Head.TrackedBranch;
495  await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false);
496  await ResetToSha(trackedBranch.Tip.Sha, progressReporter, cancellationToken).ConfigureAwait(false);
497  }
498 
500  public Task ResetToSha(string sha, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
501  {
502  if (sha == null)
503  throw new ArgumentNullException(nameof(sha));
504  if (progressReporter == null)
505  throw new ArgumentNullException(nameof(progressReporter));
506 
507  logger.LogDebug("Reset to sha: {0}", sha.Substring(0, 7));
508 
509  libGitRepo.RemoveUntrackedFiles();
510  cancellationToken.ThrowIfCancellationRequested();
511 
512  var gitObject = libGitRepo.Lookup(sha, ObjectType.Commit);
513  cancellationToken.ThrowIfCancellationRequested();
514 
515  if (gitObject == null)
516  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot reset to non-existent SHA: {0}", sha));
517 
518  libGitRepo.Reset(ResetMode.Hard, gitObject.Peel<Commit>(), new CheckoutOptions
519  {
520  OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
521  });
522  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
523 
525  public async Task CopyTo(string path, CancellationToken cancellationToken)
526  {
527  if (path == null)
528  throw new ArgumentNullException(nameof(path));
529  logger.LogTrace("Copying to {0}...", path);
530  await ioMananger.CopyDirectory(ioMananger.ResolvePath(), path, new List<string> { ".git" }, cancellationToken).ConfigureAwait(false);
531  }
532 
534  public async Task<bool?> MergeOrigin(string committerName, string committerEmail, Action<int> progressReporter, CancellationToken cancellationToken)
535  {
536  if (progressReporter == null)
537  throw new ArgumentNullException(nameof(progressReporter));
538 
539  MergeResult result = null;
540  Branch trackedBranch = null;
541 
542  var oldHead = libGitRepo.Head;
543  var oldTip = oldHead.Tip;
544 
545  await Task.Factory.StartNew(() =>
546  {
547  if (!Tracking)
548  throw new JobException(ErrorCode.RepoReferenceRequired);
549 
550  libGitRepo.RemoveUntrackedFiles();
551 
552  cancellationToken.ThrowIfCancellationRequested();
553 
554  trackedBranch = libGitRepo.Head.TrackedBranch;
555  logger.LogDebug("Merge origin/{2}: <{0} ({1})>", committerName, committerEmail, trackedBranch.FriendlyName);
556  result = libGitRepo.Merge(trackedBranch, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions
557  {
558  CommitOnSuccess = true,
559  FailOnConflict = true,
560  FastForwardStrategy = FastForwardStrategy.Default,
561  SkipReuc = true,
562  OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
563  });
564 
565  cancellationToken.ThrowIfCancellationRequested();
566 
567  if (result.Status == MergeStatus.Conflicts)
568  {
569  logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName);
570  libGitRepo.Reset(ResetMode.Hard, oldTip, new CheckoutOptions
571  {
572  OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
573  });
574  cancellationToken.ThrowIfCancellationRequested();
575  }
576 
577  libGitRepo.RemoveUntrackedFiles();
578  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
579 
580  if (result.Status == MergeStatus.Conflicts)
581  {
582  await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List<string> { oldTip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken).ConfigureAwait(false);
583  return null;
584  }
585 
586  return result.Status == MergeStatus.FastForward;
587  }
588 
590  public async Task<bool> Sychronize(string username, string password, string committerName, string committerEmail, Action<int> progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken)
591  {
592  if (committerName == null)
593  throw new ArgumentNullException(nameof(committerName));
594  if (committerEmail == null)
595  throw new ArgumentNullException(nameof(committerEmail));
596  if (progressReporter == null)
597  throw new ArgumentNullException(nameof(progressReporter));
598 
599  if (username == null && password == null)
600  {
601  logger.LogTrace("Not synchronizing due to lack of credentials!");
602  return false;
603  }
604 
605  logger.LogTrace("Begin Synchronize...");
606 
607  if (username == null)
608  throw new ArgumentNullException(nameof(username));
609  if (password == null)
610  throw new ArgumentNullException(nameof(password));
611 
612  var startHead = Head;
613 
614  logger.LogTrace("Configuring <{0} ({1})> as author/committer", committerName, committerEmail);
615  await Task.Factory.StartNew(() =>
616  {
617  libGitRepo.Config.Set("user.name", committerName);
618  cancellationToken.ThrowIfCancellationRequested();
619  libGitRepo.Config.Set("user.email", committerEmail);
620  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
621 
622  cancellationToken.ThrowIfCancellationRequested();
623  try
624  {
625  await eventConsumer.HandleEvent(
626  EventType.RepoPreSynchronize,
627  new List<string>
628  {
629  ioMananger.ResolvePath()
630  },
631  cancellationToken)
632  .ConfigureAwait(false);
633  }
634  finally
635  {
636  logger.LogTrace("Resetting and cleaning untracked files...");
637  await Task.Factory.StartNew(() =>
638  {
639  libGitRepo.RemoveUntrackedFiles();
640  cancellationToken.ThrowIfCancellationRequested();
641  libGitRepo.Reset(ResetMode.Hard, libGitRepo.Head.Tip, new CheckoutOptions
642  {
643  OnCheckoutProgress = CheckoutProgressHandler(progress => progressReporter(progress / 10))
644  });
645  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
646  }
647 
648  void FinalReporter(int progress) => progressReporter((int)(((float)progress) / 100 * 90));
649 
650  if (!synchronizeTrackedBranch)
651  {
652  await PushHeadToTemporaryBranch(username, password, FinalReporter, cancellationToken).ConfigureAwait(false);
653  return false;
654  }
655 
656  var sameHead = Head == startHead;
657  if (sameHead || !Tracking)
658  {
659  logger.LogTrace("Aborted synchronize due to {0}!", sameHead ? "lack of changes" : "not being on tracked reference");
660  return false;
661  }
662 
663  logger.LogInformation("Synchronizing with origin...");
664 
665  return await Task.Factory.StartNew(() =>
666  {
667  var remote = libGitRepo.Network.Remotes.First();
668  try
669  {
670  libGitRepo.Network.Push(libGitRepo.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken));
671  return true;
672  }
673  catch (NonFastForwardException)
674  {
675  logger.LogInformation("Synchronize aborted, non-fast forward!");
676  return false;
677  }
678  catch (UserCancelledException e)
679  {
680  cancellationToken.ThrowIfCancellationRequested();
681  throw new InvalidOperationException("Caught UserCancelledException without cancellationToken triggering", e);
682  }
683  catch (LibGit2SharpException e)
684  {
685  logger.LogWarning("Unable to make synchronization push! Exception: {0}", e);
686  return false;
687  }
688  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
689  }
690 
692  public Task<bool> IsSha(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
693  {
694  // check if it's a tag
695  var gitObject = libGitRepo.Lookup(committish, ObjectType.Tag);
696  if (gitObject != null)
697  return false;
698  cancellationToken.ThrowIfCancellationRequested();
699 
700  // check if it's a branch
701  if (libGitRepo.Branches[committish] != null)
702  return false;
703  cancellationToken.ThrowIfCancellationRequested();
704 
705  // err on the side of references, if we can't look it up, assume its a reference
706  if (libGitRepo.Lookup<Commit>(committish) != null)
707  return true;
708  return false;
709  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
710  }
711 }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
+Go to the documentation of this file.
1 using LibGit2Sharp;
2 using LibGit2Sharp.Handlers;
3 using Microsoft.Extensions.Logging;
4 using System;
5 using System.Collections.Generic;
6 using System.Globalization;
7 using System.Linq;
8 using System.Threading;
9 using System.Threading.Tasks;
11 using Tgstation.Server.Host.IO;
13 
14 namespace Tgstation.Server.Host.Components.Repository
15 {
17  sealed class Repository : IRepository
18  {
22  public const string GitHubUrl = "://github.com/";
23 
27  public const string OriginTrackingErrorTemplate = "Unable to determine most recent origin commit of {0}. Marking it as an origin commit. This may result in invalid git metadata until the next hard reset to an origin reference.";
28 
32  public const string RemoteTemporaryBranchName = "___TGSTempBranch";
33 
34  const string UnknownReference = "<UNKNOWN>";
35 
37  public bool IsGitHubRepository { get; }
38 
40  public string GitHubOwner { get; }
41 
43  public string GitHubRepoName { get; }
44 
46  public bool Tracking => Reference != null && libGitRepo.Head.IsTracking;
47 
49  public string Head => libGitRepo.Head.Tip.Sha;
50 
52  public string Reference => libGitRepo.Head.FriendlyName;
53 
55  public string Origin => libGitRepo.Network.Remotes.First().Url;
56 
60  readonly LibGit2Sharp.IRepository libGitRepo;
61 
66 
71 
76 
81 
85  readonly ILogger<Repository> logger;
86 
90  readonly Action onDispose;
91 
95  bool disposed;
96 
102  static CheckoutProgressHandler CheckoutProgressHandler(Action<int> progressReporter) => (a, completedSteps, totalSteps) => progressReporter((int)(((float)completedSteps) / totalSteps * 100));
103 
114  public Repository(
115  LibGit2Sharp.IRepository libGitRepo,
116  ILibGit2Commands commands,
117  IIOManager ioMananger,
118  IEventConsumer eventConsumer,
119  ICredentialsProvider credentialsProvider,
120  ILogger<Repository> logger,
121  Action onDispose)
122  {
123  this.libGitRepo = libGitRepo ?? throw new ArgumentNullException(nameof(libGitRepo));
124  this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
125  this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger));
126  this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
127  this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
128  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
129  this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
130 
131  IsGitHubRepository = Origin.Contains(GitHubUrl, StringComparison.InvariantCultureIgnoreCase);
132 
133  if (IsGitHubRepository)
134  {
135  GetRepositoryOwnerName(Origin, out var owner, out var name);
136  GitHubOwner = owner;
137  GitHubRepoName = name;
138  }
139  }
140 
142  public void Dispose()
143  {
144  lock (onDispose)
145  {
146  if (disposed)
147  return;
148 
149  disposed = true;
150  }
151 
152  logger.LogTrace("Disposing...");
153  libGitRepo.Dispose();
154  onDispose();
155  }
156 
157  void GetRepositoryOwnerName(string remote, out string owner, out string name)
158  {
159  // Assume standard gh format: [(git)|(https)]://github.com/owner/repo(.git)[0-1]
160  // Yes use .git twice in case it was weird
161  var toRemove = new string[] { ".git", "/", ".git" };
162  foreach (string item in toRemove)
163  if (remote.EndsWith(item, StringComparison.OrdinalIgnoreCase))
164  remote = remote.Substring(0, remote.LastIndexOf(item, StringComparison.OrdinalIgnoreCase));
165  var splits = remote.Split('/');
166  name = splits.Last();
167  owner = splits[^2].Split('.').First();
168 
169  logger.LogTrace("GetRepositoryOwnerName({0}) => {1} / {2}", remote, owner, name);
170  }
171 
180  PushOptions GeneratePushOptions(Action<int> progressReporter, string username, string password, CancellationToken cancellationToken) => new PushOptions
181  {
182  OnPackBuilderProgress = (stage, current, total) =>
183  {
184  var baseProgress = stage == PackBuilderStage.Counting ? 0 : 25;
185  progressReporter(baseProgress + ((int)(25 * ((float)current) / total)));
186  return !cancellationToken.IsCancellationRequested;
187  },
188  OnNegotiationCompletedBeforePush = (a) => !cancellationToken.IsCancellationRequested,
189  OnPushTransferProgress = (a, sentBytes, totalBytes) =>
190  {
191  progressReporter(50 + ((int)(50 * ((float)sentBytes) / totalBytes)));
192  return !cancellationToken.IsCancellationRequested;
193  },
194  CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
195  };
196 
203  void RawCheckout(string committish, Action<int> progressReporter, CancellationToken cancellationToken)
204  {
205  logger.LogTrace("Checkout: {0}", committish);
206 
207  progressReporter(0);
208  cancellationToken.ThrowIfCancellationRequested();
209 
210  var checkoutOptions = new CheckoutOptions
211  {
212  CheckoutModifiers = CheckoutModifiers.Force,
213  OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
214  };
215 
216  void RunCheckout() => commands.Checkout(
217  libGitRepo,
218  checkoutOptions,
219  committish);
220 
221  try
222  {
223  RunCheckout();
224  }
225  catch (NotFoundException)
226  {
227  // Maybe (likely) a remote?
228  var remoteName = $"origin/{committish}";
229  var potentialBranch = libGitRepo.Branches.FirstOrDefault(
230  branch => branch.FriendlyName.Equals(remoteName, StringComparison.Ordinal));
231  cancellationToken.ThrowIfCancellationRequested();
232 
233  if (potentialBranch == default)
234  throw;
235 
236  logger.LogDebug("Creating local branch for {0}...", potentialBranch.FriendlyName);
237  libGitRepo.CreateBranch(committish, potentialBranch.Tip);
238  cancellationToken.ThrowIfCancellationRequested();
239 
240  RunCheckout();
241  }
242 
243  cancellationToken.ThrowIfCancellationRequested();
244 
245  libGitRepo.RemoveUntrackedFiles();
246  }
247 
249  #pragma warning disable CA1506 // TODO: Decomplexify
250  public async Task<bool?> AddTestMerge(
251  TestMergeParameters testMergeParameters,
252  string committerName,
253  string committerEmail,
254  string username,
255  string password,
256  Action<int> progressReporter,
257  CancellationToken cancellationToken)
258  {
259  if (testMergeParameters == null)
260  throw new ArgumentNullException(nameof(testMergeParameters));
261  if (committerName == null)
262  throw new ArgumentNullException(nameof(committerName));
263  if (committerEmail == null)
264  throw new ArgumentNullException(nameof(committerEmail));
265  if (progressReporter == null)
266  throw new ArgumentNullException(nameof(progressReporter));
267 
268  logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({2}) by <{3} ({4})>",
269  testMergeParameters.Number,
270  testMergeParameters.PullRequestRevision?.Substring(0, 7),
271  testMergeParameters.Comment,
272  committerName,
273  committerEmail);
274 
275  if (!IsGitHubRepository)
276  throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!");
277 
278  var commitMessage = String.Format(
279  CultureInfo.InvariantCulture,
280  "Test merge of pull request #{0}{1}{2}",
281  testMergeParameters.Number,
282  testMergeParameters.Comment != null
283  ? Environment.NewLine
284  : String.Empty,
285  testMergeParameters.Comment ?? String.Empty);
286 
287  var prBranchName = String.Format(CultureInfo.InvariantCulture, "pr-{0}", testMergeParameters.Number);
288  var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", testMergeParameters.Number, prBranchName);
289 
290  var refSpec = String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName);
291  var refSpecList = new List<string> { refSpec };
292  var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", testMergeParameters.Number);
293 
294  var originalCommit = libGitRepo.Head;
295 
296  MergeResult result = null;
297 
298  var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now);
299  await Task.Factory.StartNew(() =>
300  {
301  try
302  {
303  try
304  {
305  logger.LogTrace("Fetching refspec {0}...", refSpec);
306 
307  var remote = libGitRepo.Network.Remotes.First();
308  progressReporter(0);
309  commands.Fetch(
310  libGitRepo,
311  refSpecList,
312  remote,
313  new FetchOptions
314  {
315  Prune = true,
316  OnProgress = (a) => !cancellationToken.IsCancellationRequested,
317  OnTransferProgress = (a) =>
318  {
319  var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
320  progressReporter((int)percentage);
321  return !cancellationToken.IsCancellationRequested;
322  },
323  OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
324  CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
325  },
326  logMessage);
327  }
328  catch (UserCancelledException) { }
329 
330  cancellationToken.ThrowIfCancellationRequested();
331 
332  libGitRepo.RemoveUntrackedFiles();
333 
334  cancellationToken.ThrowIfCancellationRequested();
335 
336  testMergeParameters.PullRequestRevision = libGitRepo.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha;
337 
338  cancellationToken.ThrowIfCancellationRequested();
339 
340  logger.LogTrace("Merging {0} into {1}...", testMergeParameters.PullRequestRevision.Substring(0, 7), Reference);
341 
342  result = libGitRepo.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions
343  {
344  CommitOnSuccess = commitMessage == null,
345  FailOnConflict = true,
346  FastForwardStrategy = FastForwardStrategy.NoFastForward,
347  SkipReuc = true,
348  OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50)))
349  });
350  }
351  finally
352  {
353  libGitRepo.Branches.Remove(localBranchName);
354  }
355 
356  cancellationToken.ThrowIfCancellationRequested();
357 
358  if (result.Status == MergeStatus.Conflicts)
359  {
360  var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
361  logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo);
362  RawCheckout(revertTo, progressReporter, cancellationToken);
363  cancellationToken.ThrowIfCancellationRequested();
364  }
365 
366  libGitRepo.RemoveUntrackedFiles();
367  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
368 
369  if (result.Status == MergeStatus.Conflicts)
370  {
371  await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List<string> { originalCommit.Tip.Sha, testMergeParameters.PullRequestRevision, originalCommit.FriendlyName ?? UnknownReference, prBranchName }, cancellationToken).ConfigureAwait(false);
372  return null;
373  }
374 
375  if (commitMessage != null && result.Status != MergeStatus.UpToDate)
376  {
377  logger.LogTrace("Committing merge: \"{0}\"...", commitMessage);
378  await Task.Factory.StartNew(() => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions
379  {
380  PrettifyMessage = true
381  }), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
382  }
383 
384  await eventConsumer.HandleEvent(
385  EventType.RepoMergePullRequest,
386  new List<string>
387  {
388  testMergeParameters.Number.ToString(CultureInfo.InvariantCulture),
389  testMergeParameters.PullRequestRevision,
390  testMergeParameters.Comment
391  },
392  cancellationToken)
393  .ConfigureAwait(false);
394 
395  return result.Status != MergeStatus.NonFastForward;
396  }
397  #pragma warning restore CA1506
398 
400  public async Task CheckoutObject(string committish, Action<int> progressReporter, CancellationToken cancellationToken)
401  {
402  if (committish == null)
403  throw new ArgumentNullException(nameof(committish));
404  if (progressReporter == null)
405  throw new ArgumentNullException(nameof(progressReporter));
406  logger.LogDebug("Checkout object: {0}...", committish);
407  await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { committish }, cancellationToken).ConfigureAwait(false);
408  await Task.Factory.StartNew(() =>
409  {
410  libGitRepo.RemoveUntrackedFiles();
411  RawCheckout(committish, progressReporter, cancellationToken);
412  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
413  }
414 
416  public async Task FetchOrigin(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken)
417  {
418  if (progressReporter == null)
419  throw new ArgumentNullException(nameof(progressReporter));
420  logger.LogDebug("Fetch origin...");
421  await eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty<string>(), cancellationToken).ConfigureAwait(false);
422  await Task.Factory.StartNew(() =>
423  {
424  var remote = libGitRepo.Network.Remotes.First();
425  try
426  {
427  Commands.Fetch((LibGit2Sharp.Repository)libGitRepo, remote.Name, remote.FetchRefSpecs.Select(x => x.Specification), new FetchOptions
428  {
429  Prune = true,
430  OnProgress = (a) => !cancellationToken.IsCancellationRequested,
431  OnTransferProgress = (a) =>
432  {
433  var percentage = 100 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
434  progressReporter((int)percentage);
435  return !cancellationToken.IsCancellationRequested;
436  },
437  OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
438  CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
439  }, "Fetch origin commits");
440  }
441  catch (UserCancelledException)
442  {
443  cancellationToken.ThrowIfCancellationRequested();
444  }
445  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
446  }
447 
456  Task PushHeadToTemporaryBranch(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
457  {
458  logger.LogInformation("Pushing changes to temporary remote branch...");
459  var branch = libGitRepo.CreateBranch(RemoteTemporaryBranchName);
460  try
461  {
462  cancellationToken.ThrowIfCancellationRequested();
463  var remote = libGitRepo.Network.Remotes.First();
464  try
465  {
466  var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName);
467  libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
468  var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName);
469  libGitRepo.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken));
470  }
471  catch (UserCancelledException)
472  {
473  cancellationToken.ThrowIfCancellationRequested();
474  }
475  catch(LibGit2SharpException e)
476  {
477  logger.LogWarning("Unable to push to temporary branch! Exception: {0}", e);
478  }
479  }
480  finally
481  {
482  libGitRepo.Branches.Remove(branch);
483  }
484  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
485 
487  public async Task ResetToOrigin(Action<int> progressReporter, CancellationToken cancellationToken)
488  {
489  if (progressReporter == null)
490  throw new ArgumentNullException(nameof(progressReporter));
491  if (!Tracking)
492  throw new JobException(ErrorCode.RepoReferenceRequired);
493  logger.LogTrace("Reset to origin...");
494  var trackedBranch = libGitRepo.Head.TrackedBranch;
495  await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false);
496  await ResetToSha(trackedBranch.Tip.Sha, progressReporter, cancellationToken).ConfigureAwait(false);
497  }
498 
500  public Task ResetToSha(string sha, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
501  {
502  if (sha == null)
503  throw new ArgumentNullException(nameof(sha));
504  if (progressReporter == null)
505  throw new ArgumentNullException(nameof(progressReporter));
506 
507  logger.LogDebug("Reset to sha: {0}", sha.Substring(0, 7));
508 
509  libGitRepo.RemoveUntrackedFiles();
510  cancellationToken.ThrowIfCancellationRequested();
511 
512  var gitObject = libGitRepo.Lookup(sha, ObjectType.Commit);
513  cancellationToken.ThrowIfCancellationRequested();
514 
515  if (gitObject == null)
516  throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot reset to non-existent SHA: {0}", sha));
517 
518  libGitRepo.Reset(ResetMode.Hard, gitObject.Peel<Commit>(), new CheckoutOptions
519  {
520  OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
521  });
522  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
523 
525  public async Task CopyTo(string path, CancellationToken cancellationToken)
526  {
527  if (path == null)
528  throw new ArgumentNullException(nameof(path));
529  logger.LogTrace("Copying to {0}...", path);
530  await ioMananger.CopyDirectory(ioMananger.ResolvePath(), path, new List<string> { ".git" }, cancellationToken).ConfigureAwait(false);
531  }
532 
534  public async Task<bool?> MergeOrigin(string committerName, string committerEmail, Action<int> progressReporter, CancellationToken cancellationToken)
535  {
536  if (progressReporter == null)
537  throw new ArgumentNullException(nameof(progressReporter));
538 
539  MergeResult result = null;
540  Branch trackedBranch = null;
541 
542  var oldHead = libGitRepo.Head;
543  var oldTip = oldHead.Tip;
544 
545  await Task.Factory.StartNew(() =>
546  {
547  if (!Tracking)
548  throw new JobException(ErrorCode.RepoReferenceRequired);
549 
550  libGitRepo.RemoveUntrackedFiles();
551 
552  cancellationToken.ThrowIfCancellationRequested();
553 
554  trackedBranch = libGitRepo.Head.TrackedBranch;
555  logger.LogDebug("Merge origin/{2}: <{0} ({1})>", committerName, committerEmail, trackedBranch.FriendlyName);
556  result = libGitRepo.Merge(trackedBranch, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions
557  {
558  CommitOnSuccess = true,
559  FailOnConflict = true,
560  FastForwardStrategy = FastForwardStrategy.Default,
561  SkipReuc = true,
562  OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
563  });
564 
565  cancellationToken.ThrowIfCancellationRequested();
566 
567  if (result.Status == MergeStatus.Conflicts)
568  {
569  logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName);
570  libGitRepo.Reset(ResetMode.Hard, oldTip, new CheckoutOptions
571  {
572  OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
573  });
574  cancellationToken.ThrowIfCancellationRequested();
575  }
576 
577  libGitRepo.RemoveUntrackedFiles();
578  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
579 
580  if (result.Status == MergeStatus.Conflicts)
581  {
582  await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List<string> { oldTip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken).ConfigureAwait(false);
583  return null;
584  }
585 
586  return result.Status == MergeStatus.FastForward;
587  }
588 
590  public async Task<bool> Sychronize(string username, string password, string committerName, string committerEmail, Action<int> progressReporter, bool synchronizeTrackedBranch, CancellationToken cancellationToken)
591  {
592  if (committerName == null)
593  throw new ArgumentNullException(nameof(committerName));
594  if (committerEmail == null)
595  throw new ArgumentNullException(nameof(committerEmail));
596  if (progressReporter == null)
597  throw new ArgumentNullException(nameof(progressReporter));
598 
599  if (username == null && password == null)
600  {
601  logger.LogTrace("Not synchronizing due to lack of credentials!");
602  return false;
603  }
604 
605  logger.LogTrace("Begin Synchronize...");
606 
607  if (username == null)
608  throw new ArgumentNullException(nameof(username));
609  if (password == null)
610  throw new ArgumentNullException(nameof(password));
611 
612  var startHead = Head;
613 
614  logger.LogTrace("Configuring <{0} ({1})> as author/committer", committerName, committerEmail);
615  await Task.Factory.StartNew(() =>
616  {
617  libGitRepo.Config.Set("user.name", committerName);
618  cancellationToken.ThrowIfCancellationRequested();
619  libGitRepo.Config.Set("user.email", committerEmail);
620  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
621 
622  cancellationToken.ThrowIfCancellationRequested();
623  try
624  {
625  await eventConsumer.HandleEvent(
626  EventType.RepoPreSynchronize,
627  new List<string>
628  {
629  ioMananger.ResolvePath()
630  },
631  cancellationToken)
632  .ConfigureAwait(false);
633  }
634  finally
635  {
636  logger.LogTrace("Resetting and cleaning untracked files...");
637  await Task.Factory.StartNew(() =>
638  {
639  libGitRepo.RemoveUntrackedFiles();
640  cancellationToken.ThrowIfCancellationRequested();
641  libGitRepo.Reset(ResetMode.Hard, libGitRepo.Head.Tip, new CheckoutOptions
642  {
643  OnCheckoutProgress = CheckoutProgressHandler(progress => progressReporter(progress / 10))
644  });
645  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
646  }
647 
648  void FinalReporter(int progress) => progressReporter((int)(((float)progress) / 100 * 90));
649 
650  if (!synchronizeTrackedBranch)
651  {
652  await PushHeadToTemporaryBranch(username, password, FinalReporter, cancellationToken).ConfigureAwait(false);
653  return false;
654  }
655 
656  var sameHead = Head == startHead;
657  if (sameHead || !Tracking)
658  {
659  logger.LogTrace("Aborted synchronize due to {0}!", sameHead ? "lack of changes" : "not being on tracked reference");
660  return false;
661  }
662 
663  logger.LogInformation("Synchronizing with origin...");
664 
665  return await Task.Factory.StartNew(() =>
666  {
667  var remote = libGitRepo.Network.Remotes.First();
668  try
669  {
670  libGitRepo.Network.Push(libGitRepo.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken));
671  return true;
672  }
673  catch (NonFastForwardException)
674  {
675  logger.LogInformation("Synchronize aborted, non-fast forward!");
676  return false;
677  }
678  catch (UserCancelledException e)
679  {
680  cancellationToken.ThrowIfCancellationRequested();
681  throw new InvalidOperationException("Caught UserCancelledException without cancellationToken triggering", e);
682  }
683  catch (LibGit2SharpException e)
684  {
685  logger.LogWarning("Unable to make synchronization push! Exception: {0}", e);
686  return false;
687  }
688  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
689  }
690 
692  public Task<bool> IsSha(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
693  {
694  // check if it's a tag
695  var gitObject = libGitRepo.Lookup(committish, ObjectType.Tag);
696  if (gitObject != null)
697  return false;
698  cancellationToken.ThrowIfCancellationRequested();
699 
700  // check if it's a branch
701  if (libGitRepo.Branches[committish] != null)
702  return false;
703  cancellationToken.ThrowIfCancellationRequested();
704 
705  // err on the side of references, if we can't look it up, assume its a reference
706  if (libGitRepo.Lookup<Commit>(committish) != null)
707  return true;
708  return false;
709  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
710  }
711 }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
Consumes EventTypes and takes the appropriate actions
diff --git a/_token_8cs_source.html b/_token_8cs_source.html index 11b22d924a..3906aee1ee 100644 --- a/_token_8cs_source.html +++ b/_token_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
Token.cs
-Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class Token
9  {
13  public string Bearer { get; set; }
14 
18  public DateTimeOffset ExpiresAt { get; set; }
19  }
20 }
+Go to the documentation of this file.
1 using System;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class Token
9  {
13  public string? Bearer { get; set; }
14 
18  public DateTimeOffset ExpiresAt { get; set; }
19  }
20 }
Represents a JWT returned by the API
Definition: Token.cs:8
diff --git a/_unauthorized_exception_8cs_source.html b/_unauthorized_exception_8cs_source.html index 9bcf8f8812..712a650973 100644 --- a/_unauthorized_exception_8cs_source.html +++ b/_unauthorized_exception_8cs_source.html @@ -94,14 +94,14 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
UnauthorizedException.cs
-Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class UnauthorizedException : ApiException
11  {
17  public UnauthorizedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  { }
19 
24  public UnauthorizedException(string message) : base(message) { }
25 
31  public UnauthorizedException(string message, Exception innerException) : base(message, innerException) { }
32  }
33 }
+Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class UnauthorizedException : ApiException
11  {
17  public UnauthorizedException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  { }
19 
24  public UnauthorizedException(string message) : base(message) { }
25 
31  public UnauthorizedException(string message, Exception innerException) : base(message, innerException) { }
32  }
33 }
UnauthorizedException(string message)
Construct an UnauthorizedException with a message
UnauthorizedException(string message, Exception innerException)
Construct an UnauthorizedException with a message and innerException
API related Exceptions.
Definition: ApiException.cs:10
-
UnauthorizedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)
Construct an UnauthorizedException
+
UnauthorizedException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
Construct an UnauthorizedException
Occurs when the client provides invalid credentials
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
diff --git a/_user_update_8cs_source.html b/_user_update_8cs_source.html index e97c153e8e..485aacc3f3 100644 --- a/_user_update_8cs_source.html +++ b/_user_update_8cs_source.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
UserUpdate.cs
-Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class UserUpdate : User
9  {
13  [Required]
14  public string Password { get; set; }
15  }
16 }
+Go to the documentation of this file.
1 using System.ComponentModel.DataAnnotations;
2 
3 namespace Tgstation.Server.Api.Models
4 {
8  public sealed class UserUpdate : User
9  {
13  [Required]
14  public string? Password { get; set; }
15  }
16 }
For editing a given User. Will never be returned by the API
Definition: UserUpdate.cs:8
diff --git a/_version_mismatch_exception_8cs_source.html b/_version_mismatch_exception_8cs_source.html index d2de40a15d..51a3f8627f 100644 --- a/_version_mismatch_exception_8cs_source.html +++ b/_version_mismatch_exception_8cs_source.html @@ -94,8 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
VersionMismatchException.cs
-Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class VersionMismatchException : ApiException
11  {
17  public VersionMismatchException(ErrorMessage errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  {
19  }
20 
25 
30  public VersionMismatchException(string message) : base(message) { }
31 
37  public VersionMismatchException(string message, Exception innerException) : base(message, innerException) { }
38  }
39 }
VersionMismatchException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the VersionMismatchException .
-
VersionMismatchException()
Construct an VersionMismatchException
+Go to the documentation of this file.
1 using System;
2 using System.Net.Http;
4 
5 namespace Tgstation.Server.Client
6 {
10  public sealed class VersionMismatchException : ApiException
11  {
17  public VersionMismatchException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
18  {
19  }
20 
25 
30  public VersionMismatchException(string message) : base(message) { }
31 
37  public VersionMismatchException(string message, Exception innerException) : base(message, innerException) { }
38  }
39 }
VersionMismatchException()
Construct an VersionMismatchException
Occurs when the API version of the client is not compatible with the server&#39;s
@@ -103,6 +102,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
API related Exceptions.
Definition: ApiException.cs:10
VersionMismatchException(string message, Exception innerException)
Construct an VersionMismatchException with a message and innerException
+
VersionMismatchException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
Initialize a new instance of the VersionMismatchException .
VersionMismatchException(string message)
Construct an VersionMismatchException with a message
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
diff --git a/api.html b/api.html index baa7997757..27f2b4f452 100644 --- a/api.html +++ b/api.html @@ -300,7 +300,7 @@ Repository Commands for Git Aliases

All these actions are done with the POST method

git pull (Only if Tgstation.Server.Api.Models.Repository.Reference is set):

1 {
2  "updateFromOrigin": true
3 }

git checkout <commit sha> (Unsets Tgstation.Server.Api.Models.Repository.Reference):

1 {
2  "checkoutSha": "<commit sha>"
3 }

git checkout -f <branch or tag> && git clean -fxd (Sets Tgstation.Server.Api.Models.Repository.Reference):

1 {
2  "reference": "<branch or tag>"
3 }

git fetch && git checkout -f branch && git clean -fxd && git reset –hard origin/branch (Sets Tgstation.Server.Api.Models.Repository.Reference);

1 {
2  "updateFromOrigin": true,
3  "reference": "branch"
4 }

Test Merging

-

Any of the above POST methods can be combined with the Tgstation.Server.Api.Models.Repository.NewTestMerges field to add test merges on top of the checked out commit.

+

Any of the above POST methods can be combined with the Tgstation.Server.Api.Models.Repository.NewTestMerges field to add test merges on top of the checked out commit.

1 {
2  "newTestMerges": [
3  {
4  "number": 12345,
5  "comment": "I'm merging this for x reason"
6  },
7  {
8  "number": 12346,
9  "pullRequestRevision": "abcdef1"
10  }
11  ]
12 }

If the server detects a set of Tgstation.Server.Api.Models.TestMergeParameters being applied that it has seen before, it'll instead attempt to checkout the commit that was created then.

Unsetting Authentication

diff --git a/class_tgstation_1_1_server_1_1_api_1_1_api_headers-members.html b/class_tgstation_1_1_server_1_1_api_1_1_api_headers-members.html index d1cf085c3d..591f64d7a8 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_api_headers-members.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_api_headers-members.html @@ -103,7 +103,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); ApiHeaders(ProductHeaderValue userAgent, string token)Tgstation.Server.Api.ApiHeaders ApiHeaders(ProductHeaderValue userAgent, string username, string password)Tgstation.Server.Api.ApiHeaders ApiHeaders(RequestHeaders requestHeaders)Tgstation.Server.Api.ApiHeaders - ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)Tgstation.Server.Api.ApiHeadersprivate + ApiHeaders(ProductHeaderValue userAgent, string?token, string?username, string?password)Tgstation.Server.Api.ApiHeadersprivate ApiVersionTgstation.Server.Api.ApiHeaders ApiVersionHeaderTgstation.Server.Api.ApiHeaders ApplicationJsonTgstation.Server.Api.ApiHeaders diff --git a/class_tgstation_1_1_server_1_1_api_1_1_api_headers.html b/class_tgstation_1_1_server_1_1_api_1_1_api_headers.html index 423fbc8657..1ffa7d282a 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_api_headers.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_api_headers.html @@ -193,9 +193,9 @@ Properties - - - + + +

Private Member Functions

 ApiHeaders (ProductHeaderValue userAgent, string token, string username, string password)
 Construct ApiHeaders More...
 
 ApiHeaders (ProductHeaderValue userAgent, string?token, string?username, string?password)
 Construct ApiHeaders More...
 
@@ -339,7 +339,7 @@ Static Private Attributes - +

Private Attributes

@@ -355,19 +355,19 @@ Static Private Attributes - + - + - + @@ -503,8 +503,8 @@ Here is the caller graph for this function:

Definition at line 259 of file ApiHeaders.cs.

-

Referenced by Tgstation.Server.Client.ApiClient.RunRequest< TResult >().

-
260  {
261  if (headers == null)
262  throw new ArgumentNullException(nameof(headers));
263  if (instanceId.HasValue && InstanceId.HasValue && instanceId != InstanceId)
264  throw new InvalidOperationException("Specified instance ID in constructor and SetRequestHeaders!");
265 
266  headers.Clear();
267  headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson));
269  headers.Authorization = new AuthenticationHeaderValue(JwtAuthenticationScheme, Token);
270  else
271  headers.Authorization = new AuthenticationHeaderValue(
273  Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}")));
274 
275  headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
276  headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
277  instanceId = instanceId ?? InstanceId;
278  if (instanceId.HasValue)
279  headers.Add(InstanceIdHeader, instanceId.ToString());
280  }
static readonly AssemblyName AssemblyName
The current System.Reflection.AssemblyName
Definition: ApiHeaders.cs:57
+

Referenced by Tgstation.Server.Client.ApiClient.RunRequest< TResult >().

+
260  {
261  if (headers == null)
262  throw new ArgumentNullException(nameof(headers));
263  if (instanceId.HasValue && InstanceId.HasValue && instanceId != InstanceId)
264  throw new InvalidOperationException("Specified instance ID in constructor and SetRequestHeaders!");
265 
266  headers.Clear();
267  headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson));
269  headers.Authorization = new AuthenticationHeaderValue(JwtAuthenticationScheme, Token);
270  else
271  headers.Authorization = new AuthenticationHeaderValue(
273  Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}")));
274 
275  headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
276  headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
277  instanceId ??= InstanceId;
278  if (instanceId.HasValue)
279  headers.Add(InstanceIdHeader, instanceId.ToString());
280  }
static readonly AssemblyName AssemblyName
The current System.Reflection.AssemblyName
Definition: ApiHeaders.cs:57
const string ApplicationJson
TODO: Remove this when we upgrade to .NET Standard 2.1
Definition: ApiHeaders.cs:22
Version ApiVersion
The client&#39;s API version
Definition: ApiHeaders.cs:82
ProductHeaderValue UserAgent
The client&#39;s user agent as a ProductHeaderValue if valid
Definition: ApiHeaders.cs:72
@@ -521,7 +521,7 @@ Here is the caller graph for this function:
- +

@@ -559,7 +559,7 @@ Here is the caller graph for this function:

Definition at line 22 of file ApiHeaders.cs.

-

Referenced by Tgstation.Server.Host.Core.SwaggerConfiguration.AddDefaultResponses(), Tgstation.Server.Host.Controllers.BridgeController.Process(), and Tgstation.Server.Client.ApiClient.RunRequest< TResult >().

+

Referenced by Tgstation.Server.Host.Core.SwaggerConfiguration.AddDefaultResponses(), Tgstation.Server.Host.Controllers.BridgeController.Process(), and Tgstation.Server.Client.ApiClient.RunRequest< TResult >().

@@ -745,7 +745,7 @@ Here is the caller graph for this function:

Definition at line 62 of file ApiHeaders.cs.

-

Referenced by Tgstation.Server.Host.Core.SwaggerConfiguration.Configure(), and Tgstation.Server.Api.Models.ErrorMessage.ErrorMessage().

+

Referenced by Tgstation.Server.Host.Core.SwaggerConfiguration.Configure(), and Tgstation.Server.Api.Models.ErrorMessage.ErrorMessage().

diff --git a/class_tgstation_1_1_server_1_1_api_1_1_api_headers_a21f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.map b/class_tgstation_1_1_server_1_1_api_1_1_api_headers_a21f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.map index d85a5a0868..04c0785e0e 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_api_headers_a21f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.map +++ b/class_tgstation_1_1_server_1_1_api_1_1_api_headers_a21f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.map @@ -1,3 +1,3 @@ - + diff --git a/class_tgstation_1_1_server_1_1_api_1_1_api_headers_a21f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.md5 b/class_tgstation_1_1_server_1_1_api_1_1_api_headers_a21f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.md5 index a5738e9213..da0b31739e 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_api_headers_a21f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.md5 +++ b/class_tgstation_1_1_server_1_1_api_1_1_api_headers_a21f2a1859fe5e8cb8849ffd9ecc01c39_icgraph.md5 @@ -1 +1 @@ -21fe11e061038eafe956f37f9f0993bc \ No newline at end of file +64ee986f75829c19952660c3cf2d0f08 \ No newline at end of file diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_chat_bot.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_chat_bot.html index 4b81ac1ae2..12b4132a7c 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_chat_bot.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_chat_bot.html @@ -183,7 +183,7 @@ Properties
Returns
if the Channels are valid for the Internal.ChatBot.Provider, otherwise

Definition at line 19 of file ChatBot.cs.

-
20  {
21  if (!Provider.HasValue)
22  return true;
23  switch (Provider.Value)
24  {
25  case ChatProvider.Discord:
26  return Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true;
27  case ChatProvider.Irc:
28  return Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true;
29  default:
30  throw new InvalidOperationException("Invalid provider type!");
31  }
32  }
ChatProvider Provider
The ChatProvider used for the connection
Definition: ChatBot.cs:46
+
20  {
21  if (!Provider.HasValue)
22  return true;
23  return Provider.Value switch
24  {
25  ChatProvider.Discord => Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true,
26  ChatProvider.Irc => Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true,
27  _ => throw new InvalidOperationException("Invalid provider type!"),
28  };
29  }
ChatProvider Provider
The ChatProvider used for the connection
Definition: ChatBot.cs:46
ICollection< ChatChannel > Channels
Channels the Discord bot should listen/announce in
Definition: ChatBot.cs:13
ChatProvider
Represents a chat service provider
Definition: ChatProvider.cs:6
diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_code_extensions.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_code_extensions.html index 2899e5b31a..b5d1fed9c4 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_code_extensions.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_code_extensions.html @@ -113,7 +113,7 @@ Static Public Member Functions

Detailed Description

Extension methods for the ErrorCode .

-

Definition at line 9 of file ErrorCodeExtensions.cs.

+

Definition at line 10 of file ErrorCodeExtensions.cs.

Member Function Documentation

@@ -146,8 +146,8 @@ Static Public Member Functions
Returns
A description of the errorCode on success, on failure.
-

Definition at line 16 of file ErrorCodeExtensions.cs.

-
17  {
18  var attributes = (DescriptionAttribute[])typeof(ErrorCode)
19  .GetField(errorCode.ToString())
20  ?.GetCustomAttributes(typeof(DescriptionAttribute), false);
21 
22  return attributes?.FirstOrDefault()?.Description;
23  }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
+

Definition at line 17 of file ErrorCodeExtensions.cs.

+
18  {
19  var attributes = (IEnumerable<DescriptionAttribute>?)typeof(ErrorCode)
20  .GetField(errorCode.ToString())
21  ?.GetCustomAttributes(typeof(DescriptionAttribute), false);
22 
23  return attributes?.FirstOrDefault()?.Description;
24  }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message-members.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message-members.html index 435f39d0a2..6f1f62cd99 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message-members.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message-members.html @@ -102,9 +102,10 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
string string?  token,
string string?  username,
string string?  password 
- - - + + + +
AdditionalDataTgstation.Server.Api.Models.ErrorMessage
ErrorCodeTgstation.Server.Api.Models.ErrorMessage
ErrorMessage(ErrorCode errorCode)Tgstation.Server.Api.Models.ErrorMessage
MessageTgstation.Server.Api.Models.ErrorMessage
ServerApiVersionTgstation.Server.Api.Models.ErrorMessage
ErrorMessage()Tgstation.Server.Api.Models.ErrorMessage
ErrorMessage(ErrorCode errorCode)Tgstation.Server.Api.Models.ErrorMessage
MessageTgstation.Server.Api.Models.ErrorMessage
ServerApiVersionTgstation.Server.Api.Models.ErrorMessage
+ +

Initializes a new instance of the ErrorMessage .

+ +

Definition at line 37 of file ErrorMessage.cs.

+
37 { }
+
+
@@ -153,10 +175,10 @@ Properties -

Definition at line 38 of file ErrorMessage.cs.

+

Definition at line 43 of file ErrorMessage.cs.

References Tgstation.Server.Api.ApiHeaders.Version.

-
39  {
40  ErrorCode = errorCode;
41  Message = errorCode.Describe();
42  ServerApiVersion = ApiHeaders.Version;
43  }
string Message
A human readable description of the error
Definition: ErrorMessage.cs:21
+
44  {
45  ErrorCode = errorCode;
46  Message = errorCode.Describe();
47  ServerApiVersion = ApiHeaders.Version;
48  }
string Message
A human readable description of the error
Definition: ErrorMessage.cs:21
Version ServerApiVersion
The version of the API the server is using
Definition: ErrorMessage.cs:15
ErrorCode ErrorCode
The ErrorCode of the ErrorMessage.
Definition: ErrorMessage.cs:32
diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_chat_bot.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_chat_bot.html index 53a0b38025..fa50744a1b 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_chat_bot.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_chat_bot.html @@ -169,7 +169,7 @@ Properties
Returns
A ChatConnectionStringBuilder for the ChatBot.

Definition at line 59 of file ChatBot.cs.

-
60  {
61  if (ConnectionString == null)
62  return null;
63  switch (Provider)
64  {
65  case ChatProvider.Discord:
66  return new DiscordConnectionStringBuilder(ConnectionString);
67  case ChatProvider.Irc:
68  return new IrcConnectionStringBuilder(ConnectionString);
69  default:
70  throw new InvalidOperationException("Invalid Provider!");
71  }
72  }
ChatProvider Provider
The ChatProvider used for the connection
Definition: ChatBot.cs:46
+
60  {
61  if (ConnectionString == null)
62  return null;
63  return Provider switch
64  {
65  ChatProvider.Discord => new DiscordConnectionStringBuilder(ConnectionString),
66  ChatProvider.Irc => new IrcConnectionStringBuilder(ConnectionString),
67  _ => throw new InvalidOperationException("Invalid Provider!"),
68  };
69  }
ChatProvider Provider
The ChatProvider used for the connection
Definition: ChatBot.cs:46
string ConnectionString
The information used to connect to the Provider
Definition: ChatBot.cs:53
ChatProvider
Represents a chat service provider
Definition: ChatProvider.cs:6
@@ -197,10 +197,10 @@ Properties -

Definition at line 78 of file ChatBot.cs.

+

Definition at line 75 of file ChatBot.cs.

References Tgstation.Server.Api.Models.Internal.ChatConnectionStringBuilder.ToString().

-
79  {
80  ConnectionString = stringBuilder?.ToString();
81  }
string ConnectionString
The information used to connect to the Provider
Definition: ChatBot.cs:53
+
76  {
77  ConnectionString = stringBuilder?.ToString() ?? throw new ArgumentNullException(nameof(stringBuilder));
78  }
string ConnectionString
The information used to connect to the Provider
Definition: ChatBot.cs:53

Here is the call graph for this function:
diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_chat_connection_string_builder.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_chat_connection_string_builder.html index 429ea5f5b7..6a4c54d416 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_chat_connection_string_builder.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_chat_connection_string_builder.html @@ -157,7 +157,7 @@ Properties

Implemented in Tgstation.Server.Api.Models.IrcConnectionStringBuilder, and Tgstation.Server.Api.Models.DiscordConnectionStringBuilder.

-

Referenced by Tgstation.Server.Api.Models.Internal.ChatBot.SetConnectionStringBuilder().

+

Referenced by Tgstation.Server.Api.Models.Internal.ChatBot.SetConnectionStringBuilder().

Here is the caller graph for this function:
diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository-members.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository-members.html index 938439781c..0937721324 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository-members.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository-members.html @@ -110,7 +110,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); CommitterNameTgstation.Server.Api.Models.Internal.RepositorySettings GitHubNameTgstation.Server.Api.Models.Repository GitHubOwnerTgstation.Server.Api.Models.Repository - NewTestMergesTgstation.Server.Api.Models.Repository + NewTestMergesTgstation.Server.Api.Models.Repository OriginTgstation.Server.Api.Models.Repository PostTestMergeCommentTgstation.Server.Api.Models.Internal.RepositorySettings PushTestMergeCommitsTgstation.Server.Api.Models.Internal.RepositorySettings diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html index 5f69dcbd8b..fd1b89fcae 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html @@ -146,9 +146,9 @@ Properties string Reference [get, set]  The branch or tag HEAD points to More...
  -List< TestMergeParametersNewTestMerges [get, set]TestMergeParameters for new TestMerges. Note that merges that conflict will not be performed More...
-  +ICollection< TestMergeParametersNewTestMerges [get, set]TestMergeParameters for new TestMerges. Note that merges that conflict will not be performed More...
- Properties inherited from Tgstation.Server.Api.Models.Internal.RepositorySettings string CommitterName [get, set]  The name of the committer More...
@@ -283,7 +283,7 @@ Properties
- +
@@ -291,7 +291,7 @@ Properties diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_token.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_token.html index 785c1a6f53..605a5ed536 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_token.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_token.html @@ -140,7 +140,7 @@ Properties

Definition at line 13 of file Token.cs.

-

Referenced by Tgstation.Server.Client.ServerClientFactory.CreateServerClient(), and Tgstation.Server.Client.ServerClient.ServerClient().

+

Referenced by Tgstation.Server.Client.ServerClientFactory.CreateFromToken(), and Tgstation.Server.Client.ServerClient.ServerClient().

diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user-members.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user-members.html index 062cb49e0c..88eec4cc35 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user-members.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user-members.html @@ -101,10 +101,10 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Api.Models.User, including all inherited members.

- +
List<TestMergeParameters> Tgstation.Server.Api.Models.Repository.NewTestMergesICollection<TestMergeParameters> Tgstation.Server.Api.Models.Repository.NewTestMerges
- + - + diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html index b7085d27ab..8b24ec04d6 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html @@ -94,7 +94,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
@@ -122,14 +122,14 @@ Collaboration diagram for Tgstation.Server.Api.Models.User:
[legend]
AdministrationRightsTgstation.Server.Api.Models.Internal.User
AdminNameTgstation.Server.Api.Models.User
AdminNameTgstation.Server.Api.Models.Userstatic
CreatedAtTgstation.Server.Api.Models.Internal.User
CreatedByTgstation.Server.Api.Models.User
DefaultAdminPasswordTgstation.Server.Api.Models.User
DefaultAdminPasswordTgstation.Server.Api.Models.Userstatic
EnabledTgstation.Server.Api.Models.Internal.User
IdTgstation.Server.Api.Models.Internal.User
InstanceManagerRightsTgstation.Server.Api.Models.Internal.User
- - - - - - - + + + + + + +

-Public Attributes

const string AdminName = "Admin"
 The name of the default admin user More...
 
const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"
 The default admin password More...
 

+Static Public Attributes

static readonly string AdminName = "Admin"
 The name of the default admin user More...
 
static readonly string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"
 The default admin password More...
 
@@ -163,14 +163,22 @@ Properties

Definition at line 4 of file User.cs.

Member Data Documentation

- +
+

Properties

+ + + + +
- +
const string Tgstation.Server.Api.Models.User.AdminName = "Admin"readonly string Tgstation.Server.Api.Models.User.AdminName = "Admin"
+
+static

The name of the default admin user

@@ -179,14 +187,22 @@ Properties
- +
+ + + + + +
- +
const string Tgstation.Server.Api.Models.User.DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"readonly string Tgstation.Server.Api.Models.User.DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"
+
+static

The default admin password

diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update-members.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update-members.html index 1c3a24fa44..a272e607c7 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update-members.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update-members.html @@ -101,10 +101,10 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Api.Models.UserUpdate, including all inherited members.

- + - + diff --git a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update.html b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update.html index f203480bcc..0a0c89bcbf 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update.html @@ -156,13 +156,13 @@ Properties
AdministrationRightsTgstation.Server.Api.Models.Internal.User
AdminNameTgstation.Server.Api.Models.User
AdminNameTgstation.Server.Api.Models.Userstatic
CreatedAtTgstation.Server.Api.Models.Internal.User
CreatedByTgstation.Server.Api.Models.User
DefaultAdminPasswordTgstation.Server.Api.Models.User
DefaultAdminPasswordTgstation.Server.Api.Models.Userstatic
EnabledTgstation.Server.Api.Models.Internal.User
IdTgstation.Server.Api.Models.Internal.User
InstanceManagerRightsTgstation.Server.Api.Models.Internal.User
- - - - - - - + + + + + + +

Additional Inherited Members

- Public Attributes inherited from Tgstation.Server.Api.Models.User
const string AdminName = "Admin"
 The name of the default admin user More...
 
const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"
 The default admin password More...
 
- Static Public Attributes inherited from Tgstation.Server.Api.Models.User
static readonly string AdminName = "Admin"
 The name of the default admin user More...
 
static readonly string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory"
 The default admin password More...
 

Detailed Description

For editing a given User. Will never be returned by the API

diff --git a/class_tgstation_1_1_server_1_1_api_1_1_routes.html b/class_tgstation_1_1_server_1_1_api_1_1_routes.html index a2b055b77f..10a25b5890 100644 --- a/class_tgstation_1_1_server_1_1_api_1_1_routes.html +++ b/class_tgstation_1_1_server_1_1_api_1_1_routes.html @@ -534,7 +534,7 @@ Here is the caller graph for this function:

Definition at line 14 of file Routes.cs.

-

Referenced by Tgstation.Server.Client.ServerClientFactory.CreateServerClient(), and Tgstation.Server.Client.ServerClient.ServerClient().

+

Referenced by Tgstation.Server.Client.ServerClientFactory.CreateFromLogin(), Tgstation.Server.Client.ApiClient.RefreshToken(), and Tgstation.Server.Client.ServerClient.ServerClient().

diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_client-members.html b/class_tgstation_1_1_server_1_1_client_1_1_api_client-members.html index 483592545c..37e61b17b1 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_api_client-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_api_client-members.html @@ -101,7 +101,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.ApiClient, including all inherited members.

- + @@ -113,20 +113,23 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); - - + + - - + + + + - - - - - + + + + + +
AddRequestLogger(IRequestLogger requestLogger)Tgstation.Server.Client.ApiClient
ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders)Tgstation.Server.Client.ApiClient
ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders)Tgstation.Server.Client.ApiClient
Create< TBody, TResult >(string route, TBody body, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Create< TBody, TResult >(string route, TBody body, long instanceId, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Create< TResult >(string route, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Dispose()Tgstation.Server.Client.ApiClient
GetSerializerSettings()Tgstation.Server.Client.ApiClientprivatestatic
HandleBadResponse(HttpResponseMessage response, string json)Tgstation.Server.Client.ApiClientprivatestatic
HeadersTgstation.Server.Client.ApiClient
headersTgstation.Server.Client.ApiClientprivate
headersTgstation.Server.Client.ApiClientprivate
HeadersTgstation.Server.Client.ApiClient
httpClientTgstation.Server.Client.ApiClientprivate
Patch< TResult >(string route, long instanceId, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Read< TResult >(string route, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Read< TResult >(string route, long instanceId, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
requestLoggersTgstation.Server.Client.ApiClientprivate
RunRequest< TResult >(string route, object body, HttpMethod method, long?instanceId, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClientprivate
RefreshToken(CancellationToken cancellationToken)Tgstation.Server.Client.ApiClientprivate
requestLoggersTgstation.Server.Client.ApiClientprivate
RunRequest< TResult >(string route, object?body, HttpMethod method, long?instanceId, bool tokenRefresh, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClientprivate
semaphoreSlimTgstation.Server.Client.ApiClientprivate
TimeoutTgstation.Server.Client.ApiClient
Update< TBody >(string route, TBody body, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Update< TBody, TResult >(string route, TBody body, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Update< TBody, TResult >(string route, TBody body, long instanceId, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Update< TResult >(string route, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
UrlTgstation.Server.Client.ApiClient
tokenRefreshHeadersTgstation.Server.Client.ApiClientprivate
Update< TBody >(string route, TBody body, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Update< TBody, TResult >(string route, TBody body, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Update< TBody, TResult >(string route, TBody body, long instanceId, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
Update< TResult >(string route, CancellationToken cancellationToken)Tgstation.Server.Client.ApiClient
UrlTgstation.Server.Client.ApiClient
- - - + + + @@ -205,9 +205,11 @@ Properties

Public Member Functions

 ApiClient (IHttpClient httpClient, Uri url, ApiHeaders apiHeaders)
 Construct an ApiClient More...
 
 ApiClient (IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders)
 Construct an ApiClient More...
 
void Dispose ()
 
Task< TResult > Create< TResult > (string route, CancellationToken cancellationToken)
- - - + + + + +

Private Member Functions

async Task< TResult > RunRequest< TResult > (string route, object body, HttpMethod method, long?instanceId, CancellationToken cancellationToken)
 Main request method More...
 
async Task< TResult > RunRequest< TResult > (string route, object?body, HttpMethod method, long?instanceId, bool tokenRefresh, CancellationToken cancellationToken)
 Main request method More...
 
async Task< bool > RefreshToken (CancellationToken cancellationToken)
 
@@ -224,6 +226,12 @@ Private Attributes + + + + + + @@ -232,7 +240,7 @@ Private Attributes

Definition at line 18 of file ApiClient.cs.

Constructor & Destructor Documentation

- +

Static Private Member Functions

readonly List< IRequestLoggerrequestLoggers
 The IRequestLoggers used by the ApiClient More...
 
readonly ApiHeaders tokenRefreshHeaders
 Backing field for Headers More...
 
readonly SemaphoreSlim semaphoreSlim
 The SemaphoreSlim for Token refreshes. More...
 
ApiHeaders headers
 Backing field for Headers More...
 
@@ -252,7 +260,13 @@ Private Attributes - + + + + + + + @@ -267,14 +281,17 @@ Private Attributes
ApiHeaders apiHeaders apiHeaders,
ApiHeaderstokenRefreshHeaders 
- + +
httpClientThe value of httpClient
urlThe value of Url
apiHeadersThe value of ApiHeaders
apiHeadersThe value of Headers
tokenRefreshHeadersThe value of tokenRefreshHeaders
-

Definition at line 105 of file ApiClient.cs.

-
106  {
107  this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
108  Url = url ?? throw new ArgumentNullException(nameof(url));
109  headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
110 
111  requestLoggers = new List<IRequestLogger>();
112  }
ApiHeaders headers
Backing field for Headers
Definition: ApiClient.cs:50
+

Definition at line 116 of file ApiClient.cs.

+
117  {
118  this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
119  Url = url ?? throw new ArgumentNullException(nameof(url));
120  headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
122 
123  requestLoggers = new List<IRequestLogger>();
124  semaphoreSlim = new SemaphoreSlim(1);
125  }
ApiHeaders headers
Backing field for Headers
Definition: ApiClient.cs:60
+
readonly ApiHeaders tokenRefreshHeaders
Backing field for Headers
Definition: ApiClient.cs:50
readonly List< IRequestLogger > requestLoggers
The IRequestLoggers used by the ApiClient
Definition: ApiClient.cs:45
+
readonly SemaphoreSlim semaphoreSlim
The SemaphoreSlim for Token refreshes.
Definition: ApiClient.cs:55
readonly IHttpClient httpClient
The HttpClient for the ApiClient
Definition: ApiClient.cs:40
@@ -604,6 +621,10 @@ Private Attributes

+

Definition at line 128 of file ApiClient.cs.

+
129  {
130  httpClient.Dispose();
131  semaphoreSlim.Dispose();
132  }
readonly SemaphoreSlim semaphoreSlim
The SemaphoreSlim for Token refreshes.
Definition: ApiClient.cs:55
+
readonly IHttpClient httpClient
The HttpClient for the ApiClient
Definition: ApiClient.cs:40
+
@@ -661,8 +682,8 @@ Private Attributes
-

Definition at line 58 of file ApiClient.cs.

-
59  {
60  ErrorMessage errorMessage = null;
61  try
62  {
63  // check if json serializes to an error message
64  errorMessage = JsonConvert.DeserializeObject<ErrorMessage>(json, GetSerializerSettings());
65  }
66  catch (JsonException) { }
67 
68 #pragma warning disable IDE0010 // Add missing cases
69  switch (response.StatusCode)
70 #pragma warning restore IDE0010 // Add missing cases
71  {
72  case HttpStatusCode.UpgradeRequired:
73  throw new VersionMismatchException(errorMessage, response);
74  case HttpStatusCode.Unauthorized:
75  throw new UnauthorizedException(errorMessage, response);
76  case HttpStatusCode.InternalServerError:
77  throw new ServerErrorException(errorMessage, response);
78  case HttpStatusCode.NotImplemented:
79  // unprocessable entity
80  case (HttpStatusCode)422:
81  throw new MethodNotSupportedException(errorMessage, response);
82  case HttpStatusCode.NotFound:
83  case HttpStatusCode.Gone:
84  case HttpStatusCode.Conflict:
85  throw new ConflictException(errorMessage, response);
86  case HttpStatusCode.Forbidden:
87  throw new InsufficientPermissionsException(response);
88  case HttpStatusCode.ServiceUnavailable:
89  throw new ServiceUnavailableException(response);
90  case HttpStatusCode.RequestTimeout:
91  throw new RequestTimeoutException(response);
92  case (HttpStatusCode)429:
93  throw new RateLimitException(errorMessage, response);
94  default:
95  throw new ApiConflictException(errorMessage, response);
96  }
97  }
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
+

Definition at line 68 of file ApiClient.cs.

+
69  {
70  ErrorMessage? errorMessage = null;
71  try
72  {
73  // check if json serializes to an error message
74  errorMessage = JsonConvert.DeserializeObject<ErrorMessage>(json, GetSerializerSettings());
75  }
76  catch (JsonException) { }
77 
78 #pragma warning disable IDE0010 // Add missing cases
79  switch (response.StatusCode)
80 #pragma warning restore IDE0010 // Add missing cases
81  {
82  case HttpStatusCode.UpgradeRequired:
83  throw new VersionMismatchException(errorMessage, response);
84  case HttpStatusCode.Unauthorized:
85  throw new UnauthorizedException(errorMessage, response);
86  case HttpStatusCode.InternalServerError:
87  throw new ServerErrorException(errorMessage, response);
88  case HttpStatusCode.NotImplemented:
89  // unprocessable entity
90  case (HttpStatusCode)422:
91  throw new MethodNotSupportedException(errorMessage, response);
92  case HttpStatusCode.NotFound:
93  case HttpStatusCode.Gone:
94  case HttpStatusCode.Conflict:
95  throw new ConflictException(errorMessage, response);
96  case HttpStatusCode.Forbidden:
97  throw new InsufficientPermissionsException(response);
98  case HttpStatusCode.ServiceUnavailable:
99  throw new ServiceUnavailableException(response);
100  case HttpStatusCode.RequestTimeout:
101  throw new RequestTimeoutException(response);
102  case (HttpStatusCode)429:
103  throw new RateLimitException(errorMessage, response);
104  default:
105  throw new ApiConflictException(errorMessage, response);
106  }
107  }
Represents an error message returned by the server
Definition: ErrorMessage.cs:9
static JsonSerializerSettings GetSerializerSettings()
@@ -769,7 +790,40 @@ Private Attributes
- + +
+
+ + + + + +
+ + + + + + + + +
async Task<bool> Tgstation.Server.Client.ApiClient.RefreshToken (CancellationToken cancellationToken)
+
+private
+
+ +

Definition at line 212 of file ApiClient.cs.

+ +

References Tgstation.Server.Api.Routes.Root.

+
213  {
214  if (tokenRefreshHeaders == null)
215  return false;
216 
217  try
218  {
219  var token = await RunRequest<Token>(Routes.Root, null, HttpMethod.Post, null, true, cancellationToken);
220  headers = new ApiHeaders(headers.UserAgent!, token.Bearer!);
221  }
222  catch (ClientException)
223  {
224  return false;
225  }
226 
227  return true;
228  }
ApiHeaders headers
Backing field for Headers
Definition: ApiClient.cs:60
+
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
+
Routes to a server actions
Definition: Routes.cs:9
+
readonly ApiHeaders tokenRefreshHeaders
Backing field for Headers
Definition: ApiClient.cs:50
+
const string Root
The root controller
Definition: Routes.cs:14
+
+
+
+
@@ -785,7 +839,7 @@ Private Attributes - + @@ -800,6 +854,12 @@ Private Attributes + + + + + + @@ -832,21 +892,26 @@ Private Attributes +
object object?  body,
long?  instanceId,
bool tokenRefresh,
bodyThe body of the request
methodThe method of the request
instanceIdThe optional Instance.Id for the request
tokenRefreshIf this is a token refresh operation.
cancellationTokenThe CancellationToken for the operation
Returns
A Task<TResult> resulting in the response on success
-

Definition at line 127 of file ApiClient.cs.

+

Definition at line 145 of file ApiClient.cs.

References Tgstation.Server.Api.ApiHeaders.ApplicationJson, and Tgstation.Server.Api.ApiHeaders.SetRequestHeaders().

-
128  {
129  if (route == null)
130  throw new ArgumentNullException(nameof(route));
131  if (method == null)
132  throw new ArgumentNullException(nameof(method));
133  if (body == null && (method == HttpMethod.Post || method == HttpMethod.Put))
134  throw new InvalidOperationException("Body cannot be null for POST or PUT!");
135 
136  HttpResponseMessage response;
137  var fullUri = new Uri(Url, route);
138  var serializerSettings = GetSerializerSettings();
139  using (var request = new HttpRequestMessage(method, fullUri))
140  {
141  if (body != null)
142  request.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
143 
144  headers.SetRequestHeaders(request.Headers, instanceId);
145 
146  await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
147 
148  response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
149  }
150 
151  using (response)
152  {
153  await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
154 
155  var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
156 
157  if (!response.IsSuccessStatusCode)
158  HandleBadResponse(response, json);
159 
160  if (String.IsNullOrWhiteSpace(json))
161  json = JsonConvert.SerializeObject(new object());
162 
163  try
164  {
165  return JsonConvert.DeserializeObject<TResult>(json, serializerSettings);
166  }
167  catch (JsonException)
168  {
169  throw new UnrecognizedResponseException(response);
170  }
171  }
172  }
const string ApplicationJson
TODO: Remove this when we upgrade to .NET Standard 2.1
Definition: ApiHeaders.cs:22
+
146  {
147  if (route == null)
148  throw new ArgumentNullException(nameof(route));
149  if (method == null)
150  throw new ArgumentNullException(nameof(method));
151  if (body == null && (method == HttpMethod.Post || method == HttpMethod.Put))
152  throw new InvalidOperationException("Body cannot be null for POST or PUT!");
153 
154  HttpResponseMessage response;
155  var fullUri = new Uri(Url, route);
156  var serializerSettings = GetSerializerSettings();
157  using (var request = new HttpRequestMessage(method, fullUri))
158  {
159  if (body != null)
160  request.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
161 
162  var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers;
163  headersToUse.SetRequestHeaders(request.Headers, instanceId);
164 
165  // This is meant to be a gate against token refresh operations
166  await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
167  if(!tokenRefresh)
168  semaphoreSlim.Release();
169 
170  try
171  {
172  await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
173 
174  response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
175  }
176  finally
177  {
178  if (tokenRefresh)
179  semaphoreSlim.Release();
180  }
181  }
182 
183  using (response)
184  {
185  await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
186 
187  var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
188 
189  if (!response.IsSuccessStatusCode)
190  {
191  if (!tokenRefresh
192  && response.StatusCode == HttpStatusCode.Unauthorized
193  && await RefreshToken(cancellationToken).ConfigureAwait(false))
194  return await RunRequest<TResult>(route, body, method, instanceId, false, cancellationToken).ConfigureAwait(false);
195  HandleBadResponse(response, json);
196  }
197 
198  if (String.IsNullOrWhiteSpace(json))
199  json = JsonConvert.SerializeObject(new object());
200 
201  try
202  {
203  return JsonConvert.DeserializeObject<TResult>(json, serializerSettings) !;
204  }
205  catch (JsonException)
206  {
207  throw new UnrecognizedResponseException(response);
208  }
209  }
210  }
const string ApplicationJson
TODO: Remove this when we upgrade to .NET Standard 2.1
Definition: ApiHeaders.cs:22
void SetRequestHeaders(HttpRequestHeaders headers, long?instanceId=null)
Set HttpRequestHeaders using the ApiHeaders. This initially clears headers
Definition: ApiHeaders.cs:259
-
ApiHeaders headers
Backing field for Headers
Definition: ApiClient.cs:50
+
ApiHeaders headers
Backing field for Headers
Definition: ApiClient.cs:60
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
+
async Task< TResult > RunRequest< TResult >(string route, object?body, HttpMethod method, long?instanceId, bool tokenRefresh, CancellationToken cancellationToken)
Main request method
Definition: ApiClient.cs:145
+
readonly ApiHeaders tokenRefreshHeaders
Backing field for Headers
Definition: ApiClient.cs:50
+
async Task< bool > RefreshToken(CancellationToken cancellationToken)
Definition: ApiClient.cs:212
readonly List< IRequestLogger > requestLoggers
The IRequestLoggers used by the ApiClient
Definition: ApiClient.cs:45
-
static void HandleBadResponse(HttpResponseMessage response, string json)
Definition: ApiClient.cs:58
+
readonly SemaphoreSlim semaphoreSlim
The SemaphoreSlim for Token refreshes.
Definition: ApiClient.cs:55
+
static void HandleBadResponse(HttpResponseMessage response, string json)
Definition: ApiClient.cs:68
readonly IHttpClient httpClient
The HttpClient for the ApiClient
Definition: ApiClient.cs:40
Task< HttpResponseMessage > SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
Send an HTTP request
@@ -855,8 +920,8 @@ Private Attributes

Here is the call graph for this function:
-
- +
+
@@ -1029,7 +1094,7 @@ Here is the call graph for this function:

Backing field for Headers

-

Definition at line 50 of file ApiClient.cs.

+

Definition at line 60 of file ApiClient.cs.

@@ -1079,6 +1144,54 @@ Here is the call graph for this function:

Definition at line 45 of file ApiClient.cs.

+
+
+ +
+
+ + + + + +
+ + + + +
readonly SemaphoreSlim Tgstation.Server.Client.ApiClient.semaphoreSlim
+
+private
+
+ +

The SemaphoreSlim for Token refreshes.

+ +

Definition at line 55 of file ApiClient.cs.

+ +
+
+ +
+
+ + + + + +
+ + + + +
readonly ApiHeaders Tgstation.Server.Client.ApiClient.tokenRefreshHeaders
+
+private
+
+ +

Backing field for Headers

+ +

Definition at line 50 of file ApiClient.cs.

+

Property Documentation

diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_client__coll__graph.md5 b/class_tgstation_1_1_server_1_1_client_1_1_api_client__coll__graph.md5 index 7996065dc7..59ba09c358 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_api_client__coll__graph.md5 +++ b/class_tgstation_1_1_server_1_1_client_1_1_api_client__coll__graph.md5 @@ -1 +1 @@ -f0a9dde1a47eebb2731806ef442780bc \ No newline at end of file +ec0c6568326d06a52119d01025882e44 \ No newline at end of file diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_client__coll__graph.png b/class_tgstation_1_1_server_1_1_client_1_1_api_client__coll__graph.png index 5da177036a0088fd3ce69b764902d02811f1aab9..076c3e63e2d39e1188c74040123311d5081fdfb0 100644 GIT binary patch literal 55315 zcma&O1z1&Ww>7+x5NQGF5)f$+DQS=rDQRg0rMtVNq(nqSL_h?jyGuYo1f;t`WJ`BR zd=sB@&VSB(-s?L5ez^973Tv-*-*e45<``qH2vub{Tr3JK1OkEkKwer6fk3;3K%fL- zUV*=vnSK8b{)h48p`0}0;_|<=hU|C*f)4ROT2jN~{o1sY^%j9U8Y7uZAmoEt}x&`WgzC{@K z1^J-<^L?Kl-Ryt-?HgOX*V$RxO#=9Cj}NIbyh(uhoHOB{OY5_jY)mKj zslE#T2tU7$TjPWJkg>1XN1DEWU|?W!(tG(wimR)uPDT2?1a(Qt9{1T>J03kAS1&gZ z_AcV4s2{2l>vJg+Ny+Bp-Q|vG%0py71AlU~yS{J1$B#ETInP#x%Rkc71kt&EiI>U~N2@E6r>}2WFNNIY>uF1*CD!b|L#@u8$?IQ(x@JCl(HC5GOpJOMA z=x3IeH7>sgPENeo8Ka|3iLu%OQLny#|NgVaO0DZEobsV^eP!iR2d(YJJA;>Yh}IXM~nCsEh=KiW}}4$N=iyBEG#M? zM4G=75fTmz4&LSA+1cI2rxlALp%L-m78Vxf`1@jDSnNx`FGR6@c!-aMwcu2RUGM!QJNsNp1Rd-0;tes;gR8!JI_J=U<9I1eP|#cJRI}>{Z@GQJ?@!ki73zD^(5B{SuwFw8(l6|Di+B+k z`$}H6b`lLD*r3Mg>C*Rvl*Yx+jXZ3`SlZg7eSLkOKYwm%k)nSz%k>Ql8#`MmnS_Mo z*XE{5_MlON*NyAf1GA{?XH#<@av*mL;V8i~FKL?6vubN6CMI+XxH&oPF3!(peRgpZ z6hvKDhE9)n`<=|q&0SjC+A>Q^38xElb6dU}7#Nf|MMrKUSzPK$jE;`R3?d^V zGrl@JCYY6<-#s`;iSFMIR?f4$ zYx6cDQ1Z~eC@Cq4jNJ7CnU&RU5BquH_RWleD4ugonT~*+{acC_L)@O#$iSyUTBNF*pBsoo0OD(=|kC5OGfwpP6J@gsdcgarx; ziud70gV%m;W@e@YU0T1zm?R&6j0sQD_V%^`#i*UF5+T;$^wcj6Z2`Do`AjG31YBHP zv2%~o4GbB2OYb_x3xv=bXPkU(Q=QJKmKjZtkN9M0VPhlD z`fE?$xpT)6V$O>O zeKpC^o-Qlqu$IsqOYLf@5*>AKF&PJVv=xVSh<0Xv_gt(#uXWIT|ck%Zp_URG9C zo}Qj!lXB|A+VyA1S@mLIU~G(+bqo*RIGz>0mY$w|aWdM}7d@P(ZJKajO3Lr#_iZ#u zDJiKdsHm>I^u$;k?Cjm$-QT}|Z#-FH8xFRzp(?W+%-UZam6MaJtFHdJyi63L@>Rg{;r5x?`^$l^7sJmn`j`8$}ncUua=_V|DPg^uPmkh0M~L$3vFtwPmw! zNF05A^K{eOUu&AuvnG5Gk;1(zTVC$s7t_%3?c0|j4bcc5Zo0S0dCL!TJq=7vGtzHr zUSv23zF+Q5y*QY(#|%P4L-W}m*1mP?ma?)kvMgIUMXD(>;>8Qya*H_K#^&W+2)jLz zzVR~i#f62wH?g$hVvY;a?6ct!5hmPZm_atPjc1U^POjoZT_1k=L!j@LAS{r+zW%Qt zDSvjB9_s1oRR~3raOl>$SU!F{9)6vH)381~q4ynMS4W2&YXSzGz}fz&#XM|X6RfF` zkrA4tR=!S&^(ZHG^y$%J>|~{_UdfZTt@+j}``MVrf)xuYA}T6HM#4l+16?(>g^i8E zR4^%i=*YnUm$#jih2`9 zxYi^uKB+S6>GGP%DJfB4>FIu@j{h<;au8!$`@H2K=H3T+#&r+>(guTxI}uj)i4oHF zSi3tr0|P?6tZq5k!}jl|}z(x$`#E z?`Vu+-+3|0B!Rh&>S3EvhkWxLgt@OY&RAE=#+#$ZtTuB8K2dW|2a{Voq#|j1;uk* z+?K4Y%68d3qdhqtgX!1t>Jeg@BXzCa-Or`&RFv{$s)fOt6Te`I=8sa~%jge%te2cI zsgXNA8AW;(f%7Dh%jkok<3}sJxt~9OuC7v2Qr11|q4-p$oDF#|S!jE^hZ~FI8PY(wRizT3vS-(HF zdU}(v_rvYqa#pPHhf{>zm6Lcfb8;+=r)H zFGD*rTBW7M|LmEQ@XYO!N}6Z3qEX6K?Ime z2+$<+3&KL4chA%xH~jo5VsB3p6GsqXzjKw^weLNelE})c-pYzDyyxX5ADxcW*RM=W zkcc&**FAvCv#_?7%+}M@!#Cw-hr|DN!Pe8`yR@)vu{%4r1Y{>(IU7jM6wzk+K zw5Y44``R4^0u7aYw3L+C^d<4P%25!$mT4|oQWX`QJ3F~~dFT}tJqZcXuC5&VcBW6C zn%dero0=xC)cE`<*eUDHS|noyY!7AR%bb~|Wv=P#DXKjTB zzs+U%`Xhb`-_@vS+=7BITv|IzTU$GO6>_nuSIx!cWf|OLB&}h3aq_ahu2P7Az%W_8 z^Zn7@>p$-W(P3anqoRHU)L3bgStU?rpMi=95aSu!iN3A!fdF&Mw!9X;nWL7vkfJBQ zlf=uUZao@uaxwyo1A#L!@us$xghu7dBA@wF&6&EywnaWuT2f}>yBMu4Gap)vb$H0$ z92^h}O+Ptu$eV91FrLChq*d%Em6bU=muaM{eqX|(#-=5wq>Le797`-gK}e*MbC>xb zTl@Rtx|SBGWyF`}=2Ra&3Unt9Mw@t?S6Nw;9~GNLk5sX=G;rQc&Xm`vvxL*i4<<>m z`;C+j5z{sP{JZ%$2ZP{B;MQ*Mg|^l+8lred$3|=|M z@cDq@i@Kh_G~fJ|U+Ij+rDb-<$xkU7k+NP@S$U7ILc+BVvsO(w#jf*7zl|*i84fl( zM@dyS8AZ!01_TaGdpic)Q`Hu6?(*L*ZV(Sy)VGf=S5GLe~_C4Gj<6H~l=%Pg{ep`X_cJ@Qh#J%)gX3v#H#-Z^}#&bR7Nq zwXY+L@9B%x*DQ8oV(dgj9?2L89FO2!@f!%#*BY#HyDxKdv);Y?_5HgnXH|M>X-R2m zPRXP2k& zR@p}S9^~ufoNh|%Nz>ot+T1EOVG=RiEi->VG!z71r96%4HywY<|FImL%2sht@KR=@aqm#N7vs8T}d5Oo|R4 z#HM{0q|HrDzlkCcs2D#f5jgTZNguOhRUeP*Jn9d~q`z?k7rqE1a3l##P48d5iWUFB z`SL?GdEXVfK7`gVxP$0<*oE7n9Z3*fsxSXpvOURaVbm`ge43xy8oVz%+hI3e zdPAd&1;hcF#%qOJLXKLVp7&*B?t9*&Rg%ihOYW<2R8)$-P>`CLy5{KesZ@s-fNH(z zRpW0p&bN0b?Th53^WO>$8}u5$lG2KEdYx7y`!nciRE({xL`;HqU$Yd(->!?HT(&Sq z5Gf-NC)Af8MQFx@o;usIn4W{#1owiSh6XP)u>zVTJBLz#+=e0(?xZnKP9F<)fL~sI z)WsV4^6~DgKZm=lY?M9SiatI%gV}YxA4I=Trv$pYAP`%B7szsby%gf!($e=a`H8)+ zB}-HN`&c(N0fE~kI=uXo@7hYAm07aC;Y+r)H8)3J4MX(J65klk`t(!WOigppZ$)3Fwh8!V-utFHs?W zB#id=3jl>@W+L&M%F7F@s|#~-a3u+di0;Gj;9%y<0^*#UmE8Q)2p>LvELt&kMI{b- z8&>q2-=tSKvKPs=nIX;= z1;P3K`w{Z{<*xGyL_TJpD7V}g%&LJ)H8GKF7ZyT%F~)#XN(+M?9`3AHuWA<-YX81Z zEG!6dR-vTOMJiXhIF>lNR=7H6)YbGG82kpp#3Kyn3juvRi<46toKo5*)H4JUTP>%) z{(bNK5S)*rU`0qseT$Ug{a^ygmnN->`Ubx(Fcwx6+}*RwbVfo$xeIP18Vb`TkG4!C zot*p>6#O(b<3rHNl|*KI-Jb`eqHiJrx1mA!Bc{@POZp6TEZp-y(tnUj=jGukE-Q0# zH?OX{+ZUsgAv@lR1#6!hLtQxI??3eZUdCM3Rm2>}tXK&@d7e@y+&UOP|4a6EeDrn?9>|CrH`2igfAY$SDZd*I70;q5yUFHR{;ex#-+L3!u44_<#oDL$UjT zVs}Bl%_p@USiY}+He-Db4OHIFU?I{dVF?3JR9F*8Np=9kIXUOSJAARSQoL3G^i^_s z`OekZ%Uf{_>(F}@!+XxMJxPmCO-*CmEf5Hw%ScCH?u?ST@S?IXe`evbS7l9bsYUl%HHC9?TztC~ zcnxvJ$BRis;k;V+5HYhgj~%v`(C&j8M9 z`@~g7Mg}#A?%?2ci79nQlp~e!h~t`$9GNd(fEdKFlhb*MF!{A>_pV4zetvYYyBzyL z+`l{gdh7P(c933j!2v(uHs9UJEvBRk2@$ENqh0JM%h9-7--?!IbU)1V^!(4NUX|hK z5GECMj`J^%<|8KrhUg`8oYn3){YZ zGyW-Gx4k$HXVMXC-du}@u!v9Aqoz&uTvnFV(gHf>_f$=W*r~%*6gkaH4+OWx-z64} z>ataQ$mY5IkzKTbmn~0?HDPk>?VqD!AK$-+iZ*R2z()wNaxKr+#Cz`kTHV{@adsZ4 z_cXjbhTz|0G{BxO|I!lm)_MiYyY~2j=e^m!mx4~88d6gSpI=jk+fGlnCCwReF6Zhx zSfED^WFjj^!O+xuUh^NHL$Et~AWz!Zgu})MYy@1g?k9MOlh874$KP&KNp*S=X^o2$ z4uzEHwh-LELMA~(Ay*iFpd4|@iy+9T{*v=PLnD^><$Rym}GOmY& zQf_G%&s3wU^KuK1AmIE8AwB|w_p(5eVWJ&bQ+Jc5=j5!9-~F-JA%jm#@@q+x2=h6F z{)}gb?F6s8xcF_dO%z0KY3cT_o>2k&MgO-06UoU1miP zpylM-)H^N?S6FkEma_hKiHsH7F{~f!m`sjv*9YAs^lP#JLiAy(N(05Jw6yf|urOLN zA3=8Z{5-keIk6LGtjz?NXmT-B$Lm$IAVfq+zZ3KIa&Xwne_ZDEry3P=Vwsh%g6`hE z*e+YZ72De;Js(7^HuSmVOf~sm zbSK>fs;j|w3MKdRXa0NlHk*U%#V^?HtmCVz$9}y3eGjTXU>2Z9J>;$;e)OTE6&WhA zaV^)?-6i3qs`II#jYNMqi|E5=MnM=nKcBCzt~Pu6H2oG(LvjM8rKLdO@SaCZ*Lw~E zPm`l@a(p~p>-w;OuOf_EPlAAvi7B-0Nu2zr2k}f#pFR7y?717Ns4w>Rs$Y!eC>nLkq(flfxkC3U3`=wo0*k$zBj01XJ?1psHv%` zb>FO-n1tMa{O54i?K6&4{^deg&(tYzhHgR+Vn4}*m zl%5A`!4nhD>KCsV%Ln^?LAVS~)i|rGsS%*fJJmEdH-oBx83bgW@l3s^LPG4E&e^}v zle+GU6nfR(hjva*PQJcpKz$3?O@))l{x7Xz<#SQV<00T1)z#JGU%e8f!b|Lu!9F`X z3l0jZ*0=NYtZ8WQ_3)_Pkj0}Bd7F}w^5*X=F3u6zcXPVA_x(NR)==D78d1Us>65RQ z%dt9A@cjAn(9lrLJS`5L;<qH#D~N6N%#yx5^V z@cYf3JBQ{!M1(wd0;lUPcIPA;rRiIR6%h^+jD&#IXPW#Uc%3xvE?=a^$M3@;`S>)6UcbrD7lZ_b%pIWqfNVAI{h-O30PxOt3l+# z!V}yp0F_G^pEat7e~i_>L>}iu`=^sU|oRdDl6MPSf4DJ zq^4N>-7fPPWQG-Y*TX;=PHo88ey2w%HO>s{Yf>pGO}Zru-Q#8Wxw#aVp&(LG`NIEf z-%~;&D!7$jrMJj^qyF6+tsr+*1P;gj+Pl1n>EIZ83tL-iJ}V7iEZ^QS#e-ac>`Mnh zphPGC{d+3V(JJCF;AVt?*0yD2XJ3O;3O{h9sO_*f#s|ep(;kl?Lb^tOCi+>YnUkq0 zL&ToAwb=ibKR|R7rMw>5MvR3XT`*X}tdPkAQBF_)v9a;|S5NZ9!tb2OwB-#{ZC~Hj zvp+Tdr*X~~es}6j`OAf`Q{I#~`v-JH2Pts}gRoZW6rK_Vwz*>V=aT8OD?(|SSMTfo7VP5LN zgKKY)&VuiEmwSO{M3au`FhI(6-Z56F~}kQ z$Hq&l<>1euJG<^rBFaB|Deicjy2QTQ3#kPK>#!34Gy&5B_A~Xila;=w+Z~ue`GyT% zU2ixbi{4Kyt*rdf(<4*GWz;AFTsEkv>&XA4A|vU(9c?cH%gsrQ^-w`U?BdipKgJ4M z$Oc*spd%sM5JoV<_S4Zxxcl1n6dKg=3ktq%o#*A}hnw+Zh*WxCOG`_4cRn=JaLcCf z6N1P^NZ11DQQuBpUf$R^ZNvE4kN2Li2{f^{6BOV>+%~4}xvpqXZ(9rwNA{J~9hyUK zg|=pMw&~~bZiD}MjWDB`o4WdBqLJaj&P0X9qrKmQ5detlP0`YdJUzJG)=##Uy6ia( zCcZuCKiFvC_uAX&6>oa^@+C(+%T^3tK=bF@!saO4yvF{5aM8THy!izMaKnnw^va^o z14I3f7oe|i3uF(ZI!Gswt1Xm_jAkdR?DP1X7Ta;f#lBmys~y>ywfn@9zck z6;1@H{dswOe6?|-Q@{K?hT!oH4#A)H{wel^{j%jFU zWMyR)neGY+)eg!^(?4=>;1&|synHv5k>iethecV{o*B!% zw08mc2>wfzR_l&_$^+@>tz+HPRAyUVHQ`#c^+dIr(b;{EE{ygivtu7nu z_C=Tzfx!)k33&yo+#S>Q_u~FDU0v9lItb*ArKMBv!&3i?GfiDxmIyOm*WAf4+L#mp z`|_H5IlCz+WYSjFdUjJDp1%j;r>3mj-9ww2Hgh%Of%>~FZ*zf{XfFT*=x#N&4M^wl z@w2ipE;oG|%tB^fZQdY8-iG#pV0#}Ps z6~ktL=+guR;Q8~l$*Q|*YCBJP+J1I&{sZ1|F1tw!#9qo9lI{Ift|CL<^HWdgbd-$e zzTRMC8XJ>1!IfB^E5k$tCsjT@pDE?zE*2CLa;llCvJaEs=DlO`O5P3KYziR=)+*Z=HLA5`u1PWGTwo{T6zaHgWQdwOyL- z(gJ2Y<1-E3kJ(8=q#~lCq_MHFu-r!+C#l9ucV0kFetKuQU z!3Zt^75U)6Ex+Y(*1z{zS*O{~&(8R=($b+8c}2y&?d_ZKoi_Y`0QA_8Q-Cey$MCS* z#rZ8RBfm2Li-EPZ@AsTzdXwE37TUyR==VoW5TWds;Ta3Je%aG55;RGE`x&2;JWe;e3gq?|?MmB25}&62iU6=6Sq{1}_( zxqDX=U)#VS+73HJiYF<*G6Slfi;D{YH>)xOTU?hLL3H3R@{mK>x(?#P=Du%ihxPeJ zo%rc#V@Vah0pO2lO0G<`doz+K0hHBEC=yLXL`3S^+Hy?s*U+vcDHi7CMMgw4e{gkk zlNA8q&C13m%%R=7i>od+KTF## zSy53_LrovTLcMZjB>%Cxh6ZDN=aEmNKBrt)g|B>RSy?NdwuZ*1MF%S%A0H@@TH4y~ z5PA@e4Nt9&3COHyzlKd5<2i?4%zG;a2E6L;qo%0< zCnIs;biA_!PK(lUoBXd(6-Gx%*NFq^3}^Ru#;rmaCx0)E6?}}4nF~GpH%vLLB}f`^{%UL!otG9 zwie$R6%*6WDkm$e`}nb|4Tvn@Q-Ov`D20ZKDhiM9krnM`psjwbOJ{3qur$4=w>Q|K zT#NYnG{a{W1!jKHuh!VxpNjtgArp`eIy(CL`g%o01;q8;XWhu{442%rv;gI7T5-R6 z(5Bf*#KB@WUiuWNZtp)bZHLlszbD{SvjFAv2HOuD+D6xrd{ZdZI*hO8sEH8`*oMIf zGWl{}J7jO_Z3;?CuqZ)Rx19bV01*f782EFFvbfhC^T8Ue|LOetx)OaHw1MC|NVxbz z&7pf+T-+Z%O@no~rR7QIt6QMHQ{G67Ldi3g_k#0ta{+YWAS>U$k6MYH+FMmoVGFV3 zV&q~%-kqzNhesut_NV0ur4YrYN2C2N$IYLVXIjsg1fXs0nGpkwg?iPmD5w8a80QNe z;w%UJJo5B$r(3P~iyg5Y!UTQPSMjKr+1R?eyA@SbwAa&z91TsMhKrDC zQ_uqV!b@^CTM!XoH+Ud0X@Bts+)eGVv_;++hJBMSH)duX+}!>G5Jm)EbQrSwTi$m}H8)|KVu#MAK0PkEBmax`)@ z;0EDOHqg?_T5in6!No0^39x#JcPQY(Tj;I!yD;bvC2Ej7V~xl5b`-f>DE{oqra#l2 zJ96o&m$4!mn)aLvL?2^S1_I1~Y@2=ASu4p`CR94VenpccDA9@XJCGq@6czOa${{BQ zVrPFSM;)4#&1!pH-H8}#VZ%nB@v*URx^VDrDKf3<;Cjn5#Y0Q_fRWIP9V;YgCcjV1 zhyfsNd_2+h>yM0#{C9sTeQH(wE=_+u#V=Zdj+q$ChTtFw9UC%SnbXqu%RM*;pVsM4 zHs})0-JV=_gO%i2CKRnmfz7$*j4|p;q#J` zWk9-^TVNVI`Vw%p=_?}O9Q)VL=ERf~1L(DCqkA4lStO0>tc&OM!h*}t5y(bU+_-Tg z;nl04H{3~z!{eDH+(Y!^;Hb<|&n7^FeqjjmMO``9GdRC75vP=a{_0Bg!Lp~^4uE#Z z?~3>xFYSt-V^ZE5;AH%n=st7LV?+OiJYzGkwKG_kG6aGb8wY0!tTmdKuVL`lYIF(1 zp7VP<(JGck`J$!XbN&Ta<00}vgB4s4<2BC9u*m({{9uUiJ3E=0(vOgSm6SwIK#*3F z13$|vAmD#E?G9bf?!m#vK%)*B_&FkDV#GXm7!$i}?d|=+a0+ z9_W~kwl+%}tsIRHqTX-h83AFDvh_O~4Tr&|K^p>UEDt~b!R~Gy`0RA4Z8Gwr^9%%{ zv(v6e&5|KO7Kov3y8GnoXknqS+vd57rY6J8b(FrR+2=Nt!Ifbm@^I2m zv;+R9p?KiLizMaJTGvF!KqG6qBZwKY4v;oFq6RvxQj54lQloA+*NQf&(ZL~n|HZfMe)0IGnou3cDeO7Lc9Vl@GF$#$;N%4b-r<0Zz ziEcq!nlaQ*UW%T90jJ*snLrVZZGd|TZe%7VCPPC*z{?{3=k>rS)Os5le%Q!Z&9ddP zZH7XVmPSVr-EPbcxLU|oZhcBJ78@70ASZ`~jSVbWli+jQ#=9DD^LuyqPjE+ozW(QE zJE6!hq4&L~85lT!|9Dc`KDi^*6`yF3vpA(zi^Ca}RA9P3b=QYr_xrzCryl#ToO z`4ItY<>n^lFo&3#M{s|F1wQ)oXS%6ru{)`0cnDMMl-p`J*T2!@(TmG)!suw!N8miA zxHu`6ie9Be)=(fm&U&HeuHMwp)HK`r@`A9m>_Se`G;dh6vB^nOhF6i1k^A?A7%5Rxta9@&%1$ntmY0?|^eUrdGm-$h z93P*ZtQJ|_s6Bg3<##CI&kZ8?aGs>2V>9?Dy59I!IX%URT1A}ULb{kU1@;8UBLA&9 zN$0;WC#Rj0{Z$}YqQvqXeS-^?$BJYpy)eC2X8J2&0p9{K84?-_L;}3*rfc^(V(}zQ zOy+^L&Q?s?0b`&`NHiqX#mj_`-zD_ZsV$6Fwm_eJcjxIPOx{W~{GEx8Kt@TWAN|fw z_3`!nYx3ss_4Vb_uMQBs{7k!ijIXqvzQUZ6N zsl(plkERBPhWF&k5oajfH@NX_ZEbTjtW;H1y}gBX8zDweui_y+^=tGS!Rp1Itdy=Q zcD6S-GNP3)rKpG#*#_7;o=FKJeTeM-1=^xOXc~~oiJWI0z(Q!}3&wvi40RVoe7PJH zAX_^*KEj<|WG)w-+@6Pu;SWtPn&e2KfiNWPCa|z#262-`0dok>^;PF7qT9+-jlO@@ z#>-%#jC{633JPq^%oue|winvK>r-A*0v%ty`{oSzUn|h3X1^Ro-(=&fm_znY!W{-Q zQCOHiw;dJHX84yrK7=6~=y~Ma4=ik z^Qd?ZSV3wLkFpx_foFa0f3+)agpQux@~>Z3-r;nZ{AG{-=^Of6z|k!uCzmSX`KoKF zvZ_kl`@jY=<^*2k^mKH<^(-StavePA{c^M%2DL+ga`#t;0lgStT?;tx{q}7R*qDps<&CtoL**0& zkL8qLU`SaK*?^0}aY8lDr$hv69%3XPED>nhb__U6r5g$fA#?r24D02-*hlhn8&X-Q zt1}2XE^I7ysidUXHk~?cw1g~f#bDy-I{2d^P60P;HeGz^c^2OZG{J4?iqn^%io$uD z@g(&;OBHsdFYe@xqcH!B4jeyPqSy!x>ZK1Zo^L5H~j09XkM232+` zs>~XZs`PPa=;-|xycogPuU}7J_O!It((9gx`B*^xUvvDA8C^v1)- zZ*PyjI7en6!I%YX9>#5tc#Jfo|1G@u?R4I*b6dB9{_Md6pa`z*jDAha-93Psa1|Zh zv1V##$I09K&(@a1v@MOO7cXE}7M6(CdFTSmFJtn5B5-Yv`ukycH;9SN%*=qfpRRG1 z>x~~6$*+7{K`kTafe*tcCwnej67lt=o7VA+b6_^bBZPQf&i}u^f$#o!+NI0cvZ&yYFOe!iWm#W{Oe-y^#e}^d- zE-w6OaMGBR=vZ|)IXc3eh-?)Q&v6$$p5+b27XPts4D|y8YPQ;_#xKts8U>M>#f^e} zMm|2jptXPhz9ojbZhB-S#$C{OD*&$uF_$$M5FjHunz%JU;h$Xohb_=gSBt89t{|Srhj#(mu+^K=S%{Jr` z?%)*=@(~h1L&ze-eYnwjO!HGzE?(Pky{XHLMXA$)X2Q!o;ibX~pw*y-?p&s$5Jk?X7A6B?H9MtN3_m_B*1;%V4=+>c~AOq%iaa z7g!k24i8m6)OS47OBKBSAJy|qAFY4#<)`$in>~>^8DELQu0Z>!mhOuo{PCuV?TQZS zVzWq`V(%}?kdTnUEG2Ag?A^mdkgSUg8(xHmAHn|Z+ow~eA6$L<^t8Bm?6%4a;6J9< zCzq4neu>8Gi)cPAz49esB=~Ma%cl}deKhXpHdNQ(7d?AVaS7jozgp8pCpC)v6N`UlcWg! z4NztuJ40FXuZKuE&V4}vU6D1R)^1uTT{T=2$@}R%wC!VCyPc4W#W8>qEFJkoWkWeKkSaGZdwu* z)qK1yPiIT{yr$z{d0RBe*VNCjHGIPrK>Jcrfim{3w^tmTtGaK2tGOiI9Dz)SZ30#N zMPy{A+HYvW_VIIQ=?O4t{YCEGx-~VOQsQ&`yQs02p12agBC_dZb6N=Db?pEpQ8~Le zMfeaVA9yKnXJ7KF)g{qDRiUBr197OhxVUg*QvGnr`%}sXELl1Ns(Vg9t7iQcooW~f zEl>7-gTM~}Vr6~t3U}W&wsfbrLbBkKkOlv9da@5#6(onQ;tE6a@Kl zZ`1xjKJ+l90~ju z!>?>?Y#==VWdof(X?^g3#D0%#x@vW8E$EKmIJiMV^2GY$cbR=hm?$A(V>BHmkNu1Y zQ#?5dN&Ax$@CTm#IRqa;{QLJ)lO5>B1ghU&Cy`nYKFRH~MUxBXimP<~XJVi| zaird&z^k9<7g23U@HM-V17_Qz=#*cS^l_LyY+s+;36Z)UA^qPmtRO+9iIvrhjSzam z2#`51UY^vvqi8-@F9DmT{Wy>HWMz%4EXL*4kRnykLph-YZ#Mqf0Fnzch?tm|-Kg=s zY{p|piF=J@x#s4Rfgab zSSG*)0WwFkvCVXCMZ;N-lKFpu2wwk}@{E#_lDrhr&Boy8`5fx?pHqH{8T|{3izQAK zKjbHbXc^wsvNPCEr;<&+>@;tRGQe^ z$J)V^6t;o#BU(kKvQMAT^v-dV`1kO0QejGh%W%qJLHbLLGt2HTnP0!O=BckEjV@uX zBmvAJi-U67we%)25oUFP9nM%v_1Wo!g!t~=J2f>m;08VxIBZPSz+f$AkVDPX+}zxo zHz4R-&eKBFph^B48_HVseHeVdmi?9M}IJ))sff$-P`k z&utl%COXK={f;kW5)Tq8JDvUWni^`hTDnJ@E}DW!?+k19>Y1&h@*NicRr`kzz_$w` z;P>wedxb|J7yLWekCTFUtrWECLo{`vndBik`)<=P4d9fl4S(CvPdgF_8Q4VH zrQew$kHwGbj~vxbXoUquX$6*X@ZPyYF6!k1f>)vxg#?U1nwg2jA1}SQVgA>vb9E0Jb@Pxcpz8UpNtoP( zVYb4*zJ=aYasSPRL!f9+UIzxE=pMI4k^->?8Y)=AU`B96YiwYk#f%4rhrz)QHjsNy zzY}Mwb(--28c0yM6(KEEh7OM-aJ$k@|A_eQfhE1WQg4^;-9r~r~Hb;v)?8fkCyZ4VK6SOAc8 z=_(P@ALr(bg+R|A3=R%1DHOAUaliio;?sLiPEMYjRdfHB@&|VR_42ByT z+NCL}HMgu^vh!gQpaB!((Df!2EO9?R7zl%Xer7(P@zNO&uaoOZI@=xExA?g9KG|a3 zt&M}mEUm2c65_br7lH)wjv~^Ls73$K?qdItQ(IbQTG(@E=GCpmSgOZ%&oHWrF~l!_^^8==6P_Z1KsYd2 zYi}81-{Nq}eynwQZS7}&Je!+I3krJtk4&QLgi$vgBsPN%!BHM%c(yeyErgA+P|n#1Lei%l>q@bTjn@grk)xJW~^&82V&I@mA|xqfu$ zE5XqT{s1po03#kAN8nV3r$OxH7^$jmZPabTpg&RkMGeaI&=9Zh$sTwjj8{Dm$)i~$ zRhF(@I?;tRw}dZ-o2<~5)-l?pPjv3W=fhX>qbzCED>LbV%HPahWXRd4-it5y-1QcA z=j7%NaB*~i^ZyAlBI+vOX;6ip%~7KxQLsWa zuwn}=oo#JxVCh^>k$1f`4)xJudcOL;%%Rz((>hZ&3!^Mncv6M8LzuI#X4dZ^ z5OzGCrCmvPQ+0ITEl}ej(S7S@bq+Srh)zh7H`g7#XG6NlY&v z#-PJa0?ut-OEsvp6EV322}@hNp|4X@o8WOCtO-^oCM9N_uWDtl|eBI)N&zO&y8e;6GZ3xnKVRgtUBF zA@7Nbih_b6E`9-1?71q(M@PluYu2NMCbupQ{VxPeTF}6+0T1E;8zQiRQ1Wv$z}0M+ z|0DP+{s%GNTvcWQG%&!Zz+(`GhlXCidIfe0qKeP|4yNVQxopC{a3HCpspV{T7!auz;sbwUJhOnmx-8(9O!?8=-#jod|Tqr;_ca8J|}PHsO}`) z!BEp)k+k2l2orX%9G}$V(DzsYqrOKNsu#t~HT?6rbBr%C|F_4+cnJx&+2L))w*h>7 z;1mR$$eN(=4|XXp{)=5qgyBYcvQJG-4IUeivo#l+Dcs%M+G9R|F~@6fWyQ)xkC_-q zqI~@h>&VKN8sMJ+i=oS8Tbs;2GNm%Z%?jwaWNGHQn)JDj2VDMJp4aOH+4hYeZ#Q5( z3MSsH4gi4~oXa5ISCwIlI?TD(=m)wBrgeNd!$oXe|G#WG!v`t%oSEX}6_u4`2^hu1 zKFDS$z2!!cdDx(Z_PF~LrXq6W8C$*!HcpaA4EGmfS^WP@J`PO7 z-3*uLy&U+Ug5m=P@z<|k8`JjP`^Vk$FoNiyj}~@YlfX^_ zqi?cP4ON~B-KqNr5?b0+h}z-d;S`V;VI07UV{&}lM2?G?hn{`GiRYSbAPw z7of|j^>jcxOzdiKn7@+Rn<`*Wqg(yY(>Z|lS73Yv52gqRKt#*o|9_OA%X?xrZfv5Q91+!Bow5(6i`a(ZUm&I8q}!;CfRQTzD|STUEKtAmc_MJp!Zt;hr%bn_qk;H3*gc}3p|dtI8XJtm;n7PaH<*~{*UT7se_VDw+{L1 zmpE+%tR%onM%Lc{Q&~8A=RM!!s`MS%B>_QHqf!&h#j!+oTN{fMwha<=+K&}m1>&m?VHBHjAFJE@xyacAoe0lTj0eTRaZ+$mg zD*gGC_1v}9-0Pdx(y!7WZ+&%pnN?DXIW?K?bp0LPj8^Rqa>}+jE=g!&HpOPsPRvrw zhdD^6!SxXFMd#R5kwju~nd^1Sb85jqjXiQ6K5UD{a@#MV&7-$L)-=lq7c zY13qFr1c&CK&>N_$5;R4(NWwnJj@D=5nfiK8oevq&F9aaW{+KAud`?c78TYRh`m$6 z?*XLW!6dk15r~S&#;D}Wg8s+qM+sWfbi74UdU@8%xdM&pkNt*!5XokJbeWxqjZ!v* zD+>~@#tk}AhPt@}ygE`+Cr3vwKFA9+8ju|9ttyL(HZu$uk*MkEDJa~7qXgP5@Eb_n zXJ`Kl+D~W+pI`|CB*(=y5_$Ob$At=dOn*Wv>#=$MeV!AZu<)!8X( zH8Ou@!&>r2LC=pu_(YI{&(5~m8sZu7MUIwQ{UW2IS2Lydzc}-yU7Poq%s2rQxC2Bh z#gKVIfCcE*#hA7&jqyG%MdyCKqT+rC!$gcD9GU^FN#0GctaRT z@lsOi@kH{;jwm_Tj+D5|!6hYb**uBw@}k@2ZEuCe>Stw}YPh~97>E|<6&2U^#=swQ z>#~>O&+MbQ1bd6{c~>5YcU{(M2v+oqWez>iv}fh!Hr|}fjpx*_a^`DM8ay4OMY>-@ z7YC`eH*CAeNs~ajhgw`1ZNtOmAa=8wOCzE%XKIMZ_0GLNV1EC3NlDFAn(A*Z!C*1; zq4xw52STns+RvJ2SC*D|?2%pm{^-yQ#KszTZ_R@{2O{D?y2m6UG8@@4nf`XDPQKz9 z4OTj!S&+vu2Z$~!6BE!oAeadX2|<}n)@4A_dho<-r~#AT4c}g(OI9XM!Nn`yA@loetCtJF2R3#uJhSxu| z>Ik?FC_@HnZ~~b4LUW+IVPxEbkR&_g^KmXP0DO3258x5xe?g1(Jf7nv90X%{5XYw# z{2&dpshj#DUjMR|N{X{fW`sGfI5~4|g&jw`)Lf9pN0IHo5>YM?6m@C5p|+r-2< zY6X4hegt*So8SqM0E+;erv(M;09iwyYIMO~wRaclJ=m}NW@np#94;w*@py2n5UH2t?(v%FiP$Dw-uYMAv`*IWJ9)V`*)ijRmx!$m$80r;gb+Uk~idDgpa(_ zglWSYWD4?ZKPw4;rI?){w~9+_xvnGq^HKVZkKy9GP@FV-pSy#CIm(J7o)xN?ni>*r z-Vd6vLV%MUt#`3K|Fpckj8Db|%k_Obv;)llx^Xbk(eL_|5twFEvkRPR0;vL53=f+? z0-7Bmp)+tin(ib{CJmL!Q(jRH0FiSFze6@a5m@PPj6IQ(T3T94xGu#IMbE$heZ|T4 zLT5@!3cP;>YDLhaW&eKFE)J4akb~Uab*P;tYNS9QQZQ>Zv`^j@B3(2msP`{j3W>E5 z!nawK)N|s5Pv`Z0?rhFoUJRy7dCwxW5&O87W4y4>r^LnO4w-{;41l1HUv>#zA1)xI zGxVd)$jpqoUQAZY275-?sy4r~erawhn#9wbj+jva$x123qPC6jODwat$aWv@q?8!r zttZPhKlmTwtfe~e#vb0y;rTg*CwZCU0Fp%zK-$jKNj{8w5Csx*;NMqV?}e zcEw~zO%0vdz(u9?RP^>jTOlPL`_(n_u|nI5w?oHvEPuGCr}Py)Wu?m7_B`a|WpJH# z%Bo9ERErcmpFb@5ai3mb$44?oO|i)7`mf+wBJ;TB;-|W_tY$q8$g)5EhoFU3hC+Ug1o|tgJ~G_`l4rlA6wkw+?fDW9|XhV zmmNYAX@JG~fD9sv36LR3#58fhOzZ<>)8tM?Mh2K>{8-;fWv=40@a}8ZLgx>lzDjrB z60d5Z;ZM_0-#m%l3fs66+i(j@OVD3|#y8J(a_z1L*zFOe9}{#I!#Jtyn_Kq10+xBF zCx!^K{g2t+-i?fe7>pAzy16V3_>98ll%2LKXLfgO{qIs}+VxI}tJ1rV1cN({PM4&E zI)ciRR=w$58)uz1FC#ijN4+zvuZ445MkQ7Q}U^i&-1%{Z$|KxBhH7@S{r|)2}f&4nz#WFiJ092O1 z%nfQS?aU!CUW)=R@9C}FP8MjrgcZk(GSxs&1!p;YG;8b(%Q?M5NFMzL8=+B|M!_i2 zKMWVjK5F{=QC1day0tzY0%09QXMz*F{l$3j&jz!gz(MPF?z`$+G9PWnR#n)0C=5vs zx*2WZd`EVKm_bHq0Qr^~Ug5iyorn5OtVBUYlmS^Fn+Ha_&%`|U#oVOU+5UX6tzOf( z2o2LcvTj1jp^&;xv;3_;Yy+pY9p+Wj*xc{tdgPL~S902Gd3o-fAFEDeDvt(?TfU^v zKlo5CxhF~o2ikPaYpA-B%CMWR4`pKdwf^1TKmQ%XmWw~+GUqj(DUDAQG+LtH4A`gQ zVmLmCN^?@Z*ETo%mUp2kfk02Vk+1?2tq#GV$kw=bOPl~;A8Iahf}nOd2&Ni6cvbtM zfr2+QVzz68!7vIRFx+N%YM{WKoSHgZ9cJX_9{n$O_eq`#U}tsps844{2cQMJtx&?h zK0Dg>6iID2x`vLP4Q$7vu0VsdWQivsnr!Uzid7z)#rVf?rBN9tNBADpEoBtU9*k! z!*BB5mP|cAHRBiflHha2^}^u5ufKb?yMGoad2%co@4JcAKe_HD-?AMcE7=xl)bTJ| zp3^Pws(iv%j1!8Tc{=sgS5c~?9$J2cF0Qosm+XiXCDYEd5~zuFumX;UicuD+NEHt;k??MITzoA^eYe2%|d1PlPZRoJ$6D!iF z#X@WugZkC7A*tlIwg}0rkrJ7OLxx=6?El!hinp~trQycF6o%Xh0vy>jX3f-j6DNbB^1TQml z<&6(x-QQQzl=E}41Jp;b3$OpkE#92 zYx&;P9}6jEPb0pMIxJB@)Z|;+rub_GSF~=9@QM+Sz9`OPQDe!6_`ia&o`Q;x7JcDa zB2AA&RF>`gWuiv~LRtJMq`o?>tE`8>#jNz)R@HLL>Y(?ZI+k)buMEK^rmD50aTrJ; z&6Ga0xwtmZk7`wj;0>!;aZgstupT)(k4T>y=CcXDX?Z@QKd;5hgIZ0RLVQ|oEb0D> za)(q%mx3-pnTv_$>o>8;P+34XI(!?IUWC%iUK2Nct!+*iU(Vt!9wSK34iXmkP}G! z@uKw9e|>}E!F-)lWVds*jl8VP$+7pM0UuUO3d9XM~m6x0;V;3>Bp zZ^tF?2lBuIjs>{$<5%}_5tG;7(7gpxO;IyfPYZA-_ zzPa?2^R-{^*b}M1*R=-@%T&?b(VzgP^1G6Zl_CgQ39(M8d31h}CyH6hW9A-GwIoZg zM&>c~j)NY27Odf%kiZyEZhdWNryH;15(r;;lbRFm{Z$-~hs|$Ol*_6DTR!xheuQFI za(vywJ_aXL*Sum~7F(!@@>3R=g?BDaPv7BOKiTB3QrJl%A8y|0fwRF-^#~2Uker@O&t*r(NiT1^uIm`abgx};& z2SV=i=1rgsd=nh}HBY&DZD#6pz7_wc6`ivD+o~UnBDWBTkJ;%%EBOImjP}d6HCIwC z(X?CUYX)m=&xF7|;;Z3Y zwMsm5hsW$s06+YDJifg}YQCqQ^9|RKE#a>2&{x{lOSQEzTTS&-oziwke<`Fp@p#S} zGksAZ@^t2o!R!5YK5y1VfADin?q_KXz$+V~#zP=c6Do$K`tmz9RY_f4)j!qA#nvz0 z$6Lv%m*332EqnzL2#s+#-T;9IJ$C2fE_CKLKmXzXyXv5m_oE-o&>Y-8D+YzsP+zt@vD znERB^8}4rnuE&2;BSKy>k1~@LQI5~OeLLNB>FxdOSzrCupW-4_4Z)=T>-qBtU!Ls3 zA?AG4S(hx_XPFjpL2ml_KU#q86-nMT?+~{6x<@rOIK}Uu@qB>WnS8F zh(S{jF0!!^gXD=OK;qZ$-xg|`*nL(v zPd<9!>bPKx$Nai$WUmu{pir7bh)82n-VcHIBRebW`1F*woBH1zc>LZzZ#sDKoCti^9si9&AfCuROfSLKYu598iI85=a17` zI^zcmLMq>D7h^i_-`_kVu<|La(Z(Qy_@_W{V1%DAYcD+L{!2+_V#|sfNpk7s<~lB> zbMb##7yJhv$Y~=N79b4BX!E|W{NIBE2Ej{TQ%EJZ1rr;tp=2d zxA-KmaJ3i4>ApVoc%QETjLJqF5K*P>Z%0vi|u0*?{<_vuRR6?!1BIeY{W9Q?Y zm#E8HfE<4pCnsU=GrpLsn~;`Q+;59MVEfHSaP_x<1k(_Yig~hUoYd~z&lf6oWwDEL z%QYMn)A1C%aIXED@82Usnz-e#K^>D8gsRvOXZU243w4~ee>zymvC^R$sc(K@l{p@>)>a!>|#K%05#gOZ)}mQtC_Us zqF?j)ouwM~nm=-wH~rT8Jq;5CE8f9hUD-=R#qkZOe`}@sXi=n77!g#7Yb5U#572*J zZ}ft|Lh@>PsM-)ooZtIjcfTxuB$0b4w}f5yFjKixL>*pO$nfq!Nrhk6Z&KG*^4zc* z1~T8;3@hZ$8aZQ*;Bep7*`duVfCRv61t;7o zaP|V7S}K%RqbxgdDi$z#hF#yUV--=9JTMPeH_uCe+S6gLBGoQKy<9upyU2NP3JKz$ zo&9}bmp@6VsrtIQGtlLO*OS*`B=;cfbCC$s-wz%NUBlY65fHua)e%ez_SeL`B)HwP zvqxIOX}F4_Z>@j*@nG?Ua1%%?KuPgFJb^$tIESHZwguVAB!4h)(jY$r{|N|KMN(4} z6FtCW03=(r14-XoY*Iw>G*AfeSASYn0W1psw5}%Q$;xJaR4o_<+=TE(0BwXhK&K;p{G9YC5@BvGwa*l! zq(`7jSxMD)FRIVMd&(tn;BHjiW$V#&=#Z*eeF7kv*Xj2^C53zIq>FEb+pf)yoLagH zkT!h)aTAz632p=kQ330m4`WEodDh-&6@>-`5u#9c*lZPw)Raka2I3h&@ERK)KG^8t z!Vm?{8UfgE))=0aGx7%-f;v(uFPgas((z_ybeUtcF0Q1YE30D>RpT-ZdccSZA0tc{3L+Olb@X^YenlahpBJ;8Pfx>%*^pAh(u zQjLHh0zMAQ{pmh`n$~%Pfu{F7m@os#+v9YzdK{8A)YZ+n$oItxMuC3e1j7Cwz5$dNfADYU?RypmpJF zgfrn)G+vQu^&(f#1nambU_LJ>X-eRDc>}^C&d?+t?LQO8I^X%u21E<|2)JFd6|bU(&}NqwfAY2Mw)VdA zB@lq{Lv7nea2OTC8fLOG@5K-ONuDrhbolMi_#<3kf9P z%YbF6`=8*5W{6`eU*wvgQ;yIc^10ZhF4H*coj@BTTmUtEG1exI(W8#m%Sz?{scrCS)UZYQu^nd*D2{-8g%eraN&F`HqTZP#|r?J%v?dINU_jt5js(<7rN zf~eC1p6cpo!KcJ2&hsx<2D;3jlQ0jBkMn}_kb~m~Q1b3PfbGI)41jF0Xn?e$qPhTD zdq)-S;QsC|UHRhwiz)GksV!g;880_`?mr0rSx}31_r){SHZig8>>mXErtuIlTJ0m_#2v z+J@pD9zDpJgJEn+rNy1F&d0IINML2iK-;XRN0qEFzUm14S2M&2fW19{n&4yn^BaMN zu>6p-aAHDh^TNZ-b#OAD;xSK6Q_U|Xvo)@t;Cz5s6bOvecVDIFa9}Cs90>$7nfK`? zsP%j}16B?>M|?Wv8^4V#XG|FE#W(w}4PR>f+jk@g*no=g)qzrBv$PbQkMjd9{f1{g z`Ao{$XQy+Q2mAYySl}0d9H%HxXBe9XCk`37>@S|>-^TjzAtXD(iv?#686;d{ z^+9$bu%$>Eh>C>6KoF!wnj5f30fTW<0z^U(c?FTGW##3TR#r&@PHWI=%uG+Sx)n8z z!yIxEno&46#@@hNKPD*w0ukt|`D|kc_%y6gyI~FB$Vq>8=QA|Zuvh12%a${BR;GBP)g zA?A_vm=lX;1kTwtdQ%yOW|x~16nLF~ZMaIAvS;-(bCd~TqsWvB0S7Zl;I?Z6kd>y@ zlGe2M@8?!jM7!rtICOM(zuKIvgqKA5{%vRnVeXYXh}64sSoynQ4NVQ9)~2fdsTZo^<{jo13`W>;!NF zmd_98=BjP=AyWm`qNSxJQ~=LK=!t^h`VFnuxm#`D5>)6;}iihW+3TXm`n&n+j zfaQGq_Uc+eaamb}`vX;O)jSm-E=LApQ&WAw#P+2#Cntw-G>cnQG}XN}J~HHOLa8;! zKksZp!v`R~;m&pS^^LAN-umD#fGO8F4)o>-gS~@8V?%@1P4+;;^z?K%1?ifbBVID{ zqgu02id&`N0H>FYtyrnNx0`zVvkL-fqP`7X?U-RjF8)YU1wU{ez{na2A3F)z63!7K zG&Hn-Q!~zB>K!)rP}uN@Y_Ru3tksh8ezdBd6 zGTV3x_z0L7fi*9MLW85MTeAW*oxCtV4Y)>tbA-H3xJI{zM%tx)w1K~diEu?&ByH@V z&jrIAoCh53eSMqt08*XdpHsa!i2zx{n}&(kz(K)VT)9#i4Zb%9;lf^a<(fR*@=~Pf zgpC*4>AR=*pfw0wtgNmIY3)rly_H%3DW))ttZmZF+IkcC>@?AXA*tM#Az?}dK2u6^ zT$_J!CZ!;U9rkoMrJ19^J23%Ax+w$!`CNGJOn^ZgY<%Gq7}DJCz5jN_6B83;AxjXn z1dk!G9p$eU5}Jl;Pv$xn7K#`GzhajWT%ryt5kjpB2HY`+(bKOx{Nru*&OXN+c$0TH zzOp)kl#l+t2-B!^9#)r@+QBZRQkQoKFVBCY*?vJUnur@#_c!$a%C#YtiBdk=Hf#sV zF-+Au?f!t7IYRb$T%k&%(wn~%P`MWGJw z`OQr)Sj`|!TA%_)@=+4sv(C4rL>Oy=X(Rv@${ zanmN{wip3_8$DFsv_sTiD?sQBGxomxexiE?*jhtFni%OUB}m323WApj++%rr3=@0R zm4Ir2J`_Hbg3cNo1aHZND2a((ASyIZC4YFe=lgfmfGm3*0*U*;tUr2GR$BTZXBbXD z0Kvx0711zo!oM$q;{mc!;JC3Jvdm+Ll&Rx3d{L+XGV=3f3PvHeY6bdn&N~eO0m?XG z+6A4VQ+q!YO|a?Qtj~b}=~i&M%g5*16;1;ajyB=x?++htOb}90^)K%h-O(TvxTEJ- z^rzeH*>;h;g!Em1jNV-nfv+;%*5I;nqrfu*4+8S@4*d48urL_lCU9&8dUuGO09Vb^ zr&!!14|#ZEr7~gb2VXrjZzbB*{iQ#3>zzsUf+6n~TG%>JsiOj6etivqYQ>bD1hQ&I ztZ&K6j8%Ic@6bFBgiN$al>vK$wR3KF0d66llRevQj?P103l%x^(svJ ztPtvlnsZ~ib{ASW(8fRzsc}c}tD$EK;5$tebSd^WizG_!SQ2HzN{5JPz;U2Tg^K#Q z==q-8r{Nnw7Bz6a>gRvHorQvm9-L2D31KnAu~h;4ch<^ZiCtPLoGl?q0o<`EJ~`UnlgjkZ#_FVUYqogN*< zz2Zw?H~b2qP~K7kQDZD}b2B+Q8tAVJVBplUl%TkX&*QJIuv6|Vx?|EFGqec0(qdd~3av0cgZXV4gCca*j%(Zotff^pNZ%-O+d_zh-8)``ZUWf!$CEWZ) z`u&lBp3cKDBArMPezqd-`_;wo^ZuUvS!6!f29B^PyCyS+aG zEf@wK1qlU(m4!o5=$!rN#7FJx^p`N{vKNz|-|U{iolvZ|`BuLDW`Hk3L>R18@Vd@AfCC23Yb^op&3<<1<6D@0S+^9kj2obnyCMEa7 z(J8es&F4V#gMumuB<8#hDLLYL`bKcvvrXS~I=M81Cl;2?+Dv`*5J|3RNT7^{F8$9;F9H)@6 z!q(SpH+YjAkS!tSjX1ReN^q@1E)VPB>@Sik+n_ zVyu4dkVCHI!HeX$zXUsvy}K@1kDHk_sG67C%r>5n9#^PRrlIjWBcHUJ^5V_hv@E{n z*U@5UTpeQWxZyW-jx+q4vP|^sA@beX$?>X4kESS|b(GU@feN(}{h=Y!FdaFaY%8l*yrv9Z~rl~_yy z0`4Hohasl(+pdtD2o63^|LmSe!De7(hSQr0Ixz`}ufP!W+7?gH8JO0a^5G5MpwN|N zV*%;kZV14hzb)Bia0Fwf(;u^<=x{mXsf~NlIdf* zj(z=l**%zbQp?tKL<`Dh*gGNH^98Jd06r>#^#XJ4vQC3{nH=<6Mk3s z6Xit911yauT`xx}Dvse-M?ccmtlFc>xocl>L+V>d=q~}fIg<;zqz7Bi+mVw_9!1w2 zti@5+jXP|~W%M1pjA>~qN}Of7ppnB}J3PCo?5~Bcjh6iCrh9PUd#S68FQW8A6+{kV zQ@u%WD_3*^&U9&f+~wct4DAD418-Ndutybr6y5qbHPqA40Ra>uoTWrcLc#|THUP;b z_Ds_zXJ`DN=?q%euC!2n`n1dU3Y_&M1Y~3~@={fxo`ho)juS@6p%3AmP1^yOZTV{e zHWZJr4}JE|r1J;E`#T&`4j6~p(jsP&&ShUEf~}2mA_LY*eCN(061g|)v8Bijij_my zTc?MHUPFq($#UvHHw~)|iq%-JqhIf!VN=P->*Mx7XPqlnd`Oejx;?Ax<7T*VF+X1> zfsq(!h*j>FH4UhDAUow|T{E4lrP*NgZe!a*gN{TkNuar15r!_6I05tzdZh-fAXn6O z)XxM?11J)>ZrC9NA&&;W`vZO7lJ(M=ZSu6o>`C~aMsiz&ioe^ljojA#bdyhkfP@x{ zgbhzj&@$@SuIQ`t5_RY6GgKbk_BK?Z0D6L)@dW6hsZ%>4&dl9kk!v4E+(bu(__gU+ z6o*N;GH&9>&)-;RZ!I@3IJTP_hTJ2Vz^Cs6rwP2@iD15Ub>)i}0ZBJ>;Z%aog^=qu zlB)vnmm_MF;*LM)NXKX$aknL_kcj>P zBye-{9H#ejY2!(l!?TAn3TZ5mtOxA@Aam>M^?*PvH!Z8tZnRzx+f6ueNNwVj91FeX z>#VA$cX70B{AKd*T0x13_gPItLy|iTq8J0`VtYGOM@!48J~lgA@p7hoykSfrX$<)% zf%S&A z8GtMKtRxs11Gp{H7_2!vZ*p?XU?Ywi7vSfIOj71BH={C$lUf}~J*ieizk21kWp-At zNUhqF(Tc3X7GaPH8O1A(?h+DjE74wE!D*ZI-0fo!B}{*YUcvuw&-|Nu#7O*qbigu< z%hZh2yzxHM=KL!@15?l(f-V}`ZJ31)(Gu&mTU0Ap2|5$=663F5aV3DMa`m6yG7PnE zWMzTOiUVzQEH&74@IP7rMKF=zT^}Zf{G?;VYhGU7b6oDzuL=%&Ak0E&`!BdceF?oh z@VQ-7NXh`1*HLNtsWKChOu_F1Ha@5WYke+7xZScY{O4gKPv42!4g2)ZuoJR=G#d>C zLZGi8z9n6)K=N`51F}ESfp5Bf>lW>;^J?<+ckkd%3g;>k0$N!J5Pos-EWDtWC5>aq zMVQTU1|hi$NqmZ`s#&8J)WFV9n5D)~szC<=)=xvjbcLMn0|WZ?&PB&R|LMXEEf0GG zTbu|H+K&H4^`9rQf|U*>RbW5>z(jzxJ$*Sb9PP$!e|HH1{rwXeE5E9r`;c;EG*p~lTmI^Q8>-Zpk`WiM7R)9pr z)9CPUu^DEbQ7*%LiZ)Bu<4A_Kk2rawYcQAr66J4wfHZ+z1&)6$O(q7A+L33k&As8t4E3Uryn+1X$zKFCmrL*;zoTpz}(F-T^Q^ zl(7e1#DqyT6_t^2n#U-@3^e%Qn&T22^z4adf+p^SP}S)LXfIlbHUW}iV8Ec^0wm#l z>1yUKl0=@1bGsiNC1l%tf{YRXDuYQmA;e4>_6yJeKwo^mk{On31&xp_5EQ1SODOUk z%yZPUKu3=k>&kLBK=!vYbd5q(_%z6kjRusAc{n?Q*Ka>QL9pv0I^aHb2FOR`cj*|To_C9}Exz8){`0q{!m-4NB=k6-Jc-P z5KwDm7sP3|F4_Zh*zIicA8AaN6*4i10GdKb!~!5r(jEIQ4h0|k!-qDo3ZQ(aG25ND z;~Oc5?mTOF75S-PDBw2m#^8hi_wXW3{3M-$RHv>FTEzp-bz8?LB!R+SCnEy`VQ0f5 z?KJoAuK?DLblQMx27H?)Oe`ws)_zW&eXyRf{GALoDEk1qRzW}$hYaBQ*WQNIxrSt; z-Kos}(Wf)8ALcj2SCm0l+-xY5qxq{|m=yjm;6w^oR(K5s`czAT+Sm)>)Io+=52``Y z1kg<4XVgS`n&^m#GAOg)Ij#>x8FNLn{es+zvandf@E9<1hlYfV`lw|NK_O-W{SUmxS+{VP@qVm2#=bML2=i4$ z4@Jh1MTD)7zdMxhLUU|7!3bSYaiN1n#*_E}3_R;3iG4i!bL^_2Lzac~V4l13f*2LE zt*vbdThKjrj_urpQoO6RV2p5#jMLxwR)AMwPvgjf zBTPuh6A)|AlP590yZavEv*An#O5$oW&j5d|mKK@nQx=$KA62~w3lQ+r1R+6SEn!4V ziALFP00|)8+Ms{0IBWw3PH0hP!6=znw?vClOW+z*7;zF{TZKhM-tD~!EUu>NuM zi;oV3W5+BNI{aw&B(}@Kt10F@qAsw z!%;Wx^74MF3wL*S2Ov3olVqg#l_@;ga~wt`%Ug`DQZFXV2~pv8Q;;bz1VNAITQR=A zsB)P?&>1H_jenj9j1BQa;QnOC?^+*?L$~!)Rt_J|nfQuer+mXPv5fJjrtW2vLgh?{=`i zH3zhi;=@nGw{O3&WQ}9$T;YbogkDf^%F_VOH;Az^gzXaoN}$lqyMCPklIQ@ENBNk) ze1W|Iy3+(a!n`-3cw?t%Ema$nmEY~&LZ-?RM5WjOih~s5ho&DSXd^=WQiR}YfRI&y z*uW41^XT9_(jx25Rm=n>PKB1WqvIY(^ey6A`}>QbID?IjT~z`|dT+lAKvqzIGmzr= z_+bEluaqoWeK4EL49!yX`~Hy|9+B<`8Po%_G@;KAwBH;_JuN+wXPY6xl|Qeag_OzpZEtHjNm}?5fT9Pb(cL{1O{EdS==>r036fC5 z1tyL{#vTcL`??0BJ-{cu@9{kTd9{F!^vIq3tZOo_Qt}0~Y5LShKjoi#-6vxoi0T=R z@38e%EB&wya-vYY&TXI!U~6&&#BD4zi%#)r#eP=82?wm|>~uM0{p{En3%x0d`CDHf z^$MNoU*O>Nb$6!pzxBR}aO4RB1y+?+yjGDO7#V;|EMMiJT`okPOwu+)lo}hI*+7a` z{rD4NLzk-3oQNP2#BG$`OADr?^v}&*f_HFG@~8bP>g79gg^#g@RzM82y3+2r_vO&D zJ7Ur5Okm@Jwz_@l?u!e_SGwtSbC?-RoM?zb5MEqnebNI#0yr*V8?)tUoH4&s!=*ow z%OG?jNFu`GK4ge=_wgo3TsC`NZt49^2ysu_N*f)4n0jY<9rTjog|F_9bE%jkjpQE( zRHaJ@8#LvhtXgNkn~^o4c6&zlGbAKMo6R}&VKKayx+=$FR6=TeR+ZFW2_)XP8q9|r zVK*3hCxuggSDN(9OhZc_UJWd;d;1X2_TE}b{%1GlZo(`seWpIu(NB5TDL$MC1kX@U zuNnD3-+6i3d}-OnC;pC*lJb1UWlqrtHb?}bx77(f#OAZRow2dvK{q_@kT%3PXJz%E}`RmCL^=A6% zDec;7TxLHgo$SHy5_ut)h?19r##l+kGRuv*RztaDT#u$?(7$ir^>tvq;RX0dhX> ztX2a|B4D=JDX1PtAOEC&M6xtK!uwm`tKBPXa&_0rep`}(kGQM|MB+{x(n`=H;qLoB ze4_~Er>#9CJ!G4k)>+T{M;$#vE_dux@m!L(&`}Dy71xT79f3FgkwlJsL3f->7itr_ z);xAmRvx|HGygFTY%ifKZLNNw(&KY~w3$5r9Y%{r5b)8k42_&|=GQYk=eJN68-JBx zE_`({DdH?er)U00p!eE3=eO?3D^7}kv#=2>B>7*EU_f3}C36c_ zqi=B}Q8&N#@VRxX?{P|ON%W0L3UGEH#>D6Sl`^8#UxAZBG7CI{AaV!i3FggVb00>5 z@1YAFcI$16H5@g6a4>I1fm3k1ZbhSLxk^x zw#p~E5n5Vl(Mx&x<#&E~s(Qt{8w8IBafZkRV2Uw7Of(&&a;AF>rm{q*Bx z9~)+&5THdH+%};ssEg5%n}2Wf+~H5hE%YzPZ&<})AH~GPaQ5rJ0SaYbQ`q%%3KgGT z{MrAk{L9cm2&bIMvO=`K_qaa(Ye1`$ak$gqcJ)dnHykgFBy({u>>Lg;gp10#03A%AUBJ+%#!g!Op5v3ZMav;Li z7DA$p4%g}XT1(P+t)dgx2<&80Ok5ZpMC`622ev*FGj(N7l%RENsI0uV@i^>)nlx|W z3ZhUU=Ue;XqR(^km;$(c6(2#xT|IuBc|mEgxl-q!&k9ViWMpU0n=(DGYzY^ge|||F z&ec15C14hT<)Rv*pjf0TP)sZq7?QU(n_Yph=0@|-F*62JK1(y*#qT%gZ9lu?SvHd1WyB-U^7+w3bao@wU zPHSY~*Wyc&GqJe4Kc%4g_r(iqxt^txlRwnWQpudBKr?jUHV`7k`$NuWKE?yfkU||p zTWlGN)QVUjabUiNqptY@djhqF;=$o4?}aXU3@)PZY0kH-Ys{~jzVPU0zq9FZ8CvNlRtD*)5e9oBA)%H+i!)prtpG5+yW z;^gSDux7uMO%b<+oghPSZaqrRX!7bQg>Gq88TQQayUMyp)GLK5GB8y6J~T_;v~<#5Z^hHs;Y!7Ao8V zZk1A+naVPbA1y*;@;weu^+NTe0(C_Bls=fhr!y$kC9%4ayztf6iA10-Qu|BLckG%! zy}VV<4m)pNA0UL-YGTud9%b&oD|fKxdx#@WFvaiNj_?0wrs^sD)bZ6MG>tJlbPe=1 z^0Zxo%h8tImlFn`CZuqS3x}BUD|*PlL#Fz)LO4^0Pl7IFOH{5~P309}ft>;UL>xb_ z3UtP_E^(d(ZpVebh*&wJ^452@q`H(?h`}n@NCkv+WRd5r=j&-22{{{wZ_Haf2b^bw-b5==4!<$o8~;@zyDMum z8s!iZ4#pb_;*J9H^8jJKO%nPQ1bCf*dZJ_>D4%1!xiD0uawKyD#y1WIh4|sD-ZUO^KcAWL8YBm_87Fis-`sKOWoG57j zhH3l_x#VbuM8==m4~(k_tArbWiQyIi|6Bg(O}Wf@bJXGHC2b=iUTeIS2kYj>4+iH48`ow@47jaN-d2|1BVc^23zA!HPW z=10wE6v6?4$G?=OZ|1$M6K!T4xM-R@zgB$t)XaQ;b1-<%5j61DmwbrQ7m$pC`K};E zuef~$Yn;bANLlT&+Uv3M+|!2-(<9f=#jP?Q+uG8{BK2~`kuuIEi{f4I?czRQ9MEiw zSXVJ3&fu&(9xF~e)BkLtnb}CV_XhzIeC7V#N?rj;J?(A*EvGH9Yv8EEH*wo@SmADM zuO)|K$>Qio1DeBDycMQ(#9^(X?fkYzde^WNbJ>;v$6 z_>L90Wt<7;eclT@0eG0Ly~_T6GDzo%a>4Red5!rKfkMf3sXuR;*xQP>F#oVawx^1F z{l(Z=WSxX+$@Sv%Ww(~kLCI*~_{1FiOT4hWzqqDF(FP@}IT!!8T*VL%d}kAE5)}mVrapD>Xxx-AnF`RRraRZVDRH3icRkCYM>a}NOOt@T$Ia26H?zFO5ATLZSsL14 zH~UrT_hKZtkGY=AXRni!WnAh!7i6etcpHiw5PqqLMtLs9m3;FGVk$>z)`VCv~7#-dxFVsdpQ_-5vA?$nnZy1W?fr1 z*c1~ZU(ZN2!?9d^(o*D|t{V5T%6RjdMAz}1zVKRcs|O`}&R*SH`!B0st_4bwq0u>Z z*-5KZ3KSHZ6l6Eg-3v%Wt>-F8eiW)GF0zC zICBH+js$#>LTb@7d2HvpYThO3it>XbB>{=IM6Z1Fg2*Tu$0v_>G;{vO<-N6B;6Hz06Rxqs53NI-gnpA#s5eUDj#1#2vqYF z*lK7vm`4^3tMdTH4&vZ4fDMn1TrE3|m~h&p!l=9e8^&qdBeY)b`)b}r@4o;+Q-*bd z#q`Y7UElwV5MMGBxaZy_yB^X|c1$Y(6Sp>aZb{`xq$P1iw(uTvKaY;VM=mts@$>?H z$rRO{loVXJe$`hMMnC%#&@4?)YALzthZ@EW0YU#>stx5xq++qdGibxTJBU8}R7fF) zEBcj|Fbqzxt26h>KX~&Wj2);Nkz*twSy5Z>Fe3pL@cMTM35_?!5~Pg{GQ9EN57ah} zG7Svst7JD02Lu9n@;V#`>t4(b{IFcV0sC}KD3-VM%03Uh9nEZvLN_DR8d+4b*S>4b*+0S`ZOH~X^bHIP8SxM?}e zGKc*6cLcPy{>CIyGdDr{7Z3A{bAw0vTt;;{oUvC`ktKO@Ede0#T54!OG0Mv$t1RP+)m$_rLoSI2{o3^a+#l|4$4* z_~3ux_s5q9e}vLLu=pU35~qJ*`Lj&l?V``PCb`L^Hm(b3vnrC1kXhNXlhCqKw(MO-R(Ov4 z_xnHNKVCf7i$0%<`@Y6`o#*%b9_xqHenPgtrLrn@I*Z1a2bWo3`Er`+#5g(H=AxeirfTyOZk_c8iawnckhM@|=5GWMl^L9<2M zU14jZ=}>0<5e9k6PiRlWU1jIJ=kU)weApbFMc%9yAOqeA)0v*0M$pNvx93nX%LaUcT^L4j=(2y`<}HVpiVfejQ13|r7PVOg&*=KypK{x(3Ha0m+8pn1AT z^r&bC<<{EzDJ-dg{n8!)U%sc{)-I+U^5#(TfRoS3`QEY_MEQrT`*q74CxW@W%?Anb z{34yIfnU*yf%^PLJ%?sye^NAatkb z4P!ixJH8h$6>%3m$tK(umn9~CwICk*7|qn<$=DFhCelZvKHRGsUOph zE4}~yMCE<9Vo`SHR4!X(QYd)PuHz}+wR2}wZ0u!2Ln~eL0)7P~4?wPmJ53LsEEGdF z$(n&0W@ZOBgy3s~WcRMzygYhp>Nh>sNU_lscu@py>xA=x{rhQ}(f67dg}F_J6&m?t z3t`(Z3lL!(8+WOs`9tLgJor4dnzadh>CJhc)bJ4nEvkF z8O{ixh|VDOMmx1zI02U?LQ`k(*enXWGUOpK(a`t`98o#yot-C8dAQcdC;x&E^xX9H zbAF%27A1f+0F|NV3fj{6m)W*oMHueUzz7UUg*?A6E!6ZpwYvXq*gBIoQLWf}V-3V) zn2i#prF?JkSiv|3Sr^#|0Goh13Cz8#Juc#+JDrlMw(wh_cypzovO6B6HynskA!rLP zKYdpin0>}=EOc5iZt-LPoad@(-zH<#mdD4VQRC|@>*05jq?hH7J*e@izAC1=x+U6k2yAe5Rn?_Gf5PwIUjtaY)z#9wMGi?< zHxWJ9O_h0-94%>6(KYUfhE(tP1lrx){ww`Tdy>A0{~f6@77{47Eb)mwXZG4K_FVnY zZ-E9C8*n}M^YSX@b%G6t5GNlIAaq4v|Eb&PV?@BU#6rQq&&MYzC57(n3OE^Uw5m@}t>Wr< zRpz7BWuQZ{?<9nBIv*7;y?N!p@y?#OqgQ~df}V>{!E!9eM8_s30P`Jixkl!7Fh8P; z`TFfH>V~g9Ca*$HG|zcTr=HH6*bIJTI3LEj^~|pCs(iKkx2i*hR23j+ki zCAt2*cQ%AMHYNr@DJ*5spLPcqsG@5>!Ry(fRq{Lg?&8IV0{Iesrd~n;F{ngl9R?#4 zOYV;^C~ifwTuyC9R7n{}zmO-SVV5*DTMFY#ooO4yGa6aii|+iUjm~LfBa;JSmTgoa zdFqrwY75I9Ia+fTMO|R#^e$gLgiiVLQh#!4Tk{NRO<+M8)L)UN*upqHTp{oihge1nfV)rhwtMu4VJFUjV+h`w2F zW$9=cM_<8>(DrYy&Xpft%blqH&_A^`p8sOrw@G}hpFVT5^FZr^_dOTF*{-nIftWk@ zOfEozUCq<;FNB4}RPBt6!xkUkz-H{_4f_kR?yI06!I~K_1|GzX6~kNh)9tmoQ`nWk z!8}vzg);iC`v@v$1I3bBe8is*7Ssg$R5xtVKB_8^+WdxXps)`CSB~P5HanpI`;^Rz24KUDvaeym` z=^hl)xNbx6x6%6khf$5nVw)lSfK`%qdBIlz>d2d~PexbOWi)=nBsk4F1Fjf^E4x!` zL5lanY%@yttWn;fw?L-s*`* zF79&=IVOJ6*}^272>o8nY_~6fC?_5?2XKE8HxTA-CETC?!qO{nUzX1Uai-d{m#PDw z4znS0B>djJ!QZYRR)YDYH3;q8DQW46Ek6jn;I{{tX@p*6XA6||WUy8wcUbhh4u!!n z7Qrzd%htR6`ZhGfhg`w80qXs*Syfs2nV{(L&fI^XiB}qj7vb6T>Nyrpcw&`bjn#H_Za&2cr(2 zFZ)a-^n*IJ0-kE`cBwnAKP(jHagcufF>`_Te&e7(S@<1?1@rzd)_&B(R*|aO4CnuXgasypbRaIEmn*Jeo z%cVy{!)z7Dhgu?A9=$Am9Lc+UeelhP_*)rRDuH`&$#_acG~nU7&J=cz57I@i2IUw| z(UydlIOsdyHX6iqKIUyYbA(Cg+2UV!J|Sr9fYknwy6-WvyTW!7!{9@#CP(wo?TeWr zyu3G1i)baQrW(ZscOiFZ5MV#d6+n|v2V=A^pwvz|HMl6e>W9eNTT?8VIoD&IU7XL} zS`vJb7ynFmwVO`Qph5 z?l#Jj7=B-p-0PsQ%c5`%^ubFnF8qeiWxg?qbo#d*O=0&LR6?0Xs8V`gd|RJ7NB+(B ziD#<#jZ^w8mkMtYU*s&&HjP_ka>U#ngOXpF=}(0%YQLTEs!+<~KBv}HsdiLCXny(4 z-n6joLM0>@0z6X>*|KkSR&B;cM*0qwR9v4pd1&&^|9(pxr9Q)=8d0gmx7v7H-)jms zD_DVD0AhWzx8V#5um9-|{7IW=pX2)N(J`_fZKLb(&-H`Oq&lh3IN3$Nw(?za>#6%7 z<^UA>;}J1?;I;sn(kyF!qmc$1Z(j6q_eLxsHXdGfd7pac$Gy0l@~qMY_SqdxKSFbIa=;Z-^sv3_8L-b` zxx-s#_Dcj;nUk4^Q+d1Vettp_k}!4zYQV1_1o#_sr)Tf&TB_ZQ`C5AKlAwqEFj1O6rmLYa?gAjNprj5s}Lturi@g&{0$Ys;Y_)a8Sr zgT(vXE5+f66=n4Sv)~`5m;&W-79TGKIQ8#lV%pm=Y+nkx8e+G#?CglPknjRGyTlKg z=}QcvoE%ZlWYL@isVLrnGt!b#EJ1qxdkF68y5r%K7$#23 z$&3#22xgkcQ*3J)gzMG5Y$ZCp&Q+a*A{`r+_givnmlG0mt->J31ReAq? z$WGAJ(>n>bA&-XEFwhNh_1-9^wm^Ht2Ma>PNe?h={(YOgQa(DK*{3kNqQB)we!6(J zNgN|ADJg02E(IaH`pXyfeGK2Fs0bhHB31i8&B|>oggj|94GInh{V?X~-+_FXYhsIA zs6T`IUVQtq+s;Ztajx>ZvSghkY`L-7>)cJ0#j`Y=q{M9Yh z9Ztftk{$sF>ZpFCZR3y0%wYPwPp{tfIo&5T*+pc&0sD4ntzS{X9lWac zv9n-mhJ%jfw7Z#{A}mG3yDUY19#E}sNcfQ(x*}4wB68J3X*fGWN{e{fefXex+oRhOLo{o2Z@ak9S9_to+PH1A|lWw6d|WIrb1%2cFa4kEc>etBDDOMt)Uq z$(n*YIV4OYU_DA(PWA0QOI;^bUCHMi<&&VjIP*4Tej!Iw-R;XsF`lJ`@7!;^^gLI# zQ~vMsdHaPm=2KZraqSp;NJ!#f-LS+@icxT5AWy`O9t7-}?qYZYRe>#2Nk4?IfE5S5 z^x{`gXW%a>yD|^}YU`pMO}pW3ERs++nGVi!k@k?%pUMYe5LQVZ6x-G)RU*9=4AVJ7 z)nreKxamC`dp5ggntWjMO?Toa5<>N>39S;2D}slK$9NHI(icP;@g~*&_g!ieqde+u zIyyQcHUOaa1^1fbhw=gO9I| zc)+7ARO07^Bqm`p0V5kPd2K@rsltrm%4xoX2S;GCTnVF-b7{ivMD8N&I8b+@pqEUv z2EBXn5+W}b(8p7WdIDp4P>d$#!AwM_1sh@Kb$$PRG~H#kgZ@k6D{t}_V08iIfrz-c zIwD!HR*hHbm}vIvew%)qmSzT*A53h_tX)M`S1`T7kKEdNmZ-vK{{=S((01^!aVt>p z-tZ!lvYA5aA$-B%=H`Z?EVT)5+eKtgLpZ!Js#-Vo^J8rH+D&=NFfQ3m*j7AL<{>hM z2LkA!5`$XJ8I*o&`fawlflsqbTfH}=Gb>0*s!vIcz=7M?=OuG*j{2n9F+T1!XP(q) z&t|^|Rl6iilEY)iqPp=K`g6|mz9-T#6-1SR8cN|@BZ;J z2mRBhPoF(|=S~o0aP`-Y7bYep^^3~$lhWC71xav0&LJcOb{ zv%CH0o497HjT25zaAB}`-W=Z~R5_>t#z>!eRYBV1f}IblhV^?lLprS7Jt(&O-pbdf zBp6pGGv5(dmD7IE+cYeklI|X1u=9iSMEUQ&u6{{1!OAxSwL(wRfBYCL>YMWAX#~d% zYp0mFc#@k=z-qf5iFuHZ&=HEA53?lI`h z?l}=;0{$x6939vZNJ&b56h*C$JX$9}t>ON%0Q_2$F|Kp7_TphZP1)@REF z-~RGnY9S{lZzYL|iz7$O_6X#G?P7*C@})UGyn$cQ0cSpD0A(R&BkbfDs9%+N~fF3~{ElSD6+?14`L@mwm1Eq{~ROzq5vrI`Ly53;bjElQr zPrvJs+H>s~G8nhD7inDm0YFeeyhP@lfx;)*1(~q|@y;EzMEd$-U@LB5@9!P!zMjeνxX)lb!_VoX=$WI-JiDpTyrJn#hrpEU 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 literal 50296 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 + + diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_client_a5ff438a63f0eaf323c7cf6adc8532cba_cgraph.md5 b/class_tgstation_1_1_server_1_1_client_1_1_api_client_a5ff438a63f0eaf323c7cf6adc8532cba_cgraph.md5 new file mode 100644 index 0000000000..13482c7824 --- /dev/null +++ b/class_tgstation_1_1_server_1_1_client_1_1_api_client_a5ff438a63f0eaf323c7cf6adc8532cba_cgraph.md5 @@ -0,0 +1 @@ +4ea3815354e48c37049a988ee4eb1075 \ No newline at end of file diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_client_a5ff438a63f0eaf323c7cf6adc8532cba_cgraph.png b/class_tgstation_1_1_server_1_1_client_1_1_api_client_a5ff438a63f0eaf323c7cf6adc8532cba_cgraph.png new file mode 100644 index 0000000000000000000000000000000000000000..30931af5041a432d0d73480f9b79b18dba6a3faf GIT binary patch literal 6480 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 literal 0 HcmV?d00001 diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_client_factory-members.html b/class_tgstation_1_1_server_1_1_client_1_1_api_client_factory-members.html index ade49e6822..4631f1bec5 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_api_client_factory-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_api_client_factory-members.html @@ -100,7 +100,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.ApiClientFactory, including all inherited members.

- +
CreateApiClient(Uri url, ApiHeaders apiHeaders)Tgstation.Server.Client.ApiClientFactory
CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders)Tgstation.Server.Client.ApiClientFactory
- - - + + +

Public Member Functions

IApiClient CreateApiClient (Uri url, ApiHeaders apiHeaders)
 Create an IApiClient More...
 
IApiClient CreateApiClient (Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders)
 Create an IApiClient More...
 

Detailed Description

Definition at line 7 of file ApiClientFactory.cs.

Member Function Documentation

- +
@@ -144,7 +144,13 @@ Public Member Functions - + + + + + + + @@ -156,7 +162,7 @@ Public Member Functions

Create an IApiClient

-

Implements Tgstation.Server.Client.IApiClientFactory.

+

Implements Tgstation.Server.Client.IApiClientFactory.

diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception-members.html b/class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception-members.html index 0cc4c9491a..afeedf5975 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception-members.html @@ -101,11 +101,11 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.ApiConflictException, including all inherited members.

ApiHeaders apiHeaders apiHeaders,
ApiHeaderstokenRefreshHeaders 
- + - + diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html b/class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html index e13b75be8a..ec0b5f2c54 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html @@ -124,9 +124,9 @@ Collaboration diagram for Tgstation.Server.Client.ApiConflictException:
AdditionalServerDataTgstation.Server.Client.ApiException
ApiConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiConflictException
ApiConflictException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiConflictException
ApiConflictException()Tgstation.Server.Client.ApiConflictException
ApiConflictException(string message)Tgstation.Server.Client.ApiConflictException
ApiConflictException(string message, Exception innerException)Tgstation.Server.Client.ApiConflictException
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(HttpResponseMessage responseMessage, string message)Tgstation.Server.Client.ApiExceptionprotected
ApiException()Tgstation.Server.Client.ApiExceptionprotected
ApiException(string message)Tgstation.Server.Client.ApiExceptionprotected
- - - + + + @@ -140,9 +140,9 @@ Public Member Functions - - - + + + @@ -188,14 +188,14 @@ Additional Inherited Members

Definition at line 10 of file ApiConflictException.cs.

Constructor & Destructor Documentation

- +

Public Member Functions

 ApiConflictException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Construct an ApiConflictException using an errorMessage More...
 
 ApiConflictException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Construct an ApiConflictException using an errorMessage More...
 
 ApiConflictException ()
 Construct an ApiConflictException More...
 

Additional Inherited Members

- Protected Member Functions inherited from Tgstation.Server.Client.ApiException
 ApiException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (HttpResponseMessage responseMessage, string message)
 Initialize a new instance of the ApiException . More...
 
- + @@ -222,7 +222,7 @@ Additional Inherited Members

Definition at line 17 of file ApiConflictException.cs.

-
17  : base(errorMessage, responseMessage)
18  { }
+
17  : base(errorMessage, responseMessage)
18  { }
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_exception-members.html b/class_tgstation_1_1_server_1_1_client_1_1_api_exception-members.html index 749ea34ec6..066a2e95a2 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_api_exception-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_api_exception-members.html @@ -101,7 +101,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.ApiException, including all inherited members.

Tgstation.Server.Client.ApiConflictException.ApiConflictException (ErrorMessage ErrorMessage errorMessage,
- + diff --git a/class_tgstation_1_1_server_1_1_client_1_1_api_exception.html b/class_tgstation_1_1_server_1_1_client_1_1_api_exception.html index 7f826bd46c..16d7922a9c 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_api_exception.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_api_exception.html @@ -131,9 +131,9 @@ Collaboration diagram for Tgstation.Server.Client.ApiException:
AdditionalServerDataTgstation.Server.Client.ApiException
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(HttpResponseMessage responseMessage, string message)Tgstation.Server.Client.ApiExceptionprotected
ApiException()Tgstation.Server.Client.ApiExceptionprotected
ApiException(string message)Tgstation.Server.Client.ApiExceptionprotected
- - - + + + @@ -181,7 +181,7 @@ Properties

Definition at line 10 of file ApiException.cs.

Constructor & Destructor Documentation

- +

Protected Member Functions

 ApiException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (HttpResponseMessage responseMessage, string message)
 Initialize a new instance of the ApiException . More...
 
@@ -191,7 +191,7 @@ Properties - + @@ -225,11 +225,8 @@ Properties

Definition at line 32 of file ApiException.cs.

References Tgstation.Server.Api.Models.ErrorMessage.AdditionalData, Tgstation.Server.Api.Models.ErrorMessage.ErrorCode, and Tgstation.Server.Api.Models.ErrorMessage.ServerApiVersion.

-
32  : base(
33  responseMessage,
34  errorMessage?.Message ?? $"HTTP {responseMessage.StatusCode}. Unknown API error, ErrorMessage payload not present!")
35  {
36  ServerApiVersion = errorMessage?.ServerApiVersion;
37  AdditionalServerData = errorMessage?.AdditionalData;
38  ErrorCode = errorMessage?.ErrorCode;
39  }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
-
string AdditionalData
Additional data associated with the error message.
Definition: ErrorMessage.cs:26
+
32  : base(
33  responseMessage,
34  errorMessage?.Message ?? $"HTTP {responseMessage.StatusCode}. Unknown API error, ErrorMessage payload not present!")
35  {
36  ServerApiVersion = errorMessage?.ServerApiVersion;
37  AdditionalServerData = errorMessage?.AdditionalData;
38  ErrorCode = errorMessage?.ErrorCode;
39  }
ErrorCode
Types of ErrorMessages that the API may return.
Definition: ErrorCode.cs:10
Version ServerApiVersion
The Version of the server&#39;s API.
Definition: ApiException.cs:15
-
Version ServerApiVersion
The version of the API the server is using
Definition: ErrorMessage.cs:15
-
ErrorCode ErrorCode
The ErrorCode of the ErrorMessage.
Definition: ErrorMessage.cs:32
string AdditionalServerData
Additional error data from the server.
Definition: ApiException.cs:20
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html b/class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html index eeb2af325d..2322da2abc 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html @@ -437,7 +437,7 @@ Here is the call graph for this function:

Definition at line 58 of file ConfigurationClient.cs.

References Tgstation.Server.Api.Routes.Configuration, Tgstation.Server.Api.Routes.ConfigurationFile, and Tgstation.Server.Api.Models.ConfigurationFile.Path.

-
59  {
60  if (file == null)
61  throw new ArgumentNullException(nameof(file));
62  return apiClient.Read<ConfigurationFile>(Routes.ConfigurationFile + SanitizeGetPath(file.Path), instance.Id, cancellationToken);
63  }
readonly IApiClient apiClient
The IApiClient for the ConfigurationClient
+
59  {
60  if (file == null)
61  throw new ArgumentNullException(nameof(file));
62  return apiClient.Read<ConfigurationFile>(
63  Routes.ConfigurationFile + SanitizeGetPath(file.Path ?? throw new ArgumentException("file.Path should not be null!", nameof(file))),
64  instance.Id,
65  cancellationToken);
66  }
readonly IApiClient apiClient
The IApiClient for the ConfigurationClient
string Path
The path to the ConfigurationFile file
Routes to a server actions
Definition: Routes.cs:9
readonly Instance instance
The Instance for the ConfigurationClient
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_conflict_exception-members.html b/class_tgstation_1_1_server_1_1_client_1_1_conflict_exception-members.html index d92e9cddf2..93625306cc 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_conflict_exception-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_conflict_exception-members.html @@ -101,7 +101,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.ConflictException, including all inherited members.

Tgstation.Server.Client.ApiException.ApiException (ErrorMessage ErrorMessage errorMessage,
- + @@ -110,7 +110,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); - + diff --git a/class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html b/class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html index 777f121350..d2c2169dd4 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html @@ -124,9 +124,9 @@ Collaboration diagram for Tgstation.Server.Client.ConflictException:
AdditionalServerDataTgstation.Server.Client.ApiException
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(HttpResponseMessage responseMessage, string message)Tgstation.Server.Client.ApiExceptionprotected
ApiException()Tgstation.Server.Client.ApiExceptionprotected
ApiException(string message)Tgstation.Server.Client.ApiExceptionprotected
ClientException()Tgstation.Server.Client.ClientExceptionprotected
ClientException(string message)Tgstation.Server.Client.ClientExceptionprotected
ClientException(string message, Exception innerException)Tgstation.Server.Client.ClientExceptionprotected
ConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ConflictException
ConflictException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ConflictException
ConflictException()Tgstation.Server.Client.ConflictException
ConflictException(string message)Tgstation.Server.Client.ConflictException
ConflictException(string message, Exception innerException)Tgstation.Server.Client.ConflictException
- - - + + + @@ -140,9 +140,9 @@ Public Member Functions - - - + + + @@ -188,14 +188,14 @@ Additional Inherited Members

Definition at line 10 of file ConflictException.cs.

Constructor & Destructor Documentation

- +

Public Member Functions

 ConflictException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ConflictException . More...
 
 ConflictException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ConflictException . More...
 
 ConflictException ()
 Construct a ConflictException More...
 

Additional Inherited Members

- Protected Member Functions inherited from Tgstation.Server.Client.ApiException
 ApiException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (HttpResponseMessage responseMessage, string message)
 Initialize a new instance of the ApiException . More...
 
- + @@ -222,7 +222,7 @@ Additional Inherited Members

Definition at line 17 of file ConflictException.cs.

-
17  : base(errorMessage, responseMessage)
18  { }
+
17  : base(errorMessage, responseMessage)
18  { }
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_insufficient_permissions_exception-members.html b/class_tgstation_1_1_server_1_1_client_1_1_insufficient_permissions_exception-members.html index 2cc161802c..caaeea73b9 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_insufficient_permissions_exception-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_insufficient_permissions_exception-members.html @@ -101,7 +101,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.InsufficientPermissionsException, including all inherited members.

Tgstation.Server.Client.ConflictException.ConflictException (ErrorMessage ErrorMessage errorMessage,
- + diff --git a/class_tgstation_1_1_server_1_1_client_1_1_insufficient_permissions_exception.html b/class_tgstation_1_1_server_1_1_client_1_1_insufficient_permissions_exception.html index 3b023c8136..a3f9f383d8 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_insufficient_permissions_exception.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_insufficient_permissions_exception.html @@ -137,9 +137,9 @@ Public Member Functions - - - + + + diff --git a/class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception-members.html b/class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception-members.html index 24cace1fb6..631fc052cd 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception-members.html @@ -101,7 +101,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.MethodNotSupportedException, including all inherited members.

AdditionalServerDataTgstation.Server.Client.ApiException
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(HttpResponseMessage responseMessage, string message)Tgstation.Server.Client.ApiExceptionprotected
ApiException()Tgstation.Server.Client.ApiExceptionprotected
ApiException(string message)Tgstation.Server.Client.ApiExceptionprotected

Additional Inherited Members

- Protected Member Functions inherited from Tgstation.Server.Client.ApiException
 ApiException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (HttpResponseMessage responseMessage, string message)
 Initialize a new instance of the ApiException . More...
 
- + @@ -111,7 +111,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); - + diff --git a/class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html b/class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html index 6ffd001630..d40ef85262 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html @@ -124,9 +124,9 @@ Collaboration diagram for Tgstation.Server.Client.MethodNotSupportedException: - - - + + + @@ -137,9 +137,9 @@ Public Member Functions - - - + + + @@ -185,14 +185,14 @@ Additional Inherited Members

Definition at line 10 of file MethodNotSupportedException.cs.

Constructor & Destructor Documentation

- +
AdditionalServerDataTgstation.Server.Client.ApiException
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(HttpResponseMessage responseMessage, string message)Tgstation.Server.Client.ApiExceptionprotected
ApiException()Tgstation.Server.Client.ApiExceptionprotected
ApiException(string message)Tgstation.Server.Client.ApiExceptionprotected
ClientException(string message)Tgstation.Server.Client.ClientExceptionprotected
ClientException(string message, Exception innerException)Tgstation.Server.Client.ClientExceptionprotected
ErrorCodeTgstation.Server.Client.ApiException
MethodNotSupportedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.MethodNotSupportedException
MethodNotSupportedException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.MethodNotSupportedException
MethodNotSupportedException(string message)Tgstation.Server.Client.MethodNotSupportedException
MethodNotSupportedException(string message, Exception innerException)Tgstation.Server.Client.MethodNotSupportedException
ResponseMessageTgstation.Server.Client.ClientException

Public Member Functions

 MethodNotSupportedException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the MethodNotSupportedException . More...
 
 MethodNotSupportedException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the MethodNotSupportedException . More...
 
 MethodNotSupportedException (string message)
 Construct an MethodNotSupportedException with a message More...
 

Additional Inherited Members

- Protected Member Functions inherited from Tgstation.Server.Client.ApiException
 ApiException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (HttpResponseMessage responseMessage, string message)
 Initialize a new instance of the ApiException . More...
 
- + @@ -219,7 +219,7 @@ Additional Inherited Members

Definition at line 17 of file MethodNotSupportedException.cs.

-
17  : base(errorMessage, responseMessage)
18  { }
+
17  : base(errorMessage, responseMessage)
18  { }
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception-members.html b/class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception-members.html index 4943c35efb..187ecffc5d 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception-members.html @@ -101,7 +101,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.RateLimitException, including all inherited members.

Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException (ErrorMessage ErrorMessage errorMessage,
- + @@ -111,7 +111,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); - + diff --git a/class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html b/class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html index 5d3735a430..806a1a7436 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html @@ -125,9 +125,9 @@ Collaboration diagram for Tgstation.Server.Client.RateLimitException:
AdditionalServerDataTgstation.Server.Client.ApiException
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(HttpResponseMessage responseMessage, string message)Tgstation.Server.Client.ApiExceptionprotected
ApiException()Tgstation.Server.Client.ApiExceptionprotected
ApiException(string message)Tgstation.Server.Client.ApiExceptionprotected
ClientException(string message)Tgstation.Server.Client.ClientExceptionprotected
ClientException(string message, Exception innerException)Tgstation.Server.Client.ClientExceptionprotected
ErrorCodeTgstation.Server.Client.ApiException
RateLimitException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.RateLimitException
RateLimitException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.RateLimitException
RateLimitException()Tgstation.Server.Client.RateLimitException
RateLimitException(string message, Exception innerException)Tgstation.Server.Client.RateLimitException
ResponseMessageTgstation.Server.Client.ClientException
- - - + + + @@ -158,9 +158,9 @@ Properties - - - + + + @@ -192,14 +192,14 @@ Additional Inherited Members

Definition at line 12 of file RateLimitException.cs.

Constructor & Destructor Documentation

- +

Public Member Functions

 RateLimitException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the MethodNotSupportedException . More...
 
 RateLimitException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the MethodNotSupportedException . More...
 
 RateLimitException ()
 Construct an RateLimitException More...
 

Additional Inherited Members

- Protected Member Functions inherited from Tgstation.Server.Client.ApiException
 ApiException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (HttpResponseMessage responseMessage, string message)
 Initialize a new instance of the ApiException . More...
 
- + @@ -226,7 +226,7 @@ Additional Inherited Members

Definition at line 24 of file RateLimitException.cs.

-
24  : base(errorMessage, responseMessage)
25  {
26  if (!responseMessage.Headers.TryGetValues(HeaderNames.RetryAfter, out var values))
27  return;
28 
29  var secondsString = values.FirstOrDefault();
30  if (UInt32.TryParse(secondsString, out var seconds))
31  RetryAfter = DateTimeOffset.Now.AddSeconds(seconds);
32  }
DateTimeOffset RetryAfter
Gets the DateTimeOffset to try the request again after.
+
24  : base(errorMessage, responseMessage)
25  {
26  if (!responseMessage.Headers.TryGetValues(HeaderNames.RetryAfter, out var values))
27  return;
28 
29  var secondsString = values.FirstOrDefault();
30  if (UInt32.TryParse(secondsString, out var seconds))
31  RetryAfter = DateTimeOffset.Now.AddSeconds(seconds);
32  }
DateTimeOffset RetryAfter
Gets the DateTimeOffset to try the request again after.
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_server_client.html b/class_tgstation_1_1_server_1_1_client_1_1_server_client.html index 68313f2d73..ac4a1f8fc0 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_server_client.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_server_client.html @@ -432,8 +432,6 @@ Private Attributes

Definition at line 28 of file ServerClient.cs.

-

Referenced by Tgstation.Server.Client.ServerClientFactory.CreateServerClient().

- diff --git a/class_tgstation_1_1_server_1_1_client_1_1_server_client_factory-members.html b/class_tgstation_1_1_server_1_1_client_1_1_server_client_factory-members.html index 881b3ea3e2..66973996e0 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_server_client_factory-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_server_client_factory-members.html @@ -101,8 +101,8 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.ServerClientFactory, including all inherited members.

Tgstation.Server.Client.RateLimitException.RateLimitException (ErrorMessage ErrorMessage errorMessage,
- - + +
ApiClientFactoryTgstation.Server.Client.ServerClientFactoryprivatestatic
CreateServerClient(Uri host, string username, string password, TimeSpan timeout, CancellationToken cancellationToken)Tgstation.Server.Client.ServerClientFactory
CreateServerClient(Uri host, Token token, TimeSpan timeout)Tgstation.Server.Client.ServerClientFactory
CreateFromLogin(Uri host, string username, string password, IEnumerable< IRequestLogger >?requestLoggers=null, TimeSpan?timeout=null, bool attemptRefreshLogin=true, CancellationToken cancellationToken=default)Tgstation.Server.Client.ServerClientFactory
CreateFromToken(Uri host, Token token)Tgstation.Server.Client.ServerClientFactory
productHeaderValueTgstation.Server.Client.ServerClientFactoryprivate
ServerClientFactory(ProductHeaderValue productHeaderValue)Tgstation.Server.Client.ServerClientFactory
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html b/class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html index 9bffe4f6a7..c5b25a1e0d 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html @@ -128,12 +128,12 @@ Public Member Functions  ServerClientFactory (ProductHeaderValue productHeaderValue)  Construct a ServerClientFactory More...
  -async Task< IServerClientCreateServerClient (Uri host, string username, string password, TimeSpan timeout, CancellationToken cancellationToken) - Create a IServerClient More...
-  -IServerClient CreateServerClient (Uri host, Token token, TimeSpan timeout) - Create a IServerClient More...
-  +async Task< IServerClientCreateFromLogin (Uri host, string username, string password, IEnumerable< IRequestLogger >?requestLoggers=null, TimeSpan?timeout=null, bool attemptRefreshLogin=true, CancellationToken cancellationToken=default) + Create a IServerClient More...
+  +IServerClient CreateFromToken (Uri host, Token token) + Create a IServerClient More...
+  @@ -149,7 +149,7 @@ Static Private Attributes

Private Attributes

Detailed Description

-

Definition at line 11 of file ServerClientFactory.cs.

+

Definition at line 13 of file ServerClientFactory.cs.

Constructor & Destructor Documentation

@@ -173,18 +173,18 @@ Static Private Attributes -

Definition at line 27 of file ServerClientFactory.cs.

-
28  {
29  this.productHeaderValue = productHeaderValue ?? throw new ArgumentNullException(nameof(productHeaderValue));
30  }
readonly ProductHeaderValue productHeaderValue
The ProductHeaderValue for the ServerClientFactory
+

Definition at line 29 of file ServerClientFactory.cs.

+
30  {
31  this.productHeaderValue = productHeaderValue ?? throw new ArgumentNullException(nameof(productHeaderValue));
32  }
readonly ProductHeaderValue productHeaderValue
The ProductHeaderValue for the ServerClientFactory

Member Function Documentation

- +
- + @@ -204,14 +204,26 @@ Static Private Attributes - - + + + + + + + + + + + + + + - + @@ -223,28 +235,28 @@ Static Private Attributes

Create a IServerClient

-

Implements Tgstation.Server.Client.IServerClientFactory.

+

Implements Tgstation.Server.Client.IServerClientFactory.

-

Definition at line 33 of file ServerClientFactory.cs.

+

Definition at line 35 of file ServerClientFactory.cs.

References Tgstation.Server.Api.Routes.Root.

-
34  {
35  if (host == null)
36  throw new ArgumentNullException(nameof(host));
37  if (username == null)
38  throw new ArgumentNullException(nameof(username));
39  if (password == null)
40  throw new ArgumentNullException(nameof(password));
41 
42  Token token;
43  using (var api = ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, username, password)))
44  {
45  if (timeout != default)
46  api.Timeout = timeout;
47  token = await api.Update<Token>(Routes.Root, cancellationToken).ConfigureAwait(false);
48  }
49 
50  return CreateServerClient(host, token, timeout);
51  }
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
+
43  {
44  if (host == null)
45  throw new ArgumentNullException(nameof(host));
46  if (username == null)
47  throw new ArgumentNullException(nameof(username));
48  if (password == null)
49  throw new ArgumentNullException(nameof(password));
50 
51  requestLoggers ??= Enumerable.Empty<IRequestLogger>();
52 
53  Token token;
54  var loginHeaders = new ApiHeaders(productHeaderValue, username, password);
55  using (var api = ApiClientFactory.CreateApiClient(host, loginHeaders, null))
56  {
57  foreach (var requestLogger in requestLoggers)
58  api.AddRequestLogger(requestLogger);
59 
60  if (timeout.HasValue)
61  api.Timeout = timeout.Value;
62  token = await api.Update<Token>(Routes.Root, cancellationToken).ConfigureAwait(false);
63  }
64 
65  var client = CreateFromToken(host, token);
66  if (timeout.HasValue)
67  client.Timeout = timeout.Value;
68 
69  var apiHeaders = new ApiHeaders(productHeaderValue, token.Bearer!);
70 
71  return new ServerClient(ApiClientFactory.CreateApiClient(host, apiHeaders, loginHeaders), token);
72  }
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
Routes to a server actions
Definition: Routes.cs:9
-
IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders)
Create an IApiClient
const string Root
The root controller
Definition: Routes.cs:14
Represents a JWT returned by the API
Definition: Token.cs:8
-
readonly ProductHeaderValue productHeaderValue
The ProductHeaderValue for the ServerClientFactory
-
static readonly IApiClientFactory ApiClientFactory
The IApiClientFactory for the ServerClientFactory
-
async Task< IServerClient > CreateServerClient(Uri host, string username, string password, TimeSpan timeout, CancellationToken cancellationToken)
Create a IServerClient
+
IServerClient CreateFromToken(Uri host, Token token)
Create a IServerClient
+
readonly ProductHeaderValue productHeaderValue
The ProductHeaderValue for the ServerClientFactory
+
static readonly IApiClientFactory ApiClientFactory
The IApiClientFactory for the ServerClientFactory
+
IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders)
Create an IApiClient
- +
async Task<IServerClient> Tgstation.Server.Client.ServerClientFactory.CreateServerClient async Task<IServerClient> Tgstation.Server.Client.ServerClientFactory.CreateFromLogin ( Uri  host,
TimeSpan timeout, IEnumerable< IRequestLogger >? requestLoggers = null,
TimeSpan? timeout = null,
bool attemptRefreshLogin = true,
CancellationToken cancellationToken cancellationToken = default 
- + @@ -253,13 +265,7 @@ Static Private Attributes - - - - - - - + @@ -271,15 +277,15 @@ Static Private Attributes

Create a IServerClient

-

Implements Tgstation.Server.Client.IServerClientFactory.

+

Implements Tgstation.Server.Client.IServerClientFactory.

-

Definition at line 54 of file ServerClientFactory.cs.

+

Definition at line 75 of file ServerClientFactory.cs.

-

References Tgstation.Server.Api.Models.Token.Bearer, and Tgstation.Server.Client.ServerClient.Timeout.

-
55  {
56  if (host == null)
57  throw new ArgumentNullException(nameof(host));
58  if (token == null)
59  throw new ArgumentNullException(nameof(token));
60  var result = new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer)), token);
61  if (timeout != default)
62  result.Timeout = timeout;
63  return result;
64  }
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
-
IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders)
Create an IApiClient
-
readonly ProductHeaderValue productHeaderValue
The ProductHeaderValue for the ServerClientFactory
-
static readonly IApiClientFactory ApiClientFactory
The IApiClientFactory for the ServerClientFactory
+

References Tgstation.Server.Api.Models.Token.Bearer.

+
76  {
77  if (host == null)
78  throw new ArgumentNullException(nameof(host));
79  if (token == null)
80  throw new ArgumentNullException(nameof(token));
81  if (token.Bearer == null)
82  throw new ArgumentException("token.Bearer should not be null!", nameof(token));
83 
84  return new ServerClient(ApiClientFactory.CreateApiClient(host, new ApiHeaders(productHeaderValue, token.Bearer), null), token);
85  }
Represents the header that must be present for every server request
Definition: ApiHeaders.cs:17
+
readonly ProductHeaderValue productHeaderValue
The ProductHeaderValue for the ServerClientFactory
+
static readonly IApiClientFactory ApiClientFactory
The IApiClientFactory for the ServerClientFactory
+
IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders)
Create an IApiClient
string Bearer
The value of the JWT
Definition: Token.cs:13
@@ -305,7 +311,7 @@ Static Private Attributes

The IApiClientFactory for the ServerClientFactory

-

Definition at line 16 of file ServerClientFactory.cs.

+

Definition at line 18 of file ServerClientFactory.cs.

@@ -329,7 +335,7 @@ Static Private Attributes

The ProductHeaderValue for the ServerClientFactory

-

Definition at line 21 of file ServerClientFactory.cs.

+

Definition at line 23 of file ServerClientFactory.cs.

diff --git a/class_tgstation_1_1_server_1_1_client_1_1_server_error_exception-members.html b/class_tgstation_1_1_server_1_1_client_1_1_server_error_exception-members.html index f3fc6abc05..b38ee9b7e3 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_server_error_exception-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_server_error_exception-members.html @@ -101,7 +101,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.ServerErrorException, including all inherited members.

IServerClient Tgstation.Server.Client.ServerClientFactory.CreateServerClient IServerClient Tgstation.Server.Client.ServerClientFactory.CreateFromToken ( Uri  host,
Token token,
TimeSpan timeout token 
- + @@ -114,7 +114,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); - +
AdditionalServerDataTgstation.Server.Client.ApiException
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(HttpResponseMessage responseMessage, string message)Tgstation.Server.Client.ApiExceptionprotected
ApiException()Tgstation.Server.Client.ApiExceptionprotected
ApiException(string message)Tgstation.Server.Client.ApiExceptionprotected
ResponseMessageTgstation.Server.Client.ClientException
ServerApiVersionTgstation.Server.Client.ApiException
ServerErrorException()Tgstation.Server.Client.ServerErrorException
ServerErrorException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ServerErrorException
ServerErrorException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ServerErrorException
ServerErrorException(string message, Exception innerException)Tgstation.Server.Client.ServerErrorException
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html b/class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html index f21b84ba3f..253927d23f 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html @@ -127,9 +127,9 @@ Public Member Functions  ServerErrorException ()  Construct an ServerErrorException More...
  - ServerErrorException (ErrorMessage errorMessage, HttpResponseMessage responseMessage) - Construct an ServerErrorException with a given errorMessage More...
-  + ServerErrorException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage) + Construct an ServerErrorException with a given errorMessage More...
+   ServerErrorException (string message, Exception innerException)  Construct an ServerErrorException with a message and innerException More...
  @@ -137,9 +137,9 @@ Public Member Functions

Additional Inherited Members

- Protected Member Functions inherited from Tgstation.Server.Client.ApiExceptionApiException (ErrorMessage errorMessage, HttpResponseMessage responseMessage) - Initialize a new instance of the ApiException . More...
-  + ApiException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage) + Initialize a new instance of the ApiException . More...
+   ApiException (HttpResponseMessage responseMessage, string message)  Initialize a new instance of the ApiException . More...
  @@ -204,14 +204,14 @@ Additional Inherited Members
15 { }
- +
- + @@ -238,7 +238,7 @@ Additional Inherited Members

Definition at line 22 of file ServerErrorException.cs.

-
22  : base(errorMessage, responseMessage)
23  {
24  }
+
22  : base(errorMessage, responseMessage)
23  {
24  }
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception-members.html b/class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception-members.html index d2325f560f..0748eb730b 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception-members.html @@ -101,7 +101,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.UnauthorizedException, including all inherited members.

Tgstation.Server.Client.ServerErrorException.ServerErrorException (ErrorMessage ErrorMessage errorMessage,
- + @@ -113,7 +113,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); - +
AdditionalServerDataTgstation.Server.Client.ApiException
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(HttpResponseMessage responseMessage, string message)Tgstation.Server.Client.ApiExceptionprotected
ApiException()Tgstation.Server.Client.ApiExceptionprotected
ApiException(string message)Tgstation.Server.Client.ApiExceptionprotected
ErrorCodeTgstation.Server.Client.ApiException
ResponseMessageTgstation.Server.Client.ClientException
ServerApiVersionTgstation.Server.Client.ApiException
UnauthorizedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.UnauthorizedException
UnauthorizedException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.UnauthorizedException
UnauthorizedException(string message)Tgstation.Server.Client.UnauthorizedException
UnauthorizedException(string message, Exception innerException)Tgstation.Server.Client.UnauthorizedException
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html b/class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html index d7b2f9c95d..6e9ee849e8 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html @@ -124,9 +124,9 @@ Collaboration diagram for Tgstation.Server.Client.UnauthorizedException:
- - - + + + @@ -137,9 +137,9 @@ Public Member Functions - - - + + + @@ -185,14 +185,14 @@ Additional Inherited Members

Definition at line 10 of file UnauthorizedException.cs.

Constructor & Destructor Documentation

- +

Public Member Functions

 UnauthorizedException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Construct an UnauthorizedException More...
 
 UnauthorizedException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Construct an UnauthorizedException More...
 
 UnauthorizedException (string message)
 Construct an UnauthorizedException with a message More...
 

Additional Inherited Members

- Protected Member Functions inherited from Tgstation.Server.Client.ApiException
 ApiException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (HttpResponseMessage responseMessage, string message)
 Initialize a new instance of the ApiException . More...
 
- + @@ -219,7 +219,7 @@ Additional Inherited Members

Definition at line 17 of file UnauthorizedException.cs.

-
17  : base(errorMessage, responseMessage)
18  { }
+
17  : base(errorMessage, responseMessage)
18  { }
diff --git a/class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception-members.html b/class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception-members.html index 86699ad477..adc798b39e 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception-members.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception-members.html @@ -101,7 +101,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

This is the complete list of members for Tgstation.Server.Client.VersionMismatchException, including all inherited members.

Tgstation.Server.Client.UnauthorizedException.UnauthorizedException (ErrorMessage ErrorMessage errorMessage,
- + @@ -113,7 +113,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); - + diff --git a/class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html b/class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html index a41bb4bf3a..b5ced6ac08 100644 --- a/class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html +++ b/class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html @@ -124,9 +124,9 @@ Collaboration diagram for Tgstation.Server.Client.VersionMismatchException: - - - + + + @@ -140,9 +140,9 @@ Public Member Functions - - - + + + @@ -188,14 +188,14 @@ Additional Inherited Members

Definition at line 10 of file VersionMismatchException.cs.

Constructor & Destructor Documentation

- +
AdditionalServerDataTgstation.Server.Client.ApiException
ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.ApiExceptionprotected
ApiException(HttpResponseMessage responseMessage, string message)Tgstation.Server.Client.ApiExceptionprotected
ApiException()Tgstation.Server.Client.ApiExceptionprotected
ApiException(string message)Tgstation.Server.Client.ApiExceptionprotected
ErrorCodeTgstation.Server.Client.ApiException
ResponseMessageTgstation.Server.Client.ClientException
ServerApiVersionTgstation.Server.Client.ApiException
VersionMismatchException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.VersionMismatchException
VersionMismatchException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)Tgstation.Server.Client.VersionMismatchException
VersionMismatchException()Tgstation.Server.Client.VersionMismatchException
VersionMismatchException(string message)Tgstation.Server.Client.VersionMismatchException
VersionMismatchException(string message, Exception innerException)Tgstation.Server.Client.VersionMismatchException

Public Member Functions

 VersionMismatchException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the VersionMismatchException . More...
 
 VersionMismatchException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the VersionMismatchException . More...
 
 VersionMismatchException ()
 Construct an VersionMismatchException More...
 

Additional Inherited Members

- Protected Member Functions inherited from Tgstation.Server.Client.ApiException
 ApiException (ErrorMessage errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (ErrorMessage?errorMessage, HttpResponseMessage responseMessage)
 Initialize a new instance of the ApiException . More...
 
 ApiException (HttpResponseMessage responseMessage, string message)
 Initialize a new instance of the ApiException . More...
 
- + @@ -222,7 +222,7 @@ Additional Inherited Members

Definition at line 17 of file VersionMismatchException.cs.

-
17  : base(errorMessage, responseMessage)
18  {
19  }
+
17  : base(errorMessage, responseMessage)
18  {
19  }
diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager-members.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager-members.html index adb6a3e3d4..90bf7bd5b0 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager-members.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager-members.html @@ -108,26 +108,27 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + +
Tgstation.Server.Client.VersionMismatchException.VersionMismatchException (ErrorMessage ErrorMessage errorMessage,
Dispose()Tgstation.Server.Host.Components.InstanceManager
disposedTgstation.Server.Host.Components.InstanceManagerprivate
downgradeVersionTgstation.Server.Host.Components.InstanceManagerprivate
GetInstance(Models.Instance metadata)Tgstation.Server.Host.Components.InstanceManager
HandleRestart(Version updateVersion, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
instanceFactoryTgstation.Server.Host.Components.InstanceManagerprivate
InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IAssemblyInformationProvider assemblyInformationProvider, IJobManager jobManager, IServerControl serverControl, ISystemIdentityFactory systemIdentityFactory, IAsyncDelayer asyncDelayer, ILogger< InstanceManager > logger)Tgstation.Server.Host.Components.InstanceManager
instancesTgstation.Server.Host.Components.InstanceManagerprivate
ioManagerTgstation.Server.Host.Components.InstanceManagerprivate
jobManagerTgstation.Server.Host.Components.InstanceManagerprivate
loggerTgstation.Server.Host.Components.InstanceManagerprivate
MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
Tgstation::Server::Host::Components::IInstanceManager.OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken)Tgstation.Server.Host.Components.IInstanceManager
OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
ReadyTgstation.Server.Host.Components.InstanceManager
readyTcsTgstation.Server.Host.Components.InstanceManagerprivate
RegisterHandler(IBridgeHandler bridgeHandler)Tgstation.Server.Host.Components.InstanceManager
serverControlTgstation.Server.Host.Components.InstanceManagerprivate
StartAsync(CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
StopAsync(CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
systemIdentityFactoryTgstation.Server.Host.Components.InstanceManagerprivate
generalConfigurationTgstation.Server.Host.Components.InstanceManagerprivate
GetInstance(Models.Instance metadata)Tgstation.Server.Host.Components.InstanceManager
HandleRestart(Version updateVersion, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
instanceFactoryTgstation.Server.Host.Components.InstanceManagerprivate
InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IAssemblyInformationProvider assemblyInformationProvider, IJobManager jobManager, IServerControl serverControl, ISystemIdentityFactory systemIdentityFactory, IAsyncDelayer asyncDelayer, IOptions< GeneralConfiguration > generalConfigurationOptions, ILogger< InstanceManager > logger)Tgstation.Server.Host.Components.InstanceManager
instancesTgstation.Server.Host.Components.InstanceManagerprivate
ioManagerTgstation.Server.Host.Components.InstanceManagerprivate
jobManagerTgstation.Server.Host.Components.InstanceManagerprivate
loggerTgstation.Server.Host.Components.InstanceManagerprivate
MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
Tgstation::Server::Host::Components::IInstanceManager.OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken)Tgstation.Server.Host.Components.IInstanceManager
OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
ReadyTgstation.Server.Host.Components.InstanceManager
readyTcsTgstation.Server.Host.Components.InstanceManagerprivate
RegisterHandler(IBridgeHandler bridgeHandler)Tgstation.Server.Host.Components.InstanceManager
serverControlTgstation.Server.Host.Components.InstanceManagerprivate
StartAsync(CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
StopAsync(CancellationToken cancellationToken)Tgstation.Server.Host.Components.InstanceManager
systemIdentityFactoryTgstation.Server.Host.Components.InstanceManagerprivate
Collaboration graph
- - - - - - - - - - - - - - + + + + + + + + + + + + + + + +
[legend]
- - - + + + @@ -223,6 +225,9 @@ Private Attributes + + + @@ -242,9 +247,9 @@ Additional Inherited Members

Public Member Functions

 InstanceManager (IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IAssemblyInformationProvider assemblyInformationProvider, IJobManager jobManager, IServerControl serverControl, ISystemIdentityFactory systemIdentityFactory, IAsyncDelayer asyncDelayer, ILogger< InstanceManager > logger)
 Construct an InstanceManager More...
 
 InstanceManager (IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IAssemblyInformationProvider assemblyInformationProvider, IJobManager jobManager, IServerControl serverControl, ISystemIdentityFactory systemIdentityFactory, IAsyncDelayer asyncDelayer, IOptions< GeneralConfiguration > generalConfigurationOptions, ILogger< InstanceManager > logger)
 Construct an InstanceManager More...
 
void Dispose ()
 
IInstance GetInstance (Models.Instance metadata)
readonly IDictionary< string, IBridgeHandlerbridgeHandlers
 Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers. More...
 
readonly GeneralConfiguration generalConfiguration
 The GeneralConfiguration for the InstanceManager. More...
 
readonly TaskCompletionSource< object > readyTcs
 The TaskCompletionSource<TResult> for Ready. More...
 

Detailed Description

-

Definition at line 21 of file InstanceManager.cs.

+

Definition at line 24 of file InstanceManager.cs.

Constructor & Destructor Documentation

- +
@@ -296,6 +301,12 @@ Additional Inherited Members + + + + + + @@ -321,26 +332,28 @@ Additional Inherited Members +
IAsyncDelayer  asyncDelayer,
IOptions< GeneralConfigurationgeneralConfigurationOptions,
serverControlThe value of serverControl
systemIdentityFactoryThe value of systemIdentityFactory.
asyncDelayerThe value of asyncDelayer.
generalConfigurationOptionsThe IOptions<TOptions> containing the value of generalConfiguration.
loggerThe value of logger
-

Definition at line 108 of file InstanceManager.cs.

+

Definition at line 117 of file InstanceManager.cs.

References Tgstation.Server.Host.Core.IServerControl.RegisterForRestart().

-
118  {
119  this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
120  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
121  this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
122  this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
123  this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
124  this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
125  this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
126  this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
127  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
128 
129  serverControl.RegisterForRestart(this);
130 
131  instances = new Dictionary<long, IInstance>();
132  bridgeHandlers = new Dictionary<string, IBridgeHandler>();
133  readyTcs = new TaskCompletionSource<object>();
134  }
readonly IDictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
-
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
-
readonly TaskCompletionSource< object > readyTcs
The TaskCompletionSource<TResult> for Ready.
-
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
-
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
+
128  {
129  this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
130  this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
131  this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
132  this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
133  this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
134  this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
135  this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
136  this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
137  generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
138  this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
139 
140  serverControl.RegisterForRestart(this);
141 
142  instances = new Dictionary<long, IInstance>();
143  bridgeHandlers = new Dictionary<string, IBridgeHandler>();
144  readyTcs = new TaskCompletionSource<object>();
145  }
readonly IDictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
+
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the InstanceManager.
+
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
+
readonly TaskCompletionSource< object > readyTcs
The TaskCompletionSource<TResult> for Ready.
+
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
+
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
IRestartRegistration RegisterForRestart(IRestartHandler handler)
Register a given handler to run before stopping the server for a restart

Here is the call graph for this function:
-
- +
+
@@ -372,9 +385,11 @@ Here is the call graph for this function:

Check we have a valid system identity.

-

Definition at line 320 of file InstanceManager.cs.

-
321  {
322  using var systemIdentity = systemIdentityFactory.GetCurrent();
323  if (!systemIdentity.CanCreateSymlinks)
324  throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
325  }
readonly ISystemIdentityFactory systemIdentityFactory
The ISystemIdentityFactory for the InstanceManager
+

Definition at line 331 of file InstanceManager.cs.

+
332  {
333  if (generalConfiguration.UseExperimentalWatchdog && !Debugger.IsAttached)
334  throw new InvalidOperationException(
335  "The experimental watchdog is currently non-functional! Please disable the '{nameof(generalConfiguration.UseExperimentalWatchdog)}' option in your configuration!");
336 
337  using var systemIdentity = systemIdentityFactory.GetCurrent();
338  if (!systemIdentity.CanCreateSymlinks)
339  throw new InvalidOperationException("The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
340  }
readonly ISystemIdentityFactory systemIdentityFactory
The ISystemIdentityFactory for the InstanceManager
ISystemIdentity GetCurrent()
Retrieves a ISystemIdentity representing the user executing tgstation-server.
+
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the InstanceManager.
+
bool UseExperimentalWatchdog
If the Components.Watchdog.ExperimentalWatchdog should be used.
@@ -393,9 +408,9 @@ Here is the call graph for this function:

-

Definition at line 137 of file InstanceManager.cs.

-
138  {
139  lock (instances)
140  {
141  if (disposed)
142  return;
143  disposed = true;
144  }
145 
146  foreach (var I in instances)
147  I.Value.Dispose();
148  }
bool disposed
If the InstanceManager has been Disposed
-
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
+

Definition at line 148 of file InstanceManager.cs.

+
149  {
150  lock (instances)
151  {
152  if (disposed)
153  return;
154  disposed = true;
155  }
156 
157  foreach (var I in instances)
158  I.Value.Dispose();
159  }
bool disposed
If the InstanceManager has been Disposed
+
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
@@ -417,8 +432,8 @@ Here is the call graph for this function:

Implements Tgstation.Server.Host.Components.IInstanceManager.

-

Definition at line 151 of file InstanceManager.cs.

-
152  {
153  if (metadata == null)
154  throw new ArgumentNullException(nameof(metadata));
155  lock (instances)
156  {
157  if (!instances.TryGetValue(metadata.Id, out IInstance instance))
158  throw new InvalidOperationException("Instance not online!");
159  return instance;
160  }
161  }
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
+

Definition at line 162 of file InstanceManager.cs.

+
163  {
164  if (metadata == null)
165  throw new ArgumentNullException(nameof(metadata));
166  lock (instances)
167  {
168  if (!instances.TryGetValue(metadata.Id, out IInstance instance))
169  throw new InvalidOperationException("Instance not online!");
170  return instance;
171  }
172  }
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
@@ -450,10 +465,10 @@ Here is the call graph for this function:

Implements Tgstation.Server.Host.Core.IRestartHandler.

-

Definition at line 311 of file InstanceManager.cs.

-
312  {
313  downgradeVersion = updateVersion != null && updateVersion < assemblyInformationProvider.Version ? updateVersion : null;
314  return Task.CompletedTask;
315  }
-
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the InstanceManager
-
Version downgradeVersion
Used in StopAsync(CancellationToken) to determine if database downgrades must be made ...
+

Definition at line 322 of file InstanceManager.cs.

+
323  {
324  downgradeVersion = updateVersion != null && updateVersion < assemblyInformationProvider.Version ? updateVersion : null;
325  return Task.CompletedTask;
326  }
+
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the InstanceManager
+
Version downgradeVersion
Used in StopAsync(CancellationToken) to determine if database downgrades must be made ...
@@ -491,12 +506,12 @@ Here is the call graph for this function:

Implements Tgstation.Server.Host.Components.IInstanceManager.

-

Definition at line 164 of file InstanceManager.cs.

-
165  {
166  if (newPath == null)
167  throw new ArgumentNullException(nameof(newPath));
168  if (instance.Online.Value)
169  throw new InvalidOperationException("Cannot move an online instance!");
170  var oldPath = instance.Path;
171  await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false);
173  {
174  var targetInstance = new Models.Instance
175  {
176  Id = instance.Id
177  };
178  db.Instances.Attach(targetInstance);
179  targetInstance.Path = newPath;
180  return db.Save(cancellationToken);
181  }).ConfigureAwait(false);
182  await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false);
183  }
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
+

Definition at line 175 of file InstanceManager.cs.

+
176  {
177  if (newPath == null)
178  throw new ArgumentNullException(nameof(newPath));
179  if (instance.Online.Value)
180  throw new InvalidOperationException("Cannot move an online instance!");
181  var oldPath = instance.Path;
182  await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false);
184  {
185  var targetInstance = new Models.Instance
186  {
187  Id = instance.Id
188  };
189  db.Instances.Attach(targetInstance);
190  targetInstance.Path = newPath;
191  return db.Save(cancellationToken);
192  }).ConfigureAwait(false);
193  await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false);
194  }
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
Task CopyDirectory(string src, string dest, IEnumerable< string > ignore, CancellationToken cancellationToken)
Copies a directory from src to dest
-
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
+
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
Task UseContext(Func< IDatabaseContext, Task > operation)
Run an operation in the scope of an IDatabaseContext
-
readonly IIOManager ioManager
The IIOManager for the InstanceManager
+
readonly IIOManager ioManager
The IIOManager for the InstanceManager
@@ -532,12 +547,12 @@ Here is the call graph for this function:

-

Definition at line 186 of file InstanceManager.cs.

-
187  {
188  if (metadata == null)
189  throw new ArgumentNullException(nameof(metadata));
190  logger.LogInformation("Offlining instance ID {0}", metadata.Id);
191  IInstance instance;
192  lock (instances)
193  {
194  if (!instances.TryGetValue(metadata.Id, out instance))
195  throw new InvalidOperationException("Instance not online!");
196  instances.Remove(metadata.Id);
197  }
198 
199  try
200  {
201  // we are the one responsible for cancelling his jobs
202  var tasks = new List<Task>();
203  await databaseContextFactory.UseContext(async db =>
204  {
205  var jobs = db.Jobs.Where(x => x.Instance.Id == metadata.Id).Select(x => new Models.Job
206  {
207  Id = x.Id
208  }).ToAsyncEnumerable();
209  await jobs.ForEachAsync(job =>
210  {
211  lock (tasks)
212  tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken));
213  }, cancellationToken).ConfigureAwait(false);
214  }).ConfigureAwait(false);
215 
216  await Task.WhenAll(tasks).ConfigureAwait(false);
217 
218  await instance.StopAsync(cancellationToken).ConfigureAwait(false);
219  }
220  finally
221  {
222  instance.Dispose();
223  }
224  }
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
-
readonly IJobManager jobManager
The IJobManager for the InstanceManager
-
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
-
async Task StopAsync(CancellationToken cancellationToken)
-
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
+

Definition at line 197 of file InstanceManager.cs.

+
198  {
199  if (metadata == null)
200  throw new ArgumentNullException(nameof(metadata));
201  logger.LogInformation("Offlining instance ID {0}", metadata.Id);
202  IInstance instance;
203  lock (instances)
204  {
205  if (!instances.TryGetValue(metadata.Id, out instance))
206  throw new InvalidOperationException("Instance not online!");
207  instances.Remove(metadata.Id);
208  }
209 
210  try
211  {
212  // we are the one responsible for cancelling his jobs
213  var tasks = new List<Task>();
214  await databaseContextFactory.UseContext(async db =>
215  {
216  var jobs = db.Jobs.Where(x => x.Instance.Id == metadata.Id).Select(x => new Models.Job
217  {
218  Id = x.Id
219  }).ToAsyncEnumerable();
220  await jobs.ForEachAsync(job =>
221  {
222  lock (tasks)
223  tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken));
224  }, cancellationToken).ConfigureAwait(false);
225  }).ConfigureAwait(false);
226 
227  await Task.WhenAll(tasks).ConfigureAwait(false);
228 
229  await instance.StopAsync(cancellationToken).ConfigureAwait(false);
230  }
231  finally
232  {
233  instance.Dispose();
234  }
235  }
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
+
readonly IJobManager jobManager
The IJobManager for the InstanceManager
+
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
+
async Task StopAsync(CancellationToken cancellationToken)
+
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
Task UseContext(Func< IDatabaseContext, Task > operation)
Run an operation in the scope of an IDatabaseContext
@@ -570,10 +585,10 @@ Here is the call graph for this function:

Implements Tgstation.Server.Host.Components.IInstanceManager.

-

Definition at line 227 of file InstanceManager.cs.

-
228  {
229  if (metadata == null)
230  throw new ArgumentNullException(nameof(metadata));
231  logger.LogInformation("Onlining instance ID {0} ({1}) at {2}", metadata.Id, metadata.Name, metadata.Path);
232  var instance = instanceFactory.CreateInstance(this, metadata);
233  try
234  {
235  lock (instances)
236  {
237  if (instances.ContainsKey(metadata.Id))
238  throw new InvalidOperationException("Instance already online!");
239  instances.Add(metadata.Id, instance);
240  }
241  }
242  catch
243  {
244  instance.Dispose();
245  throw;
246  }
247 
248  await instance.StartAsync(cancellationToken).ConfigureAwait(false);
249  }
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
-
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
-
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
+

Definition at line 238 of file InstanceManager.cs.

+
239  {
240  if (metadata == null)
241  throw new ArgumentNullException(nameof(metadata));
242  logger.LogInformation("Onlining instance ID {0} ({1}) at {2}", metadata.Id, metadata.Name, metadata.Path);
243  var instance = instanceFactory.CreateInstance(this, metadata);
244  try
245  {
246  lock (instances)
247  {
248  if (instances.ContainsKey(metadata.Id))
249  throw new InvalidOperationException("Instance already online!");
250  instances.Add(metadata.Id, instance);
251  }
252  }
253  catch
254  {
255  instance.Dispose();
256  throw;
257  }
258 
259  await instance.StartAsync(cancellationToken).ConfigureAwait(false);
260  }
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
+
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
+
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
IInstance CreateInstance(IBridgeRegistrar bridgeRegistrar, Models.Instance metadata)
Create an IInstance
@@ -606,13 +621,13 @@ Here is the call graph for this function:

Implements Tgstation.Server.Host.Components.Interop.Bridge.IBridgeDispatcher.

-

Definition at line 328 of file InstanceManager.cs.

+

Definition at line 343 of file InstanceManager.cs.

References Tgstation.Server.Host.Components.Interop.DMApiParameters.AccessIdentifier, and Tgstation.Server.Host.Components.Interop.Bridge.IBridgeDispatcher.ProcessBridgeRequest().

-
329  {
330  if (parameters == null)
331  throw new ArgumentNullException(nameof(parameters));
332 
333  IBridgeHandler bridgeHandler = null;
334  for (var i = 0; bridgeHandler == null && i < 30; ++i)
335  {
336  // There's a miniscule time period where we could potentially receive a bridge request and not have the registration ready when we launch DD
337  // This is a stopgap
338  Task delayTask = Task.CompletedTask;
339  lock (bridgeHandlers)
340  if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
341  delayTask = asyncDelayer.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
342 
343  await delayTask.ConfigureAwait(false);
344  }
345 
346  if (bridgeHandler == null)
347  lock (bridgeHandlers)
348  if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
349  {
350  logger.LogWarning("Recieved invalid bridge request with accees identifier: {0}", parameters.AccessIdentifier);
351  return null;
352  }
353 
354  return await bridgeHandler.ProcessBridgeRequest(parameters, cancellationToken).ConfigureAwait(false);
355  }
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the InstanceManager
-
readonly IDictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
+
344  {
345  if (parameters == null)
346  throw new ArgumentNullException(nameof(parameters));
347 
348  IBridgeHandler bridgeHandler = null;
349  for (var i = 0; bridgeHandler == null && i < 30; ++i)
350  {
351  // There's a miniscule time period where we could potentially receive a bridge request and not have the registration ready when we launch DD
352  // This is a stopgap
353  Task delayTask = Task.CompletedTask;
354  lock (bridgeHandlers)
355  if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
356  delayTask = asyncDelayer.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
357 
358  await delayTask.ConfigureAwait(false);
359  }
360 
361  if (bridgeHandler == null)
362  lock (bridgeHandlers)
363  if (!bridgeHandlers.TryGetValue(parameters.AccessIdentifier, out bridgeHandler))
364  {
365  logger.LogWarning("Recieved invalid bridge request with accees identifier: {0}", parameters.AccessIdentifier);
366  return null;
367  }
368 
369  return await bridgeHandler.ProcessBridgeRequest(parameters, cancellationToken).ConfigureAwait(false);
370  }
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the InstanceManager
+
readonly IDictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
string AccessIdentifier
Used to identify and authenticate the DreamDaemon instance
-
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
+
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager

@@ -645,13 +660,13 @@ Here is the call graph for this function:

Implements Tgstation.Server.Host.Components.Interop.Bridge.IBridgeRegistrar.

-

Definition at line 358 of file InstanceManager.cs.

+

Definition at line 373 of file InstanceManager.cs.

References Tgstation.Server.Host.Components.Interop.DMApiParameters.AccessIdentifier, and Tgstation.Server.Host.Components.Interop.Bridge.IBridgeHandler.DMApiParameters.

-
359  {
360  if (bridgeHandler == null)
361  throw new ArgumentNullException(nameof(bridgeHandler));
362 
363  var accessIdentifier = bridgeHandler.DMApiParameters.AccessIdentifier;
364  lock (bridgeHandlers)
365  {
366  bridgeHandlers.Add(accessIdentifier, bridgeHandler);
367  logger.LogTrace("Registered bridge handler: {0}", accessIdentifier);
368  }
369 
370  return new BridgeRegistration(() =>
371  {
372  lock (bridgeHandlers)
373  {
374  bridgeHandlers.Remove(accessIdentifier);
375  logger.LogTrace("Unregistered bridge handler: {0}", accessIdentifier);
376  }
377  });
378  }
readonly IDictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
+
374  {
375  if (bridgeHandler == null)
376  throw new ArgumentNullException(nameof(bridgeHandler));
377 
378  var accessIdentifier = bridgeHandler.DMApiParameters.AccessIdentifier;
379  lock (bridgeHandlers)
380  {
381  bridgeHandlers.Add(accessIdentifier, bridgeHandler);
382  logger.LogTrace("Registered bridge handler: {0}", accessIdentifier);
383  }
384 
385  return new BridgeRegistration(() =>
386  {
387  lock (bridgeHandlers)
388  {
389  bridgeHandlers.Remove(accessIdentifier);
390  logger.LogTrace("Unregistered bridge handler: {0}", accessIdentifier);
391  }
392  });
393  }
readonly IDictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
string AccessIdentifier
Used to identify and authenticate the DreamDaemon instance
-
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
+
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
DMApiParameters DMApiParameters
The DMApiParameters for the IBridgeHandler.
@@ -690,13 +705,13 @@ Here is the call graph for this function:

-

Definition at line 299 of file InstanceManager.cs.

-
300  {
301  await jobManager.StopAsync(cancellationToken).ConfigureAwait(false);
302  await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false);
303  await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false);
304 
305  // downgrade the db if necessary
306  if (downgradeVersion != null)
307  await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false);
308  }
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
-
readonly IJobManager jobManager
The IJobManager for the InstanceManager
-
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
-
Version downgradeVersion
Used in StopAsync(CancellationToken) to determine if database downgrades must be made ...
+

Definition at line 310 of file InstanceManager.cs.

+
311  {
312  await jobManager.StopAsync(cancellationToken).ConfigureAwait(false);
313  await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false);
314  await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(false);
315 
316  // downgrade the db if necessary
317  if (downgradeVersion != null)
318  await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(false);
319  }
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
+
readonly IJobManager jobManager
The IJobManager for the InstanceManager
+
readonly IDictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.
+
Version downgradeVersion
Used in StopAsync(CancellationToken) to determine if database downgrades must be made ...
Task UseContext(Func< IDatabaseContext, Task > operation)
Run an operation in the scope of an IDatabaseContext
-
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
+
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
@@ -721,7 +736,7 @@ Here is the call graph for this function:

The IAssemblyInformationProvider for the InstanceManager

-

Definition at line 44 of file InstanceManager.cs.

+

Definition at line 47 of file InstanceManager.cs.

@@ -745,7 +760,7 @@ Here is the call graph for this function:

The IAsyncDelayer for the InstanceManager

-

Definition at line 64 of file InstanceManager.cs.

+

Definition at line 67 of file InstanceManager.cs.

@@ -769,7 +784,7 @@ Here is the call graph for this function:

Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.

-

Definition at line 79 of file InstanceManager.cs.

+

Definition at line 82 of file InstanceManager.cs.

@@ -793,7 +808,7 @@ Here is the call graph for this function:

The IDatabaseContextFactory for the InstanceManager

-

Definition at line 39 of file InstanceManager.cs.

+

Definition at line 42 of file InstanceManager.cs.

@@ -817,7 +832,7 @@ Here is the call graph for this function:

If the InstanceManager has been Disposed

-

Definition at line 94 of file InstanceManager.cs.

+

Definition at line 102 of file InstanceManager.cs.

@@ -841,7 +856,31 @@ Here is the call graph for this function:

Used in StopAsync(CancellationToken) to determine if database downgrades must be made

-

Definition at line 89 of file InstanceManager.cs.

+

Definition at line 97 of file InstanceManager.cs.

+ + + + +
+
+ + + + + +
+ + + + +
readonly GeneralConfiguration Tgstation.Server.Host.Components.InstanceManager.generalConfiguration
+
+private
+
+ +

The GeneralConfiguration for the InstanceManager.

+ +

Definition at line 87 of file InstanceManager.cs.

@@ -865,7 +904,7 @@ Here is the call graph for this function:

The IInstanceFactory for the InstanceManager

-

Definition at line 29 of file InstanceManager.cs.

+

Definition at line 32 of file InstanceManager.cs.

@@ -889,7 +928,7 @@ Here is the call graph for this function:

Map of Api.Models.Instance.Ids to respective IInstances. Also used as a object.

-

Definition at line 74 of file InstanceManager.cs.

+

Definition at line 77 of file InstanceManager.cs.

@@ -913,7 +952,7 @@ Here is the call graph for this function:

The IIOManager for the InstanceManager

-

Definition at line 34 of file InstanceManager.cs.

+

Definition at line 37 of file InstanceManager.cs.

@@ -937,7 +976,7 @@ Here is the call graph for this function:

The IJobManager for the InstanceManager

-

Definition at line 49 of file InstanceManager.cs.

+

Definition at line 52 of file InstanceManager.cs.

@@ -961,7 +1000,7 @@ Here is the call graph for this function:

The ILogger for the InstanceManager

-

Definition at line 69 of file InstanceManager.cs.

+

Definition at line 72 of file InstanceManager.cs.

@@ -977,7 +1016,7 @@ Here is the call graph for this function:

-

Definition at line 24 of file InstanceManager.cs.

+

Definition at line 27 of file InstanceManager.cs.

@@ -1001,7 +1040,7 @@ Here is the call graph for this function:

The TaskCompletionSource<TResult> for Ready.

-

Definition at line 84 of file InstanceManager.cs.

+

Definition at line 92 of file InstanceManager.cs.

@@ -1025,7 +1064,7 @@ Here is the call graph for this function:

The IServerControl for the InstanceManager

-

Definition at line 54 of file InstanceManager.cs.

+

Definition at line 57 of file InstanceManager.cs.

@@ -1049,7 +1088,7 @@ Here is the call graph for this function:

The ISystemIdentityFactory for the InstanceManager

-

Definition at line 59 of file InstanceManager.cs.

+

Definition at line 62 of file InstanceManager.cs.

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.map b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.map index 9d0ca2ba4f..3e7976f1c3 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.map +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.map @@ -1,16 +1,18 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.md5 b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.md5 index 49be56ebf3..10f062e6aa 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.md5 +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.md5 @@ -1 +1 @@ -f946ede3f0930fa3176f81d0599b5ef5 \ No newline at end of file +8de6323007ad24477b701d7f637e8f6a \ No newline at end of file diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.png b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager__coll__graph.png index 4f365c103569946ae698488e0db18167a159386c..0f8715d4120707422ec38bd8ea89070fada3acd1 100644 GIT binary patch literal 206426 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 z zo4hcU=|mR|)Bi3q0|?gxU8;e86>RMiwgu0Thl{Z4Ba?ff)`WHhe~K^o8874>$Pk5< z6lH7TRjXPB(3UXs<$4}5i(=bI_oBIF{wF^A6V!iD8jGI&(UcAvtWn&83(c1pS!76qt2mAVbOTk zv4h+s&&-^(tIbd~N*2V``h5>0-I^HhJEmX;b| z1)0Mk@~$5omXn@mZR>!)ktxaZ5>C;%*w8 zJ1aiVoAc|Xm;yQND|q5($yA-*EY+vFfNSE~3RsV{0ySwekLTfX73*Y~*tAESvZej6Fq1!l67PmSC z1yo{E7l3~Bp?wO+Qj_xDJ}_eFqr~2Ri%LNp zOAtVSCHmF0OaT2)x4)FiT7;wZlv}}@oq}&y@=~eIP_9_cueL<;wUEkmk0_8b8F4`^ z5$1RFoT#D5TduVMhktbzwLWV^44(UN;lhQ=DzVRd`t~PgKi8-kKYR#h;k2hR%T@Zd z8ReY)xCrm1*Tstec9 z`ft^>Fa{d!6N&tK&^=&d+s?gA6}opPN`ccyK585Htlk>yD_Ag03^KD-&0JDdoQ`q{ z?Tdm5q)YrRfHR5A`{UVUnyu8UHiL@bOkK(9 zuJy{i{>k^H^e9bkizHUg%O2IO=hT1iSMFUro|qF;`lG4}wjnoXcig`2@I(TF zG%ffx(0I{NzHFzVyX4H6`6_UbF&Qtsq&I_l%N4yne6OFmRosx~S+SafSBn-zFCl9M zXfpP^(OGR(W>%F(U)alseHNDW5a3HR#+%8}(1$jb9#K$vAkHShE7U>|V5% zh0V0J-+jxLz$7Dyo5Ofxux0)!-&SYCS4jh5o~NS|FFq+}j^4AL{7m+*;^>Fwjd66g zKyixP+~L3^tq(CN+uyqQt+7_gJ)>K3{G5%Bt?{N{d)K?J7e0i=`pC5XeckcZe*1u{ zVM_q_>rTIK>)IOl*^q6BeFbP4wjCd2MdH^%iAW!J2@w zt6pnXufsFCjy%?u_V*fDi0=*;`{UFsOJFerZBDY!4s}Cozx+=urXh>66}CI?&;%QB z;_$RZq0a7pL9>V?b=;rt4G!ogp43f@d^l`5XZYOFZ=du;0&j#f^OEw@EAM}-lnXy@ z`QjnrTJjdb@98}A?ag?nf%wd$CteB@b=ax#&E9a+Lyq>B=WsJFo~I~$K6)_m-TX4J zYhCWVIr5#Q zLKl|A2`$^rnoM1?jjD_T9E&8`7}KwESdJK4>~?z1#OMFz*xg;e=F5-Q!uVy3TUUT- zdvm|w+)ry>q6w3&*!yVtAL{6MR|93Accf@EZUD;z4xB!ivfNH;OPrjjJHbrZccEcf zRn)|S$|!OE`Cp5DS-C|UG~SJ=}l?8_IS1Ml)8 z#2S=m1-FHiQz(?=y|i72FgA}A_uY2=U;%Mn-s%CN=*hZep@XX$< zvhTMxcs`68V7r{KWJ?s`wuC3!n8}guEKeu~nU8RTMn + + diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager_a4ce33ffacb174f0ac669e29058854318_cgraph.md5 b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager_a4ce33ffacb174f0ac669e29058854318_cgraph.md5 new file mode 100644 index 0000000000..daf92c3b5d --- /dev/null +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager_a4ce33ffacb174f0ac669e29058854318_cgraph.md5 @@ -0,0 +1 @@ +6d6ef8429b3bb8ef2d5cb1405b7318e1 \ No newline at end of file diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager_a4ce33ffacb174f0ac669e29058854318_cgraph.png b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager_a4ce33ffacb174f0ac669e29058854318_cgraph.png new file mode 100644 index 0000000000000000000000000000000000000000..5623b160ecd0fb8a88ee1e84032617c082017c26 GIT binary patch literal 6599 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 literal 0 HcmV?d00001 diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_d_m_api_parameters.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_d_m_api_parameters.html index c2cc6a0e14..0b427f0c2b 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_d_m_api_parameters.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_d_m_api_parameters.html @@ -149,7 +149,7 @@ Properties

Definition at line 14 of file DMApiParameters.cs.

-

Referenced by Tgstation.Server.Host.Components.InstanceManager.ProcessBridgeRequest(), Tgstation.Server.Host.Components.Session.ReattachInfoHandler.ReattachInfoHandler(), Tgstation.Server.Host.Models.ReattachInformationBase.ReattachInformationBase(), Tgstation.Server.Host.Components.InstanceManager.RegisterHandler(), Tgstation.Server.Host.Components.Session.SessionController.SendCommand(), and Tgstation.Server.Host.Components.Session.SessionController.SessionController().

+

Referenced by Tgstation.Server.Host.Components.InstanceManager.ProcessBridgeRequest(), Tgstation.Server.Host.Components.Session.ReattachInfoHandler.ReattachInfoHandler(), Tgstation.Server.Host.Models.ReattachInformationBase.ReattachInformationBase(), Tgstation.Server.Host.Components.InstanceManager.RegisterHandler(), Tgstation.Server.Host.Components.Session.SessionController.SendCommand(), and Tgstation.Server.Host.Components.Session.SessionController.SessionController().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html index 580671afa8..8409f0a348 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html @@ -429,7 +429,7 @@ Here is the caller graph for this function:

Definition at line 250 of file Repository.cs.

References Tgstation.Server.Api.Models.TestMergeParameters.Comment, Tgstation.Server.Api.Models.TestMergeParameters.Number, and Tgstation.Server.Api.Models.TestMergeParameters.PullRequestRevision.

-
258  {
259  if (testMergeParameters == null)
260  throw new ArgumentNullException(nameof(testMergeParameters));
261  if (committerName == null)
262  throw new ArgumentNullException(nameof(committerName));
263  if (committerEmail == null)
264  throw new ArgumentNullException(nameof(committerEmail));
265  if (progressReporter == null)
266  throw new ArgumentNullException(nameof(progressReporter));
267 
268  logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({4}) by <{2} ({3})>",
269  testMergeParameters.Number,
270  testMergeParameters.PullRequestRevision?.Substring(0, 7),
271  committerName,
272  committerEmail,
273  testMergeParameters.Comment);
274 
275  if (!IsGitHubRepository)
276  throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!");
277 
278  var commitMessage = String.Format(
279  CultureInfo.InvariantCulture,
280  "Test merge of pull request #{0}{1}{2}",
281  testMergeParameters.Number,
282  testMergeParameters.Comment != null
283  ? Environment.NewLine
284  : String.Empty,
285  testMergeParameters.Comment ?? String.Empty);
286 
287  var prBranchName = String.Format(CultureInfo.InvariantCulture, "pr-{0}", testMergeParameters.Number);
288  var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", testMergeParameters.Number, prBranchName);
289 
290  var refSpec = String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName);
291  var refSpecList = new List<string> { refSpec };
292  var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", testMergeParameters.Number);
293 
294  var originalCommit = libGitRepo.Head;
295 
296  MergeResult result = null;
297 
298  var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now);
299  await Task.Factory.StartNew(() =>
300  {
301  try
302  {
303  try
304  {
305  logger.LogTrace("Fetching refspec {0}...", refSpec);
306 
307  var remote = libGitRepo.Network.Remotes.First();
308  progressReporter(0);
309  commands.Fetch(
310  libGitRepo,
311  refSpecList,
312  remote,
313  new FetchOptions
314  {
315  Prune = true,
316  OnProgress = (a) => !cancellationToken.IsCancellationRequested,
317  OnTransferProgress = (a) =>
318  {
319  var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
320  progressReporter((int)percentage);
321  return !cancellationToken.IsCancellationRequested;
322  },
323  OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
324  CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
325  },
326  logMessage);
327  }
328  catch (UserCancelledException) { }
329 
330  cancellationToken.ThrowIfCancellationRequested();
331 
332  libGitRepo.RemoveUntrackedFiles();
333 
334  cancellationToken.ThrowIfCancellationRequested();
335 
336  testMergeParameters.PullRequestRevision = libGitRepo.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha;
337 
338  cancellationToken.ThrowIfCancellationRequested();
339 
340  logger.LogTrace("Merging {0} into {1}...", testMergeParameters.PullRequestRevision.Substring(0, 7), Reference);
341 
342  result = libGitRepo.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions
343  {
344  CommitOnSuccess = commitMessage == null,
345  FailOnConflict = true,
346  FastForwardStrategy = FastForwardStrategy.NoFastForward,
347  SkipReuc = true,
348  OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50)))
349  });
350  }
351  finally
352  {
353  libGitRepo.Branches.Remove(localBranchName);
354  }
355 
356  cancellationToken.ThrowIfCancellationRequested();
357 
358  if (result.Status == MergeStatus.Conflicts)
359  {
360  var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
361  logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo);
362  RawCheckout(revertTo, progressReporter, cancellationToken);
363  cancellationToken.ThrowIfCancellationRequested();
364  }
365 
366  libGitRepo.RemoveUntrackedFiles();
367  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
368 
369  if (result.Status == MergeStatus.Conflicts)
370  {
371  await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List<string> { originalCommit.Tip.Sha, testMergeParameters.PullRequestRevision, originalCommit.FriendlyName ?? UnknownReference, prBranchName }, cancellationToken).ConfigureAwait(false);
372  return null;
373  }
374 
375  if (commitMessage != null && result.Status != MergeStatus.UpToDate)
376  {
377  logger.LogTrace("Committing merge: \"{0}\"...", commitMessage);
378  await Task.Factory.StartNew(() => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions
379  {
380  PrettifyMessage = true
381  }), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
382  }
383 
385  EventType.RepoMergePullRequest,
386  new List<string>
387  {
388  testMergeParameters.Number.ToString(CultureInfo.InvariantCulture),
389  testMergeParameters.PullRequestRevision,
390  testMergeParameters.Comment
391  },
392  cancellationToken)
393  .ConfigureAwait(false);
394 
395  return result.Status != MergeStatus.NonFastForward;
396  }
EventType
Types of events. Mirror in tgs.dm
Definition: EventType.cs:6
+
258  {
259  if (testMergeParameters == null)
260  throw new ArgumentNullException(nameof(testMergeParameters));
261  if (committerName == null)
262  throw new ArgumentNullException(nameof(committerName));
263  if (committerEmail == null)
264  throw new ArgumentNullException(nameof(committerEmail));
265  if (progressReporter == null)
266  throw new ArgumentNullException(nameof(progressReporter));
267 
268  logger.LogDebug("Begin AddTestMerge: #{0} at {1} ({2}) by <{3} ({4})>",
269  testMergeParameters.Number,
270  testMergeParameters.PullRequestRevision?.Substring(0, 7),
271  testMergeParameters.Comment,
272  committerName,
273  committerEmail);
274 
275  if (!IsGitHubRepository)
276  throw new InvalidOperationException("Test merging is only available on GitHub hosted origin repositories!");
277 
278  var commitMessage = String.Format(
279  CultureInfo.InvariantCulture,
280  "Test merge of pull request #{0}{1}{2}",
281  testMergeParameters.Number,
282  testMergeParameters.Comment != null
283  ? Environment.NewLine
284  : String.Empty,
285  testMergeParameters.Comment ?? String.Empty);
286 
287  var prBranchName = String.Format(CultureInfo.InvariantCulture, "pr-{0}", testMergeParameters.Number);
288  var localBranchName = String.Format(CultureInfo.InvariantCulture, "pull/{0}/headrefs/heads/{1}", testMergeParameters.Number, prBranchName);
289 
290  var refSpec = String.Format(CultureInfo.InvariantCulture, "pull/{0}/head:{1}", testMergeParameters.Number, prBranchName);
291  var refSpecList = new List<string> { refSpec };
292  var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", testMergeParameters.Number);
293 
294  var originalCommit = libGitRepo.Head;
295 
296  MergeResult result = null;
297 
298  var sig = new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now);
299  await Task.Factory.StartNew(() =>
300  {
301  try
302  {
303  try
304  {
305  logger.LogTrace("Fetching refspec {0}...", refSpec);
306 
307  var remote = libGitRepo.Network.Remotes.First();
308  progressReporter(0);
309  commands.Fetch(
310  libGitRepo,
311  refSpecList,
312  remote,
313  new FetchOptions
314  {
315  Prune = true,
316  OnProgress = (a) => !cancellationToken.IsCancellationRequested,
317  OnTransferProgress = (a) =>
318  {
319  var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
320  progressReporter((int)percentage);
321  return !cancellationToken.IsCancellationRequested;
322  },
323  OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
324  CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
325  },
326  logMessage);
327  }
328  catch (UserCancelledException) { }
329 
330  cancellationToken.ThrowIfCancellationRequested();
331 
332  libGitRepo.RemoveUntrackedFiles();
333 
334  cancellationToken.ThrowIfCancellationRequested();
335 
336  testMergeParameters.PullRequestRevision = libGitRepo.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha;
337 
338  cancellationToken.ThrowIfCancellationRequested();
339 
340  logger.LogTrace("Merging {0} into {1}...", testMergeParameters.PullRequestRevision.Substring(0, 7), Reference);
341 
342  result = libGitRepo.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions
343  {
344  CommitOnSuccess = commitMessage == null,
345  FailOnConflict = true,
346  FastForwardStrategy = FastForwardStrategy.NoFastForward,
347  SkipReuc = true,
348  OnCheckoutProgress = (a, completedSteps, totalSteps) => progressReporter(50 + ((int)(((float)completedSteps) / totalSteps * 50)))
349  });
350  }
351  finally
352  {
353  libGitRepo.Branches.Remove(localBranchName);
354  }
355 
356  cancellationToken.ThrowIfCancellationRequested();
357 
358  if (result.Status == MergeStatus.Conflicts)
359  {
360  var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
361  logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo);
362  RawCheckout(revertTo, progressReporter, cancellationToken);
363  cancellationToken.ThrowIfCancellationRequested();
364  }
365 
366  libGitRepo.RemoveUntrackedFiles();
367  }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
368 
369  if (result.Status == MergeStatus.Conflicts)
370  {
371  await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List<string> { originalCommit.Tip.Sha, testMergeParameters.PullRequestRevision, originalCommit.FriendlyName ?? UnknownReference, prBranchName }, cancellationToken).ConfigureAwait(false);
372  return null;
373  }
374 
375  if (commitMessage != null && result.Status != MergeStatus.UpToDate)
376  {
377  logger.LogTrace("Committing merge: \"{0}\"...", commitMessage);
378  await Task.Factory.StartNew(() => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions
379  {
380  PrettifyMessage = true
381  }), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
382  }
383 
385  EventType.RepoMergePullRequest,
386  new List<string>
387  {
388  testMergeParameters.Number.ToString(CultureInfo.InvariantCulture),
389  testMergeParameters.PullRequestRevision,
390  testMergeParameters.Comment
391  },
392  cancellationToken)
393  .ConfigureAwait(false);
394 
395  return result.Status != MergeStatus.NonFastForward;
396  }
EventType
Types of events. Mirror in tgs.dm
Definition: EventType.cs:6
readonly ICredentialsProvider credentialsProvider
The ICredentialsProvider for the Repository
Definition: Repository.cs:80
int Number
The number of the pull request
diff --git a/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_control_panel_configuration.html b/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_control_panel_configuration.html index 2bab24397c..fa7c01ac4c 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_control_panel_configuration.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_control_panel_configuration.html @@ -147,7 +147,7 @@ Properties

Definition at line 13 of file ControlPanelConfiguration.cs.

-

Referenced by Tgstation.Server.Host.Setup.SetupWizard.SaveConfiguration().

+

Referenced by Tgstation.Server.Host.Setup.SetupWizard.SaveConfiguration().

@@ -198,7 +198,7 @@ Properties

Definition at line 28 of file ControlPanelConfiguration.cs.

-

Referenced by Tgstation.Server.Host.Setup.SetupWizard.ConfigureControlPanel().

+

Referenced by Tgstation.Server.Host.Setup.SetupWizard.ConfigureControlPanel().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_database_configuration.html b/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_database_configuration.html index 685beaa161..bb9d3dfbf7 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_database_configuration.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_database_configuration.html @@ -156,7 +156,7 @@ Properties

Definition at line 14 of file DatabaseConfiguration.cs.

-

Referenced by Tgstation.Server.Host.Setup.SetupWizard.SaveConfiguration().

+

Referenced by Tgstation.Server.Host.Setup.SetupWizard.SaveConfiguration().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_file_logging_configuration.html b/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_file_logging_configuration.html index cebcc896d6..b99982100d 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_file_logging_configuration.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_file_logging_configuration.html @@ -208,7 +208,7 @@ Private Attributes

Definition at line 15 of file FileLoggingConfiguration.cs.

-

Referenced by Tgstation.Server.Host.Setup.SetupWizard.SaveConfiguration().

+

Referenced by Tgstation.Server.Host.Setup.SetupWizard.SaveConfiguration().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html b/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html index f26ae43c37..8110cbb3f5 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html @@ -352,7 +352,7 @@ Private Attributes

Definition at line 15 of file GeneralConfiguration.cs.

-

Referenced by Tgstation.Server.Host.Setup.SetupWizard.SaveConfiguration(), and Tgstation.Server.Host.Extensions.ServiceCollectionExtensions.UseStandardConfig< TConfig >().

+

Referenced by Tgstation.Server.Host.Setup.SetupWizard.SaveConfiguration(), and Tgstation.Server.Host.Extensions.ServiceCollectionExtensions.UseStandardConfig< TConfig >().

diff --git a/class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html b/class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html index e8100ab64e..aab0a37416 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html @@ -621,7 +621,7 @@ Here is the call graph for this function:

Definition at line 319 of file RepositoryController.cs.

References Tgstation.Server.Host.Security.AuthenticationContext.GetRight(), Tgstation.Server.Api.Models.Instance.Id, Tgstation.Server.Api.Models.Internal.User.Id, Tgstation.Server.Api.Rights.List, Tgstation.Server.Api.Models.Internal.User.Name, Tgstation.Server.Host.Database.DatabaseContext< TParentContext >.RepositorySettings, Tgstation.Server.Host.Database.DatabaseContext< TParentContext >.Save(), and Tgstation.Server.Host.Security.AuthenticationContext.User.

-
320  {
321  if (model == null)
322  throw new ArgumentNullException(nameof(model));
323 
324  if (model.AccessUser == null ^ model.AccessToken == null)
325  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchUserAndAccessToken));
326 
327  if (model.CheckoutSha != null && model.Reference != null)
328  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchShaAndReference));
329 
330  if (model.CheckoutSha != null && model.UpdateFromOrigin == true)
331  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchShaAndUpdate));
332 
333  if (model.NewTestMerges?.Any(x => model.NewTestMerges.Any(y => x != y && x.Number == y.Number)) == true)
334  return BadRequest(new ErrorMessage(ErrorCode.RepoDuplicateTestMerge));
335 
336  if (model.CommitterName?.Length == 0)
337  return BadRequest(new ErrorMessage(ErrorCode.RepoWhitespaceCommitterName));
338 
339  if (model.CommitterEmail?.Length == 0)
340  return BadRequest(new ErrorMessage(ErrorCode.RepoWhitespaceCommitterEmail));
341 
342  var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0;
343  var userRights = (RepositoryRights)AuthenticationContext.GetRight(RightsType.Repository);
344  if (newTestMerges && !userRights.HasFlag(RepositoryRights.MergePullRequest))
345  return Forbid();
346 
347  var currentModel = await DatabaseContext
349  .Where(x => x.InstanceId == Instance.Id)
350  .FirstOrDefaultAsync(cancellationToken)
351  .ConfigureAwait(false);
352 
353  if (currentModel == default)
354  return StatusCode((int)HttpStatusCode.Gone);
355 
356  bool CheckModified<T>(Expression<Func<Api.Models.Internal.RepositorySettings, T>> expression, RepositoryRights requiredRight)
357  {
358  var memberSelectorExpression = (MemberExpression)expression.Body;
359  var property = (PropertyInfo)memberSelectorExpression.Member;
360 
361  var newVal = property.GetValue(model);
362  if (newVal == null)
363  return false;
364  if (!userRights.HasFlag(requiredRight) && property.GetValue(currentModel) != newVal)
365  return true;
366 
367  property.SetValue(currentModel, newVal);
368  return false;
369  }
370 
371  if (CheckModified(x => x.AccessToken, RepositoryRights.ChangeCredentials)
372  || CheckModified(x => x.AccessUser, RepositoryRights.ChangeCredentials)
373  || CheckModified(x => x.AutoUpdatesKeepTestMerges, RepositoryRights.ChangeAutoUpdateSettings)
374  || CheckModified(x => x.AutoUpdatesSynchronize, RepositoryRights.ChangeAutoUpdateSettings)
375  || CheckModified(x => x.CommitterEmail, RepositoryRights.ChangeCommitter)
376  || CheckModified(x => x.CommitterName, RepositoryRights.ChangeCommitter)
377  || CheckModified(x => x.PushTestMergeCommits, RepositoryRights.ChangeTestMergeCommits)
378  || CheckModified(x => x.ShowTestMergeCommitters, RepositoryRights.ChangeTestMergeCommits)
379  || CheckModified(x => x.PostTestMergeComment, RepositoryRights.ChangeTestMergeCommits)
380  || (model.UpdateFromOrigin == true && !userRights.HasFlag(RepositoryRights.UpdateBranch)))
381  return Forbid();
382 
383  if (currentModel.AccessToken?.Length == 0 && currentModel.AccessUser?.Length == 0)
384  {
385  // setting an empty string clears everything
386  currentModel.AccessUser = null;
387  currentModel.AccessToken = null;
388  }
389 
390  var canRead = userRights.HasFlag(RepositoryRights.Read);
391 
392  var api = canRead ? currentModel.ToApi() : new Repository();
394 
395  if (canRead)
396  {
397  if (repoManager.CloneInProgress)
398  return Conflict(new ErrorMessage(ErrorCode.RepoCloning));
399 
400  if (repoManager.InUse)
401  return Conflict(new ErrorMessage(ErrorCode.RepoBusy));
402 
403  using var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false);
404  if (repo == null)
405  return Conflict(new ErrorMessage(ErrorCode.RepoMissing));
406  await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false);
407 
408  if (model.Origin != null && model.Origin != repo.Origin)
409  return BadRequest(new ErrorMessage(ErrorCode.RepoCantChangeOrigin));
410  }
411 
412  // this is just db stuf so stow it away
413  await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
414 
415  // format the job description
416  string description = null;
417  if (model.UpdateFromOrigin == true)
418  if (model.Reference != null)
419  description = String.Format(CultureInfo.InvariantCulture, "Fetch and hard reset repository to origin/{0}", model.Reference);
420  else if (model.CheckoutSha != null)
421  description = String.Format(CultureInfo.InvariantCulture, "Fetch and checkout {0} in repository", model.CheckoutSha);
422  else
423  description = "Pull current repository reference";
424  else if (model.Reference != null || model.CheckoutSha != null)
425  description = String.Format(CultureInfo.InvariantCulture, "Checkout repository {0} {1}", model.Reference != null ? "reference" : "SHA", model.Reference ?? model.CheckoutSha);
426 
427  if (newTestMerges)
428  description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}",
429  description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T",
430  String.Join(", ", model.NewTestMerges.Select(x =>
431  String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number,
432  x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))),
433  description != null ? String.Empty : " in repository");
434 
435  if (description == null)
436  return Json(api); // no git changes
437 
438  var job = new Models.Job
439  {
440  Description = description,
441  StartedBy = AuthenticationContext.User,
442  Instance = Instance,
443  CancelRightsType = RightsType.Repository,
444  CancelRight = (ulong)RepositoryRights.CancelPendingChanges,
445  };
446 
447  // Time to access git, do it in a job
448  await jobManager.RegisterOperation(job, async (paramJob, databaseContextFactory, progressReporter, ct) =>
449  {
450  using var repo = await repoManager.LoadRepository(ct).ConfigureAwait(false);
451  if (repo == null)
452  throw new JobException(ErrorCode.RepoMissing);
453 
454  var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null;
455 
456  var startReference = repo.Reference;
457  var startSha = repo.Head;
458  string postUpdateSha = null;
459 
460  if (newTestMerges && !repo.IsGitHubRepository)
461  throw new JobException(ErrorCode.RepoUnsupportedTestMergeRemote);
462 
463  var committerName = currentModel.ShowTestMergeCommitters.Value
465  : currentModel.CommitterName;
466 
467  var hardResettingToOriginReference = model.UpdateFromOrigin == true && model.Reference != null;
468 
469  var numSteps = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0) + (!modelHasShaOrReference ? 2 : (hardResettingToOriginReference ? 3 : 1));
470  var doneSteps = 0;
471 
472  Action<int> NextProgressReporter()
473  {
474  var tmpDoneSteps = doneSteps;
475  ++doneSteps;
476  return progress => progressReporter((progress + (100 * tmpDoneSteps)) / numSteps);
477  }
478 
479  progressReporter(0);
480 
481  // get a base line for where we are
482  Models.RevisionInformation lastRevisionInfo = null;
483 
484  var attachedInstance = new Models.Instance
485  {
486  Id = Instance.Id
487  };
488 
489  Task CallLoadRevInfo(Models.TestMerge testMergeToAdd = null, string lastOriginCommitSha = null) => databaseContextFactory
490  .UseContext(
491  async databaseContext =>
492  {
493  databaseContext.Instances.Attach(attachedInstance);
494  var needsUpdate = await LoadRevisionInformation(
495  repo,
496  databaseContext,
497  attachedInstance,
498  lastOriginCommitSha,
499  x => lastRevisionInfo = x,
500  ct)
501  .ConfigureAwait(false);
502 
503  if (testMergeToAdd != null)
504  {
505  // rev info may have already loaded the user
506  var mergedBy = databaseContext.Users.Local.FirstOrDefault(x => x.Id == AuthenticationContext.User.Id);
507  if (mergedBy == default)
508  {
509  mergedBy = new Models.User
510  {
512  };
513 
514  databaseContext.Users.Attach(mergedBy);
515  }
516 
517  testMergeToAdd.MergedBy = mergedBy;
518 
519  lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
520  {
521  TestMerge = testMergeToAdd
522  });
523  lastRevisionInfo.PrimaryTestMerge = testMergeToAdd;
524 
525  databaseContext.Users.Attach(testMergeToAdd.MergedBy);
526  }
527 
528  if (needsUpdate)
529  await databaseContext.Save(cancellationToken).ConfigureAwait(false);
530  });
531 
532  await CallLoadRevInfo().ConfigureAwait(false);
533 
534  // apply new rev info, tracking applied test merges
535  async Task UpdateRevInfo(Models.TestMerge testMergeToAdd = null)
536  {
537  var last = lastRevisionInfo;
538  await CallLoadRevInfo(testMergeToAdd, last.OriginCommitSha).ConfigureAwait(false);
539  lastRevisionInfo.ActiveTestMerges.AddRange(last.ActiveTestMerges);
540  }
541 
542  try
543  {
544  // fetch/pull
545  if (model.UpdateFromOrigin == true)
546  {
547  if (!repo.Tracking)
548  throw new JobException(ErrorCode.RepoReferenceRequired);
549  await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false);
550  doneSteps = 1;
551  if (!modelHasShaOrReference)
552  {
553  var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter(), ct).ConfigureAwait(false);
554  if (!fastForward.HasValue)
555  throw new JobException(ErrorCode.RepoMergeConflict);
556  await UpdateRevInfo().ConfigureAwait(false);
557  if (fastForward.Value)
558  {
559  lastRevisionInfo.OriginCommitSha = repo.Head;
560  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
561  postUpdateSha = repo.Head;
562  }
563  else
564  NextProgressReporter()(100);
565  }
566  }
567 
568  // checkout/hard reset
569  if (modelHasShaOrReference)
570  {
571  var validCheckoutSha =
572  model.CheckoutSha != null
573  && !repo.Head.StartsWith(model.CheckoutSha, StringComparison.OrdinalIgnoreCase);
574  var validCheckoutReference =
575  model.Reference != null
576  && !repo.Reference.Equals(model.Reference, StringComparison.OrdinalIgnoreCase);
577  if (validCheckoutSha || validCheckoutReference)
578  {
579  var committish = model.CheckoutSha ?? model.Reference;
580  var isSha = await repo.IsSha(committish, cancellationToken).ConfigureAwait(false);
581 
582  if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null))
583  throw new JobException(ErrorCode.RepoSwappedShaOrReference);
584 
585  await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false);
586  await CallLoadRevInfo().ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
587  }
588  else
589  NextProgressReporter()(100);
590 
591  if (hardResettingToOriginReference)
592  {
593  if (!repo.Tracking)
594  throw new JobException(ErrorCode.RepoReferenceNotTracking);
595  await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false);
596  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
597  await CallLoadRevInfo().ConfigureAwait(false);
598 
599  // repo head is on origin so force this
600  // will update the db if necessary
601  lastRevisionInfo.OriginCommitSha = repo.Head;
602  }
603  }
604 
605  // test merging
606  Dictionary<int, Octokit.PullRequest> prMap = null;
607  if (newTestMerges)
608  {
609  // bit of sanitization
610  foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision)))
611  I.PullRequestRevision = null;
612 
613  var gitHubClient = currentModel.AccessToken != null
614  ? gitHubClientFactory.CreateClient(currentModel.AccessToken)
615  : (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken)
618 
619  var repoOwner = repo.GitHubOwner;
620  var repoName = repo.GitHubRepoName;
621 
622  // optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
623  Models.RevisionInformation revInfoWereLookingFor = null;
624  bool needToApplyRemainingPrs = true;
625  if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
626  {
627  // In order for this to work though we need the shas of all the commits
628  if (model.NewTestMerges.Any(x => x.PullRequestRevision == null))
629  prMap = new Dictionary<int, Octokit.PullRequest>();
630 
631  bool cantSearch = false;
632  foreach (var I in model.NewTestMerges)
633  {
634  if (I.PullRequestRevision != null)
635 #pragma warning disable CA1308 // Normalize strings to uppercase
636  I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant(); // ala libgit2
637 #pragma warning restore CA1308 // Normalize strings to uppercase
638  else
639  try
640  {
641  // retrieve the latest sha
642  var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
643  prMap.Add(I.Number, pr);
644  I.PullRequestRevision = pr.Head.Sha;
645  }
646  catch
647  {
648  cantSearch = true;
649  break;
650  }
651  }
652 
653  if (!cantSearch)
654  {
655  List<Models.RevisionInformation> dbPull = null;
656 
657  await databaseContextFactory.UseContext(
658  async databaseContext =>
659  dbPull = await databaseContext.RevisionInformations
660  .Where(x => x.Instance.Id == Instance.Id
661  && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
662  && x.ActiveTestMerges.Count <= model.NewTestMerges.Count
663  && x.ActiveTestMerges.Count > 0)
664  .Include(x => x.ActiveTestMerges)
665  .ThenInclude(x => x.TestMerge)
666  .ToListAsync(cancellationToken)
667  .ConfigureAwait(false))
668  .ConfigureAwait(false);
669 
670  // split here cause this bit has to be done locally
671  revInfoWereLookingFor = dbPull
672  .Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count
673  && x.ActiveTestMerges.Select(y => y.TestMerge)
674  .All(y => model.NewTestMerges.Any(z =>
675  y.Number == z.Number
676  && y.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal)
677  && (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))))
678  .FirstOrDefault();
679 
680  if (revInfoWereLookingFor == default && model.NewTestMerges.Count > 1)
681  {
682  // okay try to add at least SOME prs we've seen before
683  var search = model.NewTestMerges.ToList();
684 
685  var appliedTestMergeIds = new List<long>();
686 
687  Models.RevisionInformation lastGoodRevInfo = null;
688  do
689  {
690  foreach (var I in search)
691  {
692  revInfoWereLookingFor = dbPull
693  .Where(x => model.NewTestMerges.Any(z =>
694  x.PrimaryTestMerge.Number == z.Number
695  && x.PrimaryTestMerge.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal)
696  && (x.PrimaryTestMerge.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))
697  && x.ActiveTestMerges.Select(y => y.TestMerge).All(y => appliedTestMergeIds.Contains(y.Id)))
698  .FirstOrDefault();
699 
700  if (revInfoWereLookingFor != null)
701  {
702  lastGoodRevInfo = revInfoWereLookingFor;
703  appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge.Id);
704  search.Remove(I);
705  break;
706  }
707  }
708  }
709  while (revInfoWereLookingFor != null && search.Count > 0);
710 
711  revInfoWereLookingFor = lastGoodRevInfo;
712  needToApplyRemainingPrs = search.Count != 0;
713  if (needToApplyRemainingPrs)
714  model.NewTestMerges = search;
715  }
716  else if (revInfoWereLookingFor != null)
717  needToApplyRemainingPrs = false;
718  }
719  }
720 
721  if (revInfoWereLookingFor != null)
722  {
723  // goteem
724  await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken).ConfigureAwait(false);
725  lastRevisionInfo = revInfoWereLookingFor;
726  }
727 
728  if (needToApplyRemainingPrs)
729  {
730  foreach (var I in model.NewTestMerges)
731  {
732  Octokit.PullRequest pr = null;
733  string errorMessage = null;
734 
735  if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == I.Number))
736  throw new JobException(ErrorCode.RepoDuplicateTestMerge);
737 
738  Exception exception = null;
739  try
740  {
741  // load from cache if possible
742  if (prMap == null || !prMap.TryGetValue(I.Number, out pr))
743  pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
744  }
745  catch (Octokit.RateLimitExceededException ex)
746  {
747  // you look at your anonymous access and sigh
748  errorMessage = "REMOTE API ERROR: RATE LIMITED";
749  exception = ex;
750  }
751  catch (Octokit.AuthorizationException ex)
752  {
753  errorMessage = "REMOTE API ERROR: BAD CREDENTIALS";
754  exception = ex;
755  }
756  catch (Octokit.NotFoundException ex)
757  {
758  // you look at your shithub and sigh
759  errorMessage = "REMOTE API ERROR: PULL REQUEST NOT FOUND";
760  exception = ex;
761  }
762 
763  if (exception != null)
764  Logger.LogWarning("Error retrieving pull request metadata: {0}", exception);
765 
766  // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
767  if (I.PullRequestRevision == null && pr != null)
768  I.PullRequestRevision = pr.Head.Sha;
769 
770  var mergeResult = await repo.AddTestMerge(
771  I,
772  committerName,
773  currentModel.CommitterEmail,
774  currentModel.AccessUser,
775  currentModel.AccessToken,
776  NextProgressReporter(),
777  ct).ConfigureAwait(false);
778 
779  if (!mergeResult.HasValue)
780  throw new JobException(
781  ErrorCode.RepoTestMergeConflict,
782  new JobException(
783  $"Merge of PR #{I.Number} at {I.PullRequestRevision.Substring(0, 7)} conflicted!"));
784 
785  ++doneSteps;
786 
787  // MergedBy will be set later
788  var tm = new Models.TestMerge
789  {
790  Author = pr?.User.Login ?? errorMessage,
791  BodyAtMerge = pr?.Body ?? errorMessage ?? String.Empty,
792  MergedAt = DateTimeOffset.Now,
793  TitleAtMerge = pr?.Title ?? errorMessage ?? String.Empty,
794  Comment = I.Comment,
795  Number = I.Number,
796  PullRequestRevision = I.PullRequestRevision,
797  Url = pr?.HtmlUrl ?? errorMessage
798  };
799 
800  await UpdateRevInfo(tm).ConfigureAwait(false);
801  }
802  }
803  }
804 
805  var currentHead = repo.Head;
806  if (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead))
807  {
808  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false);
809  await UpdateRevInfo().ConfigureAwait(false);
810  }
811  }
812  catch
813  {
814  doneSteps = 0;
815  numSteps = 2;
816 
817  // the stuff didn't make it into the db, forget what we've done and abort
818  await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false);
819  if (startReference != null && repo.Head != startSha)
820  await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
821  else
822  progressReporter(100);
823  throw;
824  }
825  }, cancellationToken).ConfigureAwait(false);
826 
827  api.ActiveJob = job.ToApi();
828  return Accepted(api);
829  }
string Name
The name of the User
Definition: User.cs:41
+
320  {
321  if (model == null)
322  throw new ArgumentNullException(nameof(model));
323 
324  if (model.AccessUser == null ^ model.AccessToken == null)
325  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchUserAndAccessToken));
326 
327  if (model.CheckoutSha != null && model.Reference != null)
328  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchShaAndReference));
329 
330  if (model.CheckoutSha != null && model.UpdateFromOrigin == true)
331  return BadRequest(new ErrorMessage(ErrorCode.RepoMismatchShaAndUpdate));
332 
333  if (model.NewTestMerges?.Any(x => model.NewTestMerges.Any(y => x != y && x.Number == y.Number)) == true)
334  return BadRequest(new ErrorMessage(ErrorCode.RepoDuplicateTestMerge));
335 
336  if (model.CommitterName?.Length == 0)
337  return BadRequest(new ErrorMessage(ErrorCode.RepoWhitespaceCommitterName));
338 
339  if (model.CommitterEmail?.Length == 0)
340  return BadRequest(new ErrorMessage(ErrorCode.RepoWhitespaceCommitterEmail));
341 
342  var newTestMerges = model.NewTestMerges != null && model.NewTestMerges.Count > 0;
343  var userRights = (RepositoryRights)AuthenticationContext.GetRight(RightsType.Repository);
344  if (newTestMerges && !userRights.HasFlag(RepositoryRights.MergePullRequest))
345  return Forbid();
346 
347  var currentModel = await DatabaseContext
349  .Where(x => x.InstanceId == Instance.Id)
350  .FirstOrDefaultAsync(cancellationToken)
351  .ConfigureAwait(false);
352 
353  if (currentModel == default)
354  return StatusCode((int)HttpStatusCode.Gone);
355 
356  bool CheckModified<T>(Expression<Func<Api.Models.Internal.RepositorySettings, T>> expression, RepositoryRights requiredRight)
357  {
358  var memberSelectorExpression = (MemberExpression)expression.Body;
359  var property = (PropertyInfo)memberSelectorExpression.Member;
360 
361  var newVal = property.GetValue(model);
362  if (newVal == null)
363  return false;
364  if (!userRights.HasFlag(requiredRight) && property.GetValue(currentModel) != newVal)
365  return true;
366 
367  property.SetValue(currentModel, newVal);
368  return false;
369  }
370 
371  if (CheckModified(x => x.AccessToken, RepositoryRights.ChangeCredentials)
372  || CheckModified(x => x.AccessUser, RepositoryRights.ChangeCredentials)
373  || CheckModified(x => x.AutoUpdatesKeepTestMerges, RepositoryRights.ChangeAutoUpdateSettings)
374  || CheckModified(x => x.AutoUpdatesSynchronize, RepositoryRights.ChangeAutoUpdateSettings)
375  || CheckModified(x => x.CommitterEmail, RepositoryRights.ChangeCommitter)
376  || CheckModified(x => x.CommitterName, RepositoryRights.ChangeCommitter)
377  || CheckModified(x => x.PushTestMergeCommits, RepositoryRights.ChangeTestMergeCommits)
378  || CheckModified(x => x.ShowTestMergeCommitters, RepositoryRights.ChangeTestMergeCommits)
379  || CheckModified(x => x.PostTestMergeComment, RepositoryRights.ChangeTestMergeCommits)
380  || (model.UpdateFromOrigin == true && !userRights.HasFlag(RepositoryRights.UpdateBranch)))
381  return Forbid();
382 
383  if (currentModel.AccessToken?.Length == 0 && currentModel.AccessUser?.Length == 0)
384  {
385  // setting an empty string clears everything
386  currentModel.AccessUser = null;
387  currentModel.AccessToken = null;
388  }
389 
390  var canRead = userRights.HasFlag(RepositoryRights.Read);
391 
392  var api = canRead ? currentModel.ToApi() : new Repository();
394 
395  if (canRead)
396  {
397  if (repoManager.CloneInProgress)
398  return Conflict(new ErrorMessage(ErrorCode.RepoCloning));
399 
400  if (repoManager.InUse)
401  return Conflict(new ErrorMessage(ErrorCode.RepoBusy));
402 
403  using var repo = await repoManager.LoadRepository(cancellationToken).ConfigureAwait(false);
404  if (repo == null)
405  return Conflict(new ErrorMessage(ErrorCode.RepoMissing));
406  await PopulateApi(api, repo, DatabaseContext, Instance, cancellationToken).ConfigureAwait(false);
407 
408  if (model.Origin != null && model.Origin != repo.Origin)
409  return BadRequest(new ErrorMessage(ErrorCode.RepoCantChangeOrigin));
410  }
411 
412  // this is just db stuf so stow it away
413  await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
414 
415  // format the job description
416  string description = null;
417  if (model.UpdateFromOrigin == true)
418  if (model.Reference != null)
419  description = String.Format(CultureInfo.InvariantCulture, "Fetch and hard reset repository to origin/{0}", model.Reference);
420  else if (model.CheckoutSha != null)
421  description = String.Format(CultureInfo.InvariantCulture, "Fetch and checkout {0} in repository", model.CheckoutSha);
422  else
423  description = "Pull current repository reference";
424  else if (model.Reference != null || model.CheckoutSha != null)
425  description = String.Format(CultureInfo.InvariantCulture, "Checkout repository {0} {1}", model.Reference != null ? "reference" : "SHA", model.Reference ?? model.CheckoutSha);
426 
427  if (newTestMerges)
428  description = String.Format(CultureInfo.InvariantCulture, "{0}est merge pull request(s) {1}{2}",
429  description != null ? String.Format(CultureInfo.InvariantCulture, "{0} and t", description) : "T",
430  String.Join(", ", model.NewTestMerges.Select(x =>
431  String.Format(CultureInfo.InvariantCulture, "#{0}{1}", x.Number,
432  x.PullRequestRevision != null ? String.Format(CultureInfo.InvariantCulture, " at {0}", x.PullRequestRevision.Substring(0, 7)) : String.Empty))),
433  description != null ? String.Empty : " in repository");
434 
435  if (description == null)
436  return Json(api); // no git changes
437 
438  var job = new Models.Job
439  {
440  Description = description,
441  StartedBy = AuthenticationContext.User,
442  Instance = Instance,
443  CancelRightsType = RightsType.Repository,
444  CancelRight = (ulong)RepositoryRights.CancelPendingChanges,
445  };
446 
447  // Time to access git, do it in a job
448  await jobManager.RegisterOperation(job, async (paramJob, databaseContextFactory, progressReporter, ct) =>
449  {
450  using var repo = await repoManager.LoadRepository(ct).ConfigureAwait(false);
451  if (repo == null)
452  throw new JobException(ErrorCode.RepoMissing);
453 
454  var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null;
455 
456  var startReference = repo.Reference;
457  var startSha = repo.Head;
458  string postUpdateSha = null;
459 
460  if (newTestMerges && !repo.IsGitHubRepository)
461  throw new JobException(ErrorCode.RepoUnsupportedTestMergeRemote);
462 
463  var committerName = currentModel.ShowTestMergeCommitters.Value
465  : currentModel.CommitterName;
466 
467  var hardResettingToOriginReference = model.UpdateFromOrigin == true && model.Reference != null;
468 
469  var numSteps = (model.NewTestMerges?.Count ?? 0) + (model.UpdateFromOrigin == true ? 1 : 0) + (!modelHasShaOrReference ? 2 : (hardResettingToOriginReference ? 3 : 1));
470  var doneSteps = 0;
471 
472  Action<int> NextProgressReporter()
473  {
474  var tmpDoneSteps = doneSteps;
475  ++doneSteps;
476  return progress => progressReporter((progress + (100 * tmpDoneSteps)) / numSteps);
477  }
478 
479  progressReporter(0);
480 
481  // get a base line for where we are
482  Models.RevisionInformation lastRevisionInfo = null;
483 
484  var attachedInstance = new Models.Instance
485  {
486  Id = Instance.Id
487  };
488 
489  Task CallLoadRevInfo(Models.TestMerge testMergeToAdd = null, string lastOriginCommitSha = null) => databaseContextFactory
490  .UseContext(
491  async databaseContext =>
492  {
493  databaseContext.Instances.Attach(attachedInstance);
494  var previousRevInfo = lastRevisionInfo;
495  var needsUpdate = await LoadRevisionInformation(
496  repo,
497  databaseContext,
498  attachedInstance,
499  lastOriginCommitSha,
500  x => lastRevisionInfo = x,
501  ct)
502  .ConfigureAwait(false);
503 
504  if (testMergeToAdd != null)
505  {
506  // rev info may have already loaded the user
507  var mergedBy = databaseContext.Users.Local.FirstOrDefault(x => x.Id == AuthenticationContext.User.Id);
508  if (mergedBy == default)
509  {
510  mergedBy = new Models.User
511  {
513  };
514 
515  databaseContext.Users.Attach(mergedBy);
516  }
517 
518  testMergeToAdd.MergedBy = mergedBy;
519 
520  lastRevisionInfo.ActiveTestMerges.AddRange(previousRevInfo.ActiveTestMerges);
521  lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
522  {
523  TestMerge = testMergeToAdd
524  });
525  lastRevisionInfo.PrimaryTestMerge = testMergeToAdd;
526 
527  needsUpdate = true;
528  }
529 
530  if (needsUpdate)
531  await databaseContext.Save(cancellationToken).ConfigureAwait(false);
532  });
533 
534  await CallLoadRevInfo().ConfigureAwait(false);
535 
536  // apply new rev info, tracking applied test merges
537  Task UpdateRevInfo(Models.TestMerge testMergeToAdd = null) => CallLoadRevInfo(testMergeToAdd, lastRevisionInfo.OriginCommitSha);
538 
539  try
540  {
541  // fetch/pull
542  if (model.UpdateFromOrigin == true)
543  {
544  if (!repo.Tracking)
545  throw new JobException(ErrorCode.RepoReferenceRequired);
546  await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter(), ct).ConfigureAwait(false);
547  doneSteps = 1;
548  if (!modelHasShaOrReference)
549  {
550  var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter(), ct).ConfigureAwait(false);
551  if (!fastForward.HasValue)
552  throw new JobException(ErrorCode.RepoMergeConflict);
553  await UpdateRevInfo().ConfigureAwait(false);
554  if (fastForward.Value)
555  {
556  lastRevisionInfo.OriginCommitSha = repo.Head;
557  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
558  postUpdateSha = repo.Head;
559  }
560  else
561  NextProgressReporter()(100);
562  }
563  }
564 
565  // checkout/hard reset
566  if (modelHasShaOrReference)
567  {
568  var validCheckoutSha =
569  model.CheckoutSha != null
570  && !repo.Head.StartsWith(model.CheckoutSha, StringComparison.OrdinalIgnoreCase);
571  var validCheckoutReference =
572  model.Reference != null
573  && !repo.Reference.Equals(model.Reference, StringComparison.OrdinalIgnoreCase);
574  if (validCheckoutSha || validCheckoutReference)
575  {
576  var committish = model.CheckoutSha ?? model.Reference;
577  var isSha = await repo.IsSha(committish, cancellationToken).ConfigureAwait(false);
578 
579  if ((isSha && model.Reference != null) || (!isSha && model.CheckoutSha != null))
580  throw new JobException(ErrorCode.RepoSwappedShaOrReference);
581 
582  await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false);
583  await CallLoadRevInfo().ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
584  }
585  else
586  NextProgressReporter()(100);
587 
588  if (hardResettingToOriginReference)
589  {
590  if (!repo.Tracking)
591  throw new JobException(ErrorCode.RepoReferenceNotTracking);
592  await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false);
593  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
594  await CallLoadRevInfo().ConfigureAwait(false);
595 
596  // repo head is on origin so force this
597  // will update the db if necessary
598  lastRevisionInfo.OriginCommitSha = repo.Head;
599  }
600  }
601 
602  // test merging
603  Dictionary<int, Octokit.PullRequest> prMap = null;
604  if (newTestMerges)
605  {
606  // bit of sanitization
607  foreach (var I in model.NewTestMerges.Where(x => String.IsNullOrWhiteSpace(x.PullRequestRevision)))
608  I.PullRequestRevision = null;
609 
610  var gitHubClient = currentModel.AccessToken != null
611  ? gitHubClientFactory.CreateClient(currentModel.AccessToken)
612  : (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken)
615 
616  var repoOwner = repo.GitHubOwner;
617  var repoName = repo.GitHubRepoName;
618 
619  // optimization: if we've already merged these exact same commits in this fashion before, just find the rev info for it and check it out
620  Models.RevisionInformation revInfoWereLookingFor = null;
621  bool needToApplyRemainingPrs = true;
622  if (lastRevisionInfo.OriginCommitSha == lastRevisionInfo.CommitSha)
623  {
624  // In order for this to work though we need the shas of all the commits
625  if (model.NewTestMerges.Any(x => x.PullRequestRevision == null))
626  prMap = new Dictionary<int, Octokit.PullRequest>();
627 
628  bool cantSearch = false;
629  foreach (var I in model.NewTestMerges)
630  {
631  if (I.PullRequestRevision != null)
632 #pragma warning disable CA1308 // Normalize strings to uppercase
633  I.PullRequestRevision = I.PullRequestRevision?.ToLowerInvariant(); // ala libgit2
634 #pragma warning restore CA1308 // Normalize strings to uppercase
635  else
636  try
637  {
638  // retrieve the latest sha
639  var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
640  prMap.Add(I.Number, pr);
641  I.PullRequestRevision = pr.Head.Sha;
642  }
643  catch
644  {
645  cantSearch = true;
646  break;
647  }
648  }
649 
650  if (!cantSearch)
651  {
652  List<Models.RevisionInformation> dbPull = null;
653 
654  await databaseContextFactory.UseContext(
655  async databaseContext =>
656  dbPull = await databaseContext.RevisionInformations
657  .Where(x => x.Instance.Id == Instance.Id
658  && x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
659  && x.ActiveTestMerges.Count <= model.NewTestMerges.Count
660  && x.ActiveTestMerges.Count > 0)
661  .Include(x => x.ActiveTestMerges)
662  .ThenInclude(x => x.TestMerge)
663  .ToListAsync(cancellationToken)
664  .ConfigureAwait(false))
665  .ConfigureAwait(false);
666 
667  // split here cause this bit has to be done locally
668  revInfoWereLookingFor = dbPull
669  .Where(x => x.ActiveTestMerges.Count == model.NewTestMerges.Count
670  && x.ActiveTestMerges.Select(y => y.TestMerge)
671  .All(y => model.NewTestMerges.Any(z =>
672  y.Number == z.Number
673  && y.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal)
674  && (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))))
675  .FirstOrDefault();
676 
677  if (revInfoWereLookingFor == default && model.NewTestMerges.Count > 1)
678  {
679  // okay try to add at least SOME prs we've seen before
680  var search = model.NewTestMerges.ToList();
681 
682  var appliedTestMergeIds = new List<long>();
683 
684  Models.RevisionInformation lastGoodRevInfo = null;
685  do
686  {
687  foreach (var I in search)
688  {
689  revInfoWereLookingFor = dbPull
690  .Where(x => model.NewTestMerges.Any(z =>
691  x.PrimaryTestMerge.Number == z.Number
692  && x.PrimaryTestMerge.PullRequestRevision.StartsWith(z.PullRequestRevision, StringComparison.Ordinal)
693  && (x.PrimaryTestMerge.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))
694  && x.ActiveTestMerges.Select(y => y.TestMerge).All(y => appliedTestMergeIds.Contains(y.Id)))
695  .FirstOrDefault();
696 
697  if (revInfoWereLookingFor != null)
698  {
699  lastGoodRevInfo = revInfoWereLookingFor;
700  appliedTestMergeIds.Add(revInfoWereLookingFor.PrimaryTestMerge.Id);
701  search.Remove(I);
702  break;
703  }
704  }
705  }
706  while (revInfoWereLookingFor != null && search.Count > 0);
707 
708  revInfoWereLookingFor = lastGoodRevInfo;
709  needToApplyRemainingPrs = search.Count != 0;
710  if (needToApplyRemainingPrs)
711  model.NewTestMerges = search;
712  }
713  else if (revInfoWereLookingFor != null)
714  needToApplyRemainingPrs = false;
715  }
716  }
717 
718  if (revInfoWereLookingFor != null)
719  {
720  // goteem
721  await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter(), cancellationToken).ConfigureAwait(false);
722  lastRevisionInfo = revInfoWereLookingFor;
723  }
724 
725  if (needToApplyRemainingPrs)
726  {
727  foreach (var I in model.NewTestMerges)
728  {
729  Octokit.PullRequest pr = null;
730  string errorMessage = null;
731 
732  if (lastRevisionInfo.ActiveTestMerges.Any(x => x.TestMerge.Number == I.Number))
733  throw new JobException(ErrorCode.RepoDuplicateTestMerge);
734 
735  Exception exception = null;
736  try
737  {
738  // load from cache if possible
739  if (prMap == null || !prMap.TryGetValue(I.Number, out pr))
740  pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, I.Number).ConfigureAwait(false);
741  }
742  catch (Octokit.RateLimitExceededException ex)
743  {
744  // you look at your anonymous access and sigh
745  errorMessage = "REMOTE API ERROR: RATE LIMITED";
746  exception = ex;
747  }
748  catch (Octokit.AuthorizationException ex)
749  {
750  errorMessage = "REMOTE API ERROR: BAD CREDENTIALS";
751  exception = ex;
752  }
753  catch (Octokit.NotFoundException ex)
754  {
755  // you look at your shithub and sigh
756  errorMessage = "REMOTE API ERROR: PULL REQUEST NOT FOUND";
757  exception = ex;
758  }
759 
760  if (exception != null)
761  Logger.LogWarning("Error retrieving pull request metadata: {0}", exception);
762 
763  // we want to take the earliest truth possible to prevent RCEs, if this fails AddTestMerge will set it
764  if (I.PullRequestRevision == null && pr != null)
765  I.PullRequestRevision = pr.Head.Sha;
766 
767  var mergeResult = await repo.AddTestMerge(
768  I,
769  committerName,
770  currentModel.CommitterEmail,
771  currentModel.AccessUser,
772  currentModel.AccessToken,
773  NextProgressReporter(),
774  ct).ConfigureAwait(false);
775 
776  if (!mergeResult.HasValue)
777  throw new JobException(
778  ErrorCode.RepoTestMergeConflict,
779  new JobException(
780  $"Merge of PR #{I.Number} at {I.PullRequestRevision.Substring(0, 7)} conflicted!"));
781 
782  ++doneSteps;
783 
784  // MergedBy will be set later
785  var tm = new Models.TestMerge
786  {
787  Author = pr?.User.Login ?? errorMessage,
788  BodyAtMerge = pr?.Body ?? errorMessage ?? String.Empty,
789  MergedAt = DateTimeOffset.Now,
790  TitleAtMerge = pr?.Title ?? errorMessage ?? String.Empty,
791  Comment = I.Comment,
792  Number = I.Number,
793  PullRequestRevision = I.PullRequestRevision,
794  Url = pr?.HtmlUrl ?? errorMessage
795  };
796 
797  await UpdateRevInfo(tm).ConfigureAwait(false);
798  }
799  }
800  }
801 
802  var currentHead = repo.Head;
803  if (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead))
804  {
805  await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false);
806  await UpdateRevInfo().ConfigureAwait(false);
807  }
808  }
809  catch
810  {
811  doneSteps = 0;
812  numSteps = 2;
813 
814  // Forget what we've done and abort
815  await repo.CheckoutObject(startReference ?? startSha, NextProgressReporter(), default).ConfigureAwait(false);
816  if (startReference != null && repo.Head != startSha)
817  await repo.ResetToSha(startSha, NextProgressReporter(), default).ConfigureAwait(false);
818  else
819  progressReporter(100);
820  throw;
821  }
822  }, cancellationToken).ConfigureAwait(false);
823 
824  api.ActiveJob = job.ToApi();
825  return Accepted(api);
826  }
string Name
The name of the User
Definition: User.cs:41
Task Save(CancellationToken cancellationToken)
Saves changes made to the IDatabaseContext
bool UpdateFromOrigin
Do the equivalent of a git pull. Will attempt to merge unless Reference is also specified in which ca...
Definition: Repository.cs:43
@@ -636,12 +636,12 @@ Here is the call graph for this function:
Many to many relationship for Models.RevisionInformation and Models.TestMerge
string CheckoutSha
The commit HEAD should point to. Not populated in responses, use RevisionInformation instead for retr...
Definition: Repository.cs:18
+
ICollection< TestMergeParameters > NewTestMerges
TestMergeParameters for new TestMerges. Note that merges that conflict will not be performed ...
Definition: Repository.cs:53
ulong GetRight(RightsType rightsType)
Get the value of a given rightsType
async Task< bool > PopulateApi(Repository model, Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, CancellationToken cancellationToken)
Backend abstract implementation of IDatabaseContext
readonly IInstanceManager instanceManager
The IInstanceManager for the RepositoryController
async Task< bool > LoadRevisionInformation(Components.Repository.IRepository repository, IDatabaseContext databaseContext, Models.Instance instance, string lastOriginCommitSha, Action< Models.RevisionInformation > revInfoSink, CancellationToken cancellationToken)
-
List< TestMergeParameters > NewTestMerges
TestMergeParameters for new TestMerges. Note that merges that conflict will not be performed ...
Definition: Repository.cs:53
string AccessUser
The username to access the git repository with
Operation exceptions thrown from the context of a Models.Job
Definition: JobException.cs:9
Represents a git repository
Definition: Repository.cs:8
diff --git a/class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html b/class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html index 4ca94a63c4..e198983b07 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html @@ -431,7 +431,7 @@ Here is the call graph for this function:
JsonConverter for serializing Versions for BYOND.
- +
string GetFileNameWithoutExtension(string path)
Gets the file name portion of a path with
GeneralConfiguration GeneralConfiguration
The Configuration.GeneralConfiguration.
diff --git a/class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html b/class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html index de8f946406..1cfaef7533 100644 --- a/class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html +++ b/class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html @@ -333,15 +333,15 @@ Private Attributes
Returns
A Task representing the running operation.
-

Definition at line 770 of file SetupWizard.cs.

-
771  {
772  var setupWizardMode = generalConfiguration.SetupWizardMode;
773  if (setupWizardMode == SetupWizardMode.Never)
774  return;
775 
776  var forceRun = setupWizardMode == SetupWizardMode.Force || setupWizardMode == SetupWizardMode.Only;
777  if (!console.Available)
778  {
779  if (forceRun)
780  throw new InvalidOperationException("Asked to run setup wizard with no console avaliable!");
781  return;
782  }
783 
784  var userConfigFileName = String.Format(CultureInfo.InvariantCulture, "appsettings.{0}.json", hostingEnvironment.EnvironmentName);
785 
786  async Task HandleSetupCancel()
787  {
788  await console.WriteAsync(String.Empty, true, default).ConfigureAwait(false);
789  await console.WriteAsync("Aborting setup!", true, default).ConfigureAwait(false);
790  }
791 
792  // Link passed cancellationToken with cancel key press
793  Task finalTask = Task.CompletedTask;
794  using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, console.CancelKeyPress))
795  using ((cancellationToken = cts.Token).Register(() => finalTask = HandleSetupCancel()))
796  try
797  {
798  var exists = await ioManager.FileExists(userConfigFileName, cancellationToken).ConfigureAwait(false);
799 
800  bool shouldRunBasedOnAutodetect;
801  if (exists)
802  {
803  var bytes = await ioManager.ReadAllBytes(userConfigFileName, cancellationToken).ConfigureAwait(false);
804  var contents = Encoding.UTF8.GetString(bytes);
805  var existingConfigIsEmpty = String.IsNullOrWhiteSpace(contents) || contents.Trim() == "{}";
806  shouldRunBasedOnAutodetect = existingConfigIsEmpty;
807  }
808  else
809  shouldRunBasedOnAutodetect = true;
810 
811  if (!shouldRunBasedOnAutodetect)
812  {
813  if (forceRun)
814  {
815  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "The configuration settings are requesting the setup wizard be run, but you already appear to have a configuration file ({0})!", userConfigFileName), true, cancellationToken).ConfigureAwait(false);
816 
817  forceRun = await PromptYesNo("Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(false);
818  }
819 
820  if (!forceRun)
821  return;
822  }
823 
824  // flush the logs to prevent console conflicts
825  await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
826 
827  await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(false);
828  }
829  finally
830  {
831  await finalTask.ConfigureAwait(false);
832  }
833  }
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the SetupWizard
Definition: SetupWizard.cs:62
+

Definition at line 767 of file SetupWizard.cs.

+
768  {
769  var setupWizardMode = generalConfiguration.SetupWizardMode;
770  if (setupWizardMode == SetupWizardMode.Never)
771  return;
772 
773  var forceRun = setupWizardMode == SetupWizardMode.Force || setupWizardMode == SetupWizardMode.Only;
774  if (!console.Available)
775  {
776  if (forceRun)
777  throw new InvalidOperationException("Asked to run setup wizard with no console avaliable!");
778  return;
779  }
780 
781  var userConfigFileName = String.Format(CultureInfo.InvariantCulture, "appsettings.{0}.json", hostingEnvironment.EnvironmentName);
782 
783  async Task HandleSetupCancel()
784  {
785  await console.WriteAsync(String.Empty, true, default).ConfigureAwait(false);
786  await console.WriteAsync("Aborting setup!", true, default).ConfigureAwait(false);
787  }
788 
789  // Link passed cancellationToken with cancel key press
790  Task finalTask = Task.CompletedTask;
791  using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, console.CancelKeyPress))
792  using ((cancellationToken = cts.Token).Register(() => finalTask = HandleSetupCancel()))
793  try
794  {
795  var exists = await ioManager.FileExists(userConfigFileName, cancellationToken).ConfigureAwait(false);
796 
797  bool shouldRunBasedOnAutodetect;
798  if (exists)
799  {
800  var bytes = await ioManager.ReadAllBytes(userConfigFileName, cancellationToken).ConfigureAwait(false);
801  var contents = Encoding.UTF8.GetString(bytes);
802  var existingConfigIsEmpty = String.IsNullOrWhiteSpace(contents) || contents.Trim() == "{}";
803  shouldRunBasedOnAutodetect = existingConfigIsEmpty;
804  }
805  else
806  shouldRunBasedOnAutodetect = true;
807 
808  if (!shouldRunBasedOnAutodetect)
809  {
810  if (forceRun)
811  {
812  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "The configuration settings are requesting the setup wizard be run, but you already appear to have a configuration file ({0})!", userConfigFileName), true, cancellationToken).ConfigureAwait(false);
813 
814  forceRun = await PromptYesNo("Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(false);
815  }
816 
817  if (!forceRun)
818  return;
819  }
820 
821  // flush the logs to prevent console conflicts
822  await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
823 
824  await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(false);
825  }
826  finally
827  {
828  await finalTask.ConfigureAwait(false);
829  }
830  }
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the SetupWizard
Definition: SetupWizard.cs:62
readonly IIOManager ioManager
The IIOManager for the SetupWizard
Definition: SetupWizard.cs:32
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the SetupWizard
Definition: SetupWizard.cs:72
SetupWizardMode
Determines if the Setup.SetupWizard will run
bool Available
If the IConsole is visible to the user
Definition: IConsole.cs:14
CancellationToken CancelKeyPress
Gets a CancellationToken that triggers if Crtl+C or an equivalent is pressed.
Definition: IConsole.cs:19
Task< bool > FileExists(string path, CancellationToken cancellationToken)
Check that the file at path exists
-
async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken)
Runs the SetupWizard
Definition: SetupWizard.cs:744
+
async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken)
Runs the SetupWizard
Definition: SetupWizard.cs:741
Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken)
Create a Task that completes after a given timeSpan
Task WriteAsync(string text, bool newLine, CancellationToken cancellationToken)
Write some text to the IConsole
@@ -383,10 +383,10 @@ Private Attributes
Returns
A Task<TResult> resulting in the new ControlPanelConfiguration
-

Definition at line 656 of file SetupWizard.cs.

+

Definition at line 653 of file SetupWizard.cs.

References Tgstation.Server.Host.Configuration.ControlPanelConfiguration.AllowedOrigins.

-
657  {
658  var config = new ControlPanelConfiguration
659  {
660  Enable = await PromptYesNo("Enable the web control panel (Incomplete)? (y/n): ", cancellationToken).ConfigureAwait(false),
661  AllowAnyOrigin = await PromptYesNo("Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(false)
662  };
663 
664  if (!config.AllowAnyOrigin)
665  {
666  await console.WriteAsync("Enter a comma seperated list of CORS allowed origins (optional): ", false, cancellationToken).ConfigureAwait(false);
667  var commaSeperatedOrigins = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
668  if (!String.IsNullOrWhiteSpace(commaSeperatedOrigins))
669  {
670  var splits = commaSeperatedOrigins.Split(',');
671  config.AllowedOrigins = new List<string>(splits.Select(x => x.Trim()));
672  }
673  }
674 
675  return config;
676  }
Task< string > ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken)
Read a line from the IConsole
+
654  {
655  var config = new ControlPanelConfiguration
656  {
657  Enable = await PromptYesNo("Enable the web control panel (Incomplete)? (y/n): ", cancellationToken).ConfigureAwait(false),
658  AllowAnyOrigin = await PromptYesNo("Allow web control panels hosted elsewhere to access the server? (Access-Control-Allow-Origin: *) (y/n): ", cancellationToken).ConfigureAwait(false)
659  };
660 
661  if (!config.AllowAnyOrigin)
662  {
663  await console.WriteAsync("Enter a comma seperated list of CORS allowed origins (optional): ", false, cancellationToken).ConfigureAwait(false);
664  var commaSeperatedOrigins = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
665  if (!String.IsNullOrWhiteSpace(commaSeperatedOrigins))
666  {
667  var splits = commaSeperatedOrigins.Split(',');
668  config.AllowedOrigins = new List<string>(splits.Select(x => x.Trim()));
669  }
670  }
671 
672  return config;
673  }
Task< string > ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken)
Read a line from the IConsole
Task WriteAsync(string text, bool newLine, CancellationToken cancellationToken)
Write some text to the IConsole
async Task< bool > PromptYesNo(string question, CancellationToken cancellationToken)
A prompt for a yes or no value
Definition: SetupWizard.cs:114
@@ -483,10 +483,9 @@ Private Attributes

Definition at line 511 of file SetupWizard.cs.

References Tgstation.Server.Api.Models.Internal.ServerInformation.MinimumPasswordLength.

-
512  {
513  var newGeneralConfiguration = new GeneralConfiguration
514  {
516  };
517 
518  do
519  {
520  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
521  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Minimum database user password length (leave blank for default of {0}): ", newGeneralConfiguration.MinimumPasswordLength), false, cancellationToken).ConfigureAwait(false);
522  var passwordLengthString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
523  if (String.IsNullOrWhiteSpace(passwordLengthString))
524  break;
525  if (UInt32.TryParse(passwordLengthString, out var passwordLength) && passwordLength >= 0)
526  {
527  newGeneralConfiguration.MinimumPasswordLength = passwordLength;
528  break;
529  }
530 
531  await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
532  }
533  while (true);
534 
535  do
536  {
537  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
538  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Timeout for sending and receiving BYOND topics (ms, 0 for infinite, leave blank for default of {0}): ", newGeneralConfiguration.ByondTopicTimeout), false, cancellationToken).ConfigureAwait(false);
539  var topicTimeoutString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
540  if (String.IsNullOrWhiteSpace(topicTimeoutString))
541  break;
542  if (Int32.TryParse(topicTimeoutString, out var topicTimeout) && topicTimeout >= 0)
543  {
544  newGeneralConfiguration.ByondTopicTimeout = topicTimeout;
545  break;
546  }
547 
548  await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
549  }
550  while (true);
551 
552  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
553  await console.WriteAsync("Enter a GitHub personal access token to bypass some rate limits (this is optional and does not require any scopes)", true, cancellationToken).ConfigureAwait(false);
554  await console.WriteAsync("GitHub personal access token: ", false, cancellationToken).ConfigureAwait(false);
555  newGeneralConfiguration.GitHubAccessToken = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false);
556  if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
557  newGeneralConfiguration.GitHubAccessToken = null;
558 
559  newGeneralConfiguration.UseExperimentalWatchdog = await PromptYesNo("Use the experimental watchdog (NOT RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false);
560 
561  newGeneralConfiguration.UseBasicWatchdogOnWindows = true;
562 
563  return newGeneralConfiguration;
564  }
SetupWizardMode
Determines if the Setup.SetupWizard will run
+
512  {
513  var newGeneralConfiguration = new GeneralConfiguration
514  {
516  };
517 
518  do
519  {
520  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
521  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Minimum database user password length (leave blank for default of {0}): ", newGeneralConfiguration.MinimumPasswordLength), false, cancellationToken).ConfigureAwait(false);
522  var passwordLengthString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
523  if (String.IsNullOrWhiteSpace(passwordLengthString))
524  break;
525  if (UInt32.TryParse(passwordLengthString, out var passwordLength) && passwordLength >= 0)
526  {
527  newGeneralConfiguration.MinimumPasswordLength = passwordLength;
528  break;
529  }
530 
531  await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
532  }
533  while (true);
534 
535  do
536  {
537  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
538  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Timeout for sending and receiving BYOND topics (ms, 0 for infinite, leave blank for default of {0}): ", newGeneralConfiguration.ByondTopicTimeout), false, cancellationToken).ConfigureAwait(false);
539  var topicTimeoutString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
540  if (String.IsNullOrWhiteSpace(topicTimeoutString))
541  break;
542  if (Int32.TryParse(topicTimeoutString, out var topicTimeout) && topicTimeout >= 0)
543  {
544  newGeneralConfiguration.ByondTopicTimeout = topicTimeout;
545  break;
546  }
547 
548  await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
549  }
550  while (true);
551 
552  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
553  await console.WriteAsync("Enter a GitHub personal access token to bypass some rate limits (this is optional and does not require any scopes)", true, cancellationToken).ConfigureAwait(false);
554  await console.WriteAsync("GitHub personal access token: ", false, cancellationToken).ConfigureAwait(false);
555  newGeneralConfiguration.GitHubAccessToken = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false);
556  if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
557  newGeneralConfiguration.GitHubAccessToken = null;
558 
559  // newGeneralConfiguration.UseExperimentalWatchdog = await PromptYesNo("Use the experimental watchdog (NOT RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false);
560  return newGeneralConfiguration;
561  }
SetupWizardMode
Determines if the Setup.SetupWizard will run
Task< string > ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken)
Read a line from the IConsole
Task WriteAsync(string text, bool newLine, CancellationToken cancellationToken)
Write some text to the IConsole
-
async Task< bool > PromptYesNo(string question, CancellationToken cancellationToken)
A prompt for a yes or no value
Definition: SetupWizard.cs:114
readonly IConsole console
The IConsole for the SetupWizard
Definition: SetupWizard.cs:37
@@ -523,8 +522,8 @@ Private Attributes
Returns
A Task<TResult> resulting in the new FileLoggingConfiguration
-

Definition at line 571 of file SetupWizard.cs.

-
572  {
573  var fileLoggingConfiguration = new FileLoggingConfiguration();
574  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
575  fileLoggingConfiguration.Disable = !await PromptYesNo("Enable file logging? (y/n): ", cancellationToken).ConfigureAwait(false);
576 
577  if (!fileLoggingConfiguration.Disable)
578  {
579  do
580  {
581  await console.WriteAsync("Log file directory path (leave blank for default): ", false, cancellationToken).ConfigureAwait(false);
582  fileLoggingConfiguration.Directory = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
583  if (String.IsNullOrWhiteSpace(fileLoggingConfiguration.Directory))
584  {
585  fileLoggingConfiguration.Directory = null;
586  break;
587  }
588 
589  // test a write of it
590  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
591  await console.WriteAsync("Testing directory access...", true, cancellationToken).ConfigureAwait(false);
592  try
593  {
594  await ioManager.CreateDirectory(fileLoggingConfiguration.Directory, cancellationToken).ConfigureAwait(false);
595  var testFile = ioManager.ConcatPath(fileLoggingConfiguration.Directory, String.Format(CultureInfo.InvariantCulture, "WizardAccesTest.{0}.deleteme", Guid.NewGuid()));
596  await ioManager.WriteAllBytes(testFile, Array.Empty<byte>(), cancellationToken).ConfigureAwait(false);
597  try
598  {
599  await ioManager.DeleteFile(testFile, cancellationToken).ConfigureAwait(false);
600  }
601  catch (OperationCanceledException)
602  {
603  throw;
604  }
605  catch (Exception e)
606  {
607  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Error deleting test log file: {0}", testFile), true, cancellationToken).ConfigureAwait(false);
608  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
609  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
610  }
611 
612  break;
613  }
614  catch (OperationCanceledException)
615  {
616  throw;
617  }
618  catch (Exception e)
619  {
620  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
621  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
622  await console.WriteAsync("Please verify the path is valid and you have access to it!", true, cancellationToken).ConfigureAwait(false);
623  }
624  }
625  while (true);
626 
627  async Task<LogLevel?> PromptLogLevel(string question)
628  {
629  do
630  {
631  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
632  await console.WriteAsync(question, true, cancellationToken).ConfigureAwait(false);
633  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ", nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false);
634  var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
635  if (String.IsNullOrWhiteSpace(responseString))
636  return null;
637  if (Enum.TryParse<LogLevel>(responseString, out var logLevel) && logLevel != LogLevel.None)
638  return logLevel;
639  await console.WriteAsync("Invalid log level!", true, cancellationToken).ConfigureAwait(false);
640  }
641  while (true);
642  }
643 
644  fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.LogLevel;
645  fileLoggingConfiguration.MicrosoftLogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for Microsoft logs (VERY verbose, default {0}).", fileLoggingConfiguration.MicrosoftLogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.MicrosoftLogLevel;
646  }
647 
648  return fileLoggingConfiguration;
649  }
Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content
+

Definition at line 568 of file SetupWizard.cs.

+
569  {
570  var fileLoggingConfiguration = new FileLoggingConfiguration();
571  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
572  fileLoggingConfiguration.Disable = !await PromptYesNo("Enable file logging? (y/n): ", cancellationToken).ConfigureAwait(false);
573 
574  if (!fileLoggingConfiguration.Disable)
575  {
576  do
577  {
578  await console.WriteAsync("Log file directory path (leave blank for default): ", false, cancellationToken).ConfigureAwait(false);
579  fileLoggingConfiguration.Directory = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
580  if (String.IsNullOrWhiteSpace(fileLoggingConfiguration.Directory))
581  {
582  fileLoggingConfiguration.Directory = null;
583  break;
584  }
585 
586  // test a write of it
587  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
588  await console.WriteAsync("Testing directory access...", true, cancellationToken).ConfigureAwait(false);
589  try
590  {
591  await ioManager.CreateDirectory(fileLoggingConfiguration.Directory, cancellationToken).ConfigureAwait(false);
592  var testFile = ioManager.ConcatPath(fileLoggingConfiguration.Directory, String.Format(CultureInfo.InvariantCulture, "WizardAccesTest.{0}.deleteme", Guid.NewGuid()));
593  await ioManager.WriteAllBytes(testFile, Array.Empty<byte>(), cancellationToken).ConfigureAwait(false);
594  try
595  {
596  await ioManager.DeleteFile(testFile, cancellationToken).ConfigureAwait(false);
597  }
598  catch (OperationCanceledException)
599  {
600  throw;
601  }
602  catch (Exception e)
603  {
604  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Error deleting test log file: {0}", testFile), true, cancellationToken).ConfigureAwait(false);
605  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
606  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
607  }
608 
609  break;
610  }
611  catch (OperationCanceledException)
612  {
613  throw;
614  }
615  catch (Exception e)
616  {
617  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
618  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
619  await console.WriteAsync("Please verify the path is valid and you have access to it!", true, cancellationToken).ConfigureAwait(false);
620  }
621  }
622  while (true);
623 
624  async Task<LogLevel?> PromptLogLevel(string question)
625  {
626  do
627  {
628  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
629  await console.WriteAsync(question, true, cancellationToken).ConfigureAwait(false);
630  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ", nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false);
631  var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
632  if (String.IsNullOrWhiteSpace(responseString))
633  return null;
634  if (Enum.TryParse<LogLevel>(responseString, out var logLevel) && logLevel != LogLevel.None)
635  return logLevel;
636  await console.WriteAsync("Invalid log level!", true, cancellationToken).ConfigureAwait(false);
637  }
638  while (true);
639  }
640 
641  fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.LogLevel;
642  fileLoggingConfiguration.MicrosoftLogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for Microsoft logs (VERY verbose, default {0}).", fileLoggingConfiguration.MicrosoftLogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.MicrosoftLogLevel;
643  }
644 
645  return fileLoggingConfiguration;
646  }
Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content
readonly IIOManager ioManager
The IIOManager for the SetupWizard
Definition: SetupWizard.cs:32
Task CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path
Task< string > ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken)
Read a line from the IConsole
@@ -707,15 +706,15 @@ Private Attributes
Returns
A Task representing the running operation
-

Definition at line 744 of file SetupWizard.cs.

-
745  {
746  // welcome message
747  await console.WriteAsync("Welcome to tgstation-server 4!", true, cancellationToken).ConfigureAwait(false);
748  await console.WriteAsync("This wizard will help you configure your server.", true, cancellationToken).ConfigureAwait(false);
749 
750  var hostingPort = await PromptForHostingPort(cancellationToken).ConfigureAwait(false);
751 
752  var databaseConfiguration = await ConfigureDatabase(cancellationToken).ConfigureAwait(false);
753 
754  var newGeneralConfiguration = await ConfigureGeneral(cancellationToken).ConfigureAwait(false);
755 
756  var fileLoggingConfiguration = await ConfigureLogging(cancellationToken).ConfigureAwait(false);
757 
758  var controlPanelConfiguration = await ConfigureControlPanel(cancellationToken).ConfigureAwait(false);
759 
760  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
761 
762  await SaveConfiguration(userConfigFileName, hostingPort, databaseConfiguration, newGeneralConfiguration, fileLoggingConfiguration, controlPanelConfiguration, cancellationToken).ConfigureAwait(false);
763  }
async Task< GeneralConfiguration > ConfigureGeneral(CancellationToken cancellationToken)
Prompts the user to create a GeneralConfiguration
Definition: SetupWizard.cs:511
+

Definition at line 741 of file SetupWizard.cs.

+
742  {
743  // welcome message
744  await console.WriteAsync("Welcome to tgstation-server 4!", true, cancellationToken).ConfigureAwait(false);
745  await console.WriteAsync("This wizard will help you configure your server.", true, cancellationToken).ConfigureAwait(false);
746 
747  var hostingPort = await PromptForHostingPort(cancellationToken).ConfigureAwait(false);
748 
749  var databaseConfiguration = await ConfigureDatabase(cancellationToken).ConfigureAwait(false);
750 
751  var newGeneralConfiguration = await ConfigureGeneral(cancellationToken).ConfigureAwait(false);
752 
753  var fileLoggingConfiguration = await ConfigureLogging(cancellationToken).ConfigureAwait(false);
754 
755  var controlPanelConfiguration = await ConfigureControlPanel(cancellationToken).ConfigureAwait(false);
756 
757  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
758 
759  await SaveConfiguration(userConfigFileName, hostingPort, databaseConfiguration, newGeneralConfiguration, fileLoggingConfiguration, controlPanelConfiguration, cancellationToken).ConfigureAwait(false);
760  }
async Task< GeneralConfiguration > ConfigureGeneral(CancellationToken cancellationToken)
Prompts the user to create a GeneralConfiguration
Definition: SetupWizard.cs:511
async Task< ushort?> PromptForHostingPort(CancellationToken cancellationToken)
Prompts the user to enter the port to host TGS on
Definition: SetupWizard.cs:135
async Task< DatabaseConfiguration > ConfigureDatabase(CancellationToken cancellationToken)
Prompts the user to create a DatabaseConfiguration
Definition: SetupWizard.cs:335
Task WriteAsync(string text, bool newLine, CancellationToken cancellationToken)
Write some text to the IConsole
-
async Task< ControlPanelConfiguration > ConfigureControlPanel(CancellationToken cancellationToken)
Prompts the user to create a ControlPanelConfiguration
Definition: SetupWizard.cs:656
+
async Task< ControlPanelConfiguration > ConfigureControlPanel(CancellationToken cancellationToken)
Prompts the user to create a ControlPanelConfiguration
Definition: SetupWizard.cs:653
readonly IConsole console
The IConsole for the SetupWizard
Definition: SetupWizard.cs:37
-
async Task< FileLoggingConfiguration > ConfigureLogging(CancellationToken cancellationToken)
Prompts the user to create a FileLoggingConfiguration
Definition: SetupWizard.cs:571
-
async Task SaveConfiguration(string userConfigFileName, ushort?hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, ControlPanelConfiguration controlPanelConfiguration, CancellationToken cancellationToken)
Saves a given Configuration set to userConfigFileName
Definition: SetupWizard.cs:689
+
async Task< FileLoggingConfiguration > ConfigureLogging(CancellationToken cancellationToken)
Prompts the user to create a FileLoggingConfiguration
Definition: SetupWizard.cs:568
+
async Task SaveConfiguration(string userConfigFileName, ushort?hostingPort, DatabaseConfiguration databaseConfiguration, GeneralConfiguration newGeneralConfiguration, FileLoggingConfiguration fileLoggingConfiguration, ControlPanelConfiguration controlPanelConfiguration, CancellationToken cancellationToken)
Saves a given Configuration set to userConfigFileName
Definition: SetupWizard.cs:686
@@ -796,10 +795,10 @@ Private Attributes
Returns
A Task representing the running operation
-

Definition at line 689 of file SetupWizard.cs.

+

Definition at line 686 of file SetupWizard.cs.

References Tgstation.Server.Host.Configuration.ControlPanelConfiguration.Section, Tgstation.Server.Host.Configuration.DatabaseConfiguration.Section, Tgstation.Server.Host.Configuration.FileLoggingConfiguration.Section, and Tgstation.Server.Host.Configuration.GeneralConfiguration.Section.

-
690  {
691  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Configuration complete! Saving to {0}", userConfigFileName), true, cancellationToken).ConfigureAwait(false);
692 
693  var map = new Dictionary<string, object>()
694  {
695  { DatabaseConfiguration.Section, databaseConfiguration },
696  { GeneralConfiguration.Section, newGeneralConfiguration },
697  { FileLoggingConfiguration.Section, fileLoggingConfiguration },
698  { ControlPanelConfiguration.Section, controlPanelConfiguration }
699  };
700 
701  if (hostingPort.HasValue)
702  map.Add("Kestrel", new
703  {
704  EndPoints = new
705  {
706  Http = new
707  {
708  Url = String.Format(CultureInfo.InvariantCulture, "http://0.0.0.0:{0}", hostingPort)
709  }
710  }
711  });
712 
713  var json = JsonConvert.SerializeObject(map, Formatting.Indented);
714  var configBytes = Encoding.UTF8.GetBytes(json);
715 
716  try
717  {
718  await ioManager.WriteAllBytes(userConfigFileName, configBytes, cancellationToken).ConfigureAwait(false);
719  }
720  catch (OperationCanceledException)
721  {
722  throw;
723  }
724  catch (Exception e)
725  {
726  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
727  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
728  await console.WriteAsync("For your convienence, here's the json we tried to write out:", true, cancellationToken).ConfigureAwait(false);
729  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
730  await console.WriteAsync(json, true, cancellationToken).ConfigureAwait(false);
731  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
732  await console.WriteAsync("Press any key to exit...", true, cancellationToken).ConfigureAwait(false);
733  await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false);
734  throw new OperationCanceledException();
735  }
736  }
Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content
+
687  {
688  await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Configuration complete! Saving to {0}", userConfigFileName), true, cancellationToken).ConfigureAwait(false);
689 
690  var map = new Dictionary<string, object>()
691  {
692  { DatabaseConfiguration.Section, databaseConfiguration },
693  { GeneralConfiguration.Section, newGeneralConfiguration },
694  { FileLoggingConfiguration.Section, fileLoggingConfiguration },
695  { ControlPanelConfiguration.Section, controlPanelConfiguration }
696  };
697 
698  if (hostingPort.HasValue)
699  map.Add("Kestrel", new
700  {
701  EndPoints = new
702  {
703  Http = new
704  {
705  Url = String.Format(CultureInfo.InvariantCulture, "http://0.0.0.0:{0}", hostingPort)
706  }
707  }
708  });
709 
710  var json = JsonConvert.SerializeObject(map, Formatting.Indented);
711  var configBytes = Encoding.UTF8.GetBytes(json);
712 
713  try
714  {
715  await ioManager.WriteAllBytes(userConfigFileName, configBytes, cancellationToken).ConfigureAwait(false);
716  }
717  catch (OperationCanceledException)
718  {
719  throw;
720  }
721  catch (Exception e)
722  {
723  await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
724  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
725  await console.WriteAsync("For your convienence, here's the json we tried to write out:", true, cancellationToken).ConfigureAwait(false);
726  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
727  await console.WriteAsync(json, true, cancellationToken).ConfigureAwait(false);
728  await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
729  await console.WriteAsync("Press any key to exit...", true, cancellationToken).ConfigureAwait(false);
730  await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false);
731  throw new OperationCanceledException();
732  }
733  }
Task WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content
readonly IIOManager ioManager
The IIOManager for the SetupWizard
Definition: SetupWizard.cs:32
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the GeneralConfiguration res...
const string Section
The key for the Microsoft.Extensions.Configuration.IConfigurationSection the DatabaseConfiguration re...
@@ -831,9 +830,9 @@ Private Attributes

-

Definition at line 836 of file SetupWizard.cs.

-
837  {
838  await CheckRunWizard(cancellationToken).ConfigureAwait(false);
839  applicationLifetime.StopApplication();
840  }
readonly IHostApplicationLifetime applicationLifetime
The IHostApplicationLifetime for the SetupWizard.
Definition: SetupWizard.cs:67
-
async Task CheckRunWizard(CancellationToken cancellationToken)
Check if it should and run the SetupWizard if necessary.
Definition: SetupWizard.cs:770
+

Definition at line 833 of file SetupWizard.cs.

+
834  {
835  await CheckRunWizard(cancellationToken).ConfigureAwait(false);
836  applicationLifetime.StopApplication();
837  }
readonly IHostApplicationLifetime applicationLifetime
The IHostApplicationLifetime for the SetupWizard.
Definition: SetupWizard.cs:67
+
async Task CheckRunWizard(CancellationToken cancellationToken)
Check if it should and run the SetupWizard if necessary.
Definition: SetupWizard.cs:767
diff --git a/functions.html b/functions.html index 0e5c4a3b97..9361705db8 100644 --- a/functions.html +++ b/functions.html @@ -241,7 +241,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Api.Models.Internal.User
  • AdminName -: Tgstation.Server.Api.Models.User +: Tgstation.Server.Api.Models.User
  • AdminOnly : Tgstation.Server.Host.Components.Chat.Commands.ByondCommand @@ -293,7 +293,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Client.AdministrationClient
  • ApiClient() -: Tgstation.Server.Client.ApiClient +: Tgstation.Server.Client.ApiClient
  • apiClient : Tgstation.Server.Client.Components.ByondClient @@ -313,7 +313,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Client.ServerClientFactory
  • ApiConflictException() -: Tgstation.Server.Client.ApiConflictException +: Tgstation.Server.Client.ApiConflictException
  • ApiController() : Tgstation.Server.Host.Controllers.ApiController diff --git a/functions_c.html b/functions_c.html index 0a35600841..9a0a1fdd0b 100644 --- a/functions_c.html +++ b/functions_c.html @@ -680,8 +680,8 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Client.IApiClient
  • CreateApiClient() -: Tgstation.Server.Client.ApiClientFactory -, Tgstation.Server.Client.IApiClientFactory +: Tgstation.Server.Client.ApiClientFactory +, Tgstation.Server.Client.IApiClientFactory
  • CreateAuthenticationContext() : Tgstation.Server.Host.Security.AuthenticationContextFactory @@ -742,10 +742,18 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • CreateFromExistingHandle() : Tgstation.Server.Host.System.ProcessExecutor
  • +
  • CreateFromLogin() +: Tgstation.Server.Client.IServerClientFactory +, Tgstation.Server.Client.ServerClientFactory +
  • CreateFromPath() : Tgstation.Server.Host.Components.Repository.ILibGit2RepositoryFactory , Tgstation.Server.Host.Components.Repository.LibGit2RepositoryFactory
  • +
  • CreateFromToken() +: Tgstation.Server.Client.IServerClientFactory +, Tgstation.Server.Client.ServerClientFactory +
  • CreateInMemory() : Tgstation.Server.Host.Components.Repository.ILibGit2RepositoryFactory , Tgstation.Server.Host.Components.Repository.LibGit2RepositoryFactory @@ -778,10 +786,6 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.IServerFactory , Tgstation.Server.Host.ServerFactory
  • -
  • CreateServerClient() -: Tgstation.Server.Client.IServerClientFactory -, Tgstation.Server.Client.ServerClientFactory -
  • CreateSymbolicLink() : Tgstation.Server.Host.IO.ISymlinkFactory , Tgstation.Server.Host.IO.PosixSymlinkFactory diff --git a/functions_d.html b/functions_d.html index 27e6e7e83b..0650ea4025 100644 --- a/functions_d.html +++ b/functions_d.html @@ -189,7 +189,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Service.Program
  • DefaultAdminPassword -: Tgstation.Server.Api.Models.User +: Tgstation.Server.Api.Models.User
  • DefaultBufferSize : Tgstation.Server.Host.IO.DefaultIOManager diff --git a/functions_e.html b/functions_e.html index ddc4ff6d34..c06f626e56 100644 --- a/functions_e.html +++ b/functions_e.html @@ -170,7 +170,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Jobs.JobException
  • ErrorMessage() -: Tgstation.Server.Api.Models.ErrorMessage +: Tgstation.Server.Api.Models.ErrorMessage , Tgstation.Server.Host.Components.Interop.DMApiResponse
  • errorStringBuilder diff --git a/functions_func.html b/functions_func.html index 478921669c..f496b66a74 100644 --- a/functions_func.html +++ b/functions_func.html @@ -164,10 +164,10 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Api.Rights.RightsHelper
  • ApiClient() -: Tgstation.Server.Client.ApiClient +: Tgstation.Server.Client.ApiClient
  • ApiConflictException() -: Tgstation.Server.Client.ApiConflictException +: Tgstation.Server.Client.ApiConflictException
  • ApiController() : Tgstation.Server.Host.Controllers.ApiController @@ -176,7 +176,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Client.ApiException
  • ApiHeaders() -: Tgstation.Server.Api.ApiHeaders +: Tgstation.Server.Api.ApiHeaders
  • Application() : Tgstation.Server.Host.Core.Application diff --git a/functions_func_c.html b/functions_func_c.html index 777e88b367..f5c5bd7dd6 100644 --- a/functions_func_c.html +++ b/functions_func_c.html @@ -348,7 +348,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Setup.SetupApplication
  • ConflictException() -: Tgstation.Server.Client.ConflictException +: Tgstation.Server.Client.ConflictException
  • Connect() : Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider @@ -406,8 +406,8 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Client.IApiClient
  • CreateApiClient() -: Tgstation.Server.Client.ApiClientFactory -, Tgstation.Server.Client.IApiClientFactory +: Tgstation.Server.Client.ApiClientFactory +, Tgstation.Server.Client.IApiClientFactory
  • CreateAuthenticationContext() : Tgstation.Server.Host.Security.AuthenticationContextFactory @@ -458,10 +458,18 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • CreateFromExistingHandle() : Tgstation.Server.Host.System.ProcessExecutor
  • +
  • CreateFromLogin() +: Tgstation.Server.Client.IServerClientFactory +, Tgstation.Server.Client.ServerClientFactory +
  • CreateFromPath() : Tgstation.Server.Host.Components.Repository.ILibGit2RepositoryFactory , Tgstation.Server.Host.Components.Repository.LibGit2RepositoryFactory
  • +
  • CreateFromToken() +: Tgstation.Server.Client.IServerClientFactory +, Tgstation.Server.Client.ServerClientFactory +
  • CreateInMemory() : Tgstation.Server.Host.Components.Repository.ILibGit2RepositoryFactory , Tgstation.Server.Host.Components.Repository.LibGit2RepositoryFactory @@ -494,10 +502,6 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.IServerFactory , Tgstation.Server.Host.ServerFactory
  • -
  • CreateServerClient() -: Tgstation.Server.Client.IServerClientFactory -, Tgstation.Server.Client.ServerClientFactory -
  • CreateSymbolicLink() : Tgstation.Server.Host.IO.ISymlinkFactory , Tgstation.Server.Host.IO.PosixSymlinkFactory diff --git a/functions_func_e.html b/functions_func_e.html index 0a1f87c085..f7d917ea94 100644 --- a/functions_func_e.html +++ b/functions_func_e.html @@ -150,7 +150,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.NativeMethods
  • ErrorMessage() -: Tgstation.Server.Api.Models.ErrorMessage +: Tgstation.Server.Api.Models.ErrorMessage
  • EventConsumer() : Tgstation.Server.Host.Components.EventConsumer diff --git a/functions_func_i.html b/functions_func_i.html index a7d7c72fb6..1101aea057 100644 --- a/functions_func_i.html +++ b/functions_func_i.html @@ -181,7 +181,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Components.InstanceFactory
  • InstanceManager() -: Tgstation.Server.Host.Components.InstanceManager +: Tgstation.Server.Host.Components.InstanceManager
  • InstanceManagerClient() : Tgstation.Server.Client.InstanceManagerClient diff --git a/functions_func_m.html b/functions_func_m.html index 2a3d436670..f069c42b8a 100644 --- a/functions_func_m.html +++ b/functions_func_m.html @@ -150,7 +150,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Components.Repository.Repository
  • MethodNotSupportedException() -: Tgstation.Server.Client.MethodNotSupportedException +: Tgstation.Server.Client.MethodNotSupportedException
  • ModifyDme() : Tgstation.Server.Host.Components.Deployment.DreamMaker diff --git a/functions_func_r.html b/functions_func_r.html index d6772032e5..25806f699f 100644 --- a/functions_func_r.html +++ b/functions_func_r.html @@ -133,7 +133,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Controllers.AdministrationController
  • RateLimitException() -: Tgstation.Server.Client.RateLimitException +: Tgstation.Server.Client.RateLimitException
  • RawCheckout() : Tgstation.Server.Host.Components.Repository.Repository @@ -203,6 +203,9 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • ReconnectionLoop() : Tgstation.Server.Host.Components.Chat.Providers.Provider
  • +
  • RefreshToken() +: Tgstation.Server.Client.ApiClient +
  • RegisterCommandHandler() : Tgstation.Server.Host.Components.Chat.ChatManager , Tgstation.Server.Host.Components.Chat.IChatManager @@ -250,7 +253,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Components.Session.SessionController
  • Repository() -: Tgstation.Server.Client.Components.RepositoryClient +: Tgstation.Server.Client.Components.RepositoryClient , Tgstation.Server.Host.Components.Repository.Repository
  • RepositoryClient() @@ -287,7 +290,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • ResolvePath() : Tgstation.Server.Host.IO.DefaultIOManager -, Tgstation.Server.Host.IO.IIOManager +, Tgstation.Server.Host.IO.IIOManager , Tgstation.Server.Host.IO.ResolvingIOManager
  • ResolvingIOManager() @@ -302,7 +305,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Components.Watchdog.WatchdogBase , Tgstation.Server.Host.Controllers.DreamDaemonController , Tgstation.Server.Host.Core.IServerControl -, Tgstation.Server.Host.Server +, Tgstation.Server.Host.Server
  • RestartRegistration() : Tgstation.Server.Host.Core.RestartRegistration @@ -357,7 +360,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Jobs.JobManager
  • RunRequest< TResult >() -: Tgstation.Server.Client.ApiClient +: Tgstation.Server.Client.ApiClient
  • RuntimeInformation() : Tgstation.Server.Host.Components.Interop.Bridge.RuntimeInformation diff --git a/functions_func_u.html b/functions_func_u.html index b734a2f0c4..a0d603dd3a 100644 --- a/functions_func_u.html +++ b/functions_func_u.html @@ -130,7 +130,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

    - u -

  • diff --git a/functions_g.html b/functions_g.html index 0b36160569..fc6f118a65 100644 --- a/functions_g.html +++ b/functions_g.html @@ -133,6 +133,9 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • GameStaticFilesSubdirectory : Tgstation.Server.Host.Components.StaticFiles.Configuration
  • +
  • generalConfiguration +: Tgstation.Server.Host.Components.InstanceManager +
  • GeneralConfiguration : Tgstation.Server.Host.Components.Watchdog.WatchdogFactory , Tgstation.Server.Host.Configuration.GeneralConfiguration diff --git a/functions_i.html b/functions_i.html index cf93425cac..37218b143c 100644 --- a/functions_i.html +++ b/functions_i.html @@ -285,7 +285,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • InstanceManager : Tgstation.Server.Api.Routes -, Tgstation.Server.Host.Components.InstanceManager +, Tgstation.Server.Host.Components.InstanceManager
  • instanceManager : Tgstation.Server.Host.Controllers.ByondController diff --git a/functions_m.html b/functions_m.html index a7b807cd4d..f64572e599 100644 --- a/functions_m.html +++ b/functions_m.html @@ -186,7 +186,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Components.Session.ReattachInfoHandler
  • MethodNotSupportedException() -: Tgstation.Server.Client.MethodNotSupportedException +: Tgstation.Server.Client.MethodNotSupportedException
  • MicrosoftLogLevel : Tgstation.Server.Host.Configuration.FileLoggingConfiguration diff --git a/functions_n.html b/functions_n.html index 030a4457b5..ff447044f0 100644 --- a/functions_n.html +++ b/functions_n.html @@ -167,7 +167,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Components.Interop.Topic.TopicParameters
  • NewTestMerges -: Tgstation.Server.Api.Models.Repository +: Tgstation.Server.Api.Models.Repository
  • NewVersion : Tgstation.Server.Api.Models.Administration @@ -180,11 +180,13 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • NextMessage() : Tgstation.Server.Host.Components.Chat.Providers.IProvider -, Tgstation.Server.Host.Components.Chat.Providers.Provider
  • nextMessage : Tgstation.Server.Host.Components.Chat.Providers.Provider
  • +
  • NextMessage() +: Tgstation.Server.Host.Components.Chat.Providers.Provider +
  • nextPort : Tgstation.Server.Host.Components.Session.SessionController
  • diff --git a/functions_prop_n.html b/functions_prop_n.html index e2a6144de2..035617ad5a 100644 --- a/functions_prop_n.html +++ b/functions_prop_n.html @@ -150,7 +150,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Components.Interop.Topic.TopicParameters
  • NewTestMerges -: Tgstation.Server.Api.Models.Repository +: Tgstation.Server.Api.Models.Repository
  • NewVersion : Tgstation.Server.Api.Models.Administration diff --git a/functions_r.html b/functions_r.html index c018d65ac5..4ecf359bd5 100644 --- a/functions_r.html +++ b/functions_r.html @@ -134,7 +134,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Controllers.AdministrationController
  • RateLimitException() -: Tgstation.Server.Client.RateLimitException +: Tgstation.Server.Client.RateLimitException
  • RawCheckout() : Tgstation.Server.Host.Components.Repository.Repository @@ -270,6 +270,9 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Components.Repository.IRepository , Tgstation.Server.Host.Components.Repository.Repository
  • +
  • RefreshToken() +: Tgstation.Server.Client.ApiClient +
  • RegisterCommandHandler() : Tgstation.Server.Host.Components.Chat.ChatManager , Tgstation.Server.Host.Components.Chat.IChatManager @@ -411,7 +414,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • ResolvePath() : Tgstation.Server.Host.IO.DefaultIOManager -, Tgstation.Server.Host.IO.IIOManager +, Tgstation.Server.Host.IO.IIOManager , Tgstation.Server.Host.IO.ResolvingIOManager
  • ResolvingIOManager() @@ -429,7 +432,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Components.Watchdog.WatchdogBase , Tgstation.Server.Host.Controllers.DreamDaemonController , Tgstation.Server.Host.Core.IServerControl -, Tgstation.Server.Host.Server +, Tgstation.Server.Host.Server
  • restartHandlers : Tgstation.Server.Host.Server @@ -485,7 +488,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Models.RevInfoTestMerge
  • RevisionInformations -: Tgstation.Server.Host.Database.DatabaseContext< TParentContext > +: Tgstation.Server.Host.Database.DatabaseContext< TParentContext > , Tgstation.Server.Host.Database.IDatabaseContext , Tgstation.Server.Host.Models.Instance
  • @@ -536,13 +539,15 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • Running : Tgstation.Server.Api.Models.DreamDaemon , Tgstation.Server.Host.Components.Watchdog.IWatchdog -, Tgstation.Server.Host.Components.Watchdog.WatchdogBase
  • running : Tgstation.Server.Host.Components.Watchdog.WatchdogBase
  • +
  • Running +: Tgstation.Server.Host.Components.Watchdog.WatchdogBase +
  • RunRequest< TResult >() -: Tgstation.Server.Client.ApiClient +: Tgstation.Server.Client.ApiClient
  • runTask : Tgstation.Server.Host.System.WindowsNetworkPromptReaper diff --git a/functions_s.html b/functions_s.html index dbd7c675b0..6ef4f6d50f 100644 --- a/functions_s.html +++ b/functions_s.html @@ -199,6 +199,9 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • Semaphore : Tgstation.Server.Host.Components.Watchdog.WatchdogBase
  • +
  • semaphoreSlim +: Tgstation.Server.Client.ApiClient +
  • SemaphoreSlimContext() : Tgstation.Server.Host.Core.SemaphoreSlimContext
  • diff --git a/functions_t.html b/functions_t.html index b020fc2738..8617eee71d 100644 --- a/functions_t.html +++ b/functions_t.html @@ -229,13 +229,13 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • Token : Tgstation.Server.Api.ApiHeaders , Tgstation.Server.Client.IServerClient -, Tgstation.Server.Client.ServerClient
  • token : Tgstation.Server.Client.ServerClient
  • Token -: Tgstation.Server.Host.System.ProgramShutdownTokenSource +: Tgstation.Server.Client.ServerClient +, Tgstation.Server.Host.System.ProgramShutdownTokenSource
  • TokenClockSkewMinutes : Tgstation.Server.Host.Security.TokenFactory @@ -250,6 +250,9 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
  • TokenFactory() : Tgstation.Server.Host.Security.TokenFactory
  • +
  • tokenRefreshHeaders +: Tgstation.Server.Client.ApiClient +
  • TokenSecuritySchemeId : Tgstation.Server.Host.Core.SwaggerConfiguration
  • diff --git a/functions_u.html b/functions_u.html index 1b2295f184..4aa1404f66 100644 --- a/functions_u.html +++ b/functions_u.html @@ -136,7 +136,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Security.WindowsSystemIdentity
  • UnauthorizedException() -: Tgstation.Server.Client.UnauthorizedException +: Tgstation.Server.Client.UnauthorizedException
  • Uninstall : Tgstation.Server.Host.Service.Program diff --git a/functions_vars.html b/functions_vars.html index 8bf3f2d11d..d808d4fe7f 100644 --- a/functions_vars.html +++ b/functions_vars.html @@ -154,7 +154,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Api.Routes
  • AdminName -: Tgstation.Server.Api.Models.User +: Tgstation.Server.Api.Models.User
  • AdminOnly : Tgstation.Server.Host.Components.Chat.Commands.ByondCommand diff --git a/functions_vars_d.html b/functions_vars_d.html index ab9a0031a0..6b80bf51b7 100644 --- a/functions_vars_d.html +++ b/functions_vars_d.html @@ -161,7 +161,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Database.DatabaseCollection< TModel >
  • DefaultAdminPassword -: Tgstation.Server.Api.Models.User +: Tgstation.Server.Api.Models.User
  • DefaultBufferSize : Tgstation.Server.Host.IO.DefaultIOManager diff --git a/functions_vars_g.html b/functions_vars_g.html index 6c26e0ab7b..4bbd0524a4 100644 --- a/functions_vars_g.html +++ b/functions_vars_g.html @@ -132,7 +132,8 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Components.StaticFiles.Configuration
  • generalConfiguration -: Tgstation.Server.Host.Controllers.AdministrationController +: Tgstation.Server.Host.Components.InstanceManager +, Tgstation.Server.Host.Controllers.AdministrationController , Tgstation.Server.Host.Controllers.HomeController , Tgstation.Server.Host.Controllers.InstanceController , Tgstation.Server.Host.Controllers.RepositoryController diff --git a/functions_vars_s.html b/functions_vars_s.html index d6ccbe7735..8777907c3f 100644 --- a/functions_vars_s.html +++ b/functions_vars_s.html @@ -152,6 +152,9 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); , Tgstation.Server.Host.Components.Repository.RepositoryManager , Tgstation.Server.Host.Components.StaticFiles.Configuration
  • +
  • semaphoreSlim +: Tgstation.Server.Client.ApiClient +
  • SendMessageCount : Tgstation.Server.Host.System.WindowsNetworkPromptReaper
  • diff --git a/functions_vars_t.html b/functions_vars_t.html index 7192d644b0..d78a39b5ce 100644 --- a/functions_vars_t.html +++ b/functions_vars_t.html @@ -163,6 +163,9 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search'); : Tgstation.Server.Host.Controllers.HomeController , Tgstation.Server.Host.Core.Application +
  • tokenRefreshHeaders +: Tgstation.Server.Client.ApiClient +
  • TokenSecuritySchemeId : Tgstation.Server.Host.Core.SwaggerConfiguration
  • diff --git a/index.html b/index.html index 0e041909ab..f7bf936368 100644 --- a/index.html +++ b/index.html @@ -114,6 +114,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');
    • libc6-i386
    • libstdc++6:i386
    • +
    • libssl1.0.0
    • gcc-multilib (Only on 64-bit systems)

    Note that tgstation-server has only ever been tested on Linux via it's docker environment. If you are having trouble with something in a native installation, or figure out a required workaround, please contact project maintainers so this documentation may be better updated.

    diff --git a/interface_tgstation_1_1_server_1_1_client_1_1_i_api_client_factory-members.html b/interface_tgstation_1_1_server_1_1_client_1_1_i_api_client_factory-members.html index 3ff6f8a415..d45f41c5f7 100644 --- a/interface_tgstation_1_1_server_1_1_client_1_1_i_api_client_factory-members.html +++ b/interface_tgstation_1_1_server_1_1_client_1_1_i_api_client_factory-members.html @@ -100,7 +100,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

    This is the complete list of members for Tgstation.Server.Client.IApiClientFactory, including all inherited members.

    - +
    CreateApiClient(Uri url, ApiHeaders apiHeaders)Tgstation.Server.Client.IApiClientFactory
    CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders)Tgstation.Server.Client.IApiClientFactory
    - - - + + +

    Public Member Functions

    IApiClient CreateApiClient (Uri url, ApiHeaders apiHeaders)
     Create an IApiClient More...
     
    IApiClient CreateApiClient (Uri url, ApiHeaders apiHeaders, ApiHeaders?tokenRefreshHeaders)
     Create an IApiClient More...
     

    Detailed Description

    For creating IApiClients

    Definition at line 9 of file IApiClientFactory.cs.

    Member Function Documentation

    - +
    @@ -137,7 +137,13 @@ Public Member Functions - + + + + + + + @@ -152,12 +158,13 @@ Public Member Functions
    ApiHeaders apiHeaders apiHeaders,
    ApiHeaderstokenRefreshHeaders 
    +
    urlThe base Uri
    apiHeadersThe ApiHeaders for the IApiClient
    tokenRefreshHeadersThe ApiHeaders to use to generate a new Api.Models.Token.
    Returns
    A new IApiClient
    -

    Implemented in Tgstation.Server.Client.ApiClientFactory.

    +

    Implemented in Tgstation.Server.Client.ApiClientFactory.

    diff --git a/interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory-members.html b/interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory-members.html index 8c525dc348..c9475086bf 100644 --- a/interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory-members.html +++ b/interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory-members.html @@ -100,8 +100,8 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');

    This is the complete list of members for Tgstation.Server.Client.IServerClientFactory, including all inherited members.

    - - + +
    CreateServerClient(Uri host, string username, string password, TimeSpan timeout=default, CancellationToken cancellationToken=default)Tgstation.Server.Client.IServerClientFactory
    CreateServerClient(Uri host, Token token, TimeSpan timeout=default)Tgstation.Server.Client.IServerClientFactory
    CreateFromLogin(Uri host, string username, string password, IEnumerable< IRequestLogger >?requestLoggers=null, TimeSpan?timeout=null, bool attemptLoginRefresh=true, CancellationToken cancellationToken=default)Tgstation.Server.Client.IServerClientFactory
    CreateFromToken(Uri host, Token token)Tgstation.Server.Client.IServerClientFactory
    - - - - - - + + + + + +

    Public Member Functions

    Task< IServerClientCreateServerClient (Uri host, string username, string password, TimeSpan timeout=default, CancellationToken cancellationToken=default)
     Create a IServerClient More...
     
    IServerClient CreateServerClient (Uri host, Token token, TimeSpan timeout=default)
     Create a IServerClient More...
     
    Task< IServerClientCreateFromLogin (Uri host, string username, string password, IEnumerable< IRequestLogger >?requestLoggers=null, TimeSpan?timeout=null, bool attemptLoginRefresh=true, CancellationToken cancellationToken=default)
     Create a IServerClient More...
     
    IServerClient CreateFromToken (Uri host, Token token)
     Create a IServerClient More...
     

    Detailed Description

    Factory for creating IServerClients

    -

    Definition at line 11 of file IServerClientFactory.cs.

    +

    Definition at line 12 of file IServerClientFactory.cs.

    Member Function Documentation

    - +
    - + @@ -151,8 +151,20 @@ Public Member Functions - - + + + + + + + + + + + + + + @@ -174,23 +186,25 @@ Public Member Functions - - + + + +
    Task<IServerClient> Tgstation.Server.Client.IServerClientFactory.CreateServerClient Task<IServerClient> Tgstation.Server.Client.IServerClientFactory.CreateFromLogin ( Uri  host,
    TimeSpan timeout = default, IEnumerable< IRequestLogger >? requestLoggers = null,
    TimeSpan? timeout = null,
    bool attemptLoginRefresh = true,
    hostThe URL to access TGS
    usernameThe username to for the IServerClient
    passwordThe password for the IServerClient
    timeoutThe TimeSpan representing timeout for the connection
    cancellationTokenThe CancellationToken for the operation
    requestLoggersOptional initial IRequestLoggers to add to the IServerClient.
    timeoutOptional TimeSpan representing timeout for the connection
    attemptLoginRefreshAttempt to refresh the received Token when it expires or becomes invalid. username and password will be stored in memory if this is .
    cancellationTokenOptional CancellationToken for the operation
    Returns
    A Task<TResult> resulting in a new IServerClient
    -

    Implemented in Tgstation.Server.Client.ServerClientFactory.

    +

    Implemented in Tgstation.Server.Client.ServerClientFactory.

    - +
    - + @@ -199,13 +213,7 @@ Public Member Functions - - - - - - - + @@ -220,13 +228,12 @@ Public Member Functions
    IServerClient Tgstation.Server.Client.IServerClientFactory.CreateServerClient IServerClient Tgstation.Server.Client.IServerClientFactory.CreateFromToken ( Uri  host,
    Token token,
    TimeSpan timeout = default token 
    -
    hostThe URL to access TGS
    tokenThe Token to access the API with
    timeoutThe TimeSpan representing timeout for the connection
    Returns
    A new IServerClient
    -

    Implemented in Tgstation.Server.Client.ServerClientFactory.

    +

    Implemented in Tgstation.Server.Client.ServerClientFactory.

    diff --git a/interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_dispatcher.html b/interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_dispatcher.html index 1c00de8d7f..8d41ba4a14 100644 --- a/interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_dispatcher.html +++ b/interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_dispatcher.html @@ -162,7 +162,7 @@ Public Member Functions

    Implemented in Tgstation.Server.Host.Components.Session.SessionController, and Tgstation.Server.Host.Components.InstanceManager.

    -

    Referenced by Tgstation.Server.Host.Components.InstanceManager.ProcessBridgeRequest().

    +

    Referenced by Tgstation.Server.Host.Components.InstanceManager.ProcessBridgeRequest().

    Here is the caller graph for this function:
    diff --git a/interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_handler.html b/interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_handler.html index db4a3b1b58..85aa95b955 100644 --- a/interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_handler.html +++ b/interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_handler.html @@ -203,7 +203,7 @@ Properties

    Definition at line 12 of file IBridgeHandler.cs.

    -

    Referenced by Tgstation.Server.Host.Components.InstanceManager.RegisterHandler().

    +

    Referenced by Tgstation.Server.Host.Components.InstanceManager.RegisterHandler().

    diff --git a/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control.html b/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control.html index 7bf829384e..31bb668c4e 100644 --- a/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control.html +++ b/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control.html @@ -248,7 +248,7 @@ Here is the caller graph for this function:

    Implemented in Tgstation.Server.Host.Server.

    -

    Referenced by Tgstation.Server.Host.Components.Chat.ChatManager.ChatManager(), Tgstation.Server.Host.Components.InstanceManager.InstanceManager(), and Tgstation.Server.Host.Components.Watchdog.WatchdogBase.WatchdogBase().

    +

    Referenced by Tgstation.Server.Host.Components.Chat.ChatManager.ChatManager(), Tgstation.Server.Host.Components.InstanceManager.InstanceManager(), and Tgstation.Server.Host.Components.Watchdog.WatchdogBase.WatchdogBase().

    Here is the caller graph for this function:
    @@ -256,7 +256,7 @@ Here is the caller graph for this function:
    - + diff --git a/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control_a35728d66030addfd5551351b8c6db942_icgraph.map b/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control_a35728d66030addfd5551351b8c6db942_icgraph.map index 5c5a123297..290571259e 100644 --- a/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control_a35728d66030addfd5551351b8c6db942_icgraph.map +++ b/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control_a35728d66030addfd5551351b8c6db942_icgraph.map @@ -1,5 +1,5 @@ - + diff --git a/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control_a35728d66030addfd5551351b8c6db942_icgraph.md5 b/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control_a35728d66030addfd5551351b8c6db942_icgraph.md5 index d6a8db4dd8..a0d9d5b2b7 100644 --- a/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control_a35728d66030addfd5551351b8c6db942_icgraph.md5 +++ b/interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control_a35728d66030addfd5551351b8c6db942_icgraph.md5 @@ -1 +1 @@ -0be6d3b529a58c43a026cc7b595ae7c9 \ No newline at end of file +68aa955e733d1ce63051aade34dc0cef \ No newline at end of file diff --git a/namespace_tgstation_1_1_server_1_1_api_1_1_models.html b/namespace_tgstation_1_1_server_1_1_api_1_1_models.html index bd4c9ec5c9..117e621da0 100644 --- a/namespace_tgstation_1_1_server_1_1_api_1_1_models.html +++ b/namespace_tgstation_1_1_server_1_1_api_1_1_models.html @@ -552,7 +552,7 @@ Enumerations

    Attempted to change the origin of an existing repository.

    RepoDuplicateTestMerge  -

    Repository.NewTestMerges contained duplicate TestMergeParameters.Numbers.

    +

    Repository.NewTestMerges contained duplicate TestMergeParameters.Numbers.

    RepoWhitespaceCommitterName 

    Attempted to set a whitespace Internal.RepositorySettings.CommitterName.

    diff --git a/search/all_1.js b/search/all_1.js index cb40970beb..4d4f9d34d6 100644 --- a/search/all_1.js +++ b/search/all_1.js @@ -38,7 +38,7 @@ var searchData= ['administrationcontroller_2ecs',['AdministrationController.cs',['../_administration_controller_8cs.html',1,'']]], ['administrationrights',['AdministrationRights',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_user.html#ade2eaa5d5e27d3f550db6180e0f66b27',1,'Tgstation.Server.Api.Models.Internal.User.AdministrationRights()'],['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#affda1440f9c5e2dfcca65138d2f89a21',1,'Tgstation.Server.Api.Rights.AdministrationRights()']]], ['administrationrights_2ecs',['AdministrationRights.cs',['../_administration_rights_8cs.html',1,'']]], - ['adminname',['AdminName',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html#a446a804a6eeed2502879382f46df0b9d',1,'Tgstation::Server::Api::Models::User']]], + ['adminname',['AdminName',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html#a9a48baf4f5ea3e824d8dc489d3aa38f6',1,'Tgstation::Server::Api::Models::User']]], ['adminonly',['AdminOnly',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_byond_command.html#a42073a7395ce9de9b0de9d36e4d6f388',1,'Tgstation.Server.Host.Components.Chat.Commands.ByondCommand.AdminOnly()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_custom_command.html#a32556e6bc56cad31d993e4be70e7ca60',1,'Tgstation.Server.Host.Components.Chat.Commands.CustomCommand.AdminOnly()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_i_command.html#a0872d9ed0ba21f3decc6d5303001fc8e',1,'Tgstation.Server.Host.Components.Chat.Commands.ICommand.AdminOnly()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_kek_command.html#ad69632c6b423bea790abde206d926425',1,'Tgstation.Server.Host.Components.Chat.Commands.KekCommand.AdminOnly()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_pull_requests_command.html#a63dc4a93cb647e14f40a613101d7335e',1,'Tgstation.Server.Host.Components.Chat.Commands.PullRequestsCommand.AdminOnly()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_revision_command.html#a17094a94c993b8bbb5fdda8b7a470707',1,'Tgstation.Server.Host.Components.Chat.Commands.RevisionCommand.AdminOnly()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_version_command.html#a801fd1e205cb8e006fefcbf8af8e3b30',1,'Tgstation.Server.Host.Components.Chat.Commands.VersionCommand.AdminOnly()']]], ['allowanyorigin',['AllowAnyOrigin',['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_control_panel_configuration.html#a5c0b28cdbd801a02bce701901a060818',1,'Tgstation::Server::Host::Configuration::ControlPanelConfiguration']]], ['allowedorigins',['AllowedOrigins',['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_control_panel_configuration.html#a7f0a8f0d0540bb5832bf61c86a98b202',1,'Tgstation::Server::Host::Configuration::ControlPanelConfiguration']]], @@ -53,22 +53,22 @@ var searchData= ['api',['API',['../api.html',1,'']]], ['api_2edox',['API.dox',['../_a_p_i_8dox.html',1,'']]], ['apiclient',['ApiClient',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html',1,'Tgstation::Server::Client']]], - ['apiclient',['ApiClient',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a26be1a658c69fad22bd229863013f2f7',1,'Tgstation.Server.Client.ApiClient.ApiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_administration_client.html#aa8fcfafea45fc308cd03fc312b49bbc9',1,'Tgstation.Server.Client.AdministrationClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_byond_client.html#a295eaf549628214344fb27e81fba5541',1,'Tgstation.Server.Client.Components.ByondClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_chat_bots_client.html#a6d24e075bdc11fb958a8b4621475b662',1,'Tgstation.Server.Client.Components.ChatBotsClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html#a42e1a0711dec8d78fc370b30b1aacf80',1,'Tgstation.Server.Client.Components.ConfigurationClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_dream_daemon_client.html#a877a9f75012ed5a6f8673e6ab152b7b8',1,'Tgstation.Server.Client.Components.DreamDaemonClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_dream_maker_client.html#a15b39a0722ab7ba469ea002df87190a7',1,'Tgstation.Server.Client.Components.DreamMakerClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_client.html#af73c0e3dbcf9e1bc1f8591f3a6331629',1,'Tgstation.Server.Client.Components.InstanceClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_user_client.html#afe9a85e5c7dfa76fcd2ffdeecc388b34',1,'Tgstation.Server.Client.Components.InstanceUserClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_jobs_client.html#acd85788ba2647284a601f7cca2e3d8cb',1,'Tgstation.Server.Client.Components.JobsClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_repository_client.html#a34759d9d0d89eab5def2d32cc90b7736',1,'Tgstation.Server.Client.Components.RepositoryClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_instance_manager_client.html#a717fb098fea90adf6d93ae860e0420fe',1,'Tgstation.Server.Client.InstanceManagerClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#a20ec30223872220ef996f4219888f3cc',1,'Tgstation.Server.Client.ServerClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_users_client.html#a1d983fe53f4fef69d2ba676884d4ce8c',1,'Tgstation.Server.Client.UsersClient.apiClient()']]], + ['apiclient',['ApiClient',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a938e9977616883fcbad31ad1b07ab0ef',1,'Tgstation.Server.Client.ApiClient.ApiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_administration_client.html#aa8fcfafea45fc308cd03fc312b49bbc9',1,'Tgstation.Server.Client.AdministrationClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_byond_client.html#a295eaf549628214344fb27e81fba5541',1,'Tgstation.Server.Client.Components.ByondClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_chat_bots_client.html#a6d24e075bdc11fb958a8b4621475b662',1,'Tgstation.Server.Client.Components.ChatBotsClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html#a42e1a0711dec8d78fc370b30b1aacf80',1,'Tgstation.Server.Client.Components.ConfigurationClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_dream_daemon_client.html#a877a9f75012ed5a6f8673e6ab152b7b8',1,'Tgstation.Server.Client.Components.DreamDaemonClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_dream_maker_client.html#a15b39a0722ab7ba469ea002df87190a7',1,'Tgstation.Server.Client.Components.DreamMakerClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_client.html#af73c0e3dbcf9e1bc1f8591f3a6331629',1,'Tgstation.Server.Client.Components.InstanceClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_user_client.html#afe9a85e5c7dfa76fcd2ffdeecc388b34',1,'Tgstation.Server.Client.Components.InstanceUserClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_jobs_client.html#acd85788ba2647284a601f7cca2e3d8cb',1,'Tgstation.Server.Client.Components.JobsClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_repository_client.html#a34759d9d0d89eab5def2d32cc90b7736',1,'Tgstation.Server.Client.Components.RepositoryClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_instance_manager_client.html#a717fb098fea90adf6d93ae860e0420fe',1,'Tgstation.Server.Client.InstanceManagerClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#a20ec30223872220ef996f4219888f3cc',1,'Tgstation.Server.Client.ServerClient.apiClient()'],['../class_tgstation_1_1_server_1_1_client_1_1_users_client.html#a1d983fe53f4fef69d2ba676884d4ce8c',1,'Tgstation.Server.Client.UsersClient.apiClient()']]], ['apiclient_2ecs',['ApiClient.cs',['../_api_client_8cs.html',1,'']]], ['apiclientfactory',['ApiClientFactory',['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#a1ce05c0c437fca7e62dd764346be53a0',1,'Tgstation::Server::Client::ServerClientFactory']]], ['apiclientfactory',['ApiClientFactory',['../class_tgstation_1_1_server_1_1_client_1_1_api_client_factory.html',1,'Tgstation::Server::Client']]], ['apiclientfactory_2ecs',['ApiClientFactory.cs',['../_api_client_factory_8cs.html',1,'']]], ['apiconflictexception',['ApiConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html',1,'Tgstation::Server::Client']]], - ['apiconflictexception',['ApiConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#a80efe66912385fd30e003eda830fc8a8',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#ab4f3940b662b24b906bf15d94a0c64b9',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException()'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#ac779b4eb27f674d0c804955a8bb50b4b',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#a76b2d08f49a952c5693d785c379328e2',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(string message, Exception innerException)']]], + ['apiconflictexception',['ApiConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#aacb270dc4bed5ac02ef0a7f9837f011b',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#ab4f3940b662b24b906bf15d94a0c64b9',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException()'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#ac779b4eb27f674d0c804955a8bb50b4b',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#a76b2d08f49a952c5693d785c379328e2',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(string message, Exception innerException)']]], ['apiconflictexception_2ecs',['ApiConflictException.cs',['../_api_conflict_exception_8cs.html',1,'']]], ['apicontroller',['ApiController',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_api_controller.html#a1049f4b2b9557ea752177833ef52052e',1,'Tgstation::Server::Host::Controllers::ApiController']]], ['apicontroller',['ApiController',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_api_controller.html',1,'Tgstation::Server::Host::Controllers']]], ['apicontroller_2ecs',['ApiController.cs',['../_api_controller_8cs.html',1,'']]], ['apiexception',['ApiException',['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html',1,'Tgstation::Server::Client']]], - ['apiexception',['ApiException',['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a1249a3ee677e1f6f512f6f0259587113',1,'Tgstation.Server.Client.ApiException.ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a17155a36f4798421f6ae9c44add39a08',1,'Tgstation.Server.Client.ApiException.ApiException(HttpResponseMessage responseMessage, string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a58f71c3c7a18641b8144f772068b976d',1,'Tgstation.Server.Client.ApiException.ApiException()'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a9dfbd90587b56d3db3e497db8b7a5ae1',1,'Tgstation.Server.Client.ApiException.ApiException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a36773be2804412a37326218e0005fa25',1,'Tgstation.Server.Client.ApiException.ApiException(string message, Exception innerException)']]], + ['apiexception',['ApiException',['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a149510570ccc25bc57e4c79bfe12e996',1,'Tgstation.Server.Client.ApiException.ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a17155a36f4798421f6ae9c44add39a08',1,'Tgstation.Server.Client.ApiException.ApiException(HttpResponseMessage responseMessage, string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a58f71c3c7a18641b8144f772068b976d',1,'Tgstation.Server.Client.ApiException.ApiException()'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a9dfbd90587b56d3db3e497db8b7a5ae1',1,'Tgstation.Server.Client.ApiException.ApiException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a36773be2804412a37326218e0005fa25',1,'Tgstation.Server.Client.ApiException.ApiException(string message, Exception innerException)']]], ['apiexception_2ecs',['ApiException.cs',['../_api_exception_8cs.html',1,'']]], ['apiheaders',['ApiHeaders',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html',1,'Tgstation::Server::Api']]], - ['apiheaders',['ApiHeaders',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_api_controller.html#a1934074118a6f4f9044b9da630c91a8e',1,'Tgstation.Server.Host.Controllers.ApiController.ApiHeaders()'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#aee584e59a0c6535f1d3c60d620036253',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string token)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#ac6a606d30d1bd1cf066cd8f5d917df18',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string username, string password)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#aa03245a124379bec7f0eb3fc672a4e43',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(RequestHeaders requestHeaders)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#a16b461300fbc981ef39230f85b680870',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)']]], + ['apiheaders',['ApiHeaders',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_api_controller.html#a1934074118a6f4f9044b9da630c91a8e',1,'Tgstation.Server.Host.Controllers.ApiController.ApiHeaders()'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#aee584e59a0c6535f1d3c60d620036253',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string token)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#ac6a606d30d1bd1cf066cd8f5d917df18',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string username, string password)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#aa03245a124379bec7f0eb3fc672a4e43',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(RequestHeaders requestHeaders)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#af8b10f130bfebd02509618c0239d6a1c',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string?token, string?username, string?password)']]], ['apiheaders_2ecs',['ApiHeaders.cs',['../_api_headers_8cs.html',1,'']]], ['apimismatch',['ApiMismatch',['../namespace_tgstation_1_1_server_1_1_api_1_1_models.html#a1a6adb59dd51244efabf4e548e7075c8a034045c1b65adb43b709ea2f4e573951',1,'Tgstation::Server::Api::Models']]], ['apivalidateonly',['ApiValidateOnly',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_runtime_information.html#aab2ba8e8c26c816abd9149e21c0d8e0c',1,'Tgstation::Server::Host::Components::Interop::Bridge::RuntimeInformation']]], @@ -94,7 +94,7 @@ var searchData= ['assemblyinfo_2ecs',['AssemblyInfo.cs',['../_tgstation_8_server_8_client_2_assembly_info_8cs.html',1,'']]], ['assemblyinfo_2ecs',['AssemblyInfo.cs',['../_tgstation_8_server_8_host_8_watchdog_2_assembly_info_8cs.html',1,'']]], ['assemblyinformationprovider',['AssemblyInformationProvider',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_assembly_information_provider.html',1,'Tgstation::Server::Host::System']]], - ['assemblyinformationprovider',['AssemblyInformationProvider',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_application.html#a160a8959deda71c27f974a9c0321dd27',1,'Tgstation.Server.Host.Setup.SetupApplication.AssemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_assembly_information_provider.html#a5fd81262bcc6aeecc0608b630f96ced4',1,'Tgstation.Server.Host.System.AssemblyInformationProvider.AssemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_command_factory.html#a0925181fde55747dca42c360486eb949',1,'Tgstation.Server.Host.Components.Chat.Commands.CommandFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_version_command.html#a2804521ce43e4dae665f0dfe31afa8c6',1,'Tgstation.Server.Host.Components.Chat.Commands.VersionCommand.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider_factory.html#af165dfbe55239bcb1d705bddbed7c01f',1,'Tgstation.Server.Host.Components.Chat.Providers.ProviderFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#ac44fead7dfc02bdd99aea10b729e82ed',1,'Tgstation.Server.Host.Components.InstanceFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#aa335c25ccb65f93242424c79396a29dc',1,'Tgstation.Server.Host.Components.InstanceManager.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#a8dbd5a414f211bbb1b0c63c5666568b8',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#a4e26166b0342015cc789ec8f897e3f65',1,'Tgstation.Server.Host.Controllers.AdministrationController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a36878504836ee1bd7b7a57f7e3d31192',1,'Tgstation.Server.Host.Controllers.HomeController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#aeb5ae966710833c1841de51f73cb2362',1,'Tgstation.Server.Host.Controllers.InstanceController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_git_hub_client_factory.html#a6ba17c2ceda75c9d153d15893712446f',1,'Tgstation.Server.Host.Core.GitHubClientFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_server_factory.html#a387f7728fad0a77333cbb190d18b400d',1,'Tgstation.Server.Host.ServerFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a5851a0d869fb5b874b39106af947c661',1,'Tgstation.Server.Host.Setup.SetupWizard.assemblyInformationProvider()']]], + ['assemblyinformationprovider',['assemblyInformationProvider',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_command_factory.html#a0925181fde55747dca42c360486eb949',1,'Tgstation.Server.Host.Components.Chat.Commands.CommandFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_version_command.html#a2804521ce43e4dae665f0dfe31afa8c6',1,'Tgstation.Server.Host.Components.Chat.Commands.VersionCommand.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider_factory.html#af165dfbe55239bcb1d705bddbed7c01f',1,'Tgstation.Server.Host.Components.Chat.Providers.ProviderFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#ac44fead7dfc02bdd99aea10b729e82ed',1,'Tgstation.Server.Host.Components.InstanceFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#aa335c25ccb65f93242424c79396a29dc',1,'Tgstation.Server.Host.Components.InstanceManager.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#a8dbd5a414f211bbb1b0c63c5666568b8',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#a4e26166b0342015cc789ec8f897e3f65',1,'Tgstation.Server.Host.Controllers.AdministrationController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a36878504836ee1bd7b7a57f7e3d31192',1,'Tgstation.Server.Host.Controllers.HomeController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#aeb5ae966710833c1841de51f73cb2362',1,'Tgstation.Server.Host.Controllers.InstanceController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_git_hub_client_factory.html#a6ba17c2ceda75c9d153d15893712446f',1,'Tgstation.Server.Host.Core.GitHubClientFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_server_factory.html#a387f7728fad0a77333cbb190d18b400d',1,'Tgstation.Server.Host.ServerFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a5851a0d869fb5b874b39106af947c661',1,'Tgstation.Server.Host.Setup.SetupWizard.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_application.html#a160a8959deda71c27f974a9c0321dd27',1,'Tgstation.Server.Host.Setup.SetupApplication.AssemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_assembly_information_provider.html#a5fd81262bcc6aeecc0608b630f96ced4',1,'Tgstation.Server.Host.System.AssemblyInformationProvider.AssemblyInformationProvider()']]], ['assemblyinformationprovider_2ecs',['AssemblyInformationProvider.cs',['../_assembly_information_provider_8cs.html',1,'']]], ['assemblyname',['AssemblyName',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#acc064dcbf2348cfcf44d06acff499977',1,'Tgstation::Server::Api::ApiHeaders']]], ['asyncdelayer',['AsyncDelayer',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#abbdc59de3b3556a08bd111264527f369',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.AsyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_factory.html#a6cba03dd228358338743984da7c2da6a',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogFactory.AsyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html#a50f199a025fe456b6bb63c9eaa18a07a',1,'Tgstation.Server.Host.Components.Chat.ChatManager.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager_factory.html#aa5bf6331a8aa3e9587376c9574910e86',1,'Tgstation.Server.Host.Components.Chat.ChatManagerFactory.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a154b7cf302e0d4b39d338846a851b898',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider_factory.html#a07009731bbb9ae0df5a4c315189b5ccb',1,'Tgstation.Server.Host.Components.Chat.Providers.ProviderFactory.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#ad6f992fed638980594fd5a6c45b8946b',1,'Tgstation.Server.Host.Components.InstanceManager.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_identity_cache.html#a9ae1ecab8e3826302aa8d692c0d659d1',1,'Tgstation.Server.Host.Security.IdentityCache.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#ad5a557224dd5d8eac2a46dec133509fb',1,'Tgstation.Server.Host.Security.TokenFactory.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#ae12ec09822e867ea3dd0f99e7357f329',1,'Tgstation.Server.Host.Setup.SetupWizard.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_windows_network_prompt_reaper.html#a0a1974764838ef42c9d2faa8dd8580b9',1,'Tgstation.Server.Host.System.WindowsNetworkPromptReaper.asyncDelayer()']]], diff --git a/search/all_10.js b/search/all_10.js index de73687d67..60927ab069 100644 --- a/search/all_10.js +++ b/search/all_10.js @@ -5,7 +5,7 @@ var searchData= ['parameters',['Parameters',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_topic_1_1_event_notification.html#a5d3f8f2bd6cfb079bf693a1fc3339a65',1,'Tgstation::Server::Host::Components::Interop::Topic::EventNotification']]], ['params',['Params',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_topic_1_1_chat_command.html#a50a65dc64b139a5eb1a46d47753f0bf9',1,'Tgstation::Server::Host::Components::Interop::Topic::ChatCommand']]], ['paramserverport',['ParamServerPort',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_d_m_api_constants.html#a8dcd2fa9f5a2900252b878d8ad560796',1,'Tgstation::Server::Host::Components::Interop::DMApiConstants']]], - ['password',['password',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a0af57fc7dec5c5f69ae6ba15756031a7',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.password()'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#af420ae2e12b49c480947cc3068f033e6',1,'Tgstation.Server.Api.ApiHeaders.Password()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_irc_connection_string_builder.html#a5dc7c8fe9762a68b49b6707498130a8b',1,'Tgstation.Server.Api.Models.IrcConnectionStringBuilder.Password()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update.html#a6b66974e72005bfe586a72e8d24940cd',1,'Tgstation.Server.Api.Models.UserUpdate.Password()']]], + ['password',['Password',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#af420ae2e12b49c480947cc3068f033e6',1,'Tgstation.Server.Api.ApiHeaders.Password()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_irc_connection_string_builder.html#a5dc7c8fe9762a68b49b6707498130a8b',1,'Tgstation.Server.Api.Models.IrcConnectionStringBuilder.Password()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user_update.html#a6b66974e72005bfe586a72e8d24940cd',1,'Tgstation.Server.Api.Models.UserUpdate.Password()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a0af57fc7dec5c5f69ae6ba15756031a7',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.password()']]], ['passwordauthenticationscheme',['PasswordAuthenticationScheme',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#ab5d031ce8e7fba2e1575dde74ba9647a',1,'Tgstation::Server::Api::ApiHeaders']]], ['passwordhash',['PasswordHash',['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_user.html#ab5648b7f95630ff37ad8b0ddd72c1f54',1,'Tgstation::Server::Host::Models::User']]], ['passwordhasher',['passwordHasher',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_cryptography_suite.html#ad519d6aa46f39a52f9a7b50e0f181fd9',1,'Tgstation::Server::Host::Security::CryptographySuite']]], @@ -17,7 +17,7 @@ var searchData= ['pathtouserbyondfolder',['PathToUserByondFolder',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_byond_installer_base.html#a679ae28275514213de77b1f1a2fc94ff',1,'Tgstation.Server.Host.Components.Byond.ByondInstallerBase.PathToUserByondFolder()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_i_byond_installer.html#a239c00a7b10d38aab58b77dc6c503c09',1,'Tgstation.Server.Host.Components.Byond.IByondInstaller.PathToUserByondFolder()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_posix_byond_installer.html#ab5d0fd8482fc3a61bbfd4256790221c1',1,'Tgstation.Server.Host.Components.Byond.PosixByondInstaller.PathToUserByondFolder()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_windows_byond_installer.html#a33cd48c2466cb881a29eb910717f24ef',1,'Tgstation.Server.Host.Components.Byond.WindowsByondInstaller.PathToUserByondFolder()']]], ['pendingswappable',['pendingSwappable',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_windows_watchdog.html#a75ca166650eaa9426bec321ee2dce177',1,'Tgstation::Server::Host::Components::Watchdog::WindowsWatchdog']]], ['platformidentifier',['PlatformIdentifier',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_platform_identifier.html',1,'Tgstation::Server::Host::System']]], - ['platformidentifier',['platformIdentifier',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#a3c8f7e67bf286c73dd25994db7ed31a5',1,'Tgstation.Server.Host.Components.InstanceFactory.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#a3ac5f067400fe019d8375fffc0e83dee',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#a9103478c93c69deb31f3549ab04dcc84',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#aab5c882433d208418174394df71cfab4',1,'Tgstation.Server.Host.Controllers.AdministrationController.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#ac62c0b1b0e346409b20c3054d4a8354a',1,'Tgstation.Server.Host.Controllers.InstanceController.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_seeder.html#a9b30ce06ad99c07d9405020656cc3a1d',1,'Tgstation.Server.Host.Database.DatabaseSeeder.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#aa225e153a659c2ebdde2e69dc9c46458',1,'Tgstation.Server.Host.Setup.SetupWizard.platformIdentifier()'],['../interface_tgstation_1_1_server_1_1_host_1_1_setup_1_1_i_post_setup_services.html#ae3c16b681516ee65f1270f13ea435086',1,'Tgstation.Server.Host.Setup.IPostSetupServices.PlatformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_post_setup_services.html#a8c3e2e92797805caa52543a95c435a8a',1,'Tgstation.Server.Host.Setup.PostSetupServices.PlatformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_platform_identifier.html#ab29c252e971636fca5893880d01f9595',1,'Tgstation.Server.Host.System.PlatformIdentifier.PlatformIdentifier()']]], + ['platformidentifier',['PlatformIdentifier',['../interface_tgstation_1_1_server_1_1_host_1_1_setup_1_1_i_post_setup_services.html#ae3c16b681516ee65f1270f13ea435086',1,'Tgstation.Server.Host.Setup.IPostSetupServices.PlatformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_post_setup_services.html#a8c3e2e92797805caa52543a95c435a8a',1,'Tgstation.Server.Host.Setup.PostSetupServices.PlatformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_platform_identifier.html#ab29c252e971636fca5893880d01f9595',1,'Tgstation.Server.Host.System.PlatformIdentifier.PlatformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#a3c8f7e67bf286c73dd25994db7ed31a5',1,'Tgstation.Server.Host.Components.InstanceFactory.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#a3ac5f067400fe019d8375fffc0e83dee',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#a9103478c93c69deb31f3549ab04dcc84',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#aab5c882433d208418174394df71cfab4',1,'Tgstation.Server.Host.Controllers.AdministrationController.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#ac62c0b1b0e346409b20c3054d4a8354a',1,'Tgstation.Server.Host.Controllers.InstanceController.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_seeder.html#a9b30ce06ad99c07d9405020656cc3a1d',1,'Tgstation.Server.Host.Database.DatabaseSeeder.platformIdentifier()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#aa225e153a659c2ebdde2e69dc9c46458',1,'Tgstation.Server.Host.Setup.SetupWizard.platformIdentifier()']]], ['platformidentifier_2ecs',['PlatformIdentifier.cs',['../_platform_identifier_8cs.html',1,'']]], ['populateapi',['PopulateApi',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#a169541f29e0361eaba0bf8ba85624f10',1,'Tgstation::Server::Host::Controllers::RepositoryController']]], ['port',['port',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a0a16e9a0617d8e75740478a5d68a4420',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.port()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_irc_connection_string_builder.html#a70bde9bd8b2a01b079ff0996a379081d',1,'Tgstation.Server.Api.Models.IrcConnectionStringBuilder.Port()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_dead_session_controller.html#a11224f1c5b3a24a01a4325e47142b66d',1,'Tgstation.Server.Host.Components.Session.DeadSessionController.Port()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_i_session_controller.html#a3ff5f80807be55abecf73a4997d27ed0',1,'Tgstation.Server.Host.Components.Session.ISessionController.Port()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#a3bbf38000d199b51ebea80ba2ac287cf',1,'Tgstation.Server.Host.Components.Session.SessionController.Port()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_reattach_information_base.html#afcf987dfdf8f483388bfd832582c6a8b',1,'Tgstation.Server.Host.Models.ReattachInformationBase.Port()']]], @@ -58,11 +58,11 @@ var searchData= ['primarytestmerge',['PrimaryTestMerge',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_revision_information.html#a628bb81ae3a0621e5e3ad472d0dab742',1,'Tgstation.Server.Api.Models.RevisionInformation.PrimaryTestMerge()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_revision_information.html#adbe03f25ff741698ab92816bf7b511c5',1,'Tgstation.Server.Host.Models.RevisionInformation.PrimaryTestMerge()']]], ['prime',['Prime',['../namespace_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge.html#a50ede689312259c6ba4fe6595f8ad91dabe7938e9a87618a98a7b6b47e12aa44b',1,'Tgstation::Server::Host::Components::Interop::Bridge']]], ['primetcs',['primeTcs',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#a524400c2981d28ac310a509e2c6d9732',1,'Tgstation::Server::Host::Components::Session::SessionController']]], - ['process',['Process',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_bridge_controller.html#a1dd8fca5d57be939d8d04d4723788639',1,'Tgstation.Server.Host.Controllers.BridgeController.Process()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process.html#a6359ddbc29f1f67ae3e9fc1446a0207e',1,'Tgstation.Server.Host.System.Process.Process()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#af72f0ef89d2933aacb840b4371a5d55c',1,'Tgstation.Server.Host.Components.Session.SessionController.process()']]], + ['process',['process',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#af72f0ef89d2933aacb840b4371a5d55c',1,'Tgstation.Server.Host.Components.Session.SessionController.process()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_bridge_controller.html#a1dd8fca5d57be939d8d04d4723788639',1,'Tgstation.Server.Host.Controllers.BridgeController.Process()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process.html#a6359ddbc29f1f67ae3e9fc1446a0207e',1,'Tgstation.Server.Host.System.Process.Process()']]], ['process',['Process',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process.html',1,'Tgstation::Server::Host::System']]], ['process_2ecs',['Process.cs',['../_process_8cs.html',1,'']]], ['processbridgerequest',['ProcessBridgeRequest',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#abd9eb0f8c590b9983279300be002d40e',1,'Tgstation.Server.Host.Components.InstanceManager.ProcessBridgeRequest()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_dispatcher.html#aedd0ce59a5bb9d3692f73e92715798c3',1,'Tgstation.Server.Host.Components.Interop.Bridge.IBridgeDispatcher.ProcessBridgeRequest()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#addf10bdb560585e28933161df7e31fef',1,'Tgstation.Server.Host.Components.Session.SessionController.ProcessBridgeRequest()']]], - ['processexecutor',['processExecutor',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_windows_byond_installer.html#a9ddafa8d405709ae20f157c9041b4b01',1,'Tgstation.Server.Host.Components.Byond.WindowsByondInstaller.processExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#a8d40fb1d02610364e59b9feedb4e75d9',1,'Tgstation.Server.Host.Components.Deployment.DreamMaker.processExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#a5fdcd121985cce7d20f2d61e0ea5eca4',1,'Tgstation.Server.Host.Components.InstanceFactory.processExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#a1e5abb3512c946d17d762285046b6e56',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.processExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#a0eb31611253abc0c887ec46f1e9b8611',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.processExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process_executor.html#a391667cca8308cb0adb27851434cb3fe',1,'Tgstation.Server.Host.System.ProcessExecutor.ProcessExecutor()']]], + ['processexecutor',['ProcessExecutor',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process_executor.html#a391667cca8308cb0adb27851434cb3fe',1,'Tgstation.Server.Host.System.ProcessExecutor.ProcessExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_windows_byond_installer.html#a9ddafa8d405709ae20f157c9041b4b01',1,'Tgstation.Server.Host.Components.Byond.WindowsByondInstaller.processExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#a8d40fb1d02610364e59b9feedb4e75d9',1,'Tgstation.Server.Host.Components.Deployment.DreamMaker.processExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#a5fdcd121985cce7d20f2d61e0ea5eca4',1,'Tgstation.Server.Host.Components.InstanceFactory.processExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#a1e5abb3512c946d17d762285046b6e56',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.processExecutor()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#a0eb31611253abc0c887ec46f1e9b8611',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.processExecutor()']]], ['processexecutor',['ProcessExecutor',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process_executor.html',1,'Tgstation::Server::Host::System']]], ['processexecutor_2ecs',['ProcessExecutor.cs',['../_process_executor_8cs.html',1,'']]], ['processfeatures',['processFeatures',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process.html#ab37b740e7d72723a6e5fce40acf040c9',1,'Tgstation.Server.Host.System.Process.processFeatures()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process_executor.html#a53a69845e62419a4db84ef86445d1eba',1,'Tgstation.Server.Host.System.ProcessExecutor.processFeatures()']]], diff --git a/search/all_12.js b/search/all_12.js index abb1866196..64251889b5 100644 --- a/search/all_12.js +++ b/search/all_12.js @@ -2,7 +2,7 @@ var searchData= [ ['ratelimit',['RateLimit',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#ae04b510fcc8e0f4cc9e380b3995e2f21',1,'Tgstation::Server::Host::Controllers::AdministrationController']]], ['ratelimitexception',['RateLimitException',['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html',1,'Tgstation::Server::Client']]], - ['ratelimitexception',['RateLimitException',['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#a8c920b2d1a4eb58bf405d784e6e02fb4',1,'Tgstation.Server.Client.RateLimitException.RateLimitException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#a1bd82db7496764293896d95a761a5b1c',1,'Tgstation.Server.Client.RateLimitException.RateLimitException()'],['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#ab30825486f294c98083fb35043f5da0a',1,'Tgstation.Server.Client.RateLimitException.RateLimitException(string message, Exception innerException)']]], + ['ratelimitexception',['RateLimitException',['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#a01a8b1511a78e0d95e2582d1cb5d2a6f',1,'Tgstation.Server.Client.RateLimitException.RateLimitException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#a1bd82db7496764293896d95a761a5b1c',1,'Tgstation.Server.Client.RateLimitException.RateLimitException()'],['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#ab30825486f294c98083fb35043f5da0a',1,'Tgstation.Server.Client.RateLimitException.RateLimitException(string message, Exception innerException)']]], ['ratelimitexception_2ecs',['RateLimitException.cs',['../_rate_limit_exception_8cs.html',1,'']]], ['rawcheckout',['RawCheckout',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html#ace81561f45095dabdd6969b281a2be08',1,'Tgstation::Server::Host::Components::Repository::Repository']]], ['rawuseragent',['RawUserAgent',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#a8e9f868cdb40bcbfb9fb5b8cdee7a5a3',1,'Tgstation::Server::Api::ApiHeaders']]], @@ -41,10 +41,10 @@ var searchData= ['realid',['RealId',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_channel_representation.html#a983af752bfee4125d2a94370dc128c48',1,'Tgstation.Server.Host.Components.Chat.ChannelRepresentation.RealId()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_user.html#ae11079589a9c5466c125247cba0e3ec4',1,'Tgstation.Server.Host.Components.Chat.ChatUser.RealId()']]], ['reattach',['Reattach',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_i_session_controller_factory.html#ac583487a447259a5698bda86205cf593',1,'Tgstation.Server.Host.Components.Session.ISessionControllerFactory.Reattach()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#a43661606c528da37b2aea840fe5766ef',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.Reattach()']]], ['reattachinfohandler',['ReattachInfoHandler',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_reattach_info_handler.html',1,'Tgstation::Server::Host::Components::Session']]], - ['reattachinfohandler',['ReattachInfoHandler',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_reattach_info_handler.html#a4edd70e178d63423a29b5a1eba88f402',1,'Tgstation.Server.Host.Components.Session.ReattachInfoHandler.ReattachInfoHandler()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#a72ff0b6fb336a2d931cf64d795d8a542',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.reattachInfoHandler()']]], + ['reattachinfohandler',['reattachInfoHandler',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#a72ff0b6fb336a2d931cf64d795d8a542',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.reattachInfoHandler()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_reattach_info_handler.html#a4edd70e178d63423a29b5a1eba88f402',1,'Tgstation.Server.Host.Components.Session.ReattachInfoHandler.ReattachInfoHandler()']]], ['reattachinfohandler_2ecs',['ReattachInfoHandler.cs',['../_reattach_info_handler_8cs.html',1,'']]], ['reattachinformation',['ReattachInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_reattach_information.html',1,'Tgstation::Server::Host::Components::Session']]], - ['reattachinformation',['ReattachInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_reattach_information.html#af91a1e36e4a5bbac1057167f4e4ee830',1,'Tgstation.Server.Host.Components.Session.ReattachInformation.ReattachInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#a2f27422dea8fc27fb0c7f1964f455319',1,'Tgstation.Server.Host.Components.Session.SessionController.reattachInformation()']]], + ['reattachinformation',['reattachInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#a2f27422dea8fc27fb0c7f1964f455319',1,'Tgstation.Server.Host.Components.Session.SessionController.reattachInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_reattach_information.html#af91a1e36e4a5bbac1057167f4e4ee830',1,'Tgstation.Server.Host.Components.Session.ReattachInformation.ReattachInformation()']]], ['reattachinformation',['ReattachInformation',['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_reattach_information.html',1,'Tgstation::Server::Host::Models']]], ['reattachinformation_2ecs',['ReattachInformation.cs',['../_components_2_session_2_reattach_information_8cs.html',1,'']]], ['reattachinformation_2ecs',['ReattachInformation.cs',['../_models_2_reattach_information_8cs.html',1,'']]], @@ -66,6 +66,7 @@ var searchData= ['reconnecttask',['reconnectTask',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#abcd5569a84d1d67a6b891b7bf7f5c6df',1,'Tgstation::Server::Host::Components::Chat::Providers::Provider']]], ['reconnecttasklock',['reconnectTaskLock',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#ac3f1219e16c1c8e9e32a07697d3affde',1,'Tgstation::Server::Host::Components::Chat::Providers::Provider']]], ['reference',['Reference',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html#a7409d655bc6993e0eb1d75743160a3b2',1,'Tgstation.Server.Api.Models.Repository.Reference()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_i_repository.html#a36f1520633eca12a57b6ed52c60c5257',1,'Tgstation.Server.Host.Components.Repository.IRepository.Reference()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html#a019e9f75f0129457c1f6f2e44e9736d0',1,'Tgstation.Server.Host.Components.Repository.Repository.Reference()']]], + ['refreshtoken',['RefreshToken',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#abd283e3f14a6be71a3d4a8dedd12123e',1,'Tgstation::Server::Client::ApiClient']]], ['registercommandhandler',['RegisterCommandHandler',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html#ae1b556bac2bf1a08d0eef0fd76297dc4',1,'Tgstation.Server.Host.Components.Chat.ChatManager.RegisterCommandHandler()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_i_chat_manager.html#a1e1ce49aa3e843a1e037060a544e125e',1,'Tgstation.Server.Host.Components.Chat.IChatManager.RegisterCommandHandler()']]], ['registeredprocesses',['registeredProcesses',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_windows_network_prompt_reaper.html#afffde309e8027f1373df6c95393c643a',1,'Tgstation::Server::Host::System::WindowsNetworkPromptReaper']]], ['registerforrestart',['RegisterForRestart',['../interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control.html#a35728d66030addfd5551351b8c6db942',1,'Tgstation.Server.Host.Core.IServerControl.RegisterForRestart()'],['../class_tgstation_1_1_server_1_1_host_1_1_server.html#a0664caef3896a16c9f668177c68002d3',1,'Tgstation.Server.Host.Server.RegisterForRestart()']]], @@ -123,8 +124,8 @@ var searchData= ['repositoryrights',['RepositoryRights',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_instance_user.html#a2c2572f088140bfc2a3f02a70125024f',1,'Tgstation.Server.Api.Models.InstanceUser.RepositoryRights()'],['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#a95336db3db40dbae9701ef6e51240f39',1,'Tgstation.Server.Api.Rights.RepositoryRights()']]], ['repositoryrights_2ecs',['RepositoryRights.cs',['../_repository_rights_8cs.html',1,'']]], ['repositorysettings',['RepositorySettings',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_repository_settings.html',1,'Tgstation::Server::Api::Models::Internal']]], - ['repositorysettings',['RepositorySettings',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#a02bd03c7389ee5a00c4287ddb2720e0a',1,'Tgstation.Server.Host.Database.DatabaseContext.RepositorySettings()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#a47f19bce8a6cf195fca87b785c5eb3a8',1,'Tgstation.Server.Host.Database.DatabaseContext.RepositorySettings()'],['../interface_tgstation_1_1_server_1_1_host_1_1_database_1_1_i_database_context.html#ad7cf0bea5adc322eea2cf52b41eb0945',1,'Tgstation.Server.Host.Database.IDatabaseContext.RepositorySettings()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_instance.html#a389d4c0693f617133a4a8db430c65ab4',1,'Tgstation.Server.Host.Models.Instance.RepositorySettings()']]], ['repositorysettings',['RepositorySettings',['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_repository_settings.html',1,'Tgstation::Server::Host::Models']]], + ['repositorysettings',['RepositorySettings',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#a02bd03c7389ee5a00c4287ddb2720e0a',1,'Tgstation.Server.Host.Database.DatabaseContext.RepositorySettings()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#a47f19bce8a6cf195fca87b785c5eb3a8',1,'Tgstation.Server.Host.Database.DatabaseContext.RepositorySettings()'],['../interface_tgstation_1_1_server_1_1_host_1_1_database_1_1_i_database_context.html#ad7cf0bea5adc322eea2cf52b41eb0945',1,'Tgstation.Server.Host.Database.IDatabaseContext.RepositorySettings()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_instance.html#a389d4c0693f617133a4a8db430c65ab4',1,'Tgstation.Server.Host.Models.Instance.RepositorySettings()']]], ['repositorysettings_2ecs',['RepositorySettings.cs',['../_tgstation_8_server_8_host_2_models_2_repository_settings_8cs.html',1,'']]], ['repositorysettings_2ecs',['RepositorySettings.cs',['../_tgstation_8_server_8_api_2_models_2_internal_2_repository_settings_8cs.html',1,'']]], ['repositorysettingscollection',['repositorySettingsCollection',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#a88e7ea0a898418b1f6bfa052e6eeccb4',1,'Tgstation::Server::Host::Database::DatabaseContext']]], @@ -134,8 +135,8 @@ var searchData= ['repowhitespacecommitteremail',['RepoWhitespaceCommitterEmail',['../namespace_tgstation_1_1_server_1_1_api_1_1_models.html#a1a6adb59dd51244efabf4e548e7075c8a8b7cce03b4c7425e09ac4534a51b95b0',1,'Tgstation::Server::Api::Models']]], ['repowhitespacecommittername',['RepoWhitespaceCommitterName',['../namespace_tgstation_1_1_server_1_1_api_1_1_models.html#a1a6adb59dd51244efabf4e548e7075c8a5aac4df009b9b8c791899cd52c320593',1,'Tgstation::Server::Api::Models']]], ['requestloggers',['requestLoggers',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a8c0c69d8203c1af593869613dfb1f8cc',1,'Tgstation::Server::Client::ApiClient']]], - ['requesttimeoutexception',['RequestTimeoutException',['../class_tgstation_1_1_server_1_1_client_1_1_request_timeout_exception.html',1,'Tgstation::Server::Client']]], ['requesttimeoutexception',['RequestTimeoutException',['../class_tgstation_1_1_server_1_1_client_1_1_request_timeout_exception.html#adc20278896b65c6ae42aa61012a6576f',1,'Tgstation.Server.Client.RequestTimeoutException.RequestTimeoutException(HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_request_timeout_exception.html#a85873681c9fb67bfd55b8751ba791e5b',1,'Tgstation.Server.Client.RequestTimeoutException.RequestTimeoutException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_request_timeout_exception.html#a80635931d781eaab27edcae371a77e7c',1,'Tgstation.Server.Client.RequestTimeoutException.RequestTimeoutException(string message, Exception innerException)']]], + ['requesttimeoutexception',['RequestTimeoutException',['../class_tgstation_1_1_server_1_1_client_1_1_request_timeout_exception.html',1,'Tgstation::Server::Client']]], ['requesttimeoutexception_2ecs',['RequestTimeoutException.cs',['../_request_timeout_exception_8cs.html',1,'']]], ['requireheaders',['requireHeaders',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_api_controller.html#a91c58c31f7a3cc51fff3cfc3b236756d',1,'Tgstation::Server::Host::Controllers::ApiController']]], ['requireinstance',['requireInstance',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_api_controller.html#afe4f020656eb2d02def6ff389fd33bcb',1,'Tgstation::Server::Host::Controllers::ApiController']]], @@ -156,8 +157,8 @@ var searchData= ['restarthandlers',['restartHandlers',['../class_tgstation_1_1_server_1_1_host_1_1_server.html#a6fd99785ffec475ce3d3835c1206505f',1,'Tgstation::Server::Host::Server']]], ['restarthost',['RestartHost',['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#affda1440f9c5e2dfcca65138d2f89a21a4ebebe34711c9ddafa1eff5faa9a377e',1,'Tgstation::Server::Api::Rights']]], ['restartlock',['restartLock',['../class_tgstation_1_1_server_1_1_host_1_1_server.html#abf1047eeb7fc9f915460a1bacc58666e',1,'Tgstation::Server::Host::Server']]], - ['restartregistration',['RestartRegistration',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_restart_registration.html',1,'Tgstation::Server::Host::Core']]], ['restartregistration',['restartRegistration',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html#ac3dec357800cfc9a526c82d61fe99803',1,'Tgstation.Server.Host.Components.Chat.ChatManager.restartRegistration()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#aefe25fd33c4a6dfb4ea27b583a2286ea',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.restartRegistration()'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_restart_registration.html#a4391d21de43d098a833168620dc24dc4',1,'Tgstation.Server.Host.Core.RestartRegistration.RestartRegistration()']]], + ['restartregistration',['RestartRegistration',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_restart_registration.html',1,'Tgstation::Server::Host::Core']]], ['restartregistration_2ecs',['RestartRegistration.cs',['../_restart_registration_8cs.html',1,'']]], ['restartrequested',['RestartRequested',['../interface_tgstation_1_1_server_1_1_host_1_1_i_server.html#ac87902a08c496cd40add682a2ec6c616',1,'Tgstation.Server.Host.IServer.RestartRequested()'],['../class_tgstation_1_1_server_1_1_host_1_1_server.html#add86891cdb4011d078e94671466ef14e',1,'Tgstation.Server.Host.Server.RestartRequested()']]], ['restarttimeout',['RestartTimeout',['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html#addf96ec4a3f16bc7d3d2cd94347dd1d3',1,'Tgstation::Server::Host::Configuration::GeneralConfiguration']]], @@ -169,15 +170,15 @@ var searchData= ['revinfotestmerge_2ecs',['RevInfoTestMerge.cs',['../_rev_info_test_merge_8cs.html',1,'']]], ['revinfotestmerges',['RevInfoTestMerges',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#a0d128ca74b004556ed82a0daff4a31df',1,'Tgstation::Server::Host::Database::DatabaseContext']]], ['revision',['Revision',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_runtime_information.html#a7809d348cb5248ec9ae4bb189fda706b',1,'Tgstation.Server.Host.Components.Interop.Bridge.RuntimeInformation.Revision()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_test_merge_information.html#a916aa6662307599b495fb7ddcdb43766',1,'Tgstation.Server.Host.Components.Interop.Bridge.TestMergeInformation.Revision()']]], - ['revisioncommand',['RevisionCommand',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_revision_command.html#a69b5a156fd8b4e7c8f9396d967d435e2',1,'Tgstation::Server::Host::Components::Chat::Commands::RevisionCommand']]], ['revisioncommand',['RevisionCommand',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_revision_command.html',1,'Tgstation::Server::Host::Components::Chat::Commands']]], + ['revisioncommand',['RevisionCommand',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_revision_command.html#a69b5a156fd8b4e7c8f9396d967d435e2',1,'Tgstation::Server::Host::Components::Chat::Commands::RevisionCommand']]], ['revisioncommand_2ecs',['RevisionCommand.cs',['../_revision_command_8cs.html',1,'']]], ['revisioninformation',['RevisionInformation',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_revision_information.html',1,'Tgstation::Server::Api::Models']]], ['revisioninformation',['RevisionInformation',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_revision_information.html',1,'Tgstation::Server::Api::Models::Internal']]], ['revisioninformation',['RevisionInformation',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_compile_job.html#a1d1274044dfcbf5e6e69798cc57beab5',1,'Tgstation.Server.Api.Models.CompileJob.RevisionInformation()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html#a0ff964c98b1bd3dac0c2d0d77f223492',1,'Tgstation.Server.Api.Models.Repository.RevisionInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_compile_job.html#a30c14e7fc4c770ff11cd0f10be4fb4dd',1,'Tgstation.Server.Host.Models.CompileJob.RevisionInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_rev_info_test_merge.html#a84e7e578fe5c0d7c09dc22f9085d7367',1,'Tgstation.Server.Host.Models.RevInfoTestMerge.RevisionInformation()']]], ['revisioninformation',['RevisionInformation',['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_revision_information.html',1,'Tgstation::Server::Host::Models']]], - ['revisioninformation_2ecs',['RevisionInformation.cs',['../_tgstation_8_server_8_host_2_models_2_revision_information_8cs.html',1,'']]], ['revisioninformation_2ecs',['RevisionInformation.cs',['../_tgstation_8_server_8_api_2_models_2_internal_2_revision_information_8cs.html',1,'']]], + ['revisioninformation_2ecs',['RevisionInformation.cs',['../_tgstation_8_server_8_host_2_models_2_revision_information_8cs.html',1,'']]], ['revisioninformation_2ecs',['RevisionInformation.cs',['../_tgstation_8_server_8_api_2_models_2_revision_information_8cs.html',1,'']]], ['revisioninformations',['RevisionInformations',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#ad6aa867ea9e6fa04646c76ecdb3ab8a0',1,'Tgstation.Server.Host.Database.DatabaseContext.RevisionInformations()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#a9d168b4868e30473ab73f97fd7ee3dea',1,'Tgstation.Server.Host.Database.DatabaseContext.RevisionInformations()'],['../interface_tgstation_1_1_server_1_1_host_1_1_database_1_1_i_database_context.html#a5633532d3f76127562905242d6a14e91',1,'Tgstation.Server.Host.Database.IDatabaseContext.RevisionInformations()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_instance.html#ae46b0c821ef239baa662cc6449042428',1,'Tgstation.Server.Host.Models.Instance.RevisionInformations()']]], ['revisioninformationscollection',['revisionInformationsCollection',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#a0436e6b418ebf670b6def1f565da10cd',1,'Tgstation::Server::Host::Database::DatabaseContext']]], @@ -198,8 +199,8 @@ var searchData= ['rundreammaker',['RunDreamMaker',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#a2c7d7d0048942b67b6c275cd4a643296',1,'Tgstation::Server::Host::Components::Deployment::DreamMaker']]], ['runimpersonated',['RunImpersonated',['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_system_identity.html#a9b525a56c704424ce14e78edcb7d003e',1,'Tgstation.Server.Host.Security.ISystemIdentity.RunImpersonated()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_posix_system_identity.html#ad8b0bb1cd74b0bc433afeb4dcc322594',1,'Tgstation.Server.Host.Security.PosixSystemIdentity.RunImpersonated()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_windows_system_identity.html#aaa070f717bb19b6d85f0253d7820bc75',1,'Tgstation.Server.Host.Security.WindowsSystemIdentity.RunImpersonated()']]], ['runjob',['RunJob',['../class_tgstation_1_1_server_1_1_host_1_1_jobs_1_1_job_manager.html#a6836fc87ae58cab85a16f3018d485eb1',1,'Tgstation::Server::Host::Jobs::JobManager']]], - ['running',['running',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#aed7531909b8a809544b643c0d74933aa',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.running()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_dream_daemon.html#ae0b56fabcaa54dd916f95949914543ef',1,'Tgstation.Server.Api.Models.DreamDaemon.Running()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_i_watchdog.html#a26fc404928f808be57119b4dcd5edbb2',1,'Tgstation.Server.Host.Components.Watchdog.IWatchdog.Running()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#a27e3ca033079704cca1f215e31f4cc0b',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.Running()']]], - ['runrequest_3c_20tresult_20_3e',['RunRequest< TResult >',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#ac61843965bd203ff9a844f6286a68818',1,'Tgstation::Server::Client::ApiClient']]], + ['running',['Running',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_dream_daemon.html#ae0b56fabcaa54dd916f95949914543ef',1,'Tgstation.Server.Api.Models.DreamDaemon.Running()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_i_watchdog.html#a26fc404928f808be57119b4dcd5edbb2',1,'Tgstation.Server.Host.Components.Watchdog.IWatchdog.Running()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#a27e3ca033079704cca1f215e31f4cc0b',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.Running()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#aed7531909b8a809544b643c0d74933aa',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.running()']]], + ['runrequest_3c_20tresult_20_3e',['RunRequest< TResult >',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a5ff438a63f0eaf323c7cf6adc8532cba',1,'Tgstation::Server::Client::ApiClient']]], ['runtask',['runTask',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_windows_network_prompt_reaper.html#afb100cbab0aa9560e4831bee360f9c0f',1,'Tgstation::Server::Host::System::WindowsNetworkPromptReaper']]], ['runtimeinformation',['RuntimeInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_runtime_information.html',1,'Tgstation::Server::Host::Components::Interop::Bridge']]], ['runtimeinformation',['RuntimeInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_bridge_response.html#a8b6ace0334f5100fb3fa41e5a2755bda',1,'Tgstation.Server.Host.Components.Interop.Bridge.BridgeResponse.RuntimeInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_reattach_information.html#a0fd772ae059a153465f76bda841292ef',1,'Tgstation.Server.Host.Components.Session.ReattachInformation.RuntimeInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_runtime_information.html#a75e05074b79b83f0c57cd15e25b77dae',1,'Tgstation.Server.Host.Components.Interop.Bridge.RuntimeInformation.RuntimeInformation()']]], diff --git a/search/all_13.js b/search/all_13.js index 1de4f1dd18..a9df8471a9 100644 --- a/search/all_13.js +++ b/search/all_13.js @@ -19,6 +19,7 @@ var searchData= ['seedadminuser',['SeedAdminUser',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_seeder.html#a196c86026367b0e221d580ef36d97c83',1,'Tgstation::Server::Host::Database::DatabaseSeeder']]], ['seeddatabase',['SeedDatabase',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_seeder.html#a09fc7208df756283dd6b475cfe013151',1,'Tgstation.Server.Host.Database.DatabaseSeeder.SeedDatabase()'],['../interface_tgstation_1_1_server_1_1_host_1_1_database_1_1_i_database_seeder.html#afbad62130c7423f9de442d6d761d3524',1,'Tgstation.Server.Host.Database.IDatabaseSeeder.SeedDatabase()']]], ['semaphore',['semaphore',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_byond_manager.html#a192e5a69ce9c2300f588cc65af3a64f2',1,'Tgstation.Server.Host.Components.Byond.ByondManager.semaphore()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_windows_byond_installer.html#ae6aa8519a6401fc95218f2de88080605',1,'Tgstation.Server.Host.Components.Byond.WindowsByondInstaller.semaphore()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository_manager.html#a550982dbdc2ce226e878702a520bbe27',1,'Tgstation.Server.Host.Components.Repository.RepositoryManager.semaphore()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#af64bb7e6efa9991a5a3a14993a3277e8',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.semaphore()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#a96d8bb542f16398eada0a59c00955005',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.Semaphore()']]], + ['semaphoreslim',['semaphoreSlim',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#ada5764977cc200cc19bb38db0b0fe1e4',1,'Tgstation::Server::Client::ApiClient']]], ['semaphoreslimcontext',['SemaphoreSlimContext',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_semaphore_slim_context.html#a600d8d0a279d2fe17d1f48c1a1bd17d4',1,'Tgstation::Server::Host::Core::SemaphoreSlimContext']]], ['semaphoreslimcontext',['SemaphoreSlimContext',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_semaphore_slim_context.html',1,'Tgstation::Server::Host::Core']]], ['semaphoreslimcontext_2ecs',['SemaphoreSlimContext.cs',['../_semaphore_slim_context_8cs.html',1,'']]], @@ -43,7 +44,7 @@ var searchData= ['serverclientfactory_2ecs',['ServerClientFactory.cs',['../_server_client_factory_8cs.html',1,'']]], ['servercontrol',['serverControl',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager_factory.html#a6b86256a68eff2c15b42badd8159a2f0',1,'Tgstation.Server.Host.Components.Chat.ChatManagerFactory.serverControl()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#ac2ba10fddea5448f4c8c08f363a0e391',1,'Tgstation.Server.Host.Components.InstanceManager.serverControl()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_factory.html#a83607d97387c92ed33c6640f0cd4ee1a',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogFactory.ServerControl()']]], ['servererrorexception',['ServerErrorException',['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html',1,'Tgstation::Server::Client']]], - ['servererrorexception',['ServerErrorException',['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a3c0883bc41902cff9f2becfdb64c0d97',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a0f2341503ba27d13ffdff703a7f99d5b',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a2426a2796880232113cfd743b1010fdb',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException(string message, Exception innerException)']]], + ['servererrorexception',['ServerErrorException',['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a3c0883bc41902cff9f2becfdb64c0d97',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a5d6579c3318e55695667c1688ad50050',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a2426a2796880232113cfd743b1010fdb',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException(string message, Exception innerException)']]], ['servererrorexception_2ecs',['ServerErrorException.cs',['../_server_error_exception_8cs.html',1,'']]], ['serverfactory',['ServerFactory',['../class_tgstation_1_1_server_1_1_host_1_1_server_factory.html',1,'Tgstation::Server::Host']]], ['serverfactory_2ecs',['ServerFactory.cs',['../_server_factory_8cs.html',1,'']]], @@ -77,7 +78,7 @@ var searchData= ['sessioncontroller',['SessionController',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html',1,'Tgstation::Server::Host::Components::Session']]], ['sessioncontroller_2ecs',['SessionController.cs',['../_session_controller_8cs.html',1,'']]], ['sessioncontrollerfactory',['SessionControllerFactory',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html',1,'Tgstation::Server::Host::Components::Session']]], - ['sessioncontrollerfactory',['SessionControllerFactory',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#a24eb5f0e32cd79487cd9bd80d5257abb',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.SessionControllerFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#aadb8f0ef87699ead4e845935591b542f',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.SessionControllerFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#a8322a848923985ee4e06d792dccb8643',1,'Tgstation.Server.Host.Components.Deployment.DreamMaker.sessionControllerFactory()']]], + ['sessioncontrollerfactory',['sessionControllerFactory',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#a8322a848923985ee4e06d792dccb8643',1,'Tgstation.Server.Host.Components.Deployment.DreamMaker.sessionControllerFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#a24eb5f0e32cd79487cd9bd80d5257abb',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.SessionControllerFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#aadb8f0ef87699ead4e845935591b542f',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.SessionControllerFactory()']]], ['sessioncontrollerfactory_2ecs',['SessionControllerFactory.cs',['../_session_controller_factory_8cs.html',1,'']]], ['setactiveversion',['SetActiveVersion',['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_byond_client.html#a798c9e8b4b224f3a78791f765d69b304',1,'Tgstation.Server.Client.Components.ByondClient.SetActiveVersion()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_byond_client.html#a76a30723a9370f391d4ca12fb31e5c71',1,'Tgstation.Server.Client.Components.IByondClient.SetActiveVersion()']]], ['setapivalidationport',['SetApiValidationPort',['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#a6aac419b6a793fe07657d96ac48dee5da926d34b9044ba920f715e8e2f6a72c62',1,'Tgstation::Server::Api::Rights']]], @@ -128,8 +129,8 @@ var searchData= ['softrestart',['SoftRestart',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_dream_daemon.html#a99e232d9a57aab1dc4546af7bc06b1ab',1,'Tgstation.Server.Api.Models.DreamDaemon.SoftRestart()'],['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#acf2642afce6567c03b79876139a10a49a31d920012be69305899466206d2a4823',1,'Tgstation.Server.Api.Rights.SoftRestart()']]], ['softshutdown',['SoftShutdown',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_dream_daemon.html#a80bd66c7667b51766516f04777da66b4',1,'Tgstation.Server.Api.Models.DreamDaemon.SoftShutdown()'],['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#acf2642afce6567c03b79876139a10a49ae814f60fe597531f5630e0aef1adea94',1,'Tgstation.Server.Api.Rights.SoftShutdown()']]], ['sqlite',['Sqlite',['../namespace_tgstation_1_1_server_1_1_host_1_1_configuration.html#acda8f6ee23a9ee8ac298f7b4a7d5617ca283f4ac146a612fabb50409a640cf21b',1,'Tgstation::Server::Host::Configuration']]], - ['sqlitedatabasecontext',['SqliteDatabaseContext',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_sqlite_database_context.html#abda09fdb86a5c362d15294c3f7ecd672',1,'Tgstation::Server::Host::Database::SqliteDatabaseContext']]], ['sqlitedatabasecontext',['SqliteDatabaseContext',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_sqlite_database_context.html',1,'Tgstation::Server::Host::Database']]], + ['sqlitedatabasecontext',['SqliteDatabaseContext',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_sqlite_database_context.html#abda09fdb86a5c362d15294c3f7ecd672',1,'Tgstation::Server::Host::Database::SqliteDatabaseContext']]], ['sqlitedatabasecontext_2ecs',['SqliteDatabaseContext.cs',['../_sqlite_database_context_8cs.html',1,'']]], ['sqlitedatabasecontextmodelsnapshot',['SqliteDatabaseContextModelSnapshot',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_sqlite_database_context_model_snapshot.html',1,'Tgstation::Server::Host::Database::Migrations']]], ['sqlitedatabasecontextmodelsnapshot_2ecs',['SqliteDatabaseContextModelSnapshot.cs',['../_sqlite_database_context_model_snapshot_8cs.html',1,'']]], @@ -172,8 +173,8 @@ var searchData= ['symlinkfactory',['symlinkFactory',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_windows_swappable_dmb_provider.html#a2993c629ac06d9e8ee6545f656fefc0f',1,'Tgstation.Server.Host.Components.Deployment.WindowsSwappableDmbProvider.symlinkFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#ae9583eef7d68626e70fd30097b6db87c',1,'Tgstation.Server.Host.Components.InstanceFactory.symlinkFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#a5a52deb2c99db89b8e8dfb4bf1a5fd3f',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.symlinkFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_windows_watchdog.html#a65ab5bf0783d6732eb6b1018f7a1983a',1,'Tgstation.Server.Host.Components.Watchdog.WindowsWatchdog.symlinkFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_windows_watchdog_factory.html#a288b7e557640d4f0f847a054847abf3a',1,'Tgstation.Server.Host.Components.Watchdog.WindowsWatchdogFactory.symlinkFactory()']]], ['symlinkstaticfilesto',['SymlinkStaticFilesTo',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#af7ad93cfb1c7c7718a3b1ffe3e12d42d',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.SymlinkStaticFilesTo()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_i_configuration.html#a255b4504ec55e9a4c006463db8aaa61e',1,'Tgstation.Server.Host.Components.StaticFiles.IConfiguration.SymlinkStaticFilesTo()']]], ['synchronizationlock',['synchronizationLock',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html#a1ac2daf67a09f128290588a5181c88ba',1,'Tgstation.Server.Host.Components.Chat.ChatManager.synchronizationLock()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_tracking_context.html#ab3b8d2b85773e2d5e5f63aff3e8c27eb',1,'Tgstation.Server.Host.Components.Chat.ChatTrackingContext.synchronizationLock()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#a0bbceae0e217ef75fc3989124017856d',1,'Tgstation.Server.Host.Components.Session.SessionController.synchronizationLock()'],['../class_tgstation_1_1_server_1_1_host_1_1_jobs_1_1_job_manager.html#aec64bbed8584f4daad12932098999e1b',1,'Tgstation.Server.Host.Jobs.JobManager.synchronizationLock()']]], - ['synchronousiomanager',['synchronousIOManager',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#a2b14e009e117f22b206bbdf743712809',1,'Tgstation.Server.Host.Components.InstanceFactory.synchronousIOManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#a29a360d050eedfecf24a5d7a75331f1d',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.synchronousIOManager()']]], ['synchronousiomanager',['SynchronousIOManager',['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_synchronous_i_o_manager.html',1,'Tgstation::Server::Host::IO']]], + ['synchronousiomanager',['synchronousIOManager',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#a2b14e009e117f22b206bbdf743712809',1,'Tgstation.Server.Host.Components.InstanceFactory.synchronousIOManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#a29a360d050eedfecf24a5d7a75331f1d',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.synchronousIOManager()']]], ['synchronousiomanager_2ecs',['SynchronousIOManager.cs',['../_synchronous_i_o_manager_8cs.html',1,'']]], ['systemidentifier',['SystemIdentifier',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_user.html#afa19e8180a87c821a7b77d27765c1824',1,'Tgstation::Server::Api::Models::Internal::User']]], ['systemidentity',['SystemIdentity',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_authentication_context.html#a05ce72960f78e0a11148f44292f3fb7b',1,'Tgstation.Server.Host.Security.AuthenticationContext.SystemIdentity()'],['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_authentication_context.html#a01b4dc318a62fa3b321593418eb1247e',1,'Tgstation.Server.Host.Security.IAuthenticationContext.SystemIdentity()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_identity_cache_object.html#a86a823a9e7ee94f2497bfae86c1efeed',1,'Tgstation.Server.Host.Security.IdentityCacheObject.SystemIdentity()']]], diff --git a/search/all_14.js b/search/all_14.js index d9d196e76d..a3ea441ec4 100644 --- a/search/all_14.js +++ b/search/all_14.js @@ -79,14 +79,15 @@ var searchData= ['timertask',['timerTask',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance.html#abf825c028f20cd903db1362892dbd833',1,'Tgstation::Server::Host::Components::Instance']]], ['titleatmerge',['TitleAtMerge',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_test_merge_base.html#a959c2922c726be725daac329f9b21e46',1,'Tgstation::Server::Api::Models::Internal::TestMergeBase']]], ['toapi',['ToApi',['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_chat_bot.html#ac6049a16c80f49d1503935589a5e0042',1,'Tgstation.Server.Host.Models.ChatBot.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_chat_channel.html#a01525688d871d70d1ab28cdda6083011',1,'Tgstation.Server.Host.Models.ChatChannel.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_compile_job.html#a64a9fc0f8da344e311b5b754eec10611',1,'Tgstation.Server.Host.Models.CompileJob.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_dream_maker_settings.html#ab477e13756ea540790df3e245e17173b',1,'Tgstation.Server.Host.Models.DreamMakerSettings.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_instance.html#a169bffb644b7ff7c3686c29373f662c1',1,'Tgstation.Server.Host.Models.Instance.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_instance_user.html#aad0b6b9eaf0b0a9462987564aa254b68',1,'Tgstation.Server.Host.Models.InstanceUser.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_job.html#aaea7f482968921045b73cbdaca4dd652',1,'Tgstation.Server.Host.Models.Job.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_repository_settings.html#acff6370af1842c659c6f8ecb85dd0ea3',1,'Tgstation.Server.Host.Models.RepositorySettings.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_revision_information.html#a8e6f5b4375c0594f10c413c4119515b1',1,'Tgstation.Server.Host.Models.RevisionInformation.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_test_merge.html#ac2943458d2fc3077a6684408fb26ab09',1,'Tgstation.Server.Host.Models.TestMerge.ToApi()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_user.html#a8effbd8cd0ce7ba4c72f40efc08a9b50',1,'Tgstation.Server.Host.Models.User.ToApi(bool recursive, bool showDetails)'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_user.html#a1d6fee3f42a73a1505d6d0d12d68b3d9',1,'Tgstation.Server.Host.Models.User.ToApi(bool showDetails)']]], - ['token',['token',['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#a7137670591717642aa084370ed40377e',1,'Tgstation.Server.Client.ServerClient.token()'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#a6a6105b1f7b3dafd096e5e11057aac8d',1,'Tgstation.Server.Api.ApiHeaders.Token()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client.html#ab3363ffdb533616bd2b6688559f9def1',1,'Tgstation.Server.Client.IServerClient.Token()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#a208bd87b1345c14d9da5e89df13d965f',1,'Tgstation.Server.Client.ServerClient.Token()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_program_shutdown_token_source.html#a24771450ef1521a1c51fc0291e3c67a0',1,'Tgstation.Server.Host.System.ProgramShutdownTokenSource.Token()']]], + ['token',['Token',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#a6a6105b1f7b3dafd096e5e11057aac8d',1,'Tgstation.Server.Api.ApiHeaders.Token()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client.html#ab3363ffdb533616bd2b6688559f9def1',1,'Tgstation.Server.Client.IServerClient.Token()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#a208bd87b1345c14d9da5e89df13d965f',1,'Tgstation.Server.Client.ServerClient.Token()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_program_shutdown_token_source.html#a24771450ef1521a1c51fc0291e3c67a0',1,'Tgstation.Server.Host.System.ProgramShutdownTokenSource.Token()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#a7137670591717642aa084370ed40377e',1,'Tgstation.Server.Client.ServerClient.token()']]], ['token',['Token',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_token.html',1,'Tgstation::Server::Api::Models']]], ['token_2ecs',['Token.cs',['../_token_8cs.html',1,'']]], ['tokenclockskewminutes',['TokenClockSkewMinutes',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#abbf0169740f418e6ffd4f8c783fdff50',1,'Tgstation::Server::Host::Security::TokenFactory']]], ['tokenexpiryminutes',['TokenExpiryMinutes',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#a0cea7772b1f5107da73c23e821e76b43',1,'Tgstation::Server::Host::Security::TokenFactory']]], + ['tokenfactory',['tokenFactory',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#afd05899e85f5a8a6b8c8e374364872f8',1,'Tgstation.Server.Host.Controllers.HomeController.tokenFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html#a93cb446fc8d8f84ea595abe709265398',1,'Tgstation.Server.Host.Core.Application.tokenFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#a6ed83527beb343b96506ad417a91ca94',1,'Tgstation.Server.Host.Security.TokenFactory.TokenFactory()']]], ['tokenfactory',['TokenFactory',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html',1,'Tgstation::Server::Host::Security']]], - ['tokenfactory',['TokenFactory',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#a6ed83527beb343b96506ad417a91ca94',1,'Tgstation.Server.Host.Security.TokenFactory.TokenFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#afd05899e85f5a8a6b8c8e374364872f8',1,'Tgstation.Server.Host.Controllers.HomeController.tokenFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html#a93cb446fc8d8f84ea595abe709265398',1,'Tgstation.Server.Host.Core.Application.tokenFactory()']]], ['tokenfactory_2ecs',['TokenFactory.cs',['../_token_factory_8cs.html',1,'']]], + ['tokenrefreshheaders',['tokenRefreshHeaders',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a5292a281f3ab688eecbf3df7feec400c',1,'Tgstation::Server::Client::ApiClient']]], ['tokensecurityschemeid',['TokenSecuritySchemeId',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_swagger_configuration.html#a0fd6cdaa7f3d8b391f91b820bfea5d56',1,'Tgstation::Server::Host::Core::SwaggerConfiguration']]], ['tokensigningkeybyteamount',['TokenSigningKeyByteAmount',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#a1daafa97f229b3c342725fba60505b0f',1,'Tgstation::Server::Host::Security::TokenFactory']]], ['tokensourceaccesslock',['tokenSourceAccessLock',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_program_shutdown_token_source.html#ac4a3842c9ed0356d2c2199a80c7820f5',1,'Tgstation::Server::Host::System::ProgramShutdownTokenSource']]], diff --git a/search/all_15.js b/search/all_15.js index e4b244c533..e39c3be855 100644 --- a/search/all_15.js +++ b/search/all_15.js @@ -4,7 +4,7 @@ var searchData= ['ultrasafe',['Ultrasafe',['../namespace_tgstation_1_1_server_1_1_api_1_1_models.html#ad4850e6b78816679602b13d63d2869b5a380d297f2cbdccba869676c96bd38bc1',1,'Tgstation::Server::Api::Models']]], ['unaskedvalidationrequest',['UnaskedValidationRequest',['../namespace_tgstation_1_1_server_1_1_host_1_1_components_1_1_session.html#a9dd0e3dfc87080e8c7a91665b79891b3a4b42bf5b19925da0a8866a1dfdc33ffd',1,'Tgstation::Server::Host::Components::Session']]], ['unauthorizedexception',['UnauthorizedException',['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html',1,'Tgstation::Server::Client']]], - ['unauthorizedexception',['UnauthorizedException',['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#a0d5d657c4a66de84328fa886fcdd9a0f',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#ab5b729c4ff826ad5fdd574db0942b8f7',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#af481bddb4a0e255b1430b6e9f4b62a7e',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(string message, Exception innerException)']]], + ['unauthorizedexception',['UnauthorizedException',['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#aad65235f9c5ef27610390fe1e41a7f53',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#ab5b729c4ff826ad5fdd574db0942b8f7',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#af481bddb4a0e255b1430b6e9f4b62a7e',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(string message, Exception innerException)']]], ['unauthorizedexception_2ecs',['UnauthorizedException.cs',['../_unauthorized_exception_8cs.html',1,'']]], ['uninstall',['Uninstall',['../class_tgstation_1_1_server_1_1_host_1_1_service_1_1_program.html#a6868da29a3857b56bba33e5d7c623b68',1,'Tgstation::Server::Host::Service::Program']]], ['unknownreference',['UnknownReference',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html#ad02f759b45e4f20dfee55cfbf3f78baa',1,'Tgstation::Server::Host::Components::Repository::Repository']]], diff --git a/search/all_16.js b/search/all_16.js index cb0037ce16..99cfe81d22 100644 --- a/search/all_16.js +++ b/search/all_16.js @@ -19,7 +19,7 @@ var searchData= ['versionfilename',['VersionFileName',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_byond_manager.html#acb1d6dfeac5bf1c13e9857dcac03be46',1,'Tgstation::Server::Host::Components::Byond::ByondManager']]], ['versionkey',['VersionKey',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_byond_manager.html#a02c588c08d51d013fa4e6c5258478352',1,'Tgstation::Server::Host::Components::Byond::ByondManager']]], ['versionmismatchexception',['VersionMismatchException',['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html',1,'Tgstation::Server::Client']]], - ['versionmismatchexception',['VersionMismatchException',['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a8437edf6af43ab4caeb8f42fc2f8d0fa',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a98f49568d813eb370d5729acf521f661',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException()'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a28b7f81f775966bb71c0dcaf27734684',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#afac834c26a3f8b12f53c8a4e6deaae2c',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(string message, Exception innerException)']]], + ['versionmismatchexception',['VersionMismatchException',['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#abc1871a5ead0f442c12525e5e024ecac',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a98f49568d813eb370d5729acf521f661',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException()'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a28b7f81f775966bb71c0dcaf27734684',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#afac834c26a3f8b12f53c8a4e6deaae2c',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(string message, Exception innerException)']]], ['versionmismatchexception_2ecs',['VersionMismatchException.cs',['../_version_mismatch_exception_8cs.html',1,'']]], ['versionprefix',['VersionPrefix',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_assembly_information_provider.html#aa4cd077258b22b53d6beaa0d78d70cb4',1,'Tgstation.Server.Host.System.AssemblyInformationProvider.VersionPrefix()'],['../interface_tgstation_1_1_server_1_1_host_1_1_system_1_1_i_assembly_information_provider.html#ab2a2be82555afbf6ffb5b7ae3d90b7f0',1,'Tgstation.Server.Host.System.IAssemblyInformationProvider.VersionPrefix()']]], ['versionstring',['VersionString',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_assembly_information_provider.html#ac9a43553319585f54b7a6b69d862250c',1,'Tgstation.Server.Host.System.AssemblyInformationProvider.VersionString()'],['../interface_tgstation_1_1_server_1_1_host_1_1_system_1_1_i_assembly_information_provider.html#abd6db0f6e1ea9273baeacaa66ae81fa3',1,'Tgstation.Server.Host.System.IAssemblyInformationProvider.VersionString()']]] diff --git a/search/all_3.js b/search/all_3.js index 1072d930c8..9d2f293134 100644 --- a/search/all_3.js +++ b/search/all_3.js @@ -185,8 +185,8 @@ var searchData= ['compiling',['compiling',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#af9fdd225bd839f2d361bbe355080d873',1,'Tgstation::Server::Host::Components::Deployment::DreamMaker']]], ['compilinglock',['compilingLock',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#a01a39fc784108fc06f4c6bd27fd55b55',1,'Tgstation::Server::Host::Components::Deployment::DreamMaker']]], ['concatpath',['ConcatPath',['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_default_i_o_manager.html#abd68930642dbf40df272b35dc438312e',1,'Tgstation.Server.Host.IO.DefaultIOManager.ConcatPath()'],['../interface_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_i_i_o_manager.html#a286f4eb5c679683b7e519847a6c3574e',1,'Tgstation.Server.Host.IO.IIOManager.ConcatPath()']]], - ['configuration',['Configuration',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html',1,'Tgstation::Server::Host::Components::StaticFiles']]], ['configuration',['configuration',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#a2fe7ecccab0b5b1be9fdda8cbf097d9b',1,'Tgstation.Server.Host.Components.Deployment.DreamMaker.configuration()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_event_consumer.html#acf0b5b1529168e0c66003f571a4edb6e',1,'Tgstation.Server.Host.Components.EventConsumer.configuration()'],['../class_tgstation_1_1_server_1_1_api_1_1_routes.html#a8aaca6b84ee519e26c8351c2b5929c18',1,'Tgstation.Server.Api.Routes.Configuration()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_instance_client.html#a16626c6a13892319d3f24e0c57dd9394',1,'Tgstation.Server.Client.Components.IInstanceClient.Configuration()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_client.html#a32339ec5eab6e79b673bed2aca4dbceb',1,'Tgstation.Server.Client.Components.InstanceClient.Configuration()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_i_instance.html#a223522ccf4c8fa14c528d55065134f42',1,'Tgstation.Server.Host.Components.IInstance.Configuration()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance.html#a49c2073606768819b91a401c21fe3555',1,'Tgstation.Server.Host.Components.Instance.Configuration()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_application.html#abecd5329f7937e822959734f3c5272d1',1,'Tgstation.Server.Host.Setup.SetupApplication.Configuration()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#ae5932bea40716ebcc3145ccfd2c48aa3',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.Configuration()'],['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#a7b02c8c3dadd90810a9eaa24712a0a20a254f642527b45bc260048e30704edb39',1,'Tgstation.Server.Api.Rights.Configuration()']]], + ['configuration',['Configuration',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html',1,'Tgstation::Server::Host::Components::StaticFiles']]], ['configuration_2ecs',['Configuration.cs',['../_configuration_8cs.html',1,'']]], ['configurationclient',['ConfigurationClient',['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html#aed092908af675dfdec16528492815010',1,'Tgstation::Server::Client::Components::ConfigurationClient']]], ['configurationclient',['ConfigurationClient',['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html',1,'Tgstation::Server::Client::Components']]], @@ -210,22 +210,22 @@ var searchData= ['configurehostedservice',['ConfigureHostedService',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html#a1d6e849d4a69fe90f23f60aaeb2bc960',1,'Tgstation.Server.Host.Core.Application.ConfigureHostedService()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_application.html#a03097b9f375ae721b318fc2aaf970b9b',1,'Tgstation.Server.Host.Setup.SetupApplication.ConfigureHostedService()']]], ['configurelogging',['ConfigureLogging',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a95d031c71d5219c0d2327191b220311b',1,'Tgstation::Server::Host::Setup::SetupWizard']]], ['configureservices',['ConfigureServices',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html#a2b610f9600937f97857f303dcfe41823',1,'Tgstation.Server.Host.Core.Application.ConfigureServices()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_application.html#a1225384501e2dfd8dd509a1f96ea2fa6',1,'Tgstation.Server.Host.Setup.SetupApplication.ConfigureServices()']]], + ['conflictexception',['ConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a4bb2278f0196e618de930579e88f3a8f',1,'Tgstation.Server.Client.ConflictException.ConflictException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a6f35d432dfaab891a7652e2fc636c258',1,'Tgstation.Server.Client.ConflictException.ConflictException()'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a5577bc93af2abe799a86b0c383902ea2',1,'Tgstation.Server.Client.ConflictException.ConflictException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a08028d33db9e62104c77df9ef3f673af',1,'Tgstation.Server.Client.ConflictException.ConflictException(string message, Exception innerException)']]], ['conflictexception',['ConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html',1,'Tgstation::Server::Client']]], - ['conflictexception',['ConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a7f70c88fb9025a7db9bac18bb8bfa5e5',1,'Tgstation.Server.Client.ConflictException.ConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a6f35d432dfaab891a7652e2fc636c258',1,'Tgstation.Server.Client.ConflictException.ConflictException()'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a5577bc93af2abe799a86b0c383902ea2',1,'Tgstation.Server.Client.ConflictException.ConflictException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a08028d33db9e62104c77df9ef3f673af',1,'Tgstation.Server.Client.ConflictException.ConflictException(string message, Exception innerException)']]], ['conflictexception_2ecs',['ConflictException.cs',['../_conflict_exception_8cs.html',1,'']]], ['connect',['Connect',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_discord_provider.html#ae0ae6a98f9e73c337f6b4fca3791df76',1,'Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_i_provider.html#a78705214e55337f3970869e3fcabb502',1,'Tgstation.Server.Host.Components.Chat.Providers.IProvider.Connect()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a5db89afa73a4637471b476a2e534861d',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.Connect()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#affce280626a132f2e2a2731946e39a0c',1,'Tgstation.Server.Host.Components.Chat.Providers.Provider.Connect()']]], ['connected',['Connected',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_discord_provider.html#ac2744da20824177724b971eb20a6d5a0',1,'Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connected()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_i_provider.html#acc07a6bfb2fbb35243aa1e147d92dd87',1,'Tgstation.Server.Host.Components.Chat.Providers.IProvider.Connected()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#aa22564e0346747e2c00dfcab7a48141c',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.Connected()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#afab0213888c139dbb1786d7f5c0d8680',1,'Tgstation.Server.Host.Components.Chat.Providers.Provider.Connected()']]], ['connectionname',['ConnectionName',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_channel_representation.html#a0d68ca440d98dc89941ef3c5781f2f61',1,'Tgstation::Server::Host::Components::Chat::ChannelRepresentation']]], ['connectionstring',['ConnectionString',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_chat_bot.html#a21674d47d37f627eb1058f47c0018f7b',1,'Tgstation.Server.Api.Models.Internal.ChatBot.ConnectionString()'],['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_database_configuration.html#a53a14b4a89de73b8ea223804c4aea365',1,'Tgstation.Server.Host.Configuration.DatabaseConfiguration.ConnectionString()']]], ['connectionsupdated',['connectionsUpdated',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html#a179b0d01333a267410199e524601dc84',1,'Tgstation::Server::Host::Components::Chat::ChatManager']]], - ['console',['Console',['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_console.html',1,'Tgstation::Server::Host::IO']]], ['console',['console',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#aadeb44bb71d804e15c6a6bd0c3131bd7',1,'Tgstation.Server.Host.Setup.SetupWizard.console()'],['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_console.html#aba874ec794084e164acb19d73c7c1dee',1,'Tgstation.Server.Host.IO.Console.Console()']]], + ['console',['Console',['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_console.html',1,'Tgstation::Server::Host::IO']]], ['console_2ecs',['Console.cs',['../_console_8cs.html',1,'']]], ['content',['Content',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_configuration_file.html#a01ea9f02cf1c5f6442b5753c2d0b0024',1,'Tgstation.Server.Api.Models.ConfigurationFile.Content()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_message.html#a3a4a159c1b7b4ff0a163f4bb29ea4844',1,'Tgstation.Server.Host.Components.Chat.Providers.Message.Content()']]], ['continue',['Continue',['../namespace_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog.html#a97c1a4a168451bd7e3be23c467aa2812aa0bfb8e59e6c13fc8d990781f77694fe',1,'Tgstation::Server::Host::Components::Watchdog']]], ['controller',['Controller',['../class_controller.html',1,'']]], - ['controlpanelconfiguration',['controlPanelConfiguration',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a81d7f216e77a45eed438ee3e88d0677c',1,'Tgstation::Server::Host::Controllers::HomeController']]], ['controlpanelconfiguration',['ControlPanelConfiguration',['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_control_panel_configuration.html',1,'Tgstation::Server::Host::Configuration']]], + ['controlpanelconfiguration',['controlPanelConfiguration',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a81d7f216e77a45eed438ee3e88d0677c',1,'Tgstation::Server::Host::Controllers::HomeController']]], ['controlpanelconfiguration_2ecs',['ControlPanelConfiguration.cs',['../_control_panel_configuration_8cs.html',1,'']]], ['convertapichatchannel',['ConvertApiChatChannel',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_chat_controller.html#a30fce4a18499c3c89b4b153b9805ce85',1,'Tgstation::Server::Host::Controllers::ChatController']]], ['copydirectory',['CopyDirectory',['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_default_i_o_manager.html#ac24671c0d1ce1f6787304c5f4bcccf7c',1,'Tgstation.Server.Host.IO.DefaultIOManager.CopyDirectory()'],['../interface_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_i_i_o_manager.html#aab4ec78e2b2d66ad39642ed92a66250c',1,'Tgstation.Server.Host.IO.IIOManager.CopyDirectory()']]], @@ -236,7 +236,7 @@ var searchData= ['create',['Create',['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_chat_bots_client.html#a2683730461ef575dbaab18fb01a10162',1,'Tgstation.Server.Client.Components.ChatBotsClient.Create()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_chat_bots_client.html#a781f11da2fc0bc852e57544872e2f47e',1,'Tgstation.Server.Client.Components.IChatBotsClient.Create()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_instance_user_client.html#a8f315515848f8b7397e71255fa1055c9',1,'Tgstation.Server.Client.Components.IInstanceUserClient.Create()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_user_client.html#af62bfee4e1cdca8a216bdade995a57b7',1,'Tgstation.Server.Client.Components.InstanceUserClient.Create()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_users_client.html#a60a4f54b4b6cb61d3c0054b9b7a04604',1,'Tgstation.Server.Client.IUsersClient.Create()'],['../class_tgstation_1_1_server_1_1_client_1_1_users_client.html#a68a9c4aea0859ef158ef8146e22d0fd6',1,'Tgstation.Server.Client.UsersClient.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_chat_controller.html#a38a75a3f14b37983b1e5a76f2186e0ad',1,'Tgstation.Server.Host.Controllers.ChatController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_configuration_controller.html#a111121fa22f2648803f8f890c8a6305b',1,'Tgstation.Server.Host.Controllers.ConfigurationController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_daemon_controller.html#af83519b8dbc46ddeffef6862367721b2',1,'Tgstation.Server.Host.Controllers.DreamDaemonController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_maker_controller.html#a7231f4980bd98c9a033e805c90e92c95',1,'Tgstation.Server.Host.Controllers.DreamMakerController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#a8712ba6603139064d68520e16c076901',1,'Tgstation.Server.Host.Controllers.InstanceController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_user_controller.html#a3a26d7b7a1b8c8c8c0ade33893f24b09',1,'Tgstation.Server.Host.Controllers.InstanceUserController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#a30a5b00625419334b1d51ae4a51aebda',1,'Tgstation.Server.Host.Controllers.RepositoryController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_user_controller.html#a99457477cad4b6adcedbba47d939f005',1,'Tgstation.Server.Host.Controllers.UserController.Create()'],['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#a580a36a11165b2590be342b45c31ec72a686e697538050e4664636337cc3b834f',1,'Tgstation.Server.Api.Rights.Create()'],['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#af199a84ed6fe6ca557db9de91070ad79a686e697538050e4664636337cc3b834f',1,'Tgstation.Server.Api.Rights.Create()']]], ['create_3c_20tbody_2c_20tresult_20_3e',['Create< TBody, TResult >',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a73701939b0af1ee33b46c931829200fc',1,'Tgstation.Server.Client.ApiClient.Create< TBody, TResult >(string route, TBody body, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#ab5fc54acd429e50a254fccaf23e52299',1,'Tgstation.Server.Client.ApiClient.Create< TBody, TResult >(string route, TBody body, long instanceId, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#a0448d3803d9f45d2e67295cef2096174',1,'Tgstation.Server.Client.IApiClient.Create< TBody, TResult >(string route, TBody body, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#a726cb2cf80d2499c3e6ff8ae67bbc0ed',1,'Tgstation.Server.Client.IApiClient.Create< TBody, TResult >(string route, TBody body, long instanceId, CancellationToken cancellationToken)']]], ['create_3c_20tresult_20_3e',['Create< TResult >',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a5c7201add0fadb2be2851aa749a0c3c6',1,'Tgstation.Server.Client.ApiClient.Create< TResult >(string route, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a70d6c289a7e07426ff72c36eb2502819',1,'Tgstation.Server.Client.ApiClient.Create< TResult >(string route, long instanceId, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#ac4bbf7c3cc9ff87beda2b13f018a25f5',1,'Tgstation.Server.Client.IApiClient.Create< TResult >(string route, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#a16d32f00e49bed861bcbd1c6c9325727',1,'Tgstation.Server.Client.IApiClient.Create< TResult >(string route, long instanceId, CancellationToken cancellationToken)']]], - ['createapiclient',['CreateApiClient',['../class_tgstation_1_1_server_1_1_client_1_1_api_client_factory.html#af1210c7eddf5533d80c0618d99fc6b4d',1,'Tgstation.Server.Client.ApiClientFactory.CreateApiClient()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client_factory.html#add8cac56a24b5f4a43d870395de0ea83',1,'Tgstation.Server.Client.IApiClientFactory.CreateApiClient()']]], + ['createapiclient',['CreateApiClient',['../class_tgstation_1_1_server_1_1_client_1_1_api_client_factory.html#aff36fce79abf84418555ce3655fcea67',1,'Tgstation.Server.Client.ApiClientFactory.CreateApiClient()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client_factory.html#ac165b326c27c0d7a834d2af5eff02645',1,'Tgstation.Server.Client.IApiClientFactory.CreateApiClient()']]], ['createauthenticationcontext',['CreateAuthenticationContext',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_authentication_context_factory.html#a26c8a971d94af552e5933deaccbbe693',1,'Tgstation.Server.Host.Security.AuthenticationContextFactory.CreateAuthenticationContext()'],['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_authentication_context_factory.html#a0976873c7f5654dbe88919ed3cb20d84',1,'Tgstation.Server.Host.Security.IAuthenticationContextFactory.CreateAuthenticationContext()']]], ['createbaseclient',['CreateBaseClient',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_git_hub_client_factory.html#a0d2033f1157b71089477c1383ed77e09',1,'Tgstation::Server::Host::Core::GitHubClientFactory']]], ['createchatmanager',['CreateChatManager',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager_factory.html#ab7cb1f1ac63ad00a080ec4cb65b03ac6',1,'Tgstation.Server.Host.Components.Chat.ChatManagerFactory.CreateChatManager()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_i_chat_manager_factory.html#a2da119ff14c5553f07a8e0c3b3d43755',1,'Tgstation.Server.Host.Components.Chat.IChatManagerFactory.CreateChatManager()']]], @@ -251,7 +251,9 @@ var searchData= ['createdirectory',['CreateDirectory',['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html#ad3f4cf594c24523ba3717b926843c078',1,'Tgstation.Server.Client.Components.ConfigurationClient.CreateDirectory()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_configuration_client.html#ab7b97489e1cb6307e417f6ecc22b9260',1,'Tgstation.Server.Client.Components.IConfigurationClient.CreateDirectory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#a9e6d3a7af0e253142f6ea1fe932f6002',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.CreateDirectory()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_i_configuration.html#aac9d5ed7fad37fc5f29295f4a3445449',1,'Tgstation.Server.Host.Components.StaticFiles.IConfiguration.CreateDirectory()'],['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_default_i_o_manager.html#a35975e796c564e8b5dd5d4a601d0c2e3',1,'Tgstation.Server.Host.IO.DefaultIOManager.CreateDirectory()'],['../interface_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_i_i_o_manager.html#a69aa1b9dbeb6af3744e568095c4376e7',1,'Tgstation.Server.Host.IO.IIOManager.CreateDirectory()'],['../interface_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_i_synchronous_i_o_manager.html#a87b99f131bf6b48d725561414d4ddfcb',1,'Tgstation.Server.Host.IO.ISynchronousIOManager.CreateDirectory()'],['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_synchronous_i_o_manager.html#a595b31e7065ee0d3431903fa0ac55156',1,'Tgstation.Server.Host.IO.SynchronousIOManager.CreateDirectory()']]], ['createdusers',['CreatedUsers',['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_user.html#abe7d2baab75bacc69885b90664d6c81a',1,'Tgstation::Server::Host::Models::User']]], ['createfromexistinghandle',['CreateFromExistingHandle',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process_executor.html#ab8ce5d8354aea3fec89a1d7687acb877',1,'Tgstation::Server::Host::System::ProcessExecutor']]], + ['createfromlogin',['CreateFromLogin',['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory.html#a861f94dac58c8dbd011dffb92a5b8ffc',1,'Tgstation.Server.Client.IServerClientFactory.CreateFromLogin()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#a10753b8b7dfa6edbd02e117680dba4bd',1,'Tgstation.Server.Client.ServerClientFactory.CreateFromLogin()']]], ['createfrompath',['CreateFromPath',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_i_lib_git2_repository_factory.html#a855673b50e2639242bea6d6770c39773',1,'Tgstation.Server.Host.Components.Repository.ILibGit2RepositoryFactory.CreateFromPath()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_lib_git2_repository_factory.html#aff825ad3b4e4f4de83285d3cf2281e7b',1,'Tgstation.Server.Host.Components.Repository.LibGit2RepositoryFactory.CreateFromPath()']]], + ['createfromtoken',['CreateFromToken',['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory.html#ada7a9fcb461c62ddb534a57e1d3791c7',1,'Tgstation.Server.Client.IServerClientFactory.CreateFromToken()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#abc81fe410ef4bbf999e7531505cac7e1',1,'Tgstation.Server.Client.ServerClientFactory.CreateFromToken()']]], ['createinmemory',['CreateInMemory',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_i_lib_git2_repository_factory.html#af08df14e3a5e9e63b14a6d65cc47abd6',1,'Tgstation.Server.Host.Components.Repository.ILibGit2RepositoryFactory.CreateInMemory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_lib_git2_repository_factory.html#ad04e55b87e2ea16c68210ce73a80cebd',1,'Tgstation.Server.Host.Components.Repository.LibGit2RepositoryFactory.CreateInMemory()']]], ['createinstance',['CreateInstance',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_i_instance_factory.html#aba000cffe2b386d227e1a9d174d026d1',1,'Tgstation.Server.Host.Components.IInstanceFactory.CreateInstance()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#ac02a0bbbf943e2b7db0d57398283b497',1,'Tgstation.Server.Host.Components.InstanceFactory.CreateInstance()']]], ['createnonexperimentalwatchdog',['CreateNonExperimentalWatchdog',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_factory.html#a46ae6b8af80e6d489c2a7714b71a9900',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogFactory.CreateNonExperimentalWatchdog()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_windows_watchdog_factory.html#a6a30173c82382567c5aa2dfe472d4683',1,'Tgstation.Server.Host.Components.Watchdog.WindowsWatchdogFactory.CreateNonExperimentalWatchdog()']]], @@ -260,7 +262,6 @@ var searchData= ['createreattachinformation',['CreateReattachInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_basic_watchdog.html#af6174583a40835ca31655641929a8d6f',1,'Tgstation.Server.Host.Components.Watchdog.BasicWatchdog.CreateReattachInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_experimental_watchdog.html#a065b63b3193290fc814da9974fe69d71',1,'Tgstation.Server.Host.Components.Watchdog.ExperimentalWatchdog.CreateReattachInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#a63aeed1dfa0a07ab3eca31f9c8afcfda',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.CreateReattachInformation()']]], ['createruntimeinformation',['CreateRuntimeInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#aeb04b547d45cf7564350506287146621',1,'Tgstation::Server::Host::Components::Session::SessionControllerFactory']]], ['createserver',['CreateServer',['../interface_tgstation_1_1_server_1_1_host_1_1_i_server_factory.html#a063542cb78195a507feb3c0e6921b01b',1,'Tgstation.Server.Host.IServerFactory.CreateServer()'],['../class_tgstation_1_1_server_1_1_host_1_1_server_factory.html#a4234b15ede546c23f9b5d35bc5c8749b',1,'Tgstation.Server.Host.ServerFactory.CreateServer()']]], - ['createserverclient',['CreateServerClient',['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory.html#aed42fe9280a989ba78b8fd2a830a93a8',1,'Tgstation.Server.Client.IServerClientFactory.CreateServerClient(Uri host, string username, string password, TimeSpan timeout=default, CancellationToken cancellationToken=default)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory.html#a58ce916728cfc81f8e143682cd1df5e7',1,'Tgstation.Server.Client.IServerClientFactory.CreateServerClient(Uri host, Token token, TimeSpan timeout=default)'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#a358685d267630347317e476195680cc9',1,'Tgstation.Server.Client.ServerClientFactory.CreateServerClient(Uri host, string username, string password, TimeSpan timeout, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#a6c53de0537efa094a9c2c52b681acaa2',1,'Tgstation.Server.Client.ServerClientFactory.CreateServerClient(Uri host, Token token, TimeSpan timeout)']]], ['createsymboliclink',['CreateSymbolicLink',['../interface_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_i_symlink_factory.html#a35bd8c53ef36c4a655e827750a40699c',1,'Tgstation.Server.Host.IO.ISymlinkFactory.CreateSymbolicLink()'],['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_posix_symlink_factory.html#ac487bf33d9a5821fb30bfd7b74915fbe',1,'Tgstation.Server.Host.IO.PosixSymlinkFactory.CreateSymbolicLink()'],['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_windows_symlink_factory.html#a87a0b1b31b48f65e53e4b3b6f476a2c0',1,'Tgstation.Server.Host.IO.WindowsSymlinkFactory.CreateSymbolicLink()'],['../class_tgstation_1_1_server_1_1_host_1_1_native_methods.html#a668c437c860fb2eafcee04a1a2a613c9',1,'Tgstation.Server.Host.NativeMethods.CreateSymbolicLink()']]], ['createsymboliclinkflags',['CreateSymbolicLinkFlags',['../class_tgstation_1_1_server_1_1_host_1_1_native_methods.html#afecbde132584de6ed7e41c8c7535f556',1,'Tgstation::Server::Host::NativeMethods']]], ['createsystemidentity',['CreateSystemIdentity',['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_system_identity_factory.html#adaf274d215700bf692b1c3964ff32103',1,'Tgstation.Server.Host.Security.ISystemIdentityFactory.CreateSystemIdentity(User user, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_system_identity_factory.html#a6dd83aca91cf43c7236afd22dc512b0a',1,'Tgstation.Server.Host.Security.ISystemIdentityFactory.CreateSystemIdentity(string username, string password, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_posix_system_identity_factory.html#a8e2c85e4ed45d68065f86749afa4651d',1,'Tgstation.Server.Host.Security.PosixSystemIdentityFactory.CreateSystemIdentity(User user, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_posix_system_identity_factory.html#a4621a750f1d0592586d26323af215d93',1,'Tgstation.Server.Host.Security.PosixSystemIdentityFactory.CreateSystemIdentity(string username, string password, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_windows_system_identity_factory.html#a5c140611a428b663c3a3f76623f825c2',1,'Tgstation.Server.Host.Security.WindowsSystemIdentityFactory.CreateSystemIdentity(User user, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_windows_system_identity_factory.html#a24c82be81ba5cda6c975a931e8bfe6f1',1,'Tgstation.Server.Host.Security.WindowsSystemIdentityFactory.CreateSystemIdentity(string username, string password, CancellationToken cancellationToken)']]], diff --git a/search/all_4.js b/search/all_4.js index 1d2342c0cc..547f31ff1b 100644 --- a/search/all_4.js +++ b/search/all_4.js @@ -33,7 +33,7 @@ var searchData= ['deadsessioncontroller',['DeadSessionController',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_dead_session_controller.html',1,'Tgstation::Server::Host::Components::Session']]], ['deadsessioncontroller_2ecs',['DeadSessionController.cs',['../_dead_session_controller_8cs.html',1,'']]], ['debug',['Debug',['../class_tgstation_1_1_server_1_1_host_1_1_service_1_1_program.html#a938d576ceba93bf3cb1a32ae1dcccc52',1,'Tgstation::Server::Host::Service::Program']]], - ['defaultadminpassword',['DefaultAdminPassword',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html#a6b47a29b6c75f6a51d8c7d0d4305e210',1,'Tgstation::Server::Api::Models::User']]], + ['defaultadminpassword',['DefaultAdminPassword',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html#a6d73f12df63e1cea9b3d011da9d9a14a',1,'Tgstation::Server::Api::Models::User']]], ['defaultbuffersize',['DefaultBufferSize',['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_default_i_o_manager.html#a8d302b2be6862b23b9a18a9412fc7fc9',1,'Tgstation::Server::Host::IO::DefaultIOManager']]], ['defaultbyondtopictimeout',['DefaultByondTopicTimeout',['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html#a51c2f2b2e7faf729aad307050a6d8a6c',1,'Tgstation::Server::Host::Configuration::GeneralConfiguration']]], ['defaultchannellimit',['DefaultChannelLimit',['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_chat_bot.html#a9f693077cc6b7bca387dd46a08014c90',1,'Tgstation::Server::Host::Models::ChatBot']]], diff --git a/search/all_5.js b/search/all_5.js index bb97526603..f66d3ce24c 100644 --- a/search/all_5.js +++ b/search/all_5.js @@ -17,7 +17,7 @@ var searchData= ['errorcode_2ecs',['ErrorCode.cs',['../_error_code_8cs.html',1,'']]], ['errorcodeextensions',['ErrorCodeExtensions',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_code_extensions.html',1,'Tgstation::Server::Api::Models']]], ['errorcodeextensions_2ecs',['ErrorCodeExtensions.cs',['../_error_code_extensions_8cs.html',1,'']]], - ['errormessage',['ErrorMessage',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_d_m_api_response.html#a1d831028e8763f5adea930f2b7ea0661',1,'Tgstation.Server.Host.Components.Interop.DMApiResponse.ErrorMessage()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message.html#abfbfef9f57a4e2d279cd3fa227b32711',1,'Tgstation.Server.Api.Models.ErrorMessage.ErrorMessage()']]], + ['errormessage',['ErrorMessage',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_d_m_api_response.html#a1d831028e8763f5adea930f2b7ea0661',1,'Tgstation.Server.Host.Components.Interop.DMApiResponse.ErrorMessage()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message.html#a594af171f50f98e2be8481692c6584cc',1,'Tgstation.Server.Api.Models.ErrorMessage.ErrorMessage()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message.html#abfbfef9f57a4e2d279cd3fa227b32711',1,'Tgstation.Server.Api.Models.ErrorMessage.ErrorMessage(ErrorCode errorCode)']]], ['errormessage',['ErrorMessage',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message.html',1,'Tgstation::Server::Api::Models']]], ['errormessage_2ecs',['ErrorMessage.cs',['../_error_message_8cs.html',1,'']]], ['errorstringbuilder',['errorStringBuilder',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process.html#a95a81e7677d54aa0b52ad539cab122f6',1,'Tgstation::Server::Host::System::Process']]], diff --git a/search/all_7.js b/search/all_7.js index 0eedb8d4e9..06cfbc9879 100644 --- a/search/all_7.js +++ b/search/all_7.js @@ -2,7 +2,7 @@ var searchData= [ ['gamestaticfilessubdirectory',['GameStaticFilesSubdirectory',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#ae8e07adce00ed418cb29954182699623',1,'Tgstation::Server::Host::Components::StaticFiles::Configuration']]], ['generalconfiguration',['GeneralConfiguration',['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html',1,'Tgstation::Server::Host::Configuration']]], - ['generalconfiguration',['GeneralConfiguration',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_factory.html#aa163ef7f1b2ec0e29736e7cfcfd20f03',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogFactory.GeneralConfiguration()'],['../interface_tgstation_1_1_server_1_1_host_1_1_setup_1_1_i_post_setup_services.html#a3a19dda55bff763cbbc27de56905ae5a',1,'Tgstation.Server.Host.Setup.IPostSetupServices.GeneralConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_post_setup_services.html#a454fd5fdcdd712c84d205389197cb33a',1,'Tgstation.Server.Host.Setup.PostSetupServices.GeneralConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html#a0d5ca385e9de5c07753ed5ff5ad84b52',1,'Tgstation.Server.Host.Configuration.GeneralConfiguration.GeneralConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#adb0b189a10ec43510b9f10cff91106fd',1,'Tgstation.Server.Host.Controllers.AdministrationController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a78f4994c9c4f2cc58d0a6d227e0195a9',1,'Tgstation.Server.Host.Controllers.HomeController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#a4f60f72c082a222a31383dbfceb755a6',1,'Tgstation.Server.Host.Controllers.InstanceController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#abe159da9c5fbc8eba3c3feff488988a0',1,'Tgstation.Server.Host.Controllers.RepositoryController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_user_controller.html#a79e1198432e7e557c8d72b3adf71bbf1',1,'Tgstation.Server.Host.Controllers.UserController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_server.html#a2823acb68609f9953788b1460ca8cdd3',1,'Tgstation.Server.Host.Server.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a47f6e49529ffb5b7760e1a5713f590e4',1,'Tgstation.Server.Host.Setup.SetupWizard.generalConfiguration()']]], + ['generalconfiguration',['generalConfiguration',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#aaa2d14e2052d1ad81104ec0e5afa218e',1,'Tgstation.Server.Host.Components.InstanceManager.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#adb0b189a10ec43510b9f10cff91106fd',1,'Tgstation.Server.Host.Controllers.AdministrationController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a78f4994c9c4f2cc58d0a6d227e0195a9',1,'Tgstation.Server.Host.Controllers.HomeController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#a4f60f72c082a222a31383dbfceb755a6',1,'Tgstation.Server.Host.Controllers.InstanceController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#abe159da9c5fbc8eba3c3feff488988a0',1,'Tgstation.Server.Host.Controllers.RepositoryController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_user_controller.html#a79e1198432e7e557c8d72b3adf71bbf1',1,'Tgstation.Server.Host.Controllers.UserController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_server.html#a2823acb68609f9953788b1460ca8cdd3',1,'Tgstation.Server.Host.Server.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a47f6e49529ffb5b7760e1a5713f590e4',1,'Tgstation.Server.Host.Setup.SetupWizard.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_factory.html#aa163ef7f1b2ec0e29736e7cfcfd20f03',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogFactory.GeneralConfiguration()'],['../interface_tgstation_1_1_server_1_1_host_1_1_setup_1_1_i_post_setup_services.html#a3a19dda55bff763cbbc27de56905ae5a',1,'Tgstation.Server.Host.Setup.IPostSetupServices.GeneralConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_post_setup_services.html#a454fd5fdcdd712c84d205389197cb33a',1,'Tgstation.Server.Host.Setup.PostSetupServices.GeneralConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html#a0d5ca385e9de5c07753ed5ff5ad84b52',1,'Tgstation.Server.Host.Configuration.GeneralConfiguration.GeneralConfiguration()']]], ['generalconfiguration_2ecs',['GeneralConfiguration.cs',['../_general_configuration_8cs.html',1,'']]], ['generalconfigurationoptions',['generalConfigurationOptions',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_post_setup_services.html#a3ce96cf3d3ee5a94ef82ee90efcd5f07',1,'Tgstation::Server::Host::Setup::PostSetupServices']]], ['generatecommands',['GenerateCommands',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_command_factory.html#a6d69dde344df916a244626be56bc1b78',1,'Tgstation.Server.Host.Components.Chat.Commands.CommandFactory.GenerateCommands()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_i_command_factory.html#a3f5fdca17c71257d34528f45f61ed469',1,'Tgstation.Server.Host.Components.Chat.Commands.ICommandFactory.GenerateCommands()']]], diff --git a/search/all_9.js b/search/all_9.js index 9acf837e00..5fffc5379b 100644 --- a/search/all_9.js +++ b/search/all_9.js @@ -181,7 +181,7 @@ var searchData= ['instanceidheader',['InstanceIdHeader',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#a28f94492eb21542e63675a53d871e3ef',1,'Tgstation::Server::Api::ApiHeaders']]], ['instancelimit',['InstanceLimit',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_server_information.html#a37f3b06cddaa9d460b13ed6544c3e528',1,'Tgstation::Server::Api::Models::Internal::ServerInformation']]], ['instancelimitreached',['InstanceLimitReached',['../namespace_tgstation_1_1_server_1_1_api_1_1_models.html#a1a6adb59dd51244efabf4e548e7075c8ac043c16f72e845bae9e6a18cf61cb70c',1,'Tgstation::Server::Api::Models']]], - ['instancemanager',['InstanceManager',['../class_tgstation_1_1_server_1_1_api_1_1_routes.html#a17bfc22b8cbc4624a6ec180aeba91da8',1,'Tgstation.Server.Api.Routes.InstanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#a6a44bb969dab0811a31b9c990c82ffd3',1,'Tgstation.Server.Host.Components.InstanceManager.InstanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_byond_controller.html#afb94a5c7ed6a392822c4d32b17e8ce20',1,'Tgstation.Server.Host.Controllers.ByondController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_chat_controller.html#a1e37d0da176e8693608a4719c05e57f9',1,'Tgstation.Server.Host.Controllers.ChatController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_configuration_controller.html#a0b158757381e0c695ba632319a14e873',1,'Tgstation.Server.Host.Controllers.ConfigurationController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_daemon_controller.html#ad259fbe273d7c5396d766da41023f6f9',1,'Tgstation.Server.Host.Controllers.DreamDaemonController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_maker_controller.html#aa45b1a91e46ea9a7983b8edf0a1c1cd6',1,'Tgstation.Server.Host.Controllers.DreamMakerController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#ad177b6d13a08c429c49b7e4294387d3c',1,'Tgstation.Server.Host.Controllers.InstanceController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#a2f7404efa7a9821510b3146bfd99cc6c',1,'Tgstation.Server.Host.Controllers.RepositoryController.instanceManager()'],['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#a7b02c8c3dadd90810a9eaa24712a0a20a7c77d4c45a003ee3d6b3589d2459cc49',1,'Tgstation.Server.Api.Rights.InstanceManager()']]], + ['instancemanager',['InstanceManager',['../class_tgstation_1_1_server_1_1_api_1_1_routes.html#a17bfc22b8cbc4624a6ec180aeba91da8',1,'Tgstation.Server.Api.Routes.InstanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#a4ce33ffacb174f0ac669e29058854318',1,'Tgstation.Server.Host.Components.InstanceManager.InstanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_byond_controller.html#afb94a5c7ed6a392822c4d32b17e8ce20',1,'Tgstation.Server.Host.Controllers.ByondController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_chat_controller.html#a1e37d0da176e8693608a4719c05e57f9',1,'Tgstation.Server.Host.Controllers.ChatController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_configuration_controller.html#a0b158757381e0c695ba632319a14e873',1,'Tgstation.Server.Host.Controllers.ConfigurationController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_daemon_controller.html#ad259fbe273d7c5396d766da41023f6f9',1,'Tgstation.Server.Host.Controllers.DreamDaemonController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_maker_controller.html#aa45b1a91e46ea9a7983b8edf0a1c1cd6',1,'Tgstation.Server.Host.Controllers.DreamMakerController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#ad177b6d13a08c429c49b7e4294387d3c',1,'Tgstation.Server.Host.Controllers.InstanceController.instanceManager()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#a2f7404efa7a9821510b3146bfd99cc6c',1,'Tgstation.Server.Host.Controllers.RepositoryController.instanceManager()'],['../namespace_tgstation_1_1_server_1_1_api_1_1_rights.html#a7b02c8c3dadd90810a9eaa24712a0a20a7c77d4c45a003ee3d6b3589d2459cc49',1,'Tgstation.Server.Api.Rights.InstanceManager()']]], ['instancemanager',['InstanceManager',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html',1,'Tgstation::Server::Host::Components']]], ['instancemanager_2ecs',['InstanceManager.cs',['../_instance_manager_8cs.html',1,'']]], ['instancemanagerclient',['InstanceManagerClient',['../class_tgstation_1_1_server_1_1_client_1_1_instance_manager_client.html',1,'Tgstation::Server::Client']]], diff --git a/search/all_d.js b/search/all_d.js index 4b53b0005e..413f0ea1bb 100644 --- a/search/all_d.js +++ b/search/all_d.js @@ -19,7 +19,7 @@ var searchData= ['messagequeue',['messageQueue',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#a0cf9e8f2427fc076d90805ef6ee06a66',1,'Tgstation::Server::Host::Components::Chat::Providers::Provider']]], ['metadata',['metadata',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#ac5b667aa89c476c9cb0cf71cb5add8a6',1,'Tgstation.Server.Host.Components.Deployment.DreamMaker.metadata()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance.html#ad54344c590beab965fead49ac08224a9',1,'Tgstation.Server.Host.Components.Instance.metadata()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_reattach_info_handler.html#aeb081b8a579ab62f1bc85c35dab38a29',1,'Tgstation.Server.Host.Components.Session.ReattachInfoHandler.metadata()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_instance_client.html#a22f01f297f6ddfa2cfca204c8b94fd83',1,'Tgstation.Server.Client.Components.IInstanceClient.Metadata()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_client.html#a0107f70cae241fd15612608e8eb93f51',1,'Tgstation.Server.Client.Components.InstanceClient.Metadata()']]], ['methodnotsupportedexception',['MethodNotSupportedException',['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html',1,'Tgstation::Server::Client']]], - ['methodnotsupportedexception',['MethodNotSupportedException',['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a6669ef2642a0c7c113ffcbb341add9db',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a37543ec8aabe8b8fa96a15243d02e318',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a5b0a4306e368279a8b18f50a7ad6bb63',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(string message, Exception innerException)']]], + ['methodnotsupportedexception',['MethodNotSupportedException',['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#ac6795b4ca2ddafd8cf3d9eb94e1e65b3',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a37543ec8aabe8b8fa96a15243d02e318',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a5b0a4306e368279a8b18f50a7ad6bb63',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(string message, Exception innerException)']]], ['methodnotsupportedexception_2ecs',['MethodNotSupportedException.cs',['../_method_not_supported_exception_8cs.html',1,'']]], ['microsoftloglevel',['MicrosoftLogLevel',['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_file_logging_configuration.html#a5d27b790b93c94ea77f83b3729be63df',1,'Tgstation::Server::Host::Configuration::FileLoggingConfiguration']]], ['migration',['Migration',['../class_migration.html',1,'']]], diff --git a/search/all_e.js b/search/all_e.js index dfe6949e72..ca19777fa4 100644 --- a/search/all_e.js +++ b/search/all_e.js @@ -12,11 +12,11 @@ var searchData= ['newport',['NewPort',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_bridge_response.html#a0503e94799c46547d56a01477d9b7f6e',1,'Tgstation.Server.Host.Components.Interop.Bridge.BridgeResponse.NewPort()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_topic_1_1_topic_parameters.html#acee5f8c5002b7657c5417b578fe2e741',1,'Tgstation.Server.Host.Components.Interop.Topic.TopicParameters.NewPort()']]], ['newrebootstate',['NewRebootState',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_topic_1_1_topic_parameters.html#a6cb18ce9da1d576a7ce97e148f065a56',1,'Tgstation::Server::Host::Components::Interop::Topic::TopicParameters']]], ['newserverversion',['NewServerVersion',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_topic_1_1_topic_parameters.html#a9e0422cc12040f835acbbb340d453ff7',1,'Tgstation::Server::Host::Components::Interop::Topic::TopicParameters']]], - ['newtestmerges',['NewTestMerges',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html#a05bdec3c08f5acba28d9e72d70f38833',1,'Tgstation::Server::Api::Models::Repository']]], + ['newtestmerges',['NewTestMerges',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html#a56be56ee822e8842553b357e20c491f2',1,'Tgstation::Server::Api::Models::Repository']]], ['newversion',['NewVersion',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_administration.html#ab40a1df200563a8351347145f50a8bc1',1,'Tgstation::Server::Api::Models::Administration']]], ['nextaction',['NextAction',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_monitor_state.html#a87534d177a8fda685d068f47526b5bad',1,'Tgstation::Server::Host::Components::Watchdog::MonitorState']]], ['nextdmbprovider',['nextDmbProvider',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dmb_factory.html#affbfb8dd410addc55b47b5c08143a509',1,'Tgstation::Server::Host::Components::Deployment::DmbFactory']]], - ['nextmessage',['nextMessage',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#ac27feec8aca499fa963dd6f6b557b818',1,'Tgstation.Server.Host.Components.Chat.Providers.Provider.nextMessage()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_i_provider.html#ab6cd020395b652aa9d79c00497b739fb',1,'Tgstation.Server.Host.Components.Chat.Providers.IProvider.NextMessage()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#a962a3510e617d6754d8e21a237d4b933',1,'Tgstation.Server.Host.Components.Chat.Providers.Provider.NextMessage()']]], + ['nextmessage',['NextMessage',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_i_provider.html#ab6cd020395b652aa9d79c00497b739fb',1,'Tgstation.Server.Host.Components.Chat.Providers.IProvider.NextMessage()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#a962a3510e617d6754d8e21a237d4b933',1,'Tgstation.Server.Host.Components.Chat.Providers.Provider.NextMessage(CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#ac27feec8aca499fa963dd6f6b557b818',1,'Tgstation.Server.Host.Components.Chat.Providers.Provider.nextMessage()']]], ['nextport',['nextPort',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#ae72efdb3c73ce549d24c16ae8fa6cd59',1,'Tgstation::Server::Host::Components::Session::SessionController']]], ['nickname',['nickname',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a75457eda73c1935c8784dc7fadf9e0ba',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.nickname()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_irc_connection_string_builder.html#a95d8cce20205d05b7bfbda1d088af791',1,'Tgstation.Server.Api.Models.IrcConnectionStringBuilder.Nickname()']]], ['nickserv',['NickServ',['../namespace_tgstation_1_1_server_1_1_api_1_1_models.html#a0a41c646f859c99d437373d8e6e0545ba1d351e780172667f7ac246f762907249',1,'Tgstation::Server::Api::Models']]], diff --git a/search/functions_0.js b/search/functions_0.js index 4e9b4cd0e5..a11b5f04b6 100644 --- a/search/functions_0.js +++ b/search/functions_0.js @@ -9,11 +9,11 @@ var searchData= ['administrationclient',['AdministrationClient',['../class_tgstation_1_1_server_1_1_client_1_1_administration_client.html#aa5d48433766cd2f29ee60007767f5387',1,'Tgstation::Server::Client::AdministrationClient']]], ['administrationcontroller',['AdministrationController',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#a6145b03bd412673cf490f67d9e1b9ad7',1,'Tgstation::Server::Host::Controllers::AdministrationController']]], ['allrights_3c_20tright_20_3e',['AllRights< TRight >',['../class_tgstation_1_1_server_1_1_api_1_1_rights_1_1_rights_helper.html#a0f8605b0068c15bb5e80fae8f858e0db',1,'Tgstation::Server::Api::Rights::RightsHelper']]], - ['apiclient',['ApiClient',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a26be1a658c69fad22bd229863013f2f7',1,'Tgstation::Server::Client::ApiClient']]], - ['apiconflictexception',['ApiConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#a80efe66912385fd30e003eda830fc8a8',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#ab4f3940b662b24b906bf15d94a0c64b9',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException()'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#ac779b4eb27f674d0c804955a8bb50b4b',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#a76b2d08f49a952c5693d785c379328e2',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(string message, Exception innerException)']]], + ['apiclient',['ApiClient',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a938e9977616883fcbad31ad1b07ab0ef',1,'Tgstation::Server::Client::ApiClient']]], + ['apiconflictexception',['ApiConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#aacb270dc4bed5ac02ef0a7f9837f011b',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#ab4f3940b662b24b906bf15d94a0c64b9',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException()'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#ac779b4eb27f674d0c804955a8bb50b4b',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_conflict_exception.html#a76b2d08f49a952c5693d785c379328e2',1,'Tgstation.Server.Client.ApiConflictException.ApiConflictException(string message, Exception innerException)']]], ['apicontroller',['ApiController',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_api_controller.html#a1049f4b2b9557ea752177833ef52052e',1,'Tgstation::Server::Host::Controllers::ApiController']]], - ['apiexception',['ApiException',['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a1249a3ee677e1f6f512f6f0259587113',1,'Tgstation.Server.Client.ApiException.ApiException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a17155a36f4798421f6ae9c44add39a08',1,'Tgstation.Server.Client.ApiException.ApiException(HttpResponseMessage responseMessage, string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a58f71c3c7a18641b8144f772068b976d',1,'Tgstation.Server.Client.ApiException.ApiException()'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a9dfbd90587b56d3db3e497db8b7a5ae1',1,'Tgstation.Server.Client.ApiException.ApiException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a36773be2804412a37326218e0005fa25',1,'Tgstation.Server.Client.ApiException.ApiException(string message, Exception innerException)']]], - ['apiheaders',['ApiHeaders',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#aee584e59a0c6535f1d3c60d620036253',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string token)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#ac6a606d30d1bd1cf066cd8f5d917df18',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string username, string password)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#aa03245a124379bec7f0eb3fc672a4e43',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(RequestHeaders requestHeaders)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#a16b461300fbc981ef39230f85b680870',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)']]], + ['apiexception',['ApiException',['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a149510570ccc25bc57e4c79bfe12e996',1,'Tgstation.Server.Client.ApiException.ApiException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a17155a36f4798421f6ae9c44add39a08',1,'Tgstation.Server.Client.ApiException.ApiException(HttpResponseMessage responseMessage, string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a58f71c3c7a18641b8144f772068b976d',1,'Tgstation.Server.Client.ApiException.ApiException()'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a9dfbd90587b56d3db3e497db8b7a5ae1',1,'Tgstation.Server.Client.ApiException.ApiException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_exception.html#a36773be2804412a37326218e0005fa25',1,'Tgstation.Server.Client.ApiException.ApiException(string message, Exception innerException)']]], + ['apiheaders',['ApiHeaders',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#aee584e59a0c6535f1d3c60d620036253',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string token)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#ac6a606d30d1bd1cf066cd8f5d917df18',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string username, string password)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#aa03245a124379bec7f0eb3fc672a4e43',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(RequestHeaders requestHeaders)'],['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#af8b10f130bfebd02509618c0239d6a1c',1,'Tgstation.Server.Api.ApiHeaders.ApiHeaders(ProductHeaderValue userAgent, string?token, string?username, string?password)']]], ['application',['Application',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html#abc6f8677db1baf9744162670d4a02a6c',1,'Tgstation::Server::Host::Core::Application']]], ['apply',['Apply',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_open_api_enum_var_names_extension.html#a984ca98811591400b90fb6b13383e318',1,'Tgstation.Server.Host.Core.OpenApiEnumVarNamesExtension.Apply()'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_swagger_configuration.html#a050adcf275d869f1db4760d553fadb6e',1,'Tgstation.Server.Host.Core.SwaggerConfiguration.Apply(OpenApiOperation operation, OperationFilterContext context)'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_swagger_configuration.html#ab65b4c6e4dbb553247a5d7524bd161a1',1,'Tgstation.Server.Host.Core.SwaggerConfiguration.Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_swagger_configuration.html#a4773a3d72f5dfce0b7f88a53e6abf4d0',1,'Tgstation.Server.Host.Core.SwaggerConfiguration.Apply(OpenApiSchema schema, SchemaFilterContext context)']]], ['applyupdate',['ApplyUpdate',['../interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control.html#a08e0b784b8a22398a183e759446bf5f3',1,'Tgstation.Server.Host.Core.IServerControl.ApplyUpdate()'],['../class_tgstation_1_1_server_1_1_host_1_1_server.html#a38345c5b5fc217b03e0142dc8513e0cd',1,'Tgstation.Server.Host.Server.ApplyUpdate()']]], diff --git a/search/functions_10.js b/search/functions_10.js index 1ba3233db0..588a758399 100644 --- a/search/functions_10.js +++ b/search/functions_10.js @@ -1,7 +1,7 @@ var searchData= [ ['ratelimit',['RateLimit',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#ae04b510fcc8e0f4cc9e380b3995e2f21',1,'Tgstation::Server::Host::Controllers::AdministrationController']]], - ['ratelimitexception',['RateLimitException',['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#a8c920b2d1a4eb58bf405d784e6e02fb4',1,'Tgstation.Server.Client.RateLimitException.RateLimitException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#a1bd82db7496764293896d95a761a5b1c',1,'Tgstation.Server.Client.RateLimitException.RateLimitException()'],['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#ab30825486f294c98083fb35043f5da0a',1,'Tgstation.Server.Client.RateLimitException.RateLimitException(string message, Exception innerException)']]], + ['ratelimitexception',['RateLimitException',['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#a01a8b1511a78e0d95e2582d1cb5d2a6f',1,'Tgstation.Server.Client.RateLimitException.RateLimitException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#a1bd82db7496764293896d95a761a5b1c',1,'Tgstation.Server.Client.RateLimitException.RateLimitException()'],['../class_tgstation_1_1_server_1_1_client_1_1_rate_limit_exception.html#ab30825486f294c98083fb35043f5da0a',1,'Tgstation.Server.Client.RateLimitException.RateLimitException(string message, Exception innerException)']]], ['rawcheckout',['RawCheckout',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html#ace81561f45095dabdd6969b281a2be08',1,'Tgstation::Server::Host::Components::Repository::Repository']]], ['read',['Read',['../class_tgstation_1_1_server_1_1_client_1_1_administration_client.html#a90fccaf2e24a3793a1c3892365415d63',1,'Tgstation.Server.Client.AdministrationClient.Read()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html#adb77f8fd7a46572e3c1d6bd90e3e300c',1,'Tgstation.Server.Client.Components.ConfigurationClient.Read()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_dream_daemon_client.html#afcc1957f652e164e4f2e9e05ca84bc9d',1,'Tgstation.Server.Client.Components.DreamDaemonClient.Read()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_dream_maker_client.html#aa2f286f3f548960f2f9f88f17eb31538',1,'Tgstation.Server.Client.Components.DreamMakerClient.Read()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_configuration_client.html#a8f860a7b9851f7bcffa232fcf82eb109',1,'Tgstation.Server.Client.Components.IConfigurationClient.Read()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_dream_daemon_client.html#a1e6a43558dcadc261304053b63476323',1,'Tgstation.Server.Client.Components.IDreamDaemonClient.Read()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_dream_maker_client.html#ac0b3a15957db0648ca8425469e1983b0',1,'Tgstation.Server.Client.Components.IDreamMakerClient.Read()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_instance_user_client.html#aae3394792a75d2690ff8038cb4e8007f',1,'Tgstation.Server.Client.Components.IInstanceUserClient.Read()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_user_client.html#ad1b39d718a1f3e635cf7ff8e24499d66',1,'Tgstation.Server.Client.Components.InstanceUserClient.Read()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_repository_client.html#a5ec2d45a88691381a86f02d6a941f912',1,'Tgstation.Server.Client.Components.IRepositoryClient.Read()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_repository_client.html#a37e9b08107bacf7d431b64ef3051fdc3',1,'Tgstation.Server.Client.Components.RepositoryClient.Read()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_administration_client.html#a2e63dc96c5b580f348e76f014924c85f',1,'Tgstation.Server.Client.IAdministrationClient.Read()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_users_client.html#adfc947bf76c50385d043a46732cb1f75',1,'Tgstation.Server.Client.IUsersClient.Read()'],['../class_tgstation_1_1_server_1_1_client_1_1_users_client.html#a7b9ce7489b2a8b3e7c8453580cfe95ff',1,'Tgstation.Server.Client.UsersClient.Read()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#ad62e7c4dde34d7c7d1039bcbf03c5b72',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.Read()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_i_configuration.html#a32086334958bd9009ec8eda200b9a200',1,'Tgstation.Server.Host.Components.StaticFiles.IConfiguration.Read()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#a21c2643c3151e9e65a6b6240a79e35c6',1,'Tgstation.Server.Host.Controllers.AdministrationController.Read()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_byond_controller.html#aa62cf4ea9a81c3772cc64fade69c095d',1,'Tgstation.Server.Host.Controllers.ByondController.Read()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_daemon_controller.html#a233a666f5638e5bca70e51a2d38a4ba7',1,'Tgstation.Server.Host.Controllers.DreamDaemonController.Read()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_maker_controller.html#a85e225aa92c30ff7e771c540a9672ba3',1,'Tgstation.Server.Host.Controllers.DreamMakerController.Read()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_user_controller.html#adb7e1f6c26a2e83339ae501c4ae955a3',1,'Tgstation.Server.Host.Controllers.InstanceUserController.Read()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_job_controller.html#a9a93d9ca244bb4f65df3dde6c00ace7d',1,'Tgstation.Server.Host.Controllers.JobController.Read()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#a0263b7f7bef9a5b23cdc52c3735af3c5',1,'Tgstation.Server.Host.Controllers.RepositoryController.Read()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_user_controller.html#ad82f699ed9786892700f4c9526eb06ad',1,'Tgstation.Server.Host.Controllers.UserController.Read()']]], ['read_3c_20tresult_20_3e',['Read< TResult >',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a1808561c1ec1ddac56ee78648788214c',1,'Tgstation.Server.Client.ApiClient.Read< TResult >(string route, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a90dffec712d3fea1328dd2b8cb4ce5b7',1,'Tgstation.Server.Client.ApiClient.Read< TResult >(string route, long instanceId, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#ae8bd028857ed8443176c7ac25edd18bd',1,'Tgstation.Server.Client.IApiClient.Read< TResult >(string route, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#a95d05b8d2e00d2da463ec7d7e6e4e2ae',1,'Tgstation.Server.Client.IApiClient.Read< TResult >(string route, long instanceId, CancellationToken cancellationToken)']]], @@ -15,6 +15,7 @@ var searchData= ['reattachinformation',['ReattachInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_reattach_information.html#af91a1e36e4a5bbac1057167f4e4ee830',1,'Tgstation::Server::Host::Components::Session::ReattachInformation']]], ['reattachinformationbase',['ReattachInformationBase',['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_reattach_information_base.html#a662295d2f9dd1746560777809a91eec0',1,'Tgstation.Server.Host.Models.ReattachInformationBase.ReattachInformationBase()'],['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_reattach_information_base.html#adbb46135d2724b48f149ded000511f49',1,'Tgstation.Server.Host.Models.ReattachInformationBase.ReattachInformationBase(ReattachInformationBase copy)']]], ['reconnectionloop',['ReconnectionLoop',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#a1c80c77a01d4ec41339f00c5f87c9692',1,'Tgstation::Server::Host::Components::Chat::Providers::Provider']]], + ['refreshtoken',['RefreshToken',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#abd283e3f14a6be71a3d4a8dedd12123e',1,'Tgstation::Server::Client::ApiClient']]], ['registercommandhandler',['RegisterCommandHandler',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html#ae1b556bac2bf1a08d0eef0fd76297dc4',1,'Tgstation.Server.Host.Components.Chat.ChatManager.RegisterCommandHandler()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_i_chat_manager.html#a1e1ce49aa3e843a1e037060a544e125e',1,'Tgstation.Server.Host.Components.Chat.IChatManager.RegisterCommandHandler()']]], ['registerforrestart',['RegisterForRestart',['../interface_tgstation_1_1_server_1_1_host_1_1_core_1_1_i_server_control.html#a35728d66030addfd5551351b8c6db942',1,'Tgstation.Server.Host.Core.IServerControl.RegisterForRestart()'],['../class_tgstation_1_1_server_1_1_host_1_1_server.html#a0664caef3896a16c9f668177c68002d3',1,'Tgstation.Server.Host.Server.RegisterForRestart()']]], ['registerhandler',['RegisterHandler',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#a785c474a5d39316af850239b0c7e107b',1,'Tgstation.Server.Host.Components.InstanceManager.RegisterHandler()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_registrar.html#aa36d6072e7a96ece183b77544c9bfe4b',1,'Tgstation.Server.Host.Components.Interop.Bridge.IBridgeRegistrar.RegisterHandler()']]], @@ -52,7 +53,7 @@ var searchData= ['rundreammaker',['RunDreamMaker',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#a2c7d7d0048942b67b6c275cd4a643296',1,'Tgstation::Server::Host::Components::Deployment::DreamMaker']]], ['runimpersonated',['RunImpersonated',['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_system_identity.html#a9b525a56c704424ce14e78edcb7d003e',1,'Tgstation.Server.Host.Security.ISystemIdentity.RunImpersonated()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_posix_system_identity.html#ad8b0bb1cd74b0bc433afeb4dcc322594',1,'Tgstation.Server.Host.Security.PosixSystemIdentity.RunImpersonated()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_windows_system_identity.html#aaa070f717bb19b6d85f0253d7820bc75',1,'Tgstation.Server.Host.Security.WindowsSystemIdentity.RunImpersonated()']]], ['runjob',['RunJob',['../class_tgstation_1_1_server_1_1_host_1_1_jobs_1_1_job_manager.html#a6836fc87ae58cab85a16f3018d485eb1',1,'Tgstation::Server::Host::Jobs::JobManager']]], - ['runrequest_3c_20tresult_20_3e',['RunRequest< TResult >',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#ac61843965bd203ff9a844f6286a68818',1,'Tgstation::Server::Client::ApiClient']]], + ['runrequest_3c_20tresult_20_3e',['RunRequest< TResult >',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a5ff438a63f0eaf323c7cf6adc8532cba',1,'Tgstation::Server::Client::ApiClient']]], ['runtimeinformation',['RuntimeInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_runtime_information.html#a75e05074b79b83f0c57cd15e25b77dae',1,'Tgstation::Server::Host::Components::Interop::Bridge::RuntimeInformation']]], ['runwatchdog',['RunWatchdog',['../class_tgstation_1_1_server_1_1_host_1_1_service_1_1_server_service.html#a5ce97a95847cecf3650ae60f0f62d836',1,'Tgstation::Server::Host::Service::ServerService']]], ['runwizard',['RunWizard',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#af95450fea6b5b6d723d91198b7b41d8e',1,'Tgstation::Server::Host::Setup::SetupWizard']]] diff --git a/search/functions_11.js b/search/functions_11.js index 693a3dede0..3a1088ada7 100644 --- a/search/functions_11.js +++ b/search/functions_11.js @@ -19,7 +19,7 @@ var searchData= ['server',['Server',['../class_tgstation_1_1_server_1_1_host_1_1_server.html#af345987c7e61e037445810ae96d8da52',1,'Tgstation::Server::Host::Server']]], ['serverclient',['ServerClient',['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#aeb7a24a33819012790a38d5fceb2c8bb',1,'Tgstation::Server::Client::ServerClient']]], ['serverclientfactory',['ServerClientFactory',['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#ae72794d4ee4b75d442896ef3764d435b',1,'Tgstation::Server::Client::ServerClientFactory']]], - ['servererrorexception',['ServerErrorException',['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a3c0883bc41902cff9f2becfdb64c0d97',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a0f2341503ba27d13ffdff703a7f99d5b',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a2426a2796880232113cfd743b1010fdb',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException(string message, Exception innerException)']]], + ['servererrorexception',['ServerErrorException',['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a3c0883bc41902cff9f2becfdb64c0d97',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a5d6579c3318e55695667c1688ad50050',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_server_error_exception.html#a2426a2796880232113cfd743b1010fdb',1,'Tgstation.Server.Client.ServerErrorException.ServerErrorException(string message, Exception innerException)']]], ['serverportproivder',['ServerPortProivder',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_server_port_proivder.html#a7406d2db02b7c64e6594bffbdb4a7308',1,'Tgstation::Server::Host::Core::ServerPortProivder']]], ['serverservice',['ServerService',['../class_tgstation_1_1_server_1_1_host_1_1_service_1_1_server_service.html#a3f4d7655e24f18f6929e48f48c79b0a2',1,'Tgstation::Server::Host::Service::ServerService']]], ['serversidemodifications',['ServerSideModifications',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_server_side_modifications.html#a80a1a88d3e1f59d285b91563e86e2ef8',1,'Tgstation::Server::Host::Components::StaticFiles::ServerSideModifications']]], diff --git a/search/functions_13.js b/search/functions_13.js index 8e4aeac4a4..efbf9bcc0d 100644 --- a/search/functions_13.js +++ b/search/functions_13.js @@ -1,6 +1,6 @@ var searchData= [ - ['unauthorizedexception',['UnauthorizedException',['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#a0d5d657c4a66de84328fa886fcdd9a0f',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#ab5b729c4ff826ad5fdd574db0942b8f7',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#af481bddb4a0e255b1430b6e9f4b62a7e',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(string message, Exception innerException)']]], + ['unauthorizedexception',['UnauthorizedException',['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#aad65235f9c5ef27610390fe1e41a7f53',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#ab5b729c4ff826ad5fdd574db0942b8f7',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_unauthorized_exception.html#af481bddb4a0e255b1430b6e9f4b62a7e',1,'Tgstation.Server.Client.UnauthorizedException.UnauthorizedException(string message, Exception innerException)']]], ['unrecognizedresponseexception',['UnrecognizedResponseException',['../class_tgstation_1_1_server_1_1_client_1_1_unrecognized_response_exception.html#a282b4f2eda537df30d31d3acc32b11db',1,'Tgstation.Server.Client.UnrecognizedResponseException.UnrecognizedResponseException(HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_unrecognized_response_exception.html#ada8a7b87eb9de50dd5b9e43b749ab1be',1,'Tgstation.Server.Client.UnrecognizedResponseException.UnrecognizedResponseException()'],['../class_tgstation_1_1_server_1_1_client_1_1_unrecognized_response_exception.html#a783856ad9431193f65c6b76381fb9990',1,'Tgstation.Server.Client.UnrecognizedResponseException.UnrecognizedResponseException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_unrecognized_response_exception.html#a299e46f1692182ca4c908d6bed931e00',1,'Tgstation.Server.Client.UnrecognizedResponseException.UnrecognizedResponseException(string message, Exception innerException)']]], ['up',['Up',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_initial_create.html#a7df7a394603b58660e31804151c6b596',1,'Tgstation.Server.Host.Database.Migrations.MSInitialCreate.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_initial_create.html#a6ce02c8ec227b29b0de690abb7b3a8af',1,'Tgstation.Server.Host.Database.Migrations.MYInitialCreate.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_add_minimum_security.html#ad01b39dca497b10060c2058a1dd66160',1,'Tgstation.Server.Host.Database.Migrations.MYAddMinimumSecurity.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_add_minimum_security.html#a48d1ac48f970e1bd84139bb112118398',1,'Tgstation.Server.Host.Database.Migrations.MSAddMinimumSecurity.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_nullable_and_foreign_key_cleanup.html#ae3c9b00858947f20604494860bf674de',1,'Tgstation.Server.Host.Database.Migrations.MYNullableAndForeignKeyCleanup.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_nullable_and_foreign_key_cleanup.html#ab83944ba8a141a2b0723b9180e638549',1,'Tgstation.Server.Host.Database.Migrations.MSNullableAndForeignKeyCleanup.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_reattach_compile_job_required.html#af9f127aef130a8dfd1fd957864d08570',1,'Tgstation.Server.Host.Database.Migrations.MSReattachCompileJobRequired.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_reattach_compile_job_required.html#a0ba7771562cd594634f73b8e239a3c6c',1,'Tgstation.Server.Host.Database.Migrations.MYReattachCompileJobRequired.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_toggle_testmerge_comments.html#a3544f139764a77e677142a392ebaf217',1,'Tgstation.Server.Host.Database.Migrations.MSToggleTestmergeComments.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_toggle_testmerge_comments.html#af4403fe4bde2f525305be051e4d6848c',1,'Tgstation.Server.Host.Database.Migrations.MYToggleTestmergeComments.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_fix_rev_info_index.html#a6eb6707c5b402b8d2ddbf31b7b25bc6e',1,'Tgstation.Server.Host.Database.Migrations.MSFixRevInfoIndex.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_fix_rev_info_index.html#ad64d31c0438c6d7d7893a2cc34d51d00',1,'Tgstation.Server.Host.Database.Migrations.MYFixRevInfoIndex.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_add_chat_bot_reconnection_interval.html#abdf29c335522af79ec11007d86808b7e',1,'Tgstation.Server.Host.Database.Migrations.MSAddChatBotReconnectionInterval.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_add_chat_bot_reconnection_interval.html#a8be4ce807c0078fead38522c78bd2547',1,'Tgstation.Server.Host.Database.Migrations.MYAddChatBotReconnectionInterval.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_tons_of_validation.html#a22370cdfbdbd8084ecc4a94c30d614c5',1,'Tgstation.Server.Host.Database.Migrations.MSTonsOfValidation.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_tons_of_validation.html#a22a2e1dbca28937f3dc4ae596b875d1f',1,'Tgstation.Server.Host.Database.Migrations.MYTonsOfValidation.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_limits_on_chat.html#a20f96799e10f96906c09d1f2cd7e26dd',1,'Tgstation.Server.Host.Database.Migrations.MSLimitsOnChat.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_limits_on_chat.html#a051b191cb4c6e4b1eaa32e96b0bbfa3a',1,'Tgstation.Server.Host.Database.Migrations.MYLimitsOnChat.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_add_compile_job_d_m_api_version.html#a5f6c37e76fc48cbf1c89c1bf174e327c',1,'Tgstation.Server.Host.Database.Migrations.MSAddCompileJobDMApiVersion.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_add_compile_job_d_m_api_version.html#a96a82dc6acfbac30b83c1f8181b6a92d',1,'Tgstation.Server.Host.Database.Migrations.MYAddCompileJobDMApiVersion.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_checkpoint_rebuild.html#ae0b40f0db7f96df8a2c37a2cd6a221ef',1,'Tgstation.Server.Host.Database.Migrations.MYCheckpointRebuild.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_checkpoint_rebuild.html#ae78d569e6f671655580e4461077b37bd',1,'Tgstation.Server.Host.Database.Migrations.MSCheckpointRebuild.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_kill_jsons_add_d_m_api_version.html#ad5f9c3ca384fa8d5d405a0c04b2f2917',1,'Tgstation.Server.Host.Database.Migrations.MSKillJsonsAddDMApiVersion.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_kill_jsons_add_d_m_api_version.html#a495a9fdada8b62f420551f79f517f98d',1,'Tgstation.Server.Host.Database.Migrations.MYKillJsonsAddDMApiVersion.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_add_job_error_codes.html#a0ea5e2853467d505261be8cc1ced22b1',1,'Tgstation.Server.Host.Database.Migrations.MSAddJobErrorCodes.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_add_job_error_codes.html#a3b4ba38bdf88d4fb349fffad5e8b564e',1,'Tgstation.Server.Host.Database.Migrations.MYAddJobErrorCodes.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_fix_cascading_delete.html#a990cb1cbeced6a9facb3146f61c3bb22',1,'Tgstation.Server.Host.Database.Migrations.MSFixCascadingDelete.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_fix_cascading_delete.html#a6115c297329930e0c71e351c03330e8b',1,'Tgstation.Server.Host.Database.Migrations.MYFixCascadingDelete.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_s_l_rebuild.html#a84b0a9d8a7be255cb2443bc3b29daaf9',1,'Tgstation.Server.Host.Database.Migrations.SLRebuild.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_add_heartbeat.html#a08dfb12aacd8b1b98b352d1afba5085c',1,'Tgstation.Server.Host.Database.Migrations.MSAddHeartbeat.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_add_heartbeat.html#a3c94db4d8958852e62984e00f8d3625a',1,'Tgstation.Server.Host.Database.Migrations.MYAddHeartbeat.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_s_l_add_heartbeat.html#a2338f4e1c23d55843679aa523fbdb9ad',1,'Tgstation.Server.Host.Database.Migrations.SLAddHeartbeat.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_s_l_remove_soft_columns.html#a893443a69066318b2a3e5868880b4d32',1,'Tgstation.Server.Host.Database.Migrations.SLRemoveSoftColumns.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_s_remove_soft_columns.html#a55448d2e7133c0b8444eb9c851954e52',1,'Tgstation.Server.Host.Database.Migrations.MSRemoveSoftColumns.Up()'],['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_migrations_1_1_m_y_remove_soft_columns.html#aed8f2b19b9edf7781ed2525d1f1a4130',1,'Tgstation.Server.Host.Database.Migrations.MYRemoveSoftColumns.Up()']]], ['update',['Update',['../class_tgstation_1_1_server_1_1_client_1_1_administration_client.html#a43842f5f15ef0b33c5450accdb18609f',1,'Tgstation.Server.Client.AdministrationClient.Update()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_chat_bots_client.html#a8f60027fc85212c9b35249cccc88d1e3',1,'Tgstation.Server.Client.Components.ChatBotsClient.Update()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_dream_daemon_client.html#a1bc64b3c8ac57f22694751cccf580f90',1,'Tgstation.Server.Client.Components.DreamDaemonClient.Update()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_dream_maker_client.html#ae428fd6c9fad6e1d019b3ded7579c0b5',1,'Tgstation.Server.Client.Components.DreamMakerClient.Update()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_chat_bots_client.html#ab2356151d04b5f17d797b22dc82c6354',1,'Tgstation.Server.Client.Components.IChatBotsClient.Update()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_dream_daemon_client.html#a9cc6c3e2db7c4491f00ff9347acfe634',1,'Tgstation.Server.Client.Components.IDreamDaemonClient.Update()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_dream_maker_client.html#a328b3fb9bebf37474ab554894b43eb54',1,'Tgstation.Server.Client.Components.IDreamMakerClient.Update()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_instance_user_client.html#a96063a725f2be7f6891ab46e66e7006b',1,'Tgstation.Server.Client.Components.IInstanceUserClient.Update()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_user_client.html#adf513d69fe64d03119446eba42df5250',1,'Tgstation.Server.Client.Components.InstanceUserClient.Update()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_repository_client.html#a24995226be87730e9b7d49d6c53f5b2c',1,'Tgstation.Server.Client.Components.IRepositoryClient.Update()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_repository_client.html#a718c5e27cfe0ddbfcc9c0746ec16a6e0',1,'Tgstation.Server.Client.Components.RepositoryClient.Update()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_administration_client.html#a666df801432630c494c916d35834a38b',1,'Tgstation.Server.Client.IAdministrationClient.Update()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_instance_manager_client.html#a337b3e0676570d2ba2928b0d2cef5595',1,'Tgstation.Server.Client.IInstanceManagerClient.Update()'],['../class_tgstation_1_1_server_1_1_client_1_1_instance_manager_client.html#aed0303f71e96db6fa88fc10cdfb196d3',1,'Tgstation.Server.Client.InstanceManagerClient.Update()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_users_client.html#aecbd14842867cfdce083b793f91ab14e',1,'Tgstation.Server.Client.IUsersClient.Update()'],['../class_tgstation_1_1_server_1_1_client_1_1_users_client.html#a89d2d761849b9b8f2932920841d996e1',1,'Tgstation.Server.Client.UsersClient.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#afbf7a02a0808b463ea298c43a03e0e99',1,'Tgstation.Server.Host.Controllers.AdministrationController.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_byond_controller.html#ad84ccddae9c935bc645b048f8ea46d7f',1,'Tgstation.Server.Host.Controllers.ByondController.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_chat_controller.html#a7568d3ba39d641f3fb486c779aea92cf',1,'Tgstation.Server.Host.Controllers.ChatController.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_configuration_controller.html#a7c19c1f24368f45b5e65ccf73fc58416',1,'Tgstation.Server.Host.Controllers.ConfigurationController.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_daemon_controller.html#ae9ce00c8ac0758a4c2328eae7d7ef2eb',1,'Tgstation.Server.Host.Controllers.DreamDaemonController.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_maker_controller.html#a6ec0755f8794e6323ba59634c76bd498',1,'Tgstation.Server.Host.Controllers.DreamMakerController.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#ad31a819fbeeca3ebd71d35892ce10b19',1,'Tgstation.Server.Host.Controllers.InstanceController.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_user_controller.html#acf0cc09f53acd6c87a91a4a01afe8daa',1,'Tgstation.Server.Host.Controllers.InstanceUserController.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#a7fc3a45ac79e0707e7fe4bb1b625761d',1,'Tgstation.Server.Host.Controllers.RepositoryController.Update()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_user_controller.html#a7f795d4042188f30317c6134b75afc70',1,'Tgstation.Server.Host.Controllers.UserController.Update()']]], diff --git a/search/functions_14.js b/search/functions_14.js index 92f4136583..657f90a47a 100644 --- a/search/functions_14.js +++ b/search/functions_14.js @@ -8,5 +8,5 @@ var searchData= ['version',['Version',['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client.html#a3bebba1e2658a94424e66713bf1085c4',1,'Tgstation.Server.Client.IServerClient.Version()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#a8c853236fdd5679440d5ba7a1289012e',1,'Tgstation.Server.Client.ServerClient.Version()']]], ['versioncommand',['VersionCommand',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_version_command.html#aac843c7ae2125089ba16ccf1e35a0a0d',1,'Tgstation::Server::Host::Components::Chat::Commands::VersionCommand']]], ['versionkey',['VersionKey',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_byond_manager.html#a02c588c08d51d013fa4e6c5258478352',1,'Tgstation::Server::Host::Components::Byond::ByondManager']]], - ['versionmismatchexception',['VersionMismatchException',['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a8437edf6af43ab4caeb8f42fc2f8d0fa',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a98f49568d813eb370d5729acf521f661',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException()'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a28b7f81f775966bb71c0dcaf27734684',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#afac834c26a3f8b12f53c8a4e6deaae2c',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(string message, Exception innerException)']]] + ['versionmismatchexception',['VersionMismatchException',['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#abc1871a5ead0f442c12525e5e024ecac',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a98f49568d813eb370d5729acf521f661',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException()'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#a28b7f81f775966bb71c0dcaf27734684',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_version_mismatch_exception.html#afac834c26a3f8b12f53c8a4e6deaae2c',1,'Tgstation.Server.Client.VersionMismatchException.VersionMismatchException(string message, Exception innerException)']]] ]; diff --git a/search/functions_2.js b/search/functions_2.js index cce5e450c7..2216fd3321 100644 --- a/search/functions_2.js +++ b/search/functions_2.js @@ -63,7 +63,7 @@ var searchData= ['configurehostedservice',['ConfigureHostedService',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html#a1d6e849d4a69fe90f23f60aaeb2bc960',1,'Tgstation.Server.Host.Core.Application.ConfigureHostedService()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_application.html#a03097b9f375ae721b318fc2aaf970b9b',1,'Tgstation.Server.Host.Setup.SetupApplication.ConfigureHostedService()']]], ['configurelogging',['ConfigureLogging',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a95d031c71d5219c0d2327191b220311b',1,'Tgstation::Server::Host::Setup::SetupWizard']]], ['configureservices',['ConfigureServices',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html#a2b610f9600937f97857f303dcfe41823',1,'Tgstation.Server.Host.Core.Application.ConfigureServices()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_application.html#a1225384501e2dfd8dd509a1f96ea2fa6',1,'Tgstation.Server.Host.Setup.SetupApplication.ConfigureServices()']]], - ['conflictexception',['ConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a7f70c88fb9025a7db9bac18bb8bfa5e5',1,'Tgstation.Server.Client.ConflictException.ConflictException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a6f35d432dfaab891a7652e2fc636c258',1,'Tgstation.Server.Client.ConflictException.ConflictException()'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a5577bc93af2abe799a86b0c383902ea2',1,'Tgstation.Server.Client.ConflictException.ConflictException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a08028d33db9e62104c77df9ef3f673af',1,'Tgstation.Server.Client.ConflictException.ConflictException(string message, Exception innerException)']]], + ['conflictexception',['ConflictException',['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a4bb2278f0196e618de930579e88f3a8f',1,'Tgstation.Server.Client.ConflictException.ConflictException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a6f35d432dfaab891a7652e2fc636c258',1,'Tgstation.Server.Client.ConflictException.ConflictException()'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a5577bc93af2abe799a86b0c383902ea2',1,'Tgstation.Server.Client.ConflictException.ConflictException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_conflict_exception.html#a08028d33db9e62104c77df9ef3f673af',1,'Tgstation.Server.Client.ConflictException.ConflictException(string message, Exception innerException)']]], ['connect',['Connect',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_discord_provider.html#ae0ae6a98f9e73c337f6b4fca3791df76',1,'Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.Connect()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_i_provider.html#a78705214e55337f3970869e3fcabb502',1,'Tgstation.Server.Host.Components.Chat.Providers.IProvider.Connect()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a5db89afa73a4637471b476a2e534861d',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.Connect()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#affce280626a132f2e2a2731946e39a0c',1,'Tgstation.Server.Host.Components.Chat.Providers.Provider.Connect()']]], ['console',['Console',['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_console.html#aba874ec794084e164acb19d73c7c1dee',1,'Tgstation::Server::Host::IO::Console']]], ['convertapichatchannel',['ConvertApiChatChannel',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_chat_controller.html#a30fce4a18499c3c89b4b153b9805ce85',1,'Tgstation::Server::Host::Controllers::ChatController']]], @@ -75,7 +75,7 @@ var searchData= ['create',['Create',['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_chat_bots_client.html#a2683730461ef575dbaab18fb01a10162',1,'Tgstation.Server.Client.Components.ChatBotsClient.Create()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_chat_bots_client.html#a781f11da2fc0bc852e57544872e2f47e',1,'Tgstation.Server.Client.Components.IChatBotsClient.Create()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_instance_user_client.html#a8f315515848f8b7397e71255fa1055c9',1,'Tgstation.Server.Client.Components.IInstanceUserClient.Create()'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_user_client.html#af62bfee4e1cdca8a216bdade995a57b7',1,'Tgstation.Server.Client.Components.InstanceUserClient.Create()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_users_client.html#a60a4f54b4b6cb61d3c0054b9b7a04604',1,'Tgstation.Server.Client.IUsersClient.Create()'],['../class_tgstation_1_1_server_1_1_client_1_1_users_client.html#a68a9c4aea0859ef158ef8146e22d0fd6',1,'Tgstation.Server.Client.UsersClient.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_chat_controller.html#a38a75a3f14b37983b1e5a76f2186e0ad',1,'Tgstation.Server.Host.Controllers.ChatController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_configuration_controller.html#a111121fa22f2648803f8f890c8a6305b',1,'Tgstation.Server.Host.Controllers.ConfigurationController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_daemon_controller.html#af83519b8dbc46ddeffef6862367721b2',1,'Tgstation.Server.Host.Controllers.DreamDaemonController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_dream_maker_controller.html#a7231f4980bd98c9a033e805c90e92c95',1,'Tgstation.Server.Host.Controllers.DreamMakerController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#a8712ba6603139064d68520e16c076901',1,'Tgstation.Server.Host.Controllers.InstanceController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_user_controller.html#a3a26d7b7a1b8c8c8c0ade33893f24b09',1,'Tgstation.Server.Host.Controllers.InstanceUserController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#a30a5b00625419334b1d51ae4a51aebda',1,'Tgstation.Server.Host.Controllers.RepositoryController.Create()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_user_controller.html#a99457477cad4b6adcedbba47d939f005',1,'Tgstation.Server.Host.Controllers.UserController.Create()']]], ['create_3c_20tbody_2c_20tresult_20_3e',['Create< TBody, TResult >',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a73701939b0af1ee33b46c931829200fc',1,'Tgstation.Server.Client.ApiClient.Create< TBody, TResult >(string route, TBody body, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#ab5fc54acd429e50a254fccaf23e52299',1,'Tgstation.Server.Client.ApiClient.Create< TBody, TResult >(string route, TBody body, long instanceId, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#a0448d3803d9f45d2e67295cef2096174',1,'Tgstation.Server.Client.IApiClient.Create< TBody, TResult >(string route, TBody body, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#a726cb2cf80d2499c3e6ff8ae67bbc0ed',1,'Tgstation.Server.Client.IApiClient.Create< TBody, TResult >(string route, TBody body, long instanceId, CancellationToken cancellationToken)']]], ['create_3c_20tresult_20_3e',['Create< TResult >',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a5c7201add0fadb2be2851aa749a0c3c6',1,'Tgstation.Server.Client.ApiClient.Create< TResult >(string route, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a70d6c289a7e07426ff72c36eb2502819',1,'Tgstation.Server.Client.ApiClient.Create< TResult >(string route, long instanceId, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#ac4bbf7c3cc9ff87beda2b13f018a25f5',1,'Tgstation.Server.Client.IApiClient.Create< TResult >(string route, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client.html#a16d32f00e49bed861bcbd1c6c9325727',1,'Tgstation.Server.Client.IApiClient.Create< TResult >(string route, long instanceId, CancellationToken cancellationToken)']]], - ['createapiclient',['CreateApiClient',['../class_tgstation_1_1_server_1_1_client_1_1_api_client_factory.html#af1210c7eddf5533d80c0618d99fc6b4d',1,'Tgstation.Server.Client.ApiClientFactory.CreateApiClient()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client_factory.html#add8cac56a24b5f4a43d870395de0ea83',1,'Tgstation.Server.Client.IApiClientFactory.CreateApiClient()']]], + ['createapiclient',['CreateApiClient',['../class_tgstation_1_1_server_1_1_client_1_1_api_client_factory.html#aff36fce79abf84418555ce3655fcea67',1,'Tgstation.Server.Client.ApiClientFactory.CreateApiClient()'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_api_client_factory.html#ac165b326c27c0d7a834d2af5eff02645',1,'Tgstation.Server.Client.IApiClientFactory.CreateApiClient()']]], ['createauthenticationcontext',['CreateAuthenticationContext',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_authentication_context_factory.html#a26c8a971d94af552e5933deaccbbe693',1,'Tgstation.Server.Host.Security.AuthenticationContextFactory.CreateAuthenticationContext()'],['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_authentication_context_factory.html#a0976873c7f5654dbe88919ed3cb20d84',1,'Tgstation.Server.Host.Security.IAuthenticationContextFactory.CreateAuthenticationContext()']]], ['createbaseclient',['CreateBaseClient',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_git_hub_client_factory.html#a0d2033f1157b71089477c1383ed77e09',1,'Tgstation::Server::Host::Core::GitHubClientFactory']]], ['createchatmanager',['CreateChatManager',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager_factory.html#ab7cb1f1ac63ad00a080ec4cb65b03ac6',1,'Tgstation.Server.Host.Components.Chat.ChatManagerFactory.CreateChatManager()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_i_chat_manager_factory.html#a2da119ff14c5553f07a8e0c3b3d43755',1,'Tgstation.Server.Host.Components.Chat.IChatManagerFactory.CreateChatManager()']]], @@ -87,7 +87,9 @@ var searchData= ['createdefaultserverfactory',['CreateDefaultServerFactory',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html#a6720ad4775ff4751c0d2e00b36f0c31f',1,'Tgstation::Server::Host::Core::Application']]], ['createdirectory',['CreateDirectory',['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_configuration_client.html#ad3f4cf594c24523ba3717b926843c078',1,'Tgstation.Server.Client.Components.ConfigurationClient.CreateDirectory()'],['../interface_tgstation_1_1_server_1_1_client_1_1_components_1_1_i_configuration_client.html#ab7b97489e1cb6307e417f6ecc22b9260',1,'Tgstation.Server.Client.Components.IConfigurationClient.CreateDirectory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#a9e6d3a7af0e253142f6ea1fe932f6002',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.CreateDirectory()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_i_configuration.html#aac9d5ed7fad37fc5f29295f4a3445449',1,'Tgstation.Server.Host.Components.StaticFiles.IConfiguration.CreateDirectory()'],['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_default_i_o_manager.html#a35975e796c564e8b5dd5d4a601d0c2e3',1,'Tgstation.Server.Host.IO.DefaultIOManager.CreateDirectory()'],['../interface_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_i_i_o_manager.html#a69aa1b9dbeb6af3744e568095c4376e7',1,'Tgstation.Server.Host.IO.IIOManager.CreateDirectory()'],['../interface_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_i_synchronous_i_o_manager.html#a87b99f131bf6b48d725561414d4ddfcb',1,'Tgstation.Server.Host.IO.ISynchronousIOManager.CreateDirectory()'],['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_synchronous_i_o_manager.html#a595b31e7065ee0d3431903fa0ac55156',1,'Tgstation.Server.Host.IO.SynchronousIOManager.CreateDirectory()']]], ['createfromexistinghandle',['CreateFromExistingHandle',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_process_executor.html#ab8ce5d8354aea3fec89a1d7687acb877',1,'Tgstation::Server::Host::System::ProcessExecutor']]], + ['createfromlogin',['CreateFromLogin',['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory.html#a861f94dac58c8dbd011dffb92a5b8ffc',1,'Tgstation.Server.Client.IServerClientFactory.CreateFromLogin()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#a10753b8b7dfa6edbd02e117680dba4bd',1,'Tgstation.Server.Client.ServerClientFactory.CreateFromLogin()']]], ['createfrompath',['CreateFromPath',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_i_lib_git2_repository_factory.html#a855673b50e2639242bea6d6770c39773',1,'Tgstation.Server.Host.Components.Repository.ILibGit2RepositoryFactory.CreateFromPath()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_lib_git2_repository_factory.html#aff825ad3b4e4f4de83285d3cf2281e7b',1,'Tgstation.Server.Host.Components.Repository.LibGit2RepositoryFactory.CreateFromPath()']]], + ['createfromtoken',['CreateFromToken',['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory.html#ada7a9fcb461c62ddb534a57e1d3791c7',1,'Tgstation.Server.Client.IServerClientFactory.CreateFromToken()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#abc81fe410ef4bbf999e7531505cac7e1',1,'Tgstation.Server.Client.ServerClientFactory.CreateFromToken()']]], ['createinmemory',['CreateInMemory',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_i_lib_git2_repository_factory.html#af08df14e3a5e9e63b14a6d65cc47abd6',1,'Tgstation.Server.Host.Components.Repository.ILibGit2RepositoryFactory.CreateInMemory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_lib_git2_repository_factory.html#ad04e55b87e2ea16c68210ce73a80cebd',1,'Tgstation.Server.Host.Components.Repository.LibGit2RepositoryFactory.CreateInMemory()']]], ['createinstance',['CreateInstance',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_i_instance_factory.html#aba000cffe2b386d227e1a9d174d026d1',1,'Tgstation.Server.Host.Components.IInstanceFactory.CreateInstance()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#ac02a0bbbf943e2b7db0d57398283b497',1,'Tgstation.Server.Host.Components.InstanceFactory.CreateInstance()']]], ['createnonexperimentalwatchdog',['CreateNonExperimentalWatchdog',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_factory.html#a46ae6b8af80e6d489c2a7714b71a9900',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogFactory.CreateNonExperimentalWatchdog()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_windows_watchdog_factory.html#a6a30173c82382567c5aa2dfe472d4683',1,'Tgstation.Server.Host.Components.Watchdog.WindowsWatchdogFactory.CreateNonExperimentalWatchdog()']]], @@ -96,7 +98,6 @@ var searchData= ['createreattachinformation',['CreateReattachInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_basic_watchdog.html#af6174583a40835ca31655641929a8d6f',1,'Tgstation.Server.Host.Components.Watchdog.BasicWatchdog.CreateReattachInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_experimental_watchdog.html#a065b63b3193290fc814da9974fe69d71',1,'Tgstation.Server.Host.Components.Watchdog.ExperimentalWatchdog.CreateReattachInformation()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#a63aeed1dfa0a07ab3eca31f9c8afcfda',1,'Tgstation.Server.Host.Components.Watchdog.WatchdogBase.CreateReattachInformation()']]], ['createruntimeinformation',['CreateRuntimeInformation',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#aeb04b547d45cf7564350506287146621',1,'Tgstation::Server::Host::Components::Session::SessionControllerFactory']]], ['createserver',['CreateServer',['../interface_tgstation_1_1_server_1_1_host_1_1_i_server_factory.html#a063542cb78195a507feb3c0e6921b01b',1,'Tgstation.Server.Host.IServerFactory.CreateServer()'],['../class_tgstation_1_1_server_1_1_host_1_1_server_factory.html#a4234b15ede546c23f9b5d35bc5c8749b',1,'Tgstation.Server.Host.ServerFactory.CreateServer()']]], - ['createserverclient',['CreateServerClient',['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory.html#aed42fe9280a989ba78b8fd2a830a93a8',1,'Tgstation.Server.Client.IServerClientFactory.CreateServerClient(Uri host, string username, string password, TimeSpan timeout=default, CancellationToken cancellationToken=default)'],['../interface_tgstation_1_1_server_1_1_client_1_1_i_server_client_factory.html#a58ce916728cfc81f8e143682cd1df5e7',1,'Tgstation.Server.Client.IServerClientFactory.CreateServerClient(Uri host, Token token, TimeSpan timeout=default)'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#a358685d267630347317e476195680cc9',1,'Tgstation.Server.Client.ServerClientFactory.CreateServerClient(Uri host, string username, string password, TimeSpan timeout, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client_factory.html#a6c53de0537efa094a9c2c52b681acaa2',1,'Tgstation.Server.Client.ServerClientFactory.CreateServerClient(Uri host, Token token, TimeSpan timeout)']]], ['createsymboliclink',['CreateSymbolicLink',['../interface_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_i_symlink_factory.html#a35bd8c53ef36c4a655e827750a40699c',1,'Tgstation.Server.Host.IO.ISymlinkFactory.CreateSymbolicLink()'],['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_posix_symlink_factory.html#ac487bf33d9a5821fb30bfd7b74915fbe',1,'Tgstation.Server.Host.IO.PosixSymlinkFactory.CreateSymbolicLink()'],['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_windows_symlink_factory.html#a87a0b1b31b48f65e53e4b3b6f476a2c0',1,'Tgstation.Server.Host.IO.WindowsSymlinkFactory.CreateSymbolicLink()'],['../class_tgstation_1_1_server_1_1_host_1_1_native_methods.html#a668c437c860fb2eafcee04a1a2a613c9',1,'Tgstation.Server.Host.NativeMethods.CreateSymbolicLink()']]], ['createsystemidentity',['CreateSystemIdentity',['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_system_identity_factory.html#adaf274d215700bf692b1c3964ff32103',1,'Tgstation.Server.Host.Security.ISystemIdentityFactory.CreateSystemIdentity(User user, CancellationToken cancellationToken)'],['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_system_identity_factory.html#a6dd83aca91cf43c7236afd22dc512b0a',1,'Tgstation.Server.Host.Security.ISystemIdentityFactory.CreateSystemIdentity(string username, string password, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_posix_system_identity_factory.html#a8e2c85e4ed45d68065f86749afa4651d',1,'Tgstation.Server.Host.Security.PosixSystemIdentityFactory.CreateSystemIdentity(User user, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_posix_system_identity_factory.html#a4621a750f1d0592586d26323af215d93',1,'Tgstation.Server.Host.Security.PosixSystemIdentityFactory.CreateSystemIdentity(string username, string password, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_windows_system_identity_factory.html#a5c140611a428b663c3a3f76623f825c2',1,'Tgstation.Server.Host.Security.WindowsSystemIdentityFactory.CreateSystemIdentity(User user, CancellationToken cancellationToken)'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_windows_system_identity_factory.html#a24c82be81ba5cda6c975a931e8bfe6f1',1,'Tgstation.Server.Host.Security.WindowsSystemIdentityFactory.CreateSystemIdentity(string username, string password, CancellationToken cancellationToken)']]], ['createtoken',['CreateToken',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#afe4c43b884ebd0e9c4b60e39d4e1c162',1,'Tgstation.Server.Host.Controllers.HomeController.CreateToken()'],['../interface_tgstation_1_1_server_1_1_host_1_1_security_1_1_i_token_factory.html#a57edffc2d8c63352fbf4ebd0b5991b45',1,'Tgstation.Server.Host.Security.ITokenFactory.CreateToken()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#a17610f96f7e87136d957e8ba3c1f5d4a',1,'Tgstation.Server.Host.Security.TokenFactory.CreateToken()']]], diff --git a/search/functions_4.js b/search/functions_4.js index 55d258d34c..0d88150b25 100644 --- a/search/functions_4.js +++ b/search/functions_4.js @@ -6,7 +6,7 @@ var searchData= ['enumchildwindows',['EnumChildWindows',['../class_tgstation_1_1_server_1_1_host_1_1_native_methods.html#a6b4b37ca121784ecd63c477cc022415d',1,'Tgstation::Server::Host::NativeMethods']]], ['enumwindow',['EnumWindow',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_windows_network_prompt_reaper.html#a8ebd64b49b87ea79d0b6b6fd5d4290b4',1,'Tgstation::Server::Host::System::WindowsNetworkPromptReaper']]], ['enumwindowproc',['EnumWindowProc',['../class_tgstation_1_1_server_1_1_host_1_1_native_methods.html#a865bfe43e7d4085326ccf40500eb4222',1,'Tgstation::Server::Host::NativeMethods']]], - ['errormessage',['ErrorMessage',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message.html#abfbfef9f57a4e2d279cd3fa227b32711',1,'Tgstation::Server::Api::Models::ErrorMessage']]], + ['errormessage',['ErrorMessage',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message.html#a594af171f50f98e2be8481692c6584cc',1,'Tgstation.Server.Api.Models.ErrorMessage.ErrorMessage()'],['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_error_message.html#abfbfef9f57a4e2d279cd3fa227b32711',1,'Tgstation.Server.Api.Models.ErrorMessage.ErrorMessage(ErrorCode errorCode)']]], ['eventconsumer',['EventConsumer',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_event_consumer.html#a6d87fe2d067c9a71ed39e1e3e1a59d86',1,'Tgstation::Server::Host::Components::EventConsumer']]], ['eventnotification',['EventNotification',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_topic_1_1_event_notification.html#af7bba6883ff8690cb16cf257291aca02',1,'Tgstation::Server::Host::Components::Interop::Topic::EventNotification']]], ['experimentalwatchdog',['ExperimentalWatchdog',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_experimental_watchdog.html#aec2b62955bd386af0514bab61831a3df',1,'Tgstation::Server::Host::Components::Watchdog::ExperimentalWatchdog']]] diff --git a/search/functions_8.js b/search/functions_8.js index 147486e77b..e4fa240cd8 100644 --- a/search/functions_8.js +++ b/search/functions_8.js @@ -14,7 +14,7 @@ var searchData= ['instanceclient',['InstanceClient',['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_client.html#af44767648e0b31f744ea09f0864c70ca',1,'Tgstation::Server::Client::Components::InstanceClient']]], ['instancecontroller',['InstanceController',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#addd1307d3fea6368330400f6927ee701',1,'Tgstation::Server::Host::Controllers::InstanceController']]], ['instancefactory',['InstanceFactory',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#a589fcf563d721704843856a9b5f7b0cb',1,'Tgstation::Server::Host::Components::InstanceFactory']]], - ['instancemanager',['InstanceManager',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#a6a44bb969dab0811a31b9c990c82ffd3',1,'Tgstation::Server::Host::Components::InstanceManager']]], + ['instancemanager',['InstanceManager',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#a4ce33ffacb174f0ac669e29058854318',1,'Tgstation::Server::Host::Components::InstanceManager']]], ['instancemanagerclient',['InstanceManagerClient',['../class_tgstation_1_1_server_1_1_client_1_1_instance_manager_client.html#a2d6043c1548bc86551fe56f12a78d4cb',1,'Tgstation::Server::Client::InstanceManagerClient']]], ['instancerenamed',['InstanceRenamed',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_i_bridge_handler.html#ab5f7604c144af43d3b7741d230e22b05',1,'Tgstation.Server.Host.Components.Interop.Bridge.IBridgeHandler.InstanceRenamed()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller.html#a903d2a63951a7425699c6936160826d7',1,'Tgstation.Server.Host.Components.Session.SessionController.InstanceRenamed()']]], ['instanceuser',['InstanceUser',['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_user_client.html#a622a63b2de1299e16c06422a974e9ad5',1,'Tgstation.Server.Client.Components.InstanceUserClient.InstanceUser(Routes.InstanceUser, instanceUser??throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken)'],['../class_tgstation_1_1_server_1_1_client_1_1_components_1_1_instance_user_client.html#a622a63b2de1299e16c06422a974e9ad5',1,'Tgstation.Server.Client.Components.InstanceUserClient.InstanceUser(Routes.InstanceUser, instanceUser??throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken)']]], diff --git a/search/functions_c.js b/search/functions_c.js index 7c70ac34d0..bd703771e2 100644 --- a/search/functions_c.js +++ b/search/functions_c.js @@ -5,7 +5,7 @@ var searchData= ['mapchannels',['MapChannels',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_discord_provider.html#a8f3c3fbf096a7118ad96f94838852028',1,'Tgstation.Server.Host.Components.Chat.Providers.DiscordProvider.MapChannels()'],['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_i_provider.html#acb8970c1062c37761b832ea9aa4f3254',1,'Tgstation.Server.Host.Components.Chat.Providers.IProvider.MapChannels()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a11c9e7a1c9b6ec1fdeb711f1ab519713',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.MapChannels()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider.html#ac08fc8e6866dc5659c16a28840674cdc',1,'Tgstation.Server.Host.Components.Chat.Providers.Provider.MapChannels()']]], ['match',['Match',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_internal_1_1_dream_daemon_launch_parameters.html#acf0f8c66b9e3cfa5af8240d0efac2e36',1,'Tgstation::Server::Api::Models::Internal::DreamDaemonLaunchParameters']]], ['mergeorigin',['MergeOrigin',['../interface_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_i_repository.html#a2911d39fd36e857c0d77503fc5c41a94',1,'Tgstation.Server.Host.Components.Repository.IRepository.MergeOrigin()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html#aa2437784b303f5c523a6638d717919eb',1,'Tgstation.Server.Host.Components.Repository.Repository.MergeOrigin()']]], - ['methodnotsupportedexception',['MethodNotSupportedException',['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a6669ef2642a0c7c113ffcbb341add9db',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(ErrorMessage errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a37543ec8aabe8b8fa96a15243d02e318',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a5b0a4306e368279a8b18f50a7ad6bb63',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(string message, Exception innerException)']]], + ['methodnotsupportedexception',['MethodNotSupportedException',['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#ac6795b4ca2ddafd8cf3d9eb94e1e65b3',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(ErrorMessage?errorMessage, HttpResponseMessage responseMessage)'],['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a37543ec8aabe8b8fa96a15243d02e318',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(string message)'],['../class_tgstation_1_1_server_1_1_client_1_1_method_not_supported_exception.html#a5b0a4306e368279a8b18f50a7ad6bb63',1,'Tgstation.Server.Client.MethodNotSupportedException.MethodNotSupportedException(string message, Exception innerException)']]], ['modifydme',['ModifyDme',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#a1f48709bfdc0a2d5f675a2a2862755aa',1,'Tgstation::Server::Host::Components::Deployment::DreamMaker']]], ['monitorlifetimes',['MonitorLifetimes',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_watchdog_base.html#adfec26f530228b80d384b86ee979ef2f',1,'Tgstation::Server::Host::Components::Watchdog::WatchdogBase']]], ['monitormessages',['MonitorMessages',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html#a530e8ef5a58711a9c6744021df74afb2',1,'Tgstation::Server::Host::Components::Chat::ChatManager']]], diff --git a/search/properties_c.js b/search/properties_c.js index 5e282959a5..6bc1708e39 100644 --- a/search/properties_c.js +++ b/search/properties_c.js @@ -5,7 +5,7 @@ var searchData= ['newport',['NewPort',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_bridge_1_1_bridge_response.html#a0503e94799c46547d56a01477d9b7f6e',1,'Tgstation.Server.Host.Components.Interop.Bridge.BridgeResponse.NewPort()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_topic_1_1_topic_parameters.html#acee5f8c5002b7657c5417b578fe2e741',1,'Tgstation.Server.Host.Components.Interop.Topic.TopicParameters.NewPort()']]], ['newrebootstate',['NewRebootState',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_topic_1_1_topic_parameters.html#a6cb18ce9da1d576a7ce97e148f065a56',1,'Tgstation::Server::Host::Components::Interop::Topic::TopicParameters']]], ['newserverversion',['NewServerVersion',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_topic_1_1_topic_parameters.html#a9e0422cc12040f835acbbb340d453ff7',1,'Tgstation::Server::Host::Components::Interop::Topic::TopicParameters']]], - ['newtestmerges',['NewTestMerges',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html#a05bdec3c08f5acba28d9e72d70f38833',1,'Tgstation::Server::Api::Models::Repository']]], + ['newtestmerges',['NewTestMerges',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_repository.html#a56be56ee822e8842553b357e20c491f2',1,'Tgstation::Server::Api::Models::Repository']]], ['newversion',['NewVersion',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_administration.html#ab40a1df200563a8351347145f50a8bc1',1,'Tgstation::Server::Api::Models::Administration']]], ['nextaction',['NextAction',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_monitor_state.html#a87534d177a8fda685d068f47526b5bad',1,'Tgstation::Server::Host::Components::Watchdog::MonitorState']]], ['nickname',['Nickname',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_irc_connection_string_builder.html#a95d8cce20205d05b7bfbda1d088af791',1,'Tgstation::Server::Api::Models::IrcConnectionStringBuilder']]], diff --git a/search/variables_0.js b/search/variables_0.js index 086bf0eca6..0ab5cd02a9 100644 --- a/search/variables_0.js +++ b/search/variables_0.js @@ -8,7 +8,7 @@ var searchData= ['address',['address',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a5b89ca154b65b12e53b6dca3ceeb0243',1,'Tgstation::Server::Host::Components::Chat::Providers::IrcProvider']]], ['adirectoryname',['ADirectoryName',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#af376ceee6d2a7c67c0c99477eea92886',1,'Tgstation::Server::Host::Components::Deployment::DreamMaker']]], ['administration',['Administration',['../class_tgstation_1_1_server_1_1_api_1_1_routes.html#a1bd414117e790a954bd7bfb83c7d4fc9',1,'Tgstation::Server::Api::Routes']]], - ['adminname',['AdminName',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html#a446a804a6eeed2502879382f46df0b9d',1,'Tgstation::Server::Api::Models::User']]], + ['adminname',['AdminName',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html#a9a48baf4f5ea3e824d8dc489d3aa38f6',1,'Tgstation::Server::Api::Models::User']]], ['adminonly',['AdminOnly',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_byond_command.html#a42073a7395ce9de9b0de9d36e4d6f388',1,'Tgstation.Server.Host.Components.Chat.Commands.ByondCommand.AdminOnly()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_kek_command.html#ad69632c6b423bea790abde206d926425',1,'Tgstation.Server.Host.Components.Chat.Commands.KekCommand.AdminOnly()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_pull_requests_command.html#a63dc4a93cb647e14f40a613101d7335e',1,'Tgstation.Server.Host.Components.Chat.Commands.PullRequestsCommand.AdminOnly()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_revision_command.html#a17094a94c993b8bbb5fdda8b7a470707',1,'Tgstation.Server.Host.Components.Chat.Commands.RevisionCommand.AdminOnly()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_version_command.html#a801fd1e205cb8e006fefcbf8af8e3b30',1,'Tgstation.Server.Host.Components.Chat.Commands.VersionCommand.AdminOnly()']]], ['alphabravostartupseperationinterval',['AlphaBravoStartupSeperationInterval',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_experimental_watchdog.html#a2ff3cf57883f2b63890498b5e6b4821b',1,'Tgstation::Server::Host::Components::Watchdog::ExperimentalWatchdog']]], ['alphaisactive',['AlphaIsActive',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_basic_watchdog.html#ae69887bebd2d65ed9157984bd4f53b7d',1,'Tgstation.Server.Host.Components.Watchdog.BasicWatchdog.AlphaIsActive()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_experimental_watchdog.html#aa3c8cbb203ca8a7c7d7c021929f9179c',1,'Tgstation.Server.Host.Components.Watchdog.ExperimentalWatchdog.AlphaIsActive()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_watchdog_1_1_experimental_watchdog.html#a4f66d0dbde98c0e34ee6a57f7b7d2a78',1,'Tgstation.Server.Host.Components.Watchdog.ExperimentalWatchdog.alphaIsActive()']]], @@ -19,7 +19,7 @@ var searchData= ['apiversionheader',['ApiVersionHeader',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#a99649cb8fbf286c635b109e8373b415e',1,'Tgstation::Server::Api::ApiHeaders']]], ['applicationjson',['ApplicationJson',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#ab997bcc41eaf1aec06776846675e4569',1,'Tgstation::Server::Api::ApiHeaders']]], ['applicationlifetime',['applicationLifetime',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#ac7f7f7b49a557d032e3c42d5a9ff50c8',1,'Tgstation::Server::Host::Setup::SetupWizard']]], - ['assemblyinformationprovider',['AssemblyInformationProvider',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_application.html#a160a8959deda71c27f974a9c0321dd27',1,'Tgstation.Server.Host.Setup.SetupApplication.AssemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_command_factory.html#a0925181fde55747dca42c360486eb949',1,'Tgstation.Server.Host.Components.Chat.Commands.CommandFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_version_command.html#a2804521ce43e4dae665f0dfe31afa8c6',1,'Tgstation.Server.Host.Components.Chat.Commands.VersionCommand.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider_factory.html#af165dfbe55239bcb1d705bddbed7c01f',1,'Tgstation.Server.Host.Components.Chat.Providers.ProviderFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#ac44fead7dfc02bdd99aea10b729e82ed',1,'Tgstation.Server.Host.Components.InstanceFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#aa335c25ccb65f93242424c79396a29dc',1,'Tgstation.Server.Host.Components.InstanceManager.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#a8dbd5a414f211bbb1b0c63c5666568b8',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#a4e26166b0342015cc789ec8f897e3f65',1,'Tgstation.Server.Host.Controllers.AdministrationController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a36878504836ee1bd7b7a57f7e3d31192',1,'Tgstation.Server.Host.Controllers.HomeController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#aeb5ae966710833c1841de51f73cb2362',1,'Tgstation.Server.Host.Controllers.InstanceController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_git_hub_client_factory.html#a6ba17c2ceda75c9d153d15893712446f',1,'Tgstation.Server.Host.Core.GitHubClientFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_server_factory.html#a387f7728fad0a77333cbb190d18b400d',1,'Tgstation.Server.Host.ServerFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a5851a0d869fb5b874b39106af947c661',1,'Tgstation.Server.Host.Setup.SetupWizard.assemblyInformationProvider()']]], + ['assemblyinformationprovider',['assemblyInformationProvider',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_command_factory.html#a0925181fde55747dca42c360486eb949',1,'Tgstation.Server.Host.Components.Chat.Commands.CommandFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_commands_1_1_version_command.html#a2804521ce43e4dae665f0dfe31afa8c6',1,'Tgstation.Server.Host.Components.Chat.Commands.VersionCommand.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider_factory.html#af165dfbe55239bcb1d705bddbed7c01f',1,'Tgstation.Server.Host.Components.Chat.Providers.ProviderFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#ac44fead7dfc02bdd99aea10b729e82ed',1,'Tgstation.Server.Host.Components.InstanceFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#aa335c25ccb65f93242424c79396a29dc',1,'Tgstation.Server.Host.Components.InstanceManager.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_session_1_1_session_controller_factory.html#a8dbd5a414f211bbb1b0c63c5666568b8',1,'Tgstation.Server.Host.Components.Session.SessionControllerFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#a4e26166b0342015cc789ec8f897e3f65',1,'Tgstation.Server.Host.Controllers.AdministrationController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a36878504836ee1bd7b7a57f7e3d31192',1,'Tgstation.Server.Host.Controllers.HomeController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#aeb5ae966710833c1841de51f73cb2362',1,'Tgstation.Server.Host.Controllers.InstanceController.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_git_hub_client_factory.html#a6ba17c2ceda75c9d153d15893712446f',1,'Tgstation.Server.Host.Core.GitHubClientFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_server_factory.html#a387f7728fad0a77333cbb190d18b400d',1,'Tgstation.Server.Host.ServerFactory.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a5851a0d869fb5b874b39106af947c661',1,'Tgstation.Server.Host.Setup.SetupWizard.assemblyInformationProvider()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_application.html#a160a8959deda71c27f974a9c0321dd27',1,'Tgstation.Server.Host.Setup.SetupApplication.AssemblyInformationProvider()']]], ['assemblyname',['AssemblyName',['../class_tgstation_1_1_server_1_1_api_1_1_api_headers.html#acc064dcbf2348cfcf44d06acff499977',1,'Tgstation::Server::Api::ApiHeaders']]], ['asyncdelayer',['asyncDelayer',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager.html#a50f199a025fe456b6bb63c9eaa18a07a',1,'Tgstation.Server.Host.Components.Chat.ChatManager.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager_factory.html#aa5bf6331a8aa3e9587376c9574910e86',1,'Tgstation.Server.Host.Components.Chat.ChatManagerFactory.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_irc_provider.html#a154b7cf302e0d4b39d338846a851b898',1,'Tgstation.Server.Host.Components.Chat.Providers.IrcProvider.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_providers_1_1_provider_factory.html#a07009731bbb9ae0df5a4c315189b5ccb',1,'Tgstation.Server.Host.Components.Chat.Providers.ProviderFactory.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#ad6f992fed638980594fd5a6c45b8946b',1,'Tgstation.Server.Host.Components.InstanceManager.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_identity_cache.html#a9ae1ecab8e3826302aa8d692c0d659d1',1,'Tgstation.Server.Host.Security.IdentityCache.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#ad5a557224dd5d8eac2a46dec133509fb',1,'Tgstation.Server.Host.Security.TokenFactory.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#ae12ec09822e867ea3dd0f99e7357f329',1,'Tgstation.Server.Host.Setup.SetupWizard.asyncDelayer()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_windows_network_prompt_reaper.html#a0a1974764838ef42c9d2faa8dd8580b9',1,'Tgstation.Server.Host.System.WindowsNetworkPromptReaper.asyncDelayer()']]], ['authenticationcontextfactory',['authenticationContextFactory',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_claims_injector.html#a65b9d8be87a72903d74d266180612800',1,'Tgstation::Server::Host::Security::ClaimsInjector']]], diff --git a/search/variables_12.js b/search/variables_12.js index 6bbfa7d347..ea40c3a2cb 100644 --- a/search/variables_12.js +++ b/search/variables_12.js @@ -5,6 +5,7 @@ var searchData= ['section',['Section',['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_control_panel_configuration.html#adbbc553f0fd0f2eb4a69a8361c5a9c80',1,'Tgstation.Server.Host.Configuration.ControlPanelConfiguration.Section()'],['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_database_configuration.html#a3c3201b453ffd190158839da139e4a13',1,'Tgstation.Server.Host.Configuration.DatabaseConfiguration.Section()'],['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_file_logging_configuration.html#a8450ea7764591aa38857686d8fc70f93',1,'Tgstation.Server.Host.Configuration.FileLoggingConfiguration.Section()'],['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html#a04dc43bd8ff33f4a8a791f750f7eb1d9',1,'Tgstation.Server.Host.Configuration.GeneralConfiguration.Section()'],['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_updates_configuration.html#aeed3d84cee92ab5e7488d5c5b52493c7',1,'Tgstation.Server.Host.Configuration.UpdatesConfiguration.Section()']]], ['securestringlength',['SecureStringLength',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_cryptography_suite.html#a893d5725e799a79d7a3dd172f098edb8',1,'Tgstation::Server::Host::Security::CryptographySuite']]], ['semaphore',['semaphore',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_byond_manager.html#a192e5a69ce9c2300f588cc65af3a64f2',1,'Tgstation.Server.Host.Components.Byond.ByondManager.semaphore()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_byond_1_1_windows_byond_installer.html#ae6aa8519a6401fc95218f2de88080605',1,'Tgstation.Server.Host.Components.Byond.WindowsByondInstaller.semaphore()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository_manager.html#a550982dbdc2ce226e878702a520bbe27',1,'Tgstation.Server.Host.Components.Repository.RepositoryManager.semaphore()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#af64bb7e6efa9991a5a3a14993a3277e8',1,'Tgstation.Server.Host.Components.StaticFiles.Configuration.semaphore()']]], + ['semaphoreslim',['semaphoreSlim',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#ada5764977cc200cc19bb38db0b0fe1e4',1,'Tgstation::Server::Client::ApiClient']]], ['sendmessagecount',['SendMessageCount',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_windows_network_prompt_reaper.html#a6c18ae3a8fda9fa9973b0a8a396328f4',1,'Tgstation::Server::Host::System::WindowsNetworkPromptReaper']]], ['serializersettings',['SerializerSettings',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_interop_1_1_d_m_api_constants.html#a38c0e12f070e67fbdedfce3854c58d42',1,'Tgstation::Server::Host::Components::Interop::DMApiConstants']]], ['servercontrol',['serverControl',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_chat_1_1_chat_manager_factory.html#a6b86256a68eff2c15b42badd8159a2f0',1,'Tgstation.Server.Host.Components.Chat.ChatManagerFactory.serverControl()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#ac2ba10fddea5448f4c8c08f363a0e391',1,'Tgstation.Server.Host.Components.InstanceManager.serverControl()']]], diff --git a/search/variables_13.js b/search/variables_13.js index 32cd6d2bb3..dd4f15ef01 100644 --- a/search/variables_13.js +++ b/search/variables_13.js @@ -6,10 +6,11 @@ var searchData= ['timercts',['timerCts',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance.html#aab6ba11c7820ee9de39771aabbecdfcb',1,'Tgstation::Server::Host::Components::Instance']]], ['timerlock',['timerLock',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance.html#af352e8a195c6b95d852ef8873390c3a0',1,'Tgstation::Server::Host::Components::Instance']]], ['timertask',['timerTask',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance.html#abf825c028f20cd903db1362892dbd833',1,'Tgstation::Server::Host::Components::Instance']]], - ['token',['token',['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#a7137670591717642aa084370ed40377e',1,'Tgstation.Server.Client.ServerClient.token()'],['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_program_shutdown_token_source.html#a24771450ef1521a1c51fc0291e3c67a0',1,'Tgstation.Server.Host.System.ProgramShutdownTokenSource.Token()']]], + ['token',['Token',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_program_shutdown_token_source.html#a24771450ef1521a1c51fc0291e3c67a0',1,'Tgstation.Server.Host.System.ProgramShutdownTokenSource.Token()'],['../class_tgstation_1_1_server_1_1_client_1_1_server_client.html#a7137670591717642aa084370ed40377e',1,'Tgstation.Server.Client.ServerClient.token()']]], ['tokenclockskewminutes',['TokenClockSkewMinutes',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#abbf0169740f418e6ffd4f8c783fdff50',1,'Tgstation::Server::Host::Security::TokenFactory']]], ['tokenexpiryminutes',['TokenExpiryMinutes',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#a0cea7772b1f5107da73c23e821e76b43',1,'Tgstation::Server::Host::Security::TokenFactory']]], ['tokenfactory',['tokenFactory',['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#afd05899e85f5a8a6b8c8e374364872f8',1,'Tgstation.Server.Host.Controllers.HomeController.tokenFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_application.html#a93cb446fc8d8f84ea595abe709265398',1,'Tgstation.Server.Host.Core.Application.tokenFactory()']]], + ['tokenrefreshheaders',['tokenRefreshHeaders',['../class_tgstation_1_1_server_1_1_client_1_1_api_client.html#a5292a281f3ab688eecbf3df7feec400c',1,'Tgstation::Server::Client::ApiClient']]], ['tokensecurityschemeid',['TokenSecuritySchemeId',['../class_tgstation_1_1_server_1_1_host_1_1_core_1_1_swagger_configuration.html#a0fd6cdaa7f3d8b391f91b820bfea5d56',1,'Tgstation::Server::Host::Core::SwaggerConfiguration']]], ['tokensigningkeybyteamount',['TokenSigningKeyByteAmount',['../class_tgstation_1_1_server_1_1_host_1_1_security_1_1_token_factory.html#a1daafa97f229b3c342725fba60505b0f',1,'Tgstation::Server::Host::Security::TokenFactory']]], ['tokensourceaccesslock',['tokenSourceAccessLock',['../class_tgstation_1_1_server_1_1_host_1_1_system_1_1_program_shutdown_token_source.html#ac4a3842c9ed0356d2c2199a80c7820f5',1,'Tgstation::Server::Host::System::ProgramShutdownTokenSource']]], diff --git a/search/variables_3.js b/search/variables_3.js index 7c0f6c391f..1112d9c0b7 100644 --- a/search/variables_3.js +++ b/search/variables_3.js @@ -8,7 +8,7 @@ var searchData= ['databasetype',['DatabaseType',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_context.html#a5c74d013d5681ba0c9529380e5a12900',1,'Tgstation::Server::Host::Database::DatabaseContext']]], ['dbconnectionfactory',['dbConnectionFactory',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#aa4ea86a1328b845cac9d0fbeb15167fb',1,'Tgstation::Server::Host::Setup::SetupWizard']]], ['dbset',['dbSet',['../class_tgstation_1_1_server_1_1_host_1_1_database_1_1_database_collection.html#a9ded40bd536adb9602f0c3733409eef3',1,'Tgstation::Server::Host::Database::DatabaseCollection']]], - ['defaultadminpassword',['DefaultAdminPassword',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html#a6b47a29b6c75f6a51d8c7d0d4305e210',1,'Tgstation::Server::Api::Models::User']]], + ['defaultadminpassword',['DefaultAdminPassword',['../class_tgstation_1_1_server_1_1_api_1_1_models_1_1_user.html#a6d73f12df63e1cea9b3d011da9d9a14a',1,'Tgstation::Server::Api::Models::User']]], ['defaultbuffersize',['DefaultBufferSize',['../class_tgstation_1_1_server_1_1_host_1_1_i_o_1_1_default_i_o_manager.html#a8d302b2be6862b23b9a18a9412fc7fc9',1,'Tgstation::Server::Host::IO::DefaultIOManager']]], ['defaultbyondtopictimeout',['DefaultByondTopicTimeout',['../class_tgstation_1_1_server_1_1_host_1_1_configuration_1_1_general_configuration.html#a51c2f2b2e7faf729aad307050a6d8a6c',1,'Tgstation::Server::Host::Configuration::GeneralConfiguration']]], ['defaultchannellimit',['DefaultChannelLimit',['../class_tgstation_1_1_server_1_1_host_1_1_models_1_1_chat_bot.html#a9f693077cc6b7bca387dd46a08014c90',1,'Tgstation::Server::Host::Models::ChatBot']]], diff --git a/search/variables_6.js b/search/variables_6.js index df3aa5142c..3eb2a38a7e 100644 --- a/search/variables_6.js +++ b/search/variables_6.js @@ -1,7 +1,7 @@ var searchData= [ ['gamestaticfilessubdirectory',['GameStaticFilesSubdirectory',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_static_files_1_1_configuration.html#ae8e07adce00ed418cb29954182699623',1,'Tgstation::Server::Host::Components::StaticFiles::Configuration']]], - ['generalconfiguration',['GeneralConfiguration',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_post_setup_services.html#a454fd5fdcdd712c84d205389197cb33a',1,'Tgstation.Server.Host.Setup.PostSetupServices.GeneralConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#adb0b189a10ec43510b9f10cff91106fd',1,'Tgstation.Server.Host.Controllers.AdministrationController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a78f4994c9c4f2cc58d0a6d227e0195a9',1,'Tgstation.Server.Host.Controllers.HomeController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#a4f60f72c082a222a31383dbfceb755a6',1,'Tgstation.Server.Host.Controllers.InstanceController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#abe159da9c5fbc8eba3c3feff488988a0',1,'Tgstation.Server.Host.Controllers.RepositoryController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_user_controller.html#a79e1198432e7e557c8d72b3adf71bbf1',1,'Tgstation.Server.Host.Controllers.UserController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_server.html#a2823acb68609f9953788b1460ca8cdd3',1,'Tgstation.Server.Host.Server.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a47f6e49529ffb5b7760e1a5713f590e4',1,'Tgstation.Server.Host.Setup.SetupWizard.generalConfiguration()']]], + ['generalconfiguration',['generalConfiguration',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_manager.html#aaa2d14e2052d1ad81104ec0e5afa218e',1,'Tgstation.Server.Host.Components.InstanceManager.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#adb0b189a10ec43510b9f10cff91106fd',1,'Tgstation.Server.Host.Controllers.AdministrationController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_home_controller.html#a78f4994c9c4f2cc58d0a6d227e0195a9',1,'Tgstation.Server.Host.Controllers.HomeController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_instance_controller.html#a4f60f72c082a222a31383dbfceb755a6',1,'Tgstation.Server.Host.Controllers.InstanceController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#abe159da9c5fbc8eba3c3feff488988a0',1,'Tgstation.Server.Host.Controllers.RepositoryController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_user_controller.html#a79e1198432e7e557c8d72b3adf71bbf1',1,'Tgstation.Server.Host.Controllers.UserController.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_server.html#a2823acb68609f9953788b1460ca8cdd3',1,'Tgstation.Server.Host.Server.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_setup_wizard.html#a47f6e49529ffb5b7760e1a5713f590e4',1,'Tgstation.Server.Host.Setup.SetupWizard.generalConfiguration()'],['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_post_setup_services.html#a454fd5fdcdd712c84d205389197cb33a',1,'Tgstation.Server.Host.Setup.PostSetupServices.GeneralConfiguration()']]], ['generalconfigurationoptions',['generalConfigurationOptions',['../class_tgstation_1_1_server_1_1_host_1_1_setup_1_1_post_setup_services.html#a3ce96cf3d3ee5a94ef82ee90efcd5f07',1,'Tgstation::Server::Host::Setup::PostSetupServices']]], ['githubclientfactory',['gitHubClientFactory',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_deployment_1_1_dream_maker.html#aa837a722f9326dea2b1f63d6a2537bb7',1,'Tgstation.Server.Host.Components.Deployment.DreamMaker.gitHubClientFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_instance_factory.html#af9e2105dfde05caa1e5a14f8c3141591',1,'Tgstation.Server.Host.Components.InstanceFactory.gitHubClientFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_administration_controller.html#a40d72314455caeb1d6b72fa047ea18a4',1,'Tgstation.Server.Host.Controllers.AdministrationController.gitHubClientFactory()'],['../class_tgstation_1_1_server_1_1_host_1_1_controllers_1_1_repository_controller.html#a646263811f3cf4b618c569685a15c90c',1,'Tgstation.Server.Host.Controllers.RepositoryController.gitHubClientFactory()']]], ['githuburl',['GitHubUrl',['../class_tgstation_1_1_server_1_1_host_1_1_components_1_1_repository_1_1_repository.html#a5a72e4dafe281ae05f52e1dbcf37ab71',1,'Tgstation::Server::Host::Components::Repository::Repository']]],

    JCS0f$~ygiu0F(WW0-dC={e_AU4 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�CjEyk_&S~+uE=Zz_PkjcerC+=bzGP@v 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äI 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